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(29.72, acousticEvents[0].EventStartSeconds, 0.1); Assert.AreEqual(32.06, acousticEvents[0].EventEndSeconds, 0.1); Assert.AreEqual(200, acousticEvents[0].LowFrequencyHertz); Assert.AreEqual(2000, acousticEvents[0].HighFrequencyHertz); Assert.AreEqual(40.91, acousticEvents[1].EventStartSeconds, 0.1); Assert.AreEqual(42.40, acousticEvents[1].EventEndSeconds, 0.1); Assert.AreEqual(200, acousticEvents[1].LowFrequencyHertz); Assert.AreEqual(2000, acousticEvents[1].HighFrequencyHertz); Assert.AreEqual(48.37, acousticEvents[2].EventStartSeconds, 0.1); Assert.AreEqual(51.27, acousticEvents[2].EventEndSeconds, 0.1); Assert.AreEqual(200, acousticEvents[2].LowFrequencyHertz); Assert.AreEqual(2000, acousticEvents[2].HighFrequencyHertz); Assert.AreEqual(54.19, acousticEvents[3].EventStartSeconds, 0.1); Assert.AreEqual(55.33, acousticEvents[3].EventEndSeconds, 0.1); Assert.AreEqual(200, acousticEvents[3].LowFrequencyHertz); Assert.AreEqual(2000, acousticEvents[3].HighFrequencyHertz); //Assert.AreEqual(0.6062, stats.SpectralEnergyDistribution, 1E-4); }
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); }
/// <summary> /// Do your analysis. This method is called once per segment (typically one-minute segments). /// </summary> public override RecognizerResults Recognize(AudioRecording recording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { string speciesName = configuration[AnalysisKeys.SpeciesName] ?? "<no species>"; string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>"; int minHz = configuration.GetInt(AnalysisKeys.MinHz); int maxHz = configuration.GetInt(AnalysisKeys.MaxHz); // BETTER TO CALCULATE THIS. IGNORE USER! // double frameOverlap = Double.Parse(configDict[Keys.FRAME_OVERLAP]); // duration of DCT in seconds double dctDuration = configuration.GetDouble(AnalysisKeys.DctDuration); // minimum acceptable value of a DCT coefficient double dctThreshold = configuration.GetDouble(AnalysisKeys.DctThreshold); // ignore oscillations below this threshold freq int minOscilFreq = configuration.GetInt(AnalysisKeys.MinOscilFreq); // ignore oscillations above this threshold freq int maxOscilFreq = configuration.GetInt(AnalysisKeys.MaxOscilFreq); // min duration of event in seconds double minDuration = configuration.GetDouble(AnalysisKeys.MinDuration); // max duration of event in seconds double maxDuration = configuration.GetDouble(AnalysisKeys.MaxDuration); // min score for an acceptable event double eventThreshold = configuration.GetDouble(AnalysisKeys.EventThreshold); if (recording.WavReader.SampleRate != 22050) { throw new InvalidOperationException("Requires a 22050Hz file"); } // The default was 512 for Canetoad. // Set longer Framesize for calls having longer pulse periodicity. const int FrameSize = 128; double windowOverlap = Oscillations2012.CalculateRequiredFrameOverlap( recording.SampleRate, FrameSize, maxOscilFreq); //windowOverlap = 0.75; // previous default // i: MAKE SONOGRAM var sonoConfig = new SonogramConfig { SourceFName = recording.BaseName, WindowSize = FrameSize, WindowOverlap = windowOverlap, //NoiseReductionType = NoiseReductionType.NONE, NoiseReductionType = NoiseReductionType.Standard, NoiseReductionParameter = 0.1, }; // sonoConfig.NoiseReductionType = SNR.Key2NoiseReductionType("STANDARD"); TimeSpan recordingDuration = recording.Duration; int sr = recording.SampleRate; double freqBinWidth = sr / (double)sonoConfig.WindowSize; BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); int rowCount = sonogram.Data.GetLength(0); int colCount = sonogram.Data.GetLength(1); // double[,] subMatrix = MatrixTools.Submatrix(sonogram.Data, 0, minBin, (rowCount - 1), maxbin); // ###################################################################### // ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER // This window is used to smooth the score array before extracting events. // A short window (e.g. 3) preserves sharper score edges to define events but also keeps noise. int scoreSmoothingWindow = 13; Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, eventThreshold, minDuration, maxDuration, scoreSmoothingWindow, out var scores, out var oscillationEvents, out var hits, segmentStartOffset); var acousticEvents = oscillationEvents.ConvertSpectralEventsToAcousticEvents(); acousticEvents.ForEach(ae => { ae.SpeciesName = speciesName; ae.SegmentDurationSeconds = recordingDuration.TotalSeconds; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; ae.Name = abbreviatedSpeciesName; }); var plot = new Plot(this.DisplayName, scores, eventThreshold); var plots = new List <Plot> { plot }; this.WriteDebugImage(recording, outputDirectory, sonogram, acousticEvents, plots, hits); return(new RecognizerResults() { Sonogram = sonogram, Hits = hits, Plots = plots, Events = acousticEvents, }); }
/// <inheritdoc/> public override RecognizerResults Recognize( AudioRecording audioRecording, Config genericConfig, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { var configuration = (GenericRecognizerConfig)genericConfig; if (configuration.Profiles?.Count < 1) { throw new ConfigFileException( "The generic recognizer needs at least one profile set. 0 were found."); } int count = configuration.Profiles.Count; var message = $"Found {count} analysis profile(s): " + configuration.Profiles.Keys.Join(", "); Log.Info(message); var allResults = new RecognizerResults() { Events = new List <AcousticEvent>(), NewEvents = new List <EventCommon>(), Hits = null, ScoreTrack = null, Plots = new List <Plot>(), Sonogram = null, }; // Now process each of the profiles foreach (var(profileName, profileConfig) in configuration.Profiles) { Log.Info("Processing profile: " + profileName); //List<AcousticEvent> acousticEvents; List <EventCommon> spectralEvents; var plots = new List <Plot>(); SpectrogramStandard sonogram; Log.Debug($"Using the {profileName} algorithm... "); if (profileConfig is CommonParameters parameters) { if (profileConfig is BlobParameters || profileConfig is OscillationParameters || profileConfig is OnebinTrackParameters || profileConfig is HarmonicParameters || profileConfig is ForwardTrackParameters || profileConfig is UpwardTrackParameters || profileConfig is OneframeTrackParameters) { sonogram = new SpectrogramStandard(ParametersToSonogramConfig(parameters), audioRecording.WavReader); if (profileConfig is BlobParameters bp) { //get the array of intensity values minus intensity in side/buffer bands. //i.e. require silence in side-bands. Otherwise might simply be getting part of a broader band acoustic event. var decibelArray = SNR.CalculateFreqBandAvIntensityMinusBufferIntensity( sonogram.Data, bp.MinHertz.Value, bp.MaxHertz.Value, bp.BottomHertzBuffer.Value, bp.TopHertzBuffer.Value, sonogram.NyquistFrequency); // prepare plot of resultant blob decibel array. var plot = PreparePlot(decibelArray, $"{profileName} (Blob:db Intensity)", bp.DecibelThreshold.Value); plots.Add(plot); // iii: CONVERT blob decibel SCORES TO ACOUSTIC EVENTS. // Note: This method does NOT do prior smoothing of the dB array. var acEvents = AcousticEvent.GetEventsAroundMaxima( decibelArray, segmentStartOffset, bp.MinHertz.Value, bp.MaxHertz.Value, bp.DecibelThreshold.Value, TimeSpan.FromSeconds(bp.MinDuration.Value), TimeSpan.FromSeconds(bp.MaxDuration.Value), sonogram.FramesPerSecond, sonogram.FBinWidth); spectralEvents = acEvents.ConvertAcousticEventsToSpectralEvents(); } else if (profileConfig is OnebinTrackParameters wp) { //get the array of intensity values minus intensity in side/buffer bands. double[] decibelArray; (spectralEvents, decibelArray) = OnebinTrackAlgorithm.GetOnebinTracks( sonogram, wp, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (Whistle:dB Intensity)", wp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is ForwardTrackParameters tp) { double[] decibelArray; (spectralEvents, decibelArray) = ForwardTrackAlgorithm.GetForwardTracks( sonogram, tp, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (Chirps:dB Intensity)", tp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is OneframeTrackParameters cp) { double[] decibelArray; (spectralEvents, decibelArray) = OneframeTrackAlgorithm.GetOneFrameTracks( sonogram, cp, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (Clicks:dB Intensity)", cp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is UpwardTrackParameters vtp) { double[] decibelArray; (spectralEvents, decibelArray) = UpwardTrackAlgorithm.GetUpwardTracks( sonogram, vtp, segmentStartOffset); var plot = PreparePlot(decibelArray, $"{profileName} (VerticalTrack:dB Intensity)", vtp.DecibelThreshold.Value); plots.Add(plot); } else if (profileConfig is HarmonicParameters hp) { double[] decibelMaxArray; double[] harmonicIntensityScores; (spectralEvents, decibelMaxArray, harmonicIntensityScores) = HarmonicParameters.GetComponentsWithHarmonics( sonogram, hp.MinHertz.Value, hp.MaxHertz.Value, sonogram.NyquistFrequency, hp.DecibelThreshold.Value, hp.DctThreshold.Value, hp.MinDuration.Value, hp.MaxDuration.Value, hp.MinFormantGap.Value, hp.MaxFormantGap.Value, segmentStartOffset); var plot = PreparePlot(harmonicIntensityScores, $"{profileName} (Harmonics:dct intensity)", hp.DctThreshold.Value); plots.Add(plot); } else if (profileConfig is OscillationParameters op) { Oscillations2012.Execute( sonogram, op.MinHertz.Value, op.MaxHertz.Value, op.DctDuration, op.MinOscillationFrequency, op.MaxOscillationFrequency, op.DctThreshold, op.EventThreshold, op.MinDuration.Value, op.MaxDuration.Value, out var scores, out var oscillationEvents, out var hits, segmentStartOffset); spectralEvents = new List <EventCommon>(oscillationEvents); //plots.Add(new Plot($"{profileName} (:OscillationScore)", scores, op.EventThreshold)); var plot = PreparePlot(scores, $"{profileName} (:OscillationScore)", op.EventThreshold); plots.Add(plot); } else { throw new InvalidOperationException(); } } else { throw new InvalidOperationException(); } //iV add additional info to the acoustic events spectralEvents.ForEach(ae => { ae.FileName = audioRecording.BaseName; ae.Name = parameters.SpeciesName; ae.Profile = profileName; //ae.SegmentDurationSeconds = audioRecording.Duration.TotalSeconds; //ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; //ae.SetTimeAndFreqScales(sonogram.FrameStep, sonogram.FrameDuration, sonogram.FBinWidth); }); } else if (profileConfig is Aed.AedConfiguration ac) { var config = new SonogramConfig { NoiseReductionType = ac.NoiseReductionType, NoiseReductionParameter = ac.NoiseReductionParameter, }; sonogram = new SpectrogramStandard(config, audioRecording.WavReader); // GET THIS TO RETURN BLOB EVENTS. spectralEvents = Aed.CallAed(sonogram, ac, segmentStartOffset, audioRecording.Duration).ToList(); } else { throw new InvalidOperationException(); } // combine the results i.e. add the events list of call events. allResults.NewEvents.AddRange(spectralEvents); allResults.Plots.AddRange(plots); // effectively keeps only the *last* sonogram produced allResults.Sonogram = sonogram; Log.Debug($"{profileName} event count = {spectralEvents.Count}"); // DEBUG PURPOSES COMMENT NEXT LINE //SaveDebugSpectrogram(allResults, genericConfig, outputDirectory, "name"); } return(allResults); }
/// <summary> /// THis method does the work. /// </summary> /// <param name="audioRecording">the recording.</param> /// <param name="configuration">the config file.</param> /// <param name="profileName">name of call/event type to be found.</param> /// <param name="segmentStartOffset">where one segment is located in the total recording.</param> /// <returns>a list of events.</returns> private static RecognizerResults WingBeats(AudioRecording audioRecording, Config configuration, string profileName, TimeSpan segmentStartOffset) { ConfigFile.TryGetProfile(configuration, profileName, out var profile); // get the common properties string speciesName = configuration[AnalysisKeys.SpeciesName] ?? "Pteropus species"; string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "Pteropus"; // The following parameters worked well on a ten minute recording containing 14-16 calls. // Note: if you lower the dB threshold, you need to increase maxDurationSeconds int minHz = profile.GetIntOrNull(AnalysisKeys.MinHz) ?? 100; int maxHz = profile.GetIntOrNull(AnalysisKeys.MaxHz) ?? 3000; double minDurationSeconds = profile.GetDoubleOrNull(AnalysisKeys.MinDuration) ?? 1.0; double maxDurationSeconds = profile.GetDoubleOrNull(AnalysisKeys.MaxDuration) ?? 10.0; double decibelThreshold = profile.GetDoubleOrNull("DecibelThreshold") ?? 6.0; double dctDuration = profile.GetDoubleOrNull("DctDuration") ?? 1.0; double dctThreshold = profile.GetDoubleOrNull("DctThreshold") ?? 0.5; double minOscFreq = profile.GetDoubleOrNull("MinOscilFreq") ?? 4.0; double maxOscFreq = profile.GetDoubleOrNull("MaxOscilFreq") ?? 6.0; double eventThreshold = profile.GetDoubleOrNull("EventThreshold") ?? 0.3; //###################### //2. Don't use samples in this recognizer. //var samples = audioRecording.WavReader.Samples; //Instead, convert each segment to a spectrogram. var sonogram = GetSonogram(configuration, audioRecording); var decibelArray = SNR.CalculateFreqBandAvIntensity(sonogram.Data, minHz, maxHz, sonogram.NyquistFrequency); // Look for wing beats using oscillation detector /* * int scoreSmoothingWindow = 11; // sets a default that was good for Cane toad * Oscillations2019.Execute( * (SpectrogramStandard)sonogram, * minHz, * maxHz, * decibelThreshold, * dctDuration, * (int)Math.Floor(minOscFreq), * (int)Math.Floor(maxOscFreq), * dctThreshold, * eventThreshold, * minDurationSeconds, * maxDurationSeconds, * scoreSmoothingWindow, * out var scores, * out var acousticEvents, * //out var hits, * segmentStartOffset); */ Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, //decibelThreshold, dctDuration, (int)Math.Floor(minOscFreq), (int)Math.Floor(maxOscFreq), dctThreshold, eventThreshold, minDurationSeconds, maxDurationSeconds, out var scores, out var acousticEvents, out var hits, segmentStartOffset); // prepare plots double intensityNormalisationMax = 3 * decibelThreshold; var normThreshold = decibelThreshold / intensityNormalisationMax; var normalisedIntensityArray = DataTools.NormaliseInZeroOne(decibelArray, 0, intensityNormalisationMax); var plot1 = new Plot(speciesName + " Wing-beat band", normalisedIntensityArray, normThreshold); var plot2 = new Plot(speciesName + " Wing-beat Osc Score", scores, eventThreshold); var plots = new List <Plot> { 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; * int maxBin = (int)Math.Round(8000 / sonogram.FBinWidth); * double startSecond = ae.EventStartSeconds - ae.SegmentStartSeconds; * int startFrame = (int)Math.Round(startSecond / sonogram.FrameStep); * int frameLength = (int)Math.Round(ae.EventDurationSeconds / sonogram.FrameStep); * int endFrame = startFrame + frameLength; * * // get only the frames from centre of the acoustic event * var subMatrix = DataTools.Submatrix(spectrogramData, startFrame + 10, 0, endFrame - 10, maxBin); * var spectrum = MatrixTools.GetColumnAverages(subMatrix); * var normalisedSpectrum = DataTools.normalise(spectrum); * normalisedSpectrum = DataTools.filterMovingAverageOdd(normalisedSpectrum, 11); * var maxId = DataTools.GetMaxIndex(normalisedSpectrum); * var hzMax = (int)Math.Ceiling(maxId * sonogram.FBinWidth); * string name = "BeatSpectrum " + (ae.SegmentStartSeconds / 60) + "m" + (int)Math.Floor(startSecond) + "s hzMax" + hzMax; * var bmp2 = GraphsAndCharts.DrawGraph(name, normalisedSpectrum, 100); * * //Set required path * bmp2.Save(Path.Combine(@"C:\PATH", name + ".png")); */ }); return(new RecognizerResults() { Events = acousticEvents, Hits = null, ScoreTrack = null, Plots = plots, Sonogram = sonogram, }); }
/// <summary> /// Do your analysis. This method is called once per segment (typically one-minute segments). /// </summary> public override RecognizerResults Recognize(AudioRecording recording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { // common properties var speciesName = configuration[AnalysisKeys.SpeciesName] ?? "<no species>"; var abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>"; int minHz = configuration.GetInt(AnalysisKeys.MinHz); int maxHz = configuration.GetInt(AnalysisKeys.MaxHz); // BETTER TO CALCULATE THIS. IGNORE USER! // double frameOverlap = Double.Parse(configDict[Keys.FRAME_OVERLAP]); // duration of DCT in seconds double dctDuration = configuration.GetDouble(AnalysisKeys.DctDuration); // minimum acceptable value of a DCT coefficient double dctThreshold = configuration.GetDouble(AnalysisKeys.DctThreshold); // ignore oscillations below this threshold freq int minOscilFreq = configuration.GetInt(AnalysisKeys.MinOscilFreq); // ignore oscillations above this threshold freq int maxOscilFreq = configuration.GetInt(AnalysisKeys.MaxOscilFreq); // min duration of event in seconds double minDuration = configuration.GetDouble(AnalysisKeys.MinDuration); // max duration of event in seconds double maxDuration = configuration.GetDouble(AnalysisKeys.MaxDuration); // min score for an acceptable event double eventThreshold = configuration.GetDouble(AnalysisKeys.EventThreshold); // this default framesize seems to work for Canetoad const int frameSize = 512; double windowOverlap = Oscillations2012.CalculateRequiredFrameOverlap( recording.SampleRate, frameSize, maxOscilFreq); //windowOverlap = 0.75; // previous default // DEBUG: Following line used to search for where indeterminism creeps into the spectrogram values which vary from run to run. //FileTools.AddArrayAdjacentToExistingArrays(Path.Combine(outputDirectory.FullName, recording.BaseName+"_RecordingSamples.csv"), recording.WavReader.GetChannel(0)); // i: MAKE SONOGRAM var sonoConfig = new SonogramConfig { SourceFName = recording.BaseName, WindowSize = frameSize, WindowOverlap = windowOverlap, // the default window is HAMMING //WindowFunction = WindowFunctions.HANNING.ToString(), //WindowFunction = WindowFunctions.NONE.ToString(), // if do not use noise reduction can get a more sensitive recogniser. NoiseReductionType = NoiseReductionType.None, }; // sonoConfig.NoiseReductionType = SNR.Key2NoiseReductionType("STANDARD"); TimeSpan recordingDuration = recording.Duration; //int sr = recording.SampleRate; //double freqBinWidth = sr / (double)sonoConfig.WindowSize; /* ############################################################################################################################################# * window sr frameDuration frames/sec hz/bin 64frameDuration hz/64bins hz/128bins * 1024 22050 46.4ms 21.5 21.5 2944ms 1376hz 2752hz * 1024 17640 58.0ms 17.2 17.2 3715ms 1100hz 2200hz * 2048 17640 116.1ms 8.6 8.6 7430ms 551hz 1100hz */ // int minBin = (int)Math.Round(minHz / freqBinWidth) + 1; // int maxbin = minBin + numberOfBins - 1; BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); //int rowCount = sonogram.Data.GetLength(0); //int colCount = sonogram.Data.GetLength(1); // DEBUG: Following lines used to search for where indeterminism creeps into the spectrogram values which vary from run to run. //double[] array = DataTools.Matrix2Array(sonogram.Data); //FileTools.AddArrayAdjacentToExistingArrays(Path.Combine(outputDirectory.FullName, recording.BaseName+".csv"), array); // ###################################################################### // ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER double minDurationOfAdvertCall = minDuration; // this boundary duration should = 5.0 seconds as of 4 June 2015. //double minDurationOfReleaseCall = 1.0; Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, eventThreshold, minDurationOfAdvertCall, maxDuration, out var scores, out var oscillationEvents, out var hits, segmentStartOffset); // DEBUG: Following line used to search for where indeterminism creeps into the event detection //FileTools.AddArrayAdjacentToExistingArrays(Path.Combine(outputDirectory.FullName, recording.BaseName+"_ScoreArray.csv"), scores); var events = oscillationEvents.ConvertSpectralEventsToAcousticEvents(); var prunedEvents = new List <AcousticEvent>(); foreach (AcousticEvent ae in events) { //if (ae.Duration < minDurationOfReleaseCall) { continue; } if (ae.EventDurationSeconds < minDurationOfAdvertCall) { continue; } if (ae.EventDurationSeconds > maxDuration) { continue; } // add additional info ae.SpeciesName = speciesName; ae.Name = abbreviatedSpeciesName; ae.SegmentDurationSeconds = recordingDuration.TotalSeconds; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; prunedEvents.Add(ae); //if (ae.Duration >= minDurationOfAdvertCall) //{ // ae.Name = abbreviatedSpeciesName; // + ".AdvertCall"; // prunedEvents.Add(ae); // continue; //} } // do a recognizer test. if (false) { if (MainEntry.InDEBUG) { RecognizerTest(scores, new FileInfo(recording.FilePath)); RecognizerTest(prunedEvents, new FileInfo(recording.FilePath)); } } var plot = new Plot(this.DisplayName, scores, eventThreshold); return(new RecognizerResults() { Sonogram = sonogram, Hits = hits, Plots = plot.AsList(), Events = prunedEvents, //Events = events }); }
/// <summary> /// THE KEY ANALYSIS METHOD. /// </summary> /// <param name="configDict"> /// The configuration for the analysis. /// </param> /// <returns> /// The results of the analysis. /// </returns> public static KoalaMaleResults Analysis(AudioRecording recording, IDictionary <string, string> configDict, TimeSpan segmentStartOffset) { int minHz = int.Parse(configDict[AnalysisKeys.MinHz]); int maxHz = int.Parse(configDict[AnalysisKeys.MaxHz]); // BETTER TO CALUCLATE THIS. IGNORE USER! // double frameOverlap = Double.Parse(configDict[Keys.FRAME_OVERLAP]); // duration of DCT in seconds double dctDuration = double.Parse(configDict[AnalysisKeys.DctDuration]); // minimum acceptable value of a DCT coefficient double dctThreshold = double.Parse(configDict[AnalysisKeys.DctThreshold]); // ignore oscillations below this threshold freq int minOscilFreq = int.Parse(configDict[AnalysisKeys.MinOscilFreq]); // ignore oscillations above this threshold freq int maxOscilFreq = int.Parse(configDict[AnalysisKeys.MaxOscilFreq]); // min duration of event in seconds double minDuration = double.Parse(configDict[AnalysisKeys.MinDuration]); // max duration of event in seconds double maxDuration = double.Parse(configDict[AnalysisKeys.MaxDuration]); // min score for an acceptable event double eventThreshold = double.Parse(configDict[AnalysisKeys.EventThreshold]); // seems to work -- frameSize = 512 and 1024 does not catch all oscillations; const int FrameSize = 256; double windowOverlap = Oscillations2012.CalculateRequiredFrameOverlap( recording.SampleRate, FrameSize, maxOscilFreq); // i: MAKE SONOGRAM var sonoConfig = new SonogramConfig { SourceFName = recording.BaseName, WindowSize = FrameSize, WindowOverlap = windowOverlap, NoiseReductionType = NoiseReductionType.None, }; ////sonoConfig.NoiseReductionType = NoiseReductionType.STANDARD; TimeSpan recordingDuration = recording.Duration; double freqBinWidth = recording.SampleRate / (double)sonoConfig.WindowSize; /* ############################################################################################################################################# * window sr frameDuration frames/sec hz/bin 64frameDuration hz/64bins hz/128bins * 1024 22050 46.4ms 21.5 21.5 2944ms 1376hz 2752hz * 1024 17640 58.0ms 17.2 17.2 3715ms 1100hz 2200hz * 2048 17640 116.1ms 8.6 8.6 7430ms 551hz 1100hz */ BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); int rowCount = sonogram.Data.GetLength(0); int colCount = sonogram.Data.GetLength(1); //double[,] subMatrix = MatrixTools.Submatrix(sonogram.Data, 0, minBin, (rowCount - 1), maxbin); // ###################################################################### // ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER // predefinition of score array Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, eventThreshold, minDuration, maxDuration, out var scores, out var oscillationEvents, out var hits, segmentStartOffset); var events = oscillationEvents.ConvertSpectralEventsToAcousticEvents(); // remove isolated koala events - this is to remove false positive identifications events = FilterMaleKoalaEvents(events); if (events == null) { events = new List <AcousticEvent>(); } else { events.ForEach( ae => { ae.SpeciesName = configDict[AnalysisKeys.SpeciesName]; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; ae.SegmentDurationSeconds = recordingDuration.TotalSeconds; }); } // ###################################################################### var plot = new Plot(AnalysisName, scores, eventThreshold); return(new KoalaMaleResults { Events = events, Hits = hits, Plot = plot, RecordingtDuration = recordingDuration, Sonogram = sonogram, }); }
/// <summary> /// Do your analysis. This method is called once per segment (typically one-minute segments). /// </summary> public override RecognizerResults Recognize(AudioRecording recording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { // WARNING: TODO TODO TODO = this method simply duplicates the CANETOAD analyser!!!!!!!!!!!!!!!!!!!!! ################### string speciesName = configuration[AnalysisKeys.SpeciesName] ?? "<no species>"; string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>"; int minHz = configuration.GetInt(AnalysisKeys.MinHz); int maxHz = configuration.GetInt(AnalysisKeys.MaxHz); // BETTER TO CALCULATE THIS. IGNORE USER! // double frameOverlap = Double.Parse(configDict[Keys.FRAME_OVERLAP]); // duration of DCT in seconds double dctDuration = configuration.GetDouble(AnalysisKeys.DctDuration); // minimum acceptable value of a DCT coefficient double dctThreshold = configuration.GetDouble(AnalysisKeys.DctThreshold); // ignore oscillations below this threshold freq int minOscilFreq = configuration.GetInt(AnalysisKeys.MinOscilFreq); // ignore oscillations above this threshold freq int maxOscilFreq = configuration.GetInt(AnalysisKeys.MaxOscilFreq); // min duration of event in seconds double minDuration = configuration.GetDouble(AnalysisKeys.MinDuration); // max duration of event in seconds double maxDuration = configuration.GetDouble(AnalysisKeys.MaxDuration); // min score for an acceptable event double eventThreshold = configuration.GetDouble(AnalysisKeys.EventThreshold); // The default was 512 for Canetoad. // Framesize = 128 seems to work for Littoria fallax. // frame size int frameSize = configuration.GetInt(AnalysisKeys.KeyFrameSize); if (recording.WavReader.SampleRate != 22050) { throw new InvalidOperationException("Requires a 22050Hz file"); } double windowOverlap = Oscillations2012.CalculateRequiredFrameOverlap( recording.SampleRate, frameSize, maxOscilFreq); //windowOverlap = 0.75; // previous default // i: MAKE SONOGRAM var sonoConfig = new SonogramConfig { SourceFName = recording.BaseName, WindowSize = frameSize, WindowOverlap = windowOverlap, NoiseReductionType = NoiseReductionType.None, }; // sonoConfig.NoiseReductionType = SNR.Key2NoiseReductionType("STANDARD"); TimeSpan recordingDuration = recording.Duration; int sr = recording.SampleRate; double freqBinWidth = sr / (double)sonoConfig.WindowSize; /* ############################################################################################################################################# * window sr frameDuration frames/sec hz/bin 64frameDuration hz/64bins hz/128bins * 1024 22050 46.4ms 21.5 21.5 2944ms 1376hz 2752hz * 1024 17640 58.0ms 17.2 17.2 3715ms 1100hz 2200hz * 2048 17640 116.1ms 8.6 8.6 7430ms 551hz 1100hz */ // int minBin = (int)Math.Round(minHz / freqBinWidth) + 1; // int maxbin = minBin + numberOfBins - 1; BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); int rowCount = sonogram.Data.GetLength(0); int colCount = sonogram.Data.GetLength(1); // double[,] subMatrix = MatrixTools.Submatrix(sonogram.Data, 0, minBin, (rowCount - 1), maxbin); // ###################################################################### // ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER //minDuration = 1.0; Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, eventThreshold, minDuration, maxDuration, out var scores, out var acousticEvents, out var hits, segmentStartOffset); acousticEvents.ForEach(ae => { ae.SpeciesName = speciesName; ae.SegmentDurationSeconds = recordingDuration.TotalSeconds; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; ae.Name = abbreviatedSpeciesName; }); var plot = new Plot(this.DisplayName, scores, eventThreshold); return(new RecognizerResults() { Sonogram = sonogram, Hits = hits, Plots = plot.AsList(), Events = acousticEvents, }); }
/// <summary> /// THE KEY ANALYSIS METHOD /// </summary> /// <param name="recording"> /// The segment Of Source File. /// </param> /// <param name="configDict"> /// The config Dict. /// </param> /// <param name="value"></param> /// <returns> /// The <see cref="LitoriaFallaxResults"/>. /// </returns> internal static LitoriaFallaxResults Analysis( AudioRecording recording, Dictionary <string, string> configDict, TimeSpan segmentStartOffset) { // WARNING: TODO TODO TODO = this method simply duplicates the CANETOAD analyser!!!!!!!!!!!!!!!!!!!!! ################### int minHz = int.Parse(configDict[AnalysisKeys.MinHz]); int maxHz = int.Parse(configDict[AnalysisKeys.MaxHz]); // BETTER TO CALCULATE THIS. IGNORE USER! // double frameOverlap = Double.Parse(configDict[Keys.FRAME_OVERLAP]); // duration of DCT in seconds double dctDuration = double.Parse(configDict[AnalysisKeys.DctDuration]); // minimum acceptable value of a DCT coefficient double dctThreshold = double.Parse(configDict[AnalysisKeys.DctThreshold]); // ignore oscillations below this threshold freq int minOscilFreq = int.Parse(configDict[AnalysisKeys.MinOscilFreq]); // ignore oscillations above this threshold freq int maxOscilFreq = int.Parse(configDict[AnalysisKeys.MaxOscilFreq]); // min duration of event in seconds double minDuration = double.Parse(configDict[AnalysisKeys.MinDuration]); // max duration of event in seconds double maxDuration = double.Parse(configDict[AnalysisKeys.MaxDuration]); // min score for an acceptable event double eventThreshold = double.Parse(configDict[AnalysisKeys.EventThreshold]); // The default was 512 for Canetoad. // Framesize = 128 seems to work for Littoria fallax. const int FrameSize = 128; double windowOverlap = Oscillations2012.CalculateRequiredFrameOverlap( recording.SampleRate, FrameSize, maxOscilFreq); //windowOverlap = 0.75; // previous default // i: MAKE SONOGRAM var sonoConfig = new SonogramConfig { SourceFName = recording.BaseName, WindowSize = FrameSize, WindowOverlap = windowOverlap, NoiseReductionType = NoiseReductionType.None, }; // sonoConfig.NoiseReductionType = SNR.Key2NoiseReductionType("STANDARD"); TimeSpan recordingDuration = recording.Duration; int sr = recording.SampleRate; double freqBinWidth = sr / (double)sonoConfig.WindowSize; /* ############################################################################################################################################# * window sr frameDuration frames/sec hz/bin 64frameDuration hz/64bins hz/128bins * 1024 22050 46.4ms 21.5 21.5 2944ms 1376hz 2752hz * 1024 17640 58.0ms 17.2 17.2 3715ms 1100hz 2200hz * 2048 17640 116.1ms 8.6 8.6 7430ms 551hz 1100hz */ // int minBin = (int)Math.Round(minHz / freqBinWidth) + 1; // int maxbin = minBin + numberOfBins - 1; BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); int rowCount = sonogram.Data.GetLength(0); int colCount = sonogram.Data.GetLength(1); // double[,] subMatrix = MatrixTools.Submatrix(sonogram.Data, 0, minBin, (rowCount - 1), maxbin); // ###################################################################### // ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER //minDuration = 1.0; double[] scores; // predefinition of score array List <AcousticEvent> acousticEvents; double[,] hits; Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, eventThreshold, minDuration, maxDuration, out scores, out acousticEvents, out hits, segmentStartOffset); acousticEvents.ForEach(ae => { ae.SpeciesName = configDict[AnalysisKeys.SpeciesName]; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; ae.SegmentDurationSeconds = recordingDuration.TotalSeconds; ae.Name = AbbreviatedName; }); var plot = new Plot(AnalysisName, scores, eventThreshold); // DEBUG ONLY ################################ TEMPORARY ################################ // Draw a standard spectrogram and mark of hites etc. bool createStandardDebugSpectrogram = true; if (createStandardDebugSpectrogram) { string fileName = "LittoriaFallaxDEBUG"; throw new NotSupportedException("YOU NEED TO FIX THIS FOR PRODUCTION"); string path = @"G:\SensorNetworks\Output\Frogs\TestOfHiResIndices-2016July\Test\Towsey.HiResIndices\SpectrogramImages"; var imageDir = new DirectoryInfo(path); if (!imageDir.Exists) { imageDir.Create(); } string filePath2 = Path.Combine(imageDir.FullName, fileName + ".png"); Image sonoBmp = DrawSonogram(sonogram, hits, plot, acousticEvents, eventThreshold); sonoBmp.Save(filePath2); } // END DEBUG ################################ TEMPORARY ################################ return(new LitoriaFallaxResults { Sonogram = sonogram, Hits = hits, Plot = plot, Events = acousticEvents, RecordingDuration = recordingDuration, }); } // Analysis()
/// <summary> /// Do your analysis. This method is called once per segment (typically one-minute segments). /// </summary> public override RecognizerResults Recognize(AudioRecording recording, Config configuration, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { string speciesName = configuration[AnalysisKeys.SpeciesName] ?? "<no species>"; string abbreviatedSpeciesName = configuration[AnalysisKeys.AbbreviatedSpeciesName] ?? "<no.sp>"; int minHz = configuration.GetInt(AnalysisKeys.MinHz); int maxHz = configuration.GetInt(AnalysisKeys.MaxHz); // BETTER TO CALCULATE THIS. IGNORE USER! // double frameOverlap = Double.Parse(configDict[Keys.FRAME_OVERLAP]); // duration of DCT in seconds double dctDuration = configuration.GetDouble(AnalysisKeys.DctDuration); // minimum acceptable value of a DCT coefficient double dctThreshold = configuration.GetDouble(AnalysisKeys.DctThreshold); // ignore oscillations below this threshold freq int minOscilFreq = configuration.GetInt(AnalysisKeys.MinOscilFreq); // ignore oscillations above this threshold freq int maxOscilFreq = configuration.GetInt(AnalysisKeys.MaxOscilFreq); // min duration of event in seconds double minDuration = configuration.GetDouble(AnalysisKeys.MinDuration); // max duration of event in seconds double maxDuration = configuration.GetDouble(AnalysisKeys.MaxDuration); // min score for an acceptable event double eventThreshold = configuration.GetDouble(AnalysisKeys.EventThreshold); // The default was 512 for Canetoad. // Framesize = 128 seems to work for Littoria fallax. // frame size int frameSize = configuration.GetInt(AnalysisKeys.KeyFrameSize); if (recording.WavReader.SampleRate != 22050) { throw new InvalidOperationException("Requires a 22050Hz file"); } double windowOverlap = Oscillations2012.CalculateRequiredFrameOverlap( recording.SampleRate, frameSize, maxOscilFreq); //windowOverlap = 0.75; // previous default // i: MAKE SONOGRAM var sonoConfig = new SonogramConfig { SourceFName = recording.BaseName, WindowSize = frameSize, WindowOverlap = windowOverlap, //NoiseReductionType = NoiseReductionType.NONE, NoiseReductionType = NoiseReductionType.Standard, NoiseReductionParameter = 0.2, }; TimeSpan recordingDuration = recording.Duration; int sr = recording.SampleRate; double freqBinWidth = sr / (double)sonoConfig.WindowSize; BaseSonogram sonogram = new SpectrogramStandard(sonoConfig, recording.WavReader); int rowCount = sonogram.Data.GetLength(0); int colCount = sonogram.Data.GetLength(1); // double[,] subMatrix = MatrixTools.Submatrix(sonogram.Data, 0, minBin, (rowCount - 1), maxbin); // ###################################################################### // ii: DO THE ANALYSIS AND RECOVER SCORES OR WHATEVER // This window is used to smooth the score array before extracting events. // A short window preserves sharper score edges to define events but also keeps noise. int scoreSmoothingWindow = 5; Oscillations2012.Execute( (SpectrogramStandard)sonogram, minHz, maxHz, dctDuration, minOscilFreq, maxOscilFreq, dctThreshold, eventThreshold, minDuration, maxDuration, scoreSmoothingWindow, out var scores, out var acousticEvents, out var hits, segmentStartOffset); acousticEvents.ForEach(ae => { ae.SpeciesName = speciesName; ae.SegmentDurationSeconds = recordingDuration.TotalSeconds; ae.SegmentStartSeconds = segmentStartOffset.TotalSeconds; ae.Name = abbreviatedSpeciesName; }); var plot = new Plot(this.DisplayName, scores, eventThreshold); var plots = new List <Plot> { plot }; // DEBUG IMAGE this recognizer only. MUST set false for deployment. bool displayDebugImage = MainEntry.InDEBUG; if (displayDebugImage) { Image debugImage = DisplayDebugImage(sonogram, acousticEvents, plots, hits); var debugPath = outputDirectory.Combine(FilenameHelpers.AnalysisResultName(Path.GetFileNameWithoutExtension(recording.BaseName), this.Identifier, "png", "DebugSpectrogram")); debugImage.Save(debugPath.FullName); } return(new RecognizerResults() { Sonogram = sonogram, Hits = hits, Plots = plots, Events = acousticEvents, }); }