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"); }
public MainWindow() { bool r; do //first get HDR file and open BDF file { OpenFileDialog dlg = new OpenFileDialog(); dlg.Title = "Open Header of dataset to be edited..."; dlg.DefaultExt = ".hdr"; // Default file extension dlg.Filter = "HDR Files (.hdr)|*.hdr"; // Filter files by extension Nullable<bool> result = dlg.ShowDialog(); if (result == null || result == false) Environment.Exit(0); directory = System.IO.Path.GetDirectoryName(dlg.FileName); //will use to find other files in dataset headerFileName = System.IO.Path.GetFileNameWithoutExtension(dlg.FileName); header = (new HeaderFileReader(dlg.OpenFile())).read(); updateFlag = header.Events.ContainsKey("**ArtifactBegin"); //indicates that we are editing a dataset that has already been edited for artifacts bdf = new BDFEDFFileReader(new FileStream(System.IO.Path.Combine(directory, header.BDFFile), FileMode.Open, FileAccess.Read)); //make list of candidate EEG channels, so that channel selection window can use it BDFLength = (double)bdf.NumberOfRecords * bdf.RecordDuration; string trans = bdf.transducer(0); //here we assumne that channel 0 is an EEG channel for (int i = 0; i < bdf.NumberOfChannels - 1; i++) //exclude Status channel if (bdf.transducer(i) == trans) EEGChannels.Add(i); Window1 w = new Window1(this); //open channel selection and file approval window r = (bool)w.ShowDialog(); } while (r == false); InitializeComponent(); Log.writeToLog("Starting EEGArtifactEditor " + Assembly.GetExecutingAssembly().GetName().Version.ToString() + " on dataset " + headerFileName); ViewerGrid.Width = BDFLength; try { //process Events in current dataset; we have to do this now in case this is an update situation, but don't have to when we finish Event.EventFactory.Instance(header.Events); // set up the factory, based on this Event dictionary //and read them in EventFileReader efr = new EventFileReader( new FileStream(System.IO.Path.Combine(directory, header.EventFile), FileMode.Open, FileAccess.Read)); // open Event file foreach (InputEvent ie in efr)// read in all Events into list events.Add(new OutputEvent(ie)); efr.Close(); //now events is list of Events in the dataset //now set zeroTime for this BDF file, after finding an appropriate (non-"naked") Event bool ok = false; foreach (OutputEvent ev in events) if (header.Events[ev.Name].IsCovered) { bdf.setZeroTime(ev); ok = true; break; } if (!ok) { ErrorWindow ew = new ErrorWindow(); ew.Message = "Unable to find a covered Event in this dataset on which to synchronize clocks. Exiting."; ew.ShowDialog(); Log.writeToLog("Unable to synchronize clocks."); this.Close(); } if (updateFlag) //re-editing this dataset for artifacts { Event.EventFactory.Instance(header.Events); // set up the factory, based on this Event dictionary //and read them in OutputEvent ev; int i = 0; double left; while (i < events.Count) { ev = events[i]; if (ev.Name == "**ArtifactBegin") { left = bdf.timeFromBeginningOfFileTo(ev); events.Remove(ev); while (i < events.Count) { ev = events[i]; if (ev.Name == "**ArtifactEnd") { MarkerCanvas.createMarkRegion(left, bdf.timeFromBeginningOfFileTo(ev)); events.Remove(ev); break; } if (ev.Name == "**ArtifactBegin") throw (new Exception("Unmatched artifact Event in Event file " + System.IO.Path.Combine(directory, header.EventFile))); i++; } } else i++; } } //initialize the individual channel canvases foreach (int chan in selectedEEGChannels) { ChannelCanvas cc = new ChannelCanvas(this, chan); currentChannelList.Add(cc); ViewerCanvas.Children.Add(cc); ViewerCanvas.Children.Add(cc.offScaleRegions); } Title = headerFileName; //set window title BDFFileInfo.Content = bdf.ToString(); HDRFileInfo.Content = header.ToString(); ElectrodeInputFileStream eif = new ElectrodeInputFileStream( new FileStream(System.IO.Path.Combine(directory, header.ElectrodeFile), FileMode.Open, FileAccess.Read)); //open Electrode file electrodes = eif.etrPositions; //initialize vertical gridline array; never more than 18 for (int i = 0; i < 18; i++) { Line l = new Line(); Grid.SetRow(l, 0); Grid.SetColumn(l, 0); l.Y1 = 0D; l.HorizontalAlignment = HorizontalAlignment.Left; l.VerticalAlignment = VerticalAlignment.Stretch; l.IsHitTestVisible = false; l.Stroke = Brushes.LightBlue; l.Visibility = Visibility.Hidden; Panel.SetZIndex(l, int.MaxValue); VerticalGrid.Children.Add(l); gridlines[i] = l; } //Initialize timer timer.AutoReset = true; timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); //Initialize channel information popup Color c1 = Color.FromArgb(0xFF, 0xF8, 0xF8, 0xF8); Color c2 = Color.FromArgb(0xFF, 0xC8, 0xC8, 0xC8); popupTB.Background = new LinearGradientBrush(c1, c2, 45D); popupTB.Foreground = Brushes.Black; popupTB.Padding = new Thickness(4D); Border b = new Border(); b.BorderThickness = new Thickness(1); b.CornerRadius = new CornerRadius(4); b.BorderBrush = Brushes.Tomato; b.Margin = new Thickness(0, 0, 24, 24); //allows drop shadow to show up b.Effect = new DropShadowEffect(); b.Child = popupTB; channelPopup.Placement = PlacementMode.MousePoint; channelPopup.AllowsTransparency = true; channelPopup.Child = b; //Initialize FOV slider FOV.Maximum = Math.Log10(BDFLength); FOV.Value = 1D; FOVMax.Text = BDFLength.ToString("0"); //Note file always uses the "main" orginal dataset name, which should be the BDFFile name; this keeps notes from all levels of //processing in the same place noteFilePath = System.IO.Path.Combine(directory, System.IO.Path.GetFileNameWithoutExtension(header.BDFFile) + ".notes.txt"); } catch (Exception ex) { ErrorWindow ew = new ErrorWindow(); ew.Message = "Error in EEGArtifactEditor initialization" + ex.Message; ew.ShowDialog(); this.Close(); //exit } //from here on the program is GUI-event driven }
public void Execute(object sender, DoWorkEventArgs e) { bw = (BackgroundWorker)sender; bw.ReportProgress(0, "Starting BDFConverter"); CCIUtilities.Log.writeToLog("Starting BDFConverter on records in " + directory); /***** Read electrode file *****/ ElectrodeInputFileStream etrFile = new ElectrodeInputFileStream( new FileStream(Path.Combine(directory, eventHeader.ElectrodeFile), FileMode.Open, FileAccess.Read)); /***** Open BDF file *****/ Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog(); dlg.Title = "Save as BDF file ..."; dlg.AddExtension = true; dlg.DefaultExt = ".bdf"; // Default file extension dlg.Filter = "BDF Files (.bdf)|*.bdf"; // Filter files by extension dlg.FileName = Path.GetFileNameWithoutExtension(eventHeader.BDFFile); bool? result = dlg.ShowDialog(); if (result == false) { e.Result = new int[] { 0, 0 }; return; } samplingRate = BDF.NSamp / BDF.RecordDuration; //Old sampling rate; exact as number of samples and duration are always an exact multiple oldOffsetInPts = Convert.ToInt32(offset * samplingRate); int newSamplingRate = (int)((double)samplingRate / (double)decimation + 0.5); //Make best estimate possible with integers newOffsetInPts = Convert.ToInt32(offset * newSamplingRate); newRecordLength = length * newSamplingRate; //new record length must be exact multiple of the sampling rate in BDF newStatus = new int[newRecordLength]; status = new int[BDF.NSamp]; GV0 = GV[0]; BDFWriter = new BDFEDFFileWriter(File.Open(dlg.FileName, FileMode.Create, FileAccess.ReadWrite), channels.Count + 1, /* Extra channel will have group variable value in it */ length, /* Record length in seconds, must be integer */ newSamplingRate, true); log = new LogFile(dlg.FileName + ".log.xml"); bigBuff = new float[BDF.NumberOfChannels - 1, newRecordLength]; //have to dimension to old channels rather than new //in case we need for reference calculations later /***** Create BDF header record *****/ BDFWriter.LocalRecordingId = BDF.LocalRecordingId; BDFWriter.LocalSubjectId = BDF.LocalSubjectId; int chan; for (int i = 0; i < channels.Count; i++) { chan = channels[i]; BDFWriter.channelLabel(i, BDF.channelLabel(chan)); BDFWriter.transducer(i, BDF.transducer(chan)); BDFWriter.dimension(i, BDF.dimension(chan)); BDFWriter.pMax(i, BDF.pMax(chan)); BDFWriter.pMin(i, BDF.pMin(chan)); BDFWriter.dMax(i, BDF.dMax(chan)); BDFWriter.dMin(i, BDF.dMin(chan)); BDFWriter.prefilter(i, BDF.prefilter(chan)); } chan = channels.Count; BDFWriter.channelLabel(chan, GV0.Name); //Make entries for old Status channel BDFWriter.transducer(chan, "None"); BDFWriter.dimension(chan, ""); BDFWriter.pMax(chan, 262143); BDFWriter.pMin(chan, -262144); BDFWriter.dMax(chan, 262143); BDFWriter.dMin(chan, -262144); BDFWriter.prefilter(chan, "None"); 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(); BDFLoc lastEvent = BDF.LocationFactory.New(); if (!EDE.intrinsic) //set threshold 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 (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)) if (allSamps) //this is a continuous copy, not Event generated episodic conversion { runBDFtoEvent(lastEvent, ref stp, ie); lastEvent = BDF.LocationFactory.New(); } else createBDFRecord(stp, ie); //Create BDF recordset around this point; i.e. Event generated episodic conversion } } if (allSamps) //copy out to end of file { stp.Rec = BDF.NumberOfRecords; stp.Pt = 0; runBDFtoEvent(lastEvent, ref stp, null); } e.Result = new int[] { BDFWriter.NumberOfRecords, BDFWriter.NumberOfRecords }; BDFWriter.Close(); EventFR.Close(); log.Close(); }
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(); }
public MainWindow() { Log.writeToLog("Starting DatasetReviewer " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); do { bool r; do { System.Windows.Forms.OpenFileDialog dlg = new System.Windows.Forms.OpenFileDialog(); dlg.Title = "Open Header file to be displayed..."; dlg.DefaultExt = ".hdr"; // Default file extension dlg.Filter = "HDR Files (.hdr)|*.hdr"; // Filter files by extension bool result = dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK; if (!result) Environment.Exit(0); directory = System.IO.Path.GetDirectoryName(dlg.FileName); //will use to find other files in dataset headerFileName = System.IO.Path.GetFileNameWithoutExtension(dlg.FileName); try { head = (new HeaderFileReader(dlg.OpenFile())).read(); } catch (Exception e) { r = false; //loop around again ErrorWindow ew = new ErrorWindow(); ew.Message = "Error reading Header file: " + e.Message; continue; } ED = head.Events; try { bdf = new BDFEDFFileStream.BDFEDFFileReader( new FileStream(System.IO.Path.Combine(directory, head.BDFFile), FileMode.Open, FileAccess.Read)); } catch (Exception e) { r = false; //loop around again ErrorWindow ew = new ErrorWindow(); ew.Message = "Error reading BDF file header: " + e.Message; ew.ShowDialog(); continue; } BDFLength = (double)bdf.NumberOfRecords * bdf.RecordDurationDouble; Window1 w = new Window1(this); r = (bool)w.ShowDialog(); } while (r == false); Log.writeToLog(" on dataset " + headerFileName); if (includeANAs) { foreach (EventDictionaryEntry ede in ED.Values) // add ANA channels that are referenced by extrinsic Events { if (ede.IsCovered && ede.IsExtrinsic) { int chan = bdf.ChannelNumberFromLabel(ede.channelName); if (!channelList.Contains(chan)) //don't enter duplicate channelList.Add(chan); } } } } while (channelList.Count == 0); InitializeComponent(); //initialize the individual channel graphs foreach (int i in channelList) { ChannelGraph pg = new ChannelGraph(this, i); GraphCanvas.Children.Add(pg); } Title = headerFileName; //set window title BDFFileInfo.Content = bdf.ToString(); HDRFileInfo.Content = head.ToString(); Event.EventFactory.Instance(head.Events); // set up the factory EventFileReader efr = null; try { efr = new EventFileReader( new FileStream(System.IO.Path.Combine(directory, head.EventFile), FileMode.Open, FileAccess.Read)); // open Event file bool z = false; foreach (Event.InputEvent ie in efr)// read in all Events into dictionary { if (ie.IsNaked) events.Add(ie); else if (events.Count(e => e.GC == ie.GC) == 0) //quietly skip duplicates { if (!z) //use first found covered Event to synchronize clocks z = bdf.setZeroTime(ie); events.Add(ie); } } efr.Close(); //now events is Dictionary of Events in the dataset; lookup by GC } catch (Exception e) { ErrorWindow ew = new ErrorWindow(); ew.Message = "Error reading Event file : " + e.Message + ". Exiting DatasetReviewer."; ew.ShowDialog(); Log.writeToLog(Environment.NewLine + e.StackTrace); Environment.Exit(0); } try { ElectrodeInputFileStream eif = new ElectrodeInputFileStream( new FileStream(System.IO.Path.Combine(directory, head.ElectrodeFile), FileMode.Open, FileAccess.Read)); //open Electrode file electrodes = eif.etrPositions; //read 'em in } catch (Exception e) { ErrorWindow ew = new ErrorWindow(); ew.Message = "Error reading Electrode file : " + e.Message + ". Exitting DatasetReviewer."; ew.ShowDialog(); Log.writeToLog(Environment.NewLine + e.StackTrace); Environment.Exit(0); } EventMarkers.Width = BDFLength; eventTB = new TextBlock(new Run("Events")); Canvas.SetBottom(eventTB, ScrollBarSize + 13D); //initialize gridline array for (int i = 0; i < 18; i++) { Line l = new Line(); Grid.SetRow(l, 0); Grid.SetColumn(l, 0); Grid.SetColumnSpan(l, 2); l.Y1 = 0D; l.HorizontalAlignment = HorizontalAlignment.Left; l.VerticalAlignment = VerticalAlignment.Stretch; l.IsHitTestVisible = false; l.Stroke = Brushes.LightBlue; l.Visibility = Visibility.Hidden; Panel.SetZIndex(l, int.MinValue); MainFrame.Children.Add(l); gridlines[i] = l; } //Initialize timer timer.AutoReset = true; timer.Elapsed += new ElapsedEventHandler(timer_Elapsed); //Initialize channel information popup Color c1 = Color.FromArgb(0xFF, 0xF8, 0xF8, 0xF8); Color c2 = Color.FromArgb(0xFF, 0xC8, 0xC8, 0xC8); popupTB.Background = new LinearGradientBrush(c1, c2, 45D); popupTB.Foreground = Brushes.Black; popupTB.Padding = new Thickness(4D); Border b = new Border(); b.BorderThickness = new Thickness(1); b.CornerRadius = new CornerRadius(4); b.BorderBrush = Brushes.Tomato; b.Margin = new Thickness(0, 0, 24, 24); //allows drop shadow to show up b.Effect = new DropShadowEffect(); b.Child = popupTB; channelPopup.Placement = PlacementMode.MousePoint; channelPopup.AllowsTransparency = true; channelPopup.Child = b; //Initialize Event information popup eventPopupTB.Background = new LinearGradientBrush(c1, c2, 45D); eventPopupTB.Foreground = Brushes.Black; eventPopupTB.Padding = new Thickness(4D); b = new Border(); b.BorderThickness = new Thickness(1); b.CornerRadius = new CornerRadius(4); b.BorderBrush = Brushes.Tomato; b.Margin = new Thickness(0, 0, 24, 24); //allows drop shadow to show up b.Effect = new DropShadowEffect(); b.Child = eventPopupTB; eventPopup.Placement = PlacementMode.MousePoint; eventPopup.AllowsTransparency = true; eventPopup.Child = b; //Initialize FOV slider FOV.Maximum = Math.Log10(BDFLength); FOV.Value = 1D; FOVMax.Text = BDFLength.ToString("0"); //Initialize Event selector bool first = true; foreach (EventDictionaryEntry e in head.Events.Values) { MenuItem mi = (MenuItem)EventSelector.FindResource("EventMenuItem"); mi.Header = e.Name; if (first) { mi.IsChecked = true; first = false; } EventSelector.Items.Add(mi); } noteFilePath = System.IO.Path.Combine(directory,System.IO.Path.ChangeExtension(head.BDFFile,".notes.txt")); //from here on the program is GUI-event driven }
//Process the PK events found: returns true if files succesfully written private bool ProcessEvents() { //GV 1 if (!head.GroupVars.ContainsKey("Source channel")) { GVEntry gve = new GVEntry(); gve.GVValueDictionary = new Dictionary<string, int>(); gve.Description = "Source channel for this Event"; foreach (ChannelItem ci in ChannelEntries.Items) { //create GV Value entry for each channel name channelOptions co = channels[ci.Channel.SelectedIndex]; int i; gve.GVValueDictionary.TryGetValue(co.name, out i); if (i == 0) gve.GVValueDictionary.Add(co.name, co.channel + 1); //Use "external" (1-based) channel numbering } head.GroupVars.Add("Source channel", gve); //Channel name: add to GV list in HDR newGVList[0] = gve; } else newGVList[0] = head.GroupVars["Source channel"]; //GV 2 newGVList[1] = head.AddOrGetGroupVar("Found fit", "Found satisfactory fit to PK signal", new string[] { "Found", "Not found" }); //GV 3 newGVList[2] = head.AddOrGetGroupVar("Magnitude", "Estimate of the magnitude of PK signal in scale of channel"); //GV 4 newGVList[3] = head.AddOrGetGroupVar("Direction", "Direction of PK signal", new string[] { "Positive", "Negative" }); //GV 5 newGVList[4] = head.AddOrGetGroupVar("Alpha TC", "Estimate of the time constant (in millisecs) for the rising edge of the PK signal"); //GV 6 newGVList[5] = head.AddOrGetGroupVar("Chi square", "Chi square estimate of goodness of fit to the PK signal"); //GV 7 newGVList[6] = head.AddOrGetGroupVar("Serial number", "Serial number for this channel/filter combination"); //GV 8 newGVList[7] = head.AddOrGetGroupVar("Trend degree", "Degree of trend removal of original PK signal plus 2"); //GV 9 newGVList[8] = head.AddOrGetGroupVar("Filter length", "Length of filter in points"); //GV 10 newGVList[9] = head.AddOrGetGroupVar("Threshold", "Capturing threshold in microV/sec"); //GV 11 newGVList[10] = head.AddOrGetGroupVar("Minimum length", "Minimum length of above-threshold filter signal in points"); //Create Event Dictionary entry for each new PK event/ChannelItem foreach (ChannelItem ci in ChannelEntries.Items) { EventDictionaryEntry ede = head.AddNewEvent(ci.ImpliedEventName, "PK detector events from PKDetectorAnalyzer based on channel " + ci.Channel.Text, newGVList); ede.BDFBased = true; //naked Event with clock BDF-based } head.Comment += (head.Comment == "" ? "" : Environment.NewLine) + "PK source Events added on " + DateTime.Now.ToString("dd MMM yyyy HH:mm:ss") + " by " + Environment.UserName; //Now read old Event file events = new List<Event.OutputEvent>(); Event.EventFactory.Instance(head.Events); // set up Event factory, based on EventDictionary in HDR EventFileReader efr = new EventFileReader( new FileStream(System.IO.Path.Combine(directory, head.EventFile), FileMode.Open, FileAccess.Read)); // open Event file bool zeroSet = false; foreach (Event.InputEvent ie in efr) // read in all Events into dictionary { events.Add(new Event.OutputEvent(ie)); //make list of all current Events if (!zeroSet && ie.IsCovered) //set zeroTime based on first encounter covered Event { bdf.setZeroTime(ie); zeroSet = true; } } efr.Close(); //write out new HDR file FileStream fs = null; bool OK = false; while (!OK) { try { head.EventFile = newFileName + ".evt"; //now we can change Event file name and write out new HDR fs = new FileStream(System.IO.Path.Combine(directory, newFileName + ".hdr"), FileMode.CreateNew, FileAccess.Write); OK = true; } catch (IOException) //force only a new { Replace_dataset rd = new Replace_dataset(newFileName, this.FNExtension.Text); rd.WindowStartupLocation = WindowStartupLocation.CenterOwner; rd.Owner = this; rd.ShowDialog(); if (rd.Result > 0) { if (rd.Result == 3) //Exit { Status.Text = "Cancelled writing new dataset"; return false; } if (rd.Result == 1) //Yes { OK = true; fs = new FileStream(System.IO.Path.Combine(directory, newFileName + ".hdr"), FileMode.Create, FileAccess.Write); } else //N0: new extension, try again { this.FNExtension.Text = rd.NewExtension.Text; //this will also change newFileName } } } } new HeaderFileWriter(fs, head); foreach (eventTime et in eventTimeList) { double ST = bdf.SampleTime(et.channelNumber); //create a naked Event at this time Event.OutputEvent newEvent = new Event.OutputEvent(head.Events[et.channelItem.ImpliedEventName], (double)(et.t0 + et.startTime) * ST); //assign GV values to new event newEvent.GVValue = new string[11]; newEvent.GVValue[0] = bdf.channelLabel(et.channelNumber); newEvent.GVValue[1] = et.foundFit ? "Found" : "Not found"; newEvent.GVValue[2] = ((int)Math.Abs(et.A)).ToString("0"); newEvent.GVValue[3] = et.sign > 0 ? "Positive" : "Negative"; newEvent.GVValue[4] = ((int)(1000D / et.a)).ToString("0"); if (et.chiSquare < 2E9) newEvent.GVValue[5] = ((int)et.chiSquare).ToString("0"); else newEvent.GVValue[5] = "0"; newEvent.GVValue[6] = et.serialNumber.ToString("0"); newEvent.GVValue[7] = (et.trendDegree + 2).ToString("0"); newEvent.GVValue[8] = et.filterLength.ToString("0"); newEvent.GVValue[9] = ((int)(et.threshold / ST)).ToString("0"); newEvent.GVValue[10] = et.minimumLength.ToString("0"); events.Add(newEvent); } events = events.OrderBy(ev => bdf.timeFromBeginningOfFileTo(ev)).ToList(); //sort Events into time order fs = new FileStream(System.IO.Path.Combine(directory,head.EventFile), FileMode.Create, FileAccess.Write); EventFileWriter efw = new EventFileWriter(fs); foreach (Event.OutputEvent ev in events) efw.writeRecord(ev); efw.Close(); lf.Close(); //close out log file //and copy out to file logStream.WriteTo(new FileStream(System.IO.Path.Combine(directory, newFileName + ".pkda.log.xml"), FileMode.Create, FileAccess.Write)); return true; }