diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41b74cc..2dc2ad5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,12 +18,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - name: Setup .NET Core 9.0 - uses: actions/setup-dotnet@v1 + - name: Setup .NET 10.0 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore @@ -46,12 +46,12 @@ jobs: runs-on: ubuntu-latest needs: build-and-test steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - name: Setup .NET Core 9.0 - uses: actions/setup-dotnet@v1 + - name: Setup .NET 10.0 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a5df36..4419b61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,8 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - + - uses: actions/checkout@v4 + - name: Verify commit exists in origin/main run: | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* @@ -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 9.0 - uses: actions/setup-dotnet@v1 + - name: Setup .NET 10.0 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Build working-directory: src/SortCS diff --git a/.vscode/launch.json b/.vscode/launch.json index 47bdd6c..11b33ff 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,9 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/SortCS.Evaluate/bin/Debug/net5.0/SortCS.Evaluate.dll", + "program": "${workspaceFolder}/src/SortCS.Evaluate/bin/Debug/net10.0/SortCS.Evaluate.dll", "args": [], - "cwd": "${workspaceFolder}/src/SortCS.Evaluate/bin/Debug/net5.0/", + "cwd": "${workspaceFolder}/src/SortCS.Evaluate/bin/Debug/net10.0/", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", "stopAtEntry": false diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..573b8e2 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + net10.0 + latest + enable + enable + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..e5a9e22 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,31 @@ + + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index ca29290..f0b70f2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,12 @@ SortCS is a 'Multiple Object Tracker' as described in [this paper](https://arxiv ```cs using SortCS; -ITracker tracker = new SortTracker(iouThreshold = 0.3f, maxMisses = 3); +// maxMisses is the number of consecutive frames an object may be occluded before its track ends. +var tracker = new SortTracker(iouThreshold: 0.3f, maxMisses: 3); + +// MaxMisses can be adjusted at runtime; the new value takes effect on the next Track(..) call. +tracker.MaxMisses = 5; + tracker.Track(new[] { new RectangleF(1695,383,159,343), diff --git a/analyzers.targets b/analyzers.targets index 0f340e9..72c1af7 100644 --- a/analyzers.targets +++ b/analyzers.targets @@ -7,12 +7,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj b/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj index ae86ee2..7775381 100644 --- a/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj +++ b/src/SortCS.Benchmarks/SortCS.Benchmarks.csproj @@ -1,17 +1,15 @@ - + Exe - net9.0 - enable - enable - + - \ No newline at end of file + + diff --git a/src/SortCS.Evaluate/Program.cs b/src/SortCS.Evaluate/Program.cs index 004d232..94e344e 100644 --- a/src/SortCS.Evaluate/Program.cs +++ b/src/SortCS.Evaluate/Program.cs @@ -1,78 +1,74 @@ -using System.IO; using System.CommandLine; -using System.CommandLine.Invocation; -using System.Threading.Tasks; -using System.Linq; -using System.Net.Http; using System.IO.Compression; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using SortCS; +using SortCS.Evaluate; -namespace SortCS.Evaluate; - -class Program +var services = new ServiceCollection(); +services.AddLogging(loggerBuilder => { - private static ILogger _logger; - - static async Task Main(string[] args) - { - 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>(); +var logger = serviceProvider.GetRequiredService>(); - 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 dataFolderOption = new Option("--data-folder") +{ + Description = "Location where data is stored using this format: https://github.com/JonathonLuiten/TrackEval/blob/master/docs/MOTChallenge-format.txt", + DefaultValueFactory = _ => new DirectoryInfo(@"../../../../../../TrackEval/data") // Assuming SortCS/src/SortCS.Evaluate/bin/debug/net10.0 is working directory +}; - var benchmarkOption = new Option( - "--benchmark", - getDefaultValue: () => "MOT20", - description: "Name of the benchmark, e.g. MOT15, MO16, MOT17 or MOT20 (default : MOT20)"); +var benchmarkOption = new Option("--benchmark") +{ + Description = "Name of the benchmark, e.g. MOT15, MO16, MOT17 or MOT20 (default : MOT20)", + DefaultValueFactory = _ => "MOT20" +}; - var splitOption = new Option( - "--split-to-eval", - getDefaultValue: () => "train", - description: "Data split on which to evalute e.g. train, test (default : train)"); +var splitOption = new Option("--split-to-eval") +{ + Description = "Data split on which to evalute e.g. train, test (default : train)", + DefaultValueFactory = _ => "train" +}; - var rootCommand = new RootCommand - { - dataFolderOption,benchmarkOption,splitOption - }; +var rootCommand = new RootCommand("App to evaluate the SortCS tracking algoritm") +{ + dataFolderOption, + benchmarkOption, + splitOption +}; - rootCommand.Description = "App to evaluate the SortCS tracking algoritm"; - rootCommand.SetHandler(async (dataFolder, benchmark, splitToEval) => - { - if (!dataFolder.Exists || !dataFolder.GetDirectories().Any()) - { - await DownloadTrackEvalExampleAsync(dataFolder); - } - var sortCsEvaluator = new SortCsEvaluator(dataFolder, benchmark, splitToEval, serviceProvider.GetService>()); - await sortCsEvaluator.EvaluateAsync(); - }, dataFolderOption, benchmarkOption, splitOption); +rootCommand.SetAction(async (parseResult, cancellationToken) => +{ + var dataFolder = parseResult.GetValue(dataFolderOption)!; + var benchmark = parseResult.GetValue(benchmarkOption)!; + var splitToEval = parseResult.GetValue(splitOption)!; - return await rootCommand.InvokeAsync(args); + if (!dataFolder.Exists || !dataFolder.GetDirectories().Any()) + { + await DownloadTrackEvalExampleAsync(dataFolder, cancellationToken); } - 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)); + var sortCsEvaluator = new SortCsEvaluator(dataFolder, benchmark, splitToEval, serviceProvider.GetRequiredService>()); + await sortCsEvaluator.EvaluateAsync(); +}); - _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 +return await rootCommand.Parse(args).InvokeAsync(); + +async Task DownloadTrackEvalExampleAsync(DirectoryInfo groundTruthFolder, CancellationToken cancellationToken) +{ + const string dataZipUrl = "https://omnomnom.vision.rwth-aachen.de/data/TrackEval/data.zip"; + groundTruthFolder.Create(); + var targetZipFile = Path.Combine(groundTruthFolder.ToString(), "..", "data.zip"); + logger.LogInformation("{TargetZipFile}", Path.GetFullPath(targetZipFile)); + + logger.LogInformation("Downloading data.zip (150mb) from {DataZipUrl} to {TargetZipFile}", dataZipUrl, targetZipFile); + using var httpClient = new HttpClient(); + var zipStream = await httpClient.GetStreamAsync(dataZipUrl, cancellationToken); + await using var fs = new FileStream(targetZipFile, FileMode.CreateNew); + await zipStream.CopyToAsync(fs, cancellationToken); + ZipFile.ExtractToDirectory(targetZipFile, Path.Combine(groundTruthFolder.ToString(), "..")); + logger.LogInformation("data.zip downloaded & extracted"); +} diff --git a/src/SortCS.Evaluate/SortCS.Evaluate.csproj b/src/SortCS.Evaluate/SortCS.Evaluate.csproj index b5ed445..dbef47d 100644 --- a/src/SortCS.Evaluate/SortCS.Evaluate.csproj +++ b/src/SortCS.Evaluate/SortCS.Evaluate.csproj @@ -2,14 +2,12 @@ Exe - net9.0 - latest - - - + + + diff --git a/src/SortCS.Evaluate/SortCsEvaluator.cs b/src/SortCS.Evaluate/SortCsEvaluator.cs index 987dd12..18694b9 100644 --- a/src/SortCS.Evaluate/SortCsEvaluator.cs +++ b/src/SortCS.Evaluate/SortCsEvaluator.cs @@ -1,13 +1,8 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using System.Linq; -using System.Collections.Generic; -using IniParser; -using System.Globalization; +using System.Diagnostics; using System.Drawing; +using System.Globalization; +using IniParser; using Microsoft.Extensions.Logging; -using System.Diagnostics; namespace SortCS.Evaluate; @@ -33,14 +28,9 @@ public SortCsEvaluator(DirectoryInfo dataFolder, string benchmark, string splitT public async Task EvaluateAsync() { var stopwatch = Stopwatch.StartNew(); - var tasks = new List>(); - foreach (var benchmarkDir in _dataFolderMot.GetDirectories()) - { - tasks.Add(EvaluateBenchMark(benchmarkDir)); - } - await Task.WhenAll(tasks); + var frameCounts = await Task.WhenAll(_dataFolderMot.GetDirectories().Select(EvaluateBenchMark)); stopwatch.Stop(); - var totalFrames = tasks.Sum(x => x.Result); + var totalFrames = frameCounts.Sum(); _logger.LogInformation("Finished evaluating {totalFrames} frames in {totalSeconds:0.} seconds ({fps:0.0} fps)", totalFrames, stopwatch.Elapsed.TotalSeconds, totalFrames / stopwatch.Elapsed.TotalSeconds); } @@ -80,7 +70,7 @@ private async Task EvaluateBenchMark(DirectoryInfo benchmarkFolder) private async Task FramesToDetectionsFile(Dictionary> frames, string path) { - using var file = new StreamWriter(path, false); + await using var file = new StreamWriter(path, false); ITracker tracker = new SortTracker(_logger); foreach (var frame in frames) @@ -88,7 +78,7 @@ private async Task FramesToDetectionsFile(Dictionary> fram var tracks = tracker.Track(frame.Value); foreach (var track in tracks) { - if (track.State == TrackState.Started || track.State == TrackState.Active) + if (track.State is TrackState.Started or TrackState.Active) { //var boxForLog = track.History.Last(); var boxForLog = track.Prediction; @@ -119,20 +109,22 @@ private static async Task>> GetFramesFromFile(F foreach (var line in lines) { var parts = line.Split(','); - var frameId = int.Parse(parts[0]); - var gtTrackId = int.Parse(parts[1]); + var frameId = int.Parse(parts[0], CultureInfo.InvariantCulture); 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)) + + if (!frames.TryGetValue(frameId, out var boxes)) { - frames.Add(frameId, new List()); + boxes = []; + frames[frameId] = boxes; } + if (bbConf > 0) { - frames[frameId].Add(new RectangleF(bbLeft, bbTop, bbWidth, bbHeight)); + boxes.Add(new RectangleF(bbLeft, bbTop, bbWidth, bbHeight)); } } diff --git a/src/SortCS.Tests/Frame.cs b/src/SortCS.Tests/Frame.cs index f3fbcd5..8d192be 100644 --- a/src/SortCS.Tests/Frame.cs +++ b/src/SortCS.Tests/Frame.cs @@ -1,13 +1,8 @@ -using System.Collections.Generic; using System.Drawing; namespace SortCS.Tests; -public class Frame +public class Frame(List boundingBoxes) { - public Frame(List boundingBoxes) - { - BoundingBoxes = boundingBoxes; - } - public List BoundingBoxes { get; set; } -} \ No newline at end of file + public List BoundingBoxes { get; set; } = boundingBoxes; +} diff --git a/src/SortCS.Tests/SortCS.Tests.csproj b/src/SortCS.Tests/SortCS.Tests.csproj index 088a9e3..49367a8 100644 --- a/src/SortCS.Tests/SortCS.Tests.csproj +++ b/src/SortCS.Tests/SortCS.Tests.csproj @@ -1,16 +1,13 @@ - net9.0 - latest false - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/SortCS.Tests/SortTrackerTests.cs b/src/SortCS.Tests/SortTrackerTests.cs index 0bfb7b0..2fec416 100644 --- a/src/SortCS.Tests/SortTrackerTests.cs +++ b/src/SortCS.Tests/SortTrackerTests.cs @@ -1,7 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; using System.Drawing; -using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace SortCS.Tests; @@ -57,7 +55,7 @@ public void SortTracker_FourEasyTracks_TrackedToEnd() } // Assert - Assert.AreEqual(4, tracks.Where(x => x.State == TrackState.Active).Count()); + Assert.AreEqual(4, tracks.Count(x => x.State == TrackState.Active)); } [TestMethod] @@ -117,4 +115,29 @@ public void SortTracker_CrossingTracks_EndInCorrectEndLocation() Assert.AreEqual(5, complexTrack1.History.Count); Assert.AreEqual(5, complexTrack2.History.Count); } + + [TestMethod] + public void SortTracker_MaxMisses_CanBeAdjustedAtRuntime() + { + // Arrange: a tracker that would normally drop a track after a single missed frame. + var sut = new SortTracker(iouThreshold: 0.3f, maxMisses: 1); + var box = new RectangleF(100, 100, 50, 50); + + sut.Track([box]); // Started + sut.Track([box]); // Active + + // Act: widen the allowed occlusion gap on the fly. + sut.MaxMisses = 5; + + Track[] tracks = []; + for (var i = 0; i < 4; i++) + { + tracks = sut.Track([]).ToArray(); // four consecutive misses + } + + // Assert: with MaxMisses raised to 5, the track survives four misses instead of being removed. + Assert.AreEqual(5, sut.MaxMisses); + Assert.IsTrue(tracks.Any(t => t.State == TrackState.Ending)); + Assert.IsFalse(tracks.Any(t => t.State == TrackState.Ended)); + } } \ No newline at end of file diff --git a/src/SortCS/EnumerableExtensions.cs b/src/SortCS/EnumerableExtensions.cs index eeb9fba..b1a00b6 100644 --- a/src/SortCS/EnumerableExtensions.cs +++ b/src/SortCS/EnumerableExtensions.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Linq; - namespace SortCS; internal static class EnumerableExtensions @@ -17,4 +14,4 @@ internal static class EnumerableExtensions return result; } -} \ No newline at end of file +} diff --git a/src/SortCS/ITracker.cs b/src/SortCS/ITracker.cs index 267d5ec..6e092e3 100644 --- a/src/SortCS/ITracker.cs +++ b/src/SortCS/ITracker.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Drawing; namespace SortCS; @@ -6,4 +5,4 @@ namespace SortCS; public interface ITracker { 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 b8475de..1624bf9 100644 --- a/src/SortCS/Kalman/KalmanBoxTracker.cs +++ b/src/SortCS/Kalman/KalmanBoxTracker.cs @@ -1,4 +1,3 @@ -using System; using System.Drawing; namespace SortCS.Kalman; diff --git a/src/SortCS/Kalman/KalmanFilter.cs b/src/SortCS/Kalman/KalmanFilter.cs index 0644312..cf48e15 100644 --- a/src/SortCS/Kalman/KalmanFilter.cs +++ b/src/SortCS/Kalman/KalmanFilter.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; namespace SortCS.Kalman; @@ -13,13 +12,13 @@ internal class KalmanFilter private readonly float _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; + private Matrix _uncertaintyCovariances = null!; // assigned via the UncertaintyCovariances init accessor in the constructor + private Matrix? _pht; + private Matrix? _s; + private Matrix? _si; + private Matrix? _k; + private Matrix? _kh; + private Matrix? _ikh; public KalmanFilter(int stateSize, int measurementSize) { diff --git a/src/SortCS/Kalman/Matrix.cs b/src/SortCS/Kalman/Matrix.cs index 2a60d49..487f82f 100644 --- a/src/SortCS/Kalman/Matrix.cs +++ b/src/SortCS/Kalman/Matrix.cs @@ -1,8 +1,5 @@ -using System; using System.Buffers; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace SortCS.Kalman; @@ -11,6 +8,7 @@ namespace SortCS.Kalman; internal class Matrix { private readonly float[,] _values; + private Matrix? _transposed; public Matrix(float[,] values) { @@ -44,9 +42,9 @@ public Matrix Transposed { get { - if (field != null) + if (_transposed != null) { - return field; + return _transposed; } var result = new float[Columns, Rows]; @@ -59,9 +57,9 @@ public Matrix Transposed } } - field = new Matrix(result); + _transposed = new Matrix(result); - return field; + return _transposed; } } diff --git a/src/SortCS/Kalman/Vector.cs b/src/SortCS/Kalman/Vector.cs index db0b918..fc8055e 100644 --- a/src/SortCS/Kalman/Vector.cs +++ b/src/SortCS/Kalman/Vector.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.Globalization; -using System.Linq; namespace SortCS.Kalman; diff --git a/src/SortCS/SortCS.csproj b/src/SortCS/SortCS.csproj index 7eb3432..4ff1329 100644 --- a/src/SortCS/SortCS.csproj +++ b/src/SortCS/SortCS.csproj @@ -1,25 +1,25 @@ - - net9.0 - preview - true + + true true Kees Schollaart, Maarten van Sambeek Vision Intelligence - AnyCPU - 1.1.0.0 - 1.1.0.0 - 1.1.0.0 + 1.11.0.0 + 1.11.0.0 + 1.11.0 SortCS SortCS is a 'Multiple Object Tracker'. SortCS is a 'Multiple Object Tracker' Object, Tracking, Multiple, AI, Computer Vision, Vision - - Symbols package added to NuGet package. + - Targets .NET 10. +- Updated dependencies to their latest versions. +- SortTracker.MaxMisses can now be adjusted at runtime. false Github https://github.com/keesschollaart81/SortCS.git LICENSE.txt + README.md en https://github.com/keesschollaart81/SortCS true @@ -29,22 +29,22 @@ true embedded - + true - + - - - - + + - + + + diff --git a/src/SortCS/SortTracker.cs b/src/SortCS/SortTracker.cs index da42a51..f9ef06e 100644 --- a/src/SortCS/SortTracker.cs +++ b/src/SortCS/SortTracker.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using System.Drawing; -using System.Linq; using HungarianAlgorithm; using Microsoft.Extensions.Logging; using SortCS.Kalman; @@ -10,7 +8,7 @@ namespace SortCS; public class SortTracker : ITracker { private readonly Dictionary _trackers; - private readonly ILogger _logger; + 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) @@ -28,7 +26,12 @@ public SortTracker(ILogger logger, float iouThreshold = 0.3f, int m public float IouThreshold { get; private init; } - public int MaxMisses { get; private init; } + /// + /// Gets or sets the maximum number of consecutive frames an object may be occluded (unmatched) + /// before its track is ended. Can be adjusted at runtime; the new value takes effect on the next + /// call. + /// + public int MaxMisses { get; set; } public IEnumerable Track(IEnumerable boxes) { @@ -80,7 +83,7 @@ public IEnumerable Track(IEnumerable boxes) var track = new Track { TrackId = _trackerIndex++, - History = new List() { unmatchedBox }, + History = [unmatchedBox], Misses = 0, State = TrackState.Started, TotalMisses = 0, @@ -101,9 +104,8 @@ private void Log(IEnumerable tracks) 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 longest = tracks.Max(x => x.History.Count); + var anyStarted = tracks.Any(x => x.History.Count == 1 && x.Misses == 0); var ended = tracks.Count(x => x.State == TrackState.Ended); if (anyStarted || ended > 0) { diff --git a/src/SortCS/Track.cs b/src/SortCS/Track.cs index 3d50f94..5f4db47 100644 --- a/src/SortCS/Track.cs +++ b/src/SortCS/Track.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Drawing; namespace SortCS; @@ -11,9 +10,9 @@ public record Track public int Misses { get; set; } - public List History { get; set; } + public List History { get; set; } = []; public TrackState State { get; set; } public RectangleF Prediction { get; set; } -} \ No newline at end of file +}