Skip to content

Commit

Permalink
Create four Algorithm classes for track events
Browse files Browse the repository at this point in the history
Issue #297
  • Loading branch information
towsey committed Apr 27, 2020
1 parent e464f63 commit 331b713
Show file tree
Hide file tree
Showing 11 changed files with 944 additions and 1,071 deletions.
12 changes: 6 additions & 6 deletions src/AudioAnalysisTools/Events/Types/CompositeEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ public static List<SpectralEvent> CombineOverlappingEvents(List<SpectralEvent> e
/// <summary>
/// Combines events that have similar bottom and top frequency bounds and whose start times are within the passed time range.
/// </summary>
public static List<SpectralEvent> CombineSimilarProximalEvents(List<SpectralEvent> events, TimeSpan startDifference, int hertzDifference)
public static List<EventCommon> CombineSimilarProximalEvents(List<SpectralEvent> events, TimeSpan startDifference, int hertzDifference)
{
if (events.Count < 2)
{
return events;
return events.Cast<EventCommon>().ToList();
}

for (int i = events.Count - 1; i >= 0; i--)
Expand All @@ -195,18 +195,18 @@ public static List<SpectralEvent> CombineSimilarProximalEvents(List<SpectralEven
}
}

return events;
return events.Cast<EventCommon>().ToList();
}

/// <summary>
/// Combines events that are possible stacked harmonics, that is, they are coincident (have similar start and end times)
/// AND stacked (their maxima are within the passed frequency gap).
/// </summary>
public static List<SpectralEvent> CombinePotentialStackedTracks(List<SpectralEvent> events, TimeSpan timeDifference, int hertzDifference)
public static List<EventCommon> CombinePotentialStackedTracks(List<SpectralEvent> events, TimeSpan timeDifference, int hertzDifference)
{
if (events.Count < 2)
{
return events;
return events.Cast<EventCommon>().ToList();
}

for (int i = events.Count - 1; i >= 0; i--)
Expand All @@ -226,7 +226,7 @@ public static List<SpectralEvent> CombinePotentialStackedTracks(List<SpectralEve
}
}

return events;
return events.Cast<EventCommon>().ToList();
}

/// <summary>
Expand Down
237 changes: 237 additions & 0 deletions src/AudioAnalysisTools/Tracks/ForwardTrackAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
// <copyright file="ForwardTrackAlgorithm.cs" company="QutEcoacoustics">
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
// </copyright>

namespace AudioAnalysisTools.Tracks
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AnalysisPrograms.Recognizers.Base;
using AudioAnalysisTools.Events;
using AudioAnalysisTools.Events.Tracks;
using AudioAnalysisTools.Events.Types;
using AudioAnalysisTools.StandardSpectrograms;
using TowseyLibrary;

public static class ForwardTrackAlgorithm
{
/// <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;

double maxScore = parameters.MaxScore.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];
foreach (var track in tracks)
{
var ae = new ChirpEvent(track, maxScore)
{
SegmentDurationSeconds = frameCount * converter.StepSize,
};

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)
{
returnEvents = CompositeEvent.CombineSimilarProximalEvents(events, parameters.SyllableStartDifference, parameters.SyllableHertzGap);
}

return (returnEvents, combinedIntensityArray);
}

public static List<Track> GetForwardTracks(double[,] peaks, double minDuration, double maxDuration, double threshold, UnitConverters converter)
{
int frameCount = peaks.GetLength(0);
int binCount = peaks.GetLength(1);

var tracks = new List<Track>();

// Look for possible track starts and initialise as track.
// 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
for (int row = 0; row < frameCount; row++)
{
for (int col = 3; col < binCount - 3; col++)
{
if (peaks[row, col] < threshold)
{
continue;
}

// Visit each spectral peak in order. Each may be start of possible track
var track = GetForwardTrack(peaks, row, col, threshold, converter);

//If track has length within duration bounds, then add the track to list.
if (track.DurationSeconds >= minDuration && track.DurationSeconds <= maxDuration)
{
tracks.Add(track);
}
}
}

return tracks;
}

public static Track GetForwardTrack(double[,] peaks, int startRow, int startBin, double threshold, UnitConverters converter)
{
var track = new Track(converter, Events.Tracks.TrackType.FowardTrack);
track.SetPoint(startRow, startBin, peaks[startRow, startBin]);

// set the start point in peaks matrix to zero to prevent return to this point.
peaks[startRow, startBin] = 0.0;

int bin = startBin;
for (int row = startRow + 1; row < peaks.GetLength(0) - 2; row++)
{
//cannot take bin value less than 3 because of edge effects.
if (bin < 3)
{
bin = 3;
}

if (bin > peaks.GetLength(1) - 4)
{
bin = peaks.GetLength(1) - 4;
}

// explore options for track ahead
double optionStraight = Math.Max(peaks[row, bin] + peaks[row + 1, bin], peaks[row, bin] + peaks[row + 1, bin - 1]);
optionStraight = Math.Max(optionStraight, peaks[row, bin] + peaks[row + 1, bin + 1]);

// option for track descent
double optionDown = Math.Max(peaks[row, bin - 1] + peaks[row + 1, bin - 1], peaks[row, bin - 1] + peaks[row + 1, bin - 2]);
optionDown = Math.Max(optionDown, peaks[row, bin - 1] + peaks[row + 1, bin]);

// need this option for a steep track descent
double optionTwoDown = Math.Max(peaks[row, bin - 2] + peaks[row + 1, bin - 2], peaks[row, bin - 2] + peaks[row + 1, bin - 1]);
optionTwoDown = Math.Max(optionTwoDown, peaks[row, bin - 2] + peaks[row + 1, bin - 3]);

// option for track asscent
double optionUp = Math.Max(peaks[row, bin + 1] + peaks[row + 1, bin + 1], peaks[row, bin + 1] + peaks[row + 1, bin]);
optionUp = Math.Max(optionUp, peaks[row, bin + 1] + peaks[row + 1, bin + 2]);

// need this option for a steep track asscent
double optionTwoUp = Math.Max(peaks[row, bin + 2] + peaks[row + 1, bin + 2], peaks[row, bin + 2] + peaks[row + 1, bin + 1]);
optionTwoUp = Math.Max(optionTwoUp, peaks[row, bin + 2] + peaks[row + 1, bin + 3]);

// get max of the five next possible steps
double[] options = { optionStraight, optionDown, optionUp, optionTwoDown, optionTwoUp };

//double[] options = { optionStraight, optionDown, optionUp, 0.0, 0.0 };
var maxId = DataTools.GetMaxIndex(options);

// if track has come to an end
var maxValue = options[maxId] / 2.0;
if (maxValue < threshold)
{
break;
}

// else set bins to zero so as not to revisit.....
for (int col = -2; col <= 2; col++)
{
peaks[row, bin + col] = 0.0;
}

// and go to the new frequency bin
if (maxId == 1)
{
bin--;
}
else
if (maxId == 2)
{
bin++;
}
else
if (maxId == 3)
{
bin -= 2;
}
else
if (maxId == 4)
{
bin += 2;
}

track.SetPoint(row, bin, maxValue);

// next line is for debug purposes
var info = track.CheckPoint(row, bin);
}

return track;
}
}
}
11 changes: 3 additions & 8 deletions src/AudioAnalysisTools/Tracks/ForwardTrackParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ namespace AnalysisPrograms.Recognizers.Base
using System;
using System.Collections.Generic;
using Acoustics.Shared;
using AudioAnalysisTools;
using AudioAnalysisTools.Events;
using AudioAnalysisTools.Events.Tracks;
using AudioAnalysisTools.StandardSpectrograms;
using TowseyLibrary;

/// <summary>
/// Parameters needed from a config file to detect fowards spectral peak tracks.
Expand All @@ -36,10 +31,10 @@ public class ForwardTrackParameters : CommonParameters
/// To qualify for combining, the event start times should not be greater than the specified seconds interval....
/// AND the difference in minimum frequency values (the Hertz gap) between consecutive tracks should not exceed the specified Hertz interval.
/// </summary>
public bool CombinePossibleChirpSequence { get; set; }
public bool CombinePossibleSyllableSequence { get; set; }

public TimeSpan ChirpStartDifference { get; set; }
public TimeSpan SyllableStartDifference { get; set; }

public int ChirpHertzGap { get; set; }
public int SyllableHertzGap { get; set; }
}
}
Loading

0 comments on commit 331b713

Please sign in to comment.