/// <summary> /// A one-frame track sounds like a click. /// A click is a sharp onset broadband sound of brief duration. Geometrically it is similar to a vertical whistle. /// THis method averages dB log values incorrectly but it is faster than doing many log conversions. /// This method is used to find acoustic events and is accurate enough for the purpose. /// </summary> public static (List <EventCommon> Events, double[] Intensity) GetOneFrameTracks( SpectrogramStandard sonogram, OneframeTrackParameters parameters, TimeSpan segmentStartOffset) { var sonogramData = sonogram.Data; int frameCount = sonogramData.GetLength(0); int binCount = sonogramData.GetLength(1); var frameStep = sonogram.FrameStep; 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); var decibelThreshold = parameters.DecibelThreshold.Value; var minBandwidthHertz = parameters.MinBandwidthHertz.Value; var maxBandwidthHertz = parameters.MaxBandwidthHertz.Value; var converter = new UnitConverters( segmentStartOffset: segmentStartOffset.TotalSeconds, sampleRate: sonogram.SampleRate, frameSize: sonogram.Configuration.WindowSize, frameOverlap: sonogram.Configuration.WindowOverlap); // Find all frame peaks and place in peaks matrix // avoid row edge effects. var peaks = new double[frameCount, binCount]; // for all time frames except 1st and last allowing for edge effects. for (int t = 1; t < frameCount - 1; t++) { // buffer zone around click is one frame wide. // for all frequency bins except top and bottom in this time frame for (int bin = minBin; bin < maxBin; bin++) { if (sonogramData[t, bin] < decibelThreshold) { continue; } // THis is where the profile of a click is defined // A click requires sudden onset, with maximum amplitude followed by decay. bool isClickPeak = sonogramData[t - 1, bin] < decibelThreshold && sonogramData[t, bin] > sonogramData[t + 1, bin]; if (isClickPeak) { peaks[t, bin] = sonogramData[t, bin]; } } } //NOTE: the Peaks matrix is same size as the sonogram. var tracks = GetOneFrameTracks(peaks, minBin, maxBin, minBandwidthHertz, maxBandwidthHertz, decibelThreshold, converter); // initialise tracks as events and get the combined intensity array. var events = new List <EventCommon>(); var temporalIntensityArray = new double[frameCount]; var maxScore = decibelThreshold * 5; foreach (var track in tracks) { var ae = new ClickEvent(track, maxScore) { 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++) { temporalIntensityArray[startRow + i] += amplitudeTrack[i]; } } // MAY NOT WANT TO Do THIS FOR ONE-FRAME tracks // combine proximal events that occupy similar frequency band //if (combineProximalSimilarEvents) //{ // TimeSpan startDifference = TimeSpan.FromSeconds(0.5); // int hertzDifference = 500; // //######################################################################## TODO TODO TODOD // //events = AcousticEvent.CombineSimilarProximalEvents(events, startDifference, hertzDifference); //} return(events, temporalIntensityArray); }
public void TestOneframeTrackAlgorithm() { // Set up the recognizer parameters. var parameters = new OneframeTrackParameters() { MinHertz = 6000, MaxHertz = 11000, MinBandwidthHertz = 100, MaxBandwidthHertz = 5000, DecibelThreshold = 2.0, }; //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) = OneframeTrackAlgorithm.GetOneFrameTracks( 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, "ClickTrack")); Assert.AreEqual(6, allResults.NewEvents.Count); var @event = (SpectralEvent)allResults.NewEvents[0]; Assert.AreEqual(10.0, @event.EventStartSeconds, 0.1); Assert.AreEqual(10.1, @event.EventEndSeconds, 0.1); Assert.AreEqual(6450, @event.LowFrequencyHertz); Assert.AreEqual(10750, @event.HighFrequencyHertz); @event = (SpectralEvent)allResults.NewEvents[2]; Assert.AreEqual(11.05, @event.EventStartSeconds, 0.05); Assert.AreEqual(11.07, @event.EventEndSeconds, 0.05); Assert.AreEqual(6450, @event.LowFrequencyHertz); Assert.AreEqual(7310, @event.HighFrequencyHertz); }