/// <summary> /// Write the measurement data into a CSV file with 1 column for each channel of data. /// </summary> /// <param name="channels">The channels of data.</param> /// <param name="csvFilename">The name of the output file.</param> private static void WriteToLogFile(Measurements channels, string csvFilename) { string filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, csvFilename); StreamWriter csvOutput = new StreamWriter(filepath); for (int channel = 0; channel < Measurements.ChannelsOfData; channel++) { for (int reading = 0; reading < channels.Readings.GetUpperBound(0); reading++) { csvOutput.WriteLine("{0},{1},{2},{3},{4},{5}", channels.Readings[reading, 0], channels.Readings[reading, 1], channels.Readings[reading, 2], channels.Readings[reading, 3], channels.Readings[reading, 4], channels.Readings[reading, 5]); } } csvOutput.Close(); }
/// <summary> /// Where all the action happens! /// </summary> /// <param name="args">None but the above constants could be parameterized.</param> static void Main(string[] args) { // Read the data from the input file. We read the whole file here, which is OK on a PC, // but it would need refactoring to read blocks at a time on an embedded system. string ecgStream = OpenInputFile(ecgSampleFilename); // Convert the text stream into a list of ECG packet objects List<ModeECGPacket> packets = ParseECGStream(ecgStream); // Convert the ECG data values for each channel into an array. This makes it easier // to analyse. Measurements channels = new Measurements(packets); // Output to csv file for analysis. This isn't really necessary but it's quick to // produce a chart in Excel. WriteToLogFile(channels, csvFilename); // Detect heartrates from the channel data. List<double> heartrates = GetHeartrates(channels); // Display our findings to the user. ReportFindings(heartrates); // Keep the console up so the user has a chance to read the heartbeats found. Console.ReadLine(); }
/// <summary> /// Work out what heartrates we can from the channel data. At this point, we don't /// know which channel contains viable data. Here we take a look at how the data fluctuates /// and if it's more than a differential threshold, we take it that this channel is the /// one containing the ECG data. With the sample file of data, there's only one channel /// of data (channel 0 actually); all others are flat-liners so presumably the test /// subjects on channels 1 to 5 are deceased! Oh no, call the doc... :-) /// </summary> /// <param name="channels">The data from all channels.</param> /// <returns>The list of heartrates that have been calculated.</returns> private static List<double> GetHeartrates(Measurements channels) { // We'll store the indices of readings in here. List<int> intervals = new List<int>(); // Go through each of the channels to detect one that contains data. for (int channel = 0; channel < Measurements.ChannelsOfData; channel++) { UInt16 currentReading = 0; UInt16 previousReading = channels.Readings[0, channel]; for (int reading = 1; reading < channels.Readings.GetUpperBound(0); reading++) { currentReading = channels.Readings[reading, channel]; UInt16 differential = (UInt16)Math.Abs(currentReading - previousReading); if (differential > differentialThreshold) { intervals.Add(reading); } previousReading = currentReading; } // If we have at least 2 values, we have an interval so we can calculate a // heartrate. It may be that the interval between them is too small though. if (intervals.Count >= 2) break; } // If we haven't detected any suitable channel of data, we can't calculate the // heartrates. if (intervals.Count == 0) return null; // Calculate heartrates from the intervals we've recorded. List<double> heartrates = new List<double>(); int interval1 = intervals[0]; for (int i = 1; i < intervals.Count; i++) { int interval2 = intervals[i]; double rate = 60 / (((double)(interval2 - interval1)) / samplingRateHz); if (rate < maximumPossibleHeartRateBPM) { heartrates.Add(rate); } interval1 = interval2; } return heartrates; }