diff --git a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml
index 53b1e1b77..414acc335 100644
--- a/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml
+++ b/src/AnalysisConfigFiles/RecognizerConfigFiles/Towsey.PteropusSpecies.yml
@@ -32,8 +32,9 @@ Profiles:
Wingbeats:
MinHz: 200
MaxHz: 2000
+ DecibelThreshold: 6.0
# duration of DCT in seconds
- DctDuration: 0.8
+ DctDuration: 0.5
# minimum acceptable value of a DCT coefficient
DctThreshold: 0.5
# ignore oscillation rates below the min & above the max threshold
@@ -46,7 +47,7 @@ Profiles:
MinDuration: 1.0
MaxDuration: 10.0
# Event threshold - use this to determine FP / FN trade-off for events.
- EventThreshold: 0.60
+ EventThreshold: 0.5
#Agonist:
# This notation means the Groote profile has all of the settings that the Standard profile has,
# however, the MinHz and MaxHz properties have been overridden.
diff --git a/src/AnalysisPrograms/Recognizers/LitoriaCaerulea.cs b/src/AnalysisPrograms/Recognizers/LitoriaCaerulea.cs
index 897f3c734..4cfcdf25f 100644
--- a/src/AnalysisPrograms/Recognizers/LitoriaCaerulea.cs
+++ b/src/AnalysisPrograms/Recognizers/LitoriaCaerulea.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,7 +193,7 @@ public override RecognizerResults Recognize(AudioRecording recording, Config con
double dctThreshold = recognizerConfig.DctThreshold;
double minOscRate = 1 / recognizerConfig.MaxPeriod;
double maxOscRate = 1 / recognizerConfig.MinPeriod;
- var dctScores = Oscillations2012.DetectOscillations(croakScoreArray, framesPerSecond, dctDuration, minOscRate, maxOscRate, dctThreshold);
+ Oscillations2019.DetectOscillations(croakScoreArray, framesPerSecond, decibelThreshold, dctDuration, minOscRate, maxOscRate, dctThreshold, out double[] dctScores, out double[] oscFreq);
// ######################################################################
// ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER
diff --git a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs
index 0034f5af7..c69c233e8 100644
--- a/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs
+++ b/src/AnalysisPrograms/Recognizers/PteropusSpecies.cs
@@ -39,6 +39,7 @@ namespace AnalysisPrograms.Recognizers
using System.IO;
using System.Linq;
using System.Reflection;
+
using Acoustics.Shared;
using Acoustics.Shared.ConfigFile;
using AnalysisPrograms.Recognizers.Base;
@@ -55,14 +56,17 @@ namespace AnalysisPrograms.Recognizers
///
internal class PteropusSpecies : RecognizerBase
{
+ private static readonly ILog PteropusLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ // The default window for Pteropus sp. Need to be fixed for accurately detecting wing beat oscillations.
+ private static readonly int DefaultWindow = 512;
+
public override string Author => "Towsey";
public override string SpeciesName => "PteropusSpecies";
public override string Description => "[ALPHA] Detects acoustic events for species of Flying Fox, Pteropus species";
- private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
-
/*
///
/// Summarize your results. This method is invoked exactly once per original file.
@@ -87,9 +91,9 @@ public override void SummariseResults(
/// config file that contains parameters used by all profiles.
/// when recording starts.
/// not sure what this is.
- /// where the recogniser results can be found.
+ /// where the recognizer results can be found.
/// assuming ????.
- /// recogniser results.
+ /// recognizer results.
public override RecognizerResults Recognize(AudioRecording audioRecording, Config genericConfig, TimeSpan segmentStartOffset, Lazy getSpectralIndexes, DirectoryInfo outputDirectory, int? imageWidth)
{
if (ConfigFile.HasProfiles(genericConfig))
@@ -102,44 +106,40 @@ public override RecognizerResults Recognize(AudioRecording audioRecording, Confi
message = message + (s + ", ");
}
- log.Debug(message);
+ PteropusLog.Debug(message);
}
else
{
- log.Warn("No configuration profiles found. Two profiles expected for the Flying Fox recogniser.");
+ PteropusLog.Warn("No configuration profiles found. Two profiles expected for the Flying Fox recogniser.");
}
- RecognizerResults territorialResults = null;
+ var territorialResults = new RecognizerResults();
- if (ConfigFile.TryGetProfile(genericConfig, "Territorial", out var profile1))
+ if (ConfigFile.TryGetProfile(genericConfig, "Territorial", out var _))
{
territorialResults = TerritorialCall(audioRecording, genericConfig, "Territorial", segmentStartOffset);
- log.Debug("Territory event count = " + territorialResults.Events.Count);
+ PteropusLog.Debug("Territory event count = " + territorialResults.Events.Count);
}
else
{
- log.Warn("Could not access Territorial configuration parameters");
+ PteropusLog.Warn("Could not access Territorial configuration parameters");
}
- RecognizerResults wingbeatResults = null;
- if (ConfigFile.TryGetProfile(genericConfig, "Wingbeats", out var profile2))
+ var wingbeatResults = new RecognizerResults();
+ if (ConfigFile.TryGetProfile(genericConfig, "Wingbeats", out var _))
{
wingbeatResults = WingBeats(audioRecording, genericConfig, "Wingbeats", segmentStartOffset);
- log.Debug("Wingbeat event count = " + wingbeatResults.Events.Count);
+ PteropusLog.Debug("Wingbeat event count = " + wingbeatResults.Events.Count);
}
else
{
- log.Warn("Could not access Wingbeats configuration parameters");
+ PteropusLog.Warn("Could not access Wingbeats configuration parameters");
}
// combine the results i.e. add wing-beat events to the list of territorial call events.
-
- // results1.Events.Concat(results2.Events.Concat)
- if (territorialResults != null && wingbeatResults != null)
- {
- territorialResults.Events.AddRange(wingbeatResults.Events);
- territorialResults.Plots.AddRange(wingbeatResults.Plots);
- }
+ //NOTE: The returned territorialResults and wingbeatResults will never be null.
+ territorialResults.Events.AddRange(wingbeatResults.Events);
+ territorialResults.Plots.AddRange(wingbeatResults.Plots);
//UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots.
// NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in config file.
@@ -176,24 +176,8 @@ private static RecognizerResults TerritorialCall(AudioRecording audioRecording,
var maxTimeSpan = TimeSpan.FromSeconds(maxDurationSeconds);
//######################
- //2. Don't use samples in this recogniser.
- //var samples = audioRecording.WavReader.Samples;
- //Instead, convert each segment to a spectrogram.
+ //2. Convert each segment to a spectrogram.
var sonogram = GetSonogram(configuration, audioRecording);
- /*
- // make a spectrogram
- var sonoConfig = new SonogramConfig
- {
- WindowSize = 512,
- NoiseReductionType = NoiseReductionType.Standard,
- NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.0,
- };
- sonoConfig.WindowOverlap = 0.0;
-
- // now construct the standard decibel spectrogram WITH noise removal, and look for LimConvex
- // get frame parameters for the analysis
- var sonogram = (BaseSonogram)new SpectrogramStandard(sonoConfig, audioRecording.WavReader);
- */
var decibelArray = SNR.CalculateFreqBandAvIntensity(sonogram.Data, minHz, maxHz, sonogram.NyquistFrequency);
// prepare plots
@@ -218,6 +202,7 @@ private static RecognizerResults TerritorialCall(AudioRecording audioRecording,
//iV add additional info to the acoustic events
acousticEvents.ForEach(ae =>
{
+ ae.FileName = audioRecording.BaseName;
ae.SpeciesName = speciesName;
ae.Name = abbreviatedSpeciesName + profileName;
ae.Profile = profileName;
@@ -246,7 +231,7 @@ private static RecognizerResults TerritorialCall(AudioRecording audioRecording,
private static List FilterEventsForSpectralProfile(List events, BaseSonogram sonogram)
{
double[,] spectrogramData = sonogram.Data;
- int colCount = spectrogramData.GetLength(1);
+ //int colCount = spectrogramData.GetLength(1);
// The following freq bins are used to demarcate freq bands for spectral tests below.
// The hertz values are hard coded but could be included in the config.yml file.
@@ -258,7 +243,7 @@ private static List FilterEventsForSpectralProfile(List FilterEventsForSpectralProfile(List FilterEventsForSpectralProfile(List { plot1, plot2 };
// ######################################################################
+
+ // add additional information about the recording and sonogram properties from which the event is derived.
acousticEvents.ForEach(ae =>
{
+ ae.FileName = audioRecording.BaseName;
ae.SpeciesName = speciesName;
ae.Name = abbreviatedSpeciesName + profileName;
ae.Profile = profileName;
ae.SegmentDurationSeconds = audioRecording.Duration.TotalSeconds;
ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds;
+ var frameOffset = sonogram.FrameStep;
+ var frameDuration = sonogram.FrameDuration;
+ ae.SetTimeAndFreqScales(frameOffset, frameDuration, sonogram.FBinWidth);
//UNCOMMENT following lines to get spectral profiles of the Wingbeat events.
/* double[,] spectrogramData = sonogram.Data;
@@ -475,13 +438,13 @@ internal static BaseSonogram GetSonogram(Config configuration, AudioRecording au
{
var sonoConfig = new SonogramConfig
{
- WindowSize = 512,
+ WindowSize = DefaultWindow,
NoiseReductionType = NoiseReductionType.Standard,
NoiseReductionParameter = configuration.GetDoubleOrNull(AnalysisKeys.NoiseBgThreshold) ?? 0.0,
+ WindowOverlap = 0.0,
};
- sonoConfig.WindowOverlap = 0.0;
- // now construct the standard decibel spectrogram WITH noise removal, and look for LimConvex
+ // now construct the standard decibel spectrogram WITH noise removal
// get frame parameters for the analysis
var sonogram = (BaseSonogram)new SpectrogramStandard(sonoConfig, audioRecording.WavReader);
return sonogram;
diff --git a/src/AnalysisPrograms/Sandpit.cs b/src/AnalysisPrograms/Sandpit.cs
index 3df8a93d5..faa43cd51 100644
--- a/src/AnalysisPrograms/Sandpit.cs
+++ b/src/AnalysisPrograms/Sandpit.cs
@@ -291,11 +291,14 @@ public static void Audio2CsvOverOneFile()
// FLYING FOX RECORDINGS
//string recordingPath = @"C:\Ecoacoustics\WavFiles\BradLawData\FlyingFox\20190127_Bellingen_Feeding_SM4.wav";
- //string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190115_Bellingen_Feeding.wav";
+ string recordingPath = @"D:\Ecoacoustics\WavFiles\FlyingFox\20190115_Bellingen_Feeding.wav";
+ //string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190115_Bellingen_Feeding_minute6.wav";
+ //string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190115_Bellingen_Feeding_minute6_OneChannel22050.wav";
//string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190121_2_Bellingen_Feeding.wav";
- string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190127_Bellingen_Feeding_SM4.wav";
+ //string recordingPath = @"C:\Ecoacoustics\WavFiles\FlyingFox\20190127_Bellingen_Feeding_SM4.wav";
string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\RecognizerConfigFiles\Towsey.PteropusSpecies.yml";
- string outputPath = @"C:\Ecoacoustics\Output\BradLaw\FlyingFox";
+ //string outputPath = @"C:\Ecoacoustics\Output\BradLaw\FlyingFox";
+ string outputPath = @"C:\Ecoacoustics\FlyingFox";
// TSHERING DEMA BHUTAN RECORDINGS
//string recordingPath = @"C:\SensorNetworks\WavFiles\TsheringDema\WBH12HOURS-D_20160403_120000.wav";
@@ -358,7 +361,6 @@ public static void Audio2CsvOverOneFile()
//string outputPath = @"C:\Ecoacoustics\Output\Test\Test24HourRecording\Delete";
//string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\Towsey.Acoustic.yml";
-
// Ivan Campos recordings
//string recordingPath = @"G:\SensorNetworks\WavFiles\Ivancampos\INCIPO01_20161031_024006_898.wav";
//string outputPath = @"G:\SensorNetworks\Output\IvanCampos\17";
@@ -420,7 +422,7 @@ public static void Audio2CsvOverOneFile()
//string outputPath = @"C:\Ecoacoustics\Output\SERF\SERFIndicesNew_2013June19";
//string configPath = @"C:\Work\GitHub\audio-analysis\src\AnalysisConfigFiles\Towsey.Acoustic.yml";
- // USE 24-hour data or parts of from MEZ, TASMAn ISLAND, liz Znidersic
+ // USE 24-hour data or parts of from MEZ, TASMAn ISLAND, liz Znidersic
// these are six hour recordings
//string recordingPath = @"C:\Ecoacoustics\WavFiles\LizZnidersic\TasmanIsland2015_Unit2_Mez\SM304256_0+1_20151114_031652.wav";
//string outputPath = @"C:\Ecoacoustics\Output\Test\Test24HourRecording\TasmanIslandMez\04";
diff --git a/src/AudioAnalysisTools/AcousticEvent.cs b/src/AudioAnalysisTools/AcousticEvent.cs
index 02e51195f..8a44b233f 100644
--- a/src/AudioAnalysisTools/AcousticEvent.cs
+++ b/src/AudioAnalysisTools/AcousticEvent.cs
@@ -11,19 +11,17 @@ namespace AudioAnalysisTools
{
using System;
using System.Collections.Generic;
- using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
- using System.Text.RegularExpressions;
using Acoustics.Shared.Contracts;
using Acoustics.Shared.Csv;
using AForge.Imaging.Filters;
using AnalysisBase.ResultBases;
+ using AudioAnalysisTools.DSP;
+ using AudioAnalysisTools.StandardSpectrograms;
using CsvHelper.Configuration;
- using DSP;
- using StandardSpectrograms;
using TowseyLibrary;
public class AcousticEvent : EventBase
@@ -113,8 +111,8 @@ public void SetEventPositionRelative(
}
///
- /// Gets or sets units = Hertz
- /// Proxied to EventBase.MinHz
+ /// Gets or sets units = Hertz.
+ /// Proxied to EventBase.MinHz.
///
public new double LowFrequencyHertz
{
@@ -301,15 +299,12 @@ public void DoMelScale(bool doMelscale, int freqBinCount)
public void SetTimeAndFreqScales(int samplingRate, int windowSize, int windowOffset)
{
- double frameDuration, frameOffset, framesPerSecond;
- CalculateTimeScale(samplingRate, windowSize, windowOffset, out frameDuration, out frameOffset, out framesPerSecond);
+ 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
- int binCount;
- double binWidth;
- CalculateFreqScale(samplingRate, windowSize, out binCount, out binWidth);
+ 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
@@ -319,11 +314,22 @@ public void SetTimeAndFreqScales(int samplingRate, int windowSize, int windowOff
}
}
+ ///
+ /// This method assumes that there is no frame overlap i.e. frame duration = frame offset.
+ ///
+ /// frames per second assuming no overlap.
+ /// Number of hertz per freq bin.
public void SetTimeAndFreqScales(double framesPerSec, double freqBinWidth)
{
- //this.FrameDuration = frameDuration; //frame duration in seconds
- this.FramesPerSecond = framesPerSec; //inverse of the frame offset
- this.FrameOffset = 1 / framesPerSec; //frame offset in seconds
+ double frameOffset = 1 / framesPerSec; //frame offset in seconds
+ this.SetTimeAndFreqScales(frameOffset, frameOffset, freqBinWidth);
+ }
+
+ public void SetTimeAndFreqScales(double frameOffset, double frameDuration, double freqBinWidth)
+ {
+ 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.FreqBinWidth = freqBinWidth; //required for freq-binID conversions
@@ -343,18 +349,19 @@ public void SetTimeAndFreqScales(double framesPerSec, double freqBinWidth)
public static Oblong ConvertEvent2Oblong(AcousticEvent ae)
{
// Translate time dimension = frames = matrix rows.
- int topRow;
- int bottomRow;
- Time2RowIDs(ae.TimeStart, ae.EventDurationSeconds, ae.FrameOffset, out topRow, out bottomRow);
+ Time2RowIDs(ae.TimeStart, ae.EventDurationSeconds, ae.FrameOffset, out var topRow, out var bottomRow);
//Translate freq dimension = freq bins = matrix columns.
- int leftCol;
- int rightCol;
- Freq2BinIDs(ae.IsMelscale, (int)ae.LowFrequencyHertz, (int)ae.HighFrequencyHertz, ae.FreqBinCount, ae.FreqBinWidth, out leftCol, out rightCol);
+ Freq2BinIDs(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);
}
+ ///
+ /// Should check that Oblong is not null before calling this method.
+ ///
+ public Rectangle GetEventAsRectangle() => new Rectangle(this.Oblong.ColumnLeft, this.Oblong.RowTop, this.Oblong.ColWidth, this.Oblong.RowWidth);
+
///
/// Sets the passed score and also a value normalised between a min and a max.
///
@@ -934,15 +941,6 @@ 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)
{
@@ -1041,8 +1039,6 @@ public static void CalculateAccuracyOnOneRecording(List results,
///
/// duration of event must exceed this to count as an event
///
- /// array value must exceed this dB threshold to count as an event
- /// name of source file to be added to AcousticEvent class
/// a list of acoustic events
//public static List ConvertIntensityArray2Events(double[] values, int minHz, int maxHz,
// double framesPerSec, double freqBinWidth,
@@ -1079,7 +1075,7 @@ public static List ConvertIntensityArray2Events(
startFrame = i;
}
else //check for the end of an event
- if (isHit == true && 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;
@@ -1146,6 +1142,7 @@ public static List GetEventsAroundMaxima(
// convert min an max Hertz durations to freq bins
int minBin = (int)Math.Round(minHz / freqBinWidth);
int maxBin = (int)Math.Round(maxHz / freqBinWidth);
+ int binCount = maxBin - minBin + 1;
// tried smoothing but not advisable since event onset can be very sudden
//values = DataTools.filterMovingAverageOdd(values, 3);
@@ -1191,22 +1188,23 @@ public static List GetEventsAroundMaxima(
endFrame = i;
- int frameDuration = endFrame - startFrame + 1;
- if (frameDuration >= minFrames && frameDuration <= maxFrames)
+ int frameCount = endFrame - startFrame + 1;
+ if (frameCount >= minFrames && frameCount <= maxFrames)
{
double startTime = startFrame * frameOffset; // time in seconds
- double eventDuration = frameDuration * frameOffset; // time in seconds
+ double eventDuration = frameCount * frameOffset; // time in seconds
AcousticEvent ev = new AcousticEvent(segmentStartOffset, startTime, eventDuration, minHz, maxHz)
{
Name = "Event", //default name
- FrameCount = frameDuration,
+ FrameCount = frameCount,
+ FreqBinCount = binCount,
Oblong = new Oblong(startFrame, minBin, endFrame, maxBin),
};
ev.SetTimeAndFreqScales(framesPerSec, freqBinWidth);
//obtain average intensity score. Note-first frame is not actually in the event.
- var subArray = DataTools.Subarray(values, startFrame + 1, frameDuration);
+ var subArray = DataTools.Subarray(values, startFrame + 1, frameCount);
ev.Score = subArray.Average();
events.Add(ev);
}
@@ -1225,18 +1223,15 @@ public static List GetEventsAroundMaxima(
/// Some analysis techniques (e.g. OD) have their own methods for extracting events from score arrays.
///
/// 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
- /// the freq scale required by AcousticEvent class
- ///
- /// duration of event must exceed this to count as an event
- /// duration of event must be less than this to count as an event
- ///
- /// score must exceed this threshold to count as an event
- /// name of source file to be added to AcousticEvent class
- /// name of the event to be added to AcousticEvent class
- /// a list of acoustic events
+ /// lower freq bound of the acoustic event.
+ /// upper freq bound of the acoustic event.
+ /// the time scale required by AcousticEvent class.
+ /// the freq scale required by AcousticEvent class.
+ /// 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.
+ /// a list of acoustic events.
public static List ConvertScoreArray2Events(
double[] scores,
int minHz,
@@ -1250,23 +1245,26 @@ public static List ConvertScoreArray2Events(
{
int count = scores.Length;
var events = new List();
- double maxPossibleScore = 5 * scoreThreshold; // used to calcualte a normalised score bewteen 0 - 1.0
+ 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;
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 && scores[i] >= scoreThreshold) //start of an event
+ if (isHit == false && scores[i] >= scoreThreshold)
{
+ //start of an event
isHit = true;
startTime = i * frameOffset;
startFrame = i;
}
else // check for the end of an event
- if (isHit == true && scores[i] <= scoreThreshold) // this is end of an event, so initialise it
+ if (isHit && scores[i] <= scoreThreshold)
{
+ // this is end of an event, so initialise it
isHit = false;
double endTime = i * frameOffset;
double duration = endTime - startTime;
@@ -1326,10 +1324,6 @@ public static List ConvertScoreArray2Events(
/// The events are required to have the passed name.
/// 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)
{
double[] scores = new double[arraySize];
@@ -1341,7 +1335,7 @@ public static double[] ExtractScoreArrayFromEvents(List events, i
double windowOffset = events[0].FrameOffset;
double frameRate = 1 / windowOffset; //frames per second
- int count = events.Count;
+ //int count = events.Count;
foreach ( AcousticEvent ae in events)
{
if (!ae.Name.Equals(nameOfTargetEvent))
@@ -1359,7 +1353,7 @@ public static double[] ExtractScoreArrayFromEvents(List events, i
}
return scores;
- } //end method
+ }
//##############################################################################################################################################
@@ -1367,10 +1361,6 @@ public static double[] ExtractScoreArrayFromEvents(List events, i
/// 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}");
@@ -1385,7 +1375,7 @@ public static void TestToCompareEvents(string fileName, DirectoryInfo opDir, str
var eventsFile = new FileInfo(eventsFilePath);
Csv.WriteToCsv(eventsFile, events);
- LoggedConsole.WriteLine($"# EVENTS TEST: Camparing List of {testName} events with those in benchmark file:");
+ LoggedConsole.WriteLine($"# EVENTS TEST: Comparing List of {testName} events with those in benchmark file:");
var benchmarkFile = new FileInfo(benchmarkFilePath);
if (!benchmarkFile.Exists)
{
diff --git a/src/AudioAnalysisTools/AudioAnalysisTools.csproj b/src/AudioAnalysisTools/AudioAnalysisTools.csproj
index 3e85ba1a3..f2ac66098 100644
--- a/src/AudioAnalysisTools/AudioAnalysisTools.csproj
+++ b/src/AudioAnalysisTools/AudioAnalysisTools.csproj
@@ -300,6 +300,7 @@
+
diff --git a/src/AudioAnalysisTools/Oscillations2012.cs b/src/AudioAnalysisTools/Oscillations2012.cs
index def1bc1a2..e4e6173ff 100644
--- a/src/AudioAnalysisTools/Oscillations2012.cs
+++ b/src/AudioAnalysisTools/Oscillations2012.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).
//
@@ -6,20 +6,44 @@ namespace AudioAnalysisTools
{
using System;
using System.Collections.Generic;
- using DSP;
- using StandardSpectrograms;
+ using AudioAnalysisTools.DSP;
+ using AudioAnalysisTools.StandardSpectrograms;
using TowseyLibrary;
///
/// NOTE: 21st June 2012.
///
/// This class contains methods to detect oscillations in a the sonogram of an audio signal.
- /// The method Execute() returns all info about oscillaitons in the passed sonogram.
+ /// The method Execute() returns all info about oscillations in the passed sonogram.
/// This method should be called in preference to those in the class OscillationAnalysis.
- /// (The latter should be depracated.)
+ /// (The latter should be deprecated.)
///
public static class Oscillations2012
{
+ public static void Execute(
+ SpectrogramStandard sonogram,
+ int minHz,
+ int maxHz,
+ double dctDuration,
+ int minOscilFreq,
+ int maxOscilFreq,
+ double dctThreshold,
+ double scoreThreshold,
+ double minDuration,
+ double maxDuration,
+ out double[] scores,
+ out List events,
+ out double[,] hits,
+ TimeSpan segmentStartOffset)
+ {
+ int scoreSmoothingWindow = 11; // sets a default that is good for Cane toad but not necessarily for other recognizers
+
+ Execute(sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, scoreThreshold,
+ minDuration, maxDuration, scoreSmoothingWindow,
+ out scores, out events, out hits,
+ segmentStartOffset);
+ }
+
public static void Execute(
SpectrogramStandard sonogram,
int minHz,
@@ -42,10 +66,6 @@ public static void Execute(
//DETECT OSCILLATIONS
hits = DetectOscillations(sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold);
-
- // debug
- ////var sum = hits.Fold((x, y) => x + y, 0.0);
-
if (hits == null)
{
LoggedConsole.WriteLine("###### WARNING: DCT length too short to detect the maxOscilFreq");
@@ -76,50 +96,24 @@ public static void Execute(
segmentStartOffset);
}
- public static void Execute(
- SpectrogramStandard sonogram,
- int minHz,
- int maxHz,
- double dctDuration,
- int minOscilFreq,
- int maxOscilFreq,
- double dctThreshold,
- double scoreThreshold,
- double minDuration,
- double maxDuration,
- out double[] scores,
- out List events,
- out double[,] hits,
- TimeSpan segmentStartOffset)
- {
- int scoreSmoothingWindow = 11; // sets a default that is good for Canetoad but not necessarily for other recognisers
-
- Execute(sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, scoreThreshold,
- minDuration, maxDuration, scoreSmoothingWindow,
- out scores, out events, out hits,
- segmentStartOffset);
- }
-
///
/// Detects oscillations in a given freq bin.
/// there are several important parameters for tuning.
/// a) DCTLength: Good values are 0.25 to 0.50 sec. Do not want too long because DCT requires stationarity.
/// Do not want too short because too small a range of oscillations
- /// b) DCTindex: Sets lower bound for oscillations of interest. Index refers to array of coeff returned by DCT.
+ /// b) DCTindex: Sets lower bound for oscillations of interest. Index refers to array of coefficient returned by DCT.
/// Array has same length as the length of the DCT. Low freq oscillations occur more often by chance. Want to exclude them.
/// c) MinAmplitude: minimum acceptable value of a DCT coefficient if hit is to be accepted.
/// The algorithm is sensitive to this value. A lower value results in more oscillation hits being returned.
///
- ///
- /// min freq bin of search band
- /// max freq bin of search band
- /// number of values
- ///
- /// threshold - do not accept a DCT coefficient if its value is less than this threshold
- ///
- ///
- public static double[,] DetectOscillations(SpectrogramStandard sonogram, int minHz, int maxHz,
- double dctDuration, int minOscilFreq, int maxOscilFreq, double dctThreshold)
+ /// A spectrogram.
+ /// min freq bin of search band.
+ /// max freq bin of search band.
+ /// number of values.
+ /// minimum oscillation freq.
+ /// maximum oscillation freq.
+ /// threshold - do not accept a DCT coefficient if its value is less than this threshold.
+ public static double[,] DetectOscillations(SpectrogramStandard sonogram, int minHz, int maxHz,double dctDuration, int minOscilFreq, int maxOscilFreq, double dctThreshold)
{
int minBin = (int)(minHz / sonogram.FBinWidth);
int maxBin = (int)(maxHz / sonogram.FBinWidth);
@@ -130,9 +124,10 @@ public static void Execute(
int midOscilFreq = minOscilFreq + ((maxOscilFreq - minOscilFreq) / 2);
+ //safety check
if (maxIndex > dctLength)
{
- return null; //safety check
+ return null;
}
int rows = sonogram.Data.GetLength(0);
@@ -150,7 +145,8 @@ public static void Execute(
//string bmpPath = @"C:\SensorNetworks\Output\cosines.png";
//ImageTools.DrawMatrix(cosines, bmpPath, true);
- for (int c = minBin; c <= maxBin; c++) //traverse columns - skip DC column
+ //traverse columns - skip DC column
+ for (int c = minBin; c <= maxBin; c++)
{
var dctArray = new double[dctLength];
@@ -162,10 +158,9 @@ public static void Execute(
dctArray[i] = matrix[r + i, c];
}
- dctArray = DataTools.SubtractMean(dctArray);
-
//dctArray = DataTools.Vector2Zscores(dctArray);
+ dctArray = DataTools.SubtractMean(dctArray);
double[] dctCoeff = MFCCStuff.DCT(dctArray, cosines);
// convert to absolute values because not interested in negative values due to phase.
@@ -190,7 +185,7 @@ public static void Execute(
// #### Tried this option for scoring oscillation hits but did not work well.
// #### Requires very fine tuning of thresholds
- //dctCoeff = DataTools.Normalise2Probabilites(dctCoeff);
+ //dctCoeff = DataTools.Normalise2Probabilities(dctCoeff);
//// sum area under curve where looking for oscillations
//double sum = 0.0;
//for (int i = minIndex; i <= maxIndex; i++)
@@ -202,8 +197,8 @@ public static void Execute(
// DEBUGGING
// DataTools.MinMax(dctCoeff, out min, out max);
- //DataTools.writeBarGraph(dctArray);
- //DataTools.writeBarGraph(dctCoeff);
+ //DataTools.writeBarGraph(dctArray);
+ //DataTools.writeBarGraph(dctCoeff);
//mark DCT location with oscillation freq, only if oscillation freq is in correct range and amplitude
if (indexOfMaxValue >= minIndex && indexOfMaxValue <= maxIndex && dctCoeff[indexOfMaxValue] > dctThreshold)
@@ -223,115 +218,20 @@ public static void Execute(
return hits;
}
- public static double[] DetectOscillations(double[] ipArray, double framesPerSecond, double dctDuration, double minOscilFreq, double maxOscilFreq, double dctThreshold)
- {
- int dctLength = (int)Math.Round(framesPerSecond * dctDuration);
- int minIndex = (int)(minOscilFreq * dctDuration * 2); //multiply by 2 because index = Pi and not 2Pi
- int maxIndex = (int)(maxOscilFreq * dctDuration * 2); //multiply by 2 because index = Pi and not 2Pi
-
- //double midOscilFreq = minOscilFreq + ((maxOscilFreq - minOscilFreq) / 2);
-
- if (maxIndex > dctLength)
- {
- return null; //safety check
- }
-
- int length = ipArray.Length;
- var dctScores = new double[length];
-
- //var hits = new double[length];
-
- double[,] cosines = MFCCStuff.Cosines(dctLength, dctLength); //set up the cosine coefficients
-
- //following two lines write bmp image of cosine matrix values for checking.
- //string bmpPath = @"C:\SensorNetworks\Output\cosines.png";
- //ImageTools.DrawMatrix(cosines, bmpPath, true);
-
- for (int r = 1; r < length - dctLength; r++)
- {
- // only stop if current location is a peak
- if (ipArray[r] < ipArray[r - 1] || ipArray[r] < ipArray[r + 1])
- {
- continue;
- }
-
- // extract array and ready for DCT
- //for (int i = 0; i < dctLength; i++) dctArray[i] = ipArray[r + i];
- var dctArray = DataTools.Subarray(ipArray, r, dctLength);
-
- dctArray = DataTools.SubtractMean(dctArray);
-
- //dctArray = DataTools.Vector2Zscores(dctArray);
-
- double[] dctCoeff = MFCCStuff.DCT(dctArray, cosines);
-
- // convert to absolute values because not interested in negative values due to phase.
- for (int i = 0; i < dctLength; i++)
- {
- dctCoeff[i] = Math.Abs(dctCoeff[i]);
- }
-
- // remove low freq oscillations from consideration
- int thresholdIndex = minIndex / 4;
- for (int i = 0; i < thresholdIndex; i++)
- {
- dctCoeff[i] = 0.0;
- }
-
- dctCoeff = DataTools.normalise2UnitLength(dctCoeff);
-
- //dct = DataTools.NormaliseMatrixValues(dct); //another option to NormaliseMatrixValues
- int indexOfMaxValue = DataTools.GetMaxIndex(dctCoeff);
-
- //double oscilFreq = indexOfMaxValue / dctDuration * 0.5; //Times 0.5 because index = Pi and not 2Pi
-
- // #### Tried this option for scoring oscillation hits but did not work well.
- // #### Requires very fine tuning of thresholds
- //dctCoeff = DataTools.Normalise2Probabilites(dctCoeff);
- //// sum area under curve where looking for oscillations
- //double sum = 0.0;
- //for (int i = minIndex; i <= maxIndex; i++)
- // sum += dctCoeff[i];
- //if (sum > dctThreshold)
- //{
- // for (int i = 0; i < dctLength; i++) hits[r + i, c] = midOscilFreq;
- //}
-
- // DEBUGGING
- // DataTools.MinMax(dctCoeff, out min, out max);
- //DataTools.writeBarGraph(dctArray);
- //DataTools.writeBarGraph(dctCoeff);
-
- //mark DCT location with oscillation freq, only if oscillation freq is in correct range and amplitude
- if (indexOfMaxValue >= minIndex && indexOfMaxValue <= maxIndex && dctCoeff[indexOfMaxValue] > dctThreshold)
- {
- //for (int i = 0; i < dctLength; i++) dctScores[r + i] = midOscilFreq;
- for (int i = 0; i < dctLength; i++)
- {
- if (dctScores[r + i] < dctCoeff[indexOfMaxValue])
- {
- dctScores[r + i] = dctCoeff[indexOfMaxValue];
- }
- }
- }
- }
-
- //return hits; //dctArray
- return dctScores;
- }
-
///
/// Removes single lines of hits from Oscillation matrix.
///
- /// the Oscillation matrix
- ///
+ /// the Oscillation matrix.
+ /// a matrix.
public static double[,] RemoveIsolatedOscillations(double[,] matrix)
{
int rows = matrix.GetLength(0);
int cols = matrix.GetLength(1);
double[,] cleanMatrix = matrix;
const double tolerance = double.Epsilon;
- for (int c = 3; c < cols - 3; c++) //traverse columns - skip DC column
+
+ //traverse columns - skip DC column
+ for (int c = 3; c < cols - 3; c++)
{
for (int r = 0; r < rows; r++)
{
@@ -340,7 +240,8 @@ public static double[] DetectOscillations(double[] ipArray, double framesPerSeco
continue;
}
- if (Math.Abs(matrix[r, c - 2]) < tolerance && Math.Abs(matrix[r, c + 2]) < tolerance) //+2 because alternate columns
+ //+2 because alternate columns
+ if (Math.Abs(matrix[r, c - 2]) < tolerance && Math.Abs(matrix[r, c + 2]) < tolerance)
{
cleanMatrix[r, c] = 0.0;
}
@@ -348,29 +249,31 @@ public static double[] DetectOscillations(double[] ipArray, double framesPerSeco
}
return cleanMatrix;
- } //end method RemoveIsolatedOscillations()
+ }
///
- /// Converts the hits derived from the oscilation detector into a score for each frame.
+ /// Converts the hits derived from the oscillation detector into a score for each frame.
/// NOTE: The oscillation detector skips every second row, so score must be adjusted for this.
///
- /// sonogram as matrix showing location of oscillation hits
- /// lower freq bound of the acoustic event
- /// upper freq bound of the acoustic event
- /// the freq scale required by AcousticEvent class
- ///
+ /// sonogram as matrix showing location of oscillation hits.
+ /// lower freq bound of the acoustic event.
+ /// upper freq bound of the acoustic event.
+ /// the freq scale required by AcousticEvent class.
public static double[] GetOscillationScores(double[,] hits, int minHz, int maxHz, double freqBinWidth)
{
int rows = hits.GetLength(0);
int minBin = (int)(minHz / freqBinWidth);
int maxBin = (int)(maxHz / freqBinWidth);
int binCount = maxBin - minBin + 1;
- double hitRange = binCount * 0.5 * 0.8; //set hit range slightly < half the bins. Half because only scan every second bin.
+
+ //set hit range slightly < half the bins. Half because only scan every second bin.
+ double hitRange = binCount * 0.5 * 0.8;
var scores = new double[rows];
for (int r = 0; r < rows; r++)
{
+ //traverse columns in required band
int score = 0;
- for (int c = minBin; c <= maxBin; c++) //traverse columns in required band
+ for (int c = minBin; c <= maxBin; c++)
{
if (hits[r, c] > 0)
{
@@ -378,7 +281,8 @@ public static double[] GetOscillationScores(double[,] hits, int minHz, int maxHz
}
}
- scores[r] = score / hitRange; //NormaliseMatrixValues the hit score in [0,1]
+ //Normalize the Matrix Values the hit score in [0,1]
+ scores[r] = score / hitRange;
if (scores[r] > 1.0)
{
scores[r] = 1.0;
@@ -386,7 +290,7 @@ public static double[] GetOscillationScores(double[,] hits, int minHz, int maxHz
}
return scores;
- }//end method GetODScores()
+ }
public static double[] GetOscillationFrequency(double[,] hits, int minHz, int maxHz, double freqBinWidth)
{
@@ -394,14 +298,15 @@ public static double[] GetOscillationFrequency(double[,] hits, int minHz, int ma
int minBin = (int)(minHz / freqBinWidth);
int maxBin = (int)(maxHz / freqBinWidth);
- //int binCount = maxBin - minBin + 1;
-
- var oscFreq = new double[rows]; //to store the oscillation frequency
+ //to store the oscillation frequency
+ var oscFreq = new double[rows];
for (int r = 0; r < rows; r++)
{
double freq = 0;
int count = 0;
- for (int c = minBin; c <= maxBin; c++) //traverse columns in required band
+
+ //traverse columns in required band
+ for (int c = minBin; c <= maxBin; c++)
{
if (hits[r, c] > 0)
{
@@ -416,30 +321,28 @@ public static double[] GetOscillationFrequency(double[,] hits, int minHz, int ma
}
else
{
- oscFreq[r] = freq / count; //return the average frequency
+ //return the average frequency
+ oscFreq[r] = freq / count;
}
-
- //if (oscFreq[r] > 1.0) oscFreq[r] = 1.0;
}
return oscFreq;
- }//end method GetODFrequency()
+ }
///
/// Converts the Oscillation Detector score array to a list of AcousticEvents.
///
- /// the array of OD scores
- ///
- /// lower freq bound of the acoustic event
- /// upper freq bound of the acoustic event
- /// the time scale required by AcousticEvent class
- /// the freq scale required by AcousticEvent class
- ///
- ///
- ///
- /// name of source file to be added to AcousticEvent class
- ///
- ///
+ /// the array of OD scores.
+ /// oscillation freq.
+ /// lower freq bound of the acoustic event.
+ /// upper freq bound of the acoustic event.
+ /// the time scale required by AcousticEvent class.
+ /// the freq scale required by AcousticEvent class.
+ /// threshold.
+ /// min threshold.
+ /// max threshold.
+ /// name of source file to be added to AcousticEvent class.
+ /// time offset.
public static List ConvertOscillationScores2Events(
double[] scores,
double[] oscFreq,
@@ -467,79 +370,75 @@ public static List ConvertOscillationScores2Events(
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 && scores[i] >= scoreThreshold) //start of an event
+ if (isHit == false && scores[i] >= scoreThreshold)
{
+ //start of an event
isHit = true;
startTime = i * frameOffset;
startFrame = i;
}
else //check for the end of an event
- if (isHit && (scores[i] < scoreThreshold || i == count - 1)) //this is end of an event, so initialise it
- {
- isHit = false;
+ if (isHit && (scores[i] < scoreThreshold || i == count - 1))
+ {
+ isHit = false;
- //double endTime = i * frameOffset;
- //double duration = endTime - startTime;
- double duration = (i - startFrame + 1) * frameOffset;
- if (duration < minDurationThreshold)
+ //double endTime = i * frameOffset;
+ //double duration = endTime - startTime;
+ double duration = (i - startFrame + 1) * frameOffset;
+ if (duration < minDurationThreshold)
{
continue; //skip events with duration shorter than threshold
}
- if (duration > maxDurationThreshold)
+ if (duration > maxDurationThreshold)
{
continue; //skip events with duration longer than threshold
}
- var ev = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz);
- ev.Name = "Oscillation"; //default name
+ //this is end of an event, so initialise it
+ var ev = new AcousticEvent(segmentStartOffset, startTime, duration, minHz, maxHz)
+ {
+ Name = "Oscillation", //default name
+ FileName = fileName,
+ };
- //ev.SetTimeAndFreqScales(framesPerSec, freqBinWidth);
- ev.FileName = fileName;
+ ev.SetTimeAndFreqScales(framesPerSec, freqBinWidth);
- //obtain average score.
- double av = 0.0;
- for (int n = startFrame; n <= i; n++)
+ //obtain average score.
+ double av = 0.0;
+ for (int n = startFrame; n <= i; n++)
{
av += scores[n];
}
- ev.Score = av / (i - startFrame + 1);
+ ev.Score = av / (i - startFrame + 1);
- //obtain oscillation freq.
- av = 0.0;
- for (int n = startFrame; n <= i; n++)
+ //obtain oscillation freq.
+ av = 0.0;
+ for (int n = startFrame; n <= i; n++)
{
av += oscFreq[n];
}
- ev.Score2 = av / (i - startFrame + 1);
- ev.Intensity = (int)ev.Score2; // store this info for later inclusion in csv file as Event Intensity
- events.Add(ev);
- }
-
- //adapt the threshold
- //if ((scores[i] >= maxThreshold) && (maxThreshold >= scoreThreshold)) scoreThreshold *= 1.01;
- //else
- //if ((scores[i] <= minThreshold) && (minThreshold <= scoreThreshold)) scoreThreshold *= 0.95;
+ ev.Score2 = av / (i - startFrame + 1);
+ ev.Intensity = (int)ev.Score2; // store this info for later inclusion in csv file as Event Intensity
+ events.Add(ev);
+ }
} //end of pass over all frames
return events;
}//end method ConvertODScores2Events()
///
- /// Calculates the optimal frame overlap for the given sample rate, frame width and max oscilation or pulse rate.
+ /// Calculates the optimal frame overlap for the given sample rate, frame width and max oscillation or pulse rate.
/// Pulse rate is determined using a DCT and efficient use of the DCT requires that the dominant pulse sit somewhere 3.4 along the array of coefficients.
///
- ///
- ///
- ///
- ///
- public static double CalculateRequiredFrameOverlap(int sr, int frameWidth, double maxOscilation)
+ public static double CalculateRequiredFrameOverlap(int sr, int frameWidth, double maxOscillation)
{
- double optimumFrameRate = 3 * maxOscilation; //so that max oscillation sits in 3/4 along the array of DCT coefficients
+ double optimumFrameRate = 3 * maxOscillation; //so that max oscillation sits in 3/4 along the array of DCT coefficients
int frameOffset = (int)(sr / optimumFrameRate);
// this line added 17 Aug 2016 to deal with high Oscillation rate frog ribits.
@@ -551,5 +450,5 @@ public static double CalculateRequiredFrameOverlap(int sr, int frameWidth, doubl
double overlap = (frameWidth - frameOffset) / (double)frameWidth;
return overlap;
}
- }//end class
-} //AudioAnalysisTools
+ }
+}
diff --git a/src/AudioAnalysisTools/Oscillations2019.cs b/src/AudioAnalysisTools/Oscillations2019.cs
new file mode 100644
index 000000000..199d1c332
--- /dev/null
+++ b/src/AudioAnalysisTools/Oscillations2019.cs
@@ -0,0 +1,178 @@
+//
+// 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 AudioAnalysisTools
+{
+ using System;
+ using System.Collections.Generic;
+ using AudioAnalysisTools.DSP;
+ using AudioAnalysisTools.StandardSpectrograms;
+ using TowseyLibrary;
+
+ ///
+ /// NOTE: 26th October 2019.
+ ///
+ /// This class contains methods to detect oscillations in a the sonogram of an audio signal.
+ /// The method Execute() returns all info about oscillations in the passed sonogram.
+ ///
+ public static class Oscillations2019
+ {
+ public static void Execute(
+ SpectrogramStandard sonogram,
+ int minHz,
+ int maxHz,
+ double decibelThreshold,
+ double dctDuration,
+ int minOscFreq,
+ int maxOscFreq,
+ double dctThreshold,
+ double scoreThreshold,
+ double minDuration,
+ double maxDuration,
+ int smoothingWindow,
+ out double[] dctScores,
+ out List events,
+ TimeSpan segmentStartOffset)
+ {
+ // smooth the frames to make oscillations more regular.
+ sonogram.Data = MatrixTools.SmoothRows(sonogram.Data, 5);
+
+ // extract array of decibel values, frame averaged over required frequency band
+ var decibelArray = SNR.CalculateFreqBandAvIntensity(sonogram.Data, minHz, maxHz, sonogram.NyquistFrequency);
+
+ // if first value is negative dB, this means noise removal was not done.
+ // Do noise removal now
+ //if (decibelArray[0] < 0.0)
+ //{
+ // NoiseRemovalModal.CalculateNoiseUsingLamelsAlgorithm(decibelArray, out double _, out double _, out double noiseMode, out double _);
+ // decibelArray = SNR.SubtractAndTruncate2Zero(decibelArray, noiseMode);
+ //}
+
+ //DETECT OSCILLATIONS
+ var framesPerSecond = sonogram.FramesPerSecond;
+ DetectOscillations(
+ decibelArray,
+ framesPerSecond,
+ decibelThreshold,
+ dctDuration,
+ minOscFreq,
+ maxOscFreq,
+ dctThreshold,
+ out dctScores,
+ out var oscFreq);
+
+ // smooth the scores - window=11 has been the DEFAULT. Now letting user set this.
+ dctScores = DataTools.filterMovingAverage(dctScores, smoothingWindow);
+
+ //double midOscFreq = minOscFreq + ((maxOscFreq - minOscFreq) / 2);
+ events = Oscillations2012.ConvertOscillationScores2Events(
+ dctScores,
+ oscFreq,
+ minHz,
+ maxHz,
+ sonogram.FramesPerSecond,
+ sonogram.FBinWidth,
+ scoreThreshold,
+ minDuration,
+ maxDuration,
+ sonogram.Configuration.SourceFName,
+ segmentStartOffset);
+ }
+
+ ///
+ /// Currently this method is called by only one species recognizer - LitoriaCaerulea.
+ ///
+ /// an array of decibel values.
+ /// the frame rate.
+ /// Ignore frames below this threshold.
+ /// Duration in seconds of the required DCT.
+ /// minimum oscillation frequency.
+ /// maximum oscillation frequency.
+ /// Threshold for the maximum DCT coefficient.
+ /// an array of dct scores.
+ /// an array of oscillation frequencies.
+ public static void DetectOscillations(
+ double[] ipArray,
+ double framesPerSecond,
+ double decibelThreshold,
+ double dctDuration,
+ double minOscFreq,
+ double maxOscFreq,
+ double dctThreshold,
+ out double[] dctScores,
+ out double[] oscFreq)
+ {
+ int dctLength = (int)Math.Round(framesPerSecond * dctDuration);
+ int minIndex = (int)(minOscFreq * dctDuration * 2); //multiply by 2 because index = Pi and not 2Pi
+ int maxIndex = (int)(maxOscFreq * dctDuration * 2); //multiply by 2 because index = Pi and not 2Pi
+ if (maxIndex > dctLength)
+ {
+ LoggedConsole.WriteWarnLine("MaxIndex > DCT length. Therefore set maxIndex = DCT length.");
+ maxIndex = dctLength;
+ }
+
+ int length = ipArray.Length;
+ dctScores = new double[length];
+ oscFreq = new double[length];
+
+ //set up the cosine coefficients
+ double[,] cosines = MFCCStuff.Cosines(dctLength, dctLength);
+
+ //following two lines write bmp image of cosine matrix values for checking.
+ //string bmpPath = @"C:\SensorNetworks\Output\cosines.png";
+ //ImageTools.DrawMatrix(cosines, bmpPath, true);
+
+ for (int r = 1; r < length - dctLength; r++)
+ {
+ // only stop if current location is a peak
+ if (ipArray[r] < ipArray[r - 1] || ipArray[r] < ipArray[r + 1])
+ {
+ continue;
+ }
+
+ // only stop if current location is a peak
+ if (ipArray[r] < decibelThreshold)
+ {
+ continue;
+ }
+
+ // extract array and ready for DCT
+ var dctArray = DataTools.Subarray(ipArray, r, dctLength);
+
+ dctArray = DataTools.SubtractMean(dctArray);
+ double[] dctCoefficient = MFCCStuff.DCT(dctArray, cosines);
+
+ // convert to absolute values because not interested in negative values due to phase.
+ for (int i = 0; i < dctLength; i++)
+ {
+ dctCoefficient[i] = Math.Abs(dctCoefficient[i]);
+ }
+
+ // remove low freq oscillations from consideration
+ int thresholdIndex = minIndex / 4;
+ for (int i = 0; i < thresholdIndex; i++)
+ {
+ dctCoefficient[i] = 0.0;
+ }
+
+ dctCoefficient = DataTools.normalise2UnitLength(dctCoefficient);
+
+ int indexOfMaxValue = DataTools.GetMaxIndex(dctCoefficient);
+
+ //mark DCT location with oscillation freq, only if oscillation freq is in correct range and amplitude
+ if (indexOfMaxValue >= minIndex && indexOfMaxValue <= maxIndex && dctCoefficient[indexOfMaxValue] > dctThreshold)
+ {
+ for (int i = 0; i < dctLength; i++)
+ {
+ if (dctScores[r + i] < dctCoefficient[indexOfMaxValue])
+ {
+ dctScores[r + i] = dctCoefficient[indexOfMaxValue];
+ oscFreq[r + i] = indexOfMaxValue / dctDuration / 2;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/TowseyLibrary/PulseTrain.cs b/src/TowseyLibrary/PulseTrain.cs
index 1579da573..59788d8d3 100644
--- a/src/TowseyLibrary/PulseTrain.cs
+++ b/src/TowseyLibrary/PulseTrain.cs
@@ -6,7 +6,6 @@
// This class contains methods to recognise pulse trains.
// It is an alternative to using the Oscillations class.
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -18,8 +17,13 @@ namespace TowseyLibrary
using Acoustics.Shared.ConfigFile;
using MathNet.Numerics.LinearAlgebra.Solvers;
+ ///
+ /// This class was an attempt to detect pulse trains as an alternative to using the Oscillation recognition methods.
+ /// It did not work effectively so discontinued the idea and have commented out the three methods.
+ ///
public static class PulseTrain
{
+ /*
///
/// This method creates a template to recognise two pulses that are possibly part of a pulse train.
/// The template is designed to detect pulse trains of at least 2 pulses!
@@ -157,5 +161,6 @@ public static double[] GetPulseTrainScore(double[] signal, double pulsesPerSecon
return scores;
}
+ */
}
}
diff --git a/tests/Acoustics.Test/Acoustics.Test.csproj b/tests/Acoustics.Test/Acoustics.Test.csproj
index 24f6dbafe..4d9317438 100644
--- a/tests/Acoustics.Test/Acoustics.Test.csproj
+++ b/tests/Acoustics.Test/Acoustics.Test.csproj
@@ -307,6 +307,7 @@
+
diff --git a/tests/Acoustics.Test/AnalysisPrograms/Recognizers/PteropusSp/PteropusSpTests.cs b/tests/Acoustics.Test/AnalysisPrograms/Recognizers/PteropusSp/PteropusSpTests.cs
new file mode 100644
index 000000000..6b37bf0d9
--- /dev/null
+++ b/tests/Acoustics.Test/AnalysisPrograms/Recognizers/PteropusSp/PteropusSpTests.cs
@@ -0,0 +1,159 @@
+//
+// 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.AnalysisPrograms.Recognizers.PteropusSp
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Drawing;
+ using System.IO;
+ using System.Linq;
+ using Acoustics.Shared;
+ using Acoustics.Shared.ConfigFile;
+ using Acoustics.Test.TestHelpers;
+ using Acoustics.Tools.Wav;
+ using global::AudioAnalysisTools;
+ using global::AudioAnalysisTools.DSP;
+ using global::AudioAnalysisTools.StandardSpectrograms;
+ using global::AudioAnalysisTools.WavTools;
+ using global::TowseyLibrary;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class PteropusSpTests
+ {
+ private DirectoryInfo outputDirectory;
+ private AudioRecording audioRecording;
+ private BaseSonogram sonogram;
+
+ ///
+ /// The one-minute recording used for these tests was originally recorded at 40kHz in two channels.
+ /// It was resampled to 22050 in one channel using Audacity for these tests.
+ /// The number of wing-beat and call events is somewhat sensitive to parameter settings.
+ /// With test settings get and extra call event.
+ ///
+ [TestInitialize]
+ public void Setup()
+ {
+ this.outputDirectory = PathHelper.GetTempDir();
+ this.audioRecording = new AudioRecording(PathHelper.ResolveAsset("Recordings", "20190115_Bellingen_Feeding_minute6_OneChannel22050.wav"));
+ var sonoConfig = new SonogramConfig
+ {
+ WindowSize = 512,
+ NoiseReductionType = NoiseReductionType.Standard,
+ NoiseReductionParameter = 3.0,
+ WindowOverlap = 0.0,
+ };
+ this.sonogram = new SpectrogramStandard(sonoConfig, this.audioRecording.WavReader);
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ PathHelper.DeleteTempDir(this.outputDirectory);
+ }
+
+ [TestMethod]
+ public void TestGetWingBeatEvents()
+ {
+ //string speciesName = "Pteropus species";
+ //string abbreviatedSpeciesName = "Pteropus";
+ int minHz = 200;
+ int maxHz = 2000;
+ double minDurationSeconds = 1.0;
+ double maxDurationSeconds = 10.0;
+ double dctDuration = 0.8;
+ double dctThreshold = 0.5;
+ double minOscilFreq = 4.0;
+ double maxOscilFreq = 6.0;
+ double eventThreshold = 0.6;
+ TimeSpan segmentStartOffset = TimeSpan.Zero;
+
+ // Look for wing beats using oscillation detector
+ Oscillations2012.Execute(
+ (SpectrogramStandard)this.sonogram,
+ minHz,
+ maxHz,
+ dctDuration,
+ (int)Math.Floor(minOscilFreq),
+ (int)Math.Floor(maxOscilFreq),
+ dctThreshold,
+ eventThreshold,
+ minDurationSeconds,
+ maxDurationSeconds,
+ out var scores,
+ out var acousticEvents,
+ out var hits,
+ segmentStartOffset);
+
+ Assert.AreEqual(4, acousticEvents.Count);
+
+ Assert.AreEqual(5, acousticEvents[0].Oblong.ColumnLeft);
+ Assert.AreEqual(46, acousticEvents[0].Oblong.ColumnRight);
+ Assert.AreEqual(1280, acousticEvents[0].Oblong.RowTop);
+ Assert.AreEqual(1381, acousticEvents[0].Oblong.RowBottom);
+
+ Assert.AreEqual(5, acousticEvents[1].Oblong.ColumnLeft);
+ Assert.AreEqual(46, acousticEvents[1].Oblong.ColumnRight);
+ Assert.AreEqual(1762, acousticEvents[1].Oblong.RowTop);
+ Assert.AreEqual(1826, acousticEvents[1].Oblong.RowBottom);
+
+ Assert.AreEqual(5, acousticEvents[2].Oblong.ColumnLeft);
+ Assert.AreEqual(46, acousticEvents[2].Oblong.ColumnRight);
+ Assert.AreEqual(2083, acousticEvents[2].Oblong.RowTop);
+ Assert.AreEqual(2208, acousticEvents[2].Oblong.RowBottom);
+
+ Assert.AreEqual(5, acousticEvents[3].Oblong.ColumnLeft);
+ Assert.AreEqual(46, acousticEvents[3].Oblong.ColumnRight);
+ Assert.AreEqual(2334, acousticEvents[3].Oblong.RowTop);
+ Assert.AreEqual(2383, acousticEvents[3].Oblong.RowBottom);
+
+ //Assert.AreEqual(0.6062, stats.SpectralEnergyDistribution, 1E-4);
+ }
+
+ [TestMethod]
+ public void TestGetEventsAroundMaxima()
+ {
+ //string abbreviatedSpeciesName = "Pteropus";
+ string speciesName = "Pteropus species";
+ int minHz = 800;
+ int maxHz = 8000;
+ var minTimeSpan = TimeSpan.FromSeconds(0.15);
+ var maxTimeSpan = TimeSpan.FromSeconds(0.8);
+ double decibelThreshold = 9.0;
+ TimeSpan segmentStartOffset = TimeSpan.Zero;
+
+ var decibelArray = SNR.CalculateFreqBandAvIntensity(this.sonogram.Data, minHz, maxHz, this.sonogram.NyquistFrequency);
+
+ // prepare plots
+ double intensityNormalisationMax = 3 * decibelThreshold;
+ var eventThreshold = decibelThreshold / intensityNormalisationMax;
+ var normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, 0, intensityNormalisationMax);
+ var plot = new Plot(speciesName + " Territory", normalisedIntensityArray, eventThreshold);
+ var plots = new List { plot };
+
+ //iii: CONVERT decibel SCORES TO ACOUSTIC EVENTS
+ var acousticEvents = AcousticEvent.GetEventsAroundMaxima(
+ decibelArray,
+ segmentStartOffset,
+ minHz,
+ maxHz,
+ decibelThreshold,
+ minTimeSpan,
+ maxTimeSpan,
+ this.sonogram.FramesPerSecond,
+ this.sonogram.FBinWidth);
+
+ Assert.AreEqual(10, acousticEvents.Count);
+
+ Assert.AreEqual(new Rectangle(19, 1751, 168, 27), acousticEvents[0].GetEventAsRectangle());
+ Assert.AreEqual(new Rectangle(19, 1840, 168, 10), acousticEvents[2].GetEventAsRectangle());
+ Assert.AreEqual(new Rectangle(19, 1961, 168, 31), acousticEvents[5].GetEventAsRectangle());
+ Assert.AreEqual(new Rectangle(19, 2294, 168, 17), acousticEvents[7].GetEventAsRectangle());
+ Assert.AreEqual(new Rectangle(19, 2504, 168, 7), acousticEvents[9].GetEventAsRectangle());
+
+ //Assert.AreEqual(28.Seconds() + segmentOffset, stats.ResultStartSeconds.Seconds());
+ }
+ }
+}
diff --git a/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs b/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs
index 00b22f818..f8151b7cd 100644
--- a/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs
+++ b/tests/Acoustics.Test/AudioAnalysisTools/StandardSpectrograms/SonogramTests.cs
@@ -4,15 +4,18 @@
namespace Acoustics.Test.AudioAnalysisTools.StandardSpectrograms
{
+ using System;
+ using System.Collections.Generic;
using System.IO;
using Acoustics.Shared;
- using DSP;
+ using Acoustics.Test.AudioAnalysisTools.DSP;
+ using Acoustics.Test.TestHelpers;
using global::AudioAnalysisTools;
using global::AudioAnalysisTools.DSP;
using global::AudioAnalysisTools.StandardSpectrograms;
using global::AudioAnalysisTools.WavTools;
+ using global::TowseyLibrary;
using Microsoft.VisualStudio.TestTools.UnitTesting;
- using TestHelpers;
///
/// Test methods for the various standard Sonograms or Spectrograms
@@ -28,6 +31,9 @@ public class SonogramTests
{
private const double AllowedDelta = 0.000001;
private DirectoryInfo outputDirectory;
+ private AudioRecording recording;
+ private FrequencyScale freqScale;
+ private SonogramConfig sonoConfig;
/*
// You can use the following additional attributes as you write your tests:
@@ -53,12 +59,30 @@ public class SonogramTests
public void Setup()
{
this.outputDirectory = PathHelper.GetTempDir();
+ this.recording = new AudioRecording(PathHelper.ResolveAsset("Recordings", "BAC2_20071008-085040.wav"));
+
+ // specified linear scale
+ this.freqScale = new FrequencyScale(nyquist: 11025, frameSize: 1024, hertzGridInterval: 1000);
+
+ // set up the config for each spectrogram
+ this.sonoConfig = new SonogramConfig
+ {
+ WindowSize = this.freqScale.FinalBinCount * 2,
+ WindowOverlap = 0.2,
+ SourceFName = this.recording.BaseName,
+ NoiseReductionType = NoiseReductionType.None,
+ NoiseReductionParameter = 0.0,
+ };
}
[TestCleanup]
public void Cleanup()
{
PathHelper.DeleteTempDir(this.outputDirectory);
+ this.recording.Dispose();
+
+ //this.freqScale.();
+ //this.sonoConfig.Dispose();
}
///
@@ -88,23 +112,9 @@ public void TestAverageOfDecibelValues()
[TestMethod]
public void TestAmplitudeSonogram()
{
- var recording = new AudioRecording(PathHelper.ResolveAsset("Recordings", "BAC2_20071008-085040.wav"));
-
- // specfied linear scale
- var freqScale = new FrequencyScale(nyquist: 11025, frameSize: 1024, hertzGridInterval: 1000);
-
- var sonoConfig = new SonogramConfig
- {
- WindowSize = freqScale.FinalBinCount * 2,
- WindowOverlap = 0.2,
- SourceFName = recording.BaseName,
- NoiseReductionType = NoiseReductionType.None,
- NoiseReductionParameter = 0.0,
- };
-
// DO EQUALITY TEST on the AMPLITUDE SONGOGRAM DATA
// Do not bother with the image because this is only an amplitude spectrogram.
- var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);
+ var sonogram = new AmplitudeSonogram(this.sonoConfig, this.recording.WavReader);
var expectedFile = PathHelper.ResolveAsset("StandardSonograms", "BAC2_20071008_AmplSonogramData.EXPECTED.bin");
// run this once to generate expected test data
@@ -120,23 +130,9 @@ public void TestAmplitudeSonogram()
[TestMethod]
public void TestDecibelSpectrogram()
{
- var recording = new AudioRecording(PathHelper.ResolveAsset("Recordings", "BAC2_20071008-085040.wav"));
-
- // specfied linear scale
- var freqScale = new FrequencyScale(nyquist: 11025, frameSize: 1024, hertzGridInterval: 1000);
-
- var sonoConfig = new SonogramConfig
- {
- WindowSize = freqScale.FinalBinCount * 2,
- WindowOverlap = 0.2,
- SourceFName = recording.BaseName,
- NoiseReductionType = NoiseReductionType.None,
- NoiseReductionParameter = 0.0,
- };
-
// DO EQUALITY TEST on the AMPLITUDE SONGOGRAM DATA
// Do not bother with the image because this is only an amplitude spectrogram.
- var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);
+ var sonogram = new AmplitudeSonogram(this.sonoConfig, this.recording.WavReader);
// DO FILE EQUALITY TEST on the DECIBEL SONGOGRAM DATA
// Do not bother with the image because this has been tested elsewhere.
@@ -157,28 +153,69 @@ public void TestDecibelSpectrogram()
[TestMethod]
public void SonogramDecibelMethodsAreEquivalent()
{
- var recording = new AudioRecording(PathHelper.ResolveAsset("Recordings", "BAC2_20071008-085040.wav"));
+ // Method 1
+ var sonogram = new AmplitudeSonogram(this.sonoConfig, this.recording.WavReader);
+ var expectedDecibelSonogram = MFCCStuff.DecibelSpectra(sonogram.Data, sonogram.Configuration.WindowPower, sonogram.SampleRate, sonogram.Configuration.epsilon);
+
+ // Method 2: make sure that the decibel spectrum is the same no matter which path we take to calculate it.
+ var actualDecibelSpectrogram = new SpectrogramStandard(this.sonoConfig, this.recording.WavReader);
- // specfied linear scale
- var freqScale = new FrequencyScale(nyquist: 11025, frameSize: 1024, hertzGridInterval: 1000);
+ CollectionAssert.That.AreEqual(expectedDecibelSonogram, actualDecibelSpectrogram.Data, EnvelopeAndFftTests.Delta);
+ }
- var sonoConfig = new SonogramConfig
+ [TestMethod]
+ public void TestAnnotatedSonogramWithPlots()
+ {
+ // Make a decibel spectrogram
+ var actualDecibelSpectrogram = new SpectrogramStandard(this.sonoConfig, this.recording.WavReader);
+
+ // prepare normalisation bounds for three plots
+ double minDecibels = -100.0;
+ double maxDecibels = -50;
+
+ //double decibelThreshold = 12.5 dB above -100 dB;
+ var normThreshold = 0.25;
+
+ //plot 1
+ int minHz = 2000;
+ int maxHz = 3000;
+ var decibelArray = SNR.CalculateFreqBandAvIntensity(actualDecibelSpectrogram.Data, minHz, maxHz, actualDecibelSpectrogram.NyquistFrequency);
+ var normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, minDecibels, maxDecibels);
+ var plot1 = new Plot("Intensity 2-3 kHz", normalisedIntensityArray, normThreshold);
+
+ //plot 2
+ minHz = 3000;
+ maxHz = 4000;
+ decibelArray = SNR.CalculateFreqBandAvIntensity(actualDecibelSpectrogram.Data, minHz, maxHz, actualDecibelSpectrogram.NyquistFrequency);
+ normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, minDecibels, maxDecibels);
+ var plot2 = new Plot("Intensity 3-4 kHz", normalisedIntensityArray, normThreshold);
+
+ //plot 3
+ minHz = 4000;
+ maxHz = 5000;
+ decibelArray = SNR.CalculateFreqBandAvIntensity(actualDecibelSpectrogram.Data, minHz, maxHz, actualDecibelSpectrogram.NyquistFrequency);
+ normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, minDecibels, maxDecibels);
+ var plot3 = new Plot("Intensity 4-5 kHz", normalisedIntensityArray, normThreshold);
+
+ // combine the plots
+ var plots = new List { plot1, plot2, plot3 };
+
+ // create three events
+ var startOffset = TimeSpan.Zero;
+ var events = new List
{
- WindowSize = freqScale.FinalBinCount * 2,
- WindowOverlap = 0.2,
- SourceFName = recording.BaseName,
- NoiseReductionType = NoiseReductionType.None,
- NoiseReductionParameter = 0.0,
+ new AcousticEvent(startOffset, 10.0, 10.0, 2000, 3000),
+ new AcousticEvent(startOffset, 25.0, 10.0, 3000, 4000),
+ new AcousticEvent(startOffset, 40.0, 10.0, 4000, 5000),
};
- // Method 1
- var sonogram = new AmplitudeSonogram(sonoConfig, recording.WavReader);
- var expectedDecibelSonogram = MFCCStuff.DecibelSpectra(sonogram.Data, sonogram.Configuration.WindowPower, sonogram.SampleRate, sonogram.Configuration.epsilon);
+ var image = SpectrogramTools.GetSonogramPlusCharts(actualDecibelSpectrogram, events, plots, null);
- // Method 2: make sure that the decibel spectrum is the same no matter which path we take to calculate it.
- var actualDecibelSpectrogram = new SpectrogramStandard(sonoConfig, recording.WavReader);
+ // create the image for visual confirmation
+ image.Save(Path.Combine(this.outputDirectory.FullName, this.recording.BaseName + ".png"));
- CollectionAssert.That.AreEqual(expectedDecibelSonogram, actualDecibelSpectrogram.Data, EnvelopeAndFftTests.Delta);
+ Assert.AreEqual(1621, image.Width);
+ Assert.AreEqual(647, image.Height);
}
}
}
diff --git a/tests/Fixtures/Recordings/20190115_Bellingen_Feeding_minute6_OneChannel22050.wav b/tests/Fixtures/Recordings/20190115_Bellingen_Feeding_minute6_OneChannel22050.wav
new file mode 100644
index 000000000..dd8d2b700
--- /dev/null
+++ b/tests/Fixtures/Recordings/20190115_Bellingen_Feeding_minute6_OneChannel22050.wav
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a98f3b2e5009318e2867172b7c5b9802f625c8c03d585a4134bf2ed03b191644
+size 2647962