From 1445182de004e3947ad58d74c70eb6474e9bb18f Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 09:39:33 +0100 Subject: [PATCH 01/14] Migration to net9 & benchmark --- SortCS.sln | 11 ++++ resources/logo.psd | Bin 623004 -> 622992 bytes src/SortCS.Benchmarks/Program.cs | 52 ++++++++++++++++++ .../SortCS.Benchmarks.csproj | 17 ++++++ src/SortCS.Evaluate/SortCS.Evaluate.csproj | 4 +- src/SortCS.Tests/SortCS.Tests.csproj | 2 +- src/SortCS/IsExternalInit.cs | 6 -- src/SortCS/SortCS.csproj | 6 +- 8 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/SortCS.Benchmarks/Program.cs create mode 100644 src/SortCS.Benchmarks/SortCS.Benchmarks.csproj delete mode 100644 src/SortCS/IsExternalInit.cs diff --git a/SortCS.sln b/SortCS.sln index 4f31d11..48abd52 100644 --- a/SortCS.sln +++ b/SortCS.sln @@ -20,6 +20,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SortCS.Tests", "src\SortCS.Tests\SortCS.Tests.csproj", "{13002E85-6B03-49DA-9700-906A24692C98}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DFA1CD64-077D-456F-B0B0-0568A3A22F1C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SortCS.Benchmarks", "src\SortCS.Benchmarks\SortCS.Benchmarks.csproj", "{8B4A7059-312F-4912-B2B3-383C25DA94DF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +42,10 @@ Global {13002E85-6B03-49DA-9700-906A24692C98}.Debug|Any CPU.Build.0 = Debug|Any CPU {13002E85-6B03-49DA-9700-906A24692C98}.Release|Any CPU.ActiveCfg = Release|Any CPU {13002E85-6B03-49DA-9700-906A24692C98}.Release|Any CPU.Build.0 = Release|Any CPU + {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -45,4 +53,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8C31716C-280A-44DD-ADAB-E48CEDFBF779} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8B4A7059-312F-4912-B2B3-383C25DA94DF} = {DFA1CD64-077D-456F-B0B0-0568A3A22F1C} + EndGlobalSection EndGlobal diff --git a/resources/logo.psd b/resources/logo.psd index a2797a40f4cf497f2727e521442dbbf74e2a22b6..7b491386b7fae5edfff9d5a9fde508845234c2de 100644 GIT binary patch delta 587 zcmbQ!tTq9N6d7M^RC>t7Xg-;R*_zwXFvQTn%D}+N)OfNNDu43?=C4dD$%ZLr=1IxM zx<)3(hPo!^md3i4$tf1PhGxdbX@-Wz#-^6$lUK5t6Q_`c-JUpw>FoE3Q<%l!!f3vE zJ%@*>sx1Qp0~-)S0kZ-F0|+y)Fyt`gGo%AC0|VpaLR;a{jQ(IvF?k`|flh>&5R+F0F`+qzQ3ULWn7qkA6Xdt= zDPgpP3M|95%tm8ai{ zWSlrTw4@0ln+cZP{I?`T0xV#~u{pdq0?N*XNM2nU0~I*C*?eCOgl%oMdFM4K+a_hR z^?Mlm>h`_tj4=?)ZH+)C$ZxmfWXx3qo8NF_J5c0`CYaNzzP&q=F$JQwy9sO?+@agu zq8YiNQX3)0l*KTLLTv9owS8g(); + +[MemoryDiagnoser] +public class SortCSBenchmarks +{ + private ITracker _tracker; + private List _frames; + + [GlobalSetup] + public void Setup() + { + _tracker = new SortTracker(); + _frames = GenerateTestFrames(100, 10); + } + + [Benchmark] + public void TrackMultipleFrames() + { + foreach (var frame in _frames) + { + _tracker.Track(frame); + } + } + + private List GenerateTestFrames(int numFrames, int objectsPerFrame) + { + var random = new Random(42); + var frames = new List(); + + for (int i = 0; i < numFrames; i++) + { + var objects = new RectangleF[objectsPerFrame]; + for (int j = 0; j < objectsPerFrame; j++) + { + objects[j] = new RectangleF( + random.Next(0, 1000), + random.Next(0, 1000), + random.Next(50, 200), + random.Next(50, 200) + ); + } + frames.Add(objects); + } + + return frames; + } +} \ No newline at end of file diff --git a/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj b/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj new file mode 100644 index 0000000..ae86ee2 --- /dev/null +++ b/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + \ No newline at end of file diff --git a/src/SortCS.Evaluate/SortCS.Evaluate.csproj b/src/SortCS.Evaluate/SortCS.Evaluate.csproj index b376855..8cac9e1 100644 --- a/src/SortCS.Evaluate/SortCS.Evaluate.csproj +++ b/src/SortCS.Evaluate/SortCS.Evaluate.csproj @@ -2,13 +2,13 @@ Exe - net5.0 + net9.0 latest - + diff --git a/src/SortCS.Tests/SortCS.Tests.csproj b/src/SortCS.Tests/SortCS.Tests.csproj index ce886a4..d8a99b7 100644 --- a/src/SortCS.Tests/SortCS.Tests.csproj +++ b/src/SortCS.Tests/SortCS.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net9.0 latest false diff --git a/src/SortCS/IsExternalInit.cs b/src/SortCS/IsExternalInit.cs deleted file mode 100644 index 74305fd..0000000 --- a/src/SortCS/IsExternalInit.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit - { - } -} \ No newline at end of file diff --git a/src/SortCS/SortCS.csproj b/src/SortCS/SortCS.csproj index c8045c6..f6d67f7 100644 --- a/src/SortCS/SortCS.csproj +++ b/src/SortCS/SortCS.csproj @@ -1,7 +1,7 @@ - netstandard2.0; net5.0 + net9.0 preview true true @@ -36,8 +36,8 @@ - - + + From ac3ccadb9ca00c315755baad71d5aa86052bc2a9 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 11:56:38 +0100 Subject: [PATCH 02/14] Trying to reduce allocations --- src/SortCS/EnumerableExtensions.cs | 2 +- src/SortCS/Kalman/KalmanBoxTracker.cs | 56 +++++++++++++++------------ src/SortCS/Kalman/Matrix.cs | 9 ++++- src/SortCS/Kalman/Vector.cs | 34 +++++++++++----- src/SortCS/SortTracker.cs | 24 +++++++----- 5 files changed, 80 insertions(+), 45 deletions(-) diff --git a/src/SortCS/EnumerableExtensions.cs b/src/SortCS/EnumerableExtensions.cs index 1080cd6..3783fe7 100644 --- a/src/SortCS/EnumerableExtensions.cs +++ b/src/SortCS/EnumerableExtensions.cs @@ -7,7 +7,7 @@ internal static class EnumerableExtensions { public static T[,] ToArray(this IEnumerable source, int firstDimensionLength, int secondDimensionLength) { - var array = source.ToArray(); + var array = source as T[] ?? source.ToArray(); var result = new T[firstDimensionLength, secondDimensionLength]; for (var i = 0; i < array.Length; i++) diff --git a/src/SortCS/Kalman/KalmanBoxTracker.cs b/src/SortCS/Kalman/KalmanBoxTracker.cs index ac43924..79d42a2 100644 --- a/src/SortCS/Kalman/KalmanBoxTracker.cs +++ b/src/SortCS/Kalman/KalmanBoxTracker.cs @@ -6,13 +6,7 @@ namespace SortCS.Kalman { internal class KalmanBoxTracker { - private readonly KalmanFilter _filter; - - public KalmanBoxTracker(RectangleF box) - { - _filter = new KalmanFilter(7, 4) - { - StateTransitionMatrix = new Matrix( + private static Matrix _stateTransitioningMatrix = new Matrix( new double[,] { { 1, 0, 0, 0, 1, 0, 0 }, @@ -22,16 +16,18 @@ public KalmanBoxTracker(RectangleF box) { 0, 0, 0, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 1, 0 }, { 0, 0, 0, 0, 0, 0, 1 } - }), - MeasurementFunction = new Matrix( + }); + + private static Matrix _measurementFunction = new Matrix( new double[,] { { 1, 0, 0, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 1, 0, 0, 0 } - }), - UncertaintyCovariances = new Matrix( + }); + + private static Matrix _uncertaintyCovariances = new Matrix( new double[,] { { 10, 0, 0, 0, 0, 0, 0 }, @@ -41,15 +37,17 @@ public KalmanBoxTracker(RectangleF box) { 0, 0, 0, 0, 10000, 0, 0 }, { 0, 0, 0, 0, 0, 10000, 0 }, { 0, 0, 0, 0, 0, 0, 10000 } - }), - MeasurementUncertainty = new Matrix(new double[,] - { - { 1, 0, 0, 0 }, - { 0, 1, 0, 0 }, - { 0, 0, 10, 0 }, - { 0, 0, 0, 10 }, - }), - ProcessUncertainty = new Matrix( + }); + + private static Matrix _measurementUncertainty = new Matrix(new double[,] + { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 10, 0 }, + { 0, 0, 0, 10 }, + }); + + private static Matrix _processUncertainty = new Matrix( new double[,] { { 1, 0, 0, 0, 0, 0, 0 }, @@ -59,7 +57,19 @@ public KalmanBoxTracker(RectangleF box) { 0, 0, 0, 0, .01, 0, 0 }, { 0, 0, 0, 0, 0, .01, 0 }, { 0, 0, 0, 0, 0, 0, .0001 } - }), + }); + + private readonly KalmanFilter _filter; + + public KalmanBoxTracker(RectangleF box) + { + _filter = new KalmanFilter(7, 4) + { + StateTransitionMatrix = _stateTransitioningMatrix, + MeasurementFunction = _measurementFunction, + UncertaintyCovariances = _uncertaintyCovariances, + MeasurementUncertainty = _measurementUncertainty, + ProcessUncertainty = _processUncertainty, CurrentState = ToMeasurement(box).Append(0, 0, 0) }; } @@ -73,9 +83,7 @@ public RectangleF Predict() { if (_filter.CurrentState[6] + _filter.CurrentState[2] <= 0) { - var state = _filter.CurrentState.ToArray(); - state[6] = 0; - _filter.CurrentState = new Vector(state); + _filter.CurrentState[6] = 0; } _filter.Predict(); diff --git a/src/SortCS/Kalman/Matrix.cs b/src/SortCS/Kalman/Matrix.cs index 5612f79..08068d0 100644 --- a/src/SortCS/Kalman/Matrix.cs +++ b/src/SortCS/Kalman/Matrix.cs @@ -180,7 +180,14 @@ public Vector Dot(Vector vector) { Debug.Assert(Columns == vector.Length, "Matrix should have the same number of columns as the vector has rows."); - return new Vector(Enumerable.Range(0, Rows).Select(Row).Select(row => row.Dot(vector)).ToArray()); + var result = new double[Rows]; + for (int i = 0; i < Rows; i++) + { + var row = Row(i); + result[i] = row.Dot(vector); + } + + return new Vector(result); } public Vector Row(int index) diff --git a/src/SortCS/Kalman/Vector.cs b/src/SortCS/Kalman/Vector.cs index 32e484c..a0622f8 100644 --- a/src/SortCS/Kalman/Vector.cs +++ b/src/SortCS/Kalman/Vector.cs @@ -20,26 +20,47 @@ public Vector(int size) public int Length => _values.Length; - public double this[int index] => _values[index]; + public double this[int index] + { + get => _values[index]; + set => _values[index] = value; + } public static Vector operator -(Vector first, Vector second) { Debug.Assert(first.Length == second.Length, "Vectors should be of equal size"); - return new Vector(first._values.Zip(second._values, (a, b) => a - b).ToArray()); + var resultArray = new double[first.Length]; + for (int i = 0; i < first.Length; i++) + { + resultArray[i] = first[i] - second[i]; + } + + return new Vector(resultArray); } public static Vector operator +(Vector first, Vector second) { Debug.Assert(first.Length == second.Length, "Vectors should be of equal size"); - return new Vector(first._values.Zip(second._values, (a, b) => a + b).ToArray()); + var resultArray = new double[first.Length]; + for (int i = 0; i < first.Length; i++) + { + resultArray[i] = first[i] + second[i]; + } + + return new Vector(resultArray); } public double Dot(Vector other) { Debug.Assert(_values.Length == other._values.Length, "Vectors should be of equal length."); Debug.Assert(_values.Length > 0, "Vectors must have at least one element."); + double sum = 0; + for (int i = 0; i < _values.Length; i++) + { + sum += _values[i] * other._values[i]; + } - return _values.Zip(other._values, (a, b) => a * b).Sum(); + return sum; } public override string ToString() @@ -51,10 +72,5 @@ internal Vector Append(params double[] extraElements) { return new Vector(_values.Concat(extraElements).ToArray()); } - - internal double[] ToArray() - { - return _values.ToArray(); - } } } \ No newline at end of file diff --git a/src/SortCS/SortTracker.cs b/src/SortCS/SortTracker.cs index 720ce5e..a1207dc 100644 --- a/src/SortCS/SortTracker.cs +++ b/src/SortCS/SortTracker.cs @@ -112,7 +112,7 @@ private void Log(IEnumerable tracks) } private (Dictionary Matched, ICollection Unmatched) MatchDetectionsWithPredictions( - ICollection boxes, + RectangleF[] boxes, ICollection trackPredictions) { if (trackPredictions.Count == 0) @@ -120,19 +120,23 @@ private void Log(IEnumerable tracks) return (new(), boxes); } - var matrix = boxes.SelectMany((box) => trackPredictions.Select((trackPrediction) => - { - var iou = IoU(box, trackPrediction); + var matrix = new int[boxes.Length, trackPredictions.Count]; + var trackPredictionsArray = trackPredictions.ToArray(); - return (int)(100 * -iou); - })).ToArray(boxes.Count, trackPredictions.Count); + for (int i = 0; i < boxes.Length; i++) + { + for (int j = 0; j < trackPredictionsArray.Length; j++) + { + matrix[i, j] = (int)(-100 * IoU(boxes[i], trackPredictionsArray[j])); + } + } - if (boxes.Count > trackPredictions.Count) + if (boxes.Length > trackPredictions.Count) { - var extra = new int[boxes.Count - trackPredictions.Count]; - matrix = Enumerable.Range(0, boxes.Count) + var extra = new int[boxes.Length - trackPredictions.Count]; + matrix = Enumerable.Range(0, boxes.Length) .SelectMany(row => Enumerable.Range(0, trackPredictions.Count).Select(col => matrix[row, col]).Concat(extra)) - .ToArray(boxes.Count, boxes.Count); + .ToArray(boxes.Length, boxes.Length); } var original = (int[,])matrix.Clone(); From 30eac75ae7341ee8f941045bd01e57b0654edd9f Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 12:30:53 +0100 Subject: [PATCH 03/14] cache the rows and cols --- src/SortCS/Kalman/Matrix.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/SortCS/Kalman/Matrix.cs b/src/SortCS/Kalman/Matrix.cs index 08068d0..eea6e4a 100644 --- a/src/SortCS/Kalman/Matrix.cs +++ b/src/SortCS/Kalman/Matrix.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -10,6 +11,9 @@ internal class Matrix { private readonly double[,] _values; + private readonly Dictionary _rows = new(); + private readonly Dictionary _cols = new(); + public Matrix(double[,] values) { _values = values; @@ -193,13 +197,23 @@ public Vector Dot(Vector vector) public Vector Row(int index) { Debug.Assert(index <= Rows, "Row index out of range."); - return new Vector(Enumerable.Range(0, Columns).Select(col => _values[index, col]).ToArray()); + if (!_rows.ContainsKey(index)) + { + _rows[index] = new Vector(Enumerable.Range(0, Columns).Select(col => _values[index, col]).ToArray()); + } + + return _rows[index]; } public Vector Column(int index) { Debug.Assert(index <= Columns, "Column index out of range."); - return new Vector(Enumerable.Range(0, Rows).Select(row => _values[row, index]).ToArray()); + if (!_cols.ContainsKey(index)) + { + _cols[index] = new Vector(Enumerable.Range(0, Rows).Select(row => _values[row, index]).ToArray()); + } + + return _cols[index]; } private double[] BackSubstition(double[,] lu, int[] indices, double[] b) @@ -245,7 +259,8 @@ private double[] BackSubstition(double[,] lu, int[] indices, double[] b) private (double[,] Result, int[] Indices, double D) GetDecomposition() { var max_row = 0; - var vv = Enumerable.Range(0, this.Rows).Select(row => 1.0d / Enumerable.Range(0, this.Columns).Select(col => Math.Abs(_values[row, col])).Max()).ToArray(); + var vv = Enumerable.Range(0, this.Rows) + .Select(row => 1.0d / Enumerable.Range(0, this.Columns).Select(col => Math.Abs(_values[row, col])).Max()).ToArray(); var result = (double[,])_values.Clone(); var index = new int[this.Rows]; var d = 1.0d; From 4cf579f8ecf24936240197578d7a7bf4b18d4362 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 16:00:10 +0100 Subject: [PATCH 04/14] Working on memory --- src/SortCS/Kalman/KalmanBoxTracker.cs | 84 +++++++++++++-------------- src/SortCS/Kalman/KalmanFilter.cs | 49 +++++++++------- src/SortCS/Kalman/Matrix.cs | 42 ++++++++++---- src/SortCS/Kalman/Vector.cs | 53 ++++++++++++----- 4 files changed, 140 insertions(+), 88 deletions(-) diff --git a/src/SortCS/Kalman/KalmanBoxTracker.cs b/src/SortCS/Kalman/KalmanBoxTracker.cs index 79d42a2..0e69f79 100644 --- a/src/SortCS/Kalman/KalmanBoxTracker.cs +++ b/src/SortCS/Kalman/KalmanBoxTracker.cs @@ -7,57 +7,57 @@ namespace SortCS.Kalman internal class KalmanBoxTracker { private static Matrix _stateTransitioningMatrix = new Matrix( - new double[,] - { - { 1, 0, 0, 0, 1, 0, 0 }, - { 0, 1, 0, 0, 0, 1, 0 }, - { 0, 0, 1, 0, 0, 0, 1 }, - { 0, 0, 0, 1, 0, 0, 0 }, - { 0, 0, 0, 0, 1, 0, 0 }, - { 0, 0, 0, 0, 0, 1, 0 }, - { 0, 0, 0, 0, 0, 0, 1 } - }); + new double[,] + { + { 1, 0, 0, 0, 1, 0, 0 }, + { 0, 1, 0, 0, 0, 1, 0 }, + { 0, 0, 1, 0, 0, 0, 1 }, + { 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0, 1, 0 }, + { 0, 0, 0, 0, 0, 0, 1 } + }); private static Matrix _measurementFunction = new Matrix( - new double[,] - { - { 1, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0 }, - { 0, 0, 1, 0, 0, 0, 0 }, - { 0, 0, 0, 1, 0, 0, 0 } - }); + new double[,] + { + { 1, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 0, 0, 0 } + }); private static Matrix _uncertaintyCovariances = new Matrix( - new double[,] - { - { 10, 0, 0, 0, 0, 0, 0 }, - { 0, 10, 0, 0, 0, 0, 0 }, - { 0, 0, 10, 0, 0, 0, 0 }, - { 0, 0, 0, 10, 0, 0, 0 }, - { 0, 0, 0, 0, 10000, 0, 0 }, - { 0, 0, 0, 0, 0, 10000, 0 }, - { 0, 0, 0, 0, 0, 0, 10000 } - }); + new double[,] + { + { 10, 0, 0, 0, 0, 0, 0 }, + { 0, 10, 0, 0, 0, 0, 0 }, + { 0, 0, 10, 0, 0, 0, 0 }, + { 0, 0, 0, 10, 0, 0, 0 }, + { 0, 0, 0, 0, 10000, 0, 0 }, + { 0, 0, 0, 0, 0, 10000, 0 }, + { 0, 0, 0, 0, 0, 0, 10000 } + }); private static Matrix _measurementUncertainty = new Matrix(new double[,] { - { 1, 0, 0, 0 }, - { 0, 1, 0, 0 }, - { 0, 0, 10, 0 }, - { 0, 0, 0, 10 }, + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 10, 0 }, + { 0, 0, 0, 10 }, }); private static Matrix _processUncertainty = new Matrix( - new double[,] - { - { 1, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0 }, - { 0, 0, 1, 0, 0, 0, 0 }, - { 0, 0, 0, 1, 0, 0, 0 }, - { 0, 0, 0, 0, .01, 0, 0 }, - { 0, 0, 0, 0, 0, .01, 0 }, - { 0, 0, 0, 0, 0, 0, .0001 } - }); + new double[,] + { + { 1, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 0, 0, .01, 0, 0 }, + { 0, 0, 0, 0, 0, .01, 0 }, + { 0, 0, 0, 0, 0, 0, .0001 } + }); private readonly KalmanFilter _filter; @@ -83,7 +83,7 @@ public RectangleF Predict() { if (_filter.CurrentState[6] + _filter.CurrentState[2] <= 0) { - _filter.CurrentState[6] = 0; + _filter.SetState(6, 0); } _filter.Predict(); diff --git a/src/SortCS/Kalman/KalmanFilter.cs b/src/SortCS/Kalman/KalmanFilter.cs index cb216d2..2db973c 100644 --- a/src/SortCS/Kalman/KalmanFilter.cs +++ b/src/SortCS/Kalman/KalmanFilter.cs @@ -3,7 +3,8 @@ namespace SortCS.Kalman { - [SuppressMessage("Major Code Smell", "S3928:Parameter names used into ArgumentException constructors should match an existing one ", Justification = "Properties throw ArgumentException for 'value'")] + [SuppressMessage("Major Code Smell", "S3928:Parameter names used into ArgumentException constructors should match an existing one ", + Justification = "Properties throw ArgumentException for 'value'")] internal class KalmanFilter { private readonly int _stateSize; @@ -17,6 +18,12 @@ internal class KalmanFilter private Vector _currentState; private Matrix _uncertaintyCovariances; + private Matrix _pht; + private Matrix _s; + private Matrix _si; + private Matrix _k; + private Matrix _kh; + private Matrix _ikh; public KalmanFilter(int stateSize, int measurementSize) { @@ -39,7 +46,7 @@ public KalmanFilter(int stateSize, int measurementSize) public Vector CurrentState { get => _currentState; - set => _currentState = value.Length == _stateSize + set => _currentState = value.Size == _stateSize ? value : throw new ArgumentException($"Vector must be of size {_stateSize}.", nameof(value)); } @@ -96,32 +103,34 @@ public Matrix MeasurementFunction : throw new ArgumentException($"Matrix must be of size {_measurementSize}x{_stateSize}.", nameof(value)); } - public void Predict(Matrix stateTransitionMatrix = null, Matrix processNoiseMatrix = null) + public void SetState(int index, double values) { - stateTransitionMatrix ??= StateTransitionMatrix; - processNoiseMatrix ??= ProcessUncertainty; - - _currentState = stateTransitionMatrix.Dot(CurrentState); - _uncertaintyCovariances = (_alphaSq * stateTransitionMatrix * UncertaintyCovariances * stateTransitionMatrix.Transposed) + processNoiseMatrix; + _currentState[index] = values; } - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", Justification = "These are well known abbreviations for the Kalman Filter")] - public void Update(Vector measurement, Matrix measurementNoise = null, Matrix measurementFunction = null) + public void Predict() { - measurementNoise ??= MeasurementUncertainty; - measurementFunction ??= MeasurementFunction; + _currentState = StateTransitionMatrix.Dot(CurrentState); + _uncertaintyCovariances = (_alphaSq * StateTransitionMatrix * UncertaintyCovariances * StateTransitionMatrix.Transposed) + + ProcessUncertainty; + } - var y = measurement - measurementFunction.Dot(CurrentState); - var pht = UncertaintyCovariances * measurementFunction.Transposed; - var S = (measurementFunction * pht) + measurementNoise; - var SI = S.Inverted; - var K = pht * SI; + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", + Justification = "These are well known abbreviations for the Kalman Filter")] + public void Update(Vector measurement) + { + _pht ??= UncertaintyCovariances * MeasurementFunction.Transposed; + _s ??= (MeasurementFunction * _pht) + MeasurementUncertainty; + _si ??= _s.Inverted; + _k ??= _pht * _si; + _kh ??= _k * MeasurementFunction; + _ikh ??= _identity - _kh; - _currentState += K.Dot(y); + var y = measurement - MeasurementFunction.Dot(CurrentState); - var I_KH = _identity - (K * measurementFunction); + _currentState += _k.Dot(y); - _uncertaintyCovariances = (I_KH * UncertaintyCovariances * I_KH.Transposed) + (K * measurementNoise * K.Transposed); + _uncertaintyCovariances = (_ikh * UncertaintyCovariances * _ikh.Transposed) + (_k * MeasurementUncertainty * _k.Transposed); } } } \ No newline at end of file diff --git a/src/SortCS/Kalman/Matrix.cs b/src/SortCS/Kalman/Matrix.cs index eea6e4a..f9b2dc4 100644 --- a/src/SortCS/Kalman/Matrix.cs +++ b/src/SortCS/Kalman/Matrix.cs @@ -1,7 +1,9 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Microsoft.VisualBasic; namespace SortCS.Kalman { @@ -46,6 +48,11 @@ public Matrix Transposed { get { + if (field != null) + { + return field; + } + var result = new double[Columns, Rows]; for (var row = 0; row < Rows; row++) @@ -56,7 +63,9 @@ public Matrix Transposed } } - return new Matrix(result); + field = new Matrix(result); + + return field; } } @@ -153,7 +162,11 @@ public Matrix Inverted { for (var col = 0; col < cols; col++) { - result[row, col] = first.Row(row).Dot(second.Column(col)); + var bufFirst = ArrayPool.Shared.Rent(first.Columns); + var bufSecond = ArrayPool.Shared.Rent(first.Rows); + result[row, col] = first.Row(row, bufFirst).Dot(second.Column(col, bufSecond)); + ArrayPool.Shared.Return(bufFirst, true); + ArrayPool.Shared.Return(bufSecond, true); } } @@ -182,38 +195,45 @@ public override string ToString() public Vector Dot(Vector vector) { - Debug.Assert(Columns == vector.Length, "Matrix should have the same number of columns as the vector has rows."); + Debug.Assert(Columns == vector.Size, "Matrix should have the same number of columns as the vector has rows."); var result = new double[Rows]; for (int i = 0; i < Rows; i++) { - var row = Row(i); + var buf = ArrayPool.Shared.Rent(Columns); + var row = Row(i, buf); result[i] = row.Dot(vector); + ArrayPool.Shared.Return(buf); } return new Vector(result); } public Vector Row(int index) + { + return Row(index, new double[Columns]); + } + + public Vector Row(int index, double[] buffer) { Debug.Assert(index <= Rows, "Row index out of range."); - if (!_rows.ContainsKey(index)) + for (int col = 0; col < Columns; col++) { - _rows[index] = new Vector(Enumerable.Range(0, Columns).Select(col => _values[index, col]).ToArray()); + buffer[col] = _values[index, col]; } - return _rows[index]; + return new Vector(buffer, Columns); } - public Vector Column(int index) + public Vector Column(int index, double[] buf) { Debug.Assert(index <= Columns, "Column index out of range."); - if (!_cols.ContainsKey(index)) + for (int row = 0; row < Rows; row++) { - _cols[index] = new Vector(Enumerable.Range(0, Rows).Select(row => _values[row, index]).ToArray()); + buf[row] = _values[row, index]; } - return _cols[index]; + return new Vector(buf, Rows); } private double[] BackSubstition(double[,] lu, int[] indices, double[] b) diff --git a/src/SortCS/Kalman/Vector.cs b/src/SortCS/Kalman/Vector.cs index a0622f8..7c9095b 100644 --- a/src/SortCS/Kalman/Vector.cs +++ b/src/SortCS/Kalman/Vector.cs @@ -1,36 +1,59 @@ +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; namespace SortCS.Kalman { - internal class Vector + internal struct Vector { private readonly double[] _values; public Vector(params double[] values) { _values = values; + Size = values.Length; + } + + public Vector(double[] values, int size) + { + if (size > values.Length) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + _values = values; + Size = size; } public Vector(int size) { _values = new double[size]; + Size = size; } - public int Length => _values.Length; + public int Size { get; } public double this[int index] { - get => _values[index]; - set => _values[index] = value; + get => index <= Size ? _values[index] : throw new Exception("nope"); + set + { + if (index > Size) + { + throw new Exception("asd"); + } + + _values[index] = value; + } } public static Vector operator -(Vector first, Vector second) { - Debug.Assert(first.Length == second.Length, "Vectors should be of equal size"); - var resultArray = new double[first.Length]; - for (int i = 0; i < first.Length; i++) + Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); + var resultArray = new double[first.Size]; + for (int i = 0; i < first.Size; i++) { resultArray[i] = first[i] - second[i]; } @@ -40,9 +63,9 @@ public double this[int index] public static Vector operator +(Vector first, Vector second) { - Debug.Assert(first.Length == second.Length, "Vectors should be of equal size"); - var resultArray = new double[first.Length]; - for (int i = 0; i < first.Length; i++) + Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); + var resultArray = new double[first.Size]; + for (int i = 0; i < first.Size; i++) { resultArray[i] = first[i] + second[i]; } @@ -52,12 +75,12 @@ public double this[int index] public double Dot(Vector other) { - Debug.Assert(_values.Length == other._values.Length, "Vectors should be of equal length."); - Debug.Assert(_values.Length > 0, "Vectors must have at least one element."); + Debug.Assert(Size == other.Size, $"Vectors should be of equal length {Size} != {other.Size}."); + Debug.Assert(Size > 0, "Vectors must have at least one element."); double sum = 0; - for (int i = 0; i < _values.Length; i++) + for (int i = 0; i < Size; i++) { - sum += _values[i] * other._values[i]; + sum += _values[i] * other[i]; } return sum; @@ -70,7 +93,7 @@ public override string ToString() internal Vector Append(params double[] extraElements) { - return new Vector(_values.Concat(extraElements).ToArray()); + return new Vector(_values.Take(Size).Concat(extraElements).ToArray()); } } } \ No newline at end of file From 02fa963d829dd31a1c179729abc525ef80c898ce Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 16:15:23 +0100 Subject: [PATCH 05/14] 1.0 & 9.0 --- .github/workflows/build.yml | 14 +++----------- .github/workflows/release.yml | 4 ++-- .vscode/settings.json | 5 +++++ src/SortCS/SortCS.csproj | 6 +++--- 4 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96ef366..bfd397d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,19 +21,11 @@ jobs: steps: - uses: actions/checkout@v2 - - - name: Setup .NET Core 2.1 + + - name: Setup .NET Core 9.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 2.1.x - - name: Setup .NET Core 3.1 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.x - - name: Setup .NET Core 5.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 5.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae464fd..9a5df36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,10 +21,10 @@ jobs: - name: Set VERSION variable from tag run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV - - name: Setup .NET Core 5.0 + - name: Setup .NET Core 9.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 9.0.x - name: Build working-directory: src/SortCS diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a04b218 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.yaml": "home-assistant" + } +} \ No newline at end of file diff --git a/src/SortCS/SortCS.csproj b/src/SortCS/SortCS.csproj index f6d67f7..3a53d04 100644 --- a/src/SortCS/SortCS.csproj +++ b/src/SortCS/SortCS.csproj @@ -8,9 +8,9 @@ Kees Schollaart, Maarten van Sambeek Vision Intelligence AnyCPU - 0.9.0.0 - 0.9.0.0 - 0.9.0.0 + 1.0.0.0 + 1.0.0.0 + 1.0.0.0 SortCS SortCS is a 'Multiple Object Tracker'. SortCS is a 'Multiple Object Tracker' From 09664efcf51a1db28ab0de7014247adea98efcb6 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 16:45:34 +0100 Subject: [PATCH 06/14] v4 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfd397d..e3332ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: run: dotnet test --no-restore --verbosity normal --logger trx --results-directory "TestResults-${{ matrix.os }}" - name: Upload dotnet test results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: dotnet-results-${{ matrix.os }} path: TestResults-${{ matrix.os }} From 1b4af69bf5ce6bffb0ec7f2333d4ef4c7cfb7b51 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 16:47:04 +0100 Subject: [PATCH 07/14] 9 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3332ed..25312d4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,10 +93,10 @@ jobs: - uses: actions/checkout@v2 - - name: Setup .NET Core 5.0 + - name: Setup .NET Core 9.0 uses: actions/setup-dotnet@v1 with: - dotnet-version: 5.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore From 51d44f588e3c06a6e4344e04683fc2b4ad80cb0b Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 17:04:03 +0100 Subject: [PATCH 08/14] remove sonar --- .github/workflows/build.yml | 120 +++++++++++------------------------- 1 file changed, 37 insertions(+), 83 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25312d4..41b74cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,111 +2,65 @@ name: Build and Test on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: - build-and-test: name: Build and test strategy: matrix: - os: [ubuntu-latest] - #os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest] + #os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: + - uses: actions/checkout@v2 - - uses: actions/checkout@v2 - - - name: Setup .NET Core 9.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 9.0.x - - - name: Restore dependencies - run: dotnet restore + - name: Setup .NET Core 9.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 9.0.x - - name: Build - run: dotnet build --no-restore + - name: Restore dependencies + run: dotnet restore - - name: Test with dotnet - run: dotnet test --no-restore --verbosity normal --logger trx --results-directory "TestResults-${{ matrix.os }}" + - name: Build + run: dotnet build --no-restore - - name: Upload dotnet test results - uses: actions/upload-artifact@v4 - with: - name: dotnet-results-${{ matrix.os }} - path: TestResults-${{ matrix.os }} - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} - - - sonar: - runs-on: windows-latest - needs: build-and-test - steps: + - name: Test with dotnet + run: dotnet test --no-restore --verbosity normal --logger trx --results-directory "TestResults-${{ matrix.os }}" - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 1.11 - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v1 - with: - path: .\.sonar\scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install SonarCloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: powershell - run: | - New-Item -Path .\.sonar\scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner - - name: Build and analyze - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: powershell - run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"keesschollaart81_SortCS" /o:"keesschollaart81" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - dotnet build - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + - name: Upload dotnet test results + uses: actions/upload-artifact@v4 + with: + name: dotnet-results-${{ matrix.os }} + path: TestResults-${{ matrix.os }} + # Use always() to always run this step to publish test results when there are test failures + if: ${{ always() }} deploy: runs-on: ubuntu-latest needs: build-and-test steps: + - uses: actions/checkout@v2 + + - name: Setup .NET Core 9.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 9.0.x - - uses: actions/checkout@v2 + - name: Restore dependencies + run: dotnet restore + working-directory: src/SortCS - - name: Setup .NET Core 9.0 - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 9.0.x - - - name: Restore dependencies - run: dotnet restore - working-directory: src/SortCS - - - name: Build - run: dotnet build -c Release --no-restore - working-directory: src/SortCS + - name: Build + run: dotnet build -c Release --no-restore + working-directory: src/SortCS - - name: Build - run: dotnet pack -c Release --no-restore - working-directory: src/SortCS - \ No newline at end of file + - name: Build + run: dotnet pack -c Release --no-restore + working-directory: src/SortCS From 720b5fc8d1667f5d456dad77bed57e19b29cd294 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 17:12:50 +0100 Subject: [PATCH 09/14] sonar-project.properties --- sonar-project.properties | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 sonar-project.properties diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..8f0d442 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,16 @@ +sonar.projectKey=keesschollaart81_SortCS +sonar.organization=keesschollaart81 + +# This is the name and version displayed in the SonarCloud UI. +sonar.projectName=SortCS +#sonar.projectVersion=1.0 + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 + +# Exclusions +sonar.exclusions=**/Benchmarks/**,**/obj/**,**/*.dll +sonar.coverage.exclusions=**/*.Tests.cs,**/*.Tests/** From 748364fbbb7c70129b01c0e8a3dd51491b137ac7 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 17:34:16 +0100 Subject: [PATCH 10/14] Cleaning --- SortCS.sln | 14 +- analyzers.targets | 8 +- src/SortCS.Evaluate/Program.cs | 107 +++--- src/SortCS.Evaluate/SortCsEvaluator.cs | 199 +++++----- src/SortCS.Tests/Frame.cs | 15 +- src/SortCS.Tests/SortTrackerTests.cs | 205 +++++----- src/SortCS/EnumerableExtensions.cs | 25 +- src/SortCS/ITracker.cs | 9 +- src/SortCS/Kalman/KalmanBoxTracker.cs | 184 +++++---- src/SortCS/Kalman/KalmanFilter.cs | 253 ++++++------- src/SortCS/Kalman/Matrix.cs | 500 ++++++++++++------------- src/SortCS/Kalman/Vector.cs | 132 ++++--- src/SortCS/SortTracker.cs | 261 +++++++------ src/SortCS/Track.cs | 19 +- src/SortCS/TrackState.cs | 15 +- 15 files changed, 962 insertions(+), 984 deletions(-) diff --git a/SortCS.sln b/SortCS.sln index 48abd52..30ecbf2 100644 --- a/SortCS.sln +++ b/SortCS.sln @@ -16,13 +16,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE = LICENSE README.md = README.md .github\workflows\release.yml = .github\workflows\release.yml + sonar-project.properties = sonar-project.properties EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SortCS.Tests", "src\SortCS.Tests\SortCS.Tests.csproj", "{13002E85-6B03-49DA-9700-906A24692C98}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DFA1CD64-077D-456F-B0B0-0568A3A22F1C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SortCS.Benchmarks", "src\SortCS.Benchmarks\SortCS.Benchmarks.csproj", "{8B4A7059-312F-4912-B2B3-383C25DA94DF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SortCS.Benchmarks", "src\SortCS.Benchmarks\SortCS.Benchmarks.csproj", "{9A2468E6-4556-4C7C-8D29-996200A10EF3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -42,10 +41,10 @@ Global {13002E85-6B03-49DA-9700-906A24692C98}.Debug|Any CPU.Build.0 = Debug|Any CPU {13002E85-6B03-49DA-9700-906A24692C98}.Release|Any CPU.ActiveCfg = Release|Any CPU {13002E85-6B03-49DA-9700-906A24692C98}.Release|Any CPU.Build.0 = Release|Any CPU - {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B4A7059-312F-4912-B2B3-383C25DA94DF}.Release|Any CPU.Build.0 = Release|Any CPU + {9A2468E6-4556-4C7C-8D29-996200A10EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A2468E6-4556-4C7C-8D29-996200A10EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A2468E6-4556-4C7C-8D29-996200A10EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A2468E6-4556-4C7C-8D29-996200A10EF3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -54,6 +53,5 @@ Global SolutionGuid = {8C31716C-280A-44DD-ADAB-E48CEDFBF779} EndGlobalSection GlobalSection(NestedProjects) = preSolution - {8B4A7059-312F-4912-B2B3-383C25DA94DF} = {DFA1CD64-077D-456F-B0B0-0568A3A22F1C} EndGlobalSection EndGlobal diff --git a/analyzers.targets b/analyzers.targets index 801eed6..fe85026 100644 --- a/analyzers.targets +++ b/analyzers.targets @@ -17,10 +17,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + all diff --git a/src/SortCS.Evaluate/Program.cs b/src/SortCS.Evaluate/Program.cs index 14f9550..785832e 100644 --- a/src/SortCS.Evaluate/Program.cs +++ b/src/SortCS.Evaluate/Program.cs @@ -8,68 +8,67 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace SortCS.Evaluate +namespace SortCS.Evaluate; + +class Program { - class Program - { - private static ILogger _logger; + private static ILogger _logger; - static async Task Main(string[] args) + static async Task Main(string[] args) + { + var services = new ServiceCollection(); + services.AddLogging(loggerBuilder => { - var services = new ServiceCollection(); - services.AddLogging(loggerBuilder => - { - loggerBuilder.ClearProviders(); - loggerBuilder.AddConsole(); - }); - var serviceProvider = services.BuildServiceProvider(); + loggerBuilder.ClearProviders(); + loggerBuilder.AddConsole(); + }); + var serviceProvider = services.BuildServiceProvider(); - _logger = serviceProvider.GetService>(); + _logger = serviceProvider.GetService>(); - var rootCommand = new RootCommand - { - new Option( - "--data-folder", - getDefaultValue: () => new DirectoryInfo(@"../../../../../../TrackEval/data"), // Sssuming SortCS/src/SortCS.Evaluate/bin/debug/net5.0 is working directory - description: "Location where data is stored using this format: https://github.com/JonathonLuiten/TrackEval/blob/master/docs/MOTChallenge-format.txt"), - new Option( - "--benchmark", - getDefaultValue: () => "MOT20", - description: "Name of the benchmark, e.g. MOT15, MO16, MOT17 or MOT20 (default : MOT20)"), - new Option( - "--split-to-eval", - getDefaultValue: () => "train", - description: "Data split on which to evalute e.g. train, test (default : train)"), - }; + var rootCommand = new RootCommand + { + new Option( + "--data-folder", + getDefaultValue: () => new DirectoryInfo(@"../../../../../../TrackEval/data"), // Sssuming SortCS/src/SortCS.Evaluate/bin/debug/net5.0 is working directory + description: "Location where data is stored using this format: https://github.com/JonathonLuiten/TrackEval/blob/master/docs/MOTChallenge-format.txt"), + new Option( + "--benchmark", + getDefaultValue: () => "MOT20", + description: "Name of the benchmark, e.g. MOT15, MO16, MOT17 or MOT20 (default : MOT20)"), + new Option( + "--split-to-eval", + getDefaultValue: () => "train", + description: "Data split on which to evalute e.g. train, test (default : train)"), + }; - rootCommand.Description = "App to evaluate the SortCS tracking algoritm"; - rootCommand.Handler = CommandHandler.Create(async (dataFolder, benchmark, splitToEval) => + rootCommand.Description = "App to evaluate the SortCS tracking algoritm"; + rootCommand.Handler = CommandHandler.Create(async (dataFolder, benchmark, splitToEval) => + { + if (!dataFolder.Exists || !dataFolder.GetDirectories().Any()) { - if (!dataFolder.Exists || !dataFolder.GetDirectories().Any()) - { - await DownloadTrackEvalExampleAsync(dataFolder); - } - var sortCsEvaluator = new SortCsEvaluator(dataFolder, benchmark, splitToEval, serviceProvider.GetService>()); - await sortCsEvaluator.EvaluateAsync(); - }); + await DownloadTrackEvalExampleAsync(dataFolder); + } + var sortCsEvaluator = new SortCsEvaluator(dataFolder, benchmark, splitToEval, serviceProvider.GetService>()); + await sortCsEvaluator.EvaluateAsync(); + }); - return await rootCommand.InvokeAsync(args); - } + return await rootCommand.InvokeAsync(args); + } - private static async Task DownloadTrackEvalExampleAsync(DirectoryInfo groundTruthFolder) - { - var dataZipUrl = "https://omnomnom.vision.rwth-aachen.de/data/TrackEval/data.zip"; - groundTruthFolder.Create(); - var targetZipFile = Path.Combine(groundTruthFolder.ToString(), "..", "data.zip"); - _logger.LogInformation(Path.GetFullPath(targetZipFile)); + private static async Task DownloadTrackEvalExampleAsync(DirectoryInfo groundTruthFolder) + { + var dataZipUrl = "https://omnomnom.vision.rwth-aachen.de/data/TrackEval/data.zip"; + groundTruthFolder.Create(); + var targetZipFile = Path.Combine(groundTruthFolder.ToString(), "..", "data.zip"); + _logger.LogInformation(Path.GetFullPath(targetZipFile)); - _logger.LogInformation($"Downloading data.zip (150mb) from {dataZipUrl} to {targetZipFile}"); - using var httpClient = new HttpClient(); - var zipStream = await httpClient.GetStreamAsync(dataZipUrl); - using var fs = new FileStream(targetZipFile, FileMode.CreateNew); - await zipStream.CopyToAsync(fs); - ZipFile.ExtractToDirectory(targetZipFile, Path.Combine(groundTruthFolder.ToString(), "..")); - _logger.LogInformation("data.zip downloaded & extracted"); - } + _logger.LogInformation($"Downloading data.zip (150mb) from {dataZipUrl} to {targetZipFile}"); + using var httpClient = new HttpClient(); + var zipStream = await httpClient.GetStreamAsync(dataZipUrl); + using var fs = new FileStream(targetZipFile, FileMode.CreateNew); + await zipStream.CopyToAsync(fs); + ZipFile.ExtractToDirectory(targetZipFile, Path.Combine(groundTruthFolder.ToString(), "..")); + _logger.LogInformation("data.zip downloaded & extracted"); } -} +} \ No newline at end of file diff --git a/src/SortCS.Evaluate/SortCsEvaluator.cs b/src/SortCS.Evaluate/SortCsEvaluator.cs index b787190..987dd12 100644 --- a/src/SortCS.Evaluate/SortCsEvaluator.cs +++ b/src/SortCS.Evaluate/SortCsEvaluator.cs @@ -9,134 +9,133 @@ using Microsoft.Extensions.Logging; using System.Diagnostics; -namespace SortCS.Evaluate +namespace SortCS.Evaluate; + +public class SortCsEvaluator { - public class SortCsEvaluator - { - private readonly DirectoryInfo _dataFolderMot; + private readonly DirectoryInfo _dataFolderMot; - private readonly DirectoryInfo _destinationDir; - private readonly ILogger _logger; + private readonly DirectoryInfo _destinationDir; + private readonly ILogger _logger; - public SortCsEvaluator(DirectoryInfo dataFolder, string benchmark, string splitToEval, ILogger logger) + public SortCsEvaluator(DirectoryInfo dataFolder, string benchmark, string splitToEval, ILogger logger) + { + _dataFolderMot = new DirectoryInfo(Path.Combine($"{dataFolder}", "gt", "mot_challenge", $"{benchmark}-{splitToEval}")); + _destinationDir = new DirectoryInfo(Path.Combine($"{dataFolder}", "trackers", "mot_challenge", $"{benchmark}-{splitToEval}", "SortCS", "data")); + if (_destinationDir.Exists) { - _dataFolderMot = new DirectoryInfo(Path.Combine($"{dataFolder}", "gt", "mot_challenge", $"{benchmark}-{splitToEval}")); - _destinationDir = new DirectoryInfo(Path.Combine($"{dataFolder}", "trackers", "mot_challenge", $"{benchmark}-{splitToEval}", "SortCS", "data")); - if (_destinationDir.Exists) - { - _destinationDir.Delete(true); - } - _destinationDir.Create(); - _logger = logger; + _destinationDir.Delete(true); } + _destinationDir.Create(); + _logger = logger; + } - public async Task EvaluateAsync() + public async Task EvaluateAsync() + { + var stopwatch = Stopwatch.StartNew(); + var tasks = new List>(); + foreach (var benchmarkDir in _dataFolderMot.GetDirectories()) { - var stopwatch = Stopwatch.StartNew(); - var tasks = new List>(); - foreach (var benchmarkDir in _dataFolderMot.GetDirectories()) - { - tasks.Add(EvaluateBenchMark(benchmarkDir)); - } - await Task.WhenAll(tasks); - stopwatch.Stop(); - var totalFrames = tasks.Sum(x => x.Result); - _logger.LogInformation("Finished evaluating {totalFrames} frames in {totalSeconds:0.} seconds ({fps:0.0} fps)", totalFrames, stopwatch.Elapsed.TotalSeconds, totalFrames / stopwatch.Elapsed.TotalSeconds); + tasks.Add(EvaluateBenchMark(benchmarkDir)); } + await Task.WhenAll(tasks); + stopwatch.Stop(); + var totalFrames = tasks.Sum(x => x.Result); + _logger.LogInformation("Finished evaluating {totalFrames} frames in {totalSeconds:0.} seconds ({fps:0.0} fps)", totalFrames, stopwatch.Elapsed.TotalSeconds, totalFrames / stopwatch.Elapsed.TotalSeconds); + } - private async Task EvaluateBenchMark(DirectoryInfo benchmarkFolder) + private async Task EvaluateBenchMark(DirectoryInfo benchmarkFolder) + { + try { - try - { - var detFile = new FileInfo(Path.Combine($"{benchmarkFolder}", "gt", "gt.txt")); - var sequenceIniFile = new FileInfo(Path.Combine($"{benchmarkFolder}", "seqinfo.ini")); + var detFile = new FileInfo(Path.Combine($"{benchmarkFolder}", "gt", "gt.txt")); + var sequenceIniFile = new FileInfo(Path.Combine($"{benchmarkFolder}", "seqinfo.ini")); + if (!detFile.Exists) + { + detFile = new FileInfo(Path.Combine($"{benchmarkFolder}", "det", "det.txt")); if (!detFile.Exists) { - detFile = new FileInfo(Path.Combine($"{benchmarkFolder}", "det", "det.txt")); - if (!detFile.Exists) - { - _logger.LogWarning("Benchmark folder {benchmarkFolder} has no GroundTruth file (gt/gt.txt)", benchmarkFolder); - return 0; - } + _logger.LogWarning("Benchmark folder {benchmarkFolder} has no GroundTruth file (gt/gt.txt)", benchmarkFolder); + return 0; } + } - var benchmarkKey = GetBenchmarkKeyFromSeqIni(sequenceIniFile); - var frames = await GetFramesFromFile(detFile); + var benchmarkKey = GetBenchmarkKeyFromSeqIni(sequenceIniFile); + var frames = await GetFramesFromFile(detFile); - var path = Path.Combine(_destinationDir.ToString(), $"{benchmarkKey}.txt"); - _logger.LogInformation("Read {framesCount} frames, output to {outputFile}", frames.Count, path); + var path = Path.Combine(_destinationDir.ToString(), $"{benchmarkKey}.txt"); + _logger.LogInformation("Read {framesCount} frames, output to {outputFile}", frames.Count, path); - await FramesToDetectionsFile(frames, path); + await FramesToDetectionsFile(frames, path); - return frames.Count; - } - catch (Exception ex) - { - _logger.LogError(ex, "Exception evaluating benchmark {benchmarkFolder}: {ex.Message}", benchmarkFolder, ex.Message); - throw; - } + return frames.Count; } - - private async Task FramesToDetectionsFile(Dictionary> frames, string path) + catch (Exception ex) { - using var file = new StreamWriter(path, false); - - ITracker tracker = new SortTracker(_logger); - foreach (var frame in frames) - { - var tracks = tracker.Track(frame.Value); - foreach (var track in tracks) - { - if (track.State == TrackState.Started || track.State == TrackState.Active) - { - //var boxForLog = track.History.Last(); - var boxForLog = track.Prediction; - //, , , , , , , , , - var line = $"{frame.Key:0.},{track.TrackId:0.},{boxForLog.Left:0.},{boxForLog.Top:0.},{boxForLog.Width:0.},{boxForLog.Height:0.},1,-1,-1,-1"; - await file.WriteLineAsync(line); - } - } - } + _logger.LogError(ex, "Exception evaluating benchmark {benchmarkFolder}: {ex.Message}", benchmarkFolder, ex.Message); + throw; } + } - private static string GetBenchmarkKeyFromSeqIni(FileInfo sequenceIniFile) - { - var iniString = File.ReadAllText(sequenceIniFile.FullName); - var parser = new IniDataParser(); - var data = parser.Parse(iniString); - var benchmarkKey = data["Sequence"]["name"]; - return benchmarkKey; - } + private async Task FramesToDetectionsFile(Dictionary> frames, string path) + { + using var file = new StreamWriter(path, false); - private static async Task>> GetFramesFromFile(FileInfo detFile) + ITracker tracker = new SortTracker(_logger); + foreach (var frame in frames) { - // GT file format (no header): , , , , , , , , , - var lines = await File.ReadAllLinesAsync(detFile.FullName); - - var frames = new Dictionary>(); - var numberInfo = new NumberFormatInfo() { NumberDecimalSeparator = "." }; - foreach (var line in lines) + var tracks = tracker.Track(frame.Value); + foreach (var track in tracks) { - var parts = line.Split(','); - var frameId = int.Parse(parts[0]); - var gtTrackId = int.Parse(parts[1]); - var bbLeft = float.Parse(parts[2], numberInfo); - var bbTop = float.Parse(parts[3], numberInfo); - var bbWidth = float.Parse(parts[4], numberInfo); - var bbHeight = float.Parse(parts[5], numberInfo); - var bbConf = float.Parse(parts[6], numberInfo); - if (!frames.ContainsKey(frameId)) - { - frames.Add(frameId, new List()); - } - if (bbConf > 0) + if (track.State == TrackState.Started || track.State == TrackState.Active) { - frames[frameId].Add(new RectangleF(bbLeft, bbTop, bbWidth, bbHeight)); + //var boxForLog = track.History.Last(); + var boxForLog = track.Prediction; + //, , , , , , , , , + var line = $"{frame.Key:0.},{track.TrackId:0.},{boxForLog.Left:0.},{boxForLog.Top:0.},{boxForLog.Width:0.},{boxForLog.Height:0.},1,-1,-1,-1"; + await file.WriteLineAsync(line); } } + } + } + + private static string GetBenchmarkKeyFromSeqIni(FileInfo sequenceIniFile) + { + var iniString = File.ReadAllText(sequenceIniFile.FullName); + var parser = new IniDataParser(); + var data = parser.Parse(iniString); + var benchmarkKey = data["Sequence"]["name"]; + return benchmarkKey; + } - return frames; + private static async Task>> GetFramesFromFile(FileInfo detFile) + { + // GT file format (no header): , , , , , , , , , + var lines = await File.ReadAllLinesAsync(detFile.FullName); + + var frames = new Dictionary>(); + var numberInfo = new NumberFormatInfo() { NumberDecimalSeparator = "." }; + foreach (var line in lines) + { + var parts = line.Split(','); + var frameId = int.Parse(parts[0]); + var gtTrackId = int.Parse(parts[1]); + var bbLeft = float.Parse(parts[2], numberInfo); + var bbTop = float.Parse(parts[3], numberInfo); + var bbWidth = float.Parse(parts[4], numberInfo); + var bbHeight = float.Parse(parts[5], numberInfo); + var bbConf = float.Parse(parts[6], numberInfo); + if (!frames.ContainsKey(frameId)) + { + frames.Add(frameId, new List()); + } + if (bbConf > 0) + { + frames[frameId].Add(new RectangleF(bbLeft, bbTop, bbWidth, bbHeight)); + } } + + return frames; } -} +} \ No newline at end of file diff --git a/src/SortCS.Tests/Frame.cs b/src/SortCS.Tests/Frame.cs index 5a01c7d..f3fbcd5 100644 --- a/src/SortCS.Tests/Frame.cs +++ b/src/SortCS.Tests/Frame.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; using System.Drawing; -namespace SortCS.Tests +namespace SortCS.Tests; + +public class Frame { - public class Frame + public Frame(List boundingBoxes) { - public Frame(List boundingBoxes) - { - BoundingBoxes = boundingBoxes; - } - public List BoundingBoxes { get; set; } + BoundingBoxes = boundingBoxes; } -} + public List BoundingBoxes { get; set; } +} \ No newline at end of file diff --git a/src/SortCS.Tests/SortTrackerTests.cs b/src/SortCS.Tests/SortTrackerTests.cs index b7a19a7..0bfb7b0 100644 --- a/src/SortCS.Tests/SortTrackerTests.cs +++ b/src/SortCS.Tests/SortTrackerTests.cs @@ -3,119 +3,118 @@ using System.Drawing; using System.Linq; -namespace SortCS.Tests +namespace SortCS.Tests; + +[TestClass] +public class SortTrackerTests { - [TestClass] - public class SortTrackerTests + [TestMethod] + public void SortTracker_FourEasyTracks_TrackedToEnd() { - [TestMethod] - public void SortTracker_FourEasyTracks_TrackedToEnd() - { - // Arrange - var mot15Track = new List{ - new Frame(new List{ - new RectangleF(1703,385,157,339), - new RectangleF(1293,455,83,213), - new RectangleF(259,449,101,261), - new RectangleF(1253,529,55,127) - }), - new Frame(new List{ - new RectangleF(1699,383,159,341), - new RectangleF(1293,455,83,213), - new RectangleF(261,447,101,263), - new RectangleF(1253,529,55,127) - }), - new Frame(new List{ - new RectangleF(1697,383,159,343), - new RectangleF(1293,455,83,213), - new RectangleF(263,447,101,263), - new RectangleF(1255,529,55,127), - new RectangleF(429,300,55,127) - }), - new Frame(new List{ - new RectangleF(1695,383,159,343), - new RectangleF(1293,455,83,213), - new RectangleF(265,447,101,263), - new RectangleF(1257,529,55,127) - }), - new Frame(new List{ - new RectangleF(1693,381,159,347), - new RectangleF(1295,455,83,213), - new RectangleF(267,447,101,263), - new RectangleF(1259, 529,55,129) - }), - }; + // Arrange + var mot15Track = new List{ + new(new List{ + new(1703,385,157,339), + new(1293,455,83,213), + new(259,449,101,261), + new(1253,529,55,127) + }), + new(new List{ + new(1699,383,159,341), + new(1293,455,83,213), + new(261,447,101,263), + new(1253,529,55,127) + }), + new(new List{ + new(1697,383,159,343), + new(1293,455,83,213), + new(263,447,101,263), + new(1255,529,55,127), + new(429,300,55,127) + }), + new(new List{ + new(1695,383,159,343), + new(1293,455,83,213), + new(265,447,101,263), + new(1257,529,55,127) + }), + new(new List{ + new(1693,381,159,347), + new(1295,455,83,213), + new(267,447,101,263), + new(1259, 529,55,129) + }), + }; - var tracks = Enumerable.Empty(); - var sut = new SortTracker(); + var tracks = Enumerable.Empty(); + var sut = new SortTracker(); - // Act - foreach (var frame in mot15Track) - { - // ToArray because otherwise the IEnumerable is not evaluated. - tracks = sut.Track(frame.BoundingBoxes).ToArray(); - } - - // Assert - Assert.AreEqual(4, tracks.Where(x => x.State == TrackState.Active).Count()); + // Act + foreach (var frame in mot15Track) + { + // ToArray because otherwise the IEnumerable is not evaluated. + tracks = sut.Track(frame.BoundingBoxes).ToArray(); } - [TestMethod] - public void SortTracker_CrossingTracks_EndInCorrectEndLocation() - { - // Arrange - var crossingTrack = new List{ - new Frame(new List{ - new RectangleF(0.8f, 0.3f, 0.1f, 0.1f), - new RectangleF(0.1f, 0.1f, 0.15f, 0.15f) - }), - new Frame(new List{ - new RectangleF(0.8f, 0.35f, 0.1f, 0.1f), - new RectangleF(0.2f, 0.2f, 0.15f, 0.15f) - }), - new Frame(new List{ - new RectangleF(0.3f, 0.3f, 0.15f, 0.15f), - new RectangleF(0.8f, 0.4f, 0.1f, 0.1f) - }), - new Frame(new List{ - new RectangleF(0.4f, 0.4f, 0.15f, 0.15f), - new RectangleF(0.8f, 0.45f, 0.1f, 0.1f) - }), - new Frame(new List{ - new RectangleF(0.5f, 0.5f, 0.15f, 0.15f), - new RectangleF(0.8f, 0.5f, 0.1f, 0.1f) - }), - new Frame(new List()), - new Frame(new List()), - new Frame(new List()), - new Frame(new List()), - new Frame(new List()) - }; - var tracks = Enumerable.Empty(); + // Assert + Assert.AreEqual(4, tracks.Where(x => x.State == TrackState.Active).Count()); + } + + [TestMethod] + public void SortTracker_CrossingTracks_EndInCorrectEndLocation() + { + // Arrange + var crossingTrack = new List{ + new(new List{ + new(0.8f, 0.3f, 0.1f, 0.1f), + new(0.1f, 0.1f, 0.15f, 0.15f) + }), + new(new List{ + new(0.8f, 0.35f, 0.1f, 0.1f), + new(0.2f, 0.2f, 0.15f, 0.15f) + }), + new(new List{ + new(0.3f, 0.3f, 0.15f, 0.15f), + new(0.8f, 0.4f, 0.1f, 0.1f) + }), + new(new List{ + new(0.4f, 0.4f, 0.15f, 0.15f), + new(0.8f, 0.45f, 0.1f, 0.1f) + }), + new(new List{ + new(0.5f, 0.5f, 0.15f, 0.15f), + new(0.8f, 0.5f, 0.1f, 0.1f) + }), + new(new List()), + new(new List()), + new(new List()), + new(new List()), + new(new List()) + }; + var tracks = Enumerable.Empty(); - var sut = new SortTracker(0.2f); + var sut = new SortTracker(0.2f); - // Act - foreach (var frame in crossingTrack) + // Act + foreach (var frame in crossingTrack) + { + var result = sut.Track(frame.BoundingBoxes).ToArray(); + if (result.Any()) { - var result = sut.Track(frame.BoundingBoxes).ToArray(); - if (result.Any()) - { - tracks = result; - } + tracks = result; } + } - var complexTrack1 = tracks.ElementAt(0); - var complexTrack2 = tracks.ElementAt(1); - var firstBoxOfTrack2 = complexTrack2.History.FirstOrDefault(); - var lastBoxOfTrack2 = complexTrack2.History.LastOrDefault(); + var complexTrack1 = tracks.ElementAt(0); + var complexTrack2 = tracks.ElementAt(1); + var firstBoxOfTrack2 = complexTrack2.History.FirstOrDefault(); + var lastBoxOfTrack2 = complexTrack2.History.LastOrDefault(); - // Assert - Assert.AreEqual(TrackState.Ended, complexTrack1.State); - Assert.AreEqual(TrackState.Ended, complexTrack2.State); - Assert.AreEqual(0.5, lastBoxOfTrack2.Top, 0.00); - Assert.AreEqual(5, complexTrack1.History.Count); - Assert.AreEqual(5, complexTrack2.History.Count); - } + // Assert + Assert.AreEqual(TrackState.Ended, complexTrack1.State); + Assert.AreEqual(TrackState.Ended, complexTrack2.State); + Assert.AreEqual(0.5, lastBoxOfTrack2.Top, 0.00); + Assert.AreEqual(5, complexTrack1.History.Count); + Assert.AreEqual(5, complexTrack2.History.Count); } -} +} \ No newline at end of file diff --git a/src/SortCS/EnumerableExtensions.cs b/src/SortCS/EnumerableExtensions.cs index 3783fe7..eeb9fba 100644 --- a/src/SortCS/EnumerableExtensions.cs +++ b/src/SortCS/EnumerableExtensions.cs @@ -1,21 +1,20 @@ using System.Collections.Generic; using System.Linq; -namespace SortCS +namespace SortCS; + +internal static class EnumerableExtensions { - internal static class EnumerableExtensions + public static T[,] ToArray(this IEnumerable source, int firstDimensionLength, int secondDimensionLength) { - public static T[,] ToArray(this IEnumerable source, int firstDimensionLength, int secondDimensionLength) - { - var array = source as T[] ?? source.ToArray(); - var result = new T[firstDimensionLength, secondDimensionLength]; - - for (var i = 0; i < array.Length; i++) - { - result[i / secondDimensionLength, i % secondDimensionLength] = array[i]; - } + var array = source as T[] ?? source.ToArray(); + var result = new T[firstDimensionLength, secondDimensionLength]; - return result; + for (var i = 0; i < array.Length; i++) + { + result[i / secondDimensionLength, i % secondDimensionLength] = array[i]; } + + return result; } -} +} \ No newline at end of file diff --git a/src/SortCS/ITracker.cs b/src/SortCS/ITracker.cs index d47afb9..267d5ec 100644 --- a/src/SortCS/ITracker.cs +++ b/src/SortCS/ITracker.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; using System.Drawing; -namespace SortCS +namespace SortCS; + +public interface ITracker { - public interface ITracker - { - IEnumerable Track(IEnumerable boxes); - } + IEnumerable Track(IEnumerable boxes); } \ No newline at end of file diff --git a/src/SortCS/Kalman/KalmanBoxTracker.cs b/src/SortCS/Kalman/KalmanBoxTracker.cs index 0e69f79..88191ab 100644 --- a/src/SortCS/Kalman/KalmanBoxTracker.cs +++ b/src/SortCS/Kalman/KalmanBoxTracker.cs @@ -1,114 +1,112 @@ using System; -using System.Collections.Generic; using System.Drawing; -namespace SortCS.Kalman +namespace SortCS.Kalman; + +internal class KalmanBoxTracker { - internal class KalmanBoxTracker - { - private static Matrix _stateTransitioningMatrix = new Matrix( - new double[,] - { - { 1, 0, 0, 0, 1, 0, 0 }, - { 0, 1, 0, 0, 0, 1, 0 }, - { 0, 0, 1, 0, 0, 0, 1 }, - { 0, 0, 0, 1, 0, 0, 0 }, - { 0, 0, 0, 0, 1, 0, 0 }, - { 0, 0, 0, 0, 0, 1, 0 }, - { 0, 0, 0, 0, 0, 0, 1 } - }); - - private static Matrix _measurementFunction = new Matrix( - new double[,] - { - { 1, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0 }, - { 0, 0, 1, 0, 0, 0, 0 }, - { 0, 0, 0, 1, 0, 0, 0 } - }); - - private static Matrix _uncertaintyCovariances = new Matrix( - new double[,] - { - { 10, 0, 0, 0, 0, 0, 0 }, - { 0, 10, 0, 0, 0, 0, 0 }, - { 0, 0, 10, 0, 0, 0, 0 }, - { 0, 0, 0, 10, 0, 0, 0 }, - { 0, 0, 0, 0, 10000, 0, 0 }, - { 0, 0, 0, 0, 0, 10000, 0 }, - { 0, 0, 0, 0, 0, 0, 10000 } - }); - - private static Matrix _measurementUncertainty = new Matrix(new double[,] + private static readonly Matrix _stateTransitioningMatrix = new( + new double[,] { - { 1, 0, 0, 0 }, - { 0, 1, 0, 0 }, - { 0, 0, 10, 0 }, - { 0, 0, 0, 10 }, + { 1, 0, 0, 0, 1, 0, 0 }, + { 0, 1, 0, 0, 0, 1, 0 }, + { 0, 0, 1, 0, 0, 0, 1 }, + { 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0, 1, 0 }, + { 0, 0, 0, 0, 0, 0, 1 } }); - private static Matrix _processUncertainty = new Matrix( - new double[,] - { - { 1, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 0 }, - { 0, 0, 1, 0, 0, 0, 0 }, - { 0, 0, 0, 1, 0, 0, 0 }, - { 0, 0, 0, 0, .01, 0, 0 }, - { 0, 0, 0, 0, 0, .01, 0 }, - { 0, 0, 0, 0, 0, 0, .0001 } - }); - - private readonly KalmanFilter _filter; - - public KalmanBoxTracker(RectangleF box) + private static readonly Matrix _measurementFunction = new( + new double[,] { - _filter = new KalmanFilter(7, 4) - { - StateTransitionMatrix = _stateTransitioningMatrix, - MeasurementFunction = _measurementFunction, - UncertaintyCovariances = _uncertaintyCovariances, - MeasurementUncertainty = _measurementUncertainty, - ProcessUncertainty = _processUncertainty, - CurrentState = ToMeasurement(box).Append(0, 0, 0) - }; - } + { 1, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 0, 0, 0 } + }); - public void Update(RectangleF box) + private static readonly Matrix _uncertaintyCovariances = new( + new double[,] { - _filter.Update(ToMeasurement(box)); - } + { 10, 0, 0, 0, 0, 0, 0 }, + { 0, 10, 0, 0, 0, 0, 0 }, + { 0, 0, 10, 0, 0, 0, 0 }, + { 0, 0, 0, 10, 0, 0, 0 }, + { 0, 0, 0, 0, 10000, 0, 0 }, + { 0, 0, 0, 0, 0, 10000, 0 }, + { 0, 0, 0, 0, 0, 0, 10000 } + }); + + private static readonly Matrix _measurementUncertainty = new(new double[,] + { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 10, 0 }, + { 0, 0, 0, 10 }, + }); - public RectangleF Predict() + private static readonly Matrix _processUncertainty = new( + new[,] { - if (_filter.CurrentState[6] + _filter.CurrentState[2] <= 0) - { - _filter.SetState(6, 0); - } + { 1, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0, 0, 0 }, + { 0, 0, 0, 1, 0, 0, 0 }, + { 0, 0, 0, 0, .01, 0, 0 }, + { 0, 0, 0, 0, 0, .01, 0 }, + { 0, 0, 0, 0, 0, 0, .0001 } + }); - _filter.Predict(); + private readonly KalmanFilter _filter; - var prediction = ToBoundingBox(_filter.CurrentState); + public KalmanBoxTracker(RectangleF box) + { + _filter = new KalmanFilter(7, 4) + { + StateTransitionMatrix = _stateTransitioningMatrix, + MeasurementFunction = _measurementFunction, + UncertaintyCovariances = _uncertaintyCovariances, + MeasurementUncertainty = _measurementUncertainty, + ProcessUncertainty = _processUncertainty, + CurrentState = ToMeasurement(box).Append(0, 0, 0) + }; + } - return prediction; - } + public void Update(RectangleF box) + { + _filter.Update(ToMeasurement(box)); + } - private static Vector ToMeasurement(RectangleF box) + public RectangleF Predict() + { + if (_filter.CurrentState[6] + _filter.CurrentState[2] <= 0) { - var center = new PointF(box.Left + (box.Width / 2f), box.Top + (box.Height / 2f)); - return new Vector(center.X, center.Y, box.Width * (double)box.Height, box.Width / (double)box.Height); + _filter.SetState(6, 0); } - private static RectangleF ToBoundingBox(Vector currentState) - { - var w = Math.Sqrt(currentState[2] * currentState[3]); - var h = currentState[2] / w; - - return new RectangleF( - (float)(currentState[0] - (w / 2)), - (float)(currentState[1] - (h / 2)), - (float)w, - (float)h); - } + _filter.Predict(); + + var prediction = ToBoundingBox(_filter.CurrentState); + + return prediction; + } + + private static Vector ToMeasurement(RectangleF box) + { + var center = new PointF(box.Left + (box.Width / 2f), box.Top + (box.Height / 2f)); + return new Vector(center.X, center.Y, box.Width * (double)box.Height, box.Width / (double)box.Height); + } + + private static RectangleF ToBoundingBox(Vector currentState) + { + var w = Math.Sqrt(currentState[2] * currentState[3]); + var h = currentState[2] / w; + + return new RectangleF( + (float)(currentState[0] - (w / 2)), + (float)(currentState[1] - (h / 2)), + (float)w, + (float)h); } } \ No newline at end of file diff --git a/src/SortCS/Kalman/KalmanFilter.cs b/src/SortCS/Kalman/KalmanFilter.cs index 2db973c..50689d3 100644 --- a/src/SortCS/Kalman/KalmanFilter.cs +++ b/src/SortCS/Kalman/KalmanFilter.cs @@ -1,136 +1,131 @@ using System; using System.Diagnostics.CodeAnalysis; -namespace SortCS.Kalman +namespace SortCS.Kalman; + +[SuppressMessage("Major Code Smell", "S3928:Parameter names used into ArgumentException constructors should match an existing one ", + Justification = "Properties throw ArgumentException for 'value'")] +internal class KalmanFilter { - [SuppressMessage("Major Code Smell", "S3928:Parameter names used into ArgumentException constructors should match an existing one ", - Justification = "Properties throw ArgumentException for 'value'")] - internal class KalmanFilter + private readonly int _stateSize; + private readonly int _measurementSize; + private readonly Matrix _identity; + private readonly double _alphaSq; + + private Vector _currentState; + private Matrix _uncertaintyCovariances; + private Matrix _pht; + private Matrix _s; + private Matrix _si; + private Matrix _k; + private Matrix _kh; + private Matrix _ikh; + + public KalmanFilter(int stateSize, int measurementSize) + { + _stateSize = stateSize; + _measurementSize = measurementSize; + _identity = Matrix.Identity(stateSize); + _alphaSq = 1.0d; + + StateTransitionMatrix = _identity; // F + MeasurementFunction = new Matrix(_measurementSize, _stateSize); // H + UncertaintyCovariances = Matrix.Identity(_stateSize); // P + MeasurementUncertainty = Matrix.Identity(_measurementSize); // R + ProcessUncertainty = _identity; // Q + CurrentState = new Vector(stateSize); + } + + /// + /// Gets or sets the current state. + /// + public Vector CurrentState + { + get => _currentState; + set => _currentState = value.Size == _stateSize + ? value + : throw new ArgumentException($"Vector must be of size {_stateSize}.", nameof(value)); + } + + /// + /// Gets the uncertainty covariances. + /// + public Matrix UncertaintyCovariances + { + get => _uncertaintyCovariances; + init => _uncertaintyCovariances = value.Rows == _stateSize && value.Columns == _stateSize + ? value + : throw new ArgumentException($"Matrix must be of size {_stateSize}x{_stateSize}.", nameof(value)); + } + + /// + /// Gets the process uncertainty. + /// + public Matrix ProcessUncertainty + { + get; + init => field = value.Rows == _stateSize && value.Columns == _stateSize + ? value + : throw new ArgumentException($"Matrix must be of size {_stateSize}x{_stateSize}.", nameof(value)); + } + + public Matrix MeasurementUncertainty + { + get; + init => field = value.Rows == _measurementSize && value.Columns == _measurementSize + ? value + : throw new ArgumentException($"Matrix must be of size {_measurementSize}x{_measurementSize}.", nameof(value)); + } + + /// + /// Gets the state transition matrix. + /// + public Matrix StateTransitionMatrix { - private readonly int _stateSize; - private readonly int _measurementSize; - private readonly Matrix _identity; - private readonly Matrix _processUncertainty; - private readonly Matrix _stateTransitionMatrix; - private readonly Matrix _measurementFunction; - private readonly Matrix _measurementUncertainty; - private readonly double _alphaSq; - - private Vector _currentState; - private Matrix _uncertaintyCovariances; - private Matrix _pht; - private Matrix _s; - private Matrix _si; - private Matrix _k; - private Matrix _kh; - private Matrix _ikh; - - public KalmanFilter(int stateSize, int measurementSize) - { - _stateSize = stateSize; - _measurementSize = measurementSize; - _identity = Matrix.Identity(stateSize); - _alphaSq = 1.0d; - - StateTransitionMatrix = _identity; // F - MeasurementFunction = new Matrix(_measurementSize, _stateSize); // H - UncertaintyCovariances = Matrix.Identity(_stateSize); // P - MeasurementUncertainty = Matrix.Identity(_measurementSize); // R - ProcessUncertainty = _identity; // Q - CurrentState = new Vector(stateSize); - } - - /// - /// Gets or sets the current state. - /// - public Vector CurrentState - { - get => _currentState; - set => _currentState = value.Size == _stateSize - ? value - : throw new ArgumentException($"Vector must be of size {_stateSize}.", nameof(value)); - } - - /// - /// Gets the uncertainty covariances. - /// - public Matrix UncertaintyCovariances - { - get => _uncertaintyCovariances; - init => _uncertaintyCovariances = value.Rows == _stateSize && value.Columns == _stateSize - ? value - : throw new ArgumentException($"Matrix must be of size {_stateSize}x{_stateSize}.", nameof(value)); - } - - /// - /// Gets the process uncertainty. - /// - public Matrix ProcessUncertainty - { - get => _processUncertainty; - init => _processUncertainty = value.Rows == _stateSize && value.Columns == _stateSize - ? value - : throw new ArgumentException($"Matrix must be of size {_stateSize}x{_stateSize}.", nameof(value)); - } - - public Matrix MeasurementUncertainty - { - get => _measurementUncertainty; - init => _measurementUncertainty = value.Rows == _measurementSize && value.Columns == _measurementSize - ? value - : throw new ArgumentException($"Matrix must be of size {_measurementSize}x{_measurementSize}.", nameof(value)); - } - - /// - /// Gets the state transition matrix. - /// - public Matrix StateTransitionMatrix - { - get => _stateTransitionMatrix; - init => _stateTransitionMatrix = value.Rows == _stateSize && value.Columns == _stateSize - ? value - : throw new ArgumentException($"Matrix must be of size {_stateSize}x{_stateSize}.", nameof(value)); - } - - /// - /// Gets the measurement function. - /// - public Matrix MeasurementFunction - { - get => _measurementFunction; - init => _measurementFunction = value.Rows == _measurementSize && value.Columns == _stateSize - ? value - : throw new ArgumentException($"Matrix must be of size {_measurementSize}x{_stateSize}.", nameof(value)); - } - - public void SetState(int index, double values) - { - _currentState[index] = values; - } - - public void Predict() - { - _currentState = StateTransitionMatrix.Dot(CurrentState); - _uncertaintyCovariances = (_alphaSq * StateTransitionMatrix * UncertaintyCovariances * StateTransitionMatrix.Transposed) + - ProcessUncertainty; - } - - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", - Justification = "These are well known abbreviations for the Kalman Filter")] - public void Update(Vector measurement) - { - _pht ??= UncertaintyCovariances * MeasurementFunction.Transposed; - _s ??= (MeasurementFunction * _pht) + MeasurementUncertainty; - _si ??= _s.Inverted; - _k ??= _pht * _si; - _kh ??= _k * MeasurementFunction; - _ikh ??= _identity - _kh; - - var y = measurement - MeasurementFunction.Dot(CurrentState); - - _currentState += _k.Dot(y); - - _uncertaintyCovariances = (_ikh * UncertaintyCovariances * _ikh.Transposed) + (_k * MeasurementUncertainty * _k.Transposed); - } + get; + init => field = value.Rows == _stateSize && value.Columns == _stateSize + ? value + : throw new ArgumentException($"Matrix must be of size {_stateSize}x{_stateSize}.", nameof(value)); + } + + /// + /// Gets the measurement function. + /// + public Matrix MeasurementFunction + { + get; + init => field = value.Rows == _measurementSize && value.Columns == _stateSize + ? value + : throw new ArgumentException($"Matrix must be of size {_measurementSize}x{_stateSize}.", nameof(value)); + } + + public void SetState(int index, double values) + { + _currentState[index] = values; + } + + public void Predict() + { + _currentState = StateTransitionMatrix.Dot(CurrentState); + _uncertaintyCovariances = (_alphaSq * StateTransitionMatrix * UncertaintyCovariances * StateTransitionMatrix.Transposed) + + ProcessUncertainty; + } + + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", + Justification = "These are well known abbreviations for the Kalman Filter")] + public void Update(Vector measurement) + { + _pht ??= UncertaintyCovariances * MeasurementFunction.Transposed; + _s ??= (MeasurementFunction * _pht) + MeasurementUncertainty; + _si ??= _s.Inverted; + _k ??= _pht * _si; + _kh ??= _k * MeasurementFunction; + _ikh ??= _identity - _kh; + + var y = measurement - MeasurementFunction.Dot(CurrentState); + + _currentState += _k.Dot(y); + + _uncertaintyCovariances = (_ikh * UncertaintyCovariances * _ikh.Transposed) + (_k * MeasurementUncertainty * _k.Transposed); } } \ No newline at end of file diff --git a/src/SortCS/Kalman/Matrix.cs b/src/SortCS/Kalman/Matrix.cs index f9b2dc4..999952d 100644 --- a/src/SortCS/Kalman/Matrix.cs +++ b/src/SortCS/Kalman/Matrix.cs @@ -3,372 +3,370 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Microsoft.VisualBasic; -namespace SortCS.Kalman +namespace SortCS.Kalman; + +[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] +[DebuggerTypeProxy(typeof(MatrixDisplay))] +internal class Matrix { - [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - [DebuggerTypeProxy(typeof(MatrixDisplay))] - internal class Matrix - { - private readonly double[,] _values; + private readonly double[,] _values; - private readonly Dictionary _rows = new(); - private readonly Dictionary _cols = new(); + private readonly Dictionary _rows = new(); + private readonly Dictionary _cols = new(); - public Matrix(double[,] values) - { - _values = values; - Rows = _values.GetLength(0); - Columns = _values.GetLength(1); - } + public Matrix(double[,] values) + { + _values = values; + Rows = _values.GetLength(0); + Columns = _values.GetLength(1); + } - public Matrix(int[,] values) - : this(values.GetLength(0), values.GetLength(1)) + public Matrix(int[,] values) + : this(values.GetLength(0), values.GetLength(1)) + { + for (var row = 0; row < Rows; row++) { - for (var row = 0; row < Rows; row++) + for (var col = 0; col < Columns; col++) { - for (var col = 0; col < Columns; col++) - { - _values[row, col] = (double)values[row, col]; - } + _values[row, col] = (double)values[row, col]; } } + } - public Matrix(int rows, int columns) - : this(new double[rows, columns]) - { - } + public Matrix(int rows, int columns) + : this(new double[rows, columns]) + { + } - public int Rows { get; } + public int Rows { get; } - public int Columns { get; } + public int Columns { get; } - public Matrix Transposed + public Matrix Transposed + { + get { - get + if (field != null) { - if (field != null) - { - return field; - } + return field; + } - var result = new double[Columns, Rows]; + var result = new double[Columns, Rows]; - for (var row = 0; row < Rows; row++) + for (var row = 0; row < Rows; row++) + { + for (var col = 0; col < Columns; col++) { - for (var col = 0; col < Columns; col++) - { - result[col, row] = _values[row, col]; - } + result[col, row] = _values[row, col]; } + } - field = new Matrix(result); + field = new Matrix(result); - return field; - } + return field; } + } - public Matrix Inverted + public Matrix Inverted + { + get { - get - { - Debug.Assert(Rows == Columns, "Matrix must be square."); + Debug.Assert(Rows == Columns, "Matrix must be square."); - var (lu, indices, d) = GetDecomposition(); - var result = new double[Rows, Columns]; + var (lu, indices, d) = GetDecomposition(); + var result = new double[Rows, Columns]; - for (var col = 0; col < Columns; col++) - { - var column = new double[Columns]; + for (var col = 0; col < Columns; col++) + { + var column = new double[Columns]; - column[col] = 1.0d; + column[col] = 1.0d; - var x = BackSubstition(lu, indices, column); + var x = BackSubstition(lu, indices, column); - for (var row = 0; row < Rows; row++) - { - result[row, col] = x[row]; - } + for (var row = 0; row < Rows; row++) + { + result[row, col] = x[row]; } - - return new Matrix(result); } + + return new Matrix(result); } + } - private string DebuggerDisplay => ToString(); + private string DebuggerDisplay => ToString(); - public static Matrix operator +(Matrix first, Matrix second) - { - Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns, "Matrices must have the same size."); + public static Matrix operator +(Matrix first, Matrix second) + { + Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns, "Matrices must have the same size."); - var result = new double[first.Rows, first.Columns]; + var result = new double[first.Rows, first.Columns]; - for (var row = 0; row < first.Rows; row++) + for (var row = 0; row < first.Rows; row++) + { + for (var col = 0; col < first.Columns; col++) { - for (var col = 0; col < first.Columns; col++) - { - result[row, col] = first._values[row, col] + second._values[row, col]; - } + result[row, col] = first._values[row, col] + second._values[row, col]; } - - return new Matrix(result); } - public static Matrix operator -(Matrix first, Matrix second) - { - Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns, "Matrices must have the same size."); + return new Matrix(result); + } - var result = new double[first.Rows, first.Columns]; + public static Matrix operator -(Matrix first, Matrix second) + { + Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns, "Matrices must have the same size."); - for (var row = 0; row < first.Rows; row++) + var result = new double[first.Rows, first.Columns]; + + for (var row = 0; row < first.Rows; row++) + { + for (var col = 0; col < first.Columns; col++) { - for (var col = 0; col < first.Columns; col++) - { - result[row, col] = first._values[row, col] - second._values[row, col]; - } + result[row, col] = first._values[row, col] - second._values[row, col]; } - - return new Matrix(result); } - public static Matrix operator *(double scalar, Matrix matrix) - { - var result = new double[matrix.Rows, matrix.Columns]; + return new Matrix(result); + } - for (var row = 0; row < matrix.Rows; row++) + public static Matrix operator *(double scalar, Matrix matrix) + { + var result = new double[matrix.Rows, matrix.Columns]; + + for (var row = 0; row < matrix.Rows; row++) + { + for (var col = 0; col < matrix.Columns; col++) { - for (var col = 0; col < matrix.Columns; col++) - { - result[row, col] = matrix._values[row, col] * scalar; - } + result[row, col] = matrix._values[row, col] * scalar; } - - return new Matrix(result); } - public static Matrix operator *(Matrix matrix, double scalar) - { - return scalar * matrix; - } + return new Matrix(result); + } - public static Matrix operator *(Matrix first, Matrix second) - { - var result = new double[first.Rows, second.Columns]; - var rows = result.GetLength(0); - var cols = result.GetLength(1); + public static Matrix operator *(Matrix matrix, double scalar) + { + return scalar * matrix; + } - for (var row = 0; row < rows; row++) + public static Matrix operator *(Matrix first, Matrix second) + { + var result = new double[first.Rows, second.Columns]; + var rows = result.GetLength(0); + var cols = result.GetLength(1); + + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) { - for (var col = 0; col < cols; col++) - { - var bufFirst = ArrayPool.Shared.Rent(first.Columns); - var bufSecond = ArrayPool.Shared.Rent(first.Rows); - result[row, col] = first.Row(row, bufFirst).Dot(second.Column(col, bufSecond)); - ArrayPool.Shared.Return(bufFirst, true); - ArrayPool.Shared.Return(bufSecond, true); - } + var bufFirst = ArrayPool.Shared.Rent(first.Columns); + var bufSecond = ArrayPool.Shared.Rent(first.Rows); + result[row, col] = first.Row(row, bufFirst).Dot(second.Column(col, bufSecond)); + ArrayPool.Shared.Return(bufFirst, true); + ArrayPool.Shared.Return(bufSecond, true); } - - return new Matrix(result); } - public static Matrix Identity(int size) - { - var identity = new double[size, size]; + return new Matrix(result); + } - for (var row = 0; row < size; row++) + public static Matrix Identity(int size) + { + var identity = new double[size, size]; + + for (var row = 0; row < size; row++) + { + for (var col = 0; col < size; col++) { - for (var col = 0; col < size; col++) - { - identity[row, col] = row == col ? 1.0d : 0d; - } + identity[row, col] = row == col ? 1.0d : 0d; } - - return new Matrix(identity); } - public override string ToString() + return new Matrix(identity); + } + + public override string ToString() + { + return $"{{{Rows}x{Columns}}} |{string.Join("|", Enumerable.Range(0, Rows).Select(row => $" {Row(row):###0.##} "))}|"; + } + + public Vector Dot(Vector vector) + { + Debug.Assert(Columns == vector.Size, "Matrix should have the same number of columns as the vector has rows."); + + var result = new double[Rows]; + for (int i = 0; i < Rows; i++) { - return $"{{{Rows}x{Columns}}} |{string.Join("|", Enumerable.Range(0, Rows).Select(row => $" {Row(row):###0.##} "))}|"; + var buf = ArrayPool.Shared.Rent(Columns); + var row = Row(i, buf); + result[i] = row.Dot(vector); + ArrayPool.Shared.Return(buf); } - public Vector Dot(Vector vector) - { - Debug.Assert(Columns == vector.Size, "Matrix should have the same number of columns as the vector has rows."); + return new Vector(result); + } - var result = new double[Rows]; - for (int i = 0; i < Rows; i++) - { - var buf = ArrayPool.Shared.Rent(Columns); - var row = Row(i, buf); - result[i] = row.Dot(vector); - ArrayPool.Shared.Return(buf); - } + public Vector Row(int index) + { + return Row(index, new double[Columns]); + } - return new Vector(result); + public Vector Row(int index, double[] buffer) + { + Debug.Assert(index <= Rows, "Row index out of range."); + for (int col = 0; col < Columns; col++) + { + buffer[col] = _values[index, col]; } - public Vector Row(int index) + return new Vector(buffer, Columns); + } + + public Vector Column(int index, double[] buf) + { + Debug.Assert(index <= Columns, "Column index out of range."); + for (int row = 0; row < Rows; row++) { - return Row(index, new double[Columns]); + buf[row] = _values[row, index]; } - public Vector Row(int index, double[] buffer) + return new Vector(buf, Rows); + } + + private double[] BackSubstition(double[,] lu, int[] indices, double[] b) + { + var x = (double[])b.Clone(); + var ii = 0; + for (var row = 0; row < Rows; row++) { - Debug.Assert(index <= Rows, "Row index out of range."); - for (int col = 0; col < Columns; col++) + var ip = indices[row]; + var sum = x[ip]; + + x[ip] = x[row]; + + if (ii == 0) + { + for (var col = ii; col <= row - 1; col++) + { + sum -= lu[row, col] * x[col]; + } + } + else if (sum == 0) { - buffer[col] = _values[index, col]; + ii = row; } - return new Vector(buffer, Columns); + x[row] = sum; } - public Vector Column(int index, double[] buf) + for (var row = Rows - 1; row >= 0; row--) { - Debug.Assert(index <= Columns, "Column index out of range."); - for (int row = 0; row < Rows; row++) + var sum = x[row]; + for (var col = row + 1; col < Columns; col++) { - buf[row] = _values[row, index]; + sum -= lu[row, col] * x[col]; } - return new Vector(buf, Rows); + x[row] = sum / lu[row, row]; } - private double[] BackSubstition(double[,] lu, int[] indices, double[] b) + return x; + } + + private (double[,] Result, int[] Indices, double D) GetDecomposition() + { + var max_row = 0; + var vv = Enumerable.Range(0, this.Rows) + .Select(row => 1.0d / Enumerable.Range(0, this.Columns).Select(col => Math.Abs(_values[row, col])).Max()).ToArray(); + var result = (double[,])_values.Clone(); + var index = new int[this.Rows]; + var d = 1.0d; + + for (var col = 0; col < Columns; col++) { - var x = (double[])b.Clone(); - var ii = 0; - for (var row = 0; row < Rows; row++) + for (var row = 0; row < col; row++) { - var ip = indices[row]; - var sum = x[ip]; - - x[ip] = x[row]; - - if (ii == 0) + var sum = result[row, col]; + for (var k = 0; k < row; k++) { - for (var col = ii; col <= row - 1; col++) - { - sum -= lu[row, col] * x[col]; - } - } - else if (sum == 0) - { - ii = row; + sum -= result[row, k] * result[k, col]; } - x[row] = sum; + result[row, col] = sum; } - for (var row = Rows - 1; row >= 0; row--) + var max = 0d; + for (var row = col; row < Rows; row++) { - var sum = x[row]; - for (var col = row + 1; col < Columns; col++) + var sum = result[row, col]; + for (var k = 0; k < col; k++) { - sum -= lu[row, col] * x[col]; + sum -= result[row, k] * result[k, col]; } - x[row] = sum / lu[row, row]; - } + result[row, col] = sum; - return x; - } + var tmp = vv[row] * Math.Abs(sum); - private (double[,] Result, int[] Indices, double D) GetDecomposition() - { - var max_row = 0; - var vv = Enumerable.Range(0, this.Rows) - .Select(row => 1.0d / Enumerable.Range(0, this.Columns).Select(col => Math.Abs(_values[row, col])).Max()).ToArray(); - var result = (double[,])_values.Clone(); - var index = new int[this.Rows]; - var d = 1.0d; - - for (var col = 0; col < Columns; col++) - { - for (var row = 0; row < col; row++) + if (tmp >= max) { - var sum = result[row, col]; - for (var k = 0; k < row; k++) - { - sum -= result[row, k] * result[k, col]; - } - - result[row, col] = sum; + max = tmp; + max_row = row; } + } - var max = 0d; - for (var row = col; row < Rows; row++) + if (col != max_row) + { + for (var k = 0; k < Rows; k++) { - var sum = result[row, col]; - for (var k = 0; k < col; k++) - { - sum -= result[row, k] * result[k, col]; - } - - result[row, col] = sum; - - var tmp = vv[row] * Math.Abs(sum); - - if (tmp >= max) - { - max = tmp; - max_row = row; - } + var tmp = result[max_row, k]; + result[max_row, k] = result[col, k]; + result[col, k] = tmp; } - if (col != max_row) - { - for (var k = 0; k < Rows; k++) - { - var tmp = result[max_row, k]; - result[max_row, k] = result[col, k]; - result[col, k] = tmp; - } - - d = -d; - vv[max_row] = vv[col]; - } + d = -d; + vv[max_row] = vv[col]; + } - index[col] = max_row; + index[col] = max_row; - if (col != Rows - 1) + if (col != Rows - 1) + { + var tmp = 1.0d / result[col, col]; + for (var row = col + 1; row < Rows; row++) { - var tmp = 1.0d / result[col, col]; - for (var row = col + 1; row < Rows; row++) - { - result[row, col] *= tmp; - } + result[row, col] *= tmp; } } - - return (result, index, d); } - internal class MatrixDisplay + return (result, index, d); + } + + internal class MatrixDisplay + { + public MatrixDisplay(Matrix matrix) { - public MatrixDisplay(Matrix matrix) - { - Cells = Enumerable.Range(0, matrix.Rows) - .Select(row => - new Cell(string.Join(" ", Enumerable.Range(0, matrix.Columns).Select(col => matrix._values[row, col])))) - .ToArray(); - } + Cells = Enumerable.Range(0, matrix.Rows) + .Select(row => + new Cell(string.Join(" ", Enumerable.Range(0, matrix.Columns).Select(col => matrix._values[row, col])))) + .ToArray(); + } - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public Cell[] Cells { get; } + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public Cell[] Cells { get; } - [DebuggerDisplay("{" + nameof(Value) + ", nq}")] - internal class Cell + [DebuggerDisplay("{" + nameof(Value) + ", nq}")] + internal class Cell + { + public Cell(string value) { - public Cell(string value) - { - Value = value; - } - - public string Value { get; } + Value = value; } + + public string Value { get; } } } } \ No newline at end of file diff --git a/src/SortCS/Kalman/Vector.cs b/src/SortCS/Kalman/Vector.cs index 7c9095b..31f2663 100644 --- a/src/SortCS/Kalman/Vector.cs +++ b/src/SortCS/Kalman/Vector.cs @@ -1,99 +1,97 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; -namespace SortCS.Kalman +namespace SortCS.Kalman; + +internal struct Vector { - internal struct Vector + private readonly double[] _values; + + public Vector(params double[] values) { - private readonly double[] _values; + _values = values; + Size = values.Length; + } - public Vector(params double[] values) + public Vector(double[] values, int size) + { + if (size > values.Length) { - _values = values; - Size = values.Length; + throw new ArgumentOutOfRangeException(nameof(size)); } - public Vector(double[] values, int size) + _values = values; + Size = size; + } + + public Vector(int size) + { + _values = new double[size]; + Size = size; + } + + public int Size { get; } + + public double this[int index] + { + get => index <= Size ? _values[index] : throw new Exception("nope"); + set { - if (size > values.Length) + if (index > Size) { - throw new ArgumentOutOfRangeException(nameof(size)); + throw new ArgumentOutOfRangeException(nameof(index)); } - _values = values; - Size = size; + _values[index] = value; } + } - public Vector(int size) + public static Vector operator -(Vector first, Vector second) + { + Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); + var resultArray = new double[first.Size]; + for (int i = 0; i < first.Size; i++) { - _values = new double[size]; - Size = size; + resultArray[i] = first[i] - second[i]; } - public int Size { get; } + return new Vector(resultArray); + } - public double this[int index] + public static Vector operator +(Vector first, Vector second) + { + Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); + var resultArray = new double[first.Size]; + for (int i = 0; i < first.Size; i++) { - get => index <= Size ? _values[index] : throw new Exception("nope"); - set - { - if (index > Size) - { - throw new Exception("asd"); - } - - _values[index] = value; - } + resultArray[i] = first[i] + second[i]; } - public static Vector operator -(Vector first, Vector second) - { - Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); - var resultArray = new double[first.Size]; - for (int i = 0; i < first.Size; i++) - { - resultArray[i] = first[i] - second[i]; - } - - return new Vector(resultArray); - } + return new Vector(resultArray); + } - public static Vector operator +(Vector first, Vector second) + public double Dot(Vector other) + { + Debug.Assert(Size == other.Size, $"Vectors should be of equal length {Size} != {other.Size}."); + Debug.Assert(Size > 0, "Vectors must have at least one element."); + double sum = 0; + for (int i = 0; i < Size; i++) { - Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); - var resultArray = new double[first.Size]; - for (int i = 0; i < first.Size; i++) - { - resultArray[i] = first[i] + second[i]; - } - - return new Vector(resultArray); + sum += _values[i] * other[i]; } - public double Dot(Vector other) - { - Debug.Assert(Size == other.Size, $"Vectors should be of equal length {Size} != {other.Size}."); - Debug.Assert(Size > 0, "Vectors must have at least one element."); - double sum = 0; - for (int i = 0; i < Size; i++) - { - sum += _values[i] * other[i]; - } - - return sum; - } + return sum; + } - public override string ToString() - { - return string.Join(", ", _values.Select(v => v.ToString("###0.00", CultureInfo.InvariantCulture))); - } + public override string ToString() + { + return string.Join(", ", _values.Select(v => v.ToString("###0.00", CultureInfo.InvariantCulture))); + } - internal Vector Append(params double[] extraElements) - { - return new Vector(_values.Take(Size).Concat(extraElements).ToArray()); - } + internal Vector Append(params double[] extraElements) + { + return new Vector(_values.Take(Size).Concat(extraElements).ToArray()); } } \ No newline at end of file diff --git a/src/SortCS/SortTracker.cs b/src/SortCS/SortTracker.cs index a1207dc..a483e8f 100644 --- a/src/SortCS/SortTracker.cs +++ b/src/SortCS/SortTracker.cs @@ -5,168 +5,167 @@ using Microsoft.Extensions.Logging; using SortCS.Kalman; -namespace SortCS +namespace SortCS; + +public class SortTracker : ITracker { - public class SortTracker : ITracker + private readonly Dictionary _trackers; + private readonly ILogger _logger; + private int _trackerIndex = 1; // MOT Evaluations requires a start index of 1 + + public SortTracker(float iouThreshold = 0.3f, int maxMisses = 3) { - private readonly Dictionary _trackers; - private readonly ILogger _logger; - private int _trackerIndex = 1; // MOT Evaluations requires a start index of 1 + _trackers = new Dictionary(); + IouThreshold = iouThreshold; + MaxMisses = maxMisses; + } - public SortTracker(float iouThreshold = 0.3f, int maxMisses = 3) - { - _trackers = new Dictionary(); - IouThreshold = iouThreshold; - MaxMisses = maxMisses; - } + public SortTracker(ILogger logger, float iouThreshold = 0.3f, int maxMisses = 3) + : this(iouThreshold, maxMisses) + { + _logger = logger; + } - public SortTracker(ILogger logger, float iouThreshold = 0.3f, int maxMisses = 3) - : this(iouThreshold, maxMisses) - { - _logger = logger; - } + public float IouThreshold { get; private init; } - public float IouThreshold { get; private init; } + public int MaxMisses { get; private init; } - public int MaxMisses { get; private init; } + public IEnumerable Track(IEnumerable boxes) + { + var predictions = new Dictionary(); - public IEnumerable Track(IEnumerable boxes) + foreach (var tracker in _trackers) { - var predictions = new Dictionary(); + var prediction = tracker.Value.Tracker.Predict(); + predictions.Add(tracker.Key, prediction); + } - foreach (var tracker in _trackers) - { - var prediction = tracker.Value.Tracker.Predict(); - predictions.Add(tracker.Key, prediction); - } + var boxesArray = boxes.ToArray(); - var boxesArray = boxes.ToArray(); + var (matchedBoxes, unmatchedBoxes) = MatchDetectionsWithPredictions(boxesArray, predictions.Values); - var (matchedBoxes, unmatchedBoxes) = MatchDetectionsWithPredictions(boxesArray, predictions.Values); + var activeTrackids = new HashSet(); + foreach (var item in matchedBoxes) + { + var prediction = predictions.ElementAt(item.Key); + var track = _trackers[prediction.Key]; + track.Track.History.Add(item.Value); + track.Track.Misses = 0; + track.Track.State = TrackState.Active; + track.Tracker.Update(item.Value); + track.Track.Prediction = prediction.Value; + + activeTrackids.Add(track.Track.TrackId); + } - var activeTrackids = new HashSet(); - foreach (var item in matchedBoxes) - { - var prediction = predictions.ElementAt(item.Key); - var track = _trackers[prediction.Key]; - track.Track.History.Add(item.Value); - track.Track.Misses = 0; - track.Track.State = TrackState.Active; - track.Tracker.Update(item.Value); - track.Track.Prediction = prediction.Value; - - activeTrackids.Add(track.Track.TrackId); - } + var missedTracks = _trackers.Where(x => !activeTrackids.Contains(x.Key)); + foreach (var missedTrack in missedTracks) + { + missedTrack.Value.Track.Misses++; + missedTrack.Value.Track.TotalMisses++; + missedTrack.Value.Track.State = TrackState.Ending; + } - var missedTracks = _trackers.Where(x => !activeTrackids.Contains(x.Key)); - foreach (var missedTrack in missedTracks) - { - missedTrack.Value.Track.Misses++; - missedTrack.Value.Track.TotalMisses++; - missedTrack.Value.Track.State = TrackState.Ending; - } + var toRemove = _trackers.Where(x => x.Value.Track.Misses > MaxMisses).ToList(); + foreach (var tr in toRemove) + { + tr.Value.Track.State = TrackState.Ended; + _trackers.Remove(tr.Key); + } - var toRemove = _trackers.Where(x => x.Value.Track.Misses > MaxMisses).ToList(); - foreach (var tr in toRemove) + foreach (var unmatchedBox in unmatchedBoxes) + { + var track = new Track { - tr.Value.Track.State = TrackState.Ended; - _trackers.Remove(tr.Key); - } + TrackId = _trackerIndex++, + History = new List() { unmatchedBox }, + Misses = 0, + State = TrackState.Started, + TotalMisses = 0, + Prediction = unmatchedBox + }; + _trackers.Add(track.TrackId, (track, new KalmanBoxTracker(unmatchedBox))); + } - foreach (var unmatchedBox in unmatchedBoxes) - { - var track = new Track - { - TrackId = _trackerIndex++, - History = new List() { unmatchedBox }, - Misses = 0, - State = TrackState.Started, - TotalMisses = 0, - Prediction = unmatchedBox - }; - _trackers.Add(track.TrackId, (track, new KalmanBoxTracker(unmatchedBox))); - } + var result = _trackers.Select(x => x.Value.Track).Concat(toRemove.Select(y => y.Value.Track)); + Log(result); + return result; + } - var result = _trackers.Select(x => x.Value.Track).Concat(toRemove.Select(y => y.Value.Track)); - Log(result); - return result; + private void Log(IEnumerable tracks) + { + if (_logger == null || !tracks.Any()) + { + return; } - private void Log(IEnumerable tracks) + var tracksWithHistory = tracks.Where(x => x.History != null); + var longest = tracksWithHistory.Max(x => x.History.Count); + var anyStarted = tracksWithHistory.Any(x => x.History.Count == 1 && x.Misses == 0); + var ended = tracks.Count(x => x.State == TrackState.Ended); + if (anyStarted || ended > 0) { - if (_logger == null || !tracks.Any()) - { - return; - } - - var tracksWithHistory = tracks.Where(x => x.History != null); - var longest = tracksWithHistory.Max(x => x.History.Count); - var anyStarted = tracksWithHistory.Any(x => x.History.Count == 1 && x.Misses == 0); - var ended = tracks.Count(x => x.State == TrackState.Ended); - if (anyStarted || ended > 0) - { - var tracksStr = tracks.Select(x => $"{x.TrackId}{(x.State == TrackState.Active ? null : $": {x.State}")}"); + var tracksStr = tracks.Select(x => $"{x.TrackId}{(x.State == TrackState.Active ? null : $": {x.State}")}"); - _logger.LogDebug("Tracks: [{tracks}], Longest: {longest}, Ended: {ended}", string.Join(",", tracksStr), longest, ended); - } + _logger.LogDebug("Tracks: [{tracks}], Longest: {longest}, Ended: {ended}", string.Join(",", tracksStr), longest, ended); } + } - private (Dictionary Matched, ICollection Unmatched) MatchDetectionsWithPredictions( - RectangleF[] boxes, - ICollection trackPredictions) + private (Dictionary Matched, ICollection Unmatched) MatchDetectionsWithPredictions( + RectangleF[] boxes, + ICollection trackPredictions) + { + if (trackPredictions.Count == 0) { - if (trackPredictions.Count == 0) - { - return (new(), boxes); - } - - var matrix = new int[boxes.Length, trackPredictions.Count]; - var trackPredictionsArray = trackPredictions.ToArray(); + return (new(), boxes); + } - for (int i = 0; i < boxes.Length; i++) - { - for (int j = 0; j < trackPredictionsArray.Length; j++) - { - matrix[i, j] = (int)(-100 * IoU(boxes[i], trackPredictionsArray[j])); - } - } + var matrix = new int[boxes.Length, trackPredictions.Count]; + var trackPredictionsArray = trackPredictions.ToArray(); - if (boxes.Length > trackPredictions.Count) + for (int i = 0; i < boxes.Length; i++) + { + for (int j = 0; j < trackPredictionsArray.Length; j++) { - var extra = new int[boxes.Length - trackPredictions.Count]; - matrix = Enumerable.Range(0, boxes.Length) - .SelectMany(row => Enumerable.Range(0, trackPredictions.Count).Select(col => matrix[row, col]).Concat(extra)) - .ToArray(boxes.Length, boxes.Length); + matrix[i, j] = (int)(-100 * IoU(boxes[i], trackPredictionsArray[j])); } - - var original = (int[,])matrix.Clone(); - var minimalThreshold = (int)(-IouThreshold * 100); - var boxTrackerMapping = matrix.FindAssignments() - .Select((ti, bi) => (bi, ti)) - .Where(bt => bt.ti < trackPredictions.Count && original[bt.bi, bt.ti] <= minimalThreshold) - .ToDictionary(bt => bt.bi, bt => bt.ti); - - var unmatchedBoxes = boxes.Where((_, index) => !boxTrackerMapping.ContainsKey(index)).ToArray(); - var matchedBoxes = boxes.Select((box, index) => boxTrackerMapping.TryGetValue(index, out var tracker) - ? (Tracker: tracker, Box: box) - : (Tracker: -1, Box: RectangleF.Empty)) - .Where(tb => tb.Tracker != -1) - .ToDictionary(tb => tb.Tracker, tb => tb.Box); - - return (matchedBoxes, unmatchedBoxes); } - private double IoU(RectangleF a, RectangleF b) + if (boxes.Length > trackPredictions.Count) { - RectangleF intersection = RectangleF.Intersect(a, b); - if (intersection.IsEmpty) - { - return 0; - } + var extra = new int[boxes.Length - trackPredictions.Count]; + matrix = Enumerable.Range(0, boxes.Length) + .SelectMany(row => Enumerable.Range(0, trackPredictions.Count).Select(col => matrix[row, col]).Concat(extra)) + .ToArray(boxes.Length, boxes.Length); + } + + var original = (int[,])matrix.Clone(); + var minimalThreshold = (int)(-IouThreshold * 100); + var boxTrackerMapping = matrix.FindAssignments() + .Select((ti, bi) => (bi, ti)) + .Where(bt => bt.ti < trackPredictions.Count && original[bt.bi, bt.ti] <= minimalThreshold) + .ToDictionary(bt => bt.bi, bt => bt.ti); + + var unmatchedBoxes = boxes.Where((_, index) => !boxTrackerMapping.ContainsKey(index)).ToArray(); + var matchedBoxes = boxes.Select((box, index) => boxTrackerMapping.TryGetValue(index, out var tracker) + ? (Tracker: tracker, Box: box) + : (Tracker: -1, Box: RectangleF.Empty)) + .Where(tb => tb.Tracker != -1) + .ToDictionary(tb => tb.Tracker, tb => tb.Box); + + return (matchedBoxes, unmatchedBoxes); + } - double intersectArea = (1.0 + intersection.Width) * (1.0 + intersection.Height); - double unionArea = ((1.0 + a.Width) * (1.0 + a.Height)) + ((1.0 + b.Width) * (1.0 + b.Height)) - intersectArea; - return intersectArea / (unionArea + 1e-5); + private double IoU(RectangleF a, RectangleF b) + { + RectangleF intersection = RectangleF.Intersect(a, b); + if (intersection.IsEmpty) + { + return 0; } + + double intersectArea = (1.0 + intersection.Width) * (1.0 + intersection.Height); + double unionArea = ((1.0 + a.Width) * (1.0 + a.Height)) + ((1.0 + b.Width) * (1.0 + b.Height)) - intersectArea; + return intersectArea / (unionArea + 1e-5); } -} +} \ No newline at end of file diff --git a/src/SortCS/Track.cs b/src/SortCS/Track.cs index a445358..3d50f94 100644 --- a/src/SortCS/Track.cs +++ b/src/SortCS/Track.cs @@ -1,20 +1,19 @@ using System.Collections.Generic; using System.Drawing; -namespace SortCS +namespace SortCS; + +public record Track { - public record Track - { - public int TrackId { get; set; } + public int TrackId { get; set; } - public int TotalMisses { get; set; } + public int TotalMisses { get; set; } - public int Misses { get; set; } + public int Misses { get; set; } - public List History { get; set; } + public List History { get; set; } - public TrackState State { get; set; } + public TrackState State { get; set; } - public RectangleF Prediction { get; set; } - } + public RectangleF Prediction { get; set; } } \ No newline at end of file diff --git a/src/SortCS/TrackState.cs b/src/SortCS/TrackState.cs index b422deb..502746f 100644 --- a/src/SortCS/TrackState.cs +++ b/src/SortCS/TrackState.cs @@ -1,10 +1,9 @@ -namespace SortCS +namespace SortCS; + +public enum TrackState { - public enum TrackState - { - Started, - Active, - Ending, - Ended - } + Started, + Active, + Ending, + Ended } \ No newline at end of file From 6071bf974bd52397abc2009bed3f75302a7314de Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 17:49:32 +0100 Subject: [PATCH 11/14] feeding the monster --- analyzers.targets | 43 +++++++++++-------------- src/SortCS.Benchmarks/Program.cs | 4 +-- src/SortCS/Kalman/Matrix.cs | 55 +++++++++++++++----------------- src/SortCS/Kalman/Vector.cs | 14 ++++---- src/SortCS/SortTracker.cs | 24 +++++++------- 5 files changed, 66 insertions(+), 74 deletions(-) diff --git a/analyzers.targets b/analyzers.targets index fe85026..0f340e9 100644 --- a/analyzers.targets +++ b/analyzers.targets @@ -1,32 +1,27 @@ - + - - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + true + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + - + \ No newline at end of file diff --git a/src/SortCS.Benchmarks/Program.cs b/src/SortCS.Benchmarks/Program.cs index 96a1d70..f5efd8d 100644 --- a/src/SortCS.Benchmarks/Program.cs +++ b/src/SortCS.Benchmarks/Program.cs @@ -32,10 +32,10 @@ private List GenerateTestFrames(int numFrames, int objectsPerFrame var random = new Random(42); var frames = new List(); - for (int i = 0; i < numFrames; i++) + for (var i = 0; i < numFrames; i++) { var objects = new RectangleF[objectsPerFrame]; - for (int j = 0; j < objectsPerFrame; j++) + for (var j = 0; j < objectsPerFrame; j++) { objects[j] = new RectangleF( random.Next(0, 1000), diff --git a/src/SortCS/Kalman/Matrix.cs b/src/SortCS/Kalman/Matrix.cs index 999952d..e181b14 100644 --- a/src/SortCS/Kalman/Matrix.cs +++ b/src/SortCS/Kalman/Matrix.cs @@ -12,9 +12,6 @@ internal class Matrix { private readonly double[,] _values; - private readonly Dictionary _rows = new(); - private readonly Dictionary _cols = new(); - public Matrix(double[,] values) { _values = values; @@ -72,9 +69,9 @@ public Matrix Inverted { get { - Debug.Assert(Rows == Columns, "Matrix must be square."); + Debug.Assert(Rows == Columns); - var (lu, indices, d) = GetDecomposition(); + var (lu, indices) = GetDecomposition(); var result = new double[Rows, Columns]; for (var col = 0; col < Columns; col++) @@ -83,7 +80,7 @@ public Matrix Inverted column[col] = 1.0d; - var x = BackSubstition(lu, indices, column); + var x = BackSubstitution(lu, indices, column); for (var row = 0; row < Rows; row++) { @@ -99,7 +96,7 @@ public Matrix Inverted public static Matrix operator +(Matrix first, Matrix second) { - Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns, "Matrices must have the same size."); + Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns); var result = new double[first.Rows, first.Columns]; @@ -116,7 +113,7 @@ public Matrix Inverted public static Matrix operator -(Matrix first, Matrix second) { - Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns, "Matrices must have the same size."); + Debug.Assert(first.Rows == second.Rows && first.Columns == second.Columns); var result = new double[first.Rows, first.Columns]; @@ -194,10 +191,10 @@ public override string ToString() public Vector Dot(Vector vector) { - Debug.Assert(Columns == vector.Size, "Matrix should have the same number of columns as the vector has rows."); + Debug.Assert(Columns == vector.Size); var result = new double[Rows]; - for (int i = 0; i < Rows; i++) + for (var i = 0; i < Rows; i++) { var buf = ArrayPool.Shared.Rent(Columns); var row = Row(i, buf); @@ -215,8 +212,8 @@ public Vector Row(int index) public Vector Row(int index, double[] buffer) { - Debug.Assert(index <= Rows, "Row index out of range."); - for (int col = 0; col < Columns; col++) + Debug.Assert(index <= Rows); + for (var col = 0; col < Columns; col++) { buffer[col] = _values[index, col]; } @@ -226,8 +223,8 @@ public Vector Row(int index, double[] buffer) public Vector Column(int index, double[] buf) { - Debug.Assert(index <= Columns, "Column index out of range."); - for (int row = 0; row < Rows; row++) + Debug.Assert(index <= Columns); + for (var row = 0; row < Rows; row++) { buf[row] = _values[row, index]; } @@ -235,7 +232,7 @@ public Vector Column(int index, double[] buf) return new Vector(buf, Rows); } - private double[] BackSubstition(double[,] lu, int[] indices, double[] b) + private double[] BackSubstitution(double[,] lu, int[] indices, double[] b) { var x = (double[])b.Clone(); var ii = 0; @@ -246,14 +243,14 @@ private double[] BackSubstition(double[,] lu, int[] indices, double[] b) x[ip] = x[row]; - if (ii == 0) + if (Math.Sign(ii) == 0) { for (var col = ii; col <= row - 1; col++) { sum -= lu[row, col] * x[col]; } } - else if (sum == 0) + else if (Math.Sign(sum) == 0) { ii = row; } @@ -275,13 +272,13 @@ private double[] BackSubstition(double[,] lu, int[] indices, double[] b) return x; } - private (double[,] Result, int[] Indices, double D) GetDecomposition() + private (double[,] Result, int[] Indices) GetDecomposition() { - var max_row = 0; - var vv = Enumerable.Range(0, this.Rows) - .Select(row => 1.0d / Enumerable.Range(0, this.Columns).Select(col => Math.Abs(_values[row, col])).Max()).ToArray(); + var maxRow = 0; + var vv = Enumerable.Range(0, Rows) + .Select(row => 1.0d / Enumerable.Range(0, Columns).Select(col => Math.Abs(_values[row, col])).Max()).ToArray(); var result = (double[,])_values.Clone(); - var index = new int[this.Rows]; + var index = new int[Rows]; var d = 1.0d; for (var col = 0; col < Columns; col++) @@ -313,24 +310,22 @@ private double[] BackSubstition(double[,] lu, int[] indices, double[] b) if (tmp >= max) { max = tmp; - max_row = row; + maxRow = row; } } - if (col != max_row) + if (col != maxRow) { for (var k = 0; k < Rows; k++) { - var tmp = result[max_row, k]; - result[max_row, k] = result[col, k]; - result[col, k] = tmp; + (result[maxRow, k], result[col, k]) = (result[col, k], result[maxRow, k]); } d = -d; - vv[max_row] = vv[col]; + vv[maxRow] = vv[col]; } - index[col] = max_row; + index[col] = maxRow; if (col != Rows - 1) { @@ -342,7 +337,7 @@ private double[] BackSubstition(double[,] lu, int[] indices, double[] b) } } - return (result, index, d); + return (result, index); } internal class MatrixDisplay diff --git a/src/SortCS/Kalman/Vector.cs b/src/SortCS/Kalman/Vector.cs index 31f2663..7b5cab0 100644 --- a/src/SortCS/Kalman/Vector.cs +++ b/src/SortCS/Kalman/Vector.cs @@ -36,7 +36,7 @@ public Vector(int size) public double this[int index] { - get => index <= Size ? _values[index] : throw new Exception("nope"); + get => index <= Size ? _values[index] : throw new ArgumentOutOfRangeException(nameof(index)); set { if (index > Size) @@ -50,9 +50,9 @@ public double this[int index] public static Vector operator -(Vector first, Vector second) { - Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); + Debug.Assert(first.Size == second.Size); var resultArray = new double[first.Size]; - for (int i = 0; i < first.Size; i++) + for (var i = 0; i < first.Size; i++) { resultArray[i] = first[i] - second[i]; } @@ -62,9 +62,9 @@ public double this[int index] public static Vector operator +(Vector first, Vector second) { - Debug.Assert(first.Size == second.Size, "Vectors should be of equal size"); + Debug.Assert(first.Size == second.Size); var resultArray = new double[first.Size]; - for (int i = 0; i < first.Size; i++) + for (var i = 0; i < first.Size; i++) { resultArray[i] = first[i] + second[i]; } @@ -75,9 +75,9 @@ public double this[int index] public double Dot(Vector other) { Debug.Assert(Size == other.Size, $"Vectors should be of equal length {Size} != {other.Size}."); - Debug.Assert(Size > 0, "Vectors must have at least one element."); + Debug.Assert(Size > 0); double sum = 0; - for (int i = 0; i < Size; i++) + for (var i = 0; i < Size; i++) { sum += _values[i] * other[i]; } diff --git a/src/SortCS/SortTracker.cs b/src/SortCS/SortTracker.cs index a483e8f..871061d 100644 --- a/src/SortCS/SortTracker.cs +++ b/src/SortCS/SortTracker.cs @@ -58,12 +58,14 @@ public IEnumerable Track(IEnumerable boxes) activeTrackids.Add(track.Track.TrackId); } - var missedTracks = _trackers.Where(x => !activeTrackids.Contains(x.Key)); + var missedTracks = _trackers + .Where(x => !activeTrackids.Contains(x.Key)) + .Select(x => x.Value.Track); foreach (var missedTrack in missedTracks) { - missedTrack.Value.Track.Misses++; - missedTrack.Value.Track.TotalMisses++; - missedTrack.Value.Track.State = TrackState.Ending; + missedTrack.Misses++; + missedTrack.TotalMisses++; + missedTrack.State = TrackState.Ending; } var toRemove = _trackers.Where(x => x.Value.Track.Misses > MaxMisses).ToList(); @@ -107,7 +109,7 @@ private void Log(IEnumerable tracks) { var tracksStr = tracks.Select(x => $"{x.TrackId}{(x.State == TrackState.Active ? null : $": {x.State}")}"); - _logger.LogDebug("Tracks: [{tracks}], Longest: {longest}, Ended: {ended}", string.Join(",", tracksStr), longest, ended); + _logger.LogDebug("Tracks: [{Tracks}], Longest: {Longest}, Ended: {Ended}", string.Join(",", tracksStr), longest, ended); } } @@ -123,9 +125,9 @@ private void Log(IEnumerable tracks) var matrix = new int[boxes.Length, trackPredictions.Count]; var trackPredictionsArray = trackPredictions.ToArray(); - for (int i = 0; i < boxes.Length; i++) + for (var i = 0; i < boxes.Length; i++) { - for (int j = 0; j < trackPredictionsArray.Length; j++) + for (var j = 0; j < trackPredictionsArray.Length; j++) { matrix[i, j] = (int)(-100 * IoU(boxes[i], trackPredictionsArray[j])); } @@ -156,16 +158,16 @@ private void Log(IEnumerable tracks) return (matchedBoxes, unmatchedBoxes); } - private double IoU(RectangleF a, RectangleF b) + private static double IoU(RectangleF a, RectangleF b) { - RectangleF intersection = RectangleF.Intersect(a, b); + var intersection = RectangleF.Intersect(a, b); if (intersection.IsEmpty) { return 0; } - double intersectArea = (1.0 + intersection.Width) * (1.0 + intersection.Height); - double unionArea = ((1.0 + a.Width) * (1.0 + a.Height)) + ((1.0 + b.Width) * (1.0 + b.Height)) - intersectArea; + var intersectArea = (1.0 + intersection.Width) * (1.0 + intersection.Height); + var unionArea = ((1.0 + a.Width) * (1.0 + a.Height)) + ((1.0 + b.Width) * (1.0 + b.Height)) - intersectArea; return intersectArea / (unionArea + 1e-5); } } \ No newline at end of file From 4a43f315164e0327b69aee62e393676c8b696d80 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 20:07:40 +0100 Subject: [PATCH 12/14] remove settings file --- .vscode/settings.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a04b218..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "files.associations": { - "*.yaml": "home-assistant" - } -} \ No newline at end of file From 63f826ab9e5bac83efaed22ca13c064ec5e5893c Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 20:23:24 +0100 Subject: [PATCH 13/14] nore more warnings --- src/SortCS.Benchmarks/Program.cs | 8 +++--- src/SortCS.Evaluate/Program.cs | 32 ++++++++++++---------- src/SortCS.Evaluate/SortCS.Evaluate.csproj | 2 +- src/SortCS.Tests/SortCS.Tests.csproj | 8 +++--- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/SortCS.Benchmarks/Program.cs b/src/SortCS.Benchmarks/Program.cs index f5efd8d..5093cae 100644 --- a/src/SortCS.Benchmarks/Program.cs +++ b/src/SortCS.Benchmarks/Program.cs @@ -8,8 +8,8 @@ [MemoryDiagnoser] public class SortCSBenchmarks { - private ITracker _tracker; - private List _frames; + private ITracker? _tracker; + private List? _frames; [GlobalSetup] public void Setup() @@ -21,9 +21,9 @@ public void Setup() [Benchmark] public void TrackMultipleFrames() { - foreach (var frame in _frames) + foreach (var frame in _frames ?? []) { - _tracker.Track(frame); + _tracker!.Track(frame); } } diff --git a/src/SortCS.Evaluate/Program.cs b/src/SortCS.Evaluate/Program.cs index 785832e..004d232 100644 --- a/src/SortCS.Evaluate/Program.cs +++ b/src/SortCS.Evaluate/Program.cs @@ -26,24 +26,28 @@ static async Task Main(string[] args) _logger = serviceProvider.GetService>(); + var dataFolderOption = new Option( + "--data-folder", + getDefaultValue: () => new DirectoryInfo(@"../../../../../../TrackEval/data"), // Sssuming SortCS/src/SortCS.Evaluate/bin/debug/net5.0 is working directory + description: "Location where data is stored using this format: https://github.com/JonathonLuiten/TrackEval/blob/master/docs/MOTChallenge-format.txt"); + + var benchmarkOption = new Option( + "--benchmark", + getDefaultValue: () => "MOT20", + description: "Name of the benchmark, e.g. MOT15, MO16, MOT17 or MOT20 (default : MOT20)"); + + var splitOption = new Option( + "--split-to-eval", + getDefaultValue: () => "train", + description: "Data split on which to evalute e.g. train, test (default : train)"); + var rootCommand = new RootCommand { - new Option( - "--data-folder", - getDefaultValue: () => new DirectoryInfo(@"../../../../../../TrackEval/data"), // Sssuming SortCS/src/SortCS.Evaluate/bin/debug/net5.0 is working directory - description: "Location where data is stored using this format: https://github.com/JonathonLuiten/TrackEval/blob/master/docs/MOTChallenge-format.txt"), - new Option( - "--benchmark", - getDefaultValue: () => "MOT20", - description: "Name of the benchmark, e.g. MOT15, MO16, MOT17 or MOT20 (default : MOT20)"), - new Option( - "--split-to-eval", - getDefaultValue: () => "train", - description: "Data split on which to evalute e.g. train, test (default : train)"), + dataFolderOption,benchmarkOption,splitOption }; rootCommand.Description = "App to evaluate the SortCS tracking algoritm"; - rootCommand.Handler = CommandHandler.Create(async (dataFolder, benchmark, splitToEval) => + rootCommand.SetHandler(async (dataFolder, benchmark, splitToEval) => { if (!dataFolder.Exists || !dataFolder.GetDirectories().Any()) { @@ -51,7 +55,7 @@ static async Task Main(string[] args) } var sortCsEvaluator = new SortCsEvaluator(dataFolder, benchmark, splitToEval, serviceProvider.GetService>()); await sortCsEvaluator.EvaluateAsync(); - }); + }, dataFolderOption, benchmarkOption, splitOption); return await rootCommand.InvokeAsync(args); } diff --git a/src/SortCS.Evaluate/SortCS.Evaluate.csproj b/src/SortCS.Evaluate/SortCS.Evaluate.csproj index 8cac9e1..b5ed445 100644 --- a/src/SortCS.Evaluate/SortCS.Evaluate.csproj +++ b/src/SortCS.Evaluate/SortCS.Evaluate.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/SortCS.Tests/SortCS.Tests.csproj b/src/SortCS.Tests/SortCS.Tests.csproj index d8a99b7..6f971ac 100644 --- a/src/SortCS.Tests/SortCS.Tests.csproj +++ b/src/SortCS.Tests/SortCS.Tests.csproj @@ -7,10 +7,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 1fb02f14cc8f58f4a543deea8d7f51dfe90d0601 Mon Sep 17 00:00:00 2001 From: Kees Schollaart Date: Sun, 22 Dec 2024 20:32:38 +0100 Subject: [PATCH 14/14] this better? --- src/SortCS.Tests/SortCS.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SortCS.Tests/SortCS.Tests.csproj b/src/SortCS.Tests/SortCS.Tests.csproj index 6f971ac..088a9e3 100644 --- a/src/SortCS.Tests/SortCS.Tests.csproj +++ b/src/SortCS.Tests/SortCS.Tests.csproj @@ -8,7 +8,7 @@ - + all