mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 10:46:30 +00:00
switcher dataset explorer
lat lon -> geopoint correct location for gps if small keypoints number
This commit is contained in:
@@ -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,12 +171,44 @@ 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)
|
||||
{
|
||||
_horizontalLine.X2 = e.NewSize.Width;
|
||||
@@ -253,7 +288,7 @@ public class CanvasEditor : Canvas
|
||||
ClearSelections();
|
||||
|
||||
_curAnn.IsSelected = true;
|
||||
|
||||
|
||||
SelectionState = SelectionState.AnnMoving;
|
||||
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);
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user