/// <summary> /// This method is called once per segment (typically one-minute segments). /// </summary> /// <param name="audioRecording">one minute of audio recording.</param> /// <param name="config">config file that contains parameters used by all profiles.</param> /// <param name="segmentStartOffset">when recording starts.</param> /// <param name="getSpectralIndexes">not sure what this is.</param> /// <param name="outputDirectory">where the recognizer results can be found.</param> /// <param name="imageWidth"> assuming ????.</param> /// <returns>recognizer results.</returns> public override RecognizerResults Recognize( AudioRecording audioRecording, Config config, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { //class NinoxBoobookConfig is defined at bottom of this file. var genericConfig = (NinoxBoobookConfig)config; var recognizer = new GenericRecognizer(); RecognizerResults combinedResults = recognizer.Recognize( audioRecording, genericConfig, segmentStartOffset, getSpectralIndexes, outputDirectory, imageWidth); // DO POST-PROCESSING of EVENTS // Filter out the chirp events for possible combining. var(chirpEvents, others) = combinedResults.NewEvents.FilterForEventType <ChirpEvent, EventCommon>(); // Uncomment the next line when want to obtain the event frequency profiles. // WriteFrequencyProfiles(chirpEvents); // Calculate frequency profile score for each event foreach (var ev in chirpEvents) { SetFrequencyProfileScore((ChirpEvent)ev); } // Combine overlapping events. If the dB threshold is set low, may get lots of little events. var newEvents = CompositeEvent.CombineOverlappingEvents(chirpEvents.Cast <EventCommon>().ToList()); if (genericConfig.CombinePossibleSyllableSequence) { // convert events to spectral events for possible combining. var(spectralEvents, _) = combinedResults.NewEvents.FilterForEventType <SpectralEvent, EventCommon>(); var startDiff = genericConfig.SyllableStartDifference; var hertzDiff = genericConfig.SyllableHertzGap; newEvents = CompositeEvent.CombineSimilarProximalEvents(spectralEvents, TimeSpan.FromSeconds(startDiff), (int)hertzDiff); } combinedResults.NewEvents = newEvents; //UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots. // NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in <Towsey.PteropusSpecies.yml> config file. //GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName); return(combinedResults); }
/// <summary> /// EXPANATION: A vertical track is a near click or rapidly frequency-modulated tone. A good example is the whip component of the whip-bird call. /// They would typically be only a few time-frames duration. /// THis method averages dB log values incorrectly but it is faster than doing many log conversions and is accurate enough for the purpose. /// </summary> /// <param name="sonogram">The spectrogram to be searched.</param> /// <param name="parameters">parameters for the upwards track algorithm.</param> /// <param name="segmentStartOffset">The start time of the current recording segment under analysis.</param> /// <returns>A list of acoustic events containing foward tracks.</returns> public static (List <EventCommon> Events, double[] CombinedIntensity) GetUpwardTracks( SpectrogramStandard sonogram, AnalysisPrograms.Recognizers.Base.UpwardTrackParameters 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 minBandwidthHertz = parameters.MinBandwidthHertz.Value; var maxBandwidthHertz = parameters.MaxBandwidthHertz.Value; var decibelThreshold = parameters.DecibelThreshold.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 (int row = 1; row < frameCount - 1; row++) { for (int col = minBin; col < maxBin; col++) { if (sonogramData[row, col] < decibelThreshold) { continue; } // if given matrix element is greater than in frame either side bool isPeak = (sonogramData[row, col] > sonogramData[row - 1, col]) && (sonogramData[row, col] > sonogramData[row + 1, col]); if (isPeak) { peaks[row, col] = sonogramData[row, col]; } } } //NOTE: the Peaks matrix is same size as the sonogram. var tracks = GetUpwardTracks(peaks, minBin, maxBin, minBandwidthHertz, maxBandwidthHertz, decibelThreshold, converter); // initialise tracks as events and get the combined intensity array. var events = new List <SpectralEvent>(); var temporalIntensityArray = new double[frameCount]; var scoreRange = new Interval <double>(0.0, decibelThreshold * 5); foreach (var track in tracks) { var ae = new WhipEvent(track, scoreRange) { SegmentStartSeconds = segmentStartOffset.TotalSeconds, SegmentDurationSeconds = frameCount * converter.SecondsPerFrameStep, Name = "Whip", }; events.Add(ae); // fill the intensity array //var startRow = (int)converter.TemporalScale.To(track.StartTimeSeconds); var startRow = converter.FrameFromStartTime(track.StartTimeSeconds); var amplitudeTrack = track.GetAmplitudeOverTimeFrames(); for (int i = 0; i < amplitudeTrack.Length; i++) { temporalIntensityArray[startRow + i] += amplitudeTrack[i]; } } List <EventCommon> returnEvents = events.Cast <EventCommon>().ToList(); // combine proximal events that occupy similar frequency band if (parameters.CombineProximalSimilarEvents) { returnEvents = CompositeEvent.CombineSimilarProximalEvents(events, parameters.SyllableStartDifference, parameters.SyllableHertzDifference); } return(returnEvents, temporalIntensityArray); }
/// <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); }
/// <summary> /// This method is called once per segment (typically one-minute segments). /// </summary> /// <param name="audioRecording">one minute of audio recording.</param> /// <param name="config">config file that contains parameters used by all profiles.</param> /// <param name="segmentStartOffset">when recording starts.</param> /// <param name="getSpectralIndexes">not sure what this is.</param> /// <param name="outputDirectory">where the recognizer results can be found.</param> /// <param name="imageWidth"> assuming ????.</param> /// <returns>recognizer results.</returns> public override RecognizerResults Recognize( AudioRecording audioRecording, Config config, TimeSpan segmentStartOffset, Lazy <IndexCalculateResult[]> getSpectralIndexes, DirectoryInfo outputDirectory, int?imageWidth) { //class BotaurusPoiciloptilusConfig is define at bottom of this file. var genericConfig = (BotaurusPoiciloptilusConfig)config; var recognizer = new GenericRecognizer(); RecognizerResults combinedResults = recognizer.Recognize( audioRecording, genericConfig, segmentStartOffset, getSpectralIndexes, outputDirectory, imageWidth); // DO POST-PROCESSING of EVENTS var events = combinedResults.NewEvents; // Following two commented lines are different ways of casting lists. //var newEvents = spectralEvents.Cast<EventCommon>().ToList(); //var spectralEvents = events.Select(x => (SpectralEvent)x).ToList(); List <EventCommon> newEvents; // NOTE: If the dB threshold is set low, may get lots of little events. if (genericConfig.CombinePossibleSyllableSequence) { // Convert events to spectral events for combining of possible sequences. var spectralEvents = events.Cast <SpectralEvent>().ToList(); var startDiff = genericConfig.SyllableStartDifference; var hertzDiff = genericConfig.SyllableHertzGap; newEvents = CompositeEvent.CombineSimilarProximalEvents(spectralEvents, TimeSpan.FromSeconds(startDiff), (int)hertzDiff); } else { newEvents = events; } //filter the events for duration in seconds var minimumEventDuration = 0.5; if (genericConfig.CombinePossibleSyllableSequence) { minimumEventDuration = 2.0; } var filteredEvents = new List <EventCommon>(); foreach (var ev in newEvents) { var eventDuration = ((SpectralEvent)ev).EventDurationSeconds; if (eventDuration > minimumEventDuration && eventDuration < 11.0) { filteredEvents.Add(ev); } } combinedResults.NewEvents = filteredEvents; //UNCOMMENT following line if you want special debug spectrogram, i.e. with special plots. // NOTE: Standard spectrograms are produced by setting SaveSonogramImages: "True" or "WhenEventsDetected" in UserName.SpeciesName.yml config file. //GenericRecognizer.SaveDebugSpectrogram(territorialResults, genericConfig, outputDirectory, audioRecording.BaseName); return(combinedResults); }