diff --git a/src/AnalysisPrograms/EPR.cs b/src/AnalysisPrograms/EPR.cs deleted file mode 100644 index e0019bb16..000000000 --- a/src/AnalysisPrograms/EPR.cs +++ /dev/null @@ -1,362 +0,0 @@ -// -// 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 AnalysisPrograms -{ - using System; - using System.Collections.Generic; - using System.ComponentModel.DataAnnotations; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - using AnalysisPrograms.Production.Arguments; - using AudioAnalysisTools; - using AudioAnalysisTools.DSP; - using AudioAnalysisTools.StandardSpectrograms; - using AudioAnalysisTools.WavTools; - using McMaster.Extensions.CommandLineUtils; - using TowseyLibrary; - - /// - /// ######### 2021 NOTE: - /// This class was refactored in July 2021 to remove all dependencies on the Oscillations2010 class. The 2010 class was long out of date. - /// Instead, the Execute() method now calls the Oscillations2019 class which implements a similar but improved algorithm. - /// However some work remains to be done to remove the previous system of config dictionaries. - /// - /// ######## THE FOLLOWING NOTES DATE BACK TO 2010-2011. - /// This program runs an alternative version of Event Pattern Recognition (EPR) - /// It can be used to detect Ground Parrots. - /// It was developed by Michael Towsey in 20010 to address difficulties in the original EPR algorithm - see more below. - /// COMMAND LINE ARGUMENTS: - /// string recordingPath = args[0]; //the recording to be scanned - /// string iniPath = args[1]; //the initialisation file containing parameters for AED and EPR - /// string targetName = args[2]; //prefix of name of the created output files - /// - /// The program currently produces only ONE output file: an image of the recording to be scanned with an energy track and two score tracks. - /// 1) Energy track - Measure of the total energy in the user defined frequency band, one value per frame. - /// 2) Score Track 1 - Oscillation score - Requires user defined parameters to detect the repeated chirp of a Ground Parrot. - /// Is a way to cut down the search space. Only deploy template at places where high Oscillation score. - /// 3) Score Track 2 - Template score - dB centre-surround difference of each template rectangle. - /// Currently the dB Score is averaged over the 15 AEs in the groundparrot template. - /// - /// THE EXISTING ALGORITHM: - /// 1) Convert the signal to dB spectrogram - /// 2) AED: i) noise removal - /// ii) convert to binary using dB threshold. - /// iii) Use spidering algorithm to marquee acoustic events. - /// iv) Split over-size events where possible - /// v) Remove under-size events. - /// 3) EPR: i) Align first AE of template to first 'valid' AE in spectrogram. A valid AE is one whose lower left vertex lies in the - /// user-defined freq band. Currently align lower left vertex for groundparrot recogniser. - /// ii) For each AE in template find closest AE in spectrogram. (Least euclidian distance) - /// iii) For each AE in template, calculate percent overlap to closest AE in spectrogram. - /// iv) Apply threshold to adjust the FP-FN trade-off. - /// - /// PROBLEM WITH EXISTING ALGORITHM: - /// AED: - /// i) Major problem is that the AEs found by AED depend greatly on the user-supplied dB threshold. - /// If threshold too low, the components of the ground parrot call get incorporated into a single larger AE. - /// ii) The spidering algorithm (copied by Brad from MatLab into F#) is computationally expensive. - /// EPR: - /// i) EPR is hard coded for groundparrots. In particular the configuration of AEs in the template is hard-coded. - /// ii) EPR is hard coded to align the template using the lower-left vertex of the first AE. - /// This is suitable for the rising cadence of a groundparrot call - but not if descending. - /// - /// POSSIBLE SOLUTIONS TO EPR AND AED - /// AED: - /// i) Need an approach whose result does not depend critically on the user-supplied dB threshold. - /// SOLUTION: Try multiple thresholds starting high and dropping in 2dB steps - pick largest score. - /// ii) Use oscillation detection (OD) to find locations where good change of ground parrot. - /// This only works if the chirps are repeated at fixed interval. - /// EPR: - /// i) Instead of aligning lower-left of AEs align the centroid. - /// ii) Only consider AEs whose centroid lies in the frequency band. - /// iii) Only consider AEs whose area is 'similar' to first AE of template. - /// iv) Only find overlaps for AEs 2-15 if first overlap exceeds the threshold. - /// - /// ############################################################################################################### - /// TWO NEW EPR ALGORITHMS BELOW: - /// IDEA 1: - /// Note: NOT all the above ideas have been implemented. Just a few. - /// The below does NOT implement AED and does not attempt noise removal to avoid the dB thresholding problem. - /// The below uses a different EPR metric - /// 1) Convert the signal to dB spectrogram - /// 2) Detect energy oscillations in the user defined frequency band. - /// i) Calulate the dB power in freq band of each frame. - /// 3) DCT: - /// i) Use Discrete Cosine Transform to detect oscillations in band energy. - /// ii) Only apply template where the DCT score exceeds a threshold (normalised). Align start of template to high dB frame. - /// 4) TEMPLATE SCORE - /// i) Calculate dB score for first AE in template. dB score = max_dB - surround_dB - /// where max_dB = max dB value for all pixels in first template AE. - /// ii) Do not proceed if dB score below threshold else calculate dB score for remaining template AEs. - /// iii) Calculate average dB score over all 15 AEs in template. - /// - /// COMMENT ON NEW ALGORITHM - /// 1) It is very fast. - /// 2) Works well where call shows up as energy oscillation. - /// 3) Is not as accurate because the dB score has less discrimination than original EPR. - /// BUT COULD COMBINE THE TWO APPROACHES. - /// - /// ############################################################################################################### - /// IDEA 2: ANOTHER EPR ALGORITHM - /// 1) Convert the signal to dB spectrogram - /// 2) Detect energy oscillations in the user defined frequency band. - /// i) Calulate the dB power in freq band of each frame. - /// - /// 3) DCT: OPTIONAL - /// ONLY PROCEED IF HAVE HIGH dB SCORE and HIGH DCT SCORE - /// - /// 4) NOISE COMPENSAION - /// Subtract modal noise but DO NOT truncate -dB values to zero. - /// - /// 5) AT POINTS THAT EXCEED dB and DCT thresholds - /// i) loop through dB thresholds from 10dB down to 3dB in steps of 1-2dB. - /// ii) determine valid AEs in freq band and within certain time range of current position. - /// A valid AE has two attributes: a) entirely within freq band and time width; b) >= 70% overlap with first template AE - /// iii) Accumulate a list of valid starting point AEs - /// iv) Apply template to each valid start point. - /// a) extract AEs at dB threshold apporpriate to the valid AE - /// a) align centroid of valid AE with centroid of first template AE - /// c) calculate the overlap score. - /// - public class EPR - { - public const string CommandName = "EPR"; - - [Command( - CommandName, - Description = "[UNMAINTAINED] Event Pattern Recognition - used for ground-parrots (TOWSEY version). Revise code if intend to use.")] - public class Arguments : SourceAndConfigArguments - { - [Option(Description = "prefix of name of the created output files")] - [LegalFilePath] - [Required] - public string Target { get; set; } - - public override Task Execute(CommandLineApplication app) - { - EPR.Execute(this); - return this.Ok(); - } - } - - public static void Execute(Arguments arguments) - { - MainEntry.WarnIfDeveloperEntryUsed(); - - string title = "# EVENT PATTERN RECOGNITION."; - string date = "# DATE AND TIME: " + DateTime.Now; - Log.WriteLine(title); - Log.WriteLine(date); - - Log.Verbosity = 1; - - string targetName = arguments.Target; // prefix of name of created files - - var input = arguments.Source; - - string recordingFileName = input.Name; - string recordingDirectory = input.DirectoryName; - DirectoryInfo outputDir = arguments.Config.ToFileInfo().Directory; - FileInfo targetPath = outputDir.CombineFile(targetName + "_target.txt"); - FileInfo targetNoNoisePath = outputDir.CombineFile(targetName + "_targetNoNoise.txt"); - FileInfo noisePath = outputDir.CombineFile(targetName + "_noise.txt"); - FileInfo targetImagePath = outputDir.CombineFile(targetName + "_target.png"); - FileInfo paramsPath = outputDir.CombineFile(targetName + "_params.txt"); - - Log.WriteIfVerbose("# Output folder =" + outputDir); - - //i: GET RECORDING - AudioRecording recording = new AudioRecording(input.FullName); - - //if (recording.SampleRate != 22050) recording.ConvertSampleRate22kHz(); THIS METHOD CALL IS OBSOLETE - int sr = recording.SampleRate; - - //ii: READ PARAMETER VALUES FROM INI FILE - var config = new ConfigDictionary(arguments.Config); - Dictionary dict = config.GetTable(); - - // framing parameters - //double frameOverlap = FeltTemplates_Use.FeltFrameOverlap; // default = 0.5 - double frameOverlap = double.Parse(dict["FRAME_OVERLAP"]); - - //the search band band - int minHz = int.Parse(dict["MIN_HZ"]); - int maxHz = int.Parse(dict["MAX_HZ"]); - - // oscillation OD parameters - double dctDuration = double.Parse(dict["dctDuration"]); // 2.0; // seconds - double dctThreshold = double.Parse(dict["dctThreshold"]); // 0.5; - int minOscFreq = int.Parse(dict["minOscillationFrequency"]); // 4; - int maxOscFreq = int.Parse(dict["maxOscillationFrequency"]); // 5; - double decibelThreshold = double.Parse(dict["decibelThreshold"]); - - // iii initialize the spectrogram config with default values.. - SonogramConfig sonoConfig = new SonogramConfig - { - SourceFName = recording.BaseName, - - //sonoConfig.WindowSize = windowSize; - WindowOverlap = frameOverlap, - }; - - // iv: generate the spectrogram - var spectrogram = new SpectrogramStandard(sonoConfig, recording.WavReader); - - Log.WriteLine( - "Frames: Size={0}, Count={1}, Duration={2:f1}ms, Overlap={5:f2}%, Offset={3:f1}ms, Frames/s={4:f1}", - spectrogram.Configuration.WindowSize, - spectrogram.FrameCount, - spectrogram.FrameDuration * 1000, - spectrogram.FrameStep * 1000, - spectrogram.FramesPerSecond, - frameOverlap); - - int binCount = (int)(maxHz / spectrogram.FBinWidth) - (int)(minHz / spectrogram.FBinWidth) + 1; - Log.WriteIfVerbose("Freq band: {0} Hz - {1} Hz. (Freq bin count = {2})", minHz, maxHz, binCount); - - // v: extract the subband energy array - // smooth the spectra in all time-frames. - spectrogram.Data = MatrixTools.SmoothRows(spectrogram.Data, 3); - - // extract array of decibel values, frame averaged over required frequency band - var decibelArray = SNR.CalculateFreqBandAvIntensity(spectrogram.Data, minHz, maxHz, spectrogram.NyquistFrequency); - - // can noise reduce the decibel array here if it helps. - // but only if noise reduction not already done earlier. - - // ############################################################################################################################################# - // vi: look for oscillation at required oscillation rate for ground parrots. - // The following call to the Oscillations2010 class is no longer available. Class deprecated on 2 July 2021. - // Instead use the below call to the Oscillations2019 class. - /* - double[] odScores = Oscillations2010.DetectOscillationsInScoreArray( - dBArray, - dctDuration, - sonogram.FramesPerSecond, - dctThreshold, - normaliseDCT, - minOscilRate, - maxOscilRate); - */ - - // vi: look for oscillation at required oscillation rate for ground parrots. - var framesPerSecond = spectrogram.FramesPerSecond; - Oscillations2019.DetectOscillations( - decibelArray, - framesPerSecond, - decibelThreshold, - dctDuration, - minOscFreq, - maxOscFreq, - dctThreshold, - out var dctScores, - out var oscFreq); - - double maxOctScore = 1.0; - dctScores = SNR.NormaliseDecibelArray_ZeroOne(dctScores, maxOctScore); - dctScores = DataTools.filterMovingAverage(dctScores, 5); - - // ############################################################################################################################################# - // vii: LOOK FOR GROUND PARROTS USING TEMPLATE - var template = GroundParrotRecogniser.ReadGroundParrotTemplateAsList(spectrogram); - double[] gpScores = DetectEPR(template, spectrogram, dctScores, dctThreshold); - gpScores = DataTools.normalise(gpScores); //NormaliseMatrixValues 0 - 1 - - // ############################################################################################################################################# - // viii: SAVE image of extracted event in the original sonogram - string sonogramImagePath = outputDir + Path.GetFileNameWithoutExtension(recordingFileName) + ".png"; - } - - public static double[] DetectEPR(List template, BaseSonogram sonogram, double[] odScores, double odThreshold) - { - int length = sonogram.FrameCount; - double[] eprScores = new double[length]; - Oblong ob1 = template[0].Oblong; // the first chirp in template - Oblong obZ = template[template.Count - 1].Oblong; // the last chirp in template - int templateLength = obZ.RowBottom; - - for (int frame = 0; frame < length - templateLength; frame++) - { - if (odScores[frame] < odThreshold) - { - continue; - } - - // get best freq band and max score for the first rectangle. - double maxScore = -double.MaxValue; - int freqBinOffset = 0; - for (int bin = -5; bin < 15; bin++) - { - Oblong ob = new Oblong(ob1.RowTop + frame, ob1.ColumnLeft + bin, ob1.RowBottom + frame, ob1.ColumnRight + bin); - double score = GetLocationScore(sonogram, ob); - if (score > maxScore) - { - maxScore = score; - freqBinOffset = bin; - } - } - - //if location score exceeds threshold of 6 dB then get remaining scores. - if (maxScore < 6.0) - { - continue; - } - - foreach (AcousticEvent ae in template) - { - Oblong ob = new Oblong(ae.Oblong.RowTop + frame, ae.Oblong.ColumnLeft + freqBinOffset, ae.Oblong.RowBottom + frame, ae.Oblong.ColumnRight + freqBinOffset); - double score = GetLocationScore(sonogram, ob); - eprScores[frame] += score; - } - - eprScores[frame] /= template.Count; - } - - return eprScores; - } - - /// - /// reutrns the difference between the maximum dB value in a retangular location and the average of the boundary dB values. - /// - public static double GetLocationScore(BaseSonogram sonogram, Oblong ob) - { - double max = -double.MaxValue; - for (int r = ob.RowTop; r < ob.RowBottom; r++) - { - for (int c = ob.ColumnLeft; c < ob.ColumnRight; c++) - { - if (sonogram.Data[r, c] > max) - { - max = sonogram.Data[r, c]; - } - } - } - - //calculate average boundary value - int boundaryLength = 2 * (ob.RowBottom - ob.RowTop + 1 + ob.ColumnRight - ob.ColumnLeft + 1); - double boundaryValue = 0.0; - for (int r = ob.RowTop; r < ob.RowBottom; r++) - { - boundaryValue += sonogram.Data[r, ob.ColumnLeft] + sonogram.Data[r, ob.ColumnRight]; - } - - for (int c = ob.ColumnLeft; c < ob.ColumnRight; c++) - { - boundaryValue += sonogram.Data[ob.RowTop, c] + sonogram.Data[ob.RowBottom, c]; - } - - boundaryValue /= boundaryLength; - - double score = max - boundaryValue; - if (score < 0.0) - { - score = 0.0; - } - - return score; - } - } // end class -} \ No newline at end of file diff --git a/src/AnalysisPrograms/Production/Arguments/MainArgs.cs b/src/AnalysisPrograms/Production/Arguments/MainArgs.cs index 0c6d9acf5..71f14af28 100644 --- a/src/AnalysisPrograms/Production/Arguments/MainArgs.cs +++ b/src/AnalysisPrograms/Production/Arguments/MainArgs.cs @@ -43,7 +43,7 @@ namespace AnalysisPrograms.Production.Arguments typeof(ContentDescription.BuildModel.Arguments), typeof(Audio2InputForConvCnn.Arguments), typeof(DifferenceSpectrogram.Arguments), - typeof(EPR.Arguments), + //typeof(EPR.Arguments), typeof(GroundParrotRecogniser.Arguments), typeof(LSKiwi3.Arguments), typeof(LSKiwiROC.Arguments),