diff --git a/src/AnalysisBase/ResultBases/EventBase.cs b/src/AnalysisBase/ResultBases/EventBase.cs
index ea37af352..80959cc82 100644
--- a/src/AnalysisBase/ResultBases/EventBase.cs
+++ b/src/AnalysisBase/ResultBases/EventBase.cs
@@ -1,4 +1,4 @@
-// --------------------------------------------------------------------------------------------------------------------
+// --------------------------------------------------------------------------------------------------------------------
//
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
//
@@ -12,14 +12,14 @@ namespace AnalysisBase.ResultBases
using System;
///
- /// The base class for all Event style results
+ /// The base class for all Event style results.
///
public abstract class EventBase : ResultBase
{
private double eventStartSeconds;
///
- /// Gets or sets the time the current audio segment is offset from the start of the file/recording.
+ /// Gets or sets the time (in seconds) from start of the file/recording to start of the current audio segment.
///
///
/// will always be greater than or equal to .
@@ -36,7 +36,7 @@ public abstract class EventBase : ResultBase
///
///
/// 2017-09: This field USED to be offset relative to the current segment.
- /// 2017-09: This field is NOW equivalent to
+ /// 2017-09: This field is NOW equivalent to .
///
public virtual double EventStartSeconds
{
@@ -60,6 +60,13 @@ public virtual double EventStartSeconds
///
public virtual double? LowFrequencyHertz { get; protected set; }
+ ///
+ /// Sets both the Segment start and the Event start.
+ /// is measured relative to the start of the recording.
+ /// is measured relative to the start of the segment.
+ /// This method sets both and which
+ /// are both measured relative to the start of the recording.
+ ///
protected void SetEventStartRelative(TimeSpan segmentStart, double eventStartSegmentRelative)
{
this.SegmentStartSeconds = segmentStart.TotalSeconds;
diff --git a/src/AnalysisPrograms/AnalyseLongRecordings/AnalyseLongRecording.cs b/src/AnalysisPrograms/AnalyseLongRecordings/AnalyseLongRecording.cs
index 1cb00f4f5..9643f87e3 100644
--- a/src/AnalysisPrograms/AnalyseLongRecordings/AnalyseLongRecording.cs
+++ b/src/AnalysisPrograms/AnalyseLongRecordings/AnalyseLongRecording.cs
@@ -73,7 +73,8 @@ public static void Execute(Arguments arguments)
{
Log.Warn($"Config file {configFile.FullName} not found... attempting to resolve config file");
- // we use .ToString() here to get the original input string - Using fullname always produces an absolute path wrt to pwd... we don't want to prematurely make asusmptions:
+ // we use .ToString() here to get the original input string.
+ // Using fullname always produces an absolute path relative to pwd... we don't want to prematurely make assumptions:
// e.g. We require a missing absolute path to fail... that wouldn't work with .Name
// e.g. We require a relative path to try and resolve, using .FullName would fail the first absolute check inside ResolveConfigFile
configFile = ConfigFile.Resolve(configFile.ToString(), Directory.GetCurrentDirectory().ToDirectoryInfo());
diff --git a/src/AnalysisPrograms/EventStatistics/EventStatisticsEntry.cs b/src/AnalysisPrograms/EventStatistics/EventStatisticsEntry.cs
index eb5fab25b..bea3e007a 100644
--- a/src/AnalysisPrograms/EventStatistics/EventStatisticsEntry.cs
+++ b/src/AnalysisPrograms/EventStatistics/EventStatisticsEntry.cs
@@ -49,7 +49,7 @@ public static async Task ExecuteAsync(Arguments arguments)
Log.Warn($"Config file {config.FullName} not found... attempting to resolve config file");
// we use the original input string - Using FileInfo fullname always produces an
- // absolute path wrt to pwd... we don't want to prematurely make assumptions:
+ // absolute path relative to pwd... we don't want to prematurely make assumptions:
// e.g. We require a missing absolute path to fail... that wouldn't work with .Name
// e.g. We require a relative path to try and resolve, using .FullName would fail the first absolute
// check inside ResolveConfigFile
diff --git a/src/AnalysisPrograms/OscillationRecogniser.cs b/src/AnalysisPrograms/OscillationRecogniser.cs
index 4cfb76af1..71e932483 100644
--- a/src/AnalysisPrograms/OscillationRecogniser.cs
+++ b/src/AnalysisPrograms/OscillationRecogniser.cs
@@ -12,6 +12,7 @@ namespace AnalysisPrograms
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+ using Acoustics.Shared.Csv;
using AudioAnalysisTools;
using AudioAnalysisTools.StandardSpectrograms;
using AudioAnalysisTools.WavTools;
@@ -126,15 +127,10 @@ public static void Execute(Arguments arguments)
pcHIF = 100 * hifCount / sonogram.FrameCount;
}
- //write event count to results file.
- double sigDuration = sonogram.Duration.TotalSeconds;
+ // write event count to results file.
string fname = recordingFile.BaseName();
- int count = predictedEvents.Count;
- //string str = String.Format("#RecordingName\tDuration(sec)\t#Ev\tCompT(ms)\t%hiFrames\n{0}\t{1}\t{2}\t{3}\t{4}\n", fname, sigDuration, count, analysisDuration.TotalMilliseconds, pcHIF);
- string str = string.Format("{0}\t{1}\t{2}\t{3}\t{4}", fname, sigDuration, count, analysisDuration.TotalMilliseconds, pcHIF);
- StringBuilder sb = AcousticEvent.WriteEvents(predictedEvents, str);
- FileTools.WriteTextFile(opPath, sb.ToString());
+ Csv.WriteToCsv(opPath.ToFileInfo(), predictedEvents);
//draw images of sonograms
string imagePath = outputDir + fname + ".png";
diff --git a/src/AnalysisPrograms/Recognizers/Base/RecognizerBase.cs b/src/AnalysisPrograms/Recognizers/Base/RecognizerBase.cs
index 8e068092a..9f2306d53 100644
--- a/src/AnalysisPrograms/Recognizers/Base/RecognizerBase.cs
+++ b/src/AnalysisPrograms/Recognizers/Base/RecognizerBase.cs
@@ -336,35 +336,6 @@ protected virtual Image DrawSonogram(
double eventThreshold)
{
var image = SpectrogramTools.GetSonogramPlusCharts(sonogram, predictedEvents, scores, hits);
-
- //const bool doHighlightSubband = false;
- //const bool add1KHzLines = true;
- //var image = new Image_MultiTrack(sonogram.GetImage(doHighlightSubband, add1KHzLines, doMelScale: false));
- //image.AddTrack(ImageTrack.GetTimeTrack(sonogram.Duration, sonogram.FramesPerSecond));
- //image.AddTrack(ImageTrack.GetSegmentationTrack(sonogram));
-
- //if (scores != null)
- //{
- // foreach (var plot in scores)
- // {
- // image.AddTrack(ImageTrack.GetNamedScoreTrack(plot.data, 0.0, 1.0, plot.threshold, plot.title));
- // }
- //}
-
- //if (hits != null)
- //{
- // image.OverlayRedTransparency(hits);
- //}
-
- //if (predictedEvents != null && predictedEvents.Count > 0)
- //{
- // image.AddEvents(
- // predictedEvents,
- // sonogram.NyquistFrequency,
- // sonogram.Configuration.FreqBinCount,
- // sonogram.FramesPerSecond);
- //}
-
return image;
}
diff --git a/src/AnalysisPrograms/Recognizers/Base/WhistleParameters.cs b/src/AnalysisPrograms/Recognizers/Base/WhistleParameters.cs
index 071b49772..77231d622 100644
--- a/src/AnalysisPrograms/Recognizers/Base/WhistleParameters.cs
+++ b/src/AnalysisPrograms/Recognizers/Base/WhistleParameters.cs
@@ -39,10 +39,6 @@ public static (List, double[]) GetWhistles(
double binWidth = nyquist / (double)binCount;
int minBin = (int)Math.Round(minHz / binWidth);
int maxBin = (int)Math.Round(maxHz / binWidth);
- //int binCountInBand = maxBin - minBin + 1;
-
- // buffer zone around whistle is four bins wide.
- int N = 4;
// list of accumulated acoustic events
var events = new List();
@@ -54,7 +50,8 @@ public static (List, double[]) GetWhistles(
// set up an intensity array for the frequency bin.
double[] intensity = new double[frameCount];
- if (minBin < N)
+ // buffer zone around whistle is four bins wide.
+ if (minBin < 4)
{
// for all time frames in this frequency bin
for (int t = 0; t < frameCount; t++)
@@ -110,33 +107,9 @@ public static (List, double[]) GetWhistles(
} //end for all freq bins
// combine adjacent acoustic events
- events = AcousticEvent.CombineOverlappingEvents(events);
+ events = AcousticEvent.CombineOverlappingEvents(events, segmentStartOffset);
return (events, combinedIntensityArray);
}
-
- /*
- ///
- /// Calculates the average intensity in a freq band having min and max freq,
- /// AND then subtracts average intensity in the side/buffer bands, below and above.
- /// THis method adds dB log values incorrectly but it is faster than doing many log conversions.
- /// This method is used to find acoustic events and is accurate enough for the purpose.
- ///
- public static double[] CalculateFreqBandAvIntensityMinusBufferIntensity(double[,] sonogramData, int minHz, int maxHz, int nyquist)
- {
- var bandIntensity = SNR.CalculateFreqBandAvIntensity(sonogramData, minHz, maxHz, nyquist);
- var bottomSideBandIntensity = SNR.CalculateFreqBandAvIntensity(sonogramData, minHz - bottomHzBuffer, minHz, nyquist);
- var topSideBandIntensity = SNR.CalculateFreqBandAvIntensity(sonogramData, maxHz, maxHz + topHzBuffer, nyquist);
-
- int frameCount = sonogramData.GetLength(0);
- double[] netIntensity = new double[frameCount];
- for (int i = 0; i < frameCount; i++)
- {
- netIntensity[i] = bandIntensity[i] - bottomSideBandIntensity[i] - topSideBandIntensity[i];
- }
-
- return netIntensity;
- }
- */
}
}
diff --git a/src/AnalysisPrograms/Recognizers/GenericRecognizer.cs b/src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
index 76c451136..21aa9a855 100644
--- a/src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
+++ b/src/AnalysisPrograms/Recognizers/GenericRecognizer.cs
@@ -325,18 +325,9 @@ private static Plot PreparePlot(double[] array, string title, double threshold)
///
static void SaveDebugSpectrogram(RecognizerResults results, Config genericConfig, DirectoryInfo outputDirectory, string baseName)
{
- //var image1 = results.Sonogram.GetImage(false, true, false);
- //image1.Save(Path.Combine("C:\\temp\\test1.profile.png"));
-
- //var image2 = results.Sonogram.GetImageFullyAnnotated("Test");
- //image2.Save(Path.Combine("C:\\temp\\test2.profile.png"));
-
var image3 = SpectrogramTools.GetSonogramPlusCharts(results.Sonogram, results.Events, results.Plots, null);
- //image3.Save(Path.Combine(outputDirectory.FullName, baseName + ".profile.png"));
- image3.Save(Path.Combine("C:\\temp", baseName + ".profile.png"));
-
- //sonogram.GetImageFullyAnnotated("test").Save("C:\\temp\\test.png");
+ image3.Save(Path.Combine(outputDirectory.FullName, baseName + ".profile.png"));
}
/// />
diff --git a/src/AnalysisPrograms/Recognizers/LewiniaPectoralis.cs b/src/AnalysisPrograms/Recognizers/LewiniaPectoralis.cs
index ca8dee5d9..499b1f654 100644
--- a/src/AnalysisPrograms/Recognizers/LewiniaPectoralis.cs
+++ b/src/AnalysisPrograms/Recognizers/LewiniaPectoralis.cs
@@ -1,4 +1,4 @@
-// --------------------------------------------------------------------------------------------------------------------
+// --------------------------------------------------------------------------------------------------------------------
//
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
//
@@ -193,14 +193,6 @@ public override RecognizerResults Recognize(
prunedEvents.Add(ae);
}
- // do a recognizer TEST.
- if (false)
- {
- var testDir = new DirectoryInfo(outputDirectory.Parent.Parent.FullName);
- TestTools.RecognizerScoresTest(recording.BaseName, testDir, recognizerConfig.AnalysisName, scoreArray);
- AcousticEvent.TestToCompareEvents(recording.BaseName, testDir, recognizerConfig.AnalysisName, predictedEvents);
- }
-
// increase very low scores
for (int j = 0; j < scoreArray.Length; j++)
{
diff --git a/src/AnalysisPrograms/Recognizers/LitoriaBicolor.cs b/src/AnalysisPrograms/Recognizers/LitoriaBicolor.cs
index a0a6109f7..cf14962cb 100644
--- a/src/AnalysisPrograms/Recognizers/LitoriaBicolor.cs
+++ b/src/AnalysisPrograms/Recognizers/LitoriaBicolor.cs
@@ -152,14 +152,6 @@ public override RecognizerResults Recognize(AudioRecording recording, Config con
ae.SegmentDurationSeconds = recordingDuration.TotalSeconds;
}
- // do a RECOGNIZER TEST.
- if (false)
- {
- var testDir = new DirectoryInfo(outputDirectory.Parent.Parent.FullName);
- TestTools.RecognizerScoresTest(recording.BaseName, testDir, recognizerConfig.AnalysisName, scoreArray);
- AcousticEvent.TestToCompareEvents(recording.BaseName, testDir, recognizerConfig.AnalysisName, predictedEvents);
- }
-
var plot = new Plot(this.DisplayName, scoreArray, recognizerConfig.EventThreshold);
return new RecognizerResults()
{
@@ -171,7 +163,7 @@ public override RecognizerResults Recognize(AudioRecording recording, Config con
}
///
- /// ################ THE KEY ANALYSIS METHOD
+ /// THE KEY ANALYSIS METHOD.
///
///
///
diff --git a/src/AnalysisPrograms/Recognizers/LitoriaWatjulumensis.cs b/src/AnalysisPrograms/Recognizers/LitoriaWatjulumensis.cs
index a8741adb3..501aa406c 100644
--- a/src/AnalysisPrograms/Recognizers/LitoriaWatjulumensis.cs
+++ b/src/AnalysisPrograms/Recognizers/LitoriaWatjulumensis.cs
@@ -157,14 +157,6 @@ public override RecognizerResults Recognize(AudioRecording recording, Config con
ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds;
}
- // do a recognizer TEST.
- if (false)
- {
- var testDir = new DirectoryInfo(outputDirectory.Parent.Parent.FullName);
- TestTools.RecognizerScoresTest(recording.BaseName, testDir, recognizerConfig.AnalysisName, scoreArray);
- AcousticEvent.TestToCompareEvents(recording.BaseName, testDir, recognizerConfig.AnalysisName, predictedEvents);
- }
-
var plot = new Plot(this.DisplayName, scoreArray, recognizerConfig.EventThreshold);
return new RecognizerResults()
{
@@ -175,8 +167,8 @@ public override RecognizerResults Recognize(AudioRecording recording, Config con
};
}
- ///
- /// ################ THE KEY ANALYSIS METHOD for TRILLS
+ ///
+ /// ################ THE KEY ANALYSIS METHOD for TRILLS
///
/// See Anthony's ExempliGratia.Recognize() method in order to see how to use methods for config profiles.
///
diff --git a/src/AudioAnalysisTools/AcousticEvent.cs b/src/AudioAnalysisTools/AcousticEvent.cs
index 4b188aa29..b28e06b17 100644
--- a/src/AudioAnalysisTools/AcousticEvent.cs
+++ b/src/AudioAnalysisTools/AcousticEvent.cs
@@ -11,10 +11,8 @@ namespace AudioAnalysisTools
{
using System;
using System.Collections.Generic;
- using System.IO;
using System.Linq;
using System.Text;
- using Acoustics.Shared;
using Acoustics.Shared.Contracts;
using Acoustics.Shared.Csv;
using Acoustics.Shared.ImageSharp;
@@ -26,7 +24,7 @@ namespace AudioAnalysisTools
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using TowseyLibrary;
- using Path = System.IO.Path;
+ using static Acoustics.Shared.ImageSharp.Drawing;
public class AcousticEvent : EventBase
{
@@ -40,7 +38,7 @@ public sealed class AcousticEventClassMap : ClassMap
{
nameof(TimeStart), nameof(TimeEnd),
nameof(Bandwidth), nameof(IsMelscale), nameof(FrameOffset),
- nameof(FramesPerSecond), nameof(Name2), nameof(ScoreComment),
+ nameof(FramesPerSecond),
nameof(ScoreNormalised), nameof(Score_MaxPossible),
nameof(Score_MaxInEvent), nameof(Score_TimeOfMaxInEvent),
nameof(Score2Name), nameof(Score2), nameof(Periodicity), nameof(DominantFreq),
@@ -73,42 +71,44 @@ public AcousticEventClassMap()
///
/// Gets the time offset from start of current segment to start of event in seconds.
- /// NOTE: AcousticEvents do not have a notion of time offset wrt start of recording ; - only to start of current recording segment.
- /// Proxied to EventBase.EventStartSeconds.
+ /// Proxied to EventBase.EventStartSeconds.
///
///
+ ///
+ /// NOTE: is relative to the start of a segment. This notion is obsolete!
+ /// Events must always be stored relative to start of the recording.
+ ///
/// Note: converted to private setter so we can control how this is set. Recommend using
/// after event instantiation to modify bounds.
///
+ [Obsolete("Bounds relative to the segment are inconsistent with our rules for always measuring from the start of the recording.")]
public double TimeStart { get; private set; }
///
- /// Gets or sets units = seconds
- /// Time offset from start of current segment to end of the event.
- /// Written into the csv file under column "EventEndSeconds"
+ /// Gets the time offset (in seconds) from start of current segment to end of the event.
/// This field is NOT in EventBase. EventBase only requires TimeStart because it is designed to also accomodate points.
///
///
- /// Note: converted to private setter so we can control how this is set. Recommend using
- /// after event instantiation to modify bounds.
+ ///
+ /// NOTE: is relative to the start of a segment. This notion is obsolete!
+ /// Events must always be stored relative to start of the recording.
+ ///
+ /// Note: converted to private setter so we can control how this is set.
+ /// Recommend using after event instantiation to modify bounds.
///
+ [Obsolete("Bounds relative to the segment are inconsistent with our rules for always measuring from the start of the recording.")]
public double TimeEnd { get; private set; }
+ ///
+ /// Gets the end time of an event relative to the recording start.
+ ///
public double EventEndSeconds => this.TimeEnd + this.SegmentStartSeconds;
+ ///
+ /// Gets the start time of an event relative to the recording start.
+ ///
public override double EventStartSeconds => this.TimeStart + this.SegmentStartSeconds;
- public void SetEventPositionRelative(
- TimeSpan segmentStartOffset,
- double eventStartSegment,
- double eventEndSegment)
- {
- this.TimeStart = eventStartSegment;
- this.TimeEnd = eventEndSegment;
-
- this.SetEventStartRelative(segmentStartOffset, eventStartSegment);
- }
-
///
/// Gets or sets units = Hertz.
/// Proxied to EventBase.MinHz.
@@ -126,30 +126,39 @@ public void SetEventPositionRelative(
}
}
- /// Gets or sets units = Hertz
+ /// Gets or sets units = Hertz.
public double HighFrequencyHertz { get; set; }
+ ///
+ /// Gets the bandwidth of an acoustic event.
+ ///
public double Bandwidth => this.HighFrequencyHertz - this.LowFrequencyHertz + 1;
public bool IsMelscale { get; set; }
+ ///
+ /// Gets or sets the bounds of an event with respect to the segment start
+ /// BUT in terms of the frame count (from segment start) and frequency bin (from zero Hertz).
+ /// This is no longer the preferred way to operate with acoustic event bounds.
+ /// Better to use real units (seconds and Hertz) and provide the acoustic event with scale information.
+ ///
public Oblong Oblong { get; set; }
- /// Gets or sets required for conversions to & from MEL scale AND for drawing event on spectrum
+ /// Gets or sets required for conversions to & from MEL scale AND for drawing event on spectrum.
public int FreqBinCount { get; set; }
///
- /// Gets required for freq-binID conversions
+ /// Gets required for freq-binID conversions.
///
public double FreqBinWidth { get; private set; }
- /// Gets frame duration in seconds
+ /// Gets frame duration in seconds.
public double FrameDuration { get; private set; }
- /// Gets or sets time between frame starts in seconds. Inverse of FramesPerSecond
+ /// Gets or sets time between frame starts in seconds. Inverse of FramesPerSecond.
public double FrameOffset { get; set; }
- /// Gets or sets number of frame starts per second. Inverse of the frame offset
+ /// Gets or sets number of frame starts per second. Inverse of the frame offset.
public double FramesPerSecond { get; set; }
//PROPERTIES OF THE EVENTS i.e. Name, SCORE ETC
@@ -157,12 +166,7 @@ public void SetEventPositionRelative(
public string Name { get; set; }
- public string Name2 { get; set; }
-
- /// Gets or sets average score through the event.
- public string ScoreComment { get; set; }
-
- /// Gets or sets score normalised in range [0,1]. NOTE: Max is set = to five times user supplied threshold
+ /// Gets or sets score normalised in range [0,1]. NOTE: Max is set = to five times user supplied threshold.
public double ScoreNormalised { get; set; }
/// Gets max Possible Score: set = to 5x user supplied threshold. An arbitrary value used for score normalisation.
@@ -174,55 +178,47 @@ public void SetEventPositionRelative(
public string Score2Name { get; set; }
- /// Gets or sets second score if required
- public double Score2 { get; set; } // e.g. for Birgits recognisers
+ /// Gets or sets second score if required.
+ ///
+ [Obsolete("We should use another type of Event class to represent this concept")]
+ public double Score2 { get; set; }
+
+ /////
+ ///// Gets or sets a list of points that can be used to identifies features in spectrogram relative to the Event.
+ ///// i.e. Points can be outside of events and can have negative values.
+ ///// Point location is relative to the top left corner of the event.
+ /////
+ //public List Points { get; set; }
///
- /// Gets or sets a list of points that can be used to identifies features in spectrogram relative to the Event.
- /// i.e. Points can be outside of events and can have negative values.
- /// Point location is relative to the top left corner of the event.
+ /// Gets or sets the periodicity of acoustic energy in an event.
+ /// Use for events which have an oscillating acoustic energy - e.g. for frog calls.
///
- public List Points { get; set; }
-
- public double Periodicity { get; set; } // for events which have an oscillating acoustic energy - used for frog calls
+ public double Periodicity { get; set; }
public double DominantFreq { get; set; } // the dominant freq in the event - used for frog calls
- // double I1MeandB; //mean intensity of pixels in the event prior to noise subtraction
- // double I1Var; //,
- // double I2MeandB; // mean intensity of pixels in the event after Wiener filter, prior to noise subtraction
- // double I2Var; //,
- private double I3Mean; // mean intensity of pixels in the event AFTER noise reduciton - USED FOR CLUSTERING
- private double I3Var; // variance of intensity of pixels in the event.
-
- // following are no longer needed. Delete eventually.
- /*
- //KIWI SCORES
- public double kiwi_durationScore;
- public double kiwi_hitScore;
- public double kiwi_snrScore;
- public double kiwi_sdPeakScore;
- public double kiwi_intensityScore;
- public double kiwi_gridScore;
- public double kiwi_chirpScore;
- public double kiwi_bandWidthScore;
- public double kiwi_deltaPeriodScore;
- public double kiwi_comboScore;
- */
-
- /// Gets or sets a value indicating whether use this if want to filter or tag some members of a list for some purpose.
+ ///
+ /// Gets or sets a value that can be used to filter or tag some members of a list of acoustic events.
+ /// Was used for constructing data sets.
+ ///
public bool Tag { get; set; }
- /// Gets or sets assigned value when reading in a list of user identified events. Indicates a user assigned assessment of event intensity
+ /// Gets or sets assigned value when reading in a list of user identified events. Indicates a user assigned assessment of event intensity.
public int Intensity { get; set; }
- /// Gets or sets assigned value when reading in a list of user identified events. Indicates a user assigned assessment of event quality
+ /// Gets or sets assigned value when reading in a list of user identified events. Indicates a user assigned assessment of event quality.
public int Quality { get; set; }
public Color BorderColour { get; set; }
public Color ScoreColour { get; set; }
+ ///
+ /// Initializes a new instance of the class.
+ /// Sets some default colors for drawing an event on a spectrogram.
+ /// THis is the first of three constructors.
+ ///
public AcousticEvent()
{
this.BorderColour = DefaultBorderColor;
@@ -231,11 +227,20 @@ public AcousticEvent()
this.IsMelscale = false;
}
- public AcousticEvent(TimeSpan segmentStartOffset, double startTime, double eventDuration, double minFreq, double maxFreq)
+ ///
+ /// Initializes a new instance of the class.
+ /// This constructor requires the minimum information to establish the temporal and frequency bounds of an acoustic event.
+ ///
+ /// The start of the current segment relative to start of recording.
+ /// event start with respect to start of segment.
+ /// event end with respect to start of segment.
+ /// Lower frequency bound of event.
+ /// Upper frequency bound of event.
+ public AcousticEvent(TimeSpan segmentStartOffset, double eventStartSegmentRelative, double eventDuration, double minFreq, double maxFreq)
: this()
{
- this.SetEventPositionRelative(segmentStartOffset, startTime, startTime + eventDuration);
-
+ var eventEndSegmentRelative = eventStartSegmentRelative + eventDuration;
+ this.SetEventPositionRelative(segmentStartOffset, eventStartSegmentRelative, eventEndSegmentRelative);
this.LowFrequencyHertz = minFreq;
this.HighFrequencyHertz = maxFreq;
@@ -246,6 +251,8 @@ public AcousticEvent(TimeSpan segmentStartOffset, double startTime, double event
///
/// Initializes a new instance of the class.
/// This constructor currently works ONLY for linear Hertz scale events.
+ /// It requires the event bounds to provided (using Oblong) in terms of time frame and frequency bin counts.
+ /// Scale information must also be provided to convert bounds into real values (seconds, Hertz).
///
/// An oblong initialized with bin and frame numbers marking location of the event.
/// to set the freq scale.
@@ -288,22 +295,44 @@ public AcousticEvent(TimeSpan segmentStartOffset, Oblong o, int nyquistFrequency
///
public string Profile { get; set; }
- public void DoMelScale(bool doMelscale, int freqBinCount)
+ //public void DoMelScale(bool doMelscale, int freqBinCount)
+ //{
+ // this.IsMelscale = doMelscale;
+ // this.FreqBinCount = freqBinCount;
+ //}
+
+ ///
+ /// Set the start and end times of an event with respect to the segment start time
+ /// AND also calls method to set event start time with respect the recording/file start.
+ ///
+ public void SetEventPositionRelative(
+ TimeSpan segmentStartOffset,
+ double eventStartSegmentRelative,
+ double eventEndSegmentRelative)
{
- this.IsMelscale = doMelscale;
- this.FreqBinCount = freqBinCount;
+ this.TimeStart = eventStartSegmentRelative;
+ this.TimeEnd = eventEndSegmentRelative;
+
+ this.SetEventStartRelative(segmentStartOffset, eventStartSegmentRelative);
}
+ ///
+ /// THe only call to this method is from a no-longer used recogniser.
+ /// Could be deleted.
+ /// It sets the time and frequency scales for an event given the sr, and window size.
+ ///
public void SetTimeAndFreqScales(int samplingRate, int windowSize, int windowOffset)
{
- CalculateTimeScale(samplingRate, windowSize, windowOffset, out var frameDuration, out var frameOffset, out var framesPerSecond);
- this.FrameDuration = frameDuration; //frame duration in seconds
- this.FrameOffset = frameOffset; //frame offset in seconds
- this.FramesPerSecond = framesPerSecond; //inverse of the frame offset
+ //set the frame duration and offset in seconds
+ this.FrameDuration = windowSize / (double)samplingRate;
+ this.FrameOffset = windowOffset / (double)samplingRate;
+ this.FramesPerSecond = 1 / this.FrameOffset;
- CalculateFreqScale(samplingRate, windowSize, out var binCount, out var binWidth);
- this.FreqBinCount = binCount; //required for conversions to & from MEL scale
- this.FreqBinWidth = binWidth; //required for freq-binID conversions
+ //set the Freq Scale. Required for freq-binID conversions
+ this.FreqBinWidth = samplingRate / (double)windowSize;
+
+ //required for conversions to & from MEL scale
+ this.FreqBinCount = windowSize / 2;
if (this.Oblong == null)
{
@@ -326,9 +355,7 @@ public void SetTimeAndFreqScales(double frameOffset, double frameDuration, doubl
{
this.FramesPerSecond = 1 / frameOffset; //inverse of the frame offset
this.FrameDuration = frameDuration; //frame duration in seconds
- this.FrameOffset = frameOffset; //frame duration in seconds
-
- //this.FreqBinCount = binCount; //required for conversions to & from MEL scale
+ this.FrameOffset = frameOffset; //frame duration in seconds
this.FreqBinWidth = freqBinWidth; //required for freq-binID conversions
if (this.Oblong == null)
@@ -337,6 +364,37 @@ public void SetTimeAndFreqScales(double frameOffset, double frameDuration, doubl
}
}
+ ///
+ /// Converts the Hertz (frequency) bounds of an event to the frequency bin number.
+ /// The frequency bin is an index into the columns of the spectrogram data matrix.
+ /// Since the spectrogram data matrix is oriented with the origin at top left,
+ /// the low frequency bin will have a lower column index than the high freq bin.
+ ///
+ /// mel scale.
+ /// lower freq bound.
+ /// upper freq bound.
+ /// Nyquist freq in Herz.
+ /// frequency scale.
+ /// return bin index for lower freq bound.
+ /// return bin index for upper freq bound.
+ public static void ConvertHertzToFrequencyBin(bool doMelscale, int minFreq, int maxFreq, int nyquist, double binWidth, out int leftCol, out int rightCol)
+ {
+ if (doMelscale)
+ {
+ int binCount = (int)(nyquist / binWidth) + 1;
+ double maxMel = MFCCStuff.Mel(nyquist);
+ int melRange = (int)(maxMel - 0 + 1);
+ double binsPerMel = binCount / (double)melRange;
+ leftCol = (int)Math.Round(MFCCStuff.Mel(minFreq) * binsPerMel);
+ rightCol = (int)Math.Round(MFCCStuff.Mel(maxFreq) * binsPerMel);
+ }
+ else
+ {
+ leftCol = (int)Math.Round(minFreq / binWidth);
+ rightCol = (int)Math.Round(maxFreq / binWidth);
+ }
+ }
+
///
/// Calculates the matrix/image indices of the acoustic event, when given the time/freq scales.
/// This method called only by previous method:- Acousticevent.SetTimeAndFreqScales().
@@ -345,11 +403,12 @@ public void SetTimeAndFreqScales(double frameOffset, double frameDuration, doubl
///
public static Oblong ConvertEvent2Oblong(AcousticEvent ae)
{
- // Translate time dimension = frames = matrix rows.
- Time2RowIDs(ae.TimeStart, ae.EventDurationSeconds, ae.FrameOffset, out var topRow, out var bottomRow);
+ // Translate time dimension (seconds) to frames to matrix rows.
+ var topRow = (int)Math.Round(ae.TimeStart / ae.FrameOffset);
+ var bottomRow = (int)Math.Round((ae.TimeStart + ae.EventDurationSeconds) / ae.FrameOffset);
//Translate freq dimension = freq bins = matrix columns.
- Freq2BinIDs(ae.IsMelscale, (int)ae.LowFrequencyHertz, (int)ae.HighFrequencyHertz, ae.FreqBinCount, ae.FreqBinWidth, out var leftCol, out var rightCol);
+ ConvertHertzToFrequencyBin(ae.IsMelscale, (int)ae.LowFrequencyHertz, (int)ae.HighFrequencyHertz, ae.FreqBinCount, ae.FreqBinWidth, out var leftCol, out var rightCol);
return new Oblong(topRow, leftCol, bottomRow, rightCol);
}
@@ -377,21 +436,6 @@ public void SetScores(double score, double min, double max)
}
}
- public string WriteProperties()
- {
- return " min-max=" + this.LowFrequencyHertz + "-" + this.HighFrequencyHertz + ", " + this.Oblong.ColumnLeft + "-" + this.Oblong.ColumnRight;
- }
-
- ///
- /// Draws an event on the image. Uses the fields already set on the audio event to determine correct placement.
- /// Fields requireed to be set include: `FramesPerSecond`, `FreqBinWidth`.
- ///
- public void DrawEvent(Image sonogram)
- where T : unmanaged, IPixel
- {
- this.DrawEvent(sonogram, this.FramesPerSecond, this.FreqBinWidth, sonogram.Height);
- }
-
///
/// Draws an event on the image. Allows for custom specification of variables.
/// Drawing the event requires a time scale and a frequency scale. Hence the additional arguments.
@@ -417,6 +461,7 @@ public void DrawEvent(Image imageToReturn, double framesPerSecond, double
if (duration >= 0.0 && framesPerSecond >= 0.0)
{
t1 = (int)Math.Round(this.TimeStart * framesPerSecond);
+
t2 = (int)Math.Round(this.TimeEnd * framesPerSecond);
}
else if (this.Oblong != null)
@@ -426,8 +471,7 @@ public void DrawEvent(Image imageToReturn, double framesPerSecond, double
t2 = this.Oblong.RowBottom;
}
- imageToReturn.Mutate(g => g.DrawRectangle(borderPen, t1, y1, t2, y2));
-
+ imageToReturn.Mutate(g => g.NoAA().DrawRectangle(borderPen, t1, y1, t2, y2));
if (this.HitElements != null)
{
foreach (var hitElement in this.HitElements)
@@ -441,135 +485,83 @@ public void DrawEvent(Image imageToReturn, double framesPerSecond, double
int scoreHt = (int)Math.Round(eventHeight * this.ScoreNormalised);
imageToReturn.Mutate(g =>
{
- g.DrawLine(scorePen, t1, y2 - scoreHt, t1, y2);
+ g.NoAA().DrawLine(scorePen, t1, y2 - scoreHt, t1, y2 + 1);
g.DrawTextSafe(this.Name, Drawing.Tahoma6, Color.Black, new PointF(t1, y1 - 4));
});
}
- ///
- /// Passed point is relative to top-left corner of the Acoustic Event.
- /// Oblong needs to be set for this method to work.
- ///
- public void DrawPoint(Image bmp, Point point, Color colour)
- {
- if (bmp == null)
- {
- return;
- }
-
- int maxFreqBin = (int)Math.Round(this.HighFrequencyHertz / this.FreqBinWidth);
- int row = bmp.Height - maxFreqBin - 1 + point.Y;
- int t1 = (int)Math.Round(this.TimeStart * this.FramesPerSecond); // temporal start of event
- int col = t1 + point.X;
- if (row >= bmp.Height)
- {
- row = bmp.Height - 1;
- }
-
- bmp[col, row] = colour;
- }
+ //#################################################################################################################
+ //FOLLOWING METHODS DEAL WITH THE OVERLAP OF EVENTS
///
- /// Returns the first event in the passed list which overlaps with this one IN THE SAME RECORDING.
- /// If no event overlaps return null.
+ /// Determines if two events overlap in frequency.
///
- public AcousticEvent OverlapsEventInList(List events)
+ /// event one.
+ /// event two.
+ /// true if events overlap.
+ public static bool EventsOverlapInFrequency(AcousticEvent event1, AcousticEvent event2)
{
- foreach (AcousticEvent ae in events)
+ //check if event 1 freq band overlaps event 2 freq band
+ if (event1.HighFrequencyHertz >= event2.LowFrequencyHertz && event1.HighFrequencyHertz <= event2.HighFrequencyHertz)
{
- if (this.FileName.Equals(ae.FileName) && this.Overlaps(ae))
- {
- return ae;
- }
+ return true;
}
- return null;
- }
-
- ///
- /// Returns true/false if this event time-overlaps the passed event.
- ///
- public bool Overlaps(AcousticEvent ae)
- {
- return EventsOverlap(this, ae);
- }
-
- ///
- /// Returns the fractional overlap of two events.
- /// Translate time/freq dimensions to coordinates in a matrix.
- /// Freq dimension = bins = matrix columns. Origin is top left - as per matrix in the sonogram class.
- /// Time dimension = frames = matrix rows.
- ///
- public static double EventFractionalOverlap(AcousticEvent event1, AcousticEvent event2)
- {
- //if (event1.EndTime < event2.StartTime) return 0.0;
- //if (event2.EndTime < event1.StartTime) return 0.0;
- //if (event1.MaxFreq < event2.MinFreq) return 0.0;
- //if (event2.MaxFreq < event1.MinFreq) return 0.0;
- //at this point the two events do overlap
-
- int timeOverlap = Oblong.RowOverlap(event1.Oblong, event2.Oblong);
- if (timeOverlap == 0)
+ // check if event 1 freq band overlaps event 2 freq band
+ if (event1.LowFrequencyHertz >= event2.LowFrequencyHertz && event1.LowFrequencyHertz <= event2.HighFrequencyHertz)
{
- return 0.0;
+ return true;
}
- int hzOverlap = Oblong.ColumnOverlap(event1.Oblong, event2.Oblong);
- if (hzOverlap == 0)
+ //check if event 2 freq band overlaps event 1 freq band
+ if (event2.HighFrequencyHertz >= event1.LowFrequencyHertz && event2.HighFrequencyHertz <= event1.HighFrequencyHertz)
{
- return 0.0;
+ return true;
}
- int overlapArea = timeOverlap * hzOverlap;
- double fractionalOverlap1 = overlapArea / (double)event1.Oblong.Area();
- double fractionalOverlap2 = overlapArea / (double)event2.Oblong.Area();
-
- if (fractionalOverlap1 > fractionalOverlap2)
+ // check if event 2 freq band overlaps event 1 freq band
+ if (event2.LowFrequencyHertz >= event1.LowFrequencyHertz && event2.LowFrequencyHertz <= event1.HighFrequencyHertz)
{
- return fractionalOverlap1;
- }
- else
- {
- return fractionalOverlap2;
+ return true;
}
+
+ return false;
}
///
- /// Determines if two events overlap in time or frequency or both.
+ /// Determines if two events overlap in time.
///
/// event one.
/// event two.
/// true if events overlap.
- public static bool EventsOverlap(AcousticEvent event1, AcousticEvent event2)
+ public static bool EventsOverlapInTime(AcousticEvent event1, AcousticEvent event2)
{
- var timeOverlap = false;
- var freqOverlap = false;
-
//check if event 1 starts within event 2
if (event1.EventStartSeconds >= event2.EventStartSeconds && event1.EventStartSeconds <= event2.EventEndSeconds)
{
- timeOverlap = true;
+ return true;
}
// check if event 1 ends within event 2
if (event1.EventEndSeconds >= event2.EventStartSeconds && event1.EventEndSeconds <= event2.EventEndSeconds)
{
- timeOverlap = true;
+ return true;
}
- //check if event 1 freq band overlaps event 2 freq band
- if (event1.HighFrequencyHertz >= event1.LowFrequencyHertz && event1.HighFrequencyHertz <= event2.HighFrequencyHertz)
+ // now check possibility that event2 is inside event1.
+ //check if event 2 starts within event 1
+ if (event2.EventStartSeconds >= event1.EventStartSeconds && event2.EventStartSeconds <= event1.EventEndSeconds)
{
- freqOverlap = true;
+ return true;
}
- // check if event 1 freq band overlaps event 2 freq band
- if (event1.LowFrequencyHertz >= event2.LowFrequencyHertz && event1.LowFrequencyHertz <= event2.HighFrequencyHertz)
+ // check if event 2 ends within event 1
+ if (event2.EventEndSeconds >= event1.EventStartSeconds && event2.EventEndSeconds <= event1.EventEndSeconds)
{
- freqOverlap = true;
+ return true;
}
- return timeOverlap && freqOverlap;
+ return false;
}
///
@@ -578,7 +570,7 @@ public static bool EventsOverlap(AcousticEvent event1, AcousticEvent event2)
/// Freq dimension = bins = matrix columns. Origin is top left - as per matrix in the sonogram class.
/// Time dimension = frames = matrix rows.
///
- public static List CombineOverlappingEvents(List events)
+ public static List CombineOverlappingEvents(List events, TimeSpan segmentStartOffset)
{
if (events.Count < 2)
{
@@ -589,9 +581,9 @@ public static List CombineOverlappingEvents(List e
{
for (int j = i - 1; j >= 0; j--)
{
- if (EventsOverlap(events[i], events[j]))
+ if (EventsOverlapInTime(events[i], events[j]) && EventsOverlapInFrequency(events[i], events[j]))
{
- events[j] = AcousticEvent.MergeTwoEvents(events[i], events[j]);
+ events[j] = AcousticEvent.MergeTwoEvents(events[i], events[j], segmentStartOffset);
events.RemoveAt(i);
break;
}
@@ -601,158 +593,76 @@ public static List CombineOverlappingEvents(List e
return events;
}
- public static AcousticEvent MergeTwoEvents(AcousticEvent e1, AcousticEvent e2)
+ public static AcousticEvent MergeTwoEvents(AcousticEvent e1, AcousticEvent e2, TimeSpan segmentStartOffset)
{
- //e1.EventEndSeconds = Math.Max(e1.EventEndSeconds, e2.EventEndSeconds);
- e1.EventStartSeconds = Math.Min(e1.EventStartSeconds, e2.EventStartSeconds);
+ //segmentStartOffset = TimeSpan.Zero;
+ var minTime = Math.Min(e1.TimeStart, e2.TimeStart);
+ var maxTime = Math.Max(e1.TimeEnd, e2.TimeEnd);
+ e1.SetEventPositionRelative(segmentStartOffset, minTime, maxTime);
e1.LowFrequencyHertz = Math.Min(e1.LowFrequencyHertz, e2.LowFrequencyHertz);
e1.HighFrequencyHertz = Math.Max(e1.HighFrequencyHertz, e2.HighFrequencyHertz);
-
- //e1.ResultMinute = (int)e1.ResultStartSeconds.Floor();
+ e1.Score = Math.Max(e1.Score, e2.Score);
+ e1.ScoreNormalised = Math.Max(e1.ScoreNormalised, e2.ScoreNormalised);
e1.ResultStartSeconds = e1.EventStartSeconds;
return e1;
}
- //#################################################################################################################
- //METHODS TO CONVERT BETWEEN FREQ BIN AND HERZ OR MELS
-
///
- /// converts frequency bounds of an event to left and right columns of object in sonogram matrix
- /// NOTE: binCount is required only if freq is in Mel scale
+ /// Returns the first event in the passed list which overlaps with this one IN THE SAME RECORDING.
+ /// If no event overlaps return null.
///
- /// mel scale
- /// lower freq bound
- /// upper freq bound
- /// Nyquist freq in Herz
- /// frequency scale
- /// return bin index for lower freq bound
- /// return bin index for upper freq bound
- public static void Freq2BinIDs(bool doMelscale, int minFreq, int maxFreq, int nyquist, double binWidth, out int leftCol, out int rightCol)
+ public AcousticEvent OverlapsEventInList(List events)
{
- if (doMelscale)
- {
- Freq2MelsBinIDs(minFreq, maxFreq, binWidth, nyquist, out leftCol, out rightCol);
- }
- else
+ foreach (AcousticEvent ae in events)
{
- Freq2HerzBinIDs(minFreq, maxFreq, binWidth, out leftCol, out rightCol);
+ if (this.FileName.Equals(ae.FileName) && EventsOverlapInTime(this, ae))
+ {
+ return ae;
+ }
}
- }
-
- public static void Freq2HerzBinIDs(int minFreq, int maxFreq, double binWidth, out int leftCol, out int rightCol)
- {
- leftCol = (int)Math.Round(minFreq / binWidth);
- rightCol = (int)Math.Round(maxFreq / binWidth);
- }
-
- public static void Freq2MelsBinIDs(int minFreq, int maxFreq, double binWidth, int nyquistFrequency, out int leftCol, out int rightCol)
- {
- int binCount = (int)(nyquistFrequency / binWidth) + 1;
- double maxMel = MFCCStuff.Mel(nyquistFrequency);
- int melRange = (int)(maxMel - 0 + 1);
- double binsPerMel = binCount / (double)melRange;
- leftCol = (int)Math.Round(MFCCStuff.Mel(minFreq) * binsPerMel);
- rightCol = (int)Math.Round(MFCCStuff.Mel(maxFreq) * binsPerMel);
- }
-
- //#################################################################################################################
- //METHODS TO CONVERT BETWEEN TIME BIN AND SECONDS
-
- public static void Time2RowIDs(double startTime, double duration, double frameOffset, out int topRow, out int bottomRow)
- {
- topRow = (int)Math.Round(startTime / frameOffset);
- bottomRow = (int)Math.Round((startTime + duration) / frameOffset);
- }
-
- public void SetNetIntensityAfterNoiseReduction(double mean, double var)
- {
- this.I3Mean = mean;
- this.I3Var = var;
- }
- ///
- /// returns the frame duration and offset duration in seconds.
- ///
- /// signal samples per second.
- /// number of signal samples in one window or frame.
- /// number of signal samples between start of one frame and start of next frame.
- /// units = seconds.
- /// units = second.
- /// number of frames in one second.
- public static void CalculateTimeScale(int samplingRate, int windowSize, int windowOffset, out double frameDuration, out double frameOffset, out double framesPerSecond)
- {
- frameDuration = windowSize / (double)samplingRate;
- frameOffset = windowOffset / (double)samplingRate;
- framesPerSecond = 1 / frameOffset;
+ return null;
}
+ /*
///
- /// return Nyquist / binCount.
+ /// This method not currently called but is POTENTIALLY USEFUL.
+ /// Returns the fractional overlap of two events.
+ /// Translate time/freq dimensions to coordinates in a matrix.
+ /// Freq dimension = bins = matrix columns. Origin is top left - as per matrix in the sonogram class.
+ /// Time dimension = frames = matrix rows.
///
- public static void CalculateFreqScale(int samplingRate, int windowSize, out int binCount, out double binWidth)
- {
- binCount = windowSize / 2;
- binWidth = samplingRate / (double)windowSize;
- }
-
- public static void WriteEvents(List eventList, ref StringBuilder sb)
+ public static double EventFractionalOverlap(AcousticEvent event1, AcousticEvent event2)
{
- if (eventList.Count == 0)
+ int timeOverlap = Oblong.RowOverlap(event1.Oblong, event2.Oblong);
+ if (timeOverlap == 0)
{
- string line =
- $"# Event Name\t{"Start",8:f3}\t{"End",6:f3}\t{"MinF"}\t{"MaxF"}\t{"Score1":f2}\t{"Score2":f1}\t{"SourceFile"}";
- sb.AppendLine(line);
- line = $"{"NoEvent"}\t{0.000,8:f3}\t{0.000,8:f3}\t{"N/A"}\t{"N/A"}\t{0.000:f2}\t{0.000:f1}\t{"N/A"}";
- sb.AppendLine(line);
+ return 0.0;
}
- else
+
+ int hzOverlap = Oblong.ColumnOverlap(event1.Oblong, event2.Oblong);
+ if (hzOverlap == 0)
{
- AcousticEvent ae1 = eventList[0];
- string line =
- $"# Event Name\t{"Start",8:f3}\t{"End",6:f3}\t{"MinF"}\t{"MaxF"}\t{"Score":f2}\t{ae1.Score2Name:f1}\t{"SourceFile"}";
- sb.AppendLine(line);
- foreach (AcousticEvent ae in eventList)
- {
- line =
- $"{ae.Name}\t{ae.TimeStart,8:f3}\t{ae.TimeEnd,8:f3}\t{ae.LowFrequencyHertz}\t{ae.HighFrequencyHertz}\t{ae.Score:f2}\t{ae.Score2:f1}\t{ae.FileName}";
- sb.AppendLine(line);
- }
+ return 0.0;
}
- }
- ///
- /// used to write lists of acousitc event data to an excell spread sheet.
- ///
- public static StringBuilder WriteEvents(List eventList, string str)
- {
- StringBuilder sb = new StringBuilder();
- if (eventList.Count == 0)
+ int overlapArea = timeOverlap * hzOverlap;
+ double fractionalOverlap1 = overlapArea / (double)event1.Oblong.Area();
+ double fractionalOverlap2 = overlapArea / (double)event2.Oblong.Area();
+
+ if (fractionalOverlap1 > fractionalOverlap2)
{
- string line = string.Format(
- str + "\t{0}\t{1,8:f3}\t{2,8:f3}\t{3}\t{4}\t{5:f2}\t{6:f1}\t{7}", "NoEvent", 0.000, 0.000, "N/A", "N/A", 0.000, 0.000, "N/A");
- sb.AppendLine(line);
+ return fractionalOverlap1;
}
else
{
- foreach (AcousticEvent ae in eventList)
- {
- string line = string.Format(
- str + "\t{0}\t{1,8:f3}\t{2,8:f3}\t{3}\t{4}\t{5:f2}\t{6:f1}\t{7}",
- ae.Name,
- ae.TimeStart,
- ae.TimeEnd,
- ae.LowFrequencyHertz,
- ae.HighFrequencyHertz,
- ae.Score,
- ae.Score2,
- ae.FileName);
-
- sb.AppendLine(line);
- }
+ return fractionalOverlap2;
}
-
- return sb;
}
+ */
+
+ //#################################################################################################################
+ //METHODS FOR SEGMENTATION OF A FREQ BAND BASED ON ACOUSTIC ENERGY
///
/// Segments or not depending value of boolean doSegmentation.
@@ -794,16 +704,37 @@ public static Tuple, double, double, double, double[]> GetSe
return tuple;
}
- public static Tuple, double, double, double, double[]> GetSegmentationEvents(SpectrogramStandard sonogram, TimeSpan segmentStartOffset,
- int minHz, int maxHz, double smoothWindow, double thresholdSD, double minDuration, double maxDuration)
+ ///
+ /// Segments the acoustic energy in the passed frequency band and returns as list of acoustic events.
+ /// Noise reduction is done first.
+ ///
+ /// the full spectrogram.
+ /// Start of current segment relative to the recording start.
+ /// Bottom of the required frequency band.
+ /// Top of the required frequency band.
+ /// To smooth the amplitude array.
+ /// Determines the threshold for an acoustic event.
+ /// Minimum duration of an acceptable acoustic event.
+ /// Maximum duration of an acceptable acoustic event.
+ /// a list of acoustic events.
+ public static Tuple, double, double, double, double[]> GetSegmentationEvents(
+ SpectrogramStandard sonogram,
+ TimeSpan segmentStartOffset,
+ int minHz,
+ int maxHz,
+ double smoothWindow,
+ double thresholdSD,
+ double minDuration,
+ double maxDuration)
{
int nyquist = sonogram.SampleRate / 2;
var tuple = SNR.SubbandIntensity_NoiseReduced(sonogram.Data, minHz, maxHz, nyquist, smoothWindow, sonogram.FramesPerSecond);
double[] intensity = tuple.Item1; //noise reduced intensity array
- double Q = tuple.Item2; //baseline dB in the original scale
+ double baselineDb = tuple.Item2; //baseline dB in the original scale
double oneSD = tuple.Item3; //1 SD in dB around the baseline
double dBThreshold = thresholdSD * oneSD;
+ // get list of acoustic events
var segmentEvents = ConvertIntensityArray2Events(
intensity,
segmentStartOffset,
@@ -817,13 +748,16 @@ public static Tuple, double, double, double, double[]> GetSe
foreach (AcousticEvent ev in segmentEvents)
{
ev.FileName = sonogram.Configuration.SourceFName;
-
- //ev.Name = callName;
}
- return Tuple.Create(segmentEvents, Q, oneSD, dBThreshold, intensity);
+ return Tuple.Create(segmentEvents, baselineDb, oneSD, dBThreshold, intensity);
}
+ //##############################################################################################################################################
+ // THE NEXT FOUR METHODS ARE NOT CURRENTLY CALLED.
+ // THEY WERE USED FOR COLLECTING EVENTS INTO DATA SETS for Machine Learning purposes. (Kiwi publications)
+ // MAY BE USEFUL IN FUTURE
+
///
/// returns all the events in a list that occur in the recording with passed file name.
///
@@ -839,7 +773,7 @@ public static List GetEventsInFile(List eventList,
}
return events;
- } // end method GetEventsInFile(List eventList, string fileName)
+ }
public static List GetTaggedEventsInFile(List labeledEvents, string filename)
{
@@ -919,7 +853,7 @@ public static void CalculateAccuracy(List results, List results, List results, List
/// Given two lists of AcousticEvents, one being labelled events and the other being predicted events,
@@ -988,8 +927,16 @@ public static void CalculateAccuracy(List results, List
- public static void CalculateAccuracyOnOneRecording(List results, List labels, out int tp, out int fp, out int fn,
- out double precision, out double recall, out double accuracy, out string resultsText)
+ public static void CalculateAccuracyOnOneRecording(
+ List results,
+ List labels,
+ out int tp,
+ out int fp,
+ out int fn,
+ out double precision,
+ out double recall,
+ out double accuracy,
+ out string resultsText)
{
//init values
tp = 0;
@@ -1042,7 +989,12 @@ public static void CalculateAccuracyOnOneRecording(List results,
fn++;
line = string.Format(
"False NEGATIVE: {0,4} {5,15} {1,6:f1} ...{2,6:f1} intensity={3} quality={4}",
- count, ae.TimeStart, ae.TimeEnd, ae.Intensity, ae.Quality, ae.Name);
+ count,
+ ae.TimeStart,
+ ae.TimeEnd,
+ ae.Intensity,
+ ae.Quality,
+ ae.Name);
sb.Append(line + "\t" + ae.FileName + "\n");
}
}
@@ -1068,12 +1020,10 @@ public static void CalculateAccuracyOnOneRecording(List results,
accuracy = (precision + recall) / 2;
resultsText = sb.ToString();
- } //end method
+ }
//##############################################################################################################################################
-// THE NEXT THREE METHODS CONVERT BETWEEN SCORE ARRAYS AND ACOUSTIC EVENTS
-// THE NEXT TWO METHOD CONVERT AN ARRAY OF SCORE (USUALLY INTENSITY VALUES IN A SUB-BAND) TO ACOUSTIC EVENTS.
-// THE THIRD METHOD PRODUCES A SCORE ARRAY GIVEN A LIST OF EVENTS.
+// THE NEXT THREE METHODS CONVERT AN ARRAY OF SCORE VALUES (USUALLY INTENSITY VALUES IN A SUB-BAND) TO ACOUSTIC EVENTS.
public static List ConvertIntensityArray2Events(
double[] values,
@@ -1093,17 +1043,20 @@ public static List ConvertIntensityArray2Events(
double startTime = 0.0;
int startFrame = 0;
- for (int i = 0; i < count; i++) //pass over all frames
+ //pass over all frames
+ for (int i = 0; i < count; i++)
{
- if (isHit == false && values[i] > scoreThreshold) //start of an event
+ //start of an event
+ if (isHit == false && values[i] > scoreThreshold)
{
isHit = true;
startTime = i * frameOffset;
startFrame = i;
}
else //check for the end of an event
- if (isHit && values[i] <= scoreThreshold) //this is end of an event, so initialise it
+ if (isHit && values[i] <= scoreThreshold)
{
+ //this is end of an event, so initialise it
isHit = false;
double endTime = i * frameOffset;
double duration = endTime - startTime;
@@ -1114,9 +1067,10 @@ public static List ConvertIntensityArray2Events(
continue; //skip events with duration shorter than threshold
}
- AcousticEvent ev = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz);
-
- ev.Name = "Acoustic Segment"; //default name
+ AcousticEvent ev = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz)
+ {
+ Name = "Acoustic Segment", //default name
+ };
ev.SetTimeAndFreqScales(framesPerSec, freqBinWidth);
//obtain average intensity score.
@@ -1135,7 +1089,7 @@ public static List ConvertIntensityArray2Events(
}
///
- /// Given a time series of acoustic amplitude (typically in decibels), this method finds events that match the passed constraints.
+ /// Given a time series of acoustic amplitude (typically in decibels), finds events that match the passed constraints.
///
/// an array of amplitude values, typically decibel values.
/// not sure what this is about!.
@@ -1247,9 +1201,9 @@ public static List GetEventsAroundMaxima(
/// The method uses the passed scoreThreshold in order to calculate a normalised score.
/// Max possible score := threshold * 5.
/// normalised score := score / maxPossibleScore.
- /// Some analysis techniques (e.g. OD) have their own methods for extracting events from score arrays.
+ /// Some analysis techniques (e.g. Oscillation Detection) have their own methods for extracting events from score arrays.
///
- /// the array of scores
+ /// the array of scores.
/// lower freq bound of the acoustic event.
/// upper freq bound of the acoustic event.
/// the time scale required by AcousticEvent class.
@@ -1257,7 +1211,7 @@ public static List GetEventsAroundMaxima(
/// threshold.
/// duration of event must exceed this to count as an event.
/// duration of event must be less than this to count as an event.
- /// offset.
+ /// offset.
/// a list of acoustic events.
public static List ConvertScoreArray2Events(
double[] scores,
@@ -1268,14 +1222,14 @@ public static List ConvertScoreArray2Events(
double scoreThreshold,
double minDuration,
double maxDuration,
- TimeSpan segmentStartOffset)
+ TimeSpan segmentStart)
{
int count = scores.Length;
var events = new List();
double maxPossibleScore = 5 * scoreThreshold; // used to calculate a normalised score between 0 - 1.0
bool isHit = false;
- double frameOffset = 1 / framesPerSec; // frame offset in fractions of second
- double startTime = 0.0;
+ double frameOffset = 1 / framesPerSec;
+ double startTimeInSegment = 0.0; // units = seconds
int startFrame = 0;
// pass over all frames
@@ -1285,7 +1239,7 @@ public static List ConvertScoreArray2Events(
{
//start of an event
isHit = true;
- startTime = i * frameOffset;
+ startTimeInSegment = i * frameOffset;
startFrame = i;
}
else // check for the end of an event
@@ -1294,12 +1248,13 @@ public static List ConvertScoreArray2Events(
// this is end of an event, so initialise it
isHit = false;
double endTime = i * frameOffset;
- double duration = endTime - startTime;
+ double duration = endTime - startTimeInSegment;
// if (duration < minDuration) continue; //skip events with duration shorter than threshold
if (duration < minDuration || duration > maxDuration)
{
- continue; //skip events with duration shorter than threshold
+ //skip events with duration shorter than threshold
+ continue;
}
// obtain an average score for the duration of the potential event.
@@ -1311,18 +1266,13 @@ public static List ConvertScoreArray2Events(
av /= i - startFrame + 1;
- //NOTE av cannot be < threhsold because event started and ended based on threhsold.
- // Therefore remove the following condition on 04/02/2020
- //if (av < scoreThreshold)
- //{
- // continue; //skip events whose score is < the threshold
- //}
-
- AcousticEvent ev = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz);
-
+ // Initialize the event.
+ AcousticEvent ev = new AcousticEvent(segmentStart, startTimeInSegment, duration, minHz, maxHz);
ev.SetTimeAndFreqScales(framesPerSec, freqBinWidth);
ev.Score = av;
- ev.ScoreNormalised = ev.Score / maxPossibleScore; // normalised to the user supplied threshold
+
+ // normalised to the user supplied threshold
+ ev.ScoreNormalised = ev.Score / maxPossibleScore;
if (ev.ScoreNormalised > 1.0)
{
ev.ScoreNormalised = 1.0;
@@ -1344,14 +1294,17 @@ public static List ConvertScoreArray2Events(
events.Add(ev);
}
- } //end of pass over all frames
+ }
return events;
- } //end method ConvertScoreArray2Events()
+ }
///
- /// Extracts an array of scores from a list of events.
- /// The events are required to have the passed name.
+ /// FOR POSSIBLE DELETION!
+ /// THis method called only once from a frog recogniser class that is no longer used> LitoriaCaerulea:RecognizerBase.
+ /// THis method is potentially useful but can be deleted.
+ /// Attempts to reconstruct an array of scores from a list of acoustic events.
+ /// The events are required to have the passed name (a filter).
/// The events are assumed to contain sufficient info about frame rate in order to populate the array.
///
public static double[] ExtractScoreArrayFromEvents(List events, int arraySize, string nameOfTargetEvent)
@@ -1365,12 +1318,12 @@ public static double[] ExtractScoreArrayFromEvents(List events, i
double windowOffset = events[0].FrameOffset;
double frameRate = 1 / windowOffset; //frames per second
- //int count = events.Count;
- foreach ( AcousticEvent ae in events)
+ foreach (AcousticEvent ae in events)
{
if (!ae.Name.Equals(nameOfTargetEvent))
{
- continue; //skip irrelevant events
+ //skip irrelevant events
+ continue;
}
int startFrame = (int)(ae.TimeStart * frameRate);
@@ -1386,39 +1339,7 @@ public static double[] ExtractScoreArrayFromEvents(List events, i
}
//##############################################################################################################################################
-
- ///
- /// TODO: THis should be deprecated!
- /// This method is used to do unit test on lists of events.
- /// First developed for frog recognizers - October 2016.
- ///
- public static void TestToCompareEvents(string fileName, DirectoryInfo opDir, string testName, List events)
- {
- var testDir = new DirectoryInfo(opDir + $"\\UnitTest_{testName}");
- var benchmarkDir = new DirectoryInfo(testDir + "\\ExpectedOutput");
- if (!benchmarkDir.Exists)
- {
- benchmarkDir.Create();
- }
-
- var benchmarkFilePath = Path.Combine(benchmarkDir.FullName, fileName + ".TestEvents.csv");
- var eventsFilePath = Path.Combine(testDir.FullName, fileName + ".Events.csv");
- var eventsFile = new FileInfo(eventsFilePath);
- Csv.WriteToCsv(eventsFile, events);
-
- LoggedConsole.WriteLine($"# EVENTS TEST: Comparing List of {testName} events with those in benchmark file:");
- var benchmarkFile = new FileInfo(benchmarkFilePath);
- if (!benchmarkFile.Exists)
- {
- LoggedConsole.WriteWarnLine(" A file of test/benchmark events does not exist. Writing output as future events-test file");
- Csv.WriteToCsv(benchmarkFile, events);
- }
- else
- {
- // compare the test events with benchmark
- TestTools.FileEqualityTest("Compare acoustic events.", eventsFile, benchmarkFile);
- }
- }
+ // METHODS to CLUSTER acoustic events
///
/// Although not currently used, this method and following methods could be useful in future for clustering of events.
diff --git a/src/AudioAnalysisTools/StandardSpectrograms/Image_MultiTrack.cs b/src/AudioAnalysisTools/StandardSpectrograms/Image_MultiTrack.cs
index 1e36e0e25..f3d3bfd7e 100644
--- a/src/AudioAnalysisTools/StandardSpectrograms/Image_MultiTrack.cs
+++ b/src/AudioAnalysisTools/StandardSpectrograms/Image_MultiTrack.cs
@@ -353,33 +353,42 @@ public Image OverlayRedTransparency(Image bmp)
}
///
- /// superimposes a matrix of scores on top of a sonogram.
- /// TODO: WARNING: THIS METHOD IS YET TO BE DEBUGGED SINCE TRANSITION TO SIX-LABOURS, FEB 2020.
+ /// It is assumed that the spectrogram image is grey scale.
+ /// NOTE: The score matrix must consist of reals in [0.0, 1.0].
+ /// NOTE: The image and the score matrix must have the same number of rows and columns.
+ /// In case of a spectrogram, it is assumed that the rows are frequency bins and the columns are individual spectra.
///
+ /// the spectrogram image.
+ /// the matrix of scores or hits.
public static Image OverlayScoresAsRedTransparency(Image bmp, double[,] hits)
{
Image newBmp = (Image)bmp.Clone();
int rows = hits.GetLength(0);
int cols = hits.GetLength(1);
- int imageHt = bmp.Height - 1; //subtract 1 because indices start at zero
- //traverse columns - skip DC column
- for (int c = 1; c < cols; c++)
+ if (rows != bmp.Height || cols != bmp.Width)
{
- for (int r = 0; r < rows; r++)
+ LoggedConsole.WriteErrorLine("ERROR: Image and hits matrix do not have the same dimensions.");
+ return bmp;
+ }
+
+ for (int r = 0; r < rows; r++)
+ {
+ for (int c = 0; c < cols; c++)
{
- if (hits[r, c] == 0.0)
+ if (hits[r, c] <= 0.0)
{
continue;
}
- var pixel = bmp[r, imageHt - c];
- if (pixel.R == 255)
+ if (hits[r, c] > 1.0)
{
- continue; // white
+ hits[r, c] = 1.0;
}
- newBmp[r, imageHt - c] = Color.FromRgb(255, pixel.G, pixel.B);
+ var pixel = bmp[r, c];
+ byte value = (byte)Math.Floor(hits[r, c] * 255);
+ newBmp[r, c] = Color.FromRgb(value, pixel.G, pixel.B);
}
}
@@ -387,8 +396,54 @@ public static Image OverlayScoresAsRedTransparency(Image bmp, doub
}
///
- /// superimposes a matrix of scores on top of a sonogram. USES RAINBOW PALLETTE.
- /// ASSUME MATRIX NORMALIZED IN [0,1].
+ /// Overlays a matrix of scores on an image, typically a spectrogram image.
+ /// It is assumed that the spectrogram image is grey scale.
+ /// NOTE: The score matrix must consist of integers from 0 to 255.
+ /// NOTE: The image and the score matrix must have the same number of rows and columns.
+ /// In case of a spectrogram, it is assumed that the rows are frequency bins and the columns are individual spectra.
+ ///
+ /// the spectrogram image.
+ /// the matrix of scores or hits.
+ /// The new image with overlay of scores as red transparency.
+ public static Image OverlayScoresAsRedTransparency(Image bmp, int[,] hits)
+ {
+ Image newBmp = (Image)bmp.Clone();
+ int rows = hits.GetLength(0);
+ int cols = hits.GetLength(1);
+
+ if (rows != bmp.Height || cols != bmp.Width)
+ {
+ LoggedConsole.WriteErrorLine("ERROR: Image and hits matrix do not have the same dimensions.");
+ return bmp;
+ }
+
+ for (int r = 0; r < rows; r++)
+ {
+ for (int c = 0; c < cols; c++)
+ {
+ if (hits[r, c] <= 0)
+ {
+ continue;
+ }
+
+ if (hits[r, c] > 255)
+ {
+ hits[r, c] = 255;
+ }
+
+ var pixel = bmp[c, r];
+ newBmp[c, r] = Color.FromRgb((byte)hits[r, c], pixel.G, pixel.B);
+ }
+ }
+
+ return newBmp;
+ }
+
+ ///
+ /// WARNING: THis method is yet to be debugged and tested following move to SixLabors drawing libraries.
+ /// superimposes a matrix of scores on top of a sonogram.
+ /// USES A PALLETTE of ten RAINBOW colours.
+ /// ASSUME MATRIX is NORMALIZED IN [0,1].
///
public void OverlayRainbowTransparency(IImageProcessingContext g, Image bmp)
{
diff --git a/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramSettings.cs b/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramSettings.cs
index 7d2ce4252..820e0d524 100644
--- a/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramSettings.cs
+++ b/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramSettings.cs
@@ -5,7 +5,7 @@
namespace AudioAnalysisTools.StandardSpectrograms
{
using System;
- using DSP;
+ using AudioAnalysisTools.DSP;
using TowseyLibrary;
public class SpectrogramSettings
@@ -16,6 +16,9 @@ public class SpectrogramSettings
///
public string SourceFileName { get; set; }
+ ///
+ /// Gets or sets the window or frame size. 512 is usually a suitable choice for recordings of the environment.
+ ///
public int WindowSize { get; set; } = 512;
public double WindowOverlap { get; set; } = 0.0;
@@ -26,8 +29,18 @@ public class SpectrogramSettings
///
public int WindowStep { get; set; } = 512;
- public string WindowFunction { get; set; } = WindowFunctions.HAMMING.ToString();
+ ///
+ /// Gets or sets the default FFT Window function to the Hanning window.
+ /// THe Hanning window was made default in March 2020 because it was found to produce better spectrograms
+ /// in cases where the recording is resampled up or down.
+ ///
+ public string WindowFunction { get; set; } = WindowFunctions.HANNING.ToString();
+ ///
+ /// Gets or sets the smoothing window.
+ /// Following the FFT, each spectrum is smoothed with a moving average filter to reduce its variance.
+ /// We do the minimum smoothing in order to retain spectral definition.
+ ///
public int SmoothingWindow { get; set; } = 3;
public bool DoMelScale { get; set; } = false;
@@ -49,6 +62,10 @@ public class SpectrogramAttributes
public double MaxAmplitude { get; set; }
+ ///
+ /// Gets or sets the maximum frequency that can be represented given the signals sampling rate.
+ /// The Nyquist is half the SR.
+ ///
public int NyquistFrequency { get; set; }
public TimeSpan Duration { get; set; }
@@ -56,7 +73,7 @@ public class SpectrogramAttributes
public int FrameCount { get; set; }
///
- /// Gets or sets duration of full frame or window in seconds
+ /// Gets or sets duration of full frame or window in seconds.
///
public TimeSpan FrameDuration { get; set; }
@@ -64,9 +81,15 @@ public class SpectrogramAttributes
public double FBinWidth { get; set; }
- //this.FBinWidth = this.NyquistFrequency / (double) this.FreqBinCount;
+ ///
+ /// Gets or sets the real value difference between two adjacent values of the 16, 24 bit signed integer,
+ /// used to represent the signal amplitude in the range -1 to +1.
+ ///
public double Epsilon { get; set; }
+ ///
+ /// Gets or sets the signal power added by using the chosen FFT window function.
+ ///
public double WindowPower { get; set; }
///
diff --git a/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramTools.cs b/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramTools.cs
index 8d940389a..b0cd931f4 100644
--- a/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramTools.cs
+++ b/src/AudioAnalysisTools/StandardSpectrograms/SpectrogramTools.cs
@@ -125,8 +125,6 @@ public static Image GetSonogramPlusCharts(
if (hits != null)
{
spectrogram = Image_MultiTrack.OverlayScoresAsRedTransparency(spectrogram, hits);
- //OverlayRedTransparency(bmp, this.SuperimposedRedTransparency);
- //this.SonogramImage = this.OverlayRedTransparency((Image)this.SonogramImage);
}
int pixelWidth = spectrogram.Width;
@@ -638,14 +636,14 @@ public static Tuple HistogramOfSpectralPeaks(double[,] spectrogram
public static double[,] ExtractFreqSubband(double[,] m, int minHz, int maxHz, bool doMelscale, int binCount, double binWidth)
{
- AcousticEvent.Freq2BinIDs(doMelscale, minHz, maxHz, binCount, binWidth, out var c1, out var c2);
+ AcousticEvent.ConvertHertzToFrequencyBin(doMelscale, minHz, maxHz, binCount, binWidth, out var c1, out var c2);
return DataTools.Submatrix(m, 0, c1, m.GetLength(0) - 1, c2);
}
public static double[] ExtractModalNoiseSubband(double[] modalNoise, int minHz, int maxHz, bool doMelScale, int nyquist, double binWidth)
{
//extract subband modal noise profile
- AcousticEvent.Freq2BinIDs(doMelScale, minHz, maxHz, nyquist, binWidth, out var c1, out var c2);
+ AcousticEvent.ConvertHertzToFrequencyBin(doMelScale, minHz, maxHz, nyquist, binWidth, out var c1, out var c2);
int subbandCount = c2 - c1 + 1;
var subband = new double[subbandCount];
for (int i = 0; i < subbandCount; i++)
diff --git a/tests/Acoustics.Test/AudioAnalysisTools/AcousticEventTests.cs b/tests/Acoustics.Test/AudioAnalysisTools/AcousticEventTests.cs
new file mode 100644
index 000000000..ed2c2c573
--- /dev/null
+++ b/tests/Acoustics.Test/AudioAnalysisTools/AcousticEventTests.cs
@@ -0,0 +1,117 @@
+//
+// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
+//
+
+namespace Acoustics.Test.AudioAnalysisTools
+{
+ using System;
+ using System.Collections.Generic;
+ using Acoustics.Shared.ImageSharp;
+ using Acoustics.Test.TestHelpers;
+ using global::AudioAnalysisTools;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using SixLabors.ImageSharp;
+ using SixLabors.ImageSharp.PixelFormats;
+
+ [TestClass]
+ public class AcousticEventTests : GeneratedImageTest
+ {
+ [TestMethod]
+ public void TestEventMerging()
+ {
+ // make a list of events
+ var events = new List();
+
+ double maxPossibleScore = 10.0;
+ var event1 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 1.0, eventDuration: 5.0, minFreq: 1000, maxFreq: 8000)
+ {
+ Name = "Event1",
+ Score = 1.0,
+ ScoreNormalised = 1.0 / maxPossibleScore,
+ };
+
+ events.Add(event1);
+
+ var event2 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 4.5, eventDuration: 2.0, minFreq: 1500, maxFreq: 6000)
+ {
+ Name = "Event2",
+ Score = 5.0,
+ ScoreNormalised = 5.0 / maxPossibleScore,
+ };
+ events.Add(event2);
+
+ var event3 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 7.0, eventDuration: 2.0, minFreq: 1000, maxFreq: 8000)
+ {
+ Name = "Event3",
+ Score = 9.0,
+ ScoreNormalised = 9.0 / maxPossibleScore,
+ };
+ events.Add(event3);
+
+ // combine adjacent acoustic events
+ events = AcousticEvent.CombineOverlappingEvents(events: events, segmentStartOffset: TimeSpan.Zero);
+
+ Assert.AreEqual(2, events.Count);
+ Assert.AreEqual(1.0, events[0].EventStartSeconds, 1E-4);
+ Assert.AreEqual(6.5, events[0].EventEndSeconds, 1E-4);
+ Assert.AreEqual(7.0, events[1].EventStartSeconds, 1E-4);
+ Assert.AreEqual(9.0, events[1].EventEndSeconds, 1E-4);
+
+ Assert.AreEqual(1000, events[0].LowFrequencyHertz);
+ Assert.AreEqual(8000, events[0].HighFrequencyHertz);
+ Assert.AreEqual(1000, events[1].LowFrequencyHertz);
+ Assert.AreEqual(8000, events[1].HighFrequencyHertz);
+
+ Assert.AreEqual(5.0, events[0].Score, 1E-4);
+ Assert.AreEqual(9.0, events[1].Score, 1E-4);
+ Assert.AreEqual(0.5, events[0].ScoreNormalised, 1E-4);
+ Assert.AreEqual(0.9, events[1].ScoreNormalised, 1E-4);
+ }
+
+ [TestMethod]
+ public void TestSonogramWithEventsOverlay()
+ {
+ // make a substitute sonogram image
+ var substituteSonogram = Drawing.NewImage(100, 256, Color.Black);
+
+ // make a list of events
+ var framesPerSecond = 10.0;
+ var freqBinWidth = 43.0664;
+ double maxPossibleScore = 10.0;
+
+ var events = new List();
+ var event1 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 1.0, eventDuration: 5.0, minFreq: 1000, maxFreq: 8000)
+ {
+ Score = 10.0,
+ Name = "Event1",
+ ScoreNormalised = 10.0 / maxPossibleScore,
+ };
+
+ events.Add(event1);
+ var event2 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 7.0, eventDuration: 2.0, minFreq: 1000, maxFreq: 8000)
+ {
+ Score = 1.0,
+ Name = "Event2",
+ ScoreNormalised = 1.0 / maxPossibleScore,
+ };
+ events.Add(event2);
+
+ // now add events into the spectrogram image.
+ // set color for the events
+ foreach (AcousticEvent ev in events)
+ {
+ // because we are testing placement of box not text.
+ ev.Name = string.Empty;
+ ev.BorderColour = AcousticEvent.DefaultBorderColor;
+ ev.ScoreColour = AcousticEvent.DefaultScoreColor;
+ ev.DrawEvent(substituteSonogram, framesPerSecond, freqBinWidth, 256);
+ }
+
+ this.Actual = substituteSonogram;
+
+ // BUG: this asset is faulty. See https://github.com/QutEcoacoustics/audio-analysis/issues/300#issuecomment-601537263
+ this.Expected = Image.Load(PathHelper.ResolveAssetPath("EventTests_SuperimposeEventsOnImage.png"));
+ this.AssertImagesEqual();
+ }
+ }
+}
diff --git a/tests/Acoustics.Test/AudioAnalysisTools/EventStatistics/EventStatisticsCalculateTests.cs b/tests/Acoustics.Test/AudioAnalysisTools/EventStatistics/EventStatisticsCalculateTests.cs
index f43c73762..b0cad3c33 100644
--- a/tests/Acoustics.Test/AudioAnalysisTools/EventStatistics/EventStatisticsCalculateTests.cs
+++ b/tests/Acoustics.Test/AudioAnalysisTools/EventStatistics/EventStatisticsCalculateTests.cs
@@ -16,11 +16,10 @@ namespace Acoustics.Test.AudioAnalysisTools.EventStatistics
[TestClass]
public class EventStatisticsCalculateTests
{
-
[TestMethod]
public void TestCalculateEventStatistics()
{
- int sampleRate = 22050;
+ var sampleRate = 22050;
double duration = 28;
int[] harmonics1 = { 500 };
int[] harmonics2 = { 500, 1000, 2000, 4000, 8000 };
@@ -37,8 +36,8 @@ public void TestCalculateEventStatistics()
var start = TimeSpan.FromSeconds(28) + segmentOffset;
var end = TimeSpan.FromSeconds(32) + segmentOffset;
- double lowFreq = 1500.0;
- double topFreq = 8500.0;
+ var lowFreq = 1500.0;
+ var topFreq = 8500.0;
var statsConfig = new EventStatisticsConfiguration()
{
@@ -46,7 +45,7 @@ public void TestCalculateEventStatistics()
FrameStep = 512,
};
- EventStatistics stats =
+ var stats =
EventStatisticsCalculate.AnalyzeAudioEvent(
recording,
(start, end).AsRange(),
diff --git a/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs b/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs
index 2986aab4d..b35ee9f63 100644
--- a/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs
+++ b/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs
@@ -17,6 +17,8 @@ namespace Acoustics.Test.AudioAnalysisTools.StandardSpectrograms
using global::TowseyLibrary;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SixLabors.ImageSharp;
+ using SixLabors.ImageSharp.PixelFormats;
+ using SixLabors.ImageSharp.Processing;
using Path = System.IO.Path;
///
@@ -90,7 +92,7 @@ public void Cleanup()
///
/// METHOD TO CHECK that averaging of decibel values is working.
/// var array = new[] { 96.0, 100.0, 90.0, 97.0 };
- /// The return value should = 96.98816759 dB
+ /// The return value should = 96.98816759 dB.
///
[TestMethod]
public void TestAverageOfDecibelValues()
@@ -219,5 +221,47 @@ public void TestAnnotatedSonogramWithPlots()
Assert.AreEqual(1621, image.Width);
Assert.AreEqual(656, image.Height);
}
+
+ [TestMethod]
+ public void TestSonogramHitsOverlay()
+ {
+ int width = 100;
+ int height = 256;
+
+ // make a substitute sonogram image
+ var pretendSonogram = new Image(width, height);
+
+ // make a hits matrix with crossed diagonals
+ var hitsMatrix = new int[height, width];
+ for (int i = 0; i < height; i++)
+ {
+ int col = (int)Math.Floor(width * i / (double)height);
+ int intensity = col;
+ hitsMatrix[i, col] = i;
+ hitsMatrix[i, width - col - 1] = i;
+ }
+
+ // now add in hits to the spectrogram image.
+ if (hitsMatrix != null)
+ {
+ pretendSonogram = Image_MultiTrack.OverlayScoresAsRedTransparency(pretendSonogram, hitsMatrix);
+ }
+
+ //pretendSonogram.Save("C:\\temp\\image.png");
+ var pixel = new Argb32(255, 0, 0);
+ var expectedColor = new Color(pixel);
+ var actualColor = pretendSonogram[0, height - 1];
+ Assert.AreEqual(expectedColor, actualColor);
+
+ pixel = new Argb32(128, 0, 0);
+ expectedColor = new Color(pixel);
+ actualColor = pretendSonogram[width / 2, height / 2];
+ Assert.AreEqual(expectedColor, actualColor);
+
+ pixel = new Argb32(0, 0, 0);
+ expectedColor = new Color(pixel);
+ actualColor = pretendSonogram[0, 0];
+ Assert.AreEqual(expectedColor, actualColor);
+ }
}
}
diff --git a/tests/Fixtures/EventTests_SuperimposeEventsOnImage.png b/tests/Fixtures/EventTests_SuperimposeEventsOnImage.png
new file mode 100644
index 000000000..b45eba20e
--- /dev/null
+++ b/tests/Fixtures/EventTests_SuperimposeEventsOnImage.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4fde5d7e3427a88eae8774bebb0b986b301edee6808d37bc41a3479d7320cc6e
+size 699