Ejemplo n.º 1
0
        public void TestEventMerging()
        {
            // make a list of events
            var events = new List <AcousticEvent>();

            double maxPossibleScore = 10.0;
            var    event1           = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 1.0, eventDuration: 5.0, minFreq: 1000, maxFreq: 8000)
            {
                Name            = "Event1",
                Score           = 1.0,
                ScoreNormalised = 1.0 / maxPossibleScore,
            };

            events.Add(event1);

            var event2 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 4.5, eventDuration: 2.0, minFreq: 1500, maxFreq: 6000)
            {
                Name            = "Event2",
                Score           = 5.0,
                ScoreNormalised = 5.0 / maxPossibleScore,
            };

            events.Add(event2);

            var event3 = new AcousticEvent(segmentStartOffset: TimeSpan.Zero, eventStartSegmentRelative: 7.0, eventDuration: 2.0, minFreq: 1000, maxFreq: 8000)
            {
                Name            = "Event3",
                Score           = 9.0,
                ScoreNormalised = 9.0 / maxPossibleScore,
            };

            events.Add(event3);

            // combine adjacent acoustic events
            events = AcousticEvent.CombineOverlappingEvents(events: events, segmentStartOffset: TimeSpan.Zero);

            Assert.AreEqual(2, events.Count);
            Assert.AreEqual(1.0, events[0].EventStartSeconds, 1E-4);
            Assert.AreEqual(6.5, events[0].EventEndSeconds, 1E-4);
            Assert.AreEqual(7.0, events[1].EventStartSeconds, 1E-4);
            Assert.AreEqual(9.0, events[1].EventEndSeconds, 1E-4);

            Assert.AreEqual(1000, events[0].LowFrequencyHertz);
            Assert.AreEqual(8000, events[0].HighFrequencyHertz);
            Assert.AreEqual(1000, events[1].LowFrequencyHertz);
            Assert.AreEqual(8000, events[1].HighFrequencyHertz);

            Assert.AreEqual(5.0, events[0].Score, 1E-4);
            Assert.AreEqual(9.0, events[1].Score, 1E-4);
            Assert.AreEqual(0.5, events[0].ScoreNormalised, 1E-4);
            Assert.AreEqual(0.9, events[1].ScoreNormalised, 1E-4);
        }
        /// <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.NotNull() && configuration.Profiles.Count == 0)
            {
                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>(),
                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;
                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 WhistleParameters ||
                        profileConfig is HarmonicParameters ||
                        profileConfig is SpectralPeakTrackParameters ||
                        profileConfig is VerticalTrackParameters ||
                        profileConfig is ClickParameters)
                    {
                        sonogram = new SpectrogramStandard(ParametersToSonogramConfig(parameters), audioRecording.WavReader);

                        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 acousticEvents,
                                out var hits,
                                segmentStartOffset);

                            //plots.Add(new Plot($"{profileName} (:OscillationScore)", scores, op.EventThreshold));
                            var plot = PreparePlot(scores, $"{profileName} (:OscillationScore)", op.EventThreshold);
                            plots.Add(plot);
                        }
                        else 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.
                            acousticEvents = 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);
                        }
                        else if (profileConfig is WhistleParameters wp)
                        {
                            //get the array of intensity values minus intensity in side/buffer bands.
                            double[] decibelArray;
                            (acousticEvents, decibelArray) = WhistleParameters.GetWhistles(
                                sonogram,
                                wp.MinHertz.Value,
                                wp.MaxHertz.Value,
                                sonogram.NyquistFrequency,
                                wp.DecibelThreshold.Value,
                                wp.MinDuration.Value,
                                wp.MaxDuration.Value,
                                segmentStartOffset);

                            var plot = PreparePlot(decibelArray, $"{profileName} (Whistle:dB Intensity)", wp.DecibelThreshold.Value);
                            plots.Add(plot);
                        }
                        else if (profileConfig is HarmonicParameters hp)
                        {
                            double[] decibelMaxArray;
                            double[] harmonicIntensityScores;
                            (acousticEvents, 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 SpectralPeakTrackParameters tp)
                        {
                            double[] decibelArray;
                            (acousticEvents, decibelArray) = SpectralPeakTrackParameters.GetSpectralPeakTracks(
                                sonogram,
                                tp.MinHertz.Value,
                                tp.MaxHertz.Value,
                                sonogram.NyquistFrequency,
                                tp.DecibelThreshold.Value,
                                tp.MinDuration.Value,
                                tp.MaxDuration.Value,
                                tp.CombinePossibleHarmonics,
                                segmentStartOffset);

                            var plot = PreparePlot(decibelArray, $"{profileName} (SpectralPeaks:dB Intensity)", tp.DecibelThreshold.Value);
                            plots.Add(plot);
                        }
                        else if (profileConfig is ClickParameters cp)
                        {
                            double[] decibelArray;
                            (acousticEvents, decibelArray) = ClickParameters.GetClicks(
                                sonogram,
                                cp.MinHertz.Value,
                                cp.MaxHertz.Value,
                                sonogram.NyquistFrequency,
                                cp.DecibelThreshold.Value,
                                cp.MinBandwidthHertz.Value,
                                cp.MaxBandwidthHertz.Value,
                                cp.CombineProximalSimilarEvents,
                                segmentStartOffset);

                            var plot = PreparePlot(decibelArray, $"{profileName} (Click:dB Intensity)", cp.DecibelThreshold.Value);
                            plots.Add(plot);
                        }
                        else if (profileConfig is VerticalTrackParameters vtp)
                        {
                            double[] decibelArray;
                            (acousticEvents, decibelArray) = VerticalTrackParameters.GetVerticalTracks(
                                sonogram,
                                vtp.MinHertz.Value,
                                vtp.MaxHertz.Value,
                                sonogram.NyquistFrequency,
                                vtp.DecibelThreshold.Value,
                                vtp.MinBandwidthHertz.Value,
                                vtp.MaxBandwidthHertz.Value,
                                vtp.CombineProximalSimilarEvents,
                                segmentStartOffset);

                            var plot = PreparePlot(decibelArray, $"{profileName} (VerticalTrack:dB Intensity)", vtp.DecibelThreshold.Value);
                            plots.Add(plot);
                        }
                        else
                        {
                            throw new InvalidOperationException();
                        }
                    }
                    else
                    {
                        throw new InvalidOperationException();
                    }

                    //iV add additional info to the acoustic events
                    acousticEvents.ForEach(ae =>
                    {
                        ae.FileName               = audioRecording.BaseName;
                        ae.SpeciesName            = parameters.SpeciesName;
                        ae.Name                   = parameters.ComponentName;
                        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);

                    acousticEvents = 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.Events.AddRange(acousticEvents);
                allResults.Plots.AddRange(plots);

                // effectively keeps only the *last* sonogram produced
                allResults.Sonogram = sonogram;
                Log.Debug($"{profileName} event count = {acousticEvents.Count}");

                // DEBUG PURPOSES COMMENT NEXT LINE
                //SaveDebugSpectrogram(allResults, genericConfig, outputDirectory, "name");
            }

            // combine adjacent acoustic events
            if (this.combineOverlappedEvents)
            {
                allResults.Events = AcousticEvent.CombineOverlappingEvents(allResults.Events, segmentStartOffset);
            }

            return(allResults);
        }
        /// <summary>
        /// Calculates the mean intensity in a freq band defined by its min and max freq.
        /// 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 <AcousticEvent>, double[]) GetWhistles(
            SpectrogramStandard sonogram,
            int minHz,
            int maxHz,
            int nyquist,
            double decibelThreshold,
            double minDuration,
            double maxDuration,
            TimeSpan segmentStartOffset)
        {
            var sonogramData = sonogram.Data;
            int frameCount   = sonogramData.GetLength(0);
            int binCount     = sonogramData.GetLength(1);

            double binWidth = nyquist / (double)binCount;
            int    minBin   = (int)Math.Round(minHz / binWidth);
            int    maxBin   = (int)Math.Round(maxHz / binWidth);

            // list of accumulated acoustic events
            var events = new List <AcousticEvent>();
            var combinedIntensityArray = new double[frameCount];

            // for all frequency bins except top and bottom
            for (int bin = minBin + 1; bin < maxBin; bin++)
            {
                // set up an intensity array for the frequency bin.
                double[] intensity = new double[frameCount];

                // buffer zone around whistle is four bins wide.
                if (minBin < 4)
                {
                    // for all time frames in this frequency bin
                    for (int t = 0; t < frameCount; t++)
                    {
                        var bandIntensity        = (sonogramData[t, bin - 1] + sonogramData[t, bin] + sonogramData[t, bin + 1]) / 3.0;
                        var topSideBandIntensity = (sonogramData[t, bin + 3] + sonogramData[t, bin + 4] + sonogramData[t, bin + 5]) / 3.0;
                        intensity[t] = bandIntensity - topSideBandIntensity;
                        intensity[t] = Math.Max(0.0, intensity[t]);
                    }
                }
                else
                {
                    // for all time frames in this frequency bin
                    for (int t = 0; t < frameCount; t++)
                    {
                        var bandIntensity           = (sonogramData[t, bin - 1] + sonogramData[t, bin] + sonogramData[t, bin + 1]) / 3.0;
                        var topSideBandIntensity    = (sonogramData[t, bin + 3] + sonogramData[t, bin + 4] + sonogramData[t, bin + 5]) / 6.0;
                        var bottomSideBandIntensity = (sonogramData[t, bin - 3] + sonogramData[t, bin - 4] + sonogramData[t, bin - 5]) / 6.0;
                        intensity[t] = bandIntensity - topSideBandIntensity - bottomSideBandIntensity;
                        intensity[t] = Math.Max(0.0, intensity[t]);
                    }
                }

                // smooth the decibel array to allow for brief gaps.
                intensity = DataTools.filterMovingAverageOdd(intensity, 7);

                //calculate the Hertz bounds of the acoustic events for these freq bins
                int bottomHzBound = (int)Math.Floor(sonogram.FBinWidth * (bin - 1));
                int topHzBound    = (int)Math.Ceiling(sonogram.FBinWidth * (bin + 2));

                //extract the events based on length and threshhold.
                // Note: This method does NOT do prior smoothing of the dB array.
                var acousticEvents = AcousticEvent.ConvertScoreArray2Events(
                    intensity,
                    bottomHzBound,
                    topHzBound,
                    sonogram.FramesPerSecond,
                    sonogram.FBinWidth,
                    decibelThreshold,
                    minDuration,
                    maxDuration,
                    segmentStartOffset);

                // add to conbined intensity array
                for (int t = 0; t < frameCount; t++)
                {
                    //combinedIntensityArray[t] += intensity[t];
                    combinedIntensityArray[t] = Math.Max(intensity[t], combinedIntensityArray[t]);
                }

                // combine events
                events.AddRange(acousticEvents);
            } //end for all freq bins

            // combine adjacent acoustic events
            events = AcousticEvent.CombineOverlappingEvents(events, segmentStartOffset);

            return(events, combinedIntensityArray);
        }