/// <summary> /// This method returns foward (spectral peak) tracks enclosed in spectral events. /// It averages dB log values incorrectly but it is faster than doing many log conversions. /// </summary> /// <param name="sonogram">The spectrogram to be searched.</param> /// <returns>A list of acoustic events containing foward tracks.</returns> public static (List <EventCommon> Events, double[] CombinedIntensity) GetForwardTracks( SpectrogramStandard sonogram, ForwardTrackParameters parameters, TimeSpan segmentStartOffset) { var sonogramData = sonogram.Data; int frameCount = sonogramData.GetLength(0); int binCount = sonogramData.GetLength(1); int nyquist = sonogram.NyquistFrequency; double binWidth = nyquist / (double)binCount; int minBin = (int)Math.Round(parameters.MinHertz.Value / binWidth); int maxBin = (int)Math.Round(parameters.MaxHertz.Value / binWidth); double minDuration = parameters.MinDuration.Value; double maxDuration = parameters.MaxDuration.Value; double decibelThreshold = parameters.DecibelThreshold.Value; var converter = new UnitConverters( segmentStartOffset: segmentStartOffset.TotalSeconds, sampleRate: sonogram.SampleRate, frameSize: sonogram.Configuration.WindowSize, frameOverlap: sonogram.Configuration.WindowOverlap); //Find all spectral peaks and place in peaks matrix var peaks = new double[frameCount, binCount]; for (int row = 0; row < frameCount; row++) { for (int col = minBin + 1; col < maxBin - 1; col++) { if (sonogramData[row, col] < decibelThreshold) { continue; } // if given matrix element is greater than in freq bin either side bool isPeak = (sonogramData[row, col] > sonogramData[row, col - 1]) && (sonogramData[row, col] > sonogramData[row, col + 1]); if (isPeak) { peaks[row, col] = sonogramData[row, col]; } } } var tracks = GetForwardTracks(peaks, minDuration, maxDuration, decibelThreshold, converter); // initialise tracks as events and get the combined intensity array. // list of accumulated acoustic events var events = new List <SpectralEvent>(); var combinedIntensityArray = new double[frameCount]; // The following lines are used only for debug purposes. //var options = new EventRenderingOptions(new UnitConverters(segmentStartOffset.TotalSeconds, sonogram.Duration.TotalSeconds, nyquist, frameCount, binCount)); //var spectrogram = sonogram.GetImage(doHighlightSubband: false, add1KHzLines: true, doMelScale: false); // Initialise events with tracks. foreach (var track in tracks) { //Following line used only for debug purposes. Can save as image. //spectrogram.Mutate(x => track.Draw(x, options)); var maxScore = decibelThreshold * 5; var scoreRange = new Interval <double>(0, maxScore); var ae = new ChirpEvent(track, scoreRange) { SegmentStartSeconds = segmentStartOffset.TotalSeconds, SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, Name = "noName", }; events.Add(ae); // fill the intensity array var startRow = converter.FrameFromStartTime(track.StartTimeSeconds); var amplitudeTrack = track.GetAmplitudeOverTimeFrames(); for (int i = 0; i < amplitudeTrack.Length; i++) { combinedIntensityArray[startRow + i] = Math.Max(combinedIntensityArray[startRow + i], amplitudeTrack[i]); } } List <EventCommon> returnEvents = events.Cast <EventCommon>().ToList(); // Combine coincident events that are stacked one above other. // This will help in some cases to combine related events. if (parameters.CombinePossibleHarmonics) { returnEvents = CompositeEvent.CombinePotentialStackedTracks(events, parameters.HarmonicsStartDifference, parameters.HarmonicsHertzGap); } // Combine events that are temporally close and in the same frequency band. // This will help in some cases to combine related events. if (parameters.CombinePossibleSyllableSequence) { var timeDiff = TimeSpan.FromSeconds(parameters.SyllableStartDifference); returnEvents = CompositeEvent.CombineSimilarProximalEvents(events, timeDiff, parameters.SyllableHertzGap); } return(returnEvents, combinedIntensityArray); }
public void TestForwardTrackAlgorithm() { // Set up the recognizer parameters. var parameters = new ForwardTrackParameters() { MinHertz = 500, MaxHertz = 6000, MinDuration = 0.2, MaxDuration = 1.1, DecibelThreshold = 2.0, CombinePossibleHarmonics = false, HarmonicsStartDifference = TimeSpan.FromSeconds(0.2), HarmonicsHertzGap = 200, CombinePossibleSyllableSequence = false, SyllableStartDifference = 0.2, SyllableHertzGap = 300, }; //Set up the virtual recording. int samplerate = 22050; double signalDuration = 13.0; //seconds // set up the config for a virtual spectrogram. var sonoConfig = new SonogramConfig() { WindowSize = 512, WindowStep = 512, WindowOverlap = 0.0, // this must be set WindowFunction = WindowFunctions.HANNING.ToString(), NoiseReductionType = NoiseReductionType.Standard, NoiseReductionParameter = 0.0, Duration = TimeSpan.FromSeconds(signalDuration), SampleRate = samplerate, }; var spectrogram = this.CreateArtificialSpectrogramToTestTracksAndHarmonics(sonoConfig); //var image1 = SpectrogramTools.GetSonogramPlusCharts(spectrogram, null, null, null); //results.Sonogram.GetImage().Save(this.outputDirectory + "\\debug.png"); var segmentStartOffset = TimeSpan.Zero; var plots = new List <Plot>(); var(spectralEvents, dBArray) = ForwardTrackAlgorithm.GetForwardTracks( spectrogram, parameters, segmentStartOffset); // draw a plot of max decibels in each frame double decibelNormalizationMax = 5 * parameters.DecibelThreshold.Value; var dBThreshold = parameters.DecibelThreshold.Value / decibelNormalizationMax; var normalisedDecibelArray = DataTools.NormaliseInZeroOne(dBArray, 0, decibelNormalizationMax); var plot1 = new Plot("decibel max", normalisedDecibelArray, dBThreshold); plots.Add(plot1); var allResults = new RecognizerResults() { NewEvents = new List <EventCommon>(), Hits = null, ScoreTrack = null, Plots = new List <Plot>(), Sonogram = null, }; // 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 = spectrogram; // DEBUG PURPOSES COMMENT NEXT LINE this.SaveTestOutput( outputDirectory => GenericRecognizer.SaveDebugSpectrogram(allResults, null, outputDirectory, "ForwardTrack")); Assert.AreEqual(23, allResults.NewEvents.Count); var @event = (SpectralEvent)allResults.NewEvents[4]; Assert.AreEqual(2.0, @event.EventStartSeconds, 0.1); Assert.AreEqual(2.5, @event.EventEndSeconds, 0.1); Assert.AreEqual(1720, @event.LowFrequencyHertz); Assert.AreEqual(2107, @event.HighFrequencyHertz); @event = (SpectralEvent)allResults.NewEvents[11]; Assert.AreEqual(6.0, @event.EventStartSeconds, 0.1); Assert.AreEqual(6.5, @event.EventEndSeconds, 0.1); Assert.AreEqual(2150, @event.LowFrequencyHertz); Assert.AreEqual(2580, @event.HighFrequencyHertz); }