switcher dataset explorer

lat lon -> geopoint
correct location for gps if small keypoints number
This commit is contained in:
Alex Bezdieniezhnykh
2025-06-23 20:47:28 +03:00
parent ad1e39268c
commit f58dd3d04f
31 changed files with 469 additions and 192 deletions
+2 -11
View File
@@ -51,7 +51,6 @@ public partial class Annotator
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50); private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50);
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(150); private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(150);
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new(); public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
public ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new(); public ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
@@ -223,10 +222,6 @@ public partial class Annotator
Volume.ValueChanged += (_, newValue) => Volume.ValueChanged += (_, newValue) =>
_mediator.Publish(new VolumeChangedEvent((int)newValue)); _mediator.Publish(new VolumeChangedEvent((int)newValue));
SizeChanged += (_, _) => SaveUserSettings();
LocationChanged += (_, _) => SaveUserSettings();
StateChanged += (_, _) => SaveUserSettings();
DgAnnotations.MouseDoubleClick += (sender, args) => DgAnnotations.MouseDoubleClick += (sender, args) =>
{ {
var dgRow = ItemsControl.ContainerFromElement((DataGrid)sender, (args.OriginalSource as DependencyObject)!) as DataGridRow; var dgRow = ItemsControl.ContainerFromElement((DataGrid)sender, (args.OriginalSource as DependencyObject)!) as DataGridRow;
@@ -283,11 +278,7 @@ public partial class Annotator
_appConfig.UIConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value; _appConfig.UIConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
_appConfig.UIConfig.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value; _appConfig.UIConfig.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
ThrottleExt.Throttle(() => _configUpdater.Save(_appConfig);
{
_configUpdater.Save(_appConfig);
return Task.CompletedTask;
}, SaveConfigTaskId, TimeSpan.FromSeconds(5));
} }
private void ShowTimeAnnotations(TimeSpan time) private void ShowTimeAnnotations(TimeSpan time)
@@ -589,7 +580,7 @@ public partial class Annotator
private void SoundDetections(object sender, RoutedEventArgs e) private void SoundDetections(object sender, RoutedEventArgs e)
{ {
throw new NotImplementedException(); _logger.LogInformation("To be implemented");
} }
} }
+20 -11
View File
@@ -42,7 +42,7 @@ public class AnnotatorEventHandler(
INotificationHandler<AnnotationsDeletedEvent>, INotificationHandler<AnnotationsDeletedEvent>,
INotificationHandler<AnnotationAddedEvent>, INotificationHandler<AnnotationAddedEvent>,
INotificationHandler<SetStatusTextEvent>, INotificationHandler<SetStatusTextEvent>,
INotificationHandler<GPSMatcherResultEvent> INotificationHandler<GPSMatcherResultProcessedEvent>
{ {
private const int STEP = 20; private const int STEP = 20;
private const int LARGE_STEP = 5000; private const int LARGE_STEP = 5000;
@@ -378,20 +378,29 @@ public class AnnotatorEventHandler(
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task Handle(GPSMatcherResultEvent e, CancellationToken cancellationToken) public Task Handle(GPSMatcherResultProcessedEvent e, CancellationToken cancellationToken)
{ {
mainWindow.Dispatcher.Invoke(() => mainWindow.Dispatcher.Invoke(() =>
{ {
var mapMatcher = mainWindow.MapMatcherComponent; var ann = mainWindow.MapMatcherComponent.Annotations[e.Index];
var marker = new GMapMarker(new PointLatLng(e.Latitude, e.Longitude)); AddMarker(e.GeoPoint, e.Image, Brushes.Blue);
var ann = mapMatcher.Annotations[e.Index]; if (e.ProcessedGeoPoint != e.GeoPoint)
marker.Shape = new CircleVisual(marker, size: 14, text: e.Image, background: Brushes.Blue); AddMarker(e.ProcessedGeoPoint, $"{e.Image}: corrected", Brushes.DarkViolet);
mapMatcher.SatelliteMap.Markers.Add(marker); ann.Lat = e.GeoPoint.Lat;
ann.Lat = e.Latitude; ann.Lon = e.GeoPoint.Lon;
ann.Lon = e.Longitude;
mapMatcher.SatelliteMap.Position = new PointLatLng(e.Latitude, e.Longitude);
mapMatcher.SatelliteMap.ZoomAndCenterMarkers(null);
}); });
return Task.CompletedTask; return Task.CompletedTask;
} }
private void AddMarker(GeoPoint point, string text, SolidColorBrush color)
{
var map = mainWindow.MapMatcherComponent;
var pointLatLon = new PointLatLng(point.Lat, point.Lon);
var marker = new GMapMarker(pointLatLon);
marker.Shape = new CircleVisual(marker, size: 14, text: text, background: color);
map.SatelliteMap.Markers.Add(marker);
map.SatelliteMap.Position = pointLatLon;
map.SatelliteMap.ZoomAndCenterMarkers(null);
}
} }
+4 -7
View File
@@ -103,10 +103,8 @@ public partial class MapMatcher : UserControl
OriginalMediaName = x.Name OriginalMediaName = x.Name
})).ToDictionary(x => x.i, x => x.Item2); })).ToDictionary(x => x.i, x => x.Item2);
var initialLat = double.Parse(TbLat.Text); var initialLatLon = new GeoPoint(double.Parse(TbLat.Text), double.Parse(TbLon.Text));
var initialLon = double.Parse(TbLon.Text); await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLatLon);
await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon);
} }
private async void TestGps(object sender, RoutedEventArgs e) private async void TestGps(object sender, RoutedEventArgs e)
@@ -114,8 +112,7 @@ public partial class MapMatcher : UserControl
if (string.IsNullOrEmpty(TbGpsMapFolder.Text)) if (string.IsNullOrEmpty(TbGpsMapFolder.Text))
return; return;
var initialLat = double.Parse(TbLat.Text); var initialLatLon = new GeoPoint(double.Parse(TbLat.Text), double.Parse(TbLon.Text));
var initialLon = double.Parse(TbLon.Text); await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLatLon);
await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLat, initialLon);
} }
} }
+9
View File
@@ -81,6 +81,15 @@ public class Constants
# endregion AIRecognitionConfig # endregion AIRecognitionConfig
# region GpsDeniedConfig
public static readonly GpsDeniedConfig DefaultGpsDeniedConfig = new()
{
MinKeyPoints = 15
};
# endregion
#region Thumbnails #region Thumbnails
public static readonly ThumbnailConfig DefaultThumbnailConfig = new() public static readonly ThumbnailConfig DefaultThumbnailConfig = new()
+44 -8
View File
@@ -1,4 +1,5 @@
using System.Windows; using System.Drawing;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
@@ -7,7 +8,9 @@ using Azaion.Annotator.DTO;
using Azaion.Common.DTO; using Azaion.Common.DTO;
using MediatR; using MediatR;
using Color = System.Windows.Media.Color; using Color = System.Windows.Media.Color;
using Point = System.Windows.Point;
using Rectangle = System.Windows.Shapes.Rectangle; using Rectangle = System.Windows.Shapes.Rectangle;
using Size = System.Windows.Size;
namespace Azaion.Common.Controls; namespace Azaion.Common.Controls;
@@ -160,7 +163,7 @@ public class CanvasEditor : Canvas
return; return;
var time = GetTimeFunc(); var time = GetTimeFunc();
CreateDetectionControl(CurrentAnnClass, time, new CanvasLabel var control = CreateDetectionControl(CurrentAnnClass, time, new CanvasLabel
{ {
Width = width, Width = width,
Height = height, Height = height,
@@ -168,12 +171,44 @@ public class CanvasEditor : Canvas
Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y), Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y),
Confidence = 1 Confidence = 1
}); });
control.UpdateLayout();
CheckLabelBoundaries(control);
SelectionState = SelectionState.None;
e.Handled = true;
}
else
{
CheckLabelBoundaries(_curAnn);
SelectionState = SelectionState.None;
e.Handled = true;
} }
SelectionState = SelectionState.None;
e.Handled = true;
} }
private void CheckLabelBoundaries(DetectionControl detectionControl)
{
var lb = detectionControl.DetectionLabelContainer;
var origin = lb.TranslatePoint(new Point(0, 0), this);
lb.Children[0].Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var size = lb.Children[0].DesiredSize;
var lbRect = new RectangleF((float)origin.X, (float)origin.Y, (float)size.Width, (float)size.Height);
foreach (var c in CurrentDetections)
{
if (c == detectionControl)
continue;
var detRect = new RectangleF((float)GetLeft(c), (float)GetTop(c), (float)c.Width, (float)c.Height);
detRect.Intersect(lbRect);
// var intersect = detections[i].ToRectangle();
// intersect.Intersect(detections[j].ToRectangle());
// detectionControl.
// var otherControls = allControls.Where(c => c != control);
// control.UpdateLabelPosition(otherControls);
}
}
private void CanvasResized(object sender, SizeChangedEventArgs e) private void CanvasResized(object sender, SizeChangedEventArgs e)
{ {
_horizontalLine.X2 = e.NewSize.Width; _horizontalLine.X2 = e.NewSize.Width;
@@ -253,7 +288,7 @@ public class CanvasEditor : Canvas
ClearSelections(); ClearSelections();
_curAnn.IsSelected = true; _curAnn.IsSelected = true;
SelectionState = SelectionState.AnnMoving; SelectionState = SelectionState.AnnMoving;
e.Handled = true; e.Handled = true;
} }
@@ -315,7 +350,7 @@ public class CanvasEditor : Canvas
} }
} }
private void CreateDetectionControl(DetectionClass detectionClass, TimeSpan time, CanvasLabel canvasLabel) private DetectionControl CreateDetectionControl(DetectionClass detectionClass, TimeSpan time, CanvasLabel canvasLabel)
{ {
var detectionControl = new DetectionControl(detectionClass, time, AnnotationResizeStart, canvasLabel); var detectionControl = new DetectionControl(detectionClass, time, AnnotationResizeStart, canvasLabel);
detectionControl.MouseDown += AnnotationPositionStart; detectionControl.MouseDown += AnnotationPositionStart;
@@ -324,6 +359,7 @@ public class CanvasEditor : Canvas
Children.Add(detectionControl); Children.Add(detectionControl);
CurrentDetections.Add(detectionControl); CurrentDetections.Add(detectionControl);
_newAnnotationRect.Fill = new SolidColorBrush(detectionClass.Color); _newAnnotationRect.Fill = new SolidColorBrush(detectionClass.Color);
return detectionControl;
} }
#endregion #endregion
+15 -7
View File
@@ -16,6 +16,8 @@ public class DetectionControl : Border
private readonly Grid _grid; private readonly Grid _grid;
private readonly Label _detectionLabel; private readonly Label _detectionLabel;
public readonly Canvas DetectionLabelContainer;
public TimeSpan Time { get; set; } public TimeSpan Time { get; set; }
private readonly double _confidence; private readonly double _confidence;
private List<Rectangle> _resizedRectangles = new(); private List<Rectangle> _resizedRectangles = new();
@@ -51,6 +53,16 @@ public class DetectionControl : Border
} }
} }
public (HorizontalAlignment Horizontal, VerticalAlignment Vertical) DetectionLabelPosition
{
get => (DetectionLabelContainer.HorizontalAlignment, DetectionLabelContainer.VerticalAlignment);
set
{
DetectionLabelContainer.HorizontalAlignment = value.Horizontal;
DetectionLabelContainer.VerticalAlignment = value.Vertical;
}
}
private string _detectionLabelText(string detectionClassName) => private string _detectionLabelText(string detectionClassName) =>
_confidence >= 0.995 ? detectionClassName : $"{detectionClassName}: {_confidence * 100:F0}%"; //double _confidence >= 0.995 ? detectionClassName : $"{detectionClassName}: {_confidence * 100:F0}%"; //double
@@ -62,23 +74,19 @@ public class DetectionControl : Border
_resizeStart = resizeStart; _resizeStart = resizeStart;
_confidence = canvasLabel.Confidence; _confidence = canvasLabel.Confidence;
var labelContainer = new Canvas DetectionLabelContainer = new Canvas
{ {
HorizontalAlignment = HorizontalAlignment.Right, HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top, VerticalAlignment = VerticalAlignment.Top,
ClipToBounds = false, ClipToBounds = false,
Margin = new Thickness(0, 0, 32, 0)
}; };
_detectionLabel = new Label _detectionLabel = new Label
{ {
Content = _detectionLabelText(detectionClass.Name), Content = _detectionLabelText(detectionClass.Name),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(0, -32, 0, 0),
FontSize = 16, FontSize = 16,
Visibility = Visibility.Visible Visibility = Visibility.Visible
}; };
labelContainer.Children.Add(_detectionLabel); DetectionLabelContainer.Children.Add(_detectionLabel);
_selectionFrame = new Rectangle _selectionFrame = new Rectangle
{ {
@@ -108,7 +116,7 @@ public class DetectionControl : Border
}; };
foreach (var rect in _resizedRectangles) foreach (var rect in _resizedRectangles)
_grid.Children.Add(rect); _grid.Children.Add(rect);
_grid.Children.Add(labelContainer); _grid.Children.Add(DetectionLabelContainer);
Child = _grid; Child = _grid;
Cursor = Cursors.SizeAll; Cursor = Cursors.SizeAll;
+19 -10
View File
@@ -1,5 +1,6 @@
using System.IO; using System.IO;
using System.Text; using System.Text;
using Azaion.Common.Extensions;
using Azaion.CommonSecurity; using Azaion.CommonSecurity;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -26,6 +27,8 @@ public class AppConfig
public ThumbnailConfig ThumbnailConfig { get; set; } = null!; public ThumbnailConfig ThumbnailConfig { get; set; } = null!;
public MapConfig MapConfig{ get; set; } = null!; public MapConfig MapConfig{ get; set; } = null!;
public GpsDeniedConfig GpsDeniedConfig { get; set; } = null!;
} }
public interface IConfigUpdater public interface IConfigUpdater
@@ -36,6 +39,8 @@ public interface IConfigUpdater
public class ConfigUpdater : IConfigUpdater public class ConfigUpdater : IConfigUpdater
{ {
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
public void CheckConfig() public void CheckConfig()
{ {
var exePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!; var exePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!;
@@ -67,23 +72,27 @@ public class ConfigUpdater : IConfigUpdater
}, },
ThumbnailConfig = Constants.DefaultThumbnailConfig, ThumbnailConfig = Constants.DefaultThumbnailConfig,
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig AIRecognitionConfig = Constants.DefaultAIRecognitionConfig,
GpsDeniedConfig = Constants.DefaultGpsDeniedConfig,
}; };
Save(appConfig); Save(appConfig);
} }
public void Save(AppConfig config) public void Save(AppConfig config)
{ {
//Save only user's config ThrottleExt.Throttle(async () =>
var publicConfig = new
{ {
config.LoaderClientConfig, var publicConfig = new
config.InferenceClientConfig, {
config.GpsDeniedClientConfig, config.LoaderClientConfig,
config.DirectoriesConfig, config.InferenceClientConfig,
config.UIConfig config.GpsDeniedClientConfig,
}; config.DirectoriesConfig,
config.UIConfig
};
await File.WriteAllTextAsync(SecurityConstants.CONFIG_PATH, JsonConvert.SerializeObject(publicConfig, Formatting.Indented), Encoding.UTF8);
}, SaveConfigTaskId, TimeSpan.FromSeconds(5));
File.WriteAllText(SecurityConstants.CONFIG_PATH, JsonConvert.SerializeObject(publicConfig, Formatting.Indented), Encoding.UTF8);
} }
} }
@@ -0,0 +1,6 @@
namespace Azaion.Common.DTO.Config;
public class GpsDeniedConfig
{
public int MinKeyPoints { get; set; }
}
+1
View File
@@ -6,4 +6,5 @@ public class UIConfig
public double RightPanelWidth { get; set; } public double RightPanelWidth { get; set; }
public bool GenerateAnnotatedImage { get; set; } public bool GenerateAnnotatedImage { get; set; }
public bool SilentDetection { get; set; } public bool SilentDetection { get; set; }
public bool ShowDatasetWithDetectionsOnly { get; set; }
} }
+32
View File
@@ -0,0 +1,32 @@
namespace Azaion.Common.DTO;
public class GeoPoint
{
const double PRECISION_TOLERANCE = 0.00005;
public double Lat { get; }
public double Lon { get; }
public GeoPoint() { }
public GeoPoint(double lat, double lon)
{
Lat = lat;
Lon = lon;
}
public override string ToString() => $"{Lat:F4}, {Lon:F4}";
public override bool Equals(object? obj)
{
if (obj is not GeoPoint point) return false;
return ReferenceEquals(this, obj) || Equals(point);
}
private bool Equals(GeoPoint point) =>
Math.Abs(Lat - point.Lat) < PRECISION_TOLERANCE && Math.Abs(Lon - point.Lon) < PRECISION_TOLERANCE;
public override int GetHashCode() => HashCode.Combine(Lat, Lon);
public static bool operator ==(GeoPoint left, GeoPoint right) => Equals(left, right);
public static bool operator !=(GeoPoint left, GeoPoint right) => !Equals(left, right);
}
+17
View File
@@ -0,0 +1,17 @@
namespace Azaion.Common.DTO;
public class Direction
{
public double Distance { get; set; }
public double Azimuth { get; set; }
public Direction() { }
public Direction(double distance, double azimuth)
{
Distance = distance;
Azimuth = azimuth;
}
public override string ToString() => $"{Distance:F2}, {Azimuth:F1} deg";
}
+5 -8
View File
@@ -6,11 +6,8 @@ public class SatTile
{ {
public int X { get; } public int X { get; }
public int Y { get; } public int Y { get; }
public double LeftTopLat { get; } public GeoPoint LeftTop { get; }
public double LeftTopLon { get; } public GeoPoint BottomRight { get; }
public double BottomRightLat { get; }
public double BottomRightLon { get; }
public string Url { get; set; } public string Url { get; set; }
@@ -20,12 +17,12 @@ public class SatTile
Y = y; Y = y;
Url = url; Url = url;
(LeftTopLat, LeftTopLon) = GeoUtils.TileToWorldPos(x, y, zoom); LeftTop = GeoUtils.TileToWorldPos(x, y, zoom);
(BottomRightLat, BottomRightLon) = GeoUtils.TileToWorldPos(x + 1, y + 1, zoom); BottomRight = GeoUtils.TileToWorldPos(x + 1, y + 1, zoom);
} }
public override string ToString() public override string ToString()
{ {
return $"Tile[X={X}, Y={Y}, TL=({LeftTopLat:F6}, {LeftTopLon:F6}), BR=({BottomRightLat:F6}, {BottomRightLon:F6})]"; return $"Tile[X={X}, Y={Y}, TL=({LeftTop.Lat:F6}, {LeftTop.Lon:F6}), BR=({BottomRight.Lat:F6}, {BottomRight.Lon:F6})]";
} }
} }
+56 -9
View File
@@ -1,4 +1,6 @@
namespace Azaion.Common.Extensions; using Azaion.Common.DTO;
namespace Azaion.Common.Extensions;
public static class GeoUtils public static class GeoUtils
{ {
@@ -13,26 +15,71 @@ public static class GeoUtils
return (xTile, yTile); return (xTile, yTile);
} }
public static (double lat, double lon) TileToWorldPos(int x, int y, int zoom) public static double ToRadians(double degrees) => degrees * Math.PI / 180.0;
public static double ToDegrees(double radians) => radians * 180.0 / Math.PI;
public static Direction DirectionTo(this GeoPoint p1, GeoPoint p2)
{
var lat1Rad = ToRadians(p1.Lat);
var lat2Rad = ToRadians(p2.Lat);
var dLon = ToRadians(p2.Lon - p1.Lon);
var dLat = ToRadians(p2.Lat - p1.Lat);
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(lat1Rad) * Math.Cos(lat2Rad) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
var c = 2 * Math.Asin(Math.Sqrt(a));
var distance = EARTH_RADIUS * c;
var y = Math.Sin(dLon) * Math.Cos(lat2Rad);
var x = Math.Cos(lat1Rad) * Math.Sin(lat2Rad) -
Math.Sin(lat1Rad) * Math.Cos(lat2Rad) * Math.Cos(dLon);
var azimuthRadians = Math.Atan2(y, x);
var azimuth = (ToDegrees(azimuthRadians) + 360) % 360;
return new Direction
{
Distance = distance,
Azimuth = azimuth
};
}
public static GeoPoint GoDirection(this GeoPoint startPoint, Direction direction)
{
var angularDistance = direction.Distance / EARTH_RADIUS;
var azimuthRadians = ToRadians(direction.Azimuth);
var startLatRad = ToRadians(startPoint.Lat);
var startLonRad = ToRadians(startPoint.Lon);
var destLatRad = Math.Asin(Math.Sin(startLatRad) * Math.Cos(angularDistance) +
Math.Cos(startLatRad) * Math.Sin(angularDistance) * Math.Cos(azimuthRadians));
var destLonRad = startLonRad + Math.Atan2(Math.Sin(azimuthRadians) * Math.Sin(angularDistance) * Math.Cos(startLatRad),
Math.Cos(angularDistance) - Math.Sin(startLatRad) * Math.Sin(destLatRad));
return new GeoPoint(ToDegrees(destLatRad), ToDegrees(destLonRad));
}
public static GeoPoint TileToWorldPos(int x, int y, int zoom)
{ {
var n = Math.Pow(2.0, zoom); var n = Math.Pow(2.0, zoom);
var lonDeg = x / n * 360.0 - 180.0; var lonDeg = x / n * 360.0 - 180.0;
var latRad = Math.Atan(Math.Sinh(Math.PI * (1.0 - 2.0 * y / n))); var latRad = Math.Atan(Math.Sinh(Math.PI * (1.0 - 2.0 * y / n)));
var latDeg = latRad * 180.0 / Math.PI; var latDeg = latRad * 180.0 / Math.PI;
return (latDeg, lonDeg); return new GeoPoint(latDeg, lonDeg);
} }
public static (double minLat, double maxLat, double minLon, double maxLon) GetBoundingBox(double centerLat, double centerLon, double radiusM) public static (double minLat, double maxLat, double minLon, double maxLon) GetBoundingBox(GeoPoint centerGeoPoint, double radiusM)
{ {
var latRad = centerLat * Math.PI / 180.0; var latRad = centerGeoPoint.Lat * Math.PI / 180.0;
var latDiff = (radiusM / EARTH_RADIUS) * (180.0 / Math.PI); var latDiff = (radiusM / EARTH_RADIUS) * (180.0 / Math.PI);
var minLat = Math.Max(centerLat - latDiff, -90.0); var minLat = Math.Max(centerGeoPoint.Lat - latDiff, -90.0);
var maxLat = Math.Min(centerLat + latDiff, 90.0); var maxLat = Math.Min(centerGeoPoint.Lat + latDiff, 90.0);
var lonDiff = (radiusM / (EARTH_RADIUS * Math.Cos(latRad))) * (180.0 / Math.PI); var lonDiff = (radiusM / (EARTH_RADIUS * Math.Cos(latRad))) * (180.0 / Math.PI);
var minLon = Math.Max(centerLon - lonDiff, -180.0); var minLon = Math.Max(centerGeoPoint.Lon - lonDiff, -180.0);
var maxLon = Math.Min(centerLon + lonDiff, 180.0); var maxLon = Math.Min(centerGeoPoint.Lon + lonDiff, 180.0);
return (minLat, maxLat, minLon, maxLon); return (minLat, maxLat, minLon, maxLon);
} }
@@ -0,0 +1,47 @@
using System.Linq.Expressions;
namespace Azaion.Common.Extensions;
public static class QueryableExtensions
{
/// <summary>
/// Adds Where true predicate only if result of condition is true.
/// If false predicate provided, uses it in case of false result
/// Useful for filters, when filters should be applied only when it was set (not NULL)
/// </summary>
public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> query, bool? condition,
Expression<Func<TSource, bool>> truePredicate,
Expression<Func<TSource, bool>>? falsePredicate = null)
{
if (!condition.HasValue)
return query;
if (condition.Value)
return query.Where(truePredicate);
return falsePredicate != null
? query.Where(falsePredicate)
: query;
}
/// <summary>
/// Adds Where true predicate only if result of condition is true.
/// If false predicate provided, uses it in case of false result
/// Useful for filters, when filters should be applied only when it was set (not NULL)
/// </summary>
public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> query, bool? condition,
Func<TSource, bool> truePredicate,
Func<TSource, bool>? falsePredicate = null)
{
if (!condition.HasValue)
return query;
if (condition.Value)
return query.Where(truePredicate);
return falsePredicate != null
? query.Where(falsePredicate)
: query;
}
}
+5 -5
View File
@@ -59,7 +59,7 @@ public class AnnotationService : IAnnotationService
Task.Run(async () => await InitQueueConsumer()).Wait(); Task.Run(async () => await InitQueueConsumer()).Wait();
} }
private async Task InitQueueConsumer(CancellationToken cancellationToken = default) private async Task InitQueueConsumer(CancellationToken token = default)
{ {
if (!_api.CurrentUser.Role.IsValidator()) if (!_api.CurrentUser.Role.IsValidator())
return; return;
@@ -79,7 +79,7 @@ public class AnnotationService : IAnnotationService
OffsetSpec = new OffsetTypeOffset(offsets.AnnotationsOffset), OffsetSpec = new OffsetTypeOffset(offsets.AnnotationsOffset),
MessageHandler = async (_, _, context, message) => MessageHandler = async (_, _, context, message) =>
{ {
await _messageProcessingSemaphore.WaitAsync(cancellationToken); await _messageProcessingSemaphore.WaitAsync(token);
try try
{ {
var email = (string)message.ApplicationProperties[nameof(User.Email)]!; var email = (string)message.ApplicationProperties[nameof(User.Email)]!;
@@ -101,15 +101,15 @@ public class AnnotationService : IAnnotationService
msg.Role, msg.Role,
msg.Email, msg.Email,
context.Offset, context.Offset,
token: cancellationToken); token: token);
} }
else else
{ {
var msg = MessagePackSerializer.Deserialize<AnnotationBulkMessage>(message.Data.Contents); var msg = MessagePackSerializer.Deserialize<AnnotationBulkMessage>(message.Data.Contents);
if (annotationStatus == AnnotationStatus.Validated) if (annotationStatus == AnnotationStatus.Validated)
await ValidateAnnotations(msg.AnnotationNames.ToList(), true, cancellationToken); await ValidateAnnotations(msg.AnnotationNames.ToList(), true, token);
if (annotationStatus == AnnotationStatus.Deleted) if (annotationStatus == AnnotationStatus.Deleted)
await _mediator.Publish(new AnnotationsDeletedEvent(msg.AnnotationNames.ToList(), fromQueue:true), cancellationToken); await _mediator.Publish(new AnnotationsDeletedEvent(msg.AnnotationNames.ToList(), fromQueue:true), token);
} }
} }
+34 -7
View File
@@ -1,18 +1,45 @@
using Azaion.Common.DTO;
using MediatR; using MediatR;
namespace Azaion.Common.Services; namespace Azaion.Common.Services;
public enum MatchTypeEnum
{
None = -1,
MatchTypeSingle = 0,
MatchTypeStitched = 1,
MatchTypeOpticalFlow = 2,
MatchTypeInterpolated = 3,
MatchTypeFailure = 4
}
public class GPSMatcherResultEvent : INotification public class GPSMatcherResultEvent : INotification
{ {
public int Index { get; set; } public int Index { get; set; }
public string Image { get; set; } = null!; public string Image { get; set; } = null!;
public double Latitude { get; set; } public GeoPoint GeoPoint { get; set; } = null!;
public double Longitude { get; set; } public int KeyPoints { get; set; }
public int KeyPoints { get; set; } public MatchTypeEnum MatchType { get; set; }
public int Rotation { get; set; }
public string MatchType { get; set; } = null!;
} }
public class GPSMatcherResultProcessedEvent : GPSMatcherResultEvent
{
public GeoPoint ProcessedGeoPoint { get; set; } = null!;
public GPSMatcherResultProcessedEvent() { }
public GPSMatcherResultProcessedEvent(GPSMatcherResultEvent gpsMatcherResultEvent, GeoPoint processedGeoPoint)
{
Index = gpsMatcherResultEvent.Index;
Image = gpsMatcherResultEvent.Image;
GeoPoint = gpsMatcherResultEvent.GeoPoint;
KeyPoints = gpsMatcherResultEvent.KeyPoints;
MatchType = gpsMatcherResultEvent.MatchType;
ProcessedGeoPoint = processedGeoPoint;
}
}
public class GPSMatcherJobAcceptedEvent : INotification {} public class GPSMatcherJobAcceptedEvent : INotification {}
public class GPSMatcherFinishedEvent : INotification {} public class GPSMatcherFinishedEvent : INotification {}
+31 -15
View File
@@ -1,46 +1,51 @@
using System.IO; using System.IO;
using Azaion.Common.DTO; using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions;
using MediatR;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Azaion.Common.Services; namespace Azaion.Common.Services;
public interface IGpsMatcherService public interface IGpsMatcherService
{ {
Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, CancellationToken detectToken = default); Task RunGpsMatching(string userRouteDir, GeoPoint geoPoint, CancellationToken detectToken = default);
void StopGpsMatching(); void StopGpsMatching();
Task SetGpsResult(GPSMatcherResultEvent result, CancellationToken detectToken = default); Task SetGpsResult(GPSMatcherResultEvent result, CancellationToken detectToken = default);
Task FinishGPS(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken); Task FinishGPS(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken);
} }
public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDownloader satelliteTileDownloader, IOptions<DirectoriesConfig> dirConfig) : IGpsMatcherService public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient,
ISatelliteDownloader satelliteTileDownloader,
IOptions<DirectoriesConfig> dirConfig,
IOptions<GpsDeniedConfig> gpsDeniedConfig,
IMediator mediator) : IGpsMatcherService
{ {
private readonly DirectoriesConfig _dirConfig = dirConfig.Value; 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);
private const int MAX_AVG_POINTS = 2;
private string _routeDir = ""; private string _routeDir = "";
private string _userRouteDir = ""; private string _userRouteDir = "";
private List<string> _allRouteImages = new(); private List<string> _allRouteImages = new();
private Dictionary<string, int> _currentRouteImages = new(); private Dictionary<string, int> _currentRouteImages = new();
private double _currentLat; private GeoPoint _lastGeoPoint = new();
private double _currentLon;
private CancellationToken _detectToken; private CancellationToken _detectToken;
private int _currentIndex; private int _currentIndex;
private readonly Queue<Direction> _directions = new();
public async Task RunGpsMatching(string userRouteDir, double initialLatitude, double initialLongitude, CancellationToken detectToken = default) public async Task RunGpsMatching(string userRouteDir, GeoPoint initGeoPoint, CancellationToken detectToken = default)
{ {
_routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, _dirConfig.GpsRouteDirectory); _routeDir = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, _dirConfig.GpsRouteDirectory);
_userRouteDir = userRouteDir; _userRouteDir = userRouteDir;
_allRouteImages = Directory.GetFiles(userRouteDir) _allRouteImages = Directory.GetFiles(userRouteDir)
.OrderBy(x => x).ToList(); .OrderBy(x => x).ToList();
_lastGeoPoint = initGeoPoint;
_currentLat = initialLatitude;
_currentLon = initialLongitude;
_detectToken = detectToken; _detectToken = detectToken;
await StartMatchingRound(0); await StartMatchingRound(0);
} }
@@ -63,12 +68,11 @@ public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDow
}) })
.ToDictionary(x => x.Filename, x => x.Index); .ToDictionary(x => x.Filename, x => x.Index);
await satelliteTileDownloader.GetTiles(_currentLat, _currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken); await satelliteTileDownloader.GetTiles(_lastGeoPoint, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken);
await gpsMatcherClient.StartMatching(new StartMatchingEvent await gpsMatcherClient.StartMatching(new StartMatchingEvent
{ {
ImagesCount = POINTS_COUNT, ImagesCount = POINTS_COUNT,
Latitude = _currentLat, GeoPoint = _lastGeoPoint,
Longitude = _currentLon,
SatelliteImagesDir = _dirConfig.GpsSatDirectory, SatelliteImagesDir = _dirConfig.GpsSatDirectory,
RouteDir = _dirConfig.GpsRouteDirectory RouteDir = _dirConfig.GpsRouteDirectory
}); });
@@ -83,9 +87,21 @@ public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDow
{ {
_currentIndex = _currentRouteImages[result.Image]; _currentIndex = _currentRouteImages[result.Image];
_currentRouteImages.Remove(result.Image); _currentRouteImages.Remove(result.Image);
_currentLat = result.Latitude;
_currentLon = result.Longitude; if (result.KeyPoints > gpsDeniedConfig.Value.MinKeyPoints)
await Task.CompletedTask; {
var direction = _lastGeoPoint.DirectionTo(result.GeoPoint);
_directions.Enqueue(direction);
if (_directions.Count > MAX_AVG_POINTS)
_directions.Dequeue();
_lastGeoPoint = result.GeoPoint;
}
else
{
var direction = new Direction(_directions.Average(x => x.Distance), _directions.Average(x => x.Azimuth));
_lastGeoPoint = _lastGeoPoint.GoDirection(direction);
}
await mediator.Publish(new GPSMatcherResultProcessedEvent(result, _lastGeoPoint), detectToken);
} }
public async Task FinishGPS(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken) public async Task FinishGPS(GPSMatcherFinishedEvent notification, CancellationToken cancellationToken)
+12 -14
View File
@@ -18,17 +18,16 @@ public interface IGpsMatcherClient : IDisposable
public class StartMatchingEvent public class StartMatchingEvent
{ {
public string RouteDir { get; set; } = null!; public string RouteDir { get; set; } = null!;
public string SatelliteImagesDir { get; set; } = null!; public string SatelliteImagesDir { get; set; } = null!;
public int ImagesCount { get; set; } public int ImagesCount { get; set; }
public double Latitude { get; set; } public GeoPoint GeoPoint { get; set; } = null!;
public double Longitude { get; set; } public int Altitude { get; set; } = 400;
public int Altitude { get; set; } = 400; public double CameraSensorWidth { get; set; } = 23.5;
public double CameraSensorWidth { get; set; } = 23.5; public double CameraFocalLength { get; set; } = 24;
public double CameraFocalLength { get; set; } = 24;
public override string ToString() => public override string ToString() =>
$"{RouteDir},{SatelliteImagesDir},{ImagesCount},{Latitude},{Longitude},{Altitude},{CameraSensorWidth},{CameraFocalLength}"; $"{RouteDir},{SatelliteImagesDir},{ImagesCount},{GeoPoint.Lat},{GeoPoint.Lon},{Altitude},{CameraSensorWidth},{CameraFocalLength}";
} }
public class GpsMatcherClient : IGpsMatcherClient public class GpsMatcherClient : IGpsMatcherClient
@@ -59,7 +58,6 @@ public class GpsMatcherClient : IGpsMatcherClient
catch (Exception e) catch (Exception e)
{ {
_logger.LogError(e, e.ToString()); _logger.LogError(e, e.ToString());
throw;
} }
_requestAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqPort}"; _requestAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqPort}";
@@ -93,7 +91,7 @@ public class GpsMatcherClient : IGpsMatcherClient
break; break;
default: default:
var parts = str.Split(','); var parts = str.Split(',');
if (parts.Length != 5) if (parts.Length != 6)
throw new Exception("Matching Result Failed"); throw new Exception("Matching Result Failed");
var filename = Path.GetFileNameWithoutExtension(parts[1]); var filename = Path.GetFileNameWithoutExtension(parts[1]);
@@ -101,9 +99,9 @@ public class GpsMatcherClient : IGpsMatcherClient
{ {
Index = int.Parse(parts[0]), Index = int.Parse(parts[0]),
Image = filename, Image = filename,
Latitude = double.Parse(parts[2]), GeoPoint = new GeoPoint(double.Parse(parts[2]), double.Parse(parts[3])),
Longitude = double.Parse(parts[3]), KeyPoints = int.Parse(parts[4]),
MatchType = parts[4] MatchType = Enum.TryParse<MatchTypeEnum>(parts[5], out var type) ? type : MatchTypeEnum.None
}); });
break; break;
} }
@@ -54,7 +54,6 @@ public class InferenceClient : IInferenceClient
catch (Exception e) catch (Exception e)
{ {
_logger.LogError(e, e.Message); _logger.LogError(e, e.Message);
throw;
} }
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N")); _dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
+15 -8
View File
@@ -20,7 +20,7 @@ namespace Azaion.Common.Services;
public interface ISatelliteDownloader public interface ISatelliteDownloader
{ {
Task GetTiles(double latitude, double longitude, double radiusM, int zoomLevel, CancellationToken token = default); Task GetTiles(GeoPoint geoPoint, double radiusM, int zoomLevel, CancellationToken token = default);
} }
public class SatelliteDownloader( public class SatelliteDownloader(
@@ -45,21 +45,28 @@ public class SatelliteDownloader(
private readonly string _apiKey = mapConfig.Value.ApiKey; private readonly string _apiKey = mapConfig.Value.ApiKey;
private readonly string _satDirectory = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, directoriesConfig.Value.GpsSatDirectory); private readonly string _satDirectory = Path.Combine(SecurityConstants.EXTERNAL_GPS_DENIED_FOLDER, directoriesConfig.Value.GpsSatDirectory);
public async Task GetTiles(double centerLat, double centerLon, double radiusM, int zoomLevel, CancellationToken token = default) public async Task GetTiles(GeoPoint centerGeoPoint, double radiusM, int zoomLevel, CancellationToken token = default)
{ {
await mediator.Publish(new SetStatusTextEvent($"Завантажується супутникові зображення по координатах: центр: lat: {centerLat:F3} lon: {centerLon:F3} квадрат {radiusM}м * {radiusM}м, zoom: {zoomLevel}..."), token); await mediator.Publish(new SetStatusTextEvent(
$"Завантажуються супутникові зображення по координатах: центр: " +
$"lat: {centerGeoPoint.Lat:F3} lon: {centerGeoPoint.Lon:F3} квадрат {radiusM}м * {radiusM}м, zoom: {zoomLevel}..."), token);
//empty Satellite directory //empty Satellite directory
if (Directory.Exists(_satDirectory)) if (Directory.Exists(_satDirectory))
Directory.Delete(_satDirectory, true); Directory.Delete(_satDirectory, true);
Directory.CreateDirectory(_satDirectory); Directory.CreateDirectory(_satDirectory);
var downloadTilesResult = await DownloadTiles(centerLat, centerLon, radiusM, zoomLevel, token); var dtRes = await DownloadTiles(centerGeoPoint, radiusM, zoomLevel, token);
await mediator.Publish(new SetStatusTextEvent("Завершено! Склеюється в 1 зображення..."), token); await mediator.Publish(new SetStatusTextEvent("Завершено! Склеюється в 1 зображення..."), token);
var image = ComposeTiles(downloadTilesResult.Tiles, token); var image = ComposeTiles(dtRes.Tiles, token);
if (image == null) if (image == null)
return; return;
// Save big map. Uncomment when MapHandler with custom pick images would be ready
// var outputFilename = Path.Combine(_satDirectory,
// $"map_tl_{dtRes.LatMax:F6}_{dtRes.LonMin:F6}_br_{dtRes.LatMin:F6}_{dtRes.LonMax:F6}.tif"
// );
// await image.SaveAsTiffAsync(outputFilename, token);
await mediator.Publish(new SetStatusTextEvent("Розбиття на малі зображення для опрацювання..."), token); await mediator.Publish(new SetStatusTextEvent("Розбиття на малі зображення для опрацювання..."), token);
await SplitToTiles(image, downloadTilesResult, token); await SplitToTiles(image, dtRes, token);
} }
private async Task SplitToTiles(Image<Rgba32> image, DownloadTilesResult bounds, CancellationToken token = default) private async Task SplitToTiles(Image<Rgba32> image, DownloadTilesResult bounds, CancellationToken token = default)
@@ -178,9 +185,9 @@ public class SatelliteDownloader(
} }
} }
private async Task<DownloadTilesResult> DownloadTiles(double centerLat, double centerLon, double radiusM, int zoomLevel, CancellationToken token = default) private async Task<DownloadTilesResult> DownloadTiles(GeoPoint centerGeoPoint, double radiusM, int zoomLevel, CancellationToken token = default)
{ {
var (latMin, latMax, lonMin, lonMax) = GeoUtils.GetBoundingBox(centerLat, centerLon, radiusM); var (latMin, latMax, lonMin, lonMax) = GeoUtils.GetBoundingBox(centerGeoPoint, radiusM);
var (xMin, yMin) = GeoUtils.WorldToTilePos(latMax, lonMin, zoomLevel); // Top-left corner var (xMin, yMin) = GeoUtils.WorldToTilePos(latMax, lonMin, zoomLevel); // Top-left corner
var (xMax, yMax) = GeoUtils.WorldToTilePos(latMin, lonMax, zoomLevel); // Bottom-right corner var (xMax, yMax) = GeoUtils.WorldToTilePos(latMin, lonMax, zoomLevel); // Bottom-right corner
+21 -3
View File
@@ -73,9 +73,27 @@
<ColumnDefinition Width="4"/> <ColumnDefinition Width="4"/>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<controls:DetectionClasses x:Name="LvClasses" <Grid Name="LeftPane"
Grid.Column="0" ShowGridLines="False"
Grid.Row="0" /> Background="Black"
HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
</Grid.RowDefinitions>
<controls:DetectionClasses
x:Name="LvClasses"
Grid.Column="0"
Grid.Row="0" />
<CheckBox Grid.Row="1"
Margin="10"
Foreground="White"
Name="ShowWithObjectsOnlyChBox"
Click="ShowWithObjectsOnly_OnClick">
Показувати лише анотації з об'єктами
</CheckBox>
</Grid>
<TabControl <TabControl
Name="Switcher" Name="Switcher"
Grid.Column="2" Grid.Column="2"
+31 -28
View File
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using Azaion.Common.Database; using Azaion.Common.Database;
@@ -18,60 +19,56 @@ namespace Azaion.Dataset;
public partial class DatasetExplorer public partial class DatasetExplorer
{ {
private readonly ILogger<DatasetExplorer> _logger; private readonly ILogger<DatasetExplorer> _logger;
private readonly AnnotationConfig _annotationConfig; private readonly AppConfig _appConfig;
private readonly DirectoriesConfig _directoriesConfig;
private readonly Dictionary<int, Dictionary<string, Annotation>> _annotationsDict; private readonly Dictionary<int, Dictionary<string, Annotation>> _annotationsDict;
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
public List<DetectionClass> AllDetectionClasses { get; set; } private List<DetectionClass> AllDetectionClasses { get; }
public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; set; } = new(); public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; } = new();
public readonly Dictionary<string, AnnotationThumbnail> SelectedAnnotationDict = new(); public readonly Dictionary<string, AnnotationThumbnail> SelectedAnnotationDict = new();
private int _tempSelectedClassIdx = 0; private int _tempSelectedClassIdx;
private readonly IGalleryService _galleryService; private readonly IGalleryService _galleryService;
private readonly IDbFactory _dbFactory; private readonly IDbFactory _dbFactory;
private readonly IMediator _mediator; private readonly IMediator _mediator;
public readonly List<DetectionClass> AnnotationsClasses; private readonly IAzaionApi _azaionApi;
private IAzaionApi _azaionApi; private readonly IConfigUpdater _configUpdater;
public bool ThumbnailLoading { get; set; } public bool ThumbnailLoading { get; set; }
public AnnotationThumbnail? CurrentAnnotation { get; set; } public AnnotationThumbnail? CurrentAnnotation { get; set; }
public DatasetExplorer( public DatasetExplorer(
IOptions<DirectoriesConfig> directoriesConfig, IOptions<AppConfig> appConfig,
IOptions<AnnotationConfig> annotationConfig,
ILogger<DatasetExplorer> logger, ILogger<DatasetExplorer> logger,
IGalleryService galleryService, IGalleryService galleryService,
FormState formState, FormState formState,
IDbFactory dbFactory, IDbFactory dbFactory,
IMediator mediator, IMediator mediator,
IAzaionApi azaionApi) IAzaionApi azaionApi,
IConfigUpdater configUpdater)
{ {
InitializeComponent(); InitializeComponent();
_appConfig = appConfig.Value;
_directoriesConfig = directoriesConfig.Value;
_annotationConfig = annotationConfig.Value;
_logger = logger; _logger = logger;
_galleryService = galleryService; _galleryService = galleryService;
_dbFactory = dbFactory; _dbFactory = dbFactory;
_mediator = mediator; _mediator = mediator;
_azaionApi = azaionApi; _azaionApi = azaionApi;
_configUpdater = configUpdater;
ShowWithObjectsOnlyChBox.IsChecked = _appConfig.UIConfig.ShowDatasetWithDetectionsOnly;
var photoModes = Enum.GetValues(typeof(PhotoMode)).Cast<PhotoMode>().ToList(); var photoModes = Enum.GetValues(typeof(PhotoMode)).Cast<PhotoMode>().ToList();
_annotationsDict = _annotationConfig.DetectionClasses.SelectMany(cls => photoModes.Select(mode => (int)mode + cls.Id)) _annotationsDict = _appConfig.AnnotationConfig.DetectionClasses.SelectMany(cls => photoModes.Select(mode => (int)mode + cls.Id))
.ToDictionary(x => x, _ => new Dictionary<string, Annotation>()); .ToDictionary(x => x, _ => new Dictionary<string, Annotation>());
_annotationsDict.Add(-1, []); _annotationsDict.Add(-1, []);
AnnotationsClasses = annotationConfig.Value.DetectionClasses;
Loaded += OnLoaded; Loaded += OnLoaded;
Activated += (_, _) => formState.ActiveWindow = WindowEnum.DatasetExplorer; Activated += (_, _) => formState.ActiveWindow = WindowEnum.DatasetExplorer;
ThumbnailsView.KeyDown += async (sender, args) => ThumbnailsView.KeyDown += async (_, args) =>
{ {
switch (args.Key) switch (args.Key)
{ {
@@ -102,7 +99,7 @@ public partial class DatasetExplorer
AllDetectionClasses = new List<DetectionClass>( AllDetectionClasses = new List<DetectionClass>(
new List<DetectionClass> { new() {Id = -1, Name = "All", ShortName = "All"}} new List<DetectionClass> { new() {Id = -1, Name = "All", ShortName = "All"}}
.Concat(_annotationConfig.DetectionClasses)); .Concat(_appConfig.AnnotationConfig.DetectionClasses));
LvClasses.Init(AllDetectionClasses); LvClasses.Init(AllDetectionClasses);
} }
@@ -119,7 +116,7 @@ public partial class DatasetExplorer
ann.DetectionClass = args.DetectionClass; ann.DetectionClass = args.DetectionClass;
}; };
ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _annotationConfig.DetectionClasses.First(); ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _appConfig.AnnotationConfig.DetectionClasses.First();
var allAnnotations = await _dbFactory.Run(async db => var allAnnotations = await _dbFactory.Run(async db =>
await db.Annotations.LoadWith(x => x.Detections) await db.Annotations.LoadWith(x => x.Detections)
@@ -150,8 +147,8 @@ public partial class DatasetExplorer
.OrderBy(x => x.Key) .OrderBy(x => x.Key)
.Select(gr => new ClusterDistribution .Select(gr => new ClusterDistribution
{ {
Label = $"{_annotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}", Label = $"{_appConfig.AnnotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}",
Color = _annotationConfig.DetectionClassesDict[gr.Key].Color, Color = _appConfig.AnnotationConfig.DetectionClassesDict[gr.Key].Color,
ClassCount = gr.Value.Count ClassCount = gr.Value.Count
}) })
.Where(x => x.ClassCount > 0) .Where(x => x.ClassCount > 0)
@@ -173,7 +170,7 @@ public partial class DatasetExplorer
RefreshThumbnailsButtonItem.Visibility = Visibility.Hidden; RefreshThumbnailsButtonItem.Visibility = Visibility.Hidden;
RefreshProgressBarItem.Visibility = Visibility.Visible; RefreshProgressBarItem.Visibility = Visibility.Visible;
var result = MessageBox.Show($"Видалити всі іконки та згенерувати нову базу іконок в {_directoriesConfig.ThumbnailsDirectory}?", var result = MessageBox.Show($"Видалити всі іконки та згенерувати нову базу іконок в {_appConfig.DirectoriesConfig.ThumbnailsDirectory}?",
"Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question); "Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result != MessageBoxResult.Yes) if (result != MessageBoxResult.Yes)
return; return;
@@ -204,7 +201,7 @@ public partial class DatasetExplorer
var time = ann.Time; var time = ann.Time;
ExplorerEditor.RemoveAllAnns(); ExplorerEditor.RemoveAllAnns();
ExplorerEditor.CreateDetections(time, ann.Detections, _annotationConfig.DetectionClasses, ExplorerEditor.RenderSize); ExplorerEditor.CreateDetections(time, ann.Detections, _appConfig.AnnotationConfig.DetectionClasses, ExplorerEditor.RenderSize);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -229,7 +226,7 @@ public partial class DatasetExplorer
AnnotationsTab.Visibility = Visibility.Collapsed; AnnotationsTab.Visibility = Visibility.Collapsed;
EditorTab.Visibility = Visibility.Visible; EditorTab.Visibility = Visibility.Visible;
_tempSelectedClassIdx = LvClasses.CurrentClassNumber; _tempSelectedClassIdx = LvClasses.CurrentClassNumber;
LvClasses.DetectionDataGrid.ItemsSource = _annotationConfig.DetectionClasses; LvClasses.DetectionDataGrid.ItemsSource = _appConfig.AnnotationConfig.DetectionClasses;
Switcher.SelectedIndex = 1; Switcher.SelectedIndex = 1;
LvClasses.SelectNum(Math.Max(0, _tempSelectedClassIdx - 1)); LvClasses.SelectNum(Math.Max(0, _tempSelectedClassIdx - 1));
@@ -259,16 +256,15 @@ public partial class DatasetExplorer
private async Task ReloadThumbnails() private async Task ReloadThumbnails()
{ {
var withDetectionsOnly = ShowWithObjectsOnlyChBox.IsChecked;
SelectedAnnotations.Clear(); SelectedAnnotations.Clear();
SelectedAnnotationDict.Clear(); SelectedAnnotationDict.Clear();
var annThumbnails = _annotationsDict[ExplorerEditor.CurrentAnnClass.YoloId] var annThumbnails = _annotationsDict[ExplorerEditor.CurrentAnnClass.YoloId]
.WhereIf(withDetectionsOnly, x => x.Value.Detections.Any())
.Select(x => new AnnotationThumbnail(x.Value, _azaionApi.CurrentUser.Role.IsValidator())) .Select(x => new AnnotationThumbnail(x.Value, _azaionApi.CurrentUser.Role.IsValidator()))
.OrderBy(x => !x.IsSeed) .OrderBy(x => !x.IsSeed)
.ThenByDescending(x =>x.Annotation.CreatedDate); .ThenByDescending(x =>x.Annotation.CreatedDate);
//var dict = annThumbnails.Take(20).ToDictionary(x => x.Annotation.Name, x => x.IsSeed);
foreach (var thumb in annThumbnails) foreach (var thumb in annThumbnails)
{ {
SelectedAnnotations.Add(thumb); SelectedAnnotations.Add(thumb);
@@ -292,4 +288,11 @@ public partial class DatasetExplorer
_logger.LogError(ex, ex.Message); _logger.LogError(ex, ex.Message);
} }
} }
private async void ShowWithObjectsOnly_OnClick(object sender, RoutedEventArgs e)
{
_appConfig.UIConfig.ShowDatasetWithDetectionsOnly = (sender as CheckBox)?.IsChecked ?? false;
_configUpdater.Save(_appConfig);
await ReloadThumbnails();
}
} }
+22 -16
View File
@@ -31,12 +31,12 @@ public class DatasetExplorerEventHandler(
{ Key.V, PlaybackControlEnum.ValidateAnnotations}, { Key.V, PlaybackControlEnum.ValidateAnnotations},
}; };
public async Task Handle(DatasetExplorerControlEvent notification, CancellationToken cancellationToken) public async Task Handle(DatasetExplorerControlEvent notification, CancellationToken token)
{ {
await HandleControl(notification.PlaybackControl, cancellationToken); await HandleControl(notification.PlaybackControl, token);
} }
public async Task Handle(KeyEvent keyEvent, CancellationToken cancellationToken) public async Task Handle(KeyEvent keyEvent, CancellationToken token)
{ {
if (keyEvent.WindowEnum != WindowEnum.DatasetExplorer) if (keyEvent.WindowEnum != WindowEnum.DatasetExplorer)
return; return;
@@ -52,11 +52,11 @@ public class DatasetExplorerEventHandler(
else else
{ {
if (datasetExplorer.Switcher.SelectedIndex == 1 && _keysControlEnumDict.TryGetValue(key, out var value)) if (datasetExplorer.Switcher.SelectedIndex == 1 && _keysControlEnumDict.TryGetValue(key, out var value))
await HandleControl(value, cancellationToken); await HandleControl(value, token);
} }
} }
private async Task HandleControl(PlaybackControlEnum controlEnum, CancellationToken cancellationToken = default) private async Task HandleControl(PlaybackControlEnum controlEnum, CancellationToken token = default)
{ {
switch (controlEnum) switch (controlEnum)
{ {
@@ -70,7 +70,8 @@ public class DatasetExplorerEventHandler(
.Select(x => new Detection(a.Name, x.GetLabel(datasetExplorer.ExplorerEditor.RenderSize))) .Select(x => new Detection(a.Name, x.GetLabel(datasetExplorer.ExplorerEditor.RenderSize)))
.ToList(); .ToList();
var index = datasetExplorer.ThumbnailsView.SelectedIndex; var index = datasetExplorer.ThumbnailsView.SelectedIndex;
await annotationService.SaveAnnotation(a.OriginalMediaName, a.Time, detections, token: cancellationToken); var annotation = await annotationService.SaveAnnotation(a.OriginalMediaName, a.Time, detections, token: token);
await ValidateAnnotations([annotation], token);
await datasetExplorer.EditAnnotation(index + 1); await datasetExplorer.EditAnnotation(index + 1);
break; break;
case PlaybackControlEnum.RemoveSelectedAnns: case PlaybackControlEnum.RemoveSelectedAnns:
@@ -98,19 +99,24 @@ public class DatasetExplorerEventHandler(
var annotations = datasetExplorer.ThumbnailsView.SelectedItems.Cast<AnnotationThumbnail>() var annotations = datasetExplorer.ThumbnailsView.SelectedItems.Cast<AnnotationThumbnail>()
.Select(x => x.Annotation) .Select(x => x.Annotation)
.ToList(); .ToList();
await annotationService.ValidateAnnotations(annotations.Select(x => x.Name).ToList(), token: cancellationToken); await ValidateAnnotations(annotations, token);
foreach (var ann in datasetExplorer.SelectedAnnotations.Where(x => annotations.Contains(x.Annotation)))
{
ann.Annotation.AnnotationStatus = AnnotationStatus.Validated;
if (datasetExplorer.SelectedAnnotationDict.TryGetValue(ann.Annotation.Name, out var value))
value.Annotation.AnnotationStatus = AnnotationStatus.Validated;
ann.UpdateUI();
}
break; break;
} }
} }
public Task Handle(AnnotationCreatedEvent notification, CancellationToken cancellationToken) private async Task ValidateAnnotations(List<Annotation> annotations, CancellationToken token = default)
{
await annotationService.ValidateAnnotations(annotations.Select(x => x.Name).ToList(), token: token);
foreach (var ann in datasetExplorer.SelectedAnnotations.Where(x => annotations.Contains(x.Annotation)))
{
ann.Annotation.AnnotationStatus = AnnotationStatus.Validated;
if (datasetExplorer.SelectedAnnotationDict.TryGetValue(ann.Annotation.Name, out var value))
value.Annotation.AnnotationStatus = AnnotationStatus.Validated;
ann.UpdateUI();
}
}
public Task Handle(AnnotationCreatedEvent notification, CancellationToken token)
{ {
datasetExplorer.Dispatcher.Invoke(() => datasetExplorer.Dispatcher.Invoke(() =>
{ {
@@ -140,7 +146,7 @@ public class DatasetExplorerEventHandler(
return Task.CompletedTask; return Task.CompletedTask;
} }
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken) public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken token)
{ {
try try
{ {
+2
View File
@@ -1,5 +1,6 @@
import os import os
import subprocess import subprocess
cimport constants
cdef class HardwareService: cdef class HardwareService:
@staticmethod @staticmethod
@@ -33,4 +34,5 @@ cdef class HardwareService:
cdef str drive_serial = lines[len_lines-1] cdef str drive_serial = lines[len_lines-1]
cdef str res = f'CPU: {cpu}. GPU: {gpu}. Memory: {memory}. DriveSerial: {drive_serial}' cdef str res = f'CPU: {cpu}. GPU: {gpu}. Memory: {memory}. DriveSerial: {drive_serial}'
constants.log(f'Gathered hardware: {res}')
return res return res
+2
View File
@@ -83,6 +83,7 @@ public partial class App
{ {
AnnotationConfig = Constants.DefaultAnnotationConfig, AnnotationConfig = Constants.DefaultAnnotationConfig,
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig, AIRecognitionConfig = Constants.DefaultAIRecognitionConfig,
GpsDeniedConfig = Constants.DefaultGpsDeniedConfig,
ThumbnailConfig = Constants.DefaultThumbnailConfig, ThumbnailConfig = Constants.DefaultThumbnailConfig,
}))); })));
} }
@@ -132,6 +133,7 @@ public partial class App
services.ConfigureSection<DirectoriesConfig>(context.Configuration); services.ConfigureSection<DirectoriesConfig>(context.Configuration);
services.ConfigureSection<AnnotationConfig>(context.Configuration); services.ConfigureSection<AnnotationConfig>(context.Configuration);
services.ConfigureSection<AIRecognitionConfig>(context.Configuration); services.ConfigureSection<AIRecognitionConfig>(context.Configuration);
services.ConfigureSection<GpsDeniedConfig>(context.Configuration);
services.ConfigureSection<ThumbnailConfig>(context.Configuration); services.ConfigureSection<ThumbnailConfig>(context.Configuration);
services.ConfigureSection<UIConfig>(context.Configuration); services.ConfigureSection<UIConfig>(context.Configuration);
services.ConfigureSection<MapConfig>(context.Configuration); services.ConfigureSection<MapConfig>(context.Configuration);
-16
View File
@@ -25,7 +25,6 @@ public partial class MainSuite
private readonly Dictionary<WindowEnum, Window> _openedWindows = new(); private readonly Dictionary<WindowEnum, Window> _openedWindows = new();
private readonly IInferenceClient _inferenceClient; private readonly IInferenceClient _inferenceClient;
private readonly IGpsMatcherClient _gpsMatcherClient; private readonly IGpsMatcherClient _gpsMatcherClient;
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
public MainSuite(IOptions<AppConfig> appConfig, public MainSuite(IOptions<AppConfig> appConfig,
IConfigUpdater configUpdater, IConfigUpdater configUpdater,
@@ -48,10 +47,6 @@ public partial class MainSuite
InitializeComponent(); InitializeComponent();
Loaded += OnLoaded; Loaded += OnLoaded;
Closed += OnFormClosed; Closed += OnFormClosed;
SizeChanged += (_, _) => SaveUserSettings();
LocationChanged += (_, _) => SaveUserSettings();
StateChanged += (_, _) => SaveUserSettings();
Left = (SystemParameters.WorkArea.Width - Width) / 2; Left = (SystemParameters.WorkArea.Width - Width) / 2;
} }
@@ -125,19 +120,8 @@ public partial class MainSuite
} }
} }
private void SaveUserSettings()
{
ThrottleExt.Throttle(() =>
{
_configUpdater.Save(_appConfig);
return Task.CompletedTask;
}, SaveConfigTaskId, TimeSpan.FromSeconds(2));
}
private void OnFormClosed(object? sender, EventArgs e) private void OnFormClosed(object? sender, EventArgs e)
{ {
_configUpdater.Save(_appConfig);
foreach (var window in _openedWindows) foreach (var window in _openedWindows)
window.Value.Close(); window.Value.Close();
+4 -3
View File
@@ -1,12 +1,12 @@
{ {
"LoaderClientConfig": { "LoaderClientConfig": {
"ZeroMqHost": "127.0.0.1", "ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5025, "ZeroMqPort": 5024,
"ApiUrl": "https://api.azaion.com" "ApiUrl": "https://api.azaion.com"
}, },
"InferenceClientConfig": { "InferenceClientConfig": {
"ZeroMqHost": "127.0.0.1", "ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5127, "ZeroMqPort": 5126,
"ApiUrl": "https://api.azaion.com" "ApiUrl": "https://api.azaion.com"
}, },
"GpsDeniedClientConfig": { "GpsDeniedClientConfig": {
@@ -28,6 +28,7 @@
"LeftPanelWidth": 220.0, "LeftPanelWidth": 220.0,
"RightPanelWidth": 230.0, "RightPanelWidth": 230.0,
"GenerateAnnotatedImage": true, "GenerateAnnotatedImage": true,
"SilentDetection": false "SilentDetection": false,
"ShowDatasetWithDetectionsOnly": false
} }
} }
+4 -1
View File
@@ -19,7 +19,7 @@
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1" }, { "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1" },
{ "Id": 16, "Name": "Caponier", "ShortName": "Капонір", "Color": "#ffa500" } { "Id": 16, "Name": "Caponier", "ShortName": "Капонір", "Color": "#ffa500" }
], ],
"VideoFormats": [ ".mp4", ".mov", ".avi" ], "VideoFormats": [ ".mp4", ".mov", ".avi", ".ts", ".mkv" ],
"ImageFormats": [ ".jpg", ".jpeg", ".png", ".bmp" ], "ImageFormats": [ ".jpg", ".jpeg", ".png", ".bmp" ],
"AnnotationsDbFile": "annotations.db" "AnnotationsDbFile": "annotations.db"
}, },
@@ -34,5 +34,8 @@
"ModelBatchSize": 4 "ModelBatchSize": 4
}, },
"GpsDeniedConfig": {
"MinKeyPoints": 15
},
"ThumbnailConfig": { "Size": "240,135", "Border": 10 } "ThumbnailConfig": { "Size": "240,135", "Border": 10 }
} }
+1 -1
View File
@@ -36,6 +36,6 @@ public class GetTilesTestClass
GpsSatDirectory = "satelliteMaps" GpsSatDirectory = "satelliteMaps"
}), httpClientFactory, Mock.Of<IMediator>()); }), httpClientFactory, Mock.Of<IMediator>());
await satelliteDownloader.GetTiles(48.2748909, 37.3834877, 600, 18); await satelliteDownloader.GetTiles(new GeoPoint(48.2748909, 37.3834877), 600, 18);
} }
} }
+7 -2
View File
@@ -10,13 +10,18 @@ cd ..
xcopy Azaion.Suite\bin\Release\net8.0-windows\win-x64\publish dist\ /s /e /q xcopy Azaion.Suite\bin\Release\net8.0-windows\win-x64\publish dist\ /s /e /q
del dist\config.json del dist\config.json
move dist\config.production.json dist\config.json
robocopy "dist" "dist-azaion" "Azaion.Annotator.dll" "Azaion.Dataset.dll" "Azaion.Common.dll" "Azaion.CommonSecurity.dll" /MOV robocopy "dist" "dist-azaion" "Azaion.Annotator.dll" "Azaion.Dataset.dll" "Azaion.Common.dll" "Azaion.CommonSecurity.dll" /MOV
robocopy "dist" "dist-azaion" "Azaion.Suite.dll" "Azaion.Suite.exe" "Azaion.Suite.runtimeconfig.json" "Azaion.Suite.deps.json" "config.json" "logo.png" /MOV robocopy "dist" "dist-azaion" "Azaion.Suite.dll" "Azaion.Suite.exe" "Azaion.Suite.runtimeconfig.json" "Azaion.Suite.deps.json" "logo.png" /MOV
robocopy "Azaion.LoaderUI\bin\Release\net8.0-windows\win-x64\publish" "dist-dlls" "Azaion.LoaderUI.dll" "Azaion.LoaderUI.exe" "Azaion.LoaderUI.runtimeconfig.json" ^ robocopy "Azaion.LoaderUI\bin\Release\net8.0-windows\win-x64\publish" "dist-dlls" "Azaion.LoaderUI.dll" "Azaion.LoaderUI.exe" "Azaion.LoaderUI.runtimeconfig.json" ^
"Azaion.LoaderUI.deps.json" "loaderconfig.json" "Azaion.LoaderUI.deps.json" "loaderconfig.json"
move dist\config.production.json dist\config.json
rem if config updates:
rem robocopy "dist" "dist-azaion" "config.json" /MOV
rem else:
del dist\config.json
if exist dist\libvlc\win-x86 rmdir dist\libvlc\win-x86 /s /q if exist dist\libvlc\win-x86 rmdir dist\libvlc\win-x86 /s /q
robocopy "dist" "dist-dlls" /E /MOVE robocopy "dist" "dist-dlls" /E /MOVE
+1 -1
View File
@@ -17,7 +17,7 @@ call build\download_models
echo building and upload iterative installer... echo building and upload iterative installer...
iscc build\installer.iterative.iss iscc build\installer.iterative.iss
call build\upload.cmd "suite" call build\upload.cmd "suite-dev"
echo building full installer echo building full installer
iscc build\installer.full.iss iscc build\installer.full.iss