From e579abaf67be10d81f0ec0f3730e17e7d9f97c89 Mon Sep 17 00:00:00 2001 From: Anthony Truskinger Date: Wed, 18 Sep 2019 11:54:24 +1000 Subject: [PATCH] Updated Plot.cs with changes from content-description branch See 6a319be16dec2ea730f3d0e63a83c9bf03f67989 Co-authored-by: Michael Towsey --- src/TowseyLibrary/Plot.cs | 131 +++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 15 deletions(-) diff --git a/src/TowseyLibrary/Plot.cs b/src/TowseyLibrary/Plot.cs index e842be891..35cd95da5 100644 --- a/src/TowseyLibrary/Plot.cs +++ b/src/TowseyLibrary/Plot.cs @@ -4,19 +4,16 @@ namespace TowseyLibrary { + using System; using System.Collections.Generic; using System.Drawing; /// - /// Represents a single array of data with Xand Y scales and other info useful for plotting a graph. + /// Represents a single array of data with X and Y scales and other info useful for plotting a graph. /// Was first used to represent a track of scores at the bottom of a sonogram image. /// public class Plot { - public Plot() - { - } - public Plot(string _title, double[] _data, double _threshold) { this.title = _title; @@ -40,11 +37,114 @@ public void ScaleDataArray(int newLength) this.data = DataTools.ScaleArray(this.data, newLength); } - public void NormaliseData(double min, double max) + public void NormalizeData(double min, double max) { this.data = DataTools.NormaliseInZeroOne(this.data, min, max); } + /// + /// Assumes that the data has been normalised by a call to plot.NormalizeData(double min, double max) or equivalent. + /// + /// height of the plot. + public Image DrawPlot(int height) + { + var image = new Bitmap(this.data.Length, height); + Graphics g = Graphics.FromImage(image); + g.Clear(Color.LightGray); + + if (this.data == null) + { + return image; + } + + int dataLength = this.data.Length; + double min = 0.0; + double max = 1.0; + double range = max - min; + + // Next two lines are for sub-sampling if the score array is compressed to fit smaller image width. + double subSample = dataLength / (double)image.Width; + if (subSample < 1.0) + { + subSample = 1; + } + + for (int w = 0; w < image.Width; w++) + { + int start = (int)Math.Round(w * subSample); + int end = (int)Math.Round((w + 1) * subSample); + if (end >= dataLength) + { + continue; + } + + // Find max value in sub-sample - if there is a sub-sample + double subsampleMax = -double.MaxValue; + for (int x = start; x < end; x++) + { + if (subsampleMax < this.data[x]) + { + subsampleMax = this.data[x]; + } + } + + double fraction = (subsampleMax - min) / range; + int id = height - 1 - (int)(height * fraction); + if (id < 0) + { + id = 0; + } + else if (id > height) + { + id = height; // impose bounds + } + + for (int z = id; z < height; z++) + { + image.SetPixel(w, z, Color.Black); // draw the score bar + } + + image.SetPixel(w, height - 1, Color.Black); // draw base line + } + + // Add in horizontal threshold significance line + double f = (this.threshold - min) / range; + int lineId = height - 1 - (int)(height * f); + if (lineId < 0) + { + return image; + } + + if (lineId > height) + { + return image; + } + + for (int x = 0; x < image.Width; x++) + { + image.SetPixel(x, lineId, Color.Lime); + } + + return image; + } + + public Image DrawAnnotatedPlot(int height) + { + var image = this.DrawPlot(height); + int length = image.Width; + + // var family = new FontFamily("Arial"); + // var font = new Font(family, 10, FontStyle.Regular, GraphicsUnit.Pixel); + var font = new Font("Tahoma", 9); + var g = Graphics.FromImage(image); + g.DrawString(this.title, font, Brushes.Red, new PointF(10, 0)); + + // ReSharper disable once PossibleLossOfFraction + g.DrawString(this.title, font, Brushes.Red, new PointF(length / 2, 0)); + g.DrawString(this.title, font, Brushes.Red, new PointF(length - 80, 0)); + return image; + } + public static double[] PruneScoreArray(double[] scores, double scoreThreshold, int minDuration, int maxDuration) { double[] returnData = new double[scores.Length]; @@ -58,12 +158,12 @@ public static double[] PruneScoreArray(double[] scores, double scoreThreshold, i { if (isHit == false && scores[i] >= scoreThreshold) { - //start of an event + // start of an event isHit = true; startId = i; } else // check for the end of an event - if (isHit == true && scores[i] < scoreThreshold) + if (isHit && scores[i] < scoreThreshold) { // this is end of an event, so initialise it isHit = false; @@ -72,7 +172,7 @@ public static double[] PruneScoreArray(double[] scores, double scoreThreshold, i int duration = endId - startId; if (duration < minDuration || duration > maxDuration) { - continue; //skip events with duration shorter than threshold + continue; // skip events with duration shorter than threshold } // pass over all frames @@ -81,7 +181,7 @@ public static double[] PruneScoreArray(double[] scores, double scoreThreshold, i returnData[j] = scores[j]; } } - } //end of pass over all frames + } // end of pass over all frames return returnData; } @@ -104,14 +204,14 @@ public static void FindStartsAndEndsOfScoreEvents( // pass over all frames for (int i = 0; i < count; i++) { - //start of an event + // Start of an event if (isHit == false && scores[i] >= scoreThreshold) { isHit = true; startId = i; } else // check for the end of an event - if (isHit == true && scores[i] < scoreThreshold) + if (isHit && scores[i] < scoreThreshold) { // this is end of an event, so initialise it isHit = false; @@ -120,7 +220,8 @@ public static void FindStartsAndEndsOfScoreEvents( int duration = endId - startId + 1; if (duration < minDuration || duration > maxDuration) { - continue; //skip events with duration shorter than threshold + // skip events with duration shorter than threshold + continue; } // pass over all frames @@ -131,7 +232,7 @@ public static void FindStartsAndEndsOfScoreEvents( startEnds.Add(new Point(startId, endId)); } - } //end of pass over all frames + } } } -} +} \ No newline at end of file