/// <summary> /// The Boobook call syllable is shaped like an inverted "U". Its total duration is close to 0.15 seconds. /// The rising portion lasts for 0.06s, followed by a turning portion, 0.03s, followed by the decending portion of 0.06s. /// The constants for this method were obtained from the calls in a Gympie recording obtained by Yvonne Phillips. /// </summary> /// <param name="ev">An event containing at least one forward track i.e. a chirp.</param> public static void SetFrequencyProfileScore(ChirpEvent ev) { const double risingDuration = 0.06; const double gapDuration = 0.03; const double fallingDuration = 0.06; var track = ev.Tracks.First(); var profile = track.GetTrackFrequencyProfile().ToArray(); // get the first point var firstPoint = track.Points.First(); var frameDuration = firstPoint.Seconds.Maximum - firstPoint.Seconds.Minimum; var risingFrameCount = (int)Math.Floor(risingDuration / frameDuration); var gapFrameCount = (int)Math.Floor(gapDuration / frameDuration); var fallingFrameCount = (int)Math.Floor(fallingDuration / frameDuration); var startSum = 0.0; if (profile.Length >= risingFrameCount) { for (var i = 0; i <= risingFrameCount; i++) { startSum += profile[i]; } } int startFrame = risingFrameCount + gapFrameCount; int endFrame = startFrame + fallingFrameCount; var endSum = 0.0; if (profile.Length >= endFrame) { for (var i = startFrame; i <= endFrame; i++) { endSum += profile[i]; } } // set score to 1.0 if the profile has inverted U shape. double score = 0.0; if (startSum > 0.0 && endSum < 0.0) { score = 1.0; } ev.FrequencyProfileScore = score; }
/// <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); }