Skip to content

Commit

Permalink
[WIP] Tracks drawing tests begun
Browse files Browse the repository at this point in the history
Work done for #310.

- Improves our NoAA draw line method to handle more buggy behaviour
- Added ToString human friendly display to spectral point
- Improves and tests track drawing behaviour for the line drawing case
- Added a utility constructor to Track
- Changed the offset times in unit converters
- Added tests for the spectral point comparer
- Add dot syntax to represent empty spots on a test image - easier to use when showing a test image as a full grid of 'text pixels'
  • Loading branch information
atruskie committed Apr 15, 2020
1 parent 3663cbb commit c5111ef
Show file tree
Hide file tree
Showing 21 changed files with 527 additions and 108 deletions.
46 changes: 21 additions & 25 deletions src/Acoustics.Shared/ImageSharp/Drawing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Acoustics.Shared.ImageSharp
{
using System;
using System.IO;
using System.Linq;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
Expand Down Expand Up @@ -140,7 +141,7 @@ public static Image<T> NewImage<T>(int width, int height, Color fill)

/// <summary>
/// A specialized class the deals with drawing graphics without anti-aliasing.
/// It deal with two issues:
/// It deals with two issues:
/// - Lines in ImageSharp are drawn on the centre pixel. Without AA they're drawn a pixel
/// off. This class draws all lines with +0.0,+0.5 coordinates.
/// See https://github.com/SixLabors/ImageSharp.Drawing/issues/28
Expand All @@ -159,41 +160,36 @@ public NoAA(IImageProcessingContext context)

public void DrawLine(IPen pen, int x1, int y1, int x2, int y2)
{
var a = new PointF(x1, y1) + Bug28Offset;
var b = new PointF(x2, y2) + Bug28Offset;

this.context.DrawLines(
Drawing.NoAntiAlias,
pen,
a,
b);
this.DrawLines(pen, new Point(x1, y1), new Point(x2, y2));
}

public void DrawLine(IPen pen, params PointF[] points)
public void DrawLines(IPen pen, params PointF[] points)
{
for (int i = 0; i < points.Length; i++)
// i've no idea why, but repeating the first point and last point
// and adding random offsets in reduces visual errors in line drawing!
var slope = points[0].Y.CompareTo(points[^1].Y) switch
{
points[i].Offset(Bug28Offset);
}
-1 => 0.0f,
0 => 0,
1 => 0.5f,
_ => throw new NotImplementedException(),
};
var offset = new PointF(slope, Bug28Offset.Y);
var modifiedPoints = points
.Select(p => p + offset)
.Prepend(points[0] + Bug28Offset)
.Append(points[^1] + Bug28Offset)
.ToArray();

this.context.DrawLines(
NoAntiAlias,
pen,
points);
modifiedPoints);
}

public void DrawLine(Color color, float thickness, params PointF[] points)
public void DrawLines(Color color, float thickness, params PointF[] points)
{
for (int i = 0; i < points.Length; i++)
{
points[i].Offset(Bug28Offset);
}

this.context.DrawLines(
NoAntiAlias,
color,
thickness,
points);
this.DrawLines(new Pen(color, thickness), points);
}

public void DrawRectangle(Pen pen, int x1, int y1, int x2, int y2)
Expand Down
1 change: 1 addition & 0 deletions src/Acoustics.Shared/ImageSharp/ImageSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ public static void Clear(this IImageProcessingContext context, Color color)
/// <remarks>
/// Apparently blending pixels with transparency is not supported for Rgb24 images.
/// See the FillDoesNotBlendByDefault.Test smoke test.
/// BUG: Blending does not occur with fill https://github.com/SixLabors/ImageSharp.Drawing/issues/38.
/// </remarks>
/// <param name="context">The drawing context.</param>
/// <param name="brush">The brush to fill with.</param>
Expand Down
18 changes: 17 additions & 1 deletion src/Acoustics.Shared/Interval.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,26 @@ public override int GetHashCode()
/// String representation.
/// </returns>
public override string ToString()
{
return this.ToString(false);
}

/// <summary>
/// Gets string representation of the Interval.
/// technially incorrectly representing this value.
/// </summary>
/// <param name="suppressName">
/// If true only prints interval data and not type name.
/// </param>
/// <returns>
/// String representation.
/// </returns>
public string ToString(bool suppressName)
{
var left = this.IsMinimumInclusive ? "[" : "(";
var right = this.IsMaximumInclusive ? "]" : ")";
return $"{nameof(Interval<T>)}: {left}{this.Minimum}, {this.Maximum}{right}";
var name = suppressName ? string.Empty : nameof(Interval<T>) + ": ";
return $"{name}{left}{this.Minimum}, {this.Maximum}{right}";
}

