diff --git a/Azaion.Annotator/Controls/MapMatcher.xaml.cs b/Azaion.Annotator/Controls/MapMatcher.xaml.cs index ed8922e..16d1e3b 100644 --- a/Azaion.Annotator/Controls/MapMatcher.xaml.cs +++ b/Azaion.Annotator/Controls/MapMatcher.xaml.cs @@ -1,12 +1,9 @@ using System.Collections.ObjectModel; using System.Diagnostics; -using System.Drawing; using System.IO; using System.Windows; using System.Windows.Controls; -using System.Windows.Input; using System.Windows.Media; -using Azaion.Common; using Azaion.Common.Database; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; @@ -14,7 +11,6 @@ using Azaion.Common.Extensions; using Azaion.Common.Services; using GMap.NET; using GMap.NET.MapProviders; -using GMap.NET.WindowsPresentation; using Microsoft.WindowsAPICodePack.Dialogs; namespace Azaion.Annotator.Controls; @@ -23,7 +19,7 @@ public partial class MapMatcher : UserControl { private AppConfig _appConfig = null!; List _allMediaFiles = new(); - private Dictionary _annotations = new(); + public Dictionary Annotations = new(); private string _currentDir = null!; private IGpsMatcherService _gpsMatcherService = null!; @@ -47,7 +43,7 @@ public partial class MapMatcher : UserControl private async Task OpenGpsLocation(int gpsFilesIndex) { //var media = GpsFiles.Items[gpsFilesIndex] as MediaFileInfo; - var ann = _annotations.GetValueOrDefault(gpsFilesIndex); + var ann = Annotations.GetValueOrDefault(gpsFilesIndex); if (ann == null) return; @@ -101,7 +97,7 @@ public partial class MapMatcher : UserControl _allMediaFiles = mediaFiles; GpsFiles.ItemsSource = new ObservableCollection(_allMediaFiles); - _annotations = mediaFiles.Select((x, i) => (i, new Annotation + Annotations = mediaFiles.Select((x, i) => (i, new Annotation { Name = x.Name, OriginalMediaName = x.Name @@ -110,42 +106,7 @@ public partial class MapMatcher : UserControl var initialLat = double.Parse(TbLat.Text); var initialLon = double.Parse(TbLon.Text); - await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon, async res => await SetMarker(res)); - } - - private Task SetMarker(GpsMatchResult result) - { - Dispatcher.Invoke(() => - { - var marker = new GMapMarker(new PointLatLng(result.Latitude, result.Longitude)); - var ann = _annotations[result.Index]; - marker.Shape = new CircleVisual(marker, System.Windows.Media.Brushes.Blue) - { - Text = ann.Name - }; - SatelliteMap.Markers.Add(marker); - ann.Lat = result.Latitude; - ann.Lon = result.Longitude; - SatelliteMap.Position = new PointLatLng(result.Latitude, result.Longitude); - SatelliteMap.ZoomAndCenterMarkers(null); - }); - return Task.CompletedTask; - } - - private async Task SetFromCsv(List mediaFiles) - { - - var csvResults = GpsMatchResult.ReadFromCsv(Constants.CSV_PATH); - var csvDict = csvResults - .Where(x => x.MatchType == "stitched") - .ToDictionary(x => x.Index); - foreach (var ann in _annotations) - { - var csvRes = csvDict.GetValueOrDefault(ann.Key); - if (csvRes == null) - continue; - await SetMarker(csvRes); - } + await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon); } private async void TestGps(object sender, RoutedEventArgs e) @@ -155,6 +116,6 @@ public partial class MapMatcher : UserControl var initialLat = double.Parse(TbLat.Text); var initialLon = double.Parse(TbLon.Text); - await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLat, initialLon, async res => await SetMarker(res)); + await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLat, initialLon); } } diff --git a/Azaion.Annotator/Controls/MapMatcherEventHandler.cs b/Azaion.Annotator/Controls/MapMatcherEventHandler.cs new file mode 100644 index 0000000..99abd20 --- /dev/null +++ b/Azaion.Annotator/Controls/MapMatcherEventHandler.cs @@ -0,0 +1,28 @@ +using Azaion.Common.Services; +using GMap.NET; +using GMap.NET.WindowsPresentation; +using MediatR; + +namespace Azaion.Annotator.Controls; + +public class MapMatcherEventHandler(MapMatcher mapMatcher) : INotificationHandler +{ + public Task Handle(GPSMatcherResultEvent result, CancellationToken cancellationToken) + { + mapMatcher.Dispatcher.Invoke(() => + { + var marker = new GMapMarker(new PointLatLng(result.Latitude, result.Longitude)); + var ann = mapMatcher.Annotations[result.Index]; + marker.Shape = new CircleVisual(marker, System.Windows.Media.Brushes.Blue) + { + Text = result.Image + }; + mapMatcher.SatelliteMap.Markers.Add(marker); + ann.Lat = result.Latitude; + ann.Lon = result.Longitude; + mapMatcher.SatelliteMap.Position = new PointLatLng(result.Latitude, result.Longitude); + mapMatcher.SatelliteMap.ZoomAndCenterMarkers(null); + }); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Azaion.Common/Constants.cs b/Azaion.Common/Constants.cs index e59e1d7..874d418 100644 --- a/Azaion.Common/Constants.cs +++ b/Azaion.Common/Constants.cs @@ -48,7 +48,8 @@ public class Constants new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() }, new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() }, new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor() }, - new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor() } + new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor() }, + new() { Id = 16, Name = "Caponier", ShortName = "Капонір", Color = "#ffb6c1".ToColor() }, ]; public static readonly List DefaultVideoFormats = ["mp4", "mov", "avi"]; diff --git a/Azaion.Common/DTO/ClusterDistribution.cs b/Azaion.Common/DTO/ClusterDistribution.cs new file mode 100644 index 0000000..6545d51 --- /dev/null +++ b/Azaion.Common/DTO/ClusterDistribution.cs @@ -0,0 +1,11 @@ +using System.Windows.Media; + +namespace Azaion.Common.DTO; + +public class ClusterDistribution +{ + public string Label { get; set; } = ""; + public Color Color { get; set; } + public int ClassCount { get; set; } + public double BarWidth { get; set; } +} \ No newline at end of file diff --git a/Azaion.Common/DTO/GpsMatchResult.cs b/Azaion.Common/DTO/GpsMatchResult.cs deleted file mode 100644 index 53f75fc..0000000 --- a/Azaion.Common/DTO/GpsMatchResult.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace Azaion.Common.DTO; -using System.Collections.Generic; -using System.IO; - -public class GpsMatchResult -{ - public int Index { get; set; } - public string Image { get; set; } = null!; - public double Latitude { get; set; } - public double Longitude { get; set; } - public int KeyPoints { get; set; } - public int Rotation { get; set; } - public string MatchType { get; set; } = null!; - - public static List ReadFromCsv(string csvFilePath) - { - var imageDatas = new List(); - - using var reader = new StreamReader(csvFilePath); - //read header - reader.ReadLine(); - if (reader.EndOfStream) - return new List(); - - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - if (string.IsNullOrWhiteSpace(line)) - continue; - var values = line.Split(','); - if (values.Length == 6) - { - imageDatas.Add(new GpsMatchResult - { - Image = GetFilename(values[0]), - Latitude = double.Parse(values[1]), - Longitude = double.Parse(values[2]), - KeyPoints = int.Parse(values[3]), - Rotation = int.Parse(values[4]), - MatchType = values[5] - }); - } - } - - return imageDatas; - } - - private static string GetFilename(string imagePath) => - Path.GetFileNameWithoutExtension(imagePath) - .Replace("-small", ""); -} \ No newline at end of file diff --git a/Azaion.Common/Extensions/BitmapExtensions.cs b/Azaion.Common/Extensions/BitmapExtensions.cs index 4e13bee..294a8bb 100644 --- a/Azaion.Common/Extensions/BitmapExtensions.cs +++ b/Azaion.Common/Extensions/BitmapExtensions.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Windows.Media; using System.Windows.Media.Imaging; namespace Azaion.Common.Extensions; @@ -22,4 +23,7 @@ public static class BitmapExtensions image.Freeze(); return image; } + + public static Color CreateTransparent(this Color color, byte transparency) => + Color.FromArgb(transparency, color.R, color.G, color.B); } \ No newline at end of file diff --git a/Azaion.Common/Services/GPSMatcherEvents.cs b/Azaion.Common/Services/GPSMatcherEvents.cs new file mode 100644 index 0000000..ec1cb95 --- /dev/null +++ b/Azaion.Common/Services/GPSMatcherEvents.cs @@ -0,0 +1,18 @@ +using MediatR; + +namespace Azaion.Common.Services; + +public class GPSMatcherResultEvent : INotification +{ + public int Index { get; set; } + public string Image { get; set; } = null!; + public double Latitude { get; set; } + public double Longitude { get; set; } + public int KeyPoints { get; set; } + public int Rotation { get; set; } + public string MatchType { get; set; } = null!; +} + +public class GPSMatcherJobAcceptedEvent : INotification {} + +public class GPSMatcherFinishedEvent : INotification {} \ No newline at end of file diff --git a/Azaion.Common/Services/GPSMatcherService.cs b/Azaion.Common/Services/GPSMatcherService.cs index 03c4370..9962de9 100644 --- a/Azaion.Common/Services/GPSMatcherService.cs +++ b/Azaion.Common/Services/GPSMatcherService.cs @@ -1,74 +1,102 @@ -using System.Diagnostics; -using System.IO; -using Azaion.Common.DTO; -using Azaion.Common.DTO.Config; +using System.IO; using Azaion.CommonSecurity; using Azaion.CommonSecurity.DTO; +using MediatR; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; namespace Azaion.Common.Services; public interface IGpsMatcherService { - Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, Func processResult, CancellationToken detectToken = default); + Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, CancellationToken detectToken = default); void StopGpsMatching(); } -public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDownloader satelliteTileDownloader, IOptions dirConfig) : IGpsMatcherService +public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDownloader satelliteTileDownloader, IOptions dirConfig) + : IGpsMatcherService, + INotificationHandler, + INotificationHandler { + private readonly IGpsMatcherClient _gpsMatcherClient = gpsMatcherClient; + private readonly DirectoriesConfig _dirConfig = dirConfig.Value; private const int ZOOM_LEVEL = 18; private const int POINTS_COUNT = 10; private const int DISTANCE_BETWEEN_POINTS_M = 100; private const double SATELLITE_RADIUS_M = DISTANCE_BETWEEN_POINTS_M * (POINTS_COUNT + 1); - public async Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, Func processResult, CancellationToken detectToken = default) + private string _routeDir = ""; + private string _userRouteDir = ""; + private List _allRouteImages = new(); + private Dictionary _currentRouteImages = new(); + private double _currentLat; + private double _currentLon; + private CancellationToken _detectToken; + private int _currentIndex; + + + public async Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, CancellationToken detectToken = default) { - var currentLat = initialLatitude; - var currentLon = initialLongitude; + _routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, _dirConfig.GpsRouteDirectory); + _userRouteDir = userRouteDir; - var routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, dirConfig.Value.GpsRouteDirectory); - if (Directory.Exists(routeDir)) - Directory.Delete(routeDir, true); - Directory.CreateDirectory(routeDir); + _allRouteImages = Directory.GetFiles(userRouteDir) + .OrderBy(x => x).ToList(); - var routeFiles = new List(); - foreach (var file in Directory.GetFiles(userRouteDir)) - { - routeFiles.Add(file); - File.Copy(file, Path.Combine(routeDir, Path.GetFileName(file))); - } + _currentLat = initialLatitude; + _currentLon = initialLongitude; - var indexOffset = 0; - while (routeFiles.Any()) - { - await satelliteTileDownloader.GetTiles(currentLat, currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, detectToken); - gpsMatcherClient.StartMatching(new StartMatchingEvent + _detectToken = detectToken; + await StartMatchingRound(0); + } + + private async Task StartMatchingRound(int startIndex) + { + //empty route dir + if (Directory.Exists(_routeDir)) + Directory.Delete(_routeDir, true); + Directory.CreateDirectory(_routeDir); + + _currentRouteImages = _allRouteImages + .Skip(startIndex) + .Take(POINTS_COUNT) + .Select((fullName, index) => { - ImagesCount = POINTS_COUNT, - Latitude = initialLatitude, - Longitude = initialLongitude, - SatelliteImagesDir = dirConfig.Value.GpsSatDirectory, - RouteDir = dirConfig.Value.GpsRouteDirectory - }); + var filename = Path.GetFileName(fullName); + File.Copy(Path.Combine(_userRouteDir, filename), Path.Combine(_routeDir, filename)); + return new { Filename = filename, Index = startIndex + index }; + }) + .ToDictionary(x => x.Filename, x => x.Index); - while (true) - { - var result = gpsMatcherClient.GetResult(); - if (result == null) - break; - result.Index += indexOffset; - await processResult(result); - currentLat = result.Latitude; - currentLon = result.Longitude; - routeFiles.RemoveAt(0); - } - indexOffset += POINTS_COUNT; - } + await satelliteTileDownloader.GetTiles(_currentLat, _currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken); + _gpsMatcherClient.StartMatching(new StartMatchingEvent + { + ImagesCount = POINTS_COUNT, + Latitude = _currentLat, + Longitude = _currentLon, + SatelliteImagesDir = _dirConfig.GpsSatDirectory, + RouteDir = _dirConfig.GpsRouteDirectory + }); } public void StopGpsMatching() { gpsMatcherClient.Stop(); } + + public Task Handle(GPSMatcherResultEvent result, CancellationToken cancellationToken) + { + _currentRouteImages.Remove(result.Image); + _currentLat = result.Latitude; + _currentLon = result.Longitude; + _currentIndex = _currentRouteImages[result.Image]; + return Task.CompletedTask; + } + + public async Task Handle(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken) + { + if (_currentRouteImages.Count == 0 && _currentIndex < _allRouteImages.Count) + await StartMatchingRound(_currentIndex); + } } diff --git a/Azaion.Common/Services/GpsMatcherClient.cs b/Azaion.Common/Services/GpsMatcherClient.cs index 12db99d..a527220 100644 --- a/Azaion.Common/Services/GpsMatcherClient.cs +++ b/Azaion.Common/Services/GpsMatcherClient.cs @@ -1,18 +1,16 @@ using System.Diagnostics; -using Azaion.Common.DTO; using Azaion.CommonSecurity; using Azaion.CommonSecurity.DTO; +using MediatR; using Microsoft.Extensions.Options; using NetMQ; using NetMQ.Sockets; namespace Azaion.Common.Services; -public interface IGpsMatcherClient +public interface IGpsMatcherClient : IDisposable { - void StartMatching(StartMatchingEvent startEvent); - GpsMatchResult? GetResult(int retries = 2, int tryTimeoutSeconds = 5, CancellationToken ct = default); void Stop(); } @@ -33,17 +31,22 @@ public class StartMatchingEvent public class GpsMatcherClient : IGpsMatcherClient { + private readonly IMediator _mediator; private readonly GpsDeniedClientConfig _gpsDeniedClientConfig; + private string _requestAddress; private readonly RequestSocket _requestSocket = new(); + private string _subscriberAddress; private readonly SubscriberSocket _subscriberSocket = new(); - public GpsMatcherClient(IOptions gpsDeniedClientConfig) + + public GpsMatcherClient(IMediator mediator, IOptions gpsDeniedClientConfig) { + _mediator = mediator; _gpsDeniedClientConfig = gpsDeniedClientConfig.Value; Start(); } - private void Start() + private void Start(CancellationToken ct = default) { try { @@ -60,58 +63,70 @@ public class GpsMatcherClient : IGpsMatcherClient process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; - //process.Start(); + process.Start(); } catch (Exception e) { Console.WriteLine(e); //throw; } - _requestSocket.Connect($"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqPort}"); - _subscriberSocket.Connect($"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqSubscriberPort}"); + + _requestAddress = $"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqPort}"; + _requestSocket.Connect(_requestAddress); + + _subscriberAddress = $"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqSubscriberPort}"; + _subscriberSocket.Connect(_subscriberAddress); _subscriberSocket.Subscribe(""); + _subscriberSocket.ReceiveReady += async (_, e) => await ProcessClientCommand(e.Socket, ct); + } + + private async Task ProcessClientCommand(NetMQSocket socket, CancellationToken ct) + { + while (socket.TryReceiveFrameString(TimeSpan.Zero, out var str)) + { + if (string.IsNullOrEmpty(str)) + continue; + + switch (str) + { + case "FINISHED": + await _mediator.Publish(new GPSMatcherFinishedEvent(), ct); + break; + case "OK": + await _mediator.Publish(new GPSMatcherJobAcceptedEvent(), ct); + break; + default: + var parts = str.Split(','); + if (parts.Length != 5) + throw new Exception("Matching Result Failed"); + + await _mediator.Publish(new GPSMatcherResultEvent + { + Index = int.Parse(parts[0]), + Image = parts[1], + Latitude = double.Parse(parts[2]), + Longitude = double.Parse(parts[3]), + MatchType = parts[4] + }, ct); + break; + } + } } public void StartMatching(StartMatchingEvent e) { _requestSocket.SendFrame(e.ToString()); - var response = _requestSocket.ReceiveFrameString(); - if (response != "OK") - throw new Exception("Start Matching Failed"); } - public GpsMatchResult? GetResult(int retries = 15, int tryTimeoutSeconds = 5, CancellationToken ct = default) + public void Stop() => _requestSocket.SendFrame("STOP"); + + public void Dispose() { - var tryNum = 0; - while (!ct.IsCancellationRequested && tryNum++ < retries) - { - if (!_subscriberSocket.TryReceiveFrameString(TimeSpan.FromSeconds(tryTimeoutSeconds), out var update)) - continue; - if (update == "FINISHED") - return null; + _requestSocket.SendFrame("EXIT"); + _requestSocket.Disconnect(_requestAddress); + _requestSocket.Dispose(); - var parts = update.Split(','); - if (parts.Length != 5) - throw new Exception("Matching Result Failed"); - - return new GpsMatchResult - { - Index = int.Parse(parts[0]), - Image = parts[1], - Latitude = double.Parse(parts[2]), - Longitude = double.Parse(parts[3]), - MatchType = parts[4] - }; - } - - if (!ct.IsCancellationRequested) - throw new Exception($"Unable to get bytes after {tryNum} retries, {tryTimeoutSeconds} seconds each"); - - return null; - } - - public void Stop() - { - _requestSocket.SendFrame("STOP"); + _subscriberSocket.Disconnect(_subscriberAddress); + _subscriberSocket.Dispose(); } } \ No newline at end of file diff --git a/Azaion.Dataset/Controls/ClassDistribution.xaml b/Azaion.Dataset/Controls/ClassDistribution.xaml new file mode 100644 index 0000000..416470d --- /dev/null +++ b/Azaion.Dataset/Controls/ClassDistribution.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Azaion.Dataset/Controls/ClassDistribution.xaml.cs b/Azaion.Dataset/Controls/ClassDistribution.xaml.cs new file mode 100644 index 0000000..264792a --- /dev/null +++ b/Azaion.Dataset/Controls/ClassDistribution.xaml.cs @@ -0,0 +1,22 @@ +using System.Windows; +using System.Windows.Controls; +using Azaion.Common.DTO; + +namespace Azaion.Dataset.Controls; + +public partial class ClassDistribution : UserControl +{ + public static readonly DependencyProperty ItemsProperty = + DependencyProperty.Register(nameof(Items), typeof(IEnumerable), typeof(ClassDistribution), new PropertyMetadata(null)); + + public IEnumerable Items + { + get => (IEnumerable)GetValue(ItemsProperty); + set => SetValue(ItemsProperty, value); + } + + public ClassDistribution() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Azaion.Dataset/Controls/ClassDistrtibutionProportionWidthConverter.cs b/Azaion.Dataset/Controls/ClassDistrtibutionProportionWidthConverter.cs new file mode 100644 index 0000000..9d8731a --- /dev/null +++ b/Azaion.Dataset/Controls/ClassDistrtibutionProportionWidthConverter.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using System.Windows.Data; + +namespace Azaion.Dataset.Controls +{ + public class ProportionToWidthConverter : IMultiValueConverter + { + private const double MinPixelBarWidth = 2.0; + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null || values.Length < 2 || + !(values[0] is double proportion) || + !(values[1] is double containerActualWidth)) + return MinPixelBarWidth; // Default or fallback width + + if (containerActualWidth <= 0 || !double.IsFinite(containerActualWidth) || double.IsNaN(containerActualWidth)) + return MinPixelBarWidth; // Container not ready or invalid + + double calculatedWidth = proportion * containerActualWidth; + + if (proportion >= 0 && calculatedWidth < MinPixelBarWidth) + return MinPixelBarWidth; + + return Math.Max(0, calculatedWidth); // Ensure width is not negative + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Azaion.Dataset/DatasetExplorer.xaml b/Azaion.Dataset/DatasetExplorer.xaml index c15c4ec..16debd8 100644 --- a/Azaion.Dataset/DatasetExplorer.xaml +++ b/Azaion.Dataset/DatasetExplorer.xaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel" - xmlns:scottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF" xmlns:controls="clr-namespace:Azaion.Common.Controls;assembly=Azaion.Common" xmlns:dto="clr-namespace:Azaion.Common.DTO;assembly=Azaion.Common" + xmlns:controls1="clr-namespace:Azaion.Dataset.Controls" mc:Ignorable="d" Title="Переглядач анотацій" Height="900" Width="1200" WindowState="Maximized"> @@ -104,7 +104,7 @@ - + x.Key != -1) - .Select(gr => new + .OrderBy(x => x.Key) + .Select(gr => new ClusterDistribution { - gr.Key, - _annotationConfig.DetectionClassesDict[gr.Key].ShortName, - _annotationConfig.DetectionClassesDict[gr.Key].Color, + Label = $"{_annotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}", + Color = _annotationConfig.DetectionClassesDict[gr.Key].Color, ClassCount = gr.Value.Count }) + .Where(x => x.ClassCount > 0) .ToList(); - var foregroundColor = Color.FromColor(System.Drawing.Color.Black); + var maxClassCount = Math.Max(1, data.Max(x => x.ClassCount)); - var bars = data.Select(x => new Bar + foreach (var cl in data) { - Orientation = Orientation.Horizontal, - Position = -1.5 * x.Key + 1, - Label = x.ClassCount > 200 ? x.ClassCount.ToString() : "", - FillColor = new Color(x.Color.R, x.Color.G, x.Color.B, x.Color.A), - Value = x.ClassCount, - CenterLabel = true, - LabelOffset = 10 - }).ToList(); - - ClassDistribution.Plot.Add.Bars(bars); - - foreach (var x in data) - { - var label = ClassDistribution.Plot.Add.Text(x.ShortName, 50, -1.5 * x.Key + 1.1); - label.LabelFontColor = foregroundColor; - label.LabelFontSize = 18; + cl.Color = cl.Color.CreateTransparent(150); + cl.BarWidth = Math.Clamp(cl.ClassCount / (double)maxClassCount, 0, 1); } - ClassDistribution.Plot.Axes.AutoScale(); - ClassDistribution.Plot.HideAxesAndGrid(); - ClassDistribution.Plot.FigureBackground.Color = new("#888888"); - - ClassDistribution.Refresh(); - await Task.CompletedTask; + ClassDistributionPlot.Items = data; } private async void RefreshThumbnailsBtnClick(object sender, RoutedEventArgs e) diff --git a/Azaion.Suite/MainSuite.xaml.cs b/Azaion.Suite/MainSuite.xaml.cs index 9a96a9a..fbce226 100644 --- a/Azaion.Suite/MainSuite.xaml.cs +++ b/Azaion.Suite/MainSuite.xaml.cs @@ -144,7 +144,7 @@ public partial class MainSuite window.Value.Close(); _inferenceClient.Dispose(); - _gpsMatcherClient.Stop(); + _gpsMatcherClient.Dispose(); Application.Current.Shutdown(); }