mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 11:16:30 +00:00
switcher dataset explorer
lat lon -> geopoint correct location for gps if small keypoints number
This commit is contained in:
@@ -51,7 +51,6 @@ public partial class Annotator
|
||||
|
||||
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50);
|
||||
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(150);
|
||||
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
|
||||
|
||||
public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
|
||||
public ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
|
||||
@@ -223,10 +222,6 @@ public partial class Annotator
|
||||
Volume.ValueChanged += (_, newValue) =>
|
||||
_mediator.Publish(new VolumeChangedEvent((int)newValue));
|
||||
|
||||
SizeChanged += (_, _) => SaveUserSettings();
|
||||
LocationChanged += (_, _) => SaveUserSettings();
|
||||
StateChanged += (_, _) => SaveUserSettings();
|
||||
|
||||
DgAnnotations.MouseDoubleClick += (sender, args) =>
|
||||
{
|
||||
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.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
||||
|
||||
ThrottleExt.Throttle(() =>
|
||||
{
|
||||
_configUpdater.Save(_appConfig);
|
||||
return Task.CompletedTask;
|
||||
}, SaveConfigTaskId, TimeSpan.FromSeconds(5));
|
||||
_configUpdater.Save(_appConfig);
|
||||
}
|
||||
|
||||
private void ShowTimeAnnotations(TimeSpan time)
|
||||
@@ -589,7 +580,7 @@ public partial class Annotator
|
||||
|
||||
private void SoundDetections(object sender, RoutedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_logger.LogInformation("To be implemented");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class AnnotatorEventHandler(
|
||||
INotificationHandler<AnnotationsDeletedEvent>,
|
||||
INotificationHandler<AnnotationAddedEvent>,
|
||||
INotificationHandler<SetStatusTextEvent>,
|
||||
INotificationHandler<GPSMatcherResultEvent>
|
||||
INotificationHandler<GPSMatcherResultProcessedEvent>
|
||||
{
|
||||
private const int STEP = 20;
|
||||
private const int LARGE_STEP = 5000;
|
||||
@@ -378,20 +378,29 @@ public class AnnotatorEventHandler(
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(GPSMatcherResultEvent e, CancellationToken cancellationToken)
|
||||
public Task Handle(GPSMatcherResultProcessedEvent e, CancellationToken cancellationToken)
|
||||
{
|
||||
mainWindow.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var mapMatcher = mainWindow.MapMatcherComponent;
|
||||
var marker = new GMapMarker(new PointLatLng(e.Latitude, e.Longitude));
|
||||
var ann = mapMatcher.Annotations[e.Index];
|
||||
marker.Shape = new CircleVisual(marker, size: 14, text: e.Image, background: Brushes.Blue);
|
||||
mapMatcher.SatelliteMap.Markers.Add(marker);
|
||||
ann.Lat = e.Latitude;
|
||||
ann.Lon = e.Longitude;
|
||||
mapMatcher.SatelliteMap.Position = new PointLatLng(e.Latitude, e.Longitude);
|
||||
mapMatcher.SatelliteMap.ZoomAndCenterMarkers(null);
|
||||
var ann = mainWindow.MapMatcherComponent.Annotations[e.Index];
|
||||
AddMarker(e.GeoPoint, e.Image, Brushes.Blue);
|
||||
if (e.ProcessedGeoPoint != e.GeoPoint)
|
||||
AddMarker(e.ProcessedGeoPoint, $"{e.Image}: corrected", Brushes.DarkViolet);
|
||||
ann.Lat = e.GeoPoint.Lat;
|
||||
ann.Lon = e.GeoPoint.Lon;
|
||||
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,8 @@ public partial class MapMatcher : UserControl
|
||||
OriginalMediaName = x.Name
|
||||
})).ToDictionary(x => x.i, x => x.Item2);
|
||||
|
||||
var initialLat = double.Parse(TbLat.Text);
|
||||
var initialLon = double.Parse(TbLon.Text);
|
||||
|
||||
await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLat, initialLon);
|
||||
var initialLatLon = new GeoPoint(double.Parse(TbLat.Text), double.Parse(TbLon.Text));
|
||||
await _gpsMatcherService.RunGpsMatching(dir.FullName, initialLatLon);
|
||||
}
|
||||
|
||||
private async void TestGps(object sender, RoutedEventArgs e)
|
||||
@@ -114,8 +112,7 @@ public partial class MapMatcher : UserControl
|
||||
if (string.IsNullOrEmpty(TbGpsMapFolder.Text))
|
||||
return;
|
||||
|
||||
var initialLat = double.Parse(TbLat.Text);
|
||||
var initialLon = double.Parse(TbLon.Text);
|
||||
await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLat, initialLon);
|
||||
var initialLatLon = new GeoPoint(double.Parse(TbLat.Text), double.Parse(TbLon.Text));
|
||||
await _gpsMatcherService.RunGpsMatching(TbGpsMapFolder.Text, initialLatLon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,15 @@ public class Constants
|
||||
|
||||
# endregion AIRecognitionConfig
|
||||
|
||||
# region GpsDeniedConfig
|
||||
|
||||
public static readonly GpsDeniedConfig DefaultGpsDeniedConfig = new()
|
||||
{
|
||||
MinKeyPoints = 15
|
||||
};
|
||||
|
||||
# endregion
|
||||
|
||||
#region Thumbnails
|
||||
|
||||
public static readonly ThumbnailConfig DefaultThumbnailConfig = new()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
@@ -7,7 +8,9 @@ using Azaion.Annotator.DTO;
|
||||
using Azaion.Common.DTO;
|
||||
using MediatR;
|
||||
using Color = System.Windows.Media.Color;
|
||||
using Point = System.Windows.Point;
|
||||
using Rectangle = System.Windows.Shapes.Rectangle;
|
||||
using Size = System.Windows.Size;
|
||||
|
||||
namespace Azaion.Common.Controls;
|
||||
|
||||
@@ -160,7 +163,7 @@ public class CanvasEditor : Canvas
|
||||
return;
|
||||
|
||||
var time = GetTimeFunc();
|
||||
CreateDetectionControl(CurrentAnnClass, time, new CanvasLabel
|
||||
var control = CreateDetectionControl(CurrentAnnClass, time, new CanvasLabel
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
@@ -168,10 +171,42 @@ public class CanvasEditor : Canvas
|
||||
Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y),
|
||||
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)
|
||||
@@ -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);
|
||||
detectionControl.MouseDown += AnnotationPositionStart;
|
||||
@@ -324,6 +359,7 @@ public class CanvasEditor : Canvas
|
||||
Children.Add(detectionControl);
|
||||
CurrentDetections.Add(detectionControl);
|
||||
_newAnnotationRect.Fill = new SolidColorBrush(detectionClass.Color);
|
||||
return detectionControl;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -16,6 +16,8 @@ public class DetectionControl : Border
|
||||
|
||||
private readonly Grid _grid;
|
||||
private readonly Label _detectionLabel;
|
||||
public readonly Canvas DetectionLabelContainer;
|
||||
|
||||
public TimeSpan Time { get; set; }
|
||||
private readonly double _confidence;
|
||||
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) =>
|
||||
_confidence >= 0.995 ? detectionClassName : $"{detectionClassName}: {_confidence * 100:F0}%"; //double
|
||||
|
||||
@@ -62,23 +74,19 @@ public class DetectionControl : Border
|
||||
_resizeStart = resizeStart;
|
||||
_confidence = canvasLabel.Confidence;
|
||||
|
||||
var labelContainer = new Canvas
|
||||
DetectionLabelContainer = new Canvas
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
ClipToBounds = false,
|
||||
Margin = new Thickness(0, 0, 32, 0)
|
||||
};
|
||||
_detectionLabel = new Label
|
||||
{
|
||||
Content = _detectionLabelText(detectionClass.Name),
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(0, -32, 0, 0),
|
||||
FontSize = 16,
|
||||
Visibility = Visibility.Visible
|
||||
};
|
||||
labelContainer.Children.Add(_detectionLabel);
|
||||
DetectionLabelContainer.Children.Add(_detectionLabel);
|
||||
|
||||
_selectionFrame = new Rectangle
|
||||
{
|
||||
@@ -108,7 +116,7 @@ public class DetectionControl : Border
|
||||
};
|
||||
foreach (var rect in _resizedRectangles)
|
||||
_grid.Children.Add(rect);
|
||||
_grid.Children.Add(labelContainer);
|
||||
_grid.Children.Add(DetectionLabelContainer);
|
||||
|
||||
Child = _grid;
|
||||
Cursor = Cursors.SizeAll;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Azaion.Common.Extensions;
|
||||
using Azaion.CommonSecurity;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -26,6 +27,8 @@ public class AppConfig
|
||||
public ThumbnailConfig ThumbnailConfig { get; set; } = null!;
|
||||
|
||||
public MapConfig MapConfig{ get; set; } = null!;
|
||||
|
||||
public GpsDeniedConfig GpsDeniedConfig { get; set; } = null!;
|
||||
}
|
||||
|
||||
public interface IConfigUpdater
|
||||
@@ -36,6 +39,8 @@ public interface IConfigUpdater
|
||||
|
||||
public class ConfigUpdater : IConfigUpdater
|
||||
{
|
||||
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
|
||||
|
||||
public void CheckConfig()
|
||||
{
|
||||
var exePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!;
|
||||
@@ -67,23 +72,27 @@ public class ConfigUpdater : IConfigUpdater
|
||||
},
|
||||
|
||||
ThumbnailConfig = Constants.DefaultThumbnailConfig,
|
||||
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig
|
||||
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig,
|
||||
GpsDeniedConfig = Constants.DefaultGpsDeniedConfig,
|
||||
};
|
||||
Save(appConfig);
|
||||
}
|
||||
|
||||
public void Save(AppConfig config)
|
||||
{
|
||||
//Save only user's config
|
||||
var publicConfig = new
|
||||
ThrottleExt.Throttle(async () =>
|
||||
{
|
||||
config.LoaderClientConfig,
|
||||
config.InferenceClientConfig,
|
||||
config.GpsDeniedClientConfig,
|
||||
config.DirectoriesConfig,
|
||||
config.UIConfig
|
||||
};
|
||||
var publicConfig = new
|
||||
{
|
||||
config.LoaderClientConfig,
|
||||
config.InferenceClientConfig,
|
||||
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; }
|
||||
}
|
||||
@@ -6,4 +6,5 @@ public class UIConfig
|
||||
public double RightPanelWidth { get; set; }
|
||||
public bool GenerateAnnotatedImage { get; set; }
|
||||
public bool SilentDetection { get; set; }
|
||||
public bool ShowDatasetWithDetectionsOnly { get; set; }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -6,11 +6,8 @@ public class SatTile
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public double LeftTopLat { get; }
|
||||
public double LeftTopLon { get; }
|
||||
|
||||
public double BottomRightLat { get; }
|
||||
public double BottomRightLon { get; }
|
||||
public GeoPoint LeftTop { get; }
|
||||
public GeoPoint BottomRight { get; }
|
||||
public string Url { get; set; }
|
||||
|
||||
|
||||
@@ -20,12 +17,12 @@ public class SatTile
|
||||
Y = y;
|
||||
Url = url;
|
||||
|
||||
(LeftTopLat, LeftTopLon) = GeoUtils.TileToWorldPos(x, y, zoom);
|
||||
(BottomRightLat, BottomRightLon) = GeoUtils.TileToWorldPos(x + 1, y + 1, zoom);
|
||||
LeftTop = GeoUtils.TileToWorldPos(x, y, zoom);
|
||||
BottomRight = GeoUtils.TileToWorldPos(x + 1, y + 1, zoom);
|
||||
}
|
||||
|
||||
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})]";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Azaion.Common.Extensions;
|
||||
using Azaion.Common.DTO;
|
||||
|
||||
namespace Azaion.Common.Extensions;
|
||||
|
||||
public static class GeoUtils
|
||||
{
|
||||
@@ -13,26 +15,71 @@ public static class GeoUtils
|
||||
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 lonDeg = x / n * 360.0 - 180.0;
|
||||
var latRad = Math.Atan(Math.Sinh(Math.PI * (1.0 - 2.0 * y / n)));
|
||||
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 minLat = Math.Max(centerLat - latDiff, -90.0);
|
||||
var maxLat = Math.Min(centerLat + latDiff, 90.0);
|
||||
var minLat = Math.Max(centerGeoPoint.Lat - 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 minLon = Math.Max(centerLon - lonDiff, -180.0);
|
||||
var maxLon = Math.Min(centerLon + lonDiff, 180.0);
|
||||
var minLon = Math.Max(centerGeoPoint.Lon - lonDiff, -180.0);
|
||||
var maxLon = Math.Min(centerGeoPoint.Lon + lonDiff, 180.0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class AnnotationService : IAnnotationService
|
||||
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())
|
||||
return;
|
||||
@@ -79,7 +79,7 @@ public class AnnotationService : IAnnotationService
|
||||
OffsetSpec = new OffsetTypeOffset(offsets.AnnotationsOffset),
|
||||
MessageHandler = async (_, _, context, message) =>
|
||||
{
|
||||
await _messageProcessingSemaphore.WaitAsync(cancellationToken);
|
||||
await _messageProcessingSemaphore.WaitAsync(token);
|
||||
try
|
||||
{
|
||||
var email = (string)message.ApplicationProperties[nameof(User.Email)]!;
|
||||
@@ -101,15 +101,15 @@ public class AnnotationService : IAnnotationService
|
||||
msg.Role,
|
||||
msg.Email,
|
||||
context.Offset,
|
||||
token: cancellationToken);
|
||||
token: token);
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = MessagePackSerializer.Deserialize<AnnotationBulkMessage>(message.Data.Contents);
|
||||
if (annotationStatus == AnnotationStatus.Validated)
|
||||
await ValidateAnnotations(msg.AnnotationNames.ToList(), true, cancellationToken);
|
||||
await ValidateAnnotations(msg.AnnotationNames.ToList(), true, token);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,45 @@
|
||||
using Azaion.Common.DTO;
|
||||
using MediatR;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
public enum MatchTypeEnum
|
||||
{
|
||||
None = -1,
|
||||
MatchTypeSingle = 0,
|
||||
MatchTypeStitched = 1,
|
||||
MatchTypeOpticalFlow = 2,
|
||||
MatchTypeInterpolated = 3,
|
||||
MatchTypeFailure = 4
|
||||
}
|
||||
|
||||
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 int Index { get; set; }
|
||||
public string Image { get; set; } = null!;
|
||||
public GeoPoint GeoPoint { get; set; } = null!;
|
||||
public int KeyPoints { get; set; }
|
||||
public MatchTypeEnum MatchType { get; set; }
|
||||
}
|
||||
|
||||
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 GPSMatcherFinishedEvent : INotification {}
|
||||
@@ -1,46 +1,51 @@
|
||||
using System.IO;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Extensions;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Azaion.Common.Services;
|
||||
|
||||
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();
|
||||
Task SetGpsResult(GPSMatcherResultEvent result, CancellationToken detectToken = default);
|
||||
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 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);
|
||||
private const int MAX_AVG_POINTS = 2;
|
||||
|
||||
private string _routeDir = "";
|
||||
private string _userRouteDir = "";
|
||||
private List<string> _allRouteImages = new();
|
||||
private Dictionary<string, int> _currentRouteImages = new();
|
||||
private double _currentLat;
|
||||
private double _currentLon;
|
||||
private GeoPoint _lastGeoPoint = new();
|
||||
private CancellationToken _detectToken;
|
||||
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);
|
||||
_userRouteDir = userRouteDir;
|
||||
|
||||
_allRouteImages = Directory.GetFiles(userRouteDir)
|
||||
.OrderBy(x => x).ToList();
|
||||
|
||||
_currentLat = initialLatitude;
|
||||
_currentLon = initialLongitude;
|
||||
|
||||
_lastGeoPoint = initGeoPoint;
|
||||
_detectToken = detectToken;
|
||||
await StartMatchingRound(0);
|
||||
}
|
||||
@@ -63,12 +68,11 @@ public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDow
|
||||
})
|
||||
.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
|
||||
{
|
||||
ImagesCount = POINTS_COUNT,
|
||||
Latitude = _currentLat,
|
||||
Longitude = _currentLon,
|
||||
GeoPoint = _lastGeoPoint,
|
||||
SatelliteImagesDir = _dirConfig.GpsSatDirectory,
|
||||
RouteDir = _dirConfig.GpsRouteDirectory
|
||||
});
|
||||
@@ -83,9 +87,21 @@ public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDow
|
||||
{
|
||||
_currentIndex = _currentRouteImages[result.Image];
|
||||
_currentRouteImages.Remove(result.Image);
|
||||
_currentLat = result.Latitude;
|
||||
_currentLon = result.Longitude;
|
||||
await Task.CompletedTask;
|
||||
|
||||
if (result.KeyPoints > gpsDeniedConfig.Value.MinKeyPoints)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -18,17 +18,16 @@ public interface IGpsMatcherClient : IDisposable
|
||||
|
||||
public class StartMatchingEvent
|
||||
{
|
||||
public string RouteDir { get; set; } = null!;
|
||||
public string SatelliteImagesDir { get; set; } = null!;
|
||||
public int ImagesCount { get; set; }
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public int Altitude { get; set; } = 400;
|
||||
public double CameraSensorWidth { get; set; } = 23.5;
|
||||
public double CameraFocalLength { get; set; } = 24;
|
||||
public string RouteDir { get; set; } = null!;
|
||||
public string SatelliteImagesDir { get; set; } = null!;
|
||||
public int ImagesCount { get; set; }
|
||||
public GeoPoint GeoPoint { get; set; } = null!;
|
||||
public int Altitude { get; set; } = 400;
|
||||
public double CameraSensorWidth { get; set; } = 23.5;
|
||||
public double CameraFocalLength { get; set; } = 24;
|
||||
|
||||
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
|
||||
@@ -59,7 +58,6 @@ public class GpsMatcherClient : IGpsMatcherClient
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.ToString());
|
||||
throw;
|
||||
}
|
||||
|
||||
_requestAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqPort}";
|
||||
@@ -93,7 +91,7 @@ public class GpsMatcherClient : IGpsMatcherClient
|
||||
break;
|
||||
default:
|
||||
var parts = str.Split(',');
|
||||
if (parts.Length != 5)
|
||||
if (parts.Length != 6)
|
||||
throw new Exception("Matching Result Failed");
|
||||
|
||||
var filename = Path.GetFileNameWithoutExtension(parts[1]);
|
||||
@@ -101,9 +99,9 @@ public class GpsMatcherClient : IGpsMatcherClient
|
||||
{
|
||||
Index = int.Parse(parts[0]),
|
||||
Image = filename,
|
||||
Latitude = double.Parse(parts[2]),
|
||||
Longitude = double.Parse(parts[3]),
|
||||
MatchType = parts[4]
|
||||
GeoPoint = new GeoPoint(double.Parse(parts[2]), double.Parse(parts[3])),
|
||||
KeyPoints = int.Parse(parts[4]),
|
||||
MatchType = Enum.TryParse<MatchTypeEnum>(parts[5], out var type) ? type : MatchTypeEnum.None
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ public class InferenceClient : IInferenceClient
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Azaion.Common.Services;
|
||||
|
||||
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(
|
||||
@@ -45,21 +45,28 @@ public class SatelliteDownloader(
|
||||
private readonly string _apiKey = mapConfig.Value.ApiKey;
|
||||
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
|
||||
if (Directory.Exists(_satDirectory))
|
||||
Directory.Delete(_satDirectory, true);
|
||||
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);
|
||||
var image = ComposeTiles(downloadTilesResult.Tiles, token);
|
||||
var image = ComposeTiles(dtRes.Tiles, token);
|
||||
if (image == null)
|
||||
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 SplitToTiles(image, downloadTilesResult, token);
|
||||
await SplitToTiles(image, dtRes, token);
|
||||
}
|
||||
|
||||
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 (xMax, yMax) = GeoUtils.WorldToTilePos(latMin, lonMax, zoomLevel); // Bottom-right corner
|
||||
|
||||
@@ -73,9 +73,27 @@
|
||||
<ColumnDefinition Width="4"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:DetectionClasses x:Name="LvClasses"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0" />
|
||||
<Grid Name="LeftPane"
|
||||
ShowGridLines="False"
|
||||
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
|
||||
Name="Switcher"
|
||||
Grid.Column="2"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Azaion.Common.Database;
|
||||
@@ -18,60 +19,56 @@ namespace Azaion.Dataset;
|
||||
public partial class DatasetExplorer
|
||||
{
|
||||
private readonly ILogger<DatasetExplorer> _logger;
|
||||
private readonly AnnotationConfig _annotationConfig;
|
||||
private readonly DirectoriesConfig _directoriesConfig;
|
||||
private readonly AppConfig _appConfig;
|
||||
|
||||
private readonly Dictionary<int, Dictionary<string, Annotation>> _annotationsDict;
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
|
||||
public List<DetectionClass> AllDetectionClasses { get; set; }
|
||||
public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; set; } = new();
|
||||
private List<DetectionClass> AllDetectionClasses { get; }
|
||||
public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; } = new();
|
||||
public readonly Dictionary<string, AnnotationThumbnail> SelectedAnnotationDict = new();
|
||||
|
||||
private int _tempSelectedClassIdx = 0;
|
||||
private int _tempSelectedClassIdx;
|
||||
private readonly IGalleryService _galleryService;
|
||||
private readonly IDbFactory _dbFactory;
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public readonly List<DetectionClass> AnnotationsClasses;
|
||||
private IAzaionApi _azaionApi;
|
||||
|
||||
private readonly IAzaionApi _azaionApi;
|
||||
private readonly IConfigUpdater _configUpdater;
|
||||
|
||||
public bool ThumbnailLoading { get; set; }
|
||||
|
||||
public AnnotationThumbnail? CurrentAnnotation { get; set; }
|
||||
|
||||
public DatasetExplorer(
|
||||
IOptions<DirectoriesConfig> directoriesConfig,
|
||||
IOptions<AnnotationConfig> annotationConfig,
|
||||
IOptions<AppConfig> appConfig,
|
||||
ILogger<DatasetExplorer> logger,
|
||||
IGalleryService galleryService,
|
||||
FormState formState,
|
||||
IDbFactory dbFactory,
|
||||
IMediator mediator,
|
||||
IAzaionApi azaionApi)
|
||||
IAzaionApi azaionApi,
|
||||
IConfigUpdater configUpdater)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_directoriesConfig = directoriesConfig.Value;
|
||||
_annotationConfig = annotationConfig.Value;
|
||||
_appConfig = appConfig.Value;
|
||||
_logger = logger;
|
||||
_galleryService = galleryService;
|
||||
_dbFactory = dbFactory;
|
||||
_mediator = mediator;
|
||||
_azaionApi = azaionApi;
|
||||
_configUpdater = configUpdater;
|
||||
|
||||
ShowWithObjectsOnlyChBox.IsChecked = _appConfig.UIConfig.ShowDatasetWithDetectionsOnly;
|
||||
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>());
|
||||
_annotationsDict.Add(-1, []);
|
||||
|
||||
AnnotationsClasses = annotationConfig.Value.DetectionClasses;
|
||||
|
||||
Loaded += OnLoaded;
|
||||
Activated += (_, _) => formState.ActiveWindow = WindowEnum.DatasetExplorer;
|
||||
|
||||
ThumbnailsView.KeyDown += async (sender, args) =>
|
||||
ThumbnailsView.KeyDown += async (_, args) =>
|
||||
{
|
||||
switch (args.Key)
|
||||
{
|
||||
@@ -102,7 +99,7 @@ public partial class DatasetExplorer
|
||||
|
||||
AllDetectionClasses = new List<DetectionClass>(
|
||||
new List<DetectionClass> { new() {Id = -1, Name = "All", ShortName = "All"}}
|
||||
.Concat(_annotationConfig.DetectionClasses));
|
||||
.Concat(_appConfig.AnnotationConfig.DetectionClasses));
|
||||
LvClasses.Init(AllDetectionClasses);
|
||||
}
|
||||
|
||||
@@ -119,7 +116,7 @@ public partial class DatasetExplorer
|
||||
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 =>
|
||||
await db.Annotations.LoadWith(x => x.Detections)
|
||||
@@ -150,8 +147,8 @@ public partial class DatasetExplorer
|
||||
.OrderBy(x => x.Key)
|
||||
.Select(gr => new ClusterDistribution
|
||||
{
|
||||
Label = $"{_annotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}",
|
||||
Color = _annotationConfig.DetectionClassesDict[gr.Key].Color,
|
||||
Label = $"{_appConfig.AnnotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}",
|
||||
Color = _appConfig.AnnotationConfig.DetectionClassesDict[gr.Key].Color,
|
||||
ClassCount = gr.Value.Count
|
||||
})
|
||||
.Where(x => x.ClassCount > 0)
|
||||
@@ -173,7 +170,7 @@ public partial class DatasetExplorer
|
||||
RefreshThumbnailsButtonItem.Visibility = Visibility.Hidden;
|
||||
RefreshProgressBarItem.Visibility = Visibility.Visible;
|
||||
|
||||
var result = MessageBox.Show($"Видалити всі іконки та згенерувати нову базу іконок в {_directoriesConfig.ThumbnailsDirectory}?",
|
||||
var result = MessageBox.Show($"Видалити всі іконки та згенерувати нову базу іконок в {_appConfig.DirectoriesConfig.ThumbnailsDirectory}?",
|
||||
"Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result != MessageBoxResult.Yes)
|
||||
return;
|
||||
@@ -204,7 +201,7 @@ public partial class DatasetExplorer
|
||||
|
||||
var time = ann.Time;
|
||||
ExplorerEditor.RemoveAllAnns();
|
||||
ExplorerEditor.CreateDetections(time, ann.Detections, _annotationConfig.DetectionClasses, ExplorerEditor.RenderSize);
|
||||
ExplorerEditor.CreateDetections(time, ann.Detections, _appConfig.AnnotationConfig.DetectionClasses, ExplorerEditor.RenderSize);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -229,7 +226,7 @@ public partial class DatasetExplorer
|
||||
AnnotationsTab.Visibility = Visibility.Collapsed;
|
||||
EditorTab.Visibility = Visibility.Visible;
|
||||
_tempSelectedClassIdx = LvClasses.CurrentClassNumber;
|
||||
LvClasses.DetectionDataGrid.ItemsSource = _annotationConfig.DetectionClasses;
|
||||
LvClasses.DetectionDataGrid.ItemsSource = _appConfig.AnnotationConfig.DetectionClasses;
|
||||
|
||||
Switcher.SelectedIndex = 1;
|
||||
LvClasses.SelectNum(Math.Max(0, _tempSelectedClassIdx - 1));
|
||||
@@ -259,16 +256,15 @@ public partial class DatasetExplorer
|
||||
|
||||
private async Task ReloadThumbnails()
|
||||
{
|
||||
var withDetectionsOnly = ShowWithObjectsOnlyChBox.IsChecked;
|
||||
SelectedAnnotations.Clear();
|
||||
SelectedAnnotationDict.Clear();
|
||||
var annThumbnails = _annotationsDict[ExplorerEditor.CurrentAnnClass.YoloId]
|
||||
.WhereIf(withDetectionsOnly, x => x.Value.Detections.Any())
|
||||
.Select(x => new AnnotationThumbnail(x.Value, _azaionApi.CurrentUser.Role.IsValidator()))
|
||||
.OrderBy(x => !x.IsSeed)
|
||||
.ThenByDescending(x =>x.Annotation.CreatedDate);
|
||||
|
||||
//var dict = annThumbnails.Take(20).ToDictionary(x => x.Annotation.Name, x => x.IsSeed);
|
||||
|
||||
|
||||
foreach (var thumb in annThumbnails)
|
||||
{
|
||||
SelectedAnnotations.Add(thumb);
|
||||
@@ -292,4 +288,11 @@ public partial class DatasetExplorer
|
||||
_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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,12 @@ public class DatasetExplorerEventHandler(
|
||||
{ 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)
|
||||
return;
|
||||
@@ -52,11 +52,11 @@ public class DatasetExplorerEventHandler(
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -70,7 +70,8 @@ public class DatasetExplorerEventHandler(
|
||||
.Select(x => new Detection(a.Name, x.GetLabel(datasetExplorer.ExplorerEditor.RenderSize)))
|
||||
.ToList();
|
||||
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);
|
||||
break;
|
||||
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||
@@ -98,19 +99,24 @@ public class DatasetExplorerEventHandler(
|
||||
var annotations = datasetExplorer.ThumbnailsView.SelectedItems.Cast<AnnotationThumbnail>()
|
||||
.Select(x => x.Annotation)
|
||||
.ToList();
|
||||
await annotationService.ValidateAnnotations(annotations.Select(x => x.Name).ToList(), token: cancellationToken);
|
||||
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();
|
||||
}
|
||||
await ValidateAnnotations(annotations, token);
|
||||
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(() =>
|
||||
{
|
||||
@@ -140,7 +146,7 @@ public class DatasetExplorerEventHandler(
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken)
|
||||
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import subprocess
|
||||
cimport constants
|
||||
cdef class HardwareService:
|
||||
|
||||
@staticmethod
|
||||
@@ -33,4 +34,5 @@ cdef class HardwareService:
|
||||
cdef str drive_serial = lines[len_lines-1]
|
||||
|
||||
cdef str res = f'CPU: {cpu}. GPU: {gpu}. Memory: {memory}. DriveSerial: {drive_serial}'
|
||||
constants.log(f'Gathered hardware: {res}')
|
||||
return res
|
||||
|
||||
@@ -83,6 +83,7 @@ public partial class App
|
||||
{
|
||||
AnnotationConfig = Constants.DefaultAnnotationConfig,
|
||||
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig,
|
||||
GpsDeniedConfig = Constants.DefaultGpsDeniedConfig,
|
||||
ThumbnailConfig = Constants.DefaultThumbnailConfig,
|
||||
})));
|
||||
}
|
||||
@@ -132,6 +133,7 @@ public partial class App
|
||||
services.ConfigureSection<DirectoriesConfig>(context.Configuration);
|
||||
services.ConfigureSection<AnnotationConfig>(context.Configuration);
|
||||
services.ConfigureSection<AIRecognitionConfig>(context.Configuration);
|
||||
services.ConfigureSection<GpsDeniedConfig>(context.Configuration);
|
||||
services.ConfigureSection<ThumbnailConfig>(context.Configuration);
|
||||
services.ConfigureSection<UIConfig>(context.Configuration);
|
||||
services.ConfigureSection<MapConfig>(context.Configuration);
|
||||
|
||||
@@ -25,7 +25,6 @@ public partial class MainSuite
|
||||
private readonly Dictionary<WindowEnum, Window> _openedWindows = new();
|
||||
private readonly IInferenceClient _inferenceClient;
|
||||
private readonly IGpsMatcherClient _gpsMatcherClient;
|
||||
private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
|
||||
|
||||
public MainSuite(IOptions<AppConfig> appConfig,
|
||||
IConfigUpdater configUpdater,
|
||||
@@ -48,10 +47,6 @@ public partial class MainSuite
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
Closed += OnFormClosed;
|
||||
|
||||
SizeChanged += (_, _) => SaveUserSettings();
|
||||
LocationChanged += (_, _) => SaveUserSettings();
|
||||
StateChanged += (_, _) => SaveUserSettings();
|
||||
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)
|
||||
{
|
||||
_configUpdater.Save(_appConfig);
|
||||
foreach (var window in _openedWindows)
|
||||
window.Value.Close();
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"LoaderClientConfig": {
|
||||
"ZeroMqHost": "127.0.0.1",
|
||||
"ZeroMqPort": 5025,
|
||||
"ZeroMqPort": 5024,
|
||||
"ApiUrl": "https://api.azaion.com"
|
||||
},
|
||||
"InferenceClientConfig": {
|
||||
"ZeroMqHost": "127.0.0.1",
|
||||
"ZeroMqPort": 5127,
|
||||
"ZeroMqPort": 5126,
|
||||
"ApiUrl": "https://api.azaion.com"
|
||||
},
|
||||
"GpsDeniedClientConfig": {
|
||||
@@ -28,6 +28,7 @@
|
||||
"LeftPanelWidth": 220.0,
|
||||
"RightPanelWidth": 230.0,
|
||||
"GenerateAnnotatedImage": true,
|
||||
"SilentDetection": false
|
||||
"SilentDetection": false,
|
||||
"ShowDatasetWithDetectionsOnly": false
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1" },
|
||||
{ "Id": 16, "Name": "Caponier", "ShortName": "Капонір", "Color": "#ffa500" }
|
||||
],
|
||||
"VideoFormats": [ ".mp4", ".mov", ".avi" ],
|
||||
"VideoFormats": [ ".mp4", ".mov", ".avi", ".ts", ".mkv" ],
|
||||
"ImageFormats": [ ".jpg", ".jpeg", ".png", ".bmp" ],
|
||||
"AnnotationsDbFile": "annotations.db"
|
||||
},
|
||||
@@ -34,5 +34,8 @@
|
||||
|
||||
"ModelBatchSize": 4
|
||||
},
|
||||
"GpsDeniedConfig": {
|
||||
"MinKeyPoints": 15
|
||||
},
|
||||
"ThumbnailConfig": { "Size": "240,135", "Border": 10 }
|
||||
}
|
||||
@@ -36,6 +36,6 @@ public class GetTilesTestClass
|
||||
GpsSatDirectory = "satelliteMaps"
|
||||
}), httpClientFactory, Mock.Of<IMediator>());
|
||||
|
||||
await satelliteDownloader.GetTiles(48.2748909, 37.3834877, 600, 18);
|
||||
await satelliteDownloader.GetTiles(new GeoPoint(48.2748909, 37.3834877), 600, 18);
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,18 @@ cd ..
|
||||
|
||||
xcopy Azaion.Suite\bin\Release\net8.0-windows\win-x64\publish dist\ /s /e /q
|
||||
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.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" ^
|
||||
"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
|
||||
robocopy "dist" "dist-dlls" /E /MOVE
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ call build\download_models
|
||||
|
||||
echo building and upload iterative installer...
|
||||
iscc build\installer.iterative.iss
|
||||
call build\upload.cmd "suite"
|
||||
call build\upload.cmd "suite-dev"
|
||||
|
||||
echo building full installer
|
||||
iscc build\installer.full.iss
|
||||
|
||||
Reference in New Issue
Block a user