public void TestSingleFileParsePerformance() { int recordsParsed = 0; long fastParserTime = TimeIt(5, @"TestData\large_file.fit", (stream) => { System.DateTime maxTime = System.DateTime.MinValue; var fastParser = new FastParser(stream); foreach (var dataRecord in fastParser.GetMessages()) { System.DateTime timeStamp; if (dataRecord.GlobalMessageNumber == GlobalMessageDecls.Record) { if (dataRecord.TryGetField(RecordDef.TimeStamp, out timeStamp)) { // Bogus calculation to make sure we don't optimize this away if (timeStamp > maxTime) { maxTime = timeStamp; } } recordsParsed++; } } }); Console.WriteLine("Parse large_file.fit best time {0}ms, {1} records parsed", fastParserTime, recordsParsed); }
private FileResult TestReadingAllFilesInDirectoryUsingFastParser(Stream stream, bool validateCrc) { System.DateTime maxTime = System.DateTime.MinValue; int recordsParsed = 0; var fastParser = new FastParser(stream); if (validateCrc) { Assert.IsTrue(fastParser.IsFileValid()); } foreach (var dataRecord in fastParser.GetMessages()) { System.DateTime timeStamp; if (dataRecord.GlobalMessageNumber == GlobalMessageDecls.Record) { if (dataRecord.TryGetField(RecordDef.TimeStamp, out timeStamp)) { // Bogus calculation to make sure we don't optimize this away if (timeStamp > maxTime) { maxTime = timeStamp; } } recordsParsed++; } } return(new FileResult { Records = recordsParsed, LatestDateTimeInFile = maxTime }); }
public List <FastPacket> LoadFastBinaryFile(String fastBinFileName) { List <FastPacket> packetList = new List <FastPacket>(); if (!File.Exists(fastBinFileName)) { return(packetList); } using (FileStream fileStream = File.Open(fastBinFileName, FileMode.Open)) { BinaryReader binaryReader = new BinaryReader(fileStream); while (binaryReader.BaseStream.Position <= binaryReader.BaseStream.Length - 1) { byte[] headerBytes = binaryReader.ReadBytes(Marshal.SizeOf(typeof(EnvPacketHeader))); EnvPacketHeader header = Tool.ByteToStruct <EnvPacketHeader>(headerBytes, 0, headerBytes.Length); if (header.syncHeader.EqualBytes(EnvProtocol.SyncHeader) && header.dataType == (byte)EnvProtocol.DataType.DataTypeFast) { byte[] buffer = binaryReader.ReadBytes(Marshal.SizeOf(typeof(FastPacket))); FastPacket packet; if (FastParser.Parse(buffer, out packet)) { packetList.Add(packet); } } } } return(packetList); }
public void TestFitFile() { using (var stream = System.IO.File.OpenRead(@"TestData\large_file.fit")) { var fastParser = new FastParser(stream); var listOfRecordTypesSeen = new List <ushort>(); foreach (var dataRecord in fastParser.GetMessages()) { ushort messageNumber = dataRecord.GlobalMessageNumber; if (!listOfRecordTypesSeen.Contains(messageNumber)) { MessageDecl messageDef; if (GlobalMessageDecls.Declarations.TryGetValue(messageNumber, out messageDef)) { Console.WriteLine("Record type: {0}", GlobalMessageDecls.Declarations[messageNumber].MessageName); DumpFieldsOfKnownRecord(messageDef, dataRecord.MessageDefinition.FieldDefinitions); } else { Console.WriteLine("Record type: {0}", messageNumber); DumpFieldsOfUnknownRecord(dataRecord.MessageDefinition.FieldDefinitions); } foreach (var field in dataRecord.MessageDefinition.FieldDefinitions) { } listOfRecordTypesSeen.Add(messageNumber); } } } }
public void TestReadLargeFileIntoInternalDataStructures() { using (var stream = System.IO.File.OpenRead(@"TestData\large_file.fit")) { var fastParser = new FastParser(stream); var records = new List <DataSeriesRecord>(); foreach (var dataRecord in fastParser.GetMessages()) { if (dataRecord.GlobalMessageNumber == GlobalMessageDecls.Record) { var record = new DataSeriesRecord(); double latitude, longitude, cadence, heartRate, power, distance, speed; System.DateTime timeStamp; if (dataRecord.TryGetField(RecordDef.TimeStamp, out timeStamp)) { record.TimeStamp = timeStamp; } if (dataRecord.TryGetField(RecordDef.PositionLat, out latitude)) { record.Latitude = latitude * SEMICIRCLES_TO_DEGREES; } if (dataRecord.TryGetField(RecordDef.PositionLong, out longitude)) { record.Longitude = longitude * SEMICIRCLES_TO_DEGREES; } if (dataRecord.TryGetField(RecordDef.HeartRate, out heartRate)) { record.HeartRate = heartRate; // beats * min-1 } if (dataRecord.TryGetField(RecordDef.Cadence, out cadence)) { record.Cadence = cadence; // s-1 } if (dataRecord.TryGetField(RecordDef.Power, out power)) { record.Power = power; // W } if (dataRecord.TryGetField(RecordDef.Distance, out distance)) { record.Distance = distance / 1000; // m } if (dataRecord.TryGetField(RecordDef.Speed, out speed)) { record.Speed = speed / 1000; // m/s } records.Add(record); } } Console.WriteLine("Read {0} timestamp values", records.Count); } }
public void ReadFitFileWithoutHrData() { using (var stream = System.IO.File.OpenRead(@"TestData\no_hr_data.fit")) { var fastParser = new FastParser(stream); Assert.IsTrue(fastParser.IsFileValid()); foreach (var dataRecord in fastParser.GetMessages()) { if (dataRecord.GlobalMessageNumber == GlobalMessageDecls.Record) { double heartRate; if (dataRecord.TryGetField(RecordDef.HeartRate, out heartRate)) { Assert.IsTrue(false); } } } } }
public void TestReadLargeFile() { using (var stream = System.IO.File.OpenRead(@"TestData\large_file.fit")) { var fastParser = new FastParser(stream); var recordCount = 0; double heartRate = 0, currentHeartRate; double cadence = 0, currentCadence; double power = 0, currentPower; foreach (var dataRecord in fastParser.GetMessages()) { if (dataRecord.GlobalMessageNumber == GlobalMessageDecls.Record) { if (dataRecord.TryGetField(RecordDef.HeartRate, out currentHeartRate)) { heartRate += currentHeartRate; } if (dataRecord.TryGetField(RecordDef.Cadence, out currentCadence)) { cadence += currentCadence; } if (dataRecord.TryGetField(RecordDef.Power, out currentPower)) { power += currentPower; } recordCount++; } } Console.WriteLine("Read {0} records, average HR = {1}, average Cadence = {2}, average Power = {3}", recordCount, heartRate / (double)recordCount, cadence / (double)recordCount, power / (double)recordCount); } }
/// <summary> /// Parses a .fit file and retrieves the data /// parses using FastFitParser /// </summary> public RunData ParseFastFit(FileStream fitFile) { RunData rtn = null; //init parser and list of temp files FastParser parser = new FastParser(fitFile); if (parser.IsFileValid()) { //init fields for data Double tD = 0; Double aS = 0; Double aC = 0; Double aHR = 0; Boolean sR = false; //go through DataRecord and retrieve relevant data foreach (DataRecord d in parser.GetDataRecords()) { if (d.GlobalMessageNumber == GlobalMessageDecls.Record) { if (d.TryGetField(RecordDef.Distance, out Double distance)) { if (distance > tD) { tD = distance; } } if (d.TryGetField(RecordDef.Speed, out double speed)) { aS += speed; } if (d.TryGetField(RecordDef.Cadence, out double cadence)) { aC += cadence; } if (d.TryGetField(RecordDef.HeartRate, out double heartRate)) { aHR = heartRate; } //after first run through start finding the average of the data if (sR) { aS = Math.Round(aS / 2, 2); aC = Math.Round(aC / 2, 2); aHR = Math.Round(aHR / 2, 2); } else { sR = true; } } } //convert speed and distance to meters and write to Debug. aS = Math.Round(aS / 1000, 2); tD = Math.Round(tD / 1000, 2);; Debug.WriteLine("Distance: " + tD + " m"); Debug.WriteLine("Speed: " + aS + " m/s"); Debug.WriteLine("Cadence: " + aC); Debug.WriteLine("Heart Rate: " + aHR); rtn = new RunData(); rtn.DistanceRan = tD; rtn.RunSpeed = aS; rtn.Cadence = aC; rtn.HeartRate = aHR; Console.WriteLine("Distance: " + tD + " m"); Console.WriteLine("Speed: " + aS + " m/s"); Console.WriteLine("Cadence: " + aC); Console.WriteLine("Heart Rate: " + aHR); fitFile.Close(); } return(rtn); }
private void ParseData(byte[] buffer) { if (buffer.Length <= Marshal.SizeOf(typeof(EnvPacketHeader))) { return; } EnvPacketHeader header = Tool.ByteToStruct <EnvPacketHeader>(buffer, 0, Marshal.SizeOf(typeof(EnvPacketHeader))); if (!Enumerable.SequenceEqual(header.syncHeader, EnvProtocol.SyncHeader)) { return; } byte[] body = new byte[buffer.Length - Marshal.SizeOf(typeof(EnvPacketHeader))]; Array.Copy(buffer, Marshal.SizeOf(typeof(EnvPacketHeader)), body, 0, body.Length); switch ((EnvProtocol.DataType)header.dataType) { case EnvProtocol.DataType.DataTypeSlow: SlowPacket slowPacket; if (SlowParser.Parse(body, out slowPacket)) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SlowPacket))); Marshal.StructureToPtr(slowPacket, ptr, true); WinApi.PostMessage(mainWindowHandle, WinApi.WM_SLOW_DATA, 0, ptr); } else { WinApi.PostMessage(mainWindowHandle, WinApi.WM_SLOW_DATA, 0, IntPtr.Zero); } if (IsStartLogData) { dataLogger.WriteSlowPacket(buffer); } break; case EnvProtocol.DataType.DataTypeFast: FastPacket fastPacket; if (FastParser.Parse(body, out fastPacket)) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FastPacket))); Marshal.StructureToPtr(fastPacket, ptr, true); WinApi.PostMessage(mainWindowHandle, WinApi.WM_FAST_DATA, 0, ptr); } else { WinApi.PostMessage(mainWindowHandle, WinApi.WM_FAST_DATA, 0, IntPtr.Zero); } if (IsStartLogData) { dataLogger.WriteFastPacket(buffer); } break; case EnvProtocol.DataType.DataTypeTail: List <TailPacketRs> tailPacketRs = tailParser.Parse(body); if (tailPacketRs.Count > 0) { foreach (TailPacketRs packet in tailPacketRs) { IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TailPacketRs))); Marshal.StructureToPtr(packet, ptr, true); WinApi.PostMessage(mainWindowHandle, WinApi.WM_TAIL_DATA, 0, ptr); } } else { WinApi.PostMessage(mainWindowHandle, WinApi.WM_TAIL_DATA, 0, IntPtr.Zero); } if (IsStartLogData) { dataLogger.WriteTailPacket(buffer); } break; default: break; } }
static void ComputeTrainingStressReport(Options options) { var files = Directory.GetFiles(options.Directory, "*.fit"); var timer = new Stopwatch(); timer.Start(); var sb = new StringBuilder(); Dictionary <int, Dictionary <int, double> > tssHistogram = new Dictionary <int, Dictionary <int, double> >(); var calendar = System.Globalization.DateTimeFormatInfo.CurrentInfo.Calendar; foreach (var file in files) { using (var stream = File.OpenRead(file)) { var parser = new FastParser(stream); var powerCalculator = new PowerStatisticsCalculator(FTP); bool isFirstRecord = true, retrievedTimeStampOfFirstRecord = false; DateTime timeStampOfFirstRecord = DateTime.MinValue; foreach (var message in parser.GetMessages()) { if (message.GlobalMessageNumber == GlobalMessageDecls.Record) { if (isFirstRecord) { retrievedTimeStampOfFirstRecord = message.TryGetField(RecordDef.TimeStamp, out timeStampOfFirstRecord); isFirstRecord = false; } double power; if (message.TryGetField(RecordDef.Power, out power)) { powerCalculator.Add(power); } } } if (retrievedTimeStampOfFirstRecord) { int year = timeStampOfFirstRecord.Year; int week = calendar.GetWeekOfYear(timeStampOfFirstRecord, System.Globalization.CalendarWeekRule.FirstDay, DayOfWeek.Monday); if (!tssHistogram.ContainsKey(year)) { tssHistogram[year] = new Dictionary <int, double>(); } if (tssHistogram[year].ContainsKey(week)) { tssHistogram[year][week] += powerCalculator.TrainingStressScore; } else { tssHistogram[year][week] = powerCalculator.TrainingStressScore; } } else { sb.Append(String.Format("Unknown date for {0} TSS: {1:0}\n", file, powerCalculator.TrainingStressScore)); } } } // Generate Report foreach (int year in tssHistogram.Keys) { sb.Append(String.Format("\nYear {0}\n", year)); foreach (int week in tssHistogram[year].Keys) { sb.Append(String.Format(" Week {0} TSS = {1:0}\n", week, tssHistogram[year][week])); } } timer.Stop(); Console.WriteLine(sb); Console.WriteLine("Total compute time: {0}ms", timer.ElapsedMilliseconds); }
private static void ComputeDetailedFileReport(Options options) { using (var stream = File.OpenRead(options.File)) { var parser = new FastParser(stream); var laps = new List <LapSummary>(); double minimumHeartRateStandardDeviation = 4; var normalizedPowerCalculator = new PowerStatisticsCalculator(FTP); var powerCurveCalculator = new PowerCurveCalculator(new int[] { 1, 5, 10, 30, 60, 120, 240, 300, 600, 900 }); var normalizedPowerCurveCalculator = new NormalizedPowerCurveCalculator(new int[] { 60, 120, 240, 300, 600, 900 }); var heartRateVarianceCalculator = new HeartRateVarianceCalculator(new int[] { 600, 1200, 2400, 3600 }); var efficiencyFactorCalculator = new EfficiencyFactorCalculator(new int[] { 600, 1200, 2400 }, minimumHeartRateStandardDeviation); var timer = new Stopwatch(); timer.Start(); foreach (var record in parser.GetMessages()) { if (record.GlobalMessageNumber != GlobalMessageDecls.Record) { // Dump the name of the record Console.WriteLine(record.GlobalMessageNumber.ToString()); } if (record.GlobalMessageNumber == GlobalMessageDecls.Record) { double power, heartRate; bool hasPower, hasHeartRate; if (hasPower = record.TryGetField(RecordDef.Power, out power)) { powerCurveCalculator.Add(power); normalizedPowerCalculator.Add(power); normalizedPowerCurveCalculator.Add(power); } if (hasHeartRate = record.TryGetField(RecordDef.HeartRate, out heartRate)) { heartRateVarianceCalculator.Add(heartRate); } if (hasPower && hasHeartRate) { efficiencyFactorCalculator.Add(power, heartRate); } } else if (record.IsStopTimerEvent()) { normalizedPowerCalculator.Reset(); powerCurveCalculator.Reset(); normalizedPowerCurveCalculator.Reset(); heartRateVarianceCalculator.Reset(); efficiencyFactorCalculator.Reset(); } else if (record.GlobalMessageNumber == GlobalMessageDecls.Lap) { } } timer.Stop(); Console.WriteLine("Peak Average Power Curve:\n"); for (int i = 0; i < powerCurveCalculator.Durations.Length; i++) { Console.WriteLine("Duration: {0}s, Peak Average Power: {1:0}W", powerCurveCalculator.Durations[i], powerCurveCalculator.PeakAveragePowerForDuration[i]); } Console.WriteLine("\n"); Console.WriteLine("Peak Normalized Power Curve:\n"); for (int i = 0; i < normalizedPowerCurveCalculator.Durations.Length; i++) { Console.WriteLine("Duration: {0}s, Peak Normalized Power: {1:0}W", normalizedPowerCurveCalculator.Durations[i], normalizedPowerCurveCalculator.PeakNormalizedPowerForDuration[i]); } Console.WriteLine("\n"); Console.WriteLine("Minimum Heart Rate Variance:\n"); for (int i = 0; i < heartRateVarianceCalculator.Durations.Length; i++) { Console.WriteLine("Duration: {0}s, Average Heart Rate: {1:0}bpm +/- {2:0.0}", heartRateVarianceCalculator.Durations[i], heartRateVarianceCalculator.MeanHeartRateForDuration[i], heartRateVarianceCalculator.StandardDeviationForDuration[i]); } Console.WriteLine("\n"); Console.WriteLine("Efficiency Factor:\n"); for (int i = 0; i < efficiencyFactorCalculator.Durations.Length; i++) { Console.WriteLine("Duration: {0}s, NP = {1:0}W, HR = {2:0.0}+/-{3:0.0}, EF = {4:0.000}", efficiencyFactorCalculator.Durations[i], efficiencyFactorCalculator.NormalizedPowerForDuration[i], efficiencyFactorCalculator.MeanHeartRateForDuration[i], efficiencyFactorCalculator.StandardDeviationForDuration[i], efficiencyFactorCalculator.EfficiencyFactorForDuration[i]); } Console.WriteLine("\n"); Console.WriteLine("Summary statistics:\n"); Console.WriteLine("Average Heart Rate: {0:0}bpm", heartRateVarianceCalculator.AverageHeartRate); Console.WriteLine("Average power: {0:0}W", powerCurveCalculator.AveragePower); Console.WriteLine("Normalized power: {0:0}W", normalizedPowerCalculator.NormalizedPower); Console.WriteLine("Intensity factor: {0:0.000}", normalizedPowerCalculator.IntensityFactor); Console.WriteLine("Variability index: {0:0.000}", normalizedPowerCalculator.VariabilityIndex); Console.WriteLine("Training Stress Score: {0:0}", normalizedPowerCalculator.TrainingStressScore); Console.WriteLine("Processing duration: {0}ms", timer.ElapsedMilliseconds); // Optional part to dump captured heart rate from efficiency calculator if (options.DumpHeartRateData) { for (int i = 0; i < efficiencyFactorCalculator.StandardDeviationForDuration.Length; i++) { if (efficiencyFactorCalculator.StandardDeviationForDuration[i] > 0) { for (int j = 0; j < efficiencyFactorCalculator.CapturedHeartRateTraces[i].Length; j++) { Console.WriteLine(efficiencyFactorCalculator.CapturedHeartRateTraces[i][j]); } } } } } }
static void ComputeEfficiencyFactorReport(Options options) { // Parameters double standardDeviationThreshold = 7; double meanHeartRateLimit = 142; // upper limit of Z2 var files = Directory.GetFiles(options.Directory, "*.fit"); var timer = new Stopwatch(); timer.Start(); var sb = new StringBuilder(); foreach (var file in files) { using (var stream = File.OpenRead(file)) { var parser = new FastParser(stream); var efficiencyFactorCalculator = new EfficiencyFactorCalculator(new int[] { 600 }, standardDeviationThreshold); foreach (var record in parser.GetMessages()) { if (record.GlobalMessageNumber == GlobalMessageDecls.Record) { double power, heartRate; bool hasPower = record.TryGetField(RecordDef.Power, out power); bool hasHeartRate = record.TryGetField(RecordDef.HeartRate, out heartRate); if (hasPower && hasHeartRate) { efficiencyFactorCalculator.Add(power, heartRate); } } else if (record.IsStopTimerEvent()) { efficiencyFactorCalculator.Reset(); } } if (!efficiencyFactorCalculator.HasData) { //Console.WriteLine("{0} has no HR data", Path.GetFileNameWithoutExtension(file)); } else { for (int i = 0; i < efficiencyFactorCalculator.Durations.Length; i++) { if (efficiencyFactorCalculator.StandardDeviationForDuration[i] > 0 && efficiencyFactorCalculator.MeanHeartRateForDuration[i] < meanHeartRateLimit) { sb.AppendLine(String.Format("{0}, Duration {1}s, EF = {2:0.000}, NP = {3:0}, Avg HR = {4:0.0} +/- {5:0.0}", Path.GetFileNameWithoutExtension(file), efficiencyFactorCalculator.Durations[i], efficiencyFactorCalculator.EfficiencyFactorForDuration[i], efficiencyFactorCalculator.NormalizedPowerForDuration[i], efficiencyFactorCalculator.MeanHeartRateForDuration[i], efficiencyFactorCalculator.StandardDeviationForDuration[i])); } } } } } timer.Stop(); Console.WriteLine(sb); Console.WriteLine("Total compute time: {0}ms", timer.ElapsedMilliseconds); }