Exemplo n.º 1
0
        public void Execute(object sender, DoWorkEventArgs e)
        {
            bw = (BackgroundWorker)sender;

            bw.ReportProgress(0, "Starting FMConverter");
            CCIUtilities.Log.writeToLog("Starting FMConverter on records in " + directory);

            /***** Read electrode file *****/
            ElectrodeInputFileStream etrFile = new ElectrodeInputFileStream(
                new FileStream(Path.Combine(directory, eventHeader.ElectrodeFile), FileMode.Open, FileAccess.Read));

            /***** Open FILMAN file *****/
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Save as FILMAN file ...";
            dlg.AddExtension = true;
            dlg.DefaultExt = ".fmn"; // Default file extension
            dlg.Filter = "FILMAN Files (.fmn)|*.fmn"; // Filter files by extension
            dlg.FileName=Path.GetFileNameWithoutExtension(eventHeader.BDFFile);
            Nullable<bool> result = dlg.ShowDialog();
            if (result == null || !(bool)result)
            {
                e.Result = new int[] { 0, 0 };
                return;
            }
            samplingRate = BDF.NSamp / BDF.RecordDuration;
            offsetInPts = Convert.ToInt32(offset * samplingRate);
            newRecordLength = Convert.ToInt32(Math.Ceiling(length * samplingRate / (float)decimation));

            FMStream = new FILMANOutputStream(
                File.Open(dlg.FileName, FileMode.Create, FileAccess.ReadWrite),
                GV.Count + 2, EDE.ancillarySize, channels.Count,
                newRecordLength,
                FILMANFileStream.FILMANFileStream.Format.Real);
            log = new LogFile(dlg.FileName + ".log.xml");
            FMStream.IS = Convert.ToInt32( (double)samplingRate/(double)decimation);
            bigBuff = new float[BDF.NumberOfChannels - 1, FMStream.ND]; //have to dimension to BDF rather than FMStream
                                                                        //in case we need for reference calculations

            /***** Create FILMAN header records *****/
            FMStream.GVNames(0, "Channel");
            FMStream.GVNames(1, "Montage");
            int i = 2;
            foreach (GVEntry gv in GV) FMStream.GVNames(i++, gv.Name); //generate group variable names

            for (i = 0; i < FMStream.NC; i++) //generate channel labels
            {
                string s = BDF.channelLabel(channels[i]);
                ElectrodeFileStream.ElectrodeRecord p;
                if (etrFile.etrPositions.TryGetValue(s, out p))   //add electrode location information, if available
                    FMStream.ChannelNames(i, s.PadRight(16, ' ') + p.projectPhiTheta().ToString("0"));
                else
                    FMStream.ChannelNames(i, s);
            }

            FMStream.Description(0, eventHeader.Title + " Date: " + eventHeader.Date + " " + eventHeader.Time);

            FMStream.Description(1, "File: " + Path.Combine(directory, Path.GetFileNameWithoutExtension(eventHeader.BDFFile)));

            StringBuilder sb = new StringBuilder("Subject: " + eventHeader.Subject.ToString());
            if (eventHeader.Agent != 0) sb.Append(" Agent: " + eventHeader.Agent);
            sb.Append(" Tech:");
            foreach (string s in eventHeader.Technician) sb.Append(" "  + s);
            FMStream.Description(2, sb.ToString());

            sb = new StringBuilder("Event="+EDE.Name);
            sb.Append(" Offset=" + offset.ToString("0.00"));
            sb.Append(" Length=" + length.ToString("0.00"));
            if (referenceGroups == null || referenceGroups.Count == 0) sb.Append(" No reference");
            else if (referenceGroups.Count == 1)
            {
                sb.Append(" Single ref group with");
                if (referenceGroups[0].Count >= FMStream.NC)
                    if (referenceChannels[0].Count == BDF.NumberOfChannels) sb.Append(" common average ref");
                    else if (referenceChannels[0].Count == 1)
                        sb.Append(" ref channel " + referenceChannels[0][0].ToString("0") + "=" + BDF.channelLabel(referenceChannels[0][0]));
                    else sb.Append(" multiple ref channels=" + referenceChannels[0].Count.ToString("0"));
            }
            else // complex reference expression
            {
                sb.Append(" Multiple reference groups=" + referenceGroups.Count.ToString("0"));
            }
            FMStream.Description(3, sb.ToString());

            sb = new StringBuilder("#Group vars=" + GV.Count.ToString("0"));
            if (anc) sb.Append(" Ancillary=" + FMStream.NA.ToString("0"));
            sb.Append(" #Channels=" + FMStream.NC.ToString("0"));
            sb.Append(" #Samples=" + FMStream.ND.ToString("0"));
            sb.Append(" Samp rate=" + FMStream.IS.ToString("0"));
            FMStream.Description(4, sb.ToString());

            FMStream.Description(5, BDF.LocalRecordingId);

            FMStream.writeHeader();

            log.registerHeader(this);

            /***** Open Event file for reading *****/
            EventFactory.Instance(eventHeader.Events); // set up the factory
            EventFileReader EventFR = new EventFileReader(
                new FileStream(Path.Combine(directory, eventHeader.EventFile), FileMode.Open, FileAccess.Read));

            BDFLoc stp = BDF.LocationFactory.New();
            if (!EDE.intrinsic)
                if (risingEdge) threshold = EDE.channelMin + (EDE.channelMax - EDE.channelMin) * threshold;
                else threshold = EDE.channelMax - (EDE.channelMax - EDE.channelMin) * threshold;

            nominalT = BDF.LocationFactory.New(); //nominal Event time based on Event.Time
            actualT = BDF.LocationFactory.New(); //actual Event time in Status channel
            //Note: these should be the same if the two clocks run the same rate (BioSemi DAQ and computer)
            /***** MAIN LOOP *****/
            foreach (InputEvent ie in EventFR) //Loop through Event file
            {
                bw.ReportProgress(0, "Processing event " + ie.Index.ToString("0")); //Report progress

                if (ie.Name == EDE.Name) // Event match found in Event file
                {
                    if (findEvent(ref stp, ie))
                        createFILMANRecord(stp, ie); //Create FILMAN recordset around this found point
                }
            }
            e.Result = new int[] { FMStream.NR, FMStream.NR / FMStream.NC };
            FMStream.Close();
            EventFR.Close();
            log.Close();
        }
Exemplo n.º 2
0
        public void Execute(object sender, DoWorkEventArgs e)
        {
            bw = (BackgroundWorker)sender;

            bw.ReportProgress(0, "Starting ASC conversion");
            CCIUtilities.Log.writeToLog("Started ASC conversion on records in " + headerFileName);

            /***** Read electrode file *****/
            ElectrodeInputFileStream etrFile = new ElectrodeInputFileStream(
                new FileStream(System.IO.Path.Combine(directory, head.ElectrodeFile), FileMode.Open, FileAccess.Read));

            /***** Open new FILMAN file *****/
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Save as FILMAN file ...";
            dlg.AddExtension = true;
            dlg.DefaultExt = ".fmn"; // Default file extension
            dlg.Filter = "FILMAN Files (.fmn)|*.fmn"; // Filter files by extension
            dlg.FileName = headerFileName;
            dlg.InitialDirectory = ASCtoFMConverter.Properties.Settings.Default.LastDataset; //Use dataset default, but don't save if new location
            bool? result = dlg.ShowDialog();
            if (result == null || !(bool)result)
            {
                bw.ReportProgress(0, "Conversion cancelled before FM file created.");
                e.Cancel = true;
                return;
            }
            int newRecordLength = Convert.ToInt32(Math.Ceiling(FMRecLength * samplingRate / (double)decimation));
            int FMRecordLengthInBDF = Convert.ToInt32(FMRecLength * samplingRate);

            int GVCount = 6 + GVCopyAcross.Count;
            bool PKCounterExists = false;
            foreach (EpisodeDescription ed in specs)
            {
                PKDetectorEventCounterDescription pkd = ed.PKCounter;
                if (pkd != null)
                {
                    PKCounterExists = true;
                    pkd.assignedGVNumber = GVCount;
                }
            }
            GVCount += PKCounterExists ? 3 : 0;

            //NOTE: because we have to have the number of GVs (and Channels) available when we create the output stream,
            // we have to separate the enumeration of GVs from the naming of GVs; this could be avoided by using lists
            // rather than arrays for GVNames in FILMANOuputStream and having an actual Header entity for FILMAN files;
            // one would then enter the GVNames (and Channel names) into the list before creating the ouput stream and have
            // the constructor use the counts to get NG and NC; NA could also be an array that must be created before as a
            // byte array, but this might be awkward

            FMStream = new FILMANOutputStream(
                File.Open(dlg.FileName, FileMode.Create, FileAccess.ReadWrite),
                GVCount, 0, channels.Count,
                newRecordLength,
                FILMANFileStream.FILMANFileStream.Format.Real);
            log = new LogFile(dlg.FileName + ".log.xml");
            FMStream.IS = Convert.ToInt32((double)samplingRate / (double)decimation);
            bigBuff = new double[bdf.NumberOfChannels - 1, FMStream.ND]; //have to dimension to BDF rather than FMStream
            //in case we need for reference calculations

            /***** Create FILMAN header records *****/

            //First GV names:
            //six for the standard generated
            FMStream.GVNames(0, "Channel");
            FMStream.GVNames(1, "Montage");
            FMStream.GVNames(2, "NewGroupVariable");
            FMStream.GVNames(3, "EpisodeNumber");
            FMStream.GVNames(4, "EpisodeRecordNumber");
            FMStream.GVNames(5, "SecondsFromStart");
            //then the copied-across GVs
            for (int n = 0; n < GVCopyAcross.Count; n++) FMStream.GVNames(n + 6, GVCopyAcross[n].Name);
            //and last, the GVs from the counters
            if (PKCounterExists) //if there are any PK counters, we'll need their GVs
            {
                FMStream.GVNames(GVCount - 3, "PK-rate");
                FMStream.GVNames(GVCount - 2, "PK-velocity");
                FMStream.GVNames(GVCount - 1, "PK-accel");
            }

            for (int j = 0; j < FMStream.NC; j++) //generate channel labels
            {
                string s = bdf.channelLabel(channels[j]);
                ElectrodeFileStream.ElectrodeRecord p;
                if (etrFile.etrPositions.TryGetValue(s, out p))
                    FMStream.ChannelNames(j, s.PadRight(16, ' ') + p.projectPhiTheta().ToString("0")); //add electrode location information, if available
                else
                    FMStream.ChannelNames(j, s);
            }

            FMStream.Description(0, head.Title + " Date: " + head.Date + " " + head.Time +
                " File: " + System.IO.Path.Combine(directory, System.IO.Path.GetFileNameWithoutExtension(head.BDFFile)));

            StringBuilder sb = new StringBuilder("Subject: " + head.Subject.ToString());
            if (head.Agent != 0) sb.Append(" Agent: " + head.Agent);
            sb.Append(" Tech:");
            foreach (string s in head.Technician) sb.Append(" " + s);
            FMStream.Description(1, sb.ToString());
            sb.Clear();
            sb = sb.Append(specs[0].ToString());
            for (int j = 1; j < specs.Length; j++) sb.Append("/ " + specs[j].ToString());
            string str = sb.ToString();
            int sl = str.Length;
            int k;
            if (sl < 72) { FMStream.Description(2, str); k = 3; }
            else
            {
                FMStream.Description(2, str.Substring(0, 72));
                if (sl < 144) { FMStream.Description(3, str.Substring(72)); k = 4; }
                else
                {
                    FMStream.Description(3, str.Substring(72, 72));
                    k = 5;
                    if (sl < 216) FMStream.Description(4, str.Substring(144));
                    else FMStream.Description(4, str.Substring(144, 72));
                }
            }
            sb.Clear();
            if (referenceGroups == null || referenceGroups.Count == 0) sb.Append("No reference");
            else if (referenceGroups.Count == 1)
            {
                sb.Append("Single ref group with");
                if (referenceGroups[0].Count >= FMStream.NC)
                    if (referenceChannels[0].Count == bdf.NumberOfChannels) sb.Append(" common average ref");
                    else if (referenceChannels[0].Count == 1)
                        sb.Append(" ref channel " + referenceChannels[0][0].ToString("0") + "=" + bdf.channelLabel(referenceChannels[0][0]));
                    else sb.Append(" multiple ref channels=" + referenceChannels[0].Count.ToString("0"));
            }
            else // complex reference expression
            {
                sb.Append(" Multiple reference groups=" + referenceGroups.Count.ToString("0"));
            }
            FMStream.Description(k++, sb.ToString());

            if (k < 6)
                FMStream.Description(k, bdf.LocalRecordingId);

            FMStream.writeHeader();

            log.registerHeader(this);

            EventFactory.Instance(ED);

            int epiNo = 0; //found episode counter

            //read in list of Events
            bw.ReportProgress(0, "Reading Events, synchronizing clocks, and calculating Event offsets from BDF file");
            List<InputEvent> EventList = new List<InputEvent>();
            EventFileReader efr=new EventFileReader(
                    new FileStream(System.IO.Path.Combine(directory, head.EventFile),
                    FileMode.Open, FileAccess.Read));
            InputEvent.LinkEventsToDataset(head, bdf); //link InputEvents to this specific dataset
            foreach (InputEvent ie in efr)
            {
                EventList.Add(ie);
            }
            IEnumerator<InputEvent> EventEnumerator = EventList.GetEnumerator(); //Enumerator for stepping through Event file

             //******** Synchronize clocks
            //Need to synchronize clocks by setting the BDF.zeroTime value
            //zeroTime is the time, according to the Event file clock, of the beginning of the BDF file (BioSemi clock)
            if (ignoreStatus && offsetToFirstEvent < 0D) //cannot use Status markers to synchronize clocks, so
                //use raw Event clock times as actual offsets from beginning of BDF file; in other words force all Events to be BDF-based
                bdf.setZeroTime(0D); //this keeps it from throwing an error when "synchronizing"
            else
            { //Need to find a covered (intrisic or extrinsic) Event to use as an indicial Event
                bool found = false;
                EventEnumerator.Reset();
                if (syncToFirst || ignoreStatus)
                    while (!found && EventEnumerator.MoveNext())
                    {
                        if (EventEnumerator.Current.EDE.IsCovered) //have we found a covered Event?
                        {
                            if (ignoreStatus)
                            {
                                bdf.setZeroTime(EventEnumerator.Current.Time - offsetToFirstEvent);
                                found = true;
                            }
                            else
                                found = bdf.setZeroTime(EventEnumerator.Current);
                        }
                    }
                else //sync to "middle" Event
                {
                    int midRecord = bdf.NumberOfRecords / 2;
                    BDFLocFactory fac = new BDFLocFactory(bdf);
                    BDFLoc loc = fac.New();
                    loc.Rec = midRecord;
                    bdf.read(midRecord);
                    uint v1 = head.Mask & (uint)bdf.getStatusSample(loc);
                    uint v2;
                    InputEvent IE;
                    while ((v2 = (uint)bdf.getStatusSample(++loc) & head.Mask) == v1) ; //find next Status mark after mid point of file; v2 = GC
                    while (!found && EventEnumerator.MoveNext())
                    {

                        IE = EventEnumerator.Current;
                        if (IE.GC == v2)
                        {
                            bdf.setZeroTime(IE.Time - loc.ToSecs());
                            found = true;
                        }
                    }
                }
                if (!found)
                {
                    log.Close();
                    FMStream.Close();
                    throw (new Exception("No valid synchronizing (covered) Event found; use manual synchronization"));
                }
            }
            Log.writeToLog("\tinto FM file " + dlg.FileName);

            foreach(InputEvent ie in EventList) //modify relativeTime field depending on type of Event
                ie.setRelativeTime();

            EventList = EventList.OrderBy(ev => ev.relativeTime).ToList(); //re-sort: minor order changes may occur

            //Loop through each episode specification,
            // then through the Event file to find any regions satisfying the specification,
            // then create FILMAN records for each of these regions

             //******** Episode specification loop
            for (int i = 0; i < specs.Length; i++)
            {
                EpisodeDescription currentEpisode = specs[i];
                FMStream.record.GV[2] = currentEpisode.GVValue; //set epispec GV value

                if (currentEpisode.Exclude != null)
                {
                    //Here we complete the ExclusionDescription for the given Episode specification
                    //by finding all the segemnts that must be excluded and
                    //calculating their From to To BDFPoints; this is done for each specification to
                    //permit different exclusion criteria for each EpisodeDescription

                    ExclusionDescription ed = currentEpisode.Exclude;
                    EventEnumerator.Reset();
                    EventDictionaryEntry startEDE = ed.startEvent;
                    bool t = ed.endEvent != null && ed.endEvent.GetType() == typeof(EventDictionaryEntry);
                    EventDictionaryEntry endEDE = null;
                    if(t)
                        endEDE = (EventDictionaryEntry)ed.endEvent;
                    while(EventEnumerator.MoveNext())
                    {
                        InputEvent ev = EventEnumerator.Current;
                        if (ev.Name == startEDE.Name)
                        {
                            BDFPoint b = new BDFPoint(bdf).FromSecs(bdf.timeFromBeginningOfFileTo(ev));
                            ed.From.Add(b);
                            if (t)
                                while (EventEnumerator.MoveNext())
                                {
                                    ev = EventEnumerator.Current;
                                    if (ev.Name == endEDE.Name)
                                    {
                                        ed.To.Add(new BDFPoint(bdf).FromSecs(bdf.timeFromBeginningOfFileTo(ev)));
                                        break;
                                    }
                                }
                            else
                                ed.To.Add(b);
                        }
                    }
                }

                // From here we loop through Event file until an Event is found that matches the
                // current startEvent in spec[i]; from that point a matching endEvent is sought;
                // episode is then processed; note that this implies that overlapping episodes are not
                // generally permitted (in a given specification) except when caused by offsets.

            //************* Event file loop
                EventEnumerator.Reset();
                bool found;

                do
                {
                    if (bw.CancellationPending) //look for cancellation first
                    {
                        bw.ReportProgress(0, "Conversion canceled with " + FMStream.NR.ToString("0") +
                            " records in " + (FMStream.NR / FMStream.NC).ToString("0") + " recordsets generated.");
                        FMStream.Close();
                        log.Close();
                        e.Cancel = true;
                        return;
                    }
                    InputEvent startEvent = null;
                    InputEvent endEvent = null;
                    double startTime;
                    double endTime = 0;

                    found = findNextMark(currentEpisode.Start, EventEnumerator, true, out startTime, out startEvent) &&
                        findNextMark(currentEpisode.End, EventEnumerator, false, out endTime, out endEvent);

                    if (found || startTime >= 0D && specs[i].useEOF)
                    { //use EOF
                        //***************** FILMAN record loop

                        startTime += currentEpisode.Start._offset;
                        endTime += currentEpisode.End._offset;
                        bw.ReportProgress(0, "Found episode " + (++epiNo).ToString("0") +
                            " from " + startTime.ToString("0.000") +
                            " to " + endTime.ToString("0.000"));
                        int maxNumberOfFMRecs = (int)Math.Floor((endTime - startTime) / FMRecLength);
                        log.openFoundEpisode(epiNo, startTime, endTime, maxNumberOfFMRecs);

                        BDFPoint startBDFPoint = new BDFPoint(bdf);
                        startBDFPoint.FromSecs(startTime);
                        BDFPoint endBDFPoint = new BDFPoint(startBDFPoint);

                        /***** Get group variables for this record *****/
                        FMStream.record.GV[3] = epiNo;
                        if (startEvent != null) //exclude BOF
                        {
                            int GrVar = 6; //Load up group variables, based on the start Event
                            foreach (GVEntry gve in GVCopyAcross)
                            {
                                int j = startEvent.GetIntValueForGVName(gve.Name);
                                FMStream.record.GV[GrVar++] = j < 0 ? 0 : j; //use zero to indicate "No value"
                            }
                        }

                        /***** Process each FILMAN record *****/
                        int actualNumberOfFMRecs = 0;
                        for (int rec = 1; rec <= maxNumberOfFMRecs; rec++)
                        {
                            endBDFPoint += FMRecordLengthInBDF; //update end point
                            if (currentEpisode.Exclude == null || !currentEpisode.Exclude.IsExcluded(startBDFPoint, endBDFPoint)) //is not excluded:
                            {
                                FMStream.record.GV[4] = ++actualNumberOfFMRecs; //Record number in this episode
                                FMStream.record.GV[5] = Convert.ToInt32(Math.Ceiling(startBDFPoint.ToSecs())); //Approximate seconds since start of BDF file

                                //calculate start and end times for this record: use BDFPoint values to assure accuracy;
                                //avoids problem if FMRecordLength is not exactly represented in double
                                startTime = startBDFPoint.ToSecs();
                                endTime = endBDFPoint.ToSecs();

                                //*****Count PK Events in this record*****
                                if (PKCounterExists)
                                {
                                    for (int j = GVCount - 3; j < GVCount; j++) FMStream.record.GV[j] = 0; //zero out as default
                                    if(currentEpisode.PKCounter!=null)
                                    {
                                        PKDetectorEventCounterDescription pkd = currentEpisode.PKCounter;
                                        double[] v = pkd.countMatchingEvents(startTime, startTime + FMRecLength, EventList);
                                        FMStream.record.GV[GVCount - 3] = Convert.ToInt32(1000D * v[0]); //make per thousand to nave useful integer
                                        FMStream.record.GV[GVCount - 2] = Convert.ToInt32(v[1]);
                                        FMStream.record.GV[GVCount - 1] = Convert.ToInt32(v[2]);
                                    }
                                }

                                createFILMANRecord(startBDFPoint, endBDFPoint);
                            }
                            startBDFPoint = endBDFPoint; //move start point forward
                        }
                        log.closeFoundEpisode(actualNumberOfFMRecs);
                    }

                } while (found && !(currentEpisode.Start.MatchesType("Beginning of file") == true)); //next Event, if any

            }  //next spec

            e.Result = new int[] { FMStream.NR, FMStream.NR / FMStream.NC };
            FMStream.Close();
            log.Close();
            Log.writeToLog("Completed ASC conversion with " + FMStream.NR.ToString("0") + " FM records created");
        }
Exemplo n.º 3
0
        public void Execute(object sender, DoWorkEventArgs e)
        {
            bw = (BackgroundWorker)sender;

            bw.ReportProgress(0, "Starting FMConverter");
            CCIUtilities.Log.writeToLog("Starting FMConverter on records in " + Path.Combine(directory, FileName));

            /***** Open FILMAN file *****/
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Save as FILMAN file ...";
            dlg.AddExtension = true;
            dlg.DefaultExt = ".fmn"; // Default file extension
            dlg.Filter = "FILMAN Files (.fmn)|*.fmn"; // Filter files by extension
            dlg.FileName = FileName;
            bool? result = dlg.ShowDialog();
            if (result == null || !(bool)result)
            {
                e.Result = new int[] { 0, 0 };
                return;
            }
            newRecordLengthPts = oldRecordLengthPts / decimation;

            FMStream = new FILMANOutputStream(
                File.Open(dlg.FileName, FileMode.Create, FileAccess.ReadWrite),
                3, 0, channels.Count,
                newRecordLengthPts,
                FILMANFileStream.FILMANFileStream.Format.Real);
            log = new LogFile(dlg.FileName + ".log.xml", GVMapElements);
            FMStream.IS = Convert.ToInt32((double)newRecordLengthPts / newRecordLengthSec); //rounding method
            bigBuff = new float[edfPlus.NumberOfChannels - 1, FMStream.ND]; //have to dimension to BDF rather than FMStream
            //in case we need for reference calculations

            /***** Create FILMAN header records *****/
            FMStream.GVNames(0, "Channel");
            FMStream.GVNames(1, "Montage");
            FMStream.GVNames(2, GVName);

            for (int i = 0; i < FMStream.NC; i++) //copy across channel labels
            {
                string s = edfPlus.channelLabel(channels[i]);
                FMStream.ChannelNames(i, s);
            }

            FMStream.Description(0, " Date: " + edfPlus.timeOfRecording().ToShortDateString() +
                " Time: " + edfPlus.timeOfRecording().ToShortTimeString());

            FMStream.Description(1, "Based on file: " + Path.Combine(directory, FileName));

            FMStream.Description(2, edfPlus.LocalSubjectId);

            StringBuilder sb = new StringBuilder(" Offset=" + offset.ToString("0.00"));
            sb.Append(" Length=" + newRecordLengthSec.ToString("0.000"));
            if (referenceGroups == null || referenceGroups.Count == 0) sb.Append(" No reference");
            else if (referenceGroups.Count == 1)
            {
                sb.Append(" Single ref group with");
                if (referenceGroups[0].Count >= FMStream.NC)
                    if (referenceChannels[0].Count == edfPlus.NumberOfChannels) sb.Append(" common average ref");
                    else if (referenceChannels[0].Count == 1)
                        sb.Append(" ref channel " + referenceChannels[0][0].ToString("0") + "=" + edfPlus.channelLabel(referenceChannels[0][0]));
                    else sb.Append(" multiple ref channels=" + referenceChannels[0].Count.ToString("0"));
            }
            else // complex reference expression
            {
                sb.Append(" Multiple reference groups=" + referenceGroups.Count.ToString("0"));
            }
            FMStream.Description(3, sb.ToString());

            sb = new StringBuilder("#Group var=" + GVName);
            sb.Append(" #Channels=" + FMStream.NC.ToString("0"));
            sb.Append(" #Samples=" + FMStream.ND.ToString("0"));
            sb.Append(" Samp rate=" + FMStream.IS.ToString("0"));
            FMStream.Description(4, sb.ToString());

            FMStream.Description(5, edfPlus.LocalRecordingId);

            FMStream.writeHeader();

            log.registerHeader(this);

            BDFLoc stp = edfPlus.LocationFactory.New();
            BDFLoc end = edfPlus.LocationFactory.New();
            /***** MAIN LOOP *****/
            for (int ev = 0; ev < Events.Count; ev++) //Loop through Events list
            {
                EventMark currentEvent = Events[ev];
                //check to make sure we haven't deleted this event type from GV map
                GVMapElement gv = currentEvent.GV;
                if (GVMapElements.Contains(gv))
                {
                    stp.FromSecs(currentEvent.Time + offset);
                    currentGVValue = gv.Value; //map to integer
                    if (ev < (Events.Count - 1))
                        end.FromSecs(Events[ev + 1].Time + offset);
                    else
                        end.EOF();
                    bw.ReportProgress(0, "Processing event at " + currentEvent.Time.ToString("0.000")); //Report progress

                    int n = 0;
                    while (createFILMANRecord(ref stp, end)) n++; /*Create FILMAN recordset around this found point*/
                    gv.RecordCount += n;
                    log.registerEvent(currentEvent, offset, n);
                }
            }
            int recs = FMStream.NR / FMStream.NC;
            e.Result = new int[] { FMStream.NR, recs };
            FMStream.Close();
            CCIUtilities.Log.writeToLog("Ending FMConverter, producing " + recs.ToString("0") + " records in file " + dlg.FileName);
            log.registerSummary(GVMapElements, recs);
            log.Close();
        }