mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 21:46:30 +00:00
better view for class distribution
This commit is contained in:
@@ -1,12 +1,9 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Azaion.Common;
|
|
||||||
using Azaion.Common.Database;
|
using Azaion.Common.Database;
|
||||||
using Azaion.Common.DTO;
|
using Azaion.Common.DTO;
|
||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
@@ -14,7 +11,6 @@ using Azaion.Common.Extensions;
|
|||||||
using Azaion.Common.Services;
|
using Azaion.Common.Services;
|
||||||
using GMap.NET;
|
using GMap.NET;
|
||||||
using GMap.NET.MapProviders;
|
using GMap.NET.MapProviders;
|
||||||
using GMap.NET.WindowsPresentation;
|
|
||||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Controls;
|
namespace Azaion.Annotator.Controls;
|
||||||
@@ -23,7 +19,7 @@ public partial class MapMatcher : UserControl
|
|||||||
{
|
{
|
||||||
private AppConfig _appConfig = null!;
|
private AppConfig _appConfig = null!;
|
||||||
List<MediaFileInfo> _allMediaFiles = new();
|
List<MediaFileInfo> _allMediaFiles = new();
|
||||||
private Dictionary<int, Annotation> _annotations = new();
|
public Dictionary<int, Annotation> Annotations = new();
|
||||||
private string _currentDir = null!;
|
private string _currentDir = null!;
|
||||||
private IGpsMatcherService _gpsMatcherService = null!;
|
private IGpsMatcherService _gpsMatcherService = null!;
|
||||||
|
|
||||||
@@ -47,7 +43,7 @@ public partial class MapMatcher : UserControl
|
|||||||
private async Task OpenGpsLocation(int gpsFilesIndex)
|
private async Task OpenGpsLocation(int gpsFilesIndex)
|
||||||
{
|
{
|
||||||
//var media = GpsFiles.Items[gpsFilesIndex] as MediaFileInfo;
|
//var media = GpsFiles.Items[gpsFilesIndex] as MediaFileInfo;
|
||||||
var ann = _annotations.GetValueOrDefault(gpsFilesIndex);
|
var ann = Annotations.GetValueOrDefault(gpsFilesIndex);
|
||||||
if (ann == null)
|
if (ann == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -101,7 +97,7 @@ public partial class MapMatcher : UserControl
|
|||||||
_allMediaFiles = mediaFiles;
|
_allMediaFiles = mediaFiles;
|
||||||
GpsFiles.ItemsSource = new ObservableCollection<MediaFileInfo>(_allMediaFiles);
|
GpsFiles.ItemsSource = new ObservableCollection<MediaFileInfo>(_allMediaFiles);
|
||||||
|
|
||||||
_annotations = mediaFiles.Select((x, i) => (i, new Annotation
|
Annotations = mediaFiles.Select((x, i) => (i, new Annotation
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
OriginalMediaName = x.Name
|
OriginalMediaName = x.Name
|
||||||
@@ -110,42 +106,7 @@ public partial class MapMatcher : UserControl
|
|||||||
var initialLat = double.Parse(TbLat.Text);
|
var initialLat = double.Parse(TbLat.Text);
|
||||||
var initialLon = double.Parse(TbLon.Text);
|
var initialLon = double.Parse(TbLon.Text);
|
||||||
|
|
||||||
await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon, async res => await SetMarker(res));
|
await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon);
|
||||||
}
|
|
||||||
|
|
||||||
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<MediaFileInfo> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void TestGps(object sender, RoutedEventArgs e)
|
private async void TestGps(object sender, RoutedEventArgs e)
|
||||||
@@ -155,6 +116,6 @@ public partial class MapMatcher : UserControl
|
|||||||
|
|
||||||
var initialLat = double.Parse(TbLat.Text);
|
var initialLat = double.Parse(TbLat.Text);
|
||||||
var initialLon = double.Parse(TbLon.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<GPSMatcherResultEvent>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,8 @@ public class Constants
|
|||||||
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() },
|
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() },
|
||||||
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() },
|
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() },
|
||||||
new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".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<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
|
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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<GpsMatchResult> ReadFromCsv(string csvFilePath)
|
|
||||||
{
|
|
||||||
var imageDatas = new List<GpsMatchResult>();
|
|
||||||
|
|
||||||
using var reader = new StreamReader(csvFilePath);
|
|
||||||
//read header
|
|
||||||
reader.ReadLine();
|
|
||||||
if (reader.EndOfStream)
|
|
||||||
return new List<GpsMatchResult>();
|
|
||||||
|
|
||||||
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", "");
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace Azaion.Common.Extensions;
|
namespace Azaion.Common.Extensions;
|
||||||
@@ -22,4 +23,7 @@ public static class BitmapExtensions
|
|||||||
image.Freeze();
|
image.Freeze();
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Color CreateTransparent(this Color color, byte transparency) =>
|
||||||
|
Color.FromArgb(transparency, color.R, color.G, color.B);
|
||||||
}
|
}
|
||||||
@@ -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 {}
|
||||||
@@ -1,74 +1,102 @@
|
|||||||
using System.Diagnostics;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
using Azaion.Common.DTO;
|
|
||||||
using Azaion.Common.DTO.Config;
|
|
||||||
using Azaion.CommonSecurity;
|
using Azaion.CommonSecurity;
|
||||||
using Azaion.CommonSecurity.DTO;
|
using Azaion.CommonSecurity.DTO;
|
||||||
|
using MediatR;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace Azaion.Common.Services;
|
namespace Azaion.Common.Services;
|
||||||
|
|
||||||
public interface IGpsMatcherService
|
public interface IGpsMatcherService
|
||||||
{
|
{
|
||||||
Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, Func<GpsMatchResult, Task> processResult, CancellationToken detectToken = default);
|
Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, CancellationToken detectToken = default);
|
||||||
void StopGpsMatching();
|
void StopGpsMatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDownloader satelliteTileDownloader, IOptions<DirectoriesConfig> dirConfig) : IGpsMatcherService
|
public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDownloader satelliteTileDownloader, IOptions<DirectoriesConfig> dirConfig)
|
||||||
|
: IGpsMatcherService,
|
||||||
|
INotificationHandler<GPSMatcherResultEvent>,
|
||||||
|
INotificationHandler<GPSMatcherFinishedEvent>
|
||||||
{
|
{
|
||||||
|
private readonly IGpsMatcherClient _gpsMatcherClient = gpsMatcherClient;
|
||||||
|
private readonly DirectoriesConfig _dirConfig = dirConfig.Value;
|
||||||
private const int ZOOM_LEVEL = 18;
|
private const int ZOOM_LEVEL = 18;
|
||||||
private const int POINTS_COUNT = 10;
|
private const int POINTS_COUNT = 10;
|
||||||
private const int DISTANCE_BETWEEN_POINTS_M = 100;
|
private const int DISTANCE_BETWEEN_POINTS_M = 100;
|
||||||
private const double SATELLITE_RADIUS_M = DISTANCE_BETWEEN_POINTS_M * (POINTS_COUNT + 1);
|
private const double SATELLITE_RADIUS_M = DISTANCE_BETWEEN_POINTS_M * (POINTS_COUNT + 1);
|
||||||
|
|
||||||
public async Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, Func<GpsMatchResult, Task> processResult, CancellationToken detectToken = default)
|
private string _routeDir = "";
|
||||||
{
|
private string _userRouteDir = "";
|
||||||
var currentLat = initialLatitude;
|
private List<string> _allRouteImages = new();
|
||||||
var currentLon = initialLongitude;
|
private Dictionary<string, int> _currentRouteImages = new();
|
||||||
|
private double _currentLat;
|
||||||
|
private double _currentLon;
|
||||||
|
private CancellationToken _detectToken;
|
||||||
|
private int _currentIndex;
|
||||||
|
|
||||||
var routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, dirConfig.Value.GpsRouteDirectory);
|
|
||||||
if (Directory.Exists(routeDir))
|
|
||||||
Directory.Delete(routeDir, true);
|
|
||||||
Directory.CreateDirectory(routeDir);
|
|
||||||
|
|
||||||
var routeFiles = new List<string>();
|
public async Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, CancellationToken detectToken = default)
|
||||||
foreach (var file in Directory.GetFiles(userRouteDir))
|
|
||||||
{
|
{
|
||||||
routeFiles.Add(file);
|
_routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, _dirConfig.GpsRouteDirectory);
|
||||||
File.Copy(file, Path.Combine(routeDir, Path.GetFileName(file)));
|
_userRouteDir = userRouteDir;
|
||||||
|
|
||||||
|
_allRouteImages = Directory.GetFiles(userRouteDir)
|
||||||
|
.OrderBy(x => x).ToList();
|
||||||
|
|
||||||
|
_currentLat = initialLatitude;
|
||||||
|
_currentLon = initialLongitude;
|
||||||
|
|
||||||
|
_detectToken = detectToken;
|
||||||
|
await StartMatchingRound(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexOffset = 0;
|
private async Task StartMatchingRound(int startIndex)
|
||||||
while (routeFiles.Any())
|
|
||||||
{
|
{
|
||||||
await satelliteTileDownloader.GetTiles(currentLat, currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, detectToken);
|
//empty route dir
|
||||||
gpsMatcherClient.StartMatching(new StartMatchingEvent
|
if (Directory.Exists(_routeDir))
|
||||||
|
Directory.Delete(_routeDir, true);
|
||||||
|
Directory.CreateDirectory(_routeDir);
|
||||||
|
|
||||||
|
_currentRouteImages = _allRouteImages
|
||||||
|
.Skip(startIndex)
|
||||||
|
.Take(POINTS_COUNT)
|
||||||
|
.Select((fullName, index) =>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
await satelliteTileDownloader.GetTiles(_currentLat, _currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken);
|
||||||
|
_gpsMatcherClient.StartMatching(new StartMatchingEvent
|
||||||
{
|
{
|
||||||
ImagesCount = POINTS_COUNT,
|
ImagesCount = POINTS_COUNT,
|
||||||
Latitude = initialLatitude,
|
Latitude = _currentLat,
|
||||||
Longitude = initialLongitude,
|
Longitude = _currentLon,
|
||||||
SatelliteImagesDir = dirConfig.Value.GpsSatDirectory,
|
SatelliteImagesDir = _dirConfig.GpsSatDirectory,
|
||||||
RouteDir = dirConfig.Value.GpsRouteDirectory
|
RouteDir = _dirConfig.GpsRouteDirectory
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopGpsMatching()
|
public void StopGpsMatching()
|
||||||
{
|
{
|
||||||
gpsMatcherClient.Stop();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Azaion.Common.DTO;
|
|
||||||
using Azaion.CommonSecurity;
|
using Azaion.CommonSecurity;
|
||||||
using Azaion.CommonSecurity.DTO;
|
using Azaion.CommonSecurity.DTO;
|
||||||
|
using MediatR;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NetMQ;
|
using NetMQ;
|
||||||
using NetMQ.Sockets;
|
using NetMQ.Sockets;
|
||||||
|
|
||||||
namespace Azaion.Common.Services;
|
namespace Azaion.Common.Services;
|
||||||
|
|
||||||
public interface IGpsMatcherClient
|
public interface IGpsMatcherClient : IDisposable
|
||||||
{
|
{
|
||||||
|
|
||||||
void StartMatching(StartMatchingEvent startEvent);
|
void StartMatching(StartMatchingEvent startEvent);
|
||||||
GpsMatchResult? GetResult(int retries = 2, int tryTimeoutSeconds = 5, CancellationToken ct = default);
|
|
||||||
void Stop();
|
void Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,17 +31,22 @@ public class StartMatchingEvent
|
|||||||
|
|
||||||
public class GpsMatcherClient : IGpsMatcherClient
|
public class GpsMatcherClient : IGpsMatcherClient
|
||||||
{
|
{
|
||||||
|
private readonly IMediator _mediator;
|
||||||
private readonly GpsDeniedClientConfig _gpsDeniedClientConfig;
|
private readonly GpsDeniedClientConfig _gpsDeniedClientConfig;
|
||||||
|
private string _requestAddress;
|
||||||
private readonly RequestSocket _requestSocket = new();
|
private readonly RequestSocket _requestSocket = new();
|
||||||
|
private string _subscriberAddress;
|
||||||
private readonly SubscriberSocket _subscriberSocket = new();
|
private readonly SubscriberSocket _subscriberSocket = new();
|
||||||
|
|
||||||
public GpsMatcherClient(IOptions<GpsDeniedClientConfig> gpsDeniedClientConfig)
|
|
||||||
|
public GpsMatcherClient(IMediator mediator, IOptions<GpsDeniedClientConfig> gpsDeniedClientConfig)
|
||||||
{
|
{
|
||||||
|
_mediator = mediator;
|
||||||
_gpsDeniedClientConfig = gpsDeniedClientConfig.Value;
|
_gpsDeniedClientConfig = gpsDeniedClientConfig.Value;
|
||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -60,58 +63,70 @@ public class GpsMatcherClient : IGpsMatcherClient
|
|||||||
|
|
||||||
process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
|
process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); };
|
||||||
process.ErrorDataReceived += (_, 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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
//throw;
|
//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.Subscribe("");
|
||||||
|
_subscriberSocket.ReceiveReady += async (_, e) => await ProcessClientCommand(e.Socket, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartMatching(StartMatchingEvent e)
|
private async Task ProcessClientCommand(NetMQSocket socket, CancellationToken ct)
|
||||||
{
|
{
|
||||||
_requestSocket.SendFrame(e.ToString());
|
while (socket.TryReceiveFrameString(TimeSpan.Zero, out var str))
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var tryNum = 0;
|
if (string.IsNullOrEmpty(str))
|
||||||
while (!ct.IsCancellationRequested && tryNum++ < retries)
|
|
||||||
{
|
|
||||||
if (!_subscriberSocket.TryReceiveFrameString(TimeSpan.FromSeconds(tryTimeoutSeconds), out var update))
|
|
||||||
continue;
|
continue;
|
||||||
if (update == "FINISHED")
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var parts = update.Split(',');
|
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)
|
if (parts.Length != 5)
|
||||||
throw new Exception("Matching Result Failed");
|
throw new Exception("Matching Result Failed");
|
||||||
|
|
||||||
return new GpsMatchResult
|
await _mediator.Publish(new GPSMatcherResultEvent
|
||||||
{
|
{
|
||||||
Index = int.Parse(parts[0]),
|
Index = int.Parse(parts[0]),
|
||||||
Image = parts[1],
|
Image = parts[1],
|
||||||
Latitude = double.Parse(parts[2]),
|
Latitude = double.Parse(parts[2]),
|
||||||
Longitude = double.Parse(parts[3]),
|
Longitude = double.Parse(parts[3]),
|
||||||
MatchType = parts[4]
|
MatchType = parts[4]
|
||||||
};
|
}, ct);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ct.IsCancellationRequested)
|
public void StartMatching(StartMatchingEvent e)
|
||||||
throw new Exception($"Unable to get bytes after {tryNum} retries, {tryTimeoutSeconds} seconds each");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
{
|
||||||
_requestSocket.SendFrame("STOP");
|
_requestSocket.SendFrame(e.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop() => _requestSocket.SendFrame("STOP");
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_requestSocket.SendFrame("EXIT");
|
||||||
|
_requestSocket.Disconnect(_requestAddress);
|
||||||
|
_requestSocket.Dispose();
|
||||||
|
|
||||||
|
_subscriberSocket.Disconnect(_subscriberAddress);
|
||||||
|
_subscriberSocket.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<UserControl x:Class="Azaion.Dataset.Controls.ClassDistribution"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:controls="clr-namespace:Azaion.Dataset.Controls"
|
||||||
|
xmlns:dto="clr-namespace:Azaion.Common.DTO;assembly=Azaion.Common"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300" d:DesignWidth="400"
|
||||||
|
FontFamily="Segoe UI">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<controls:ProportionToWidthConverter x:Key="ProportionToWidthConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<ListView ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType=controls:ClassDistribution}}"
|
||||||
|
BorderThickness="0" Background="#FF333333" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<ListView.ItemContainerStyle>
|
||||||
|
<Style TargetType="ListViewItem">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="Focusable" Value="False"/>
|
||||||
|
<Setter Property="Margin" Value="0,1"/>
|
||||||
|
</Style>
|
||||||
|
</ListView.ItemContainerStyle>
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type dto:ClusterDistribution}">
|
||||||
|
<Grid Height="18" x:Name="ItemGrid"> <!-- Give the Grid a name -->
|
||||||
|
<Border HorizontalAlignment="Left" VerticalAlignment="Stretch">
|
||||||
|
<Border.Width>
|
||||||
|
<MultiBinding Converter="{StaticResource ProportionToWidthConverter}">
|
||||||
|
<Binding Path="BarWidth"/>
|
||||||
|
<Binding Path="ActualWidth" ElementName="ItemGrid"/>
|
||||||
|
</MultiBinding>
|
||||||
|
</Border.Width>
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="{Binding Color}" Opacity="0.5"/>
|
||||||
|
</Border.Background>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" Margin="5,0,0,0" Foreground="White" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding ClassCount}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,5,0" Foreground="White" FontSize="12"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</UserControl>
|
||||||
@@ -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<ClusterDistribution>), typeof(ClassDistribution), new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public IEnumerable<ClusterDistribution> Items
|
||||||
|
{
|
||||||
|
get => (IEnumerable<ClusterDistribution>)GetValue(ItemsProperty);
|
||||||
|
set => SetValue(ItemsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassDistribution()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
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:controls="clr-namespace:Azaion.Common.Controls;assembly=Azaion.Common"
|
||||||
xmlns:dto="clr-namespace:Azaion.Common.DTO;assembly=Azaion.Common"
|
xmlns:dto="clr-namespace:Azaion.Common.DTO;assembly=Azaion.Common"
|
||||||
|
xmlns:controls1="clr-namespace:Azaion.Dataset.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Переглядач анотацій" Height="900" Width="1200"
|
Title="Переглядач анотацій" Height="900" Width="1200"
|
||||||
WindowState="Maximized">
|
WindowState="Maximized">
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</controls:CanvasEditor>
|
</controls:CanvasEditor>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Name="ClassDistributionTab" Header="Розподіл класів">
|
<TabItem Name="ClassDistributionTab" Header="Розподіл класів">
|
||||||
<scottPlot:WpfPlot x:Name="ClassDistribution" />
|
<controls1:ClassDistribution x:Name="ClassDistributionPlot"/>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Azaion.Common.Database;
|
|||||||
using Azaion.Common.DTO;
|
using Azaion.Common.DTO;
|
||||||
using Azaion.Common.DTO.Config;
|
using Azaion.Common.DTO.Config;
|
||||||
using Azaion.Common.Events;
|
using Azaion.Common.Events;
|
||||||
|
using Azaion.Common.Extensions;
|
||||||
using Azaion.Common.Services;
|
using Azaion.Common.Services;
|
||||||
using Azaion.CommonSecurity.DTO;
|
using Azaion.CommonSecurity.DTO;
|
||||||
using Azaion.CommonSecurity.Services;
|
using Azaion.CommonSecurity.Services;
|
||||||
@@ -13,8 +14,6 @@ using LinqToDB;
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using ScottPlot;
|
|
||||||
using Color = ScottPlot.Color;
|
|
||||||
|
|
||||||
namespace Azaion.Dataset;
|
namespace Azaion.Dataset;
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ public partial class DatasetExplorer
|
|||||||
ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _annotationConfig.DetectionClasses.First();
|
ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _annotationConfig.DetectionClasses.First();
|
||||||
|
|
||||||
await ReloadThumbnails();
|
await ReloadThumbnails();
|
||||||
await LoadClassDistribution();
|
LoadClassDistribution();
|
||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
}
|
}
|
||||||
@@ -152,47 +151,29 @@ public partial class DatasetExplorer
|
|||||||
_annotationsDict[-1][annotation.Name] = annotation;
|
_annotationsDict[-1][annotation.Name] = annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadClassDistribution()
|
private void LoadClassDistribution()
|
||||||
{
|
{
|
||||||
var data = _annotationsDict
|
var data = _annotationsDict
|
||||||
.Where(x => x.Key != -1)
|
.Where(x => x.Key != -1)
|
||||||
.Select(gr => new
|
.OrderBy(x => x.Key)
|
||||||
|
.Select(gr => new ClusterDistribution
|
||||||
{
|
{
|
||||||
gr.Key,
|
Label = $"{_annotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}",
|
||||||
_annotationConfig.DetectionClassesDict[gr.Key].ShortName,
|
Color = _annotationConfig.DetectionClassesDict[gr.Key].Color,
|
||||||
_annotationConfig.DetectionClassesDict[gr.Key].Color,
|
|
||||||
ClassCount = gr.Value.Count
|
ClassCount = gr.Value.Count
|
||||||
})
|
})
|
||||||
|
.Where(x => x.ClassCount > 0)
|
||||||
.ToList();
|
.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,
|
cl.Color = cl.Color.CreateTransparent(150);
|
||||||
Position = -1.5 * x.Key + 1,
|
cl.BarWidth = Math.Clamp(cl.ClassCount / (double)maxClassCount, 0, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassDistribution.Plot.Axes.AutoScale();
|
ClassDistributionPlot.Items = data;
|
||||||
ClassDistribution.Plot.HideAxesAndGrid();
|
|
||||||
ClassDistribution.Plot.FigureBackground.Color = new("#888888");
|
|
||||||
|
|
||||||
ClassDistribution.Refresh();
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void RefreshThumbnailsBtnClick(object sender, RoutedEventArgs e)
|
private async void RefreshThumbnailsBtnClick(object sender, RoutedEventArgs e)
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ public partial class MainSuite
|
|||||||
window.Value.Close();
|
window.Value.Close();
|
||||||
|
|
||||||
_inferenceClient.Dispose();
|
_inferenceClient.Dispose();
|
||||||
_gpsMatcherClient.Stop();
|
_gpsMatcherClient.Dispose();
|
||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user