better view for class distribution

This commit is contained in:
Alex Bezdieniezhnykh
2025-05-27 13:26:27 +03:00
parent e957f1192a
commit 34ea821fb3
15 changed files with 311 additions and 217 deletions
+2 -1
View File
@@ -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<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
+11
View File
@@ -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; }
}
-51
View File
@@ -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.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);
}
@@ -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 {}
+71 -43
View File
@@ -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<GpsMatchResult, Task> 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<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 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<GpsMatchResult, Task> processResult, CancellationToken detectToken = default)
private string _routeDir = "";
private string _userRouteDir = "";
private List<string> _allRouteImages = new();
private Dictionary<string, int> _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<string>();
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);
}
}
+58 -43
View File
@@ -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> gpsDeniedClientConfig)
public GpsMatcherClient(IMediator mediator, IOptions<GpsDeniedClientConfig> 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();
}
}