/// <summary> /// 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 <AcousticEvent> Events, double[] Intensity) GetClicks( SpectrogramStandard sonogram, int minHz, int maxHz, int nyquist, double decibelThreshold, int minBandwidthHertz, int maxBandwidthHertz, bool combineProximalSimilarEvents, 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); int bandwidthBinCount = maxBin - minBin + 1; // list of accumulated acoustic events var events = new List <AcousticEvent>(); var temporalIntensityArray = new double[frameCount]; // for all time frames except 1st and last allowing for edge effects. for (int t = 1; t < frameCount - 1; t++) { // set up an intensity array for all frequency bins in this frame. double[] clickIntensity = new double[bandwidthBinCount]; // 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++) { // THis is where the profile of a click is defined // A click requires sudden onset, with maximum amplitude followed by decay. if (sonogramData[t - 1, bin] > decibelThreshold || sonogramData[t, bin] < sonogramData[t + 1, bin]) { continue; } // THis is where the profile of a vertical ridge is defined //if (sonogramData[t, bin] < sonogramData[t - 1, bin] || sonogramData[t, bin] < sonogramData[t + 1, bin]) //{ // continue; //} clickIntensity[bin - minBin] = sonogramData[t, bin]; //clickIntensity[bin - minBin] = sonogramData[t, bin] - sonogramData[t - 1, bin]; clickIntensity[bin - minBin] = Math.Max(0.0, clickIntensity[bin - minBin]); } if (clickIntensity.Max() < decibelThreshold) { continue; } // Extract the events based on bandwidth and threshhold. var acousticEvents = ConvertSpectralArrayToClickEvents( clickIntensity, minHz, sonogram.FramesPerSecond, sonogram.FBinWidth, decibelThreshold, minBandwidthHertz, maxBandwidthHertz, t, segmentStartOffset); // add each event score to combined temporal intensity array foreach (var ae in acousticEvents) { var avClickIntensity = ae.Score; temporalIntensityArray[t] += avClickIntensity; } // add new events to list of events events.AddRange(acousticEvents); } // combine proximal events that occupy similar frequency band var startDifference = TimeSpan.FromSeconds(1.0); var hertzDifference = 100; if (combineProximalSimilarEvents) { events = AcousticEvent.CombineSimilarProximalEvents(events, startDifference, hertzDifference); } return(events, temporalIntensityArray); }
/// <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> public static (List <AcousticEvent> Events, double[] CombinedIntensity) GetVerticalTracks( SpectrogramStandard sonogram, int minHz, int maxHz, int nyquist, double decibelThreshold, int minBandwidthHertz, int maxBandwidthHertz, bool combineProximalSimilarEvents, TimeSpan segmentStartOffset) { var sonogramData = sonogram.Data; int frameCount = sonogramData.GetLength(0); int binCount = sonogramData.GetLength(1); var frameDuration = sonogram.FrameDuration; var frameStep = sonogram.FrameStep; var frameOverStep = frameDuration - frameStep; 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 temporalIntensityArray = new double[frameCount]; //Find all frame peaks and place in peaks matrix 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]; } } } // Look for track starts and initialise them as events. // Cannot include edge rows & columns because of edge effects. // Each row is a time frame which is a spectrum. Each column is a frequency bin var combinedIntensityArray = new double[frameCount]; for (int col = minBin; col < maxBin; col++) { for (int row = 2; row < frameCount - 2; row++) { // Visit each frame peak in order. Each may be start of possible track if (peaks[row, col] < decibelThreshold) { continue; } //have the beginning of a potential track var track = GetVerticalTrack(peaks, row, col, maxBin, decibelThreshold); // calculate first and last of the frame IDs in the original spectrogram int trackStartFrame = track.GetStartFrame(); int trackEndFrame = track.GetEndFrame(); // next two for debug purposes //int trackMinBin = track.GetBottomFreqBin(); //int trackTopBin = track.GetTopFreqBin(); //If track has lies within the correct bandWidth range, then create an event int trackBandWidth = track.GetTrackBandWidthHertz(binWidth); if (trackBandWidth >= minBandwidthHertz && trackBandWidth <= maxBandwidthHertz) { // get the oblong and init an event double trackDuration = ((trackEndFrame - trackStartFrame) * frameStep) + frameOverStep; var oblong = new Oblong(trackStartFrame, col, trackEndFrame, track.GetTopFreqBin()); var ae = new AcousticEvent(segmentStartOffset, oblong, nyquist, binCount, frameDuration, frameStep, frameCount) { // get the track as matrix TheTrack = track, }; events.Add(ae); // fill the intensity array var amplitudeArray = track.GetAmplitudeOverTimeFrames(); for (int i = 0; i < amplitudeArray.Length; i++) { combinedIntensityArray[row + i] += amplitudeArray[i]; } } } // rows/frames } // end cols/bins // combine proximal events that occupy similar frequency band if (combineProximalSimilarEvents) { TimeSpan startDifference = TimeSpan.FromSeconds(0.5); int hertzDifference = 500; events = AcousticEvent.CombineSimilarProximalEvents(events, startDifference, hertzDifference); } return(events, temporalIntensityArray); }