public BFSampleImplementation(IBFSample copy) { SampleIndex = copy.SampleIndex; ExgChannels = new double[copy.NumberExgChannels]; for (int i = 0; i < NumberExgChannels; i++) { SetExgDataForChannel(i, copy.GetExgDataForChannel(i)); } AcelChannels = new double[copy.NumberAccelChannels]; for (int i = 0; i < NumberAccelChannels; i++) { SetAccelDataForChannel(i, copy.GetAccelDataForChannel(i)); } OtherChannels = new double[copy.NumberOtherChannels]; for (int i = 0; i < NumberOtherChannels; i++) { SetOtherDataForChannel(i, copy.GetOtherDataForChannel(i)); } AnalogChannels = new double[copy.NumberAnalogChannels]; for (int i = 0; i < NumberAnalogChannels; i++) { SetAnalogDataForChannel(i, copy.GetAccelDataForChannel(i)); } TimeStamp = copy.TimeStamp; }
/// <summary> /// Check for Blink in the specified eye /// </summary> void CheckForBlink(IBFSample currentReading, double stdDev, double stdDevAvg, Eyes eye) { IBFSample trigger = (eye == Eyes.Left) ? BlinkLeftRisingEdgeTrigger : BlinkRightRisingEdgeTrigger; // search for rising and falling edge of the signal if (trigger != null) { // rising edge triggered, check for signal going below falling threashold if (stdDev / stdDevAvg < BlinkDownDevThreshold) { if ((currentReading.TimeStamp - trigger.TimeStamp) > BlinkPeriodThresholdMin && (currentReading.TimeStamp - trigger.TimeStamp) < BlinkPeriodThresholdMax) { DetectedBlink?.Invoke(this, new DetectedBlinkEventArgs(eye, WinkState.Wink, currentReading.TimeStamp)); ClearTrigger(eye); } else { // reject as noise DetectedBlink?.Invoke(this, new DetectedBlinkEventArgs(eye, WinkState.Falling, currentReading.TimeStamp)); ClearTrigger(eye); } } else if (currentReading.TimeStamp - trigger.TimeStamp > BlinkPeriodThresholdMax) { // taken too long, clear the rising flag DetectedBlink?.Invoke(this, new DetectedBlinkEventArgs(eye, WinkState.Falling, currentReading.TimeStamp)); ClearTrigger(eye); } } else if (trigger == null /*&& (stdDevAvg < NoisyStdDevThreshold)*/ && (stdDev / stdDevAvg > BlinkUpDevThreshold)) { DetectedBlink?.Invoke(this, new DetectedBlinkEventArgs(eye, WinkState.Rising, currentReading.TimeStamp)); SetTrigger(currentReading, eye); } }
/// <summary> /// Write sample header to file based on the first sample recorded /// </summary> void WriteSampleHeaderOnFirstSample(StreamWriter file, IBFSample nextReading) { string header = "Sample Index"; // exg channels for (int i = 0; i < nextReading.NumberExgChannels; i++) { header += $", EXG Channel {i}"; } // accelerometer channels for (int i = 0; i < nextReading.NumberExgChannels; i++) { header += $", Accel Channel {i}"; } // other channels for (int i = 0; i < nextReading.NumberOtherChannels; i++) { header += $", Other"; } // analog channels for (int i = 0; i < nextReading.NumberAnalogChannels; i++) { header += $", Analog Channel {i}"; } // time stamps header += ", Timestamp, Timestamp (Formatted)"; file.WriteLine(header); }
/// <summary> /// Check the sample index sequence /// log a warning if sample index is missing /// </summary> private void InspectSampleIndex(IBFSample sample) { if (LastSampleIndex < 0) { LastSampleIndex = (int)sample.SampleIndex; return; } var difference = sample.SampleIndex.SampleIndexDifference(LastSampleIndex); LastSampleIndex = (int)sample.SampleIndex; switch ((BrainhatBoardIds)BoardId) { case BrainhatBoardIds.CYTON_BOARD: case BrainhatBoardIds.MENTALIUM: if (difference > 1) { CountMissingIndex++; } break; case BrainhatBoardIds.CYTON_DAISY_BOARD: if (difference > 2) { CountMissingIndex++; } break; } }
/// <summary> /// Add data to the file writer /// </summary> public void AddData(IBFSample data) { if (FileWritingTask != null) { Data.Enqueue(data); NotifyAddedData.Release(); } }
/// <summary> /// Clear out the std dev collections /// </summary> void ClearStdDevRunningCollection() { for (int i = 0; i < NumberOfChannels; i++) { ChannelSdtDevRunningCollection[i].RemoveAll(); } StdDevMedians = new BFSampleImplementation(BoardId); }
public BFSampleImplementation(IBFSample sample, int numExg, int numAcel, int numOther, int numAnalog) { ExgChannels = new double[numExg]; AcelChannels = new double[numAcel]; OtherChannels = new double[numOther]; AnalogChannels = new double[numAnalog]; SampleIndex = sample.SampleIndex; TimeStamp = sample.TimeStamp; }
/// <summary> /// Create the collection of std deviations median /// </summary> void CreateChannelStdDevRunningCollection() { ChannelSdtDevRunningCollection = new List <ConcurrentQueue <double> >(); // add a collection for each channel for (int i = 0; i < NumberOfChannels; i++) { ChannelSdtDevRunningCollection.Add(new ConcurrentQueue <double>()); } StdDevMedians = new BFSampleImplementation(BoardId); }
/// <summary> /// Detect blinks function /// using current reading, the standard deviation from channel 0 (FP1) and channel 1 (FP2), /// and the average deviaiton from channel 0,1 /// </summary> public void DetectBlinks(IBFSample currentReading, double stdDev0, double stdDevAvg0, double stdDev1, double stdDevAvg1) { try { CheckForBlink(currentReading, stdDev0, stdDevAvg0, Eyes.Left); CheckForBlink(currentReading, stdDev1, stdDevAvg1, Eyes.Right); } catch (Exception e) { Log?.Invoke(this, new LogEventArgs(this, "DetectBlinks", e, LogLevel.ERROR)); } }
/// <summary> /// Process the data in the queue /// </summary> void ProcessDataQueue(IBFSample sample) { if (sample != null) { lock (UnfilteredData) UnfilteredData.Insert(0, sample); InspectSampleIndex(sample); NewSample?.Invoke(this, new BFSampleEventArgs(sample)); } }
void SetTrigger(IBFSample reading, Eyes eye) { switch (eye) { case Eyes.Left: BlinkLeftRisingEdgeTrigger = reading; break; case Eyes.Right: BlinkRightRisingEdgeTrigger = reading; break; } }
void ClearTrigger(Eyes eye) { switch (eye) { case Eyes.Left: BlinkLeftRisingEdgeTrigger = null; break; case Eyes.Right: BlinkRightRisingEdgeTrigger = null; break; } }
/// <summary> /// Log raw data processing performance /// </summary> void LogRawDataProcessingPerformance(IBFSample data) { RecordsCount++; TimeSinceLastSample.Restart(); RawDataOffsetTime = Math.Max(Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000 - data.TimeStamp), RawDataOffsetTime); if (CountRecordsTimer.ElapsedMilliseconds > 5000) { Log?.Invoke(this, new LogEventArgs(HostName, this, "LogRawDataProcessingPerformance", $"{HostName} Logged {(int)(RecordsCount / CountRecordsTimer.Elapsed.TotalSeconds)} records per second. Offset time {RawDataOffsetTime.ToString("F6")} s.", LogLevel.TRACE)); CountRecordsTimer.Restart(); RawDataOffsetTime = 0.0; RecordsCount = 0; } }
// Public Methods #region PublicMethods /// <summary> /// Setup the band powers range lists /// </summary> public void InitializeMonitorForBandPowerRangeList() { BandPowers = new IBFSample[BandPowerCalc.NumberOfBands]; // create a dictionary for the results of the band power calculation // must match the number of frequency ranges list above BandPowersCollection = new ConcurrentDictionary <string, IBFSample>(); for (int j = 0; j < BandPowerCalc.NumberOfBands; j++) { BandPowers[j] = new BFSampleImplementation(BoardId); var key = (BandPowerCalc.BandPowerCalcRangeList[j].Item1 + (BandPowerCalc.BandPowerCalcRangeList[j].Item2 - BandPowerCalc.BandPowerCalcRangeList[j].Item1) / 2).BandPowerKey(); BandPowersCollection.TryAdd(key, BandPowers[j]); } }
/// <summary> /// Create samples from a single data record (chunk of signals per sample) /// </summary> void CreateSamples(double[,] chunk) { double dataRecordTime = StartTime.Value + (ReadDataRecordsCount * DataRecordDuration); for (int i = 0; i < chunk.GetRow(0).Length; i++) { IBFSample newSample = null; newSample = new BFSampleImplementation(BoardId); if (newSample != null) { newSample.InitializeFromSample(chunk.GetColumn(i)); newSample.TimeStamp = StartTime.Value + newSample.TimeStamp; _Samples.Add(newSample); } } }
/// <summary> /// Write a sample to file /// </summary> void WriteToFile(StreamWriter file, IBFSample nextSample) { var seconds = (long)Math.Truncate(nextSample.TimeStamp); var time = DateTimeOffset.FromUnixTimeSeconds(seconds); var microseconds = nextSample.TimeStamp - seconds; // sample index var writeLine = nextSample.SampleIndex.ToString("F3"); // exg channels foreach (var nextExg in nextSample.ExgData) { writeLine += $",{nextExg:F4}"; } // accelerometer channels foreach (var nextAcel in nextSample.AccelData) { writeLine += $",{nextAcel:F4}"; } // other channels foreach (var nextOther in nextSample.OtherData) { writeLine += $",{nextOther:F4}"; } // analog channels foreach (var nextAnalog in nextSample.AnalogData) { writeLine += $",{nextAnalog:F4}"; } // raw time stamp writeLine += $",{nextSample.TimeStamp:F6}"; // formatted time stamp writeLine += string.Format(",{0}-{1}-{2} {3}:{4}:{5}.{6}", time.LocalDateTime.Year.ToString("D2"), time.LocalDateTime.Month.ToString("D2"), time.LocalDateTime.Day.ToString("D2"), time.LocalDateTime.Hour.ToString("D2"), time.LocalDateTime.Minute.ToString("D2"), time.LocalDateTime.Second.ToString("D2"), ((int)(microseconds * 1000000)).ToString("D6")); file.WriteLine(writeLine); }
/// <summary> /// Calculate band powers for the range list /// </summary> public IBFSample[] CalculateBandPowers(IEnumerable <IBFSample> samples) { var bandPowers = new IBFSample[BandPowerCalcRangeList.Count]; for (int i = 0; i < BandPowerCalcRangeList.Count; i++) { bandPowers[i] = new BFSampleImplementation(BoardId); } for (int i = 0; i < NumberOfChannels; i++) { var bandPower = CalculateBandPower(samples, SampleRate, i, BandPowerCalcRangeList); int j = 0; foreach (var nextBandPower in bandPower) { bandPowers[j++].SetExgDataForChannel(i, nextBandPower); } } return(bandPowers); }
/// <summary> /// Create a sample from a single line of ascii text /// </summary> IBFSample CreateSample(string nextLine) { IBFSample newSample = null; newSample = new BFSampleImplementation(BoardId); newSample.InitializeFromText(nextLine); // check the timestamp for valid data, this indicates we read a partial line for example the file is actively being written if (Math.Abs(newSample.TimeStamp - 0) < 0.0000001) { return(null); } // cache the start time of the first record if (!StartTime.HasValue) { StartTime = newSample.TimeStamp; } // cahce the end time EndTime = newSample.TimeStamp; return(newSample); }
/// <summary> /// Check the sample index sequence /// log a warning if sample index is missing /// </summary> void InspectSampleIndex(IBFSample sample) { var nextIndex = (int)(sample.SampleIndex); var difference = sample.SampleIndex.SampleIndexDifference(LastSampleIndex); LastSampleIndex = nextIndex; switch ((BrainhatBoardIds)BoardId) { default: if (difference > 1) { CountMissingIndex++; } break; case BrainhatBoardIds.CYTON_DAISY_BOARD: if (difference > 2) { CountMissingIndex++; } break; } }
public void AddData(IBFSample data) { FileWriter.AddData(data); }
/// <summary> /// Add data sample to the broadcast queue /// </summary> public void AddData(IBFSample sample) { DataToBroadcast.Enqueue(sample); }
public BFSampleEventArgs(IBFSample sample) { Sample = sample; }
/// <summary> /// Add data to the proecssor queue /// </summary> public void AddDataToProcessor(IBFSample data) { DataToProcess.Enqueue(data); NotifyAddedData.Release(); }
/// <summary> /// Open the file for writing and write the header information /// </summary> void WriteHeader(IBFSample firstSample) { if (firstSample == null || WroteHeader) { return; // invalid first sample or we already wrote the header } // open the file for writing FileHandle = edfOpenFileWriteOnly(FileName, 3, firstSample.SampleSize); if (FileHandle < 0) { throw new Exception("Unable to open the file."); } Log?.Invoke(this, new LogEventArgs(this, "WriteHeader", $"Started recording file {FileName}.", LogLevel.INFO)); int signalCount = 0; // Signal Properties // // sample index edfSetSamplesInDataRecord(FileHandle, signalCount, SampleRate); edfSetPhysicalMaximum(FileHandle, signalCount, 255); edfSetPhysicalMinimum(FileHandle, signalCount, 0); edfSetDigitalMaximum(FileHandle, signalCount, 8388607); edfSetDigitalMinimum(FileHandle, signalCount, -8388608); edfSetLabel(FileHandle, signalCount, "SampleIndex"); edfSetPrefilter(FileHandle, signalCount, ""); edfSetTransducer(FileHandle, signalCount, ""); edfSetPhysicalDimension(FileHandle, signalCount, "counter"); signalCount++; // // exg channels for (int i = 0; i < firstSample.NumberExgChannels; i++) { edfSetSamplesInDataRecord(FileHandle, signalCount, SampleRate); edfSetPhysicalMaximum(FileHandle, signalCount, 187500.000); edfSetPhysicalMinimum(FileHandle, signalCount, -187500.000); edfSetDigitalMaximum(FileHandle, signalCount, 8388607); edfSetDigitalMinimum(FileHandle, signalCount, -8388608); edfSetLabel(FileHandle, signalCount, $"EXG{i}"); edfSetPrefilter(FileHandle, signalCount, ""); edfSetTransducer(FileHandle, signalCount, ""); edfSetPhysicalDimension(FileHandle, signalCount, "uV"); signalCount++; } NumberOfExgChannels = firstSample.NumberExgChannels; // // acel channels for (int i = 0; i < firstSample.NumberAccelChannels; i++) { edfSetSamplesInDataRecord(FileHandle, signalCount, SampleRate); edfSetPhysicalMaximum(FileHandle, signalCount, 1.0); edfSetPhysicalMinimum(FileHandle, signalCount, -1.0); edfSetDigitalMaximum(FileHandle, signalCount, 8388607); edfSetDigitalMinimum(FileHandle, signalCount, -8388608); edfSetLabel(FileHandle, signalCount, $"Acel{i}"); edfSetPrefilter(FileHandle, signalCount, ""); edfSetTransducer(FileHandle, signalCount, ""); edfSetPhysicalDimension(FileHandle, signalCount, "unit"); signalCount++; } NumberOfAcelChannels = firstSample.NumberAccelChannels; // // other channels for (int i = 0; i < firstSample.NumberOtherChannels; i++) { edfSetSamplesInDataRecord(FileHandle, signalCount, SampleRate); edfSetPhysicalMaximum(FileHandle, signalCount, 9999.0); edfSetPhysicalMinimum(FileHandle, signalCount, -9999.0); edfSetDigitalMaximum(FileHandle, signalCount, 8388607); edfSetDigitalMinimum(FileHandle, signalCount, -8388608); edfSetLabel(FileHandle, signalCount, $"Other{i}"); edfSetPrefilter(FileHandle, signalCount, ""); edfSetTransducer(FileHandle, signalCount, ""); edfSetPhysicalDimension(FileHandle, signalCount, "other"); signalCount++; } NumberOfOtherChannels = firstSample.NumberOtherChannels; // // analog channels for (int i = 0; i < firstSample.NumberAnalogChannels; i++) { edfSetSamplesInDataRecord(FileHandle, signalCount, SampleRate); edfSetPhysicalMaximum(FileHandle, signalCount, 9999.0); edfSetPhysicalMinimum(FileHandle, signalCount, -9999.0); edfSetDigitalMaximum(FileHandle, signalCount, 8388607); edfSetDigitalMinimum(FileHandle, signalCount, -8388608); edfSetLabel(FileHandle, signalCount, $"Analog{i}"); edfSetPrefilter(FileHandle, signalCount, ""); edfSetTransducer(FileHandle, signalCount, ""); edfSetPhysicalDimension(FileHandle, signalCount, "analog"); signalCount++; } NumberOfAnalogChannels = firstSample.NumberAnalogChannels; // // timestamp edfSetSamplesInDataRecord(FileHandle, signalCount, SampleRate); edfSetPhysicalMaximum(FileHandle, signalCount, 43200.0); edfSetPhysicalMinimum(FileHandle, signalCount, 0); edfSetDigitalMaximum(FileHandle, signalCount, 8388607); edfSetDigitalMinimum(FileHandle, signalCount, -8388608); edfSetLabel(FileHandle, signalCount, "TestTime"); edfSetPrefilter(FileHandle, signalCount, ""); edfSetTransducer(FileHandle, signalCount, ""); edfSetPhysicalDimension(FileHandle, signalCount, "seconds"); FirstTimeStamp = firstSample.TimeStamp; // File Header Properties // Info.ValidateForBdf(); edfSetStartDatetime(FileHandle, firstSample.ObservationTime.Year, firstSample.ObservationTime.Month, firstSample.ObservationTime.Day, firstSample.ObservationTime.Hour, firstSample.ObservationTime.Minute, firstSample.ObservationTime.Second); edfSetSubsecondStarttime(FileHandle, firstSample.ObservationTime.Millisecond * 10_000); edfSetPatientName(FileHandle, Info.SubjectName); edfSetPatientCode(FileHandle, Info.SubjectCode); edfSetPatientYChromosome(FileHandle, (int)Info.SubjectGender); edfSetPatientBirthdate(FileHandle, Info.SubjectBirthday.Year, Info.SubjectBirthday.Month, Info.SubjectBirthday.Day); edfSetPatientAdditional(FileHandle, Info.SubjectAdditional); edfSetAdminCode(FileHandle, Info.AdminCode); edfSetTechnician(FileHandle, Info.Technician); edfSetEquipment(FileHandle, Info.Device); edfSetRecordingAdditional(FileHandle, BoardId.GetSampleNameShort()); // we are ready to write data FileTimer.Restart(); WroteHeader = true; }