add altitude + camera spec component and calc tile size by this

also restrict detections to be no bigger than in classes.json
This commit is contained in:
Oleksandr Bezdieniezhnykh
2025-09-23 01:48:10 +03:00
parent b0e4b467c1
commit fde9a9f418
36 changed files with 715 additions and 222 deletions
+16 -5
View File
@@ -76,6 +76,7 @@
<RowDefinition Height="28"></RowDefinition> <RowDefinition Height="28"></RowDefinition>
<RowDefinition Height="28"></RowDefinition> <RowDefinition Height="28"></RowDefinition>
<RowDefinition Height="*"></RowDefinition> <RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="80"></RowDefinition>
<RowDefinition Height="*"></RowDefinition> <RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -191,10 +192,20 @@
</GridView> </GridView>
</ListView.View> </ListView.View>
</ListView> </ListView>
<controls1:CameraConfigControl
x:Name="CameraConfigControl"
Grid.Column="0"
Grid.Row="4"
Camera="{Binding Camera, RelativeSource={RelativeSource AncestorType=Window}, Mode=OneWay}"
>
</controls1:CameraConfigControl>
<controls1:DetectionClasses <controls1:DetectionClasses
x:Name="LvClasses" x:Name="LvClasses"
Grid.Column="0" Grid.Column="0"
Grid.Row="4"> Grid.Row="5">
</controls1:DetectionClasses> </controls1:DetectionClasses>
<GridSplitter <GridSplitter
@@ -202,7 +213,7 @@
ResizeDirection="Columns" ResizeDirection="Columns"
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Grid.RowSpan="4" Grid.RowSpan="5"
ResizeBehavior="PreviousAndNext" ResizeBehavior="PreviousAndNext"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@@ -211,7 +222,7 @@
<wpf:VideoView <wpf:VideoView
Grid.Row="1" Grid.Row="1"
Grid.Column="2" Grid.Column="2"
Grid.RowSpan="4" Grid.RowSpan="5"
x:Name="VideoView"> x:Name="VideoView">
<controls1:CanvasEditor x:Name="Editor" <controls1:CanvasEditor x:Name="Editor"
Background="#01000000" Background="#01000000"
@@ -224,7 +235,7 @@
ResizeDirection="Columns" ResizeDirection="Columns"
Grid.Column="3" Grid.Column="3"
Grid.Row="1" Grid.Row="1"
Grid.RowSpan="4" Grid.RowSpan="5"
ResizeBehavior="PreviousAndNext" ResizeBehavior="PreviousAndNext"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
@@ -234,7 +245,7 @@
<DataGrid x:Name="DgAnnotations" <DataGrid x:Name="DgAnnotations"
Grid.Column="4" Grid.Column="4"
Grid.Row="1" Grid.Row="1"
Grid.RowSpan="4" Grid.RowSpan="5"
Background="Black" Background="Black"
Foreground="White" Foreground="White"
RowHeaderWidth="0" RowHeaderWidth="0"
+38 -24
View File
@@ -29,7 +29,7 @@ namespace Azaion.Annotator;
public partial class Annotator public partial class Annotator
{ {
private readonly AppConfig _appConfig; private readonly AppConfig? _appConfig;
private readonly LibVLC _libVlc; private readonly LibVLC _libVlc;
private readonly MediaPlayer _mediaPlayer; private readonly MediaPlayer _mediaPlayer;
private readonly IMediator _mediator; private readonly IMediator _mediator;
@@ -46,7 +46,7 @@ public partial class Annotator
private bool _gpsPanelVisible; private bool _gpsPanelVisible;
private readonly CancellationTokenSource _mainCancellationSource = new(); private readonly CancellationTokenSource _mainCancellationSource = new();
public CancellationTokenSource DetectionCancellationSource = new(); public CancellationTokenSource DetCancelSource = new();
private bool _isInferenceNow; private bool _isInferenceNow;
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50); private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(50);
@@ -59,6 +59,8 @@ public partial class Annotator
public IntervalTree<TimeSpan, Annotation> TimedAnnotations { get; set; } = new(); public IntervalTree<TimeSpan, Annotation> TimedAnnotations { get; set; } = new();
public string MainTitle { get; set; } public string MainTitle { get; set; }
public CameraConfig Camera => _appConfig?.CameraConfig ?? new CameraConfig();
public Annotator( public Annotator(
IConfigUpdater configUpdater, IConfigUpdater configUpdater,
IOptions<AppConfig> appConfig, IOptions<AppConfig> appConfig,
@@ -73,10 +75,7 @@ public partial class Annotator
IInferenceClient inferenceClient, IInferenceClient inferenceClient,
IGpsMatcherService gpsMatcherService) IGpsMatcherService gpsMatcherService)
{ {
InitializeComponent(); // Initialize configuration and services BEFORE InitializeComponent so bindings can see real values
MainTitle = $"Azaion Annotator {Constants.GetLocalVersion()}";
Title = MainTitle;
_appConfig = appConfig.Value; _appConfig = appConfig.Value;
_configUpdater = configUpdater; _configUpdater = configUpdater;
_libVlc = libVlc; _libVlc = libVlc;
@@ -89,6 +88,14 @@ public partial class Annotator
_inferenceService = inferenceService; _inferenceService = inferenceService;
_inferenceClient = inferenceClient; _inferenceClient = inferenceClient;
// Ensure bindings (e.g., Camera) resolve immediately
DataContext = this;
InitializeComponent();
MainTitle = $"Azaion Annotator {Constants.GetLocalVersion()}";
Title = MainTitle;
Loaded += OnLoaded; Loaded += OnLoaded;
Closed += OnFormClosed; Closed += OnFormClosed;
Activated += (_, _) => _formState.ActiveWindow = WindowEnum.Annotator; Activated += (_, _) => _formState.ActiveWindow = WindowEnum.Annotator;
@@ -100,7 +107,6 @@ public partial class Annotator
{ {
_appConfig.DirectoriesConfig.VideosDirectory = TbFolder.Text; _appConfig.DirectoriesConfig.VideosDirectory = TbFolder.Text;
await ReloadFiles(); await ReloadFiles();
SaveUserSettings();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -109,6 +115,13 @@ public partial class Annotator
}; };
Editor.GetTimeFunc = () => TimeSpan.FromMilliseconds(_mediaPlayer.Time); Editor.GetTimeFunc = () => TimeSpan.FromMilliseconds(_mediaPlayer.Time);
MapMatcherComponent.Init(_appConfig, gpsMatcherService); MapMatcherComponent.Init(_appConfig, gpsMatcherService);
// When camera settings change, persist config
CameraConfigControl.CameraChanged += (_, _) =>
{
if (_appConfig != null)
_configUpdater.Save(_appConfig);
};
} }
private void OnLoaded(object sender, RoutedEventArgs e) private void OnLoaded(object sender, RoutedEventArgs e)
@@ -118,13 +131,13 @@ public partial class Annotator
_suspendLayout = true; _suspendLayout = true;
MainGrid.ColumnDefinitions.FirstOrDefault()!.Width = new GridLength(_appConfig.UIConfig.LeftPanelWidth); MainGrid.ColumnDefinitions.FirstOrDefault()!.Width = new GridLength(_appConfig?.UIConfig.LeftPanelWidth ?? Constants.DEFAULT_LEFT_PANEL_WIDTH);
MainGrid.ColumnDefinitions.LastOrDefault()!.Width = new GridLength(_appConfig.UIConfig.RightPanelWidth); MainGrid.ColumnDefinitions.LastOrDefault()!.Width = new GridLength(_appConfig?.UIConfig.RightPanelWidth ?? Constants.DEFAULT_RIGHT_PANEL_WIDTH);
_suspendLayout = false; _suspendLayout = false;
TbFolder.Text = _appConfig.DirectoriesConfig.VideosDirectory; TbFolder.Text = _appConfig?.DirectoriesConfig.VideosDirectory ?? Constants.DEFAULT_VIDEO_DIR;
LvClasses.Init(_appConfig.AnnotationConfig.DetectionClasses); LvClasses.Init(_appConfig?.AnnotationConfig.DetectionClasses ?? Constants.DefaultAnnotationClasses);
} }
public void BlinkHelp(string helpText, int times = 2) public void BlinkHelp(string helpText, int times = 2)
@@ -228,7 +241,7 @@ public partial class Annotator
} }
private void SaveUserSettings() private void SaveUserSettings()
{ {
if (_suspendLayout) if (_suspendLayout || _appConfig is null)
return; return;
_appConfig.UIConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value; _appConfig.UIConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
@@ -269,7 +282,7 @@ public partial class Annotator
Editor.ZoomTo(new Point(canvasTileLocation.CenterX, canvasTileLocation.CenterY)); Editor.ZoomTo(new Point(canvasTileLocation.CenterX, canvasTileLocation.CenterY));
} }
else else
Editor.CreateDetections(annotation, _appConfig.AnnotationConfig.DetectionClasses, _formState.CurrentMediaSize); Editor.CreateDetections(annotation, _appConfig?.AnnotationConfig.DetectionClasses ?? [], _formState.CurrentMediaSize);
}); });
} }
@@ -333,11 +346,12 @@ public partial class Annotator
private async Task ReloadFiles() private async Task ReloadFiles()
{ {
var dir = new DirectoryInfo(_appConfig.DirectoriesConfig.VideosDirectory); var dir = new DirectoryInfo(_appConfig?.DirectoriesConfig.VideosDirectory ?? Constants.DEFAULT_VIDEO_DIR);
if (!dir.Exists) if (!dir.Exists)
return; return;
var videoFiles = dir.GetFiles(_appConfig.AnnotationConfig.VideoFormats.ToArray()).Select(x => var videoFiles = dir.GetFiles((_appConfig?.AnnotationConfig.VideoFormats ?? Constants.DefaultVideoFormats)
.ToArray()).Select(x =>
{ {
var media = new Media(_libVlc, x.FullName); var media = new Media(_libVlc, x.FullName);
media.Parse(); media.Parse();
@@ -351,7 +365,7 @@ public partial class Annotator
return fInfo; return fInfo;
}).ToList(); }).ToList();
var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray()) var imageFiles = dir.GetFiles((_appConfig?.AnnotationConfig.ImageFormats ?? Constants.DefaultImageFormats).ToArray())
.Select(x => new MediaFileInfo .Select(x => new MediaFileInfo
{ {
Name = x.Name, Name = x.Name,
@@ -383,7 +397,7 @@ public partial class Annotator
{ {
_mainCancellationSource.Cancel(); _mainCancellationSource.Cancel();
_inferenceService.StopInference(); _inferenceService.StopInference();
DetectionCancellationSource.Cancel(); DetCancelSource.Cancel();
_mediaPlayer.Stop(); _mediaPlayer.Stop();
_mediaPlayer.Dispose(); _mediaPlayer.Dispose();
@@ -417,14 +431,16 @@ public partial class Annotator
{ {
Title = "Open Video folder", Title = "Open Video folder",
IsFolderPicker = true, IsFolderPicker = true,
InitialDirectory = Path.GetDirectoryName(_appConfig.DirectoriesConfig.VideosDirectory) InitialDirectory = Path.GetDirectoryName(_appConfig?.DirectoriesConfig.VideosDirectory ?? Constants.DEFAULT_VIDEO_DIR)
}; };
var dialogResult = dlg.ShowDialog(); var dialogResult = dlg.ShowDialog();
if (dialogResult != CommonFileDialogResult.Ok || string.IsNullOrEmpty(dlg.FileName)) if (dialogResult != CommonFileDialogResult.Ok || string.IsNullOrEmpty(dlg.FileName))
return; return;
_appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName; if (_appConfig is not null)
_appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName;
TbFolder.Text = dlg.FileName; TbFolder.Text = dlg.FileName;
} }
@@ -495,7 +511,7 @@ public partial class Annotator
_isInferenceNow = true; _isInferenceNow = true;
AIDetectBtn.IsEnabled = false; AIDetectBtn.IsEnabled = false;
DetectionCancellationSource = new CancellationTokenSource(); DetCancelSource = new CancellationTokenSource();
var files = (FilteredMediaFiles.Count == 0 ? AllMediaFiles : FilteredMediaFiles) var files = (FilteredMediaFiles.Count == 0 ? AllMediaFiles : FilteredMediaFiles)
.Skip(LvFiles.SelectedIndex) .Skip(LvFiles.SelectedIndex)
@@ -504,9 +520,7 @@ public partial class Annotator
if (files.Count == 0) if (files.Count == 0)
return; return;
//TODO: Get Tile Size from UI based on height setup await _inferenceService.RunInference(files, _appConfig?.CameraConfig ?? Constants.DefaultCameraConfig, DetCancelSource.Token);
var tileSize = 550;
await _inferenceService.RunInference(files, tileSize, DetectionCancellationSource.Token);
LvFiles.Items.Refresh(); LvFiles.Items.Refresh();
_isInferenceNow = false; _isInferenceNow = false;
@@ -607,7 +621,7 @@ public class GradientStyleSelector : StyleSelector
foreach (var gradientStop in gradients) foreach (var gradientStop in gradients)
brush.GradientStops.Add(gradientStop); brush.GradientStops.Add(gradientStop);
style.Setters.Add(new Setter(DataGridRow.BackgroundProperty, brush)); style.Setters.Add(new Setter(Control.BackgroundProperty, brush));
return style; return style;
} }
} }
+1 -1
View File
@@ -162,7 +162,7 @@ public class AnnotatorEventHandler(
break; break;
case PlaybackControlEnum.Stop: case PlaybackControlEnum.Stop:
inferenceService.StopInference(); inferenceService.StopInference();
await mainWindow.DetectionCancellationSource.CancelAsync(); await mainWindow.DetCancelSource.CancelAsync();
mediaPlayer.Stop(); mediaPlayer.Stop();
break; break;
case PlaybackControlEnum.PreviousFrame: case PlaybackControlEnum.PreviousFrame:
+73 -65
View File
@@ -22,6 +22,12 @@ public static class Constants
private const string DEFAULT_ZMQ_LOADER_HOST = "127.0.0.1"; private const string DEFAULT_ZMQ_LOADER_HOST = "127.0.0.1";
private const int DEFAULT_ZMQ_LOADER_PORT = 5025; private const int DEFAULT_ZMQ_LOADER_PORT = 5025;
private static readonly LoaderClientConfig DefaultLoaderClientConfig = new()
{
ZeroMqHost = DEFAULT_ZMQ_LOADER_HOST,
ZeroMqPort = DEFAULT_ZMQ_LOADER_PORT,
ApiUrl = DEFAULT_API_URL
};
public const string EXTERNAL_LOADER_PATH = "azaion-loader.exe"; public const string EXTERNAL_LOADER_PATH = "azaion-loader.exe";
public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe"; public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe";
@@ -31,24 +37,32 @@ public static class Constants
public const string DEFAULT_ZMQ_INFERENCE_HOST = "127.0.0.1"; public const string DEFAULT_ZMQ_INFERENCE_HOST = "127.0.0.1";
private const int DEFAULT_ZMQ_INFERENCE_PORT = 5227; private const int DEFAULT_ZMQ_INFERENCE_PORT = 5227;
private static readonly InferenceClientConfig DefaultInferenceClientConfig = new()
{
ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST,
ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT,
ApiUrl = DEFAULT_API_URL
};
private const string DEFAULT_ZMQ_GPS_DENIED_HOST = "127.0.0.1"; private const string DEFAULT_ZMQ_GPS_DENIED_HOST = "127.0.0.1";
private const int DEFAULT_ZMQ_GPS_DENIED_PORT = 5255; private const int DEFAULT_ZMQ_GPS_DENIED_PORT = 5255;
private const int DEFAULT_ZMQ_GPS_DENIED_PUBLISH_PORT = 5256; private const int DEFAULT_ZMQ_GPS_DENIED_PUBLISH_PORT = 5256;
private static readonly GpsDeniedClientConfig DefaultGpsDeniedClientConfig = new()
{
ZeroMqHost = DEFAULT_ZMQ_GPS_DENIED_HOST,
ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT,
ZeroMqReceiverPort = DEFAULT_ZMQ_GPS_DENIED_PUBLISH_PORT
};
#endregion ExternalClientsConfig #endregion ExternalClientsConfig
# region Cache keys
public const string CURRENT_USER_CACHE_KEY = "CurrentUser"; public const string CURRENT_USER_CACHE_KEY = "CurrentUser";
public const string HARDWARE_INFO_KEY = "HardwareInfo";
# endregion
public const string JPG_EXT = ".jpg"; public const string JPG_EXT = ".jpg";
public const string TXT_EXT = ".txt"; public const string TXT_EXT = ".txt";
#region DirectoriesConfig #region DirectoriesConfig
private const string DEFAULT_VIDEO_DIR = "video"; public const string DEFAULT_VIDEO_DIR = "video";
private const string DEFAULT_LABELS_DIR = "labels"; private const string DEFAULT_LABELS_DIR = "labels";
private const string DEFAULT_IMAGES_DIR = "images"; private const string DEFAULT_IMAGES_DIR = "images";
private const string DEFAULT_RESULTS_DIR = "results"; private const string DEFAULT_RESULTS_DIR = "results";
@@ -58,31 +72,35 @@ public static class Constants
#endregion #endregion
#region AnnotatorConfig #region AnnotatorConfig
private static readonly List<DetectionClass> DefaultAnnotationClasses = public static readonly List<DetectionClass> DefaultAnnotationClasses =
[ [
new() { Id = 0, Name = "ArmorVehicle", ShortName = "Броня", Color = "#FF0000".ToColor() }, new() { Id = 0, Name = "ArmorVehicle", ShortName = "Броня", Color = "#FF0000".ToColor(), MaxSizeM = 7 },
new() { Id = 1, Name = "Truck", ShortName = "Вантаж.", Color = "#00FF00".ToColor() }, new() { Id = 1, Name = "Truck", ShortName = "Вантаж.", Color = "#00FF00".ToColor(), MaxSizeM = 8 },
new() { Id = 2, Name = "Vehicle", ShortName = "Машина", Color = "#0000FF".ToColor() }, new() { Id = 2, Name = "Vehicle", ShortName = "Машина", Color = "#0000FF".ToColor(), MaxSizeM = 7 },
new() { Id = 3, Name = "Artillery", ShortName = "Арта", Color = "#FFFF00".ToColor() }, new() { Id = 3, Name = "Artillery", ShortName = "Арта", Color = "#FFFF00".ToColor(), MaxSizeM = 14 },
new() { Id = 4, Name = "Shadow", ShortName = "Тінь", Color = "#FF00FF".ToColor() }, new() { Id = 4, Name = "Shadow", ShortName = "Тінь", Color = "#FF00FF".ToColor(), MaxSizeM = 9 },
new() { Id = 5, Name = "Trenches", ShortName = "Окопи", Color = "#00FFFF".ToColor() }, new() { Id = 5, Name = "Trenches", ShortName = "Окопи", Color = "#00FFFF".ToColor(), MaxSizeM = 10 },
new() { Id = 6, Name = "MilitaryMan", ShortName = "Військов", Color = "#188021".ToColor() }, new() { Id = 6, Name = "MilitaryMan", ShortName = "Військов", Color = "#188021".ToColor(), MaxSizeM = 2 },
new() { Id = 7, Name = "TyreTracks", ShortName = "Накати", Color = "#800000".ToColor() }, new() { Id = 7, Name = "TyreTracks", ShortName = "Накати", Color = "#800000".ToColor(), MaxSizeM = 5 },
new() { Id = 8, Name = "AdditionArmoredTank",ShortName = "Танк.захист", Color = "#008000".ToColor() }, new() { Id = 8, Name = "AdditionArmoredTank",ShortName = "Танк.захист", Color = "#008000".ToColor(), MaxSizeM = 7 },
new() { Id = 9, Name = "Smoke", ShortName = "Дим", Color = "#000080".ToColor() }, new() { Id = 9, Name = "Smoke", ShortName = "Дим", Color = "#000080".ToColor(), MaxSizeM = 8 },
new() { Id = 10, Name = "Plane", ShortName = "Літак", Color = "#000080".ToColor() }, new() { Id = 10, Name = "Plane", ShortName = "Літак", Color = "#000080".ToColor(), MaxSizeM = 12 },
new() { Id = 11, Name = "Moto", ShortName = "Мото", Color = "#808000".ToColor() }, new() { Id = 11, Name = "Moto", ShortName = "Мото", Color = "#808000".ToColor(), MaxSizeM = 3 },
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() }, new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor(), MaxSizeM = 14 },
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() }, new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor(), MaxSizeM = 8 },
new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor() }, new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor(), MaxSizeM = 15 },
new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor() }, new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor(), MaxSizeM = 20 },
new() { Id = 16, Name = "Caponier", ShortName = "Капонір", Color = "#ffb6c1".ToColor() }, new() { Id = 16, Name = "Caponier", ShortName = "Капонір", Color = "#ffb6c1".ToColor(), MaxSizeM = 10 },
new() { Id = 17, Name = "Ammo", ShortName = "БК", Color = "#33658a".ToColor(), MaxSizeM = 2 },
new() { Id = 18, Name = "Protect.Struct", ShortName = "Зуби.драк", Color = "#969647".ToColor(), MaxSizeM = 2 }
]; ];
private static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"]; public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi", "ts"];
private static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"]; public static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"];
private static readonly AnnotationConfig DefaultAnnotationConfig = new() private static readonly AnnotationConfig DefaultAnnotationConfig = new()
{ {
@@ -92,8 +110,25 @@ public static class Constants
AnnotationsDbFile = DEFAULT_ANNOTATIONS_DB_FILE AnnotationsDbFile = DEFAULT_ANNOTATIONS_DB_FILE
}; };
private const int DEFAULT_LEFT_PANEL_WIDTH = 250; #region UIConfig
private const int DEFAULT_RIGHT_PANEL_WIDTH = 250; public const int DEFAULT_LEFT_PANEL_WIDTH = 200;
public const int DEFAULT_RIGHT_PANEL_WIDTH = 200;
#endregion UIConfig
#region CameraConfig
public const int DEFAULT_ALTITUDE = 400;
public const decimal DEFAULT_CAMERA_FOCAL_LENGTH = 24m;
public const decimal DEFAULT_CAMERA_SENSOR_WIDTH = 23.5m;
public static readonly CameraConfig DefaultCameraConfig = new()
{
Altitude = DEFAULT_ALTITUDE,
CameraFocalLength = DEFAULT_CAMERA_FOCAL_LENGTH,
CameraSensorWidth = DEFAULT_CAMERA_SENSOR_WIDTH
};
#endregion CameraConfig
private const string DEFAULT_ANNOTATIONS_DB_FILE = "annotations.db"; private const string DEFAULT_ANNOTATIONS_DB_FILE = "annotations.db";
@@ -154,6 +189,7 @@ public static class Constants
public const string ANNOTATIONS_QUEUE_TABLENAME = "annotations_queue"; public const string ANNOTATIONS_QUEUE_TABLENAME = "annotations_queue";
public const string ADMIN_EMAIL = "admin@azaion.com"; public const string ADMIN_EMAIL = "admin@azaion.com";
public const string DETECTIONS_TABLENAME = "detections"; public const string DETECTIONS_TABLENAME = "detections";
public const string MEDIAFILE_TABLENAME = "mediafiles";
#endregion #endregion
@@ -170,28 +206,14 @@ public static class Constants
private static readonly InitConfig DefaultInitConfig = new() private static readonly InitConfig DefaultInitConfig = new()
{ {
LoaderClientConfig = new LoaderClientConfig LoaderClientConfig = DefaultLoaderClientConfig,
{ InferenceClientConfig = DefaultInferenceClientConfig,
ZeroMqHost = DEFAULT_ZMQ_LOADER_HOST, GpsDeniedClientConfig = DefaultGpsDeniedClientConfig,
ZeroMqPort = DEFAULT_ZMQ_LOADER_PORT,
ApiUrl = DEFAULT_API_URL
},
InferenceClientConfig = new InferenceClientConfig
{
ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST,
ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT,
ApiUrl = DEFAULT_API_URL
},
GpsDeniedClientConfig = new GpsDeniedClientConfig
{
ZeroMqHost = DEFAULT_ZMQ_GPS_DENIED_HOST,
ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT,
ZeroMqReceiverPort = DEFAULT_ZMQ_GPS_DENIED_PUBLISH_PORT
},
DirectoriesConfig = new DirectoriesConfig DirectoriesConfig = new DirectoriesConfig
{ {
ApiResourcesDirectory = "" ApiResourcesDirectory = ""
} },
CameraConfig = DefaultCameraConfig
}; };
public static readonly AppConfig FailsafeAppConfig = new() public static readonly AppConfig FailsafeAppConfig = new()
@@ -220,24 +242,10 @@ public static class Constants
AIRecognitionConfig = DefaultAIRecognitionConfig, AIRecognitionConfig = DefaultAIRecognitionConfig,
GpsDeniedConfig = DefaultGpsDeniedConfig, GpsDeniedConfig = DefaultGpsDeniedConfig,
LoaderClientConfig = new LoaderClientConfig LoaderClientConfig = DefaultLoaderClientConfig,
{ InferenceClientConfig = DefaultInferenceClientConfig,
ZeroMqHost = DEFAULT_ZMQ_LOADER_HOST, GpsDeniedClientConfig = DefaultGpsDeniedClientConfig,
ZeroMqPort = DEFAULT_ZMQ_LOADER_PORT, CameraConfig = DefaultCameraConfig
ApiUrl = DEFAULT_API_URL
},
InferenceClientConfig = new InferenceClientConfig
{
ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST,
ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT,
ApiUrl = DEFAULT_API_URL
},
GpsDeniedClientConfig = new GpsDeniedClientConfig
{
ZeroMqHost = DEFAULT_ZMQ_GPS_DENIED_HOST,
ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT,
ZeroMqReceiverPort = DEFAULT_ZMQ_GPS_DENIED_PUBLISH_PORT
}
}; };
public static InitConfig ReadInitConfig(ILogger logger) public static InitConfig ReadInitConfig(ILogger logger)
@@ -0,0 +1,71 @@
<UserControl x:Class="Azaion.Common.Controls.CameraConfigControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cfg="clr-namespace:Azaion.Common.DTO.Config"
xmlns:controls="clr-namespace:Azaion.Common.Controls"
mc:Ignorable="d"
d:DesignHeight="120" d:DesignWidth="360">
<Grid Margin="4" Background="Black">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="65"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<!-- Altitude -->
<TextBlock Grid.Row="0" Grid.Column="0"
Foreground="LightGray"
VerticalAlignment="Center" Margin="0,0,8,0" Text="Altitude, m:"/>
<Slider x:Name="AltitudeSlider" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
Minimum="0" Maximum="10000" TickFrequency="100"
IsSnapToTickEnabled="False"
Value="{Binding Camera.Altitude, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<controls:NumericUpDown x:Name="AltitudeNud"
Grid.Row="0" Grid.Column="3"
VerticalAlignment="Center"
MinValue="50"
MaxValue="5000"
Value="{Binding Camera.Altitude, RelativeSource={RelativeSource AncestorType=UserControl},
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Step="10">
</controls:NumericUpDown>
<!-- Focal length -->
<TextBlock
Foreground="LightGray"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
VerticalAlignment="Center"
Margin="0,8,8,0" Text="Focal length, mm:"/>
<controls:NumericUpDown x:Name="FocalNud"
Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2"
MinValue="0.1"
MaxValue="100"
Step="0.05"
VerticalAlignment="Center"
Value="{Binding Camera.CameraFocalLength, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</controls:NumericUpDown>
<!-- Sensor width -->
<TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
VerticalAlignment="Center"
Foreground="LightGray"
Margin="0,8,8,0" Text="Sensor width, mm:"/>
<controls:NumericUpDown x:Name="SensorNud"
Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Step="0.05"
VerticalAlignment="Center"
MinValue="0.1"
MaxValue="100"
Value="{Binding Camera.CameraSensorWidth, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</controls:NumericUpDown>
</Grid>
</UserControl>
@@ -0,0 +1,56 @@
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Azaion.Common.DTO.Config;
namespace Azaion.Common.Controls;
public partial class CameraConfigControl
{
public static readonly DependencyProperty CameraProperty = DependencyProperty.Register(
nameof(Camera), typeof(CameraConfig), typeof(CameraConfigControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public CameraConfig Camera
{
get => (CameraConfig)GetValue(CameraProperty) ?? new CameraConfig();
set => SetValue(CameraProperty, value);
}
// Fires whenever any camera parameter value changes in UI
public event EventHandler? CameraChanged;
public CameraConfigControl()
{
InitializeComponent();
DataContext = this;
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Hook up change notifications
if (AltitudeSlider != null)
AltitudeSlider.ValueChanged += (_, __) => CameraChanged?.Invoke(this, EventArgs.Empty);
SubscribeNud(AltitudeNud);
SubscribeNud(FocalNud);
SubscribeNud(SensorNud);
}
private void SubscribeNud(UserControl? nud)
{
if (nud is NumericUpDown num)
{
var dpd = DependencyPropertyDescriptor.FromProperty(NumericUpDown.ValueProperty, typeof(NumericUpDown));
dpd?.AddValueChanged(num, (_, __) => CameraChanged?.Invoke(this, EventArgs.Empty));
}
}
// Initializes the control with the provided CameraConfig instance and wires two-way binding via dependency property
public void Init(CameraConfig cameraConfig)
{
Camera = cameraConfig;
}
}
+47
View File
@@ -0,0 +1,47 @@
<UserControl x:Class="Azaion.Common.Controls.NumericUpDown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Azaion.Common.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="DimGray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="24" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="12" />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<TextBox Name="NudTextBox"
Background="DimGray"
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
TextAlignment="Right"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:NumericUpDown}}}"
TextChanged="NUDTextBox_OnTextChanged"/>
<RepeatButton
Name="NudButtonUp"
Grid.Column="1"
Grid.Row="0"
FontSize="10"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
Click="NudButtonUp_OnClick"
>^</RepeatButton>
<RepeatButton
Name="NudButtonDown"
Grid.Column="1"
Grid.Row="1"
FontSize="10"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
Click="NudButtonDown_OnClick"
>˅</RepeatButton>
</Grid>
</UserControl>
@@ -0,0 +1,101 @@
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
namespace Azaion.Common.Controls;
public partial class NumericUpDown : UserControl
{
public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register(
nameof(MinValue), typeof(decimal), typeof(NumericUpDown), new PropertyMetadata(0m));
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
nameof(MaxValue), typeof(decimal), typeof(NumericUpDown), new PropertyMetadata(100m));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(decimal), typeof(NumericUpDown), new PropertyMetadata(10m, OnValueChanged));
public static readonly DependencyProperty StepProperty = DependencyProperty.Register(
nameof(Step), typeof(decimal), typeof(NumericUpDown), new PropertyMetadata(1m));
public decimal MinValue
{
get => (decimal)GetValue(MinValueProperty);
set => SetValue(MinValueProperty, value);
}
public decimal MaxValue
{
get => (decimal)GetValue(MaxValueProperty);
set => SetValue(MaxValueProperty, value);
}
public decimal Value
{
get => (decimal)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public decimal Step
{
get => (decimal)GetValue(StepProperty);
set => SetValue(StepProperty, value);
}
public NumericUpDown()
{
InitializeComponent();
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is NumericUpDown control)
{
control.NudTextBox.Text = ((decimal)e.NewValue).ToString(CultureInfo.InvariantCulture);
control.NudTextBox.SelectionStart = control.NudTextBox.Text.Length;
}
}
private void NUDTextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
if (string.IsNullOrEmpty(NudTextBox.Text) || !decimal.TryParse(NudTextBox.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
{
NudTextBox.Text = Value.ToString(CultureInfo.InvariantCulture);
return;
}
if (number > MaxValue )
{
Value = MaxValue;
NudTextBox.Text = MaxValue.ToString(CultureInfo.InvariantCulture);
}
else if (number < MinValue)
{
Value = MinValue;
NudTextBox.Text = Value.ToString(CultureInfo.InvariantCulture);
}
else
{
Value = number;
}
NudTextBox.SelectionStart = NudTextBox.Text.Length;
}
private void NudButtonUp_OnClick(object sender, RoutedEventArgs e)
{
var step = Step <= 0 ? 1m : Step;
var newVal = Math.Min(MaxValue, Value + step);
Value = newVal;
NudTextBox.Text = Value.ToString(CultureInfo.InvariantCulture);
NudTextBox.SelectionStart = NudTextBox.Text.Length;
}
private void NudButtonDown_OnClick(object sender, RoutedEventArgs e)
{
var step = Step <= 0 ? 1m : Step;
var newVal = Math.Max(MinValue, Value - step);
Value = newVal;
NudTextBox.Text = Value.ToString(CultureInfo.InvariantCulture);
NudTextBox.SelectionStart = NudTextBox.Text.Length;
}
}
@@ -18,5 +18,8 @@ public class AIRecognitionConfig
[Key("m_bs")] public int ModelBatchSize { get; set; } = 4; [Key("m_bs")] public int ModelBatchSize { get; set; } = 4;
[Key("ov_p")] public double BigImageTileOverlapPercent { get; set; } [Key("ov_p")] public double BigImageTileOverlapPercent { get; set; }
[Key("tile_size")] public int TileSize { get; set; }
[Key("cam_a")] public double Altitude { get; set; }
[Key("cam_fl")] public double CameraFocalLength { get; set; }
[Key("cam_sw")] public double CameraSensorWidth { get; set; }
} }
+4 -1
View File
@@ -28,6 +28,8 @@ public class AppConfig
public MapConfig MapConfig{ get; set; } = null!; public MapConfig MapConfig{ get; set; } = null!;
public GpsDeniedConfig GpsDeniedConfig { get; set; } = null!; public GpsDeniedConfig GpsDeniedConfig { get; set; } = null!;
public CameraConfig CameraConfig { get; set; } = null!;
} }
public interface IConfigUpdater public interface IConfigUpdater
@@ -61,7 +63,8 @@ public class ConfigUpdater : IConfigUpdater
config.InferenceClientConfig, config.InferenceClientConfig,
config.GpsDeniedClientConfig, config.GpsDeniedClientConfig,
config.DirectoriesConfig, config.DirectoriesConfig,
config.UIConfig config.UIConfig,
config.CameraConfig
}; };
await File.WriteAllTextAsync(Constants.CONFIG_PATH, JsonConvert.SerializeObject(publicConfig, Formatting.Indented), Encoding.UTF8); await File.WriteAllTextAsync(Constants.CONFIG_PATH, JsonConvert.SerializeObject(publicConfig, Formatting.Indented), Encoding.UTF8);
+8
View File
@@ -0,0 +1,8 @@
namespace Azaion.Common.DTO.Config;
public class CameraConfig
{
public decimal Altitude { get; set; }
public decimal CameraFocalLength { get; set; }
public decimal CameraSensorWidth { get; set; }
}
+2
View File
@@ -12,6 +12,8 @@ public class DetectionClass : ICloneable
public Color Color { get; set; } public Color Color { get; set; }
public int MaxSizeM { get; set; }
[JsonIgnore] [JsonIgnore]
public string UIName public string UIName
{ {
+4 -1
View File
@@ -1,4 +1,6 @@
namespace Azaion.Common.DTO; using Azaion.Common.DTO.Config;
namespace Azaion.Common.DTO;
public class InitConfig public class InitConfig
{ {
@@ -6,4 +8,5 @@ public class InitConfig
public InferenceClientConfig InferenceClientConfig { get; set; } = null!; public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!; public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
public DirectoriesConfig DirectoriesConfig { get; set; } = null!; public DirectoriesConfig DirectoriesConfig { get; set; } = null!;
public CameraConfig CameraConfig { get; set; } = null!;
} }
-24
View File
@@ -218,27 +218,3 @@ public class YoloLabel : Label
public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.'); public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.');
} }
[MessagePackObject]
public class Detection : YoloLabel
{
[JsonProperty(PropertyName = "an")][Key("an")] public string AnnotationName { get; set; } = null!;
[JsonProperty(PropertyName = "p")][Key("p")] public double Confidence { get; set; }
[JsonProperty(PropertyName = "dn")][Key("dn")] public string Description { get; set; }
[JsonProperty(PropertyName = "af")][Key("af")] public AffiliationEnum Affiliation { get; set; }
//For db & serialization
public Detection(){}
public Detection(string annotationName, YoloLabel label, string description = "", double confidence = 1)
{
AnnotationName = annotationName;
Description = description;
ClassNumber = label.ClassNumber;
CenterX = label.CenterX;
CenterY = label.CenterY;
Height = label.Height;
Width = label.Width;
Confidence = confidence;
}
}
+1 -1
View File
@@ -78,7 +78,7 @@ public class Annotation
.Select(d => (DetectionClassesDict[d.ClassNumber].Color, d.Confidence)) .Select(d => (DetectionClassesDict[d.ClassNumber].Color, d.Confidence))
.ToList(); .ToList();
private string _className; private string? _className;
[IgnoreMember] public string ClassName [IgnoreMember] public string ClassName
{ {
get get
+2
View File
@@ -1,4 +1,5 @@
using Azaion.Common.DTO; using Azaion.Common.DTO;
using CsvHelper;
using LinqToDB; using LinqToDB;
using LinqToDB.Data; using LinqToDB.Data;
@@ -9,4 +10,5 @@ public class AnnotationsDb(DataOptions dataOptions) : DataConnection(dataOptions
public ITable<Annotation> Annotations => this.GetTable<Annotation>(); public ITable<Annotation> Annotations => this.GetTable<Annotation>();
public ITable<AnnotationQueueRecord> AnnotationsQueueRecords => this.GetTable<AnnotationQueueRecord>(); public ITable<AnnotationQueueRecord> AnnotationsQueueRecords => this.GetTable<AnnotationQueueRecord>();
public ITable<Detection> Detections => this.GetTable<Detection>(); public ITable<Detection> Detections => this.GetTable<Detection>();
public ITable<MediaFile> MediaFiles => this.GetTable<MediaFile>();
} }
@@ -0,0 +1,45 @@
using LinqToDB;
using LinqToDB.Mapping;
using Newtonsoft.Json;
namespace Azaion.Common.Database;
public static class AnnotationsDbSchemaHolder
{
public static readonly MappingSchema MappingSchema;
static AnnotationsDbSchemaHolder()
{
MappingSchema = new MappingSchema();
var builder = new FluentMappingBuilder(MappingSchema);
var annotationBuilder = builder.Entity<Annotation>();
annotationBuilder.HasTableName(Constants.ANNOTATIONS_TABLENAME)
.HasPrimaryKey(x => x.Name)
.Association(a => a.Detections, (a, d) => a.Name == d.AnnotationName)
.Property(x => x.Time).HasDataType(DataType.Int64).HasConversion(ts => ts.Ticks, t => new TimeSpan(t));
annotationBuilder
.Ignore(x => x.Milliseconds)
.Ignore(x => x.Classes)
.Ignore(x => x.Classes)
.Ignore(x => x.ImagePath)
.Ignore(x => x.LabelPath)
.Ignore(x => x.ThumbPath);
builder.Entity<Detection>()
.HasTableName(Constants.DETECTIONS_TABLENAME);
builder.Entity<AnnotationQueueRecord>()
.HasTableName(Constants.ANNOTATIONS_QUEUE_TABLENAME)
.HasPrimaryKey(x => x.Id)
.Property(x => x.AnnotationNames)
.HasDataType(DataType.NVarChar)
.HasConversion(list => JsonConvert.SerializeObject(list), str => JsonConvert.DeserializeObject<List<string>>(str) ?? new List<string>());
builder.Entity<MediaFile>()
.HasTableName(Constants.MEDIAFILE_TABLENAME);
builder.Build();
}
}
+6 -40
View File
@@ -6,10 +6,8 @@ using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions; using Azaion.Common.Extensions;
using LinqToDB; using LinqToDB;
using LinqToDB.DataProvider.SQLite; using LinqToDB.DataProvider.SQLite;
using LinqToDB.Mapping;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Serilog; using Serilog;
namespace Azaion.Common.Database; namespace Azaion.Common.Database;
@@ -64,7 +62,12 @@ public class DbFactory : IDbFactory
_fileConnection.Open(); _fileConnection.Open();
using var db = new AnnotationsDb(_fileDataOptions); using var db = new AnnotationsDb(_fileDataOptions);
SchemaMigrator.EnsureSchemaUpdated(db, typeof(Annotation), typeof(Detection)); var entityTypes = typeof(AnnotationsDb)
.GetProperties()
.Where(p => p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(ITable<>))
.Select(p => p.PropertyType.GetGenericArguments()[0])
.ToArray();
SchemaMigrator.EnsureSchemaUpdated(db, entityTypes);
_fileConnection.BackupDatabase(_memoryConnection, "main", "main", -1, null, -1); _fileConnection.BackupDatabase(_memoryConnection, "main", "main", -1, null, -1);
} }
@@ -146,40 +149,3 @@ public class DbFactory : IDbFactory
}); });
} }
} }
public static class AnnotationsDbSchemaHolder
{
public static readonly MappingSchema MappingSchema;
static AnnotationsDbSchemaHolder()
{
MappingSchema = new MappingSchema();
var builder = new FluentMappingBuilder(MappingSchema);
var annotationBuilder = builder.Entity<Annotation>();
annotationBuilder.HasTableName(Constants.ANNOTATIONS_TABLENAME)
.HasPrimaryKey(x => x.Name)
.Association(a => a.Detections, (a, d) => a.Name == d.AnnotationName)
.Property(x => x.Time).HasDataType(DataType.Int64).HasConversion(ts => ts.Ticks, t => new TimeSpan(t));
annotationBuilder
.Ignore(x => x.Milliseconds)
.Ignore(x => x.Classes)
.Ignore(x => x.Classes)
.Ignore(x => x.ImagePath)
.Ignore(x => x.LabelPath)
.Ignore(x => x.ThumbPath);
builder.Entity<Detection>()
.HasTableName(Constants.DETECTIONS_TABLENAME);
builder.Entity<AnnotationQueueRecord>()
.HasTableName(Constants.ANNOTATIONS_QUEUE_TABLENAME)
.HasPrimaryKey(x => x.Id)
.Property(x => x.AnnotationNames)
.HasDataType(DataType.NVarChar)
.HasConversion(list => JsonConvert.SerializeObject(list), str => JsonConvert.DeserializeObject<List<string>>(str) ?? new List<string>());
builder.Build();
}
}
+29
View File
@@ -0,0 +1,29 @@
using Azaion.Common.DTO;
using MessagePack;
using Newtonsoft.Json;
namespace Azaion.Common.Database;
[MessagePackObject]
public class Detection : YoloLabel
{
[JsonProperty(PropertyName = "an")][Key("an")] public string AnnotationName { get; set; } = null!;
[JsonProperty(PropertyName = "p")][Key("p")] public double Confidence { get; set; }
[JsonProperty(PropertyName = "dn")][Key("dn")] public string Description { get; set; }
[JsonProperty(PropertyName = "af")][Key("af")] public AffiliationEnum Affiliation { get; set; }
//For db & serialization
public Detection(){}
public Detection(string annotationName, YoloLabel label, string description = "", double confidence = 1)
{
AnnotationName = annotationName;
Description = description;
ClassNumber = label.ClassNumber;
CenterX = label.CenterX;
CenterY = label.CenterY;
Height = label.Height;
Width = label.Width;
Confidence = confidence;
}
}
+18
View File
@@ -0,0 +1,18 @@
namespace Azaion.Common.Database;
public class MediaFile
{
public string Name { get; set; } = null!;
public string LocalPath { get; set; } = null!;
public DateTime? ProcessedDate { get; set; }
public MediaDetectionStatus MediaDetectionStatus { get; set; } = MediaDetectionStatus.New;
}
public enum MediaDetectionStatus
{
None,
New,
Processing,
Processed,
Error
}
+7 -1
View File
@@ -21,6 +21,12 @@ public static class SchemaMigrator
var entityDescriptor = mappingSchema.GetEntityDescriptor(type); var entityDescriptor = mappingSchema.GetEntityDescriptor(type);
var tableName = entityDescriptor.Name.Name; var tableName = entityDescriptor.Name.Name;
var existingColumns = GetTableColumns(connection, tableName); var existingColumns = GetTableColumns(connection, tableName);
if (existingColumns.Count == 0) // table does not exist
{
var columnDefinitions = entityDescriptor.Columns.Select(GetColumnDefinition);
dbConnection.Execute($"CREATE TABLE {tableName} ({string.Join(", ", columnDefinitions)})");
continue;
}
foreach (var column in entityDescriptor.Columns) foreach (var column in entityDescriptor.Columns)
{ {
@@ -87,7 +93,7 @@ public static class SchemaMigrator
return $"NOT NULL DEFAULT {(Convert.ToBoolean(defaultValue) ? 1 : 0)}"; return $"NOT NULL DEFAULT {(Convert.ToBoolean(defaultValue) ? 1 : 0)}";
if (underlyingType.IsEnum) if (underlyingType.IsEnum)
return $"NOT NULL DEFAULT {(int)defaultValue}"; return $"NOT NULL DEFAULT {(int)(defaultValue ?? 0)}";
if (underlyingType.IsValueType && defaultValue is IFormattable f) if (underlyingType.IsValueType && defaultValue is IFormattable f)
return $"NOT NULL DEFAULT {f.ToString(null, System.Globalization.CultureInfo.InvariantCulture)}"; return $"NOT NULL DEFAULT {f.ToString(null, System.Globalization.CultureInfo.InvariantCulture)}";
@@ -7,7 +7,7 @@ namespace Azaion.Common.Services.Inference;
public interface IInferenceService public interface IInferenceService
{ {
Task RunInference(List<string> mediaPaths, int tileSize, CancellationToken ct = default); Task RunInference(List<string> mediaPaths, CameraConfig cameraConfig, CancellationToken ct = default);
CancellationTokenSource InferenceCancelTokenSource { get; set; } CancellationTokenSource InferenceCancelTokenSource { get; set; }
CancellationTokenSource CheckAIAvailabilityTokenSource { get; set; } CancellationTokenSource CheckAIAvailabilityTokenSource { get; set; }
void StopInference(); void StopInference();
@@ -44,14 +44,16 @@ public class InferenceService : IInferenceService
} }
} }
public async Task RunInference(List<string> mediaPaths, int tileSize, CancellationToken ct = default) public async Task RunInference(List<string> mediaPaths, CameraConfig cameraConfig, CancellationToken ct = default)
{ {
InferenceCancelTokenSource = new CancellationTokenSource(); InferenceCancelTokenSource = new CancellationTokenSource();
_client.Send(RemoteCommand.Create(CommandType.Login, _azaionApi.Credentials)); _client.Send(RemoteCommand.Create(CommandType.Login, _azaionApi.Credentials));
var aiConfig = _aiConfigOptions.Value; var aiConfig = _aiConfigOptions.Value;
aiConfig.Paths = mediaPaths; aiConfig.Paths = mediaPaths;
aiConfig.TileSize = tileSize; aiConfig.Altitude = (double)cameraConfig.Altitude;
aiConfig.CameraFocalLength = (double)cameraConfig.CameraFocalLength;
aiConfig.CameraSensorWidth = (double)cameraConfig.CameraSensorWidth;
_client.Send(RemoteCommand.Create(CommandType.Inference, aiConfig)); _client.Send(RemoteCommand.Create(CommandType.Inference, aiConfig));
using var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct, InferenceCancelTokenSource.Token); using var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct, InferenceCancelTokenSource.Token);
@@ -7,10 +7,7 @@ using Microsoft.Extensions.Logging;
namespace Azaion.Common.Services.Inference; namespace Azaion.Common.Services.Inference;
public class InferenceServiceEventHandler(IInferenceService inferenceService, public class InferenceServiceEventHandler(IInferenceService inferenceService, IAnnotationService annotationService, IMediator mediator) :
IAnnotationService annotationService,
IMediator mediator,
ILogger<InferenceServiceEventHandler> logger) :
INotificationHandler<InferenceDataEvent>, INotificationHandler<InferenceDataEvent>,
INotificationHandler<InferenceStatusEvent>, INotificationHandler<InferenceStatusEvent>,
INotificationHandler<InferenceDoneEvent> INotificationHandler<InferenceDoneEvent>
@@ -13,7 +13,7 @@ public class InferenceDataEvent(AnnotationImage annotationImage) : INotification
public class InferenceStatusEvent : INotification public class InferenceStatusEvent : INotification
{ {
[Key("mn")] [Key("mn")]
public string MediaName { get; set; } public string? MediaName { get; set; }
[Key("dc")] [Key("dc")]
public int DetectionsCount { get; set; } public int DetectionsCount { get; set; }
+4 -1
View File
@@ -9,11 +9,14 @@ cdef class AIRecognitionConfig:
cdef public double tracking_intersection_threshold cdef public double tracking_intersection_threshold
cdef public int big_image_tile_overlap_percent cdef public int big_image_tile_overlap_percent
cdef public int tile_size
cdef public bytes file_data cdef public bytes file_data
cdef public list[str] paths cdef public list[str] paths
cdef public int model_batch_size cdef public int model_batch_size
cdef public double altitude
cdef public double focal_length
cdef public double sensor_width
@staticmethod @staticmethod
cdef from_msgpack(bytes data) cdef from_msgpack(bytes data)
+17 -4
View File
@@ -15,7 +15,10 @@ cdef class AIRecognitionConfig:
model_batch_size, model_batch_size,
big_image_tile_overlap_percent, big_image_tile_overlap_percent,
tile_size
altitude,
focal_length,
sensor_width
): ):
self.frame_period_recognition = frame_period_recognition self.frame_period_recognition = frame_period_recognition
self.frame_recognition_seconds = frame_recognition_seconds self.frame_recognition_seconds = frame_recognition_seconds
@@ -30,7 +33,10 @@ cdef class AIRecognitionConfig:
self.model_batch_size = model_batch_size self.model_batch_size = model_batch_size
self.big_image_tile_overlap_percent = big_image_tile_overlap_percent self.big_image_tile_overlap_percent = big_image_tile_overlap_percent
self.tile_size = tile_size
self.altitude = altitude
self.focal_length = focal_length
self.sensor_width = sensor_width
def __str__(self): def __str__(self):
return (f'frame_seconds : {self.frame_recognition_seconds}, distance_confidence : {self.tracking_distance_confidence}, ' return (f'frame_seconds : {self.frame_recognition_seconds}, distance_confidence : {self.tracking_distance_confidence}, '
@@ -39,7 +45,11 @@ cdef class AIRecognitionConfig:
f'frame_period_recognition : {self.frame_period_recognition}, ' f'frame_period_recognition : {self.frame_period_recognition}, '
f'big_image_tile_overlap_percent: {self.big_image_tile_overlap_percent}, ' f'big_image_tile_overlap_percent: {self.big_image_tile_overlap_percent}, '
f'paths: {self.paths}, ' f'paths: {self.paths}, '
f'model_batch_size: {self.model_batch_size}') f'model_batch_size: {self.model_batch_size}, '
f'altitude: {self.altitude}, '
f'focal_length: {self.focal_length}, '
f'sensor_width: {self.sensor_width}'
)
@staticmethod @staticmethod
cdef from_msgpack(bytes data): cdef from_msgpack(bytes data):
@@ -58,5 +68,8 @@ cdef class AIRecognitionConfig:
unpacked.get("m_bs"), unpacked.get("m_bs"),
unpacked.get("ov_p", 20), unpacked.get("ov_p", 20),
unpacked.get("tile_size", 550),
unpacked.get("cam_a", 400),
unpacked.get("cam_fl", 24),
unpacked.get("cam_sw", 23.5)
) )
+1 -1
View File
@@ -48,7 +48,7 @@ cdef class Annotation:
return f"{self.name}: No detections" return f"{self.name}: No detections"
detections_str = ", ".join( detections_str = ", ".join(
f"class: {d.cls} {d.confidence * 100:.1f}% ({d.x:.2f}, {d.y:.2f})" f"class: {d.cls} {d.confidence * 100:.1f}% ({d.x:.2f}, {d.y:.2f}) ({d.w:.2f}, {d.h:.2f})"
for d in self.detections for d in self.detections
) )
return f"{self.name}: {detections_str}" return f"{self.name}: {detections_str}"
+2 -1
View File
@@ -1,7 +1,7 @@
echo Build Cython app echo Build Cython app
set CURRENT_DIR=%cd% set CURRENT_DIR=%cd%
REM Change to the parent directory of the current location REM Change to the file's directory
cd /d %~dp0 cd /d %~dp0
echo remove dist folder: echo remove dist folder:
@@ -58,5 +58,6 @@ robocopy "dist\azaion-inference\_internal" "..\dist-azaion\_internal" "onnx_engi
robocopy "dist\azaion-inference\_internal" "..\dist-dlls\_internal" /E robocopy "dist\azaion-inference\_internal" "..\dist-dlls\_internal" /E
robocopy "dist\azaion-inference" "..\dist-azaion" "azaion-inference.exe" robocopy "dist\azaion-inference" "..\dist-azaion" "azaion-inference.exe"
robocopy "." "..\dist-azaion" "classes.json"
cd /d %CURRENT_DIR% cd /d %CURRENT_DIR%
+21
View File
@@ -0,0 +1,21 @@
[
{ "Id": 0, "Name": "ArmorVehicle", "ShortName": "Броня", "Color": "#ff0000", "MaxSizeM": 8 },
{ "Id": 1, "Name": "Truck", "ShortName": "Вантаж.", "Color": "#00ff00", "MaxSizeM": 8 },
{ "Id": 2, "Name": "Vehicle", "ShortName": "Машина", "Color": "#0000ff", "MaxSizeM": 7 },
{ "Id": 3, "Name": "Atillery", "ShortName": "Арта", "Color": "#ffff00", "MaxSizeM": 14 },
{ "Id": 4, "Name": "Shadow", "ShortName": "Тінь", "Color": "#ff00ff", "MaxSizeM": 9 },
{ "Id": 5, "Name": "Trenches", "ShortName": "Окопи", "Color": "#00ffff", "MaxSizeM": 10 },
{ "Id": 6, "Name": "MilitaryMan", "ShortName": "Військов", "Color": "#188021", "MaxSizeM": 2 },
{ "Id": 7, "Name": "TyreTracks", "ShortName": "Накати", "Color": "#800000", "MaxSizeM": 5 },
{ "Id": 8, "Name": "AdditArmoredTank", "ShortName": "Танк.захист", "Color": "#008000", "MaxSizeM": 7 },
{ "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#000080", "MaxSizeM": 8 },
{ "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#a52a2a", "MaxSizeM": 12 },
{ "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#808000", "MaxSizeM": 3 },
{ "Id": 12, "Name": "CamouflageNet", "ShortName": "Сітка", "Color": "#87ceeb", "MaxSizeM": 14 },
{ "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#2f4f4f", "MaxSizeM": 8 },
{ "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#1e90ff", "MaxSizeM": 15 },
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1", "MaxSizeM": 20 },
{ "Id": 16, "Name": "Caponier", "ShortName": "Капонір", "Color": "#ffa500", "MaxSizeM": 10 },
{ "Id": 17, "Name": "Ammo", "ShortName": "БК", "Color": "#33658a", "MaxSizeM": 2 },
{ "Id": 18, "Name": "Protect.Struct", "ShortName": "Зуби.драк", "Color": "#969647", "MaxSizeM": 2 }
]
+16 -1
View File
@@ -14,8 +14,23 @@ cdef str MODELS_FOLDER
cdef int SMALL_SIZE_KB cdef int SMALL_SIZE_KB
cdef str SPLIT_SUFFIX cdef str SPLIT_SUFFIX
cdef int TILE_DUPLICATE_CONFIDENCE_THRESHOLD cdef double TILE_DUPLICATE_CONFIDENCE_THRESHOLD
cdef int METERS_IN_TILE
cdef log(str log_message) cdef log(str log_message)
cdef logerror(str error) cdef logerror(str error)
cdef format_time(int ms) cdef format_time(int ms)
cdef dict[int, AnnotationClass] annotations_dict
cdef class AnnotationClass:
cdef public int id
cdef public str name
cdef public str color
cdef public int max_object_size_meters
cdef enum WeatherMode:
Norm = 0
Wint = 20
Night = 40
+32 -1
View File
@@ -1,3 +1,4 @@
import json
import sys import sys
from loguru import logger from loguru import logger
@@ -13,7 +14,37 @@ cdef str MODELS_FOLDER = "models"
cdef int SMALL_SIZE_KB = 3 cdef int SMALL_SIZE_KB = 3
cdef str SPLIT_SUFFIX = "!split!" cdef str SPLIT_SUFFIX = "!split!"
cdef int TILE_DUPLICATE_CONFIDENCE_THRESHOLD = 5 cdef double TILE_DUPLICATE_CONFIDENCE_THRESHOLD = 0.01
cdef int METERS_IN_TILE = 25
cdef class AnnotationClass:
def __init__(self, id, name, color, max_object_size_meters):
self.id = id
self.name = name
self.color = color
self.max_object_size_meters = max_object_size_meters
def __str__(self):
return f'{self.id} {self.name} {self.color} {self.max_object_size_meters}'
cdef int weather_switcher_increase = 20
WEATHER_MODE_NAMES = {
Norm: "Norm",
Wint: "Wint",
Night: "Night"
}
with open('classes.json', 'r', encoding='utf-8') as f:
j = json.loads(f.read())
annotations_dict = {}
for i in range(0, weather_switcher_increase * 3, weather_switcher_increase):
for cl in j:
id = i + cl['Id']
mode_name = WEATHER_MODE_NAMES.get(i, "Unknown")
name = cl['Name'] if i == 0 else f'{cl["Name"]}({mode_name})'
annotations_dict[id] = AnnotationClass(id, name, cl['Color'], cl['MaxSizeM'])
logger.remove() logger.remove()
log_format = "[{time:HH:mm:ss} {level}] {message}" log_format = "[{time:HH:mm:ss} {level}] {message}"
+2 -2
View File
@@ -31,7 +31,7 @@ cdef class Inference:
cdef run_inference(self, RemoteCommand cmd) cdef run_inference(self, RemoteCommand cmd)
cdef _process_video(self, RemoteCommand cmd, AIRecognitionConfig ai_config, str video_name) cdef _process_video(self, RemoteCommand cmd, AIRecognitionConfig ai_config, str video_name)
cdef _process_images(self, RemoteCommand cmd, AIRecognitionConfig ai_config, list[str] image_paths) cdef _process_images(self, RemoteCommand cmd, AIRecognitionConfig ai_config, list[str] image_paths)
cdef _process_images_inner(self, RemoteCommand cmd, AIRecognitionConfig ai_config, list frame_data) cdef _process_images_inner(self, RemoteCommand cmd, AIRecognitionConfig ai_config, list frame_data, double ground_sampling_distance)
cdef on_annotation(self, RemoteCommand cmd, Annotation annotation) cdef on_annotation(self, RemoteCommand cmd, Annotation annotation)
cdef split_to_tiles(self, frame, path, tile_size, overlap_percent) cdef split_to_tiles(self, frame, path, tile_size, overlap_percent)
cdef stop(self) cdef stop(self)
@@ -43,5 +43,5 @@ cdef class Inference:
cdef split_list_extend(self, lst, chunk_size) cdef split_list_extend(self, lst, chunk_size)
cdef bint is_valid_video_annotation(self, Annotation annotation, AIRecognitionConfig ai_config) cdef bint is_valid_video_annotation(self, Annotation annotation, AIRecognitionConfig ai_config)
cdef bint is_valid_image_annotation(self, Annotation annotation) cdef bint is_valid_image_annotation(self, Annotation annotation, double ground_sampling_distance, frame_shape)
cdef remove_tiled_duplicates(self, Annotation annotation) cdef remove_tiled_duplicates(self, Annotation annotation)
+45 -11
View File
@@ -315,18 +315,24 @@ cdef class Inference:
constants_inf.logerror(<str>f'Failed to read image {path}') constants_inf.logerror(<str>f'Failed to read image {path}')
continue continue
original_media_name = Path(<str> path).stem.replace(" ", "") original_media_name = Path(<str> path).stem.replace(" ", "")
ground_sampling_distance = ai_config.sensor_width * ai_config.altitude / (ai_config.focal_length * img_w)
constants_inf.log(<str>f'ground sampling distance: {ground_sampling_distance}')
if img_h <= 1.5 * self.model_height and img_w <= 1.5 * self.model_width: if img_h <= 1.5 * self.model_height and img_w <= 1.5 * self.model_width:
frame_data.append((frame, original_media_name, f'{original_media_name}_000000')) frame_data.append((frame, original_media_name, f'{original_media_name}_000000'))
else: else:
res = self.split_to_tiles(frame, path, ai_config.tile_size, ai_config.big_image_tile_overlap_percent) tile_size = int(constants_inf.METERS_IN_TILE / ground_sampling_distance)
constants_inf.log(<str> f'calc tile size: {tile_size}')
res = self.split_to_tiles(frame, path, tile_size, ai_config.big_image_tile_overlap_percent)
frame_data.extend(res) frame_data.extend(res)
if len(frame_data) > self.engine.get_batch_size(): if len(frame_data) > self.engine.get_batch_size():
for chunk in self.split_list_extend(frame_data, self.engine.get_batch_size()): for chunk in self.split_list_extend(frame_data, self.engine.get_batch_size()):
self._process_images_inner(cmd, ai_config, chunk) self._process_images_inner(cmd, ai_config, chunk, ground_sampling_distance)
self.send_detection_status(cmd.client_id) self.send_detection_status(cmd.client_id)
for chunk in self.split_list_extend(frame_data, self.engine.get_batch_size()): for chunk in self.split_list_extend(frame_data, self.engine.get_batch_size()):
self._process_images_inner(cmd, ai_config, chunk) self._process_images_inner(cmd, ai_config, chunk, ground_sampling_distance)
self.send_detection_status(cmd.client_id) self.send_detection_status(cmd.client_id)
cdef send_detection_status(self, client_id): cdef send_detection_status(self, client_id):
@@ -369,7 +375,7 @@ cdef class Inference:
results.append((tile, original_media_name, name)) results.append((tile, original_media_name, name))
return results return results
cdef _process_images_inner(self, RemoteCommand cmd, AIRecognitionConfig ai_config, list frame_data): cdef _process_images_inner(self, RemoteCommand cmd, AIRecognitionConfig ai_config, list frame_data, double ground_sampling_distance):
cdef list frames, original_media_names, names cdef list frames, original_media_names, names
cdef Annotation annotation cdef Annotation annotation
cdef int i cdef int i
@@ -381,7 +387,7 @@ cdef class Inference:
list_detections = self.postprocess(outputs, ai_config) list_detections = self.postprocess(outputs, ai_config)
for i in range(len(list_detections)): for i in range(len(list_detections)):
annotation = Annotation(names[i], original_media_names[i], 0, list_detections[i]) annotation = Annotation(names[i], original_media_names[i], 0, list_detections[i])
if self.is_valid_image_annotation(annotation): if self.is_valid_image_annotation(annotation, ground_sampling_distance, frames[i].shape):
constants_inf.log(<str> f'Detected {annotation}') constants_inf.log(<str> f'Detected {annotation}')
_, image = cv2.imencode('.jpg', frames[i]) _, image = cv2.imencode('.jpg', frames[i])
annotation.image = image.tobytes() annotation.image = image.tobytes()
@@ -391,6 +397,7 @@ cdef class Inference:
self.stop_signal = True self.stop_signal = True
cdef remove_tiled_duplicates(self, Annotation annotation): cdef remove_tiled_duplicates(self, Annotation annotation):
# Parse tile info from the annotation name
right = annotation.name.rindex('!') right = annotation.name.rindex('!')
left = annotation.name.index(constants_inf.SPLIT_SUFFIX) + len(constants_inf.SPLIT_SUFFIX) left = annotation.name.index(constants_inf.SPLIT_SUFFIX) + len(constants_inf.SPLIT_SUFFIX)
tile_size_str, x_str, y_str = annotation.name[left:right].split('_') tile_size_str, x_str, y_str = annotation.name[left:right].split('_')
@@ -398,19 +405,46 @@ cdef class Inference:
x = int(x_str) x = int(x_str)
y = int(y_str) y = int(y_str)
# This will be our new, filtered list of detections
cdef list[Detection] unique_detections = []
existing_abs_detections = self._tile_detections.setdefault(annotation.original_media_name, [])
for det in annotation.detections: for det in annotation.detections:
# Calculate the absolute position and size of the detection
x1 = det.x * tile_size x1 = det.x * tile_size
y1 = det.y * tile_size y1 = det.y * tile_size
det_abs = Detection(x + x1, y + y1, det.w * tile_size, det.h * tile_size, det.cls, det.confidence) det_abs = Detection(x + x1, y + y1, det.w * tile_size, det.h * tile_size, det.cls, det.confidence)
detections = self._tile_detections.setdefault(annotation.original_media_name, [])
if det_abs in detections:
annotation.detections.remove(det)
else:
detections.append(det_abs)
cdef bint is_valid_image_annotation(self, Annotation annotation): # If it's not a duplicate, keep it and update the cache
if det_abs not in existing_abs_detections:
unique_detections.append(det)
existing_abs_detections.append(det_abs)
annotation.detections = unique_detections
cdef bint is_valid_image_annotation(self, Annotation annotation, double ground_sampling_distance, frame_shape):
if constants_inf.SPLIT_SUFFIX in annotation.name: if constants_inf.SPLIT_SUFFIX in annotation.name:
self.remove_tiled_duplicates(annotation) self.remove_tiled_duplicates(annotation)
img_h, img_w, _ = frame_shape
if annotation.detections:
constants_inf.log(<str> f'Initial ann: {annotation}')
cdef list[Detection] valid_detections = []
for det in annotation.detections:
m_w = det.w * img_w * ground_sampling_distance
m_h = det.h * img_h * ground_sampling_distance
max_size = constants_inf.annotations_dict[det.cls].max_object_size_meters
if m_w <= max_size and m_h <= max_size:
valid_detections.append(det)
constants_inf.log(<str> f'Kept ({m_w} {m_h}) <= {max_size}. class: {constants_inf.annotations_dict[det.cls].name}')
else:
constants_inf.log(<str> f'Removed ({m_w} {m_h}) > {max_size}. class: {constants_inf.annotations_dict[det.cls].name}')
# Replace the old list with the new, filtered one
annotation.detections = valid_detections
if not annotation.detections: if not annotation.detections:
return False return False
return True return True
+3 -3
View File
@@ -1,3 +1,3 @@
call ..\Azaion.Inference\build_inference call ..\Azaion.Inference\build_inference.cmd
call ..\Azaion.Loader\build_loader call ..\Azaion.Loader\build_loader.cmd
call copy_loader_inf call copy_loader_inf.cmd
+5
View File
@@ -30,5 +30,10 @@
"GenerateAnnotatedImage": true, "GenerateAnnotatedImage": true,
"SilentDetection": false, "SilentDetection": false,
"ShowDatasetWithDetectionsOnly": false "ShowDatasetWithDetectionsOnly": false
},
"CameraConfig": {
"Altitude": 400,
"CameraSensorWidth": 23.5,
"CameraFocalLength": 24
} }
} }
+19 -17
View File
@@ -1,23 +1,25 @@
{ {
"AnnotationConfig": { "AnnotationConfig": {
"DetectionClasses": [ "DetectionClasses": [
{ "Id": 0, "Name": "ArmorVehicle", "ShortName": "Броня", "Color": "#ff0000" }, { "Id": 0, "Name": "ArmorVehicle", "ShortName": "Броня", "Color": "#ff0000", "MaxSizeM": 7 },
{ "Id": 1, "Name": "Truck", "ShortName": "Вантаж.", "Color": "#00ff00" }, { "Id": 1, "Name": "Truck", "ShortName": "Вантаж.", "Color": "#00ff00", "MaxSizeM": 8 },
{ "Id": 2, "Name": "Vehicle", "ShortName": "Машина", "Color": "#0000ff" }, { "Id": 2, "Name": "Vehicle", "ShortName": "Машина", "Color": "#0000ff", "MaxSizeM": 7 },
{ "Id": 3, "Name": "Atillery", "ShortName": "Арта", "Color": "#ffff00" }, { "Id": 3, "Name": "Atillery", "ShortName": "Арта", "Color": "#ffff00", "MaxSizeM": 14 },
{ "Id": 4, "Name": "Shadow", "ShortName": "Тінь", "Color": "#ff00ff" }, { "Id": 4, "Name": "Shadow", "ShortName": "Тінь", "Color": "#ff00ff", "MaxSizeM": 9 },
{ "Id": 5, "Name": "Trenches", "ShortName": "Окопи", "Color": "#00ffff" }, { "Id": 5, "Name": "Trenches", "ShortName": "Окопи", "Color": "#00ffff", "MaxSizeM": 10 },
{ "Id": 6, "Name": "MilitaryMan", "ShortName": "Військов", "Color": "#188021" }, { "Id": 6, "Name": "MilitaryMan", "ShortName": "Військов", "Color": "#188021", "MaxSizeM": 2 },
{ "Id": 7, "Name": "TyreTracks", "ShortName": "Накати", "Color": "#800000" }, { "Id": 7, "Name": "TyreTracks", "ShortName": "Накати", "Color": "#800000", "MaxSizeM": 5 },
{ "Id": 8, "Name": "AdditArmoredTank", "ShortName": "Танк.захист", "Color": "#008000" }, { "Id": 8, "Name": "AdditArmoredTank", "ShortName": "Танк.захист", "Color": "#008000", "MaxSizeM": 7 },
{ "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#000080" }, { "Id": 9, "Name": "Smoke", "ShortName": "Дим", "Color": "#000080", "MaxSizeM": 8 },
{ "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#a52a2a" }, { "Id": 10, "Name": "Plane", "ShortName": "Літак", "Color": "#a52a2a", "MaxSizeM": 12 },
{ "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#808000" }, { "Id": 11, "Name": "Moto", "ShortName": "Мото", "Color": "#808000", "MaxSizeM": 3 },
{ "Id": 12, "Name": "CamouflageNet", "ShortName": "Сітка", "Color": "#87ceeb" }, { "Id": 12, "Name": "CamouflageNet", "ShortName": "Сітка", "Color": "#87ceeb", "MaxSizeM": 14 },
{ "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#2f4f4f" }, { "Id": 13, "Name": "CamouflageBranches", "ShortName": "Гілки", "Color": "#2f4f4f", "MaxSizeM": 8 },
{ "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#1e90ff" }, { "Id": 14, "Name": "Roof", "ShortName": "Дах", "Color": "#1e90ff", "MaxSizeM": 15 },
{ "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1" }, { "Id": 15, "Name": "Building", "ShortName": "Будівля", "Color": "#ffb6c1", "MaxSizeM": 20 },
{ "Id": 16, "Name": "Caponier", "ShortName": "Капонір", "Color": "#ffa500" } { "Id": 16, "Name": "Caponier", "ShortName": "Капонір", "Color": "#ffa500", "MaxSizeM": 10 },
{ "Id": 17, "Name": "Ammo", "ShortName": "БК", "Color": "#33658a", "MaxSizeM": 2 },
{ "Id": 18, "Name": "Protect.Struct", "ShortName": "Зуби.драк", "Color": "#969647", "MaxSizeM": 2 }
], ],
"VideoFormats": [ ".mp4", ".mov", ".avi", ".ts", ".mkv" ], "VideoFormats": [ ".mp4", ".mov", ".avi", ".ts", ".mkv" ],
"ImageFormats": [ ".jpg", ".jpeg", ".png", ".bmp" ], "ImageFormats": [ ".jpg", ".jpeg", ".png", ".bmp" ],