public int CompareTo(Interval<T> other)
Expand Down
2 changes: 1 addition & 1 deletion src/Acoustics.Tools/Audio/CustomSpectrogramUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ private static double[] InvokeDotNetFft(double[] data, int windowSize, int coeff

// calculate power of the DC value - first column of matrix
// foreach time step or frame
for (int i = 0; i < frameCount; i++)
for (int i = 0; i < frameCount; i++)
{
if (amplitudeM[i, 0] < epsilon)
{
Expand Down
2 changes: 1 addition & 1 deletion src/AudioAnalysisTools/Events/InstantEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public override void Draw(IImageProcessingContext graphics, EventRenderingOption
{
// simply draw a full-height line
var startPixel = options.Converters.SecondsToPixels(this.EventStartSeconds);
graphics.NoAA().DrawLine(
graphics.NoAA().DrawLines(
options.Border,
new PointF(startPixel, 0),
new PointF(startPixel, graphics.GetCurrentSize().Height));
Expand Down
16 changes: 11 additions & 5 deletions src/AudioAnalysisTools/Events/Interfaces/IPointData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace AudioAnalysisTools
{
using System.Linq;
using Acoustics.Shared.ImageSharp;
using AudioAnalysisTools.Events.Drawing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
Expand Down Expand Up @@ -53,14 +54,19 @@ public void DrawPointsAsFill(IImageProcessingContext graphics, EventRenderingOpt
public void DrawPointsAsPath(IImageProcessingContext graphics, EventRenderingOptions options)
{
// visits each point once
// assumes each point describes a line
// assumes each point pair describes a line
// assumes a SortedSet is used (and that iteration order is signficant, unlike with HashSet)
// TODO: maybe add an orderby?
var path = this.Points.Select(x => options.Converters.GetPoint(x)).ToArray();
var path = this
.Points
.OrderBy(x => x)
.Select(options.Converters.GetPoint)
.ToArray();

// note not using AA here
// note could base pen thickness off ISpectralPoint thickness for a more accurate representation
graphics.DrawLines(
// note: using AA here
// note: could base pen thickness off ISpectralPoint thickness for a more accurate representation
//graphics.Draw()
graphics.NoAA().DrawLines(
options.Border,
path);
}
Expand Down
5 changes: 5 additions & 0 deletions src/AudioAnalysisTools/Events/SpectralPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,10 @@ public int CompareTo(object obj)

return this.Value.CompareTo(otherPoint.Value);
}

public override string ToString()
{
return $"{nameof(SpectralPoint)}: {this.Seconds.ToString(true)} s, {this.Hertz.ToString(true)} Hz, {this.Value} value";
}
}
}
4 changes: 2 additions & 2 deletions src/AudioAnalysisTools/Events/TemporalEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public override void Draw(IImageProcessingContext graphics, EventRenderingOption
{
// simply draw a full-height lines either side of the vent
var startPixel = options.Converters.SecondsToPixels(this.EventStartSeconds);
graphics.NoAA().DrawLine(
graphics.NoAA().DrawLines(
options.Border,
new PointF(startPixel, 0),
new PointF(startPixel, graphics.GetCurrentSize().Height));

var endPixel = options.Converters.SecondsToPixels(this.EventEndSeconds);
graphics.NoAA().DrawLine(
graphics.NoAA().DrawLines(
options.Border,
new PointF(endPixel, 0),
new PointF(endPixel, graphics.GetCurrentSize().Height));
Expand Down
24 changes: 24 additions & 0 deletions src/AudioAnalysisTools/Events/Tracks/Track.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,31 @@ public class Track : ITrack
/// Initializes a new instance of the <see cref="Track"/> class.
/// Constructor.
/// </summary>
/// <param name="converter">
/// A reference to unit conversions this track class should use to
/// convert spectrogram data to real units.
/// </param>
public Track(UnitConverters converter)
{
this.converter = converter;
this.Points = new SortedSet<ISpectralPoint>();
}

/// <inheritdoc cref="Track.Track(UnitConverters)"/>
/// <param name="initialPoints">
/// A set of initial points to add into the point data collection.
/// </param>
public Track(
UnitConverters converter,
params (int Frame, int Bin, double Amplitude)[] initialPoints)
: this(converter)
{
foreach (var point in initialPoints)
{
this.SetPoint(point.Frame, point.Bin, point.Amplitude);
}
}

public int PointCount => this.Points.Count;

public double StartTimeSeconds => this.converter.SegmentStartOffset + this.Points.Min(x => x.Seconds.Minimum);
Expand Down Expand Up @@ -149,6 +168,11 @@ public double[] GetAmplitudeOverTimeFrames()
/// <summary>
/// Draws the track on an image given by its processing context.
/// </summary>
/// <remarks>
/// Implementation is fairly simple. It sorts all points by the default IComparable method
/// which sorts points by time (ascending), frequency (ascending) and finally value.
/// The sorted collection is then used as a set of points to connect lines to.
/// </remarks>
public void Draw(IImageProcessingContext graphics, EventRenderingOptions options)
{
((IPointData)this).DrawPointsAsPath(graphics, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ public static Image<Rgb24> DrawTitleBarOfGrayScaleSpectrogram(string title, int

if (tag.NotNull())
{
g.NoAA().DrawLine(
g.NoAA().DrawLines(
tag.Value,
1f,
new PointF(0, 0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static Image<Rgb24> GetSonogramPlusCharts(
{
spectrogram = Image_MultiTrack.OverlayScoresAsRedTransparency(spectrogram, hits);

// following line needs to be reworked if want to call OverlayRainbowTransparency(hits);
// following line needs to be reworked if want to call OverlayRainbowTransparency(hits);
//image.OverlayRainbowTransparency(hits);
}

Expand Down
14 changes: 12 additions & 2 deletions src/AudioAnalysisTools/UnitConverters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ public PointF GetPoint(ISpectralPoint point)
(float)this.SpectralScale.To(point.Hertz.Maximum));
}

public PointF GetPointCentroid(ISpectralPoint point)
{
var centerX = point.Seconds.Center();
var centerY = point.Hertz.Center();

return new PointF(
(float)this.TemporalScale.To(centerX),
(float)this.SpectralScale.To(centerY));
}

/// <summary>
/// Gets the width and height of an event.
/// </summary>
Expand Down Expand Up @@ -223,12 +233,12 @@ public int FrameFromStartTime(double startTime)

public double GetStartTimeInSecondsOfFrame(int frameId)
{
return frameId * this.SecondsPerFrameStep;
return this.SegmentStartOffset + (frameId * this.SecondsPerFrameStep);
}

public double GetEndTimeInSecondsOfFrame(int frameId)
{
return this.GetStartTimeInSecondsOfFrame(frameId) + this.SecondsPerFrame;
return this.SegmentStartOffset + (this.GetStartTimeInSecondsOfFrame(frameId) + this.SecondsPerFrame);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Acoustics.Test.AudioAnalysisTools.Events
using Acoustics.Shared;
using global::AudioAnalysisTools.Events;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

[TestClass]
public class SpectralPointTests
Expand Down Expand Up @@ -61,5 +62,58 @@ public void TestHashCode()
Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
Assert.AreNotEqual(a.GetHashCode(), c.GetHashCode());
}

[DataTestMethod]
[DataRow(0.1, 0.2, 1000, 2000, 3, -1)]
[DataRow(5.0, 5.2, 1000, 2000, 3, 1)]
[DataRow(1.0, 5.0, 1000, 2000, 3, -1)]
[DataRow(1.0, 5.0, 7000, 9000, 3, 1)]
[DataRow(1.0, 5.0, 5000, 6000, 1, -1)]
[DataRow(1.0, 5.0, 5000, 6000, 4, 1)]
[DataRow(1.0, 5.0, 5000, 6000, 3, 0)]
public void TestComparer(double t1, double t2, double h1, double h2, double v, int expected)
{
var other = new SpectralPoint(
(1, 5),
(5000, 6000),
3);

var test = new SpectralPoint(
(t1, t2),
(h1, h2),
v);

var actual = test.CompareTo(other);
var actualInverse = other.CompareTo(test);

Assert.AreEqual(expected, actual);

var inverseExpected = expected switch
{
-1 => 1,
0 => 0,
1 => -1,
_ => throw new NotSupportedException(),
};
Assert.AreEqual(inverseExpected, actualInverse);
}

[TestMethod]
public void TestToString()
{
Interval<double> seconds = (1, 5);
Interval<double> hertz = (5000, 6000);

var test = new SpectralPoint(
seconds,
hertz,
3);

var actual = test.ToString();

Assert.AreEqual(
$"SpectralPoint: [1, 5) s, [5000, 6000) Hz, 3 value",
actual);
}
}
}
Loading

0 comments on commit c5111ef

Please sign in to comment.