Skip to content

Commit

Permalink
Reverted changes to score, improved drawing methods
Browse files Browse the repository at this point in the history
- The score on EventBase cannot have its definition changed. I've reverted previous changes that attempted to define and normalize score values.
- I've added an example on how Score can be overrided in a child class (chirp event inthis case) so that its value can be given extra semantics
- I've reverted the change of adding MaxScore to CommonParameters. Not only is the field near useless (if a good constant value existrs, why not use it?) (if not, why not compute it?) but it is also almost impossible for a user to guess at what a good value should be! Additionally, why was it added to common parameters?
- Added event drawer class to centralise methods
- Added a bunch more options to event options to control output of events
- Changed IPointData to use an ICollection and equivalently changed Track to use a List (apparently order of insertion is important)
- Cleaned up spectral event, removed unnecessary additional draw method
- Fixed documentation of enum tyoes in Track
- Tried to write more track drawing tests but ran out of time because of all the cleaning required
-
  • Loading branch information
atruskie committed Apr 25, 2020
1 parent e464f63 commit 5b351a6
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 92 deletions.
2 changes: 2 additions & 0 deletions src/Acoustics.Shared/ImageSharp/Drawing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ static Drawing()
{
}

public static Font Roboto6 => GetFont(Roboto, 6f);

public static Font Tahoma6 => GetFont(Tahoma, 6f);

public static Font Tahoma9 => GetFont(Tahoma, 9f);
Expand Down
36 changes: 17 additions & 19 deletions src/AnalysisBase/ResultBases/EventBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ public abstract class EventBase : ResultBase
{
private double eventStartSeconds;

// For events, we store only the normalised event score.
// The setter ensures that the score lies in [0,1]
private double normalisedScore;

/// <summary>
/// Gets or sets the time (in seconds) from start of the file/recording to start of the current audio segment.
/// </summary>
Expand All @@ -30,21 +26,23 @@ public abstract class EventBase : ResultBase
/// </remarks>
public virtual double SegmentStartSeconds { get; set; }

//AudioAnalysisTools.Keys.EVENT_SCORE,
public virtual double Score
{
get
{
return this.normalisedScore;
}

set
{
// ensure the score lies in [0,1]
this.normalisedScore = Math.Max(0.0, value);
this.normalisedScore = Math.Min(1.0, value);
}
}
/// <summary>
/// Gets or sets a score for the event.
/// </summary>
/// <remarks>
/// <para>
/// The meaning, range, and behaviour of the value is purposely undefined.
/// It varies with each algorithm used and we recommend that you <b>only</b> compare values as relative
/// measures between events produced by the same algorithm.
/// </para>
/// <para>
/// If a particular recogniser wishes to define semantics for this value, it should:
/// - Override this property and add relevant documentation.
/// - Or add a new property (that possibly aliases this value) that defines and documents its semantics.
/// </para>
/// </remarks>
// AT: the above definition cannot be changed!
public virtual double Score { get; set; }

/// <summary>
/// Gets or sets the Event's Start Seconds.
Expand Down
7 changes: 0 additions & 7 deletions src/AudioAnalysisTools/CommonParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,5 @@ public abstract class CommonParameters
/// Gets or sets the threshold of "loudness" of a component. Units are decibels.
/// </summary>
public double? DecibelThreshold { get; set; } = 6;

/// <summary>
/// Gets or sets the maximum score for an event.
/// Setting this value sets a normalised score value for the event.
/// The normalised score is a linear conversion from 0 - maxScore to [0, 1].
/// </summary>
public double? MaxScore { get; set; }
}
}
77 changes: 77 additions & 0 deletions src/AudioAnalysisTools/Events/Drawing/EventDrawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// <copyright file="EventDrawer.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.Events.Drawing
{
using System;
using System.Collections.Generic;
using System.Text;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using static Acoustics.Shared.ImageSharp.Drawing;

public static class EventDrawer
{

/// <summary>
/// Draws a "score" indicator on the left edge of an event.
/// </summary>
/// <param name="graphics">The image context to draw to.</param>
/// <param name="options">The event rendering optons to use.</param>
/// <param name="@event">The event for which to draw the score indicator.</param>
public static void DrawScoreIndicator(this SpectralEvent @event, IImageProcessingContext graphics, EventRenderingOptions options)
{
if (!options.DrawScore)
{
return;
}

// TODO: add a Interval<double> ScoreRange property to EventCommon
// so we can properly normalize this value to the unit value.
// For now, we just assume it is normalized to [0,1].
var clampedScore = @event.Score.Clamp(0, 1);

if (clampedScore == 0)
{
return;
}

var rect = options.Converters.GetPixelRectangle(@event);

var scaledHeight = (float)clampedScore * rect.Height;

graphics.NoAA().DrawLines(
options.Score,
new PointF(rect.Left, rect.Bottom),
new PointF(rect.Left, rect.Bottom + scaledHeight));
}

public static void DrawEventLabel(this SpectralEvent @event, IImageProcessingContext graphics, EventRenderingOptions options)
{
if (!options.DrawLabel)
{
return;
}

var text = @event.Name;

if (string.IsNullOrWhiteSpace(text))
{
return;
}

var bounds = TextMeasurer.MeasureBounds(text, new RendererOptions(Roboto6));
var topLeft = options.Converters.GetPoint(@event);

topLeft.Offset(0, -bounds.Height);

graphics.DrawTextSafe(
@event.Name,
Roboto6,
options.Label,
topLeft);
}
}
}
27 changes: 27 additions & 0 deletions src/AudioAnalysisTools/Events/Drawing/EventRenderingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,36 @@ public EventRenderingOptions(UnitConverters converters)
/// </summary>
public IBrush Fill { get; set; } = new SolidBrush(Color.Red.WithAlpha(0.5f));

/// <summary>
/// Gets or sets the graphics options that should be used with the
/// <see cref="Fill"/> brush for rendering the contents of an event.
/// </summary>
public GraphicsOptions FillOptions { get; set; } = new GraphicsOptions()
{
};

/// <summary>
/// Gets or sets the Pen used to draw a "score" indicator
/// on the left edge of the event.
/// </summary>
public Pen Score { get; set; } = new Pen(Color.LimeGreen, 1f);

/// <summary>
/// Gets or sets the color to use when rendering labels.
/// </summary>
public Color Label { get; set; } = Color.DarkBlue;

/// <summary>
/// Gets a value indicating whether the image to draw onto represents a spectrogram.
/// </summary>
public bool TargetImageIsSpectral { get; } = true;

public bool DrawBorder { get; } = true;

public bool DrawFill { get; } = true;

public bool DrawScore { get; } = true;

public bool DrawLabel { get; } = true;
}
}
1 change: 0 additions & 1 deletion src/AudioAnalysisTools/Events/Drawing/IDrawableEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace AudioAnalysisTools.Events.Drawing
{
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

public interface IDrawableEvent
Expand Down
38 changes: 26 additions & 12 deletions src/AudioAnalysisTools/Events/Interfaces/IPointData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@

namespace AudioAnalysisTools
{
using System.Collections.Generic;
using System.Linq;
using Acoustics.Shared.ImageSharp;
using AudioAnalysisTools.Events.Drawing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

public interface IPointData
{
/// <summary>
/// Gets a collection of spectral points.
/// </summary>
/// <remarks>
/// Use a HashSet for unsorted data and use a sorted set for sorted data.
/// </remarks>
public System.Collections.Generic.ISet<ISpectralPoint> Points { get; }
public ICollection<ISpectralPoint> Points { get; }

public void DrawPointsAsFill(IImageProcessingContext graphics, EventRenderingOptions options)
{
if (!options.DrawFill)
{
return;
}

// overlay point data on image with 50% opacity
// TODO: a much more efficient implementation exists if we derive from Region and convert
// our set<points> to a region.
Expand Down Expand Up @@ -62,6 +66,11 @@ public void DrawPointsAsFill(IImageProcessingContext graphics, EventRenderingOpt

public void DrawPointsAsFillExperiment(IImageProcessingContext graphics, EventRenderingOptions options)
{
if (!options.DrawFill)
{
return;
}

var rects = this
.Points
.Select(p => new RectangularPolygon(options.Converters.GetPixelRectangle(p)))
Expand All @@ -71,20 +80,25 @@ public void DrawPointsAsFillExperiment(IImageProcessingContext graphics, EventRe
foreach (var rect in rects)
{
graphics.Fill(
new GraphicsOptions()
{
BlendPercentage = 0.8f,
//ColorBlendingMode = SixLabors.ImageSharp.PixelFormats.PixelColorBlendingMode.Multiply,
ColorBlendingMode = SixLabors.ImageSharp.PixelFormats.PixelColorBlendingMode.Overlay,
},
//Color.FromRgb(0, 255, 0),
Color.LimeGreen,
//new GraphicsOptions()
//{
// BlendPercentage = 0.8f,

// //ColorBlendingMode = PixelColorBlendingMode.Multiply,
// ColorBlendingMode = PixelColorBlendingMode.Overlay,
//},
options.FillOptions,
options.Fill,
rect);
}
}

public void DrawPointsAsPath(IImageProcessingContext graphics, EventRenderingOptions options)
{
if (!options.DrawFill)
{
return;
}
// visits each point once
// assumes each point pair describes a line
// assumes a SortedSet is used (and that iteration order is signficant, unlike with HashSet)
Expand Down
27 changes: 6 additions & 21 deletions src/AudioAnalysisTools/Events/SpectralEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,14 @@ public SpectralEvent(TimeSpan segmentStartOffset, double eventStartRecordingRela
public override void Draw(IImageProcessingContext graphics, EventRenderingOptions options)
{
// draw a border around this event
var border = options.Converters.GetPixelRectangle(this);
graphics.NoAA().DrawBorderInset(options.Border, border);
}

public void DrawWithAnnotation(IImageProcessingContext graphics, EventRenderingOptions options)
{
this.Draw(graphics, options);

var topBin = options.Converters.HertzToPixels(this.HighFrequencyHertz);
var eventPixelStart = (int)Math.Round(options.Converters.SecondsToPixels(this.EventStartSeconds));

if (this.Score > 0.0)
{
//draw the score bar to indicate relative score
var bottomBin = (int)Math.Round(options.Converters.HertzToPixels(this.LowFrequencyHertz));
var eventPixelHeight = bottomBin - topBin + 1;
int scoreHt = (int)Math.Floor(eventPixelHeight * this.Score);
var scorePen = new Pen(Color.LimeGreen, 1);
graphics.NoAA().DrawLine(scorePen, eventPixelStart, bottomBin - scoreHt, eventPixelStart, bottomBin);
if (options.DrawBorder) {
var border = options.Converters.GetPixelRectangle(this);
graphics.NoAA().DrawBorderInset(options.Border, border);
}

//TODO This text is not being drawn????????????????????????????????????????????????????????????????????????????????????????????????????????
graphics.DrawTextSafe(this.Name, Acoustics.Shared.ImageSharp.Drawing.Tahoma6, Color.DarkBlue, new PointF(eventPixelStart, topBin - 4));
this.DrawScoreIndicator(graphics, options);

this.DrawEventLabel(graphics, options);
}
}
}
2 changes: 1 addition & 1 deletion src/AudioAnalysisTools/Events/Types/BlobEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public BlobEvent()
// ################################################################ TODO TODO TODO
}

public ISet<ISpectralPoint> Points { get; } = new HashSet<ISpectralPoint>();
public ICollection<ISpectralPoint> Points { get; } = new HashSet<ISpectralPoint>();

public override void Draw(IImageProcessingContext graphics, EventRenderingOptions options)
{
Expand Down
24 changes: 16 additions & 8 deletions src/AudioAnalysisTools/Events/Types/ChirpEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@ namespace AudioAnalysisTools

public class ChirpEvent : SpectralEvent, ITracks<Track>
{
private readonly double maxScore;

/// <summary>
/// Initializes a new instance of the <see cref="ChirpEvent"/> class.
/// </summary>
/// <remarks>
/// MaxScore establishes a scale for the chirp score. Typically the amplitude of track points is decibels.
/// A satisfactory maxScore is 12.0 decibels, since this is a high SNR in enviornmental recordings.
/// The normalised score is a linear conversion from 0 - maxScore to [0, 1].
/// </summary>
/// </remarks>
/// <param name="chirp">A chirp track consisting of a sequence of spectral points.</param>
/// <param name="maxScore">A maximum score used to normalise the track score.</param>
public ChirpEvent(Track chirp, double maxScore)
{
this.Tracks.Add(chirp);

// set score = to average normalised amplitude score.
this.SetTrackScore(maxScore);
this.maxScore = maxScore;
}

public List<Track> Tracks { get; private set; } = new List<Track>(1);
Expand All @@ -46,13 +49,18 @@ public ChirpEvent(Track chirp, double maxScore)
this.Tracks.Max(x => x.HighFreqHertz);

/// <summary>
/// Sets a normalised value for the chirp's track score.
/// NOTE: It is assumed that the minimum value of the score range = zero.
/// Gets the average normalised amplitude.
/// </summary>
/// <param name="maxScore">The max score value which sets the scale.</param>
public void SetTrackScore(double maxScore)
/// <remarks>
/// This score is a normalised value for the chirp's track score.
/// NOTE: It is assumed that the minimum value of the score range = zero.
/// </remarks>
public override double Score
{
this.Score = this.Tracks[0].GetAverageTrackAmplitude() / maxScore;
get
{
return this.Tracks.Single().GetAverageTrackAmplitude() / this.maxScore;
}
}

public override void Draw(IImageProcessingContext graphics, EventRenderingOptions options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ public static Image<Rgb24> GetSonogramPlusCharts(
{
var options = new EventRenderingOptions(new UnitConverters(segmentStartTime.TotalSeconds, segmentDuration.TotalSeconds, nyquist, width, height));

//spectrogram.Mutate(x => ev.Draw(x, options));
spectrogram.Mutate(x => ev.DrawWithAnnotation(x, options));
spectrogram.Mutate(x => ev.Draw(x, options));
}
}

Expand Down
Loading

0 comments on commit 5b351a6

Please sign in to comment.