mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 10:36:30 +00:00
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:
+74
-66
@@ -22,6 +22,12 @@ public static class Constants
|
||||
|
||||
private const string DEFAULT_ZMQ_LOADER_HOST = "127.0.0.1";
|
||||
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_INFERENCE_PATH = "azaion-inference.exe";
|
||||
@@ -31,24 +37,32 @@ public static class Constants
|
||||
public const string DEFAULT_ZMQ_INFERENCE_HOST = "127.0.0.1";
|
||||
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 int DEFAULT_ZMQ_GPS_DENIED_PORT = 5255;
|
||||
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
|
||||
|
||||
# region Cache keys
|
||||
|
||||
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 TXT_EXT = ".txt";
|
||||
#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_IMAGES_DIR = "images";
|
||||
private const string DEFAULT_RESULTS_DIR = "results";
|
||||
@@ -58,31 +72,35 @@ public static class Constants
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#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 = 1, Name = "Truck", ShortName = "Вантаж.", Color = "#00FF00".ToColor() },
|
||||
new() { Id = 2, Name = "Vehicle", ShortName = "Машина", Color = "#0000FF".ToColor() },
|
||||
new() { Id = 3, Name = "Artillery", ShortName = "Арта", Color = "#FFFF00".ToColor() },
|
||||
new() { Id = 4, Name = "Shadow", ShortName = "Тінь", Color = "#FF00FF".ToColor() },
|
||||
new() { Id = 5, Name = "Trenches", ShortName = "Окопи", Color = "#00FFFF".ToColor() },
|
||||
new() { Id = 6, Name = "MilitaryMan", ShortName = "Військов", Color = "#188021".ToColor() },
|
||||
new() { Id = 7, Name = "TyreTracks", ShortName = "Накати", Color = "#800000".ToColor() },
|
||||
new() { Id = 8, Name = "AdditionArmoredTank",ShortName = "Танк.захист", Color = "#008000".ToColor() },
|
||||
new() { Id = 9, Name = "Smoke", ShortName = "Дим", Color = "#000080".ToColor() },
|
||||
new() { Id = 10, Name = "Plane", ShortName = "Літак", Color = "#000080".ToColor() },
|
||||
new() { Id = 11, Name = "Moto", ShortName = "Мото", Color = "#808000".ToColor() },
|
||||
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() },
|
||||
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() },
|
||||
new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor() },
|
||||
new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor() },
|
||||
new() { Id = 16, Name = "Caponier", ShortName = "Капонір", Color = "#ffb6c1".ToColor() },
|
||||
new() { Id = 0, Name = "ArmorVehicle", ShortName = "Броня", Color = "#FF0000".ToColor(), MaxSizeM = 7 },
|
||||
new() { Id = 1, Name = "Truck", ShortName = "Вантаж.", Color = "#00FF00".ToColor(), MaxSizeM = 8 },
|
||||
new() { Id = 2, Name = "Vehicle", ShortName = "Машина", Color = "#0000FF".ToColor(), MaxSizeM = 7 },
|
||||
new() { Id = 3, Name = "Artillery", ShortName = "Арта", Color = "#FFFF00".ToColor(), MaxSizeM = 14 },
|
||||
new() { Id = 4, Name = "Shadow", ShortName = "Тінь", Color = "#FF00FF".ToColor(), MaxSizeM = 9 },
|
||||
new() { Id = 5, Name = "Trenches", ShortName = "Окопи", Color = "#00FFFF".ToColor(), MaxSizeM = 10 },
|
||||
new() { Id = 6, Name = "MilitaryMan", ShortName = "Військов", Color = "#188021".ToColor(), MaxSizeM = 2 },
|
||||
new() { Id = 7, Name = "TyreTracks", ShortName = "Накати", Color = "#800000".ToColor(), MaxSizeM = 5 },
|
||||
new() { Id = 8, Name = "AdditionArmoredTank",ShortName = "Танк.захист", Color = "#008000".ToColor(), MaxSizeM = 7 },
|
||||
new() { Id = 9, Name = "Smoke", ShortName = "Дим", Color = "#000080".ToColor(), MaxSizeM = 8 },
|
||||
new() { Id = 10, Name = "Plane", ShortName = "Літак", Color = "#000080".ToColor(), MaxSizeM = 12 },
|
||||
new() { Id = 11, Name = "Moto", ShortName = "Мото", Color = "#808000".ToColor(), MaxSizeM = 3 },
|
||||
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor(), MaxSizeM = 14 },
|
||||
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor(), MaxSizeM = 8 },
|
||||
new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor(), MaxSizeM = 15 },
|
||||
new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor(), MaxSizeM = 20 },
|
||||
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"];
|
||||
private static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"];
|
||||
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi", "ts"];
|
||||
public static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"];
|
||||
|
||||
private static readonly AnnotationConfig DefaultAnnotationConfig = new()
|
||||
{
|
||||
@@ -91,9 +109,26 @@ public static class Constants
|
||||
ImageFormats = DefaultImageFormats,
|
||||
AnnotationsDbFile = DEFAULT_ANNOTATIONS_DB_FILE
|
||||
};
|
||||
|
||||
private const int DEFAULT_LEFT_PANEL_WIDTH = 250;
|
||||
private const int DEFAULT_RIGHT_PANEL_WIDTH = 250;
|
||||
|
||||
#region UIConfig
|
||||
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";
|
||||
|
||||
@@ -154,6 +189,7 @@ public static class Constants
|
||||
public const string ANNOTATIONS_QUEUE_TABLENAME = "annotations_queue";
|
||||
public const string ADMIN_EMAIL = "admin@azaion.com";
|
||||
public const string DETECTIONS_TABLENAME = "detections";
|
||||
public const string MEDIAFILE_TABLENAME = "mediafiles";
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -170,28 +206,14 @@ public static class Constants
|
||||
|
||||
private static readonly InitConfig DefaultInitConfig = new()
|
||||
{
|
||||
LoaderClientConfig = new LoaderClientConfig
|
||||
{
|
||||
ZeroMqHost = DEFAULT_ZMQ_LOADER_HOST,
|
||||
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
|
||||
},
|
||||
LoaderClientConfig = DefaultLoaderClientConfig,
|
||||
InferenceClientConfig = DefaultInferenceClientConfig,
|
||||
GpsDeniedClientConfig = DefaultGpsDeniedClientConfig,
|
||||
DirectoriesConfig = new DirectoriesConfig
|
||||
{
|
||||
ApiResourcesDirectory = ""
|
||||
}
|
||||
},
|
||||
CameraConfig = DefaultCameraConfig
|
||||
};
|
||||
|
||||
public static readonly AppConfig FailsafeAppConfig = new()
|
||||
@@ -220,24 +242,10 @@ public static class Constants
|
||||
AIRecognitionConfig = DefaultAIRecognitionConfig,
|
||||
GpsDeniedConfig = DefaultGpsDeniedConfig,
|
||||
|
||||
LoaderClientConfig = new LoaderClientConfig
|
||||
{
|
||||
ZeroMqHost = DEFAULT_ZMQ_LOADER_HOST,
|
||||
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
|
||||
}
|
||||
LoaderClientConfig = DefaultLoaderClientConfig,
|
||||
InferenceClientConfig = DefaultInferenceClientConfig,
|
||||
GpsDeniedClientConfig = DefaultGpsDeniedClientConfig,
|
||||
CameraConfig = DefaultCameraConfig
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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("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; }
|
||||
}
|
||||
@@ -28,6 +28,8 @@ public class AppConfig
|
||||
public MapConfig MapConfig{ get; set; } = null!;
|
||||
|
||||
public GpsDeniedConfig GpsDeniedConfig { get; set; } = null!;
|
||||
|
||||
public CameraConfig CameraConfig { get; set; } = null!;
|
||||
}
|
||||
|
||||
public interface IConfigUpdater
|
||||
@@ -61,7 +63,8 @@ public class ConfigUpdater : IConfigUpdater
|
||||
config.InferenceClientConfig,
|
||||
config.GpsDeniedClientConfig,
|
||||
config.DirectoriesConfig,
|
||||
config.UIConfig
|
||||
config.UIConfig,
|
||||
config.CameraConfig
|
||||
};
|
||||
|
||||
await File.WriteAllTextAsync(Constants.CONFIG_PATH, JsonConvert.SerializeObject(publicConfig, Formatting.Indented), Encoding.UTF8);
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -12,6 +12,8 @@ public class DetectionClass : ICloneable
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
public int MaxSizeM { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string UIName
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public class InitConfig
|
||||
{
|
||||
@@ -6,4 +8,5 @@ public class InitConfig
|
||||
public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
|
||||
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
|
||||
public DirectoriesConfig DirectoriesConfig { get; set; } = null!;
|
||||
public CameraConfig CameraConfig { get; set; } = null!;
|
||||
}
|
||||
@@ -218,27 +218,3 @@ public class YoloLabel : Label
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public class Annotation
|
||||
.Select(d => (DetectionClassesDict[d.ClassNumber].Color, d.Confidence))
|
||||
.ToList();
|
||||
|
||||
private string _className;
|
||||
private string? _className;
|
||||
[IgnoreMember] public string ClassName
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Azaion.Common.DTO;
|
||||
using CsvHelper;
|
||||
using LinqToDB;
|
||||
using LinqToDB.Data;
|
||||
|
||||
@@ -9,4 +10,5 @@ public class AnnotationsDb(DataOptions dataOptions) : DataConnection(dataOptions
|
||||
public ITable<Annotation> Annotations => this.GetTable<Annotation>();
|
||||
public ITable<AnnotationQueueRecord> AnnotationsQueueRecords => this.GetTable<AnnotationQueueRecord>();
|
||||
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,10 +6,8 @@ using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Extensions;
|
||||
using LinqToDB;
|
||||
using LinqToDB.DataProvider.SQLite;
|
||||
using LinqToDB.Mapping;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Azaion.Common.Database;
|
||||
@@ -64,7 +62,12 @@ public class DbFactory : IDbFactory
|
||||
|
||||
_fileConnection.Open();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -145,41 +148,4 @@ public class DbFactory : IDbFactory
|
||||
_logger.LogInformation($"Deleted {detDeleted} detections, {annDeleted} annotations");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -21,12 +21,18 @@ public static class SchemaMigrator
|
||||
var entityDescriptor = mappingSchema.GetEntityDescriptor(type);
|
||||
var tableName = entityDescriptor.Name.Name;
|
||||
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)
|
||||
{
|
||||
if (existingColumns.Contains(column.ColumnName, StringComparer.OrdinalIgnoreCase))
|
||||
if (existingColumns.Contains(column.ColumnName, StringComparer.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
|
||||
var columnDefinition = GetColumnDefinition(column);
|
||||
dbConnection.Execute($"ALTER TABLE {tableName} ADD COLUMN {columnDefinition}");
|
||||
}
|
||||
@@ -87,7 +93,7 @@ public static class SchemaMigrator
|
||||
return $"NOT NULL DEFAULT {(Convert.ToBoolean(defaultValue) ? 1 : 0)}";
|
||||
|
||||
if (underlyingType.IsEnum)
|
||||
return $"NOT NULL DEFAULT {(int)defaultValue}";
|
||||
return $"NOT NULL DEFAULT {(int)(defaultValue ?? 0)}";
|
||||
|
||||
if (underlyingType.IsValueType && defaultValue is IFormattable f)
|
||||
return $"NOT NULL DEFAULT {f.ToString(null, System.Globalization.CultureInfo.InvariantCulture)}";
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Azaion.Common.Services.Inference;
|
||||
|
||||
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 CheckAIAvailabilityTokenSource { get; set; }
|
||||
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();
|
||||
_client.Send(RemoteCommand.Create(CommandType.Login, _azaionApi.Credentials));
|
||||
|
||||
var aiConfig = _aiConfigOptions.Value;
|
||||
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));
|
||||
|
||||
using var combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct, InferenceCancelTokenSource.Token);
|
||||
|
||||
@@ -7,10 +7,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Azaion.Common.Services.Inference;
|
||||
|
||||
public class InferenceServiceEventHandler(IInferenceService inferenceService,
|
||||
IAnnotationService annotationService,
|
||||
IMediator mediator,
|
||||
ILogger<InferenceServiceEventHandler> logger) :
|
||||
public class InferenceServiceEventHandler(IInferenceService inferenceService, IAnnotationService annotationService, IMediator mediator) :
|
||||
INotificationHandler<InferenceDataEvent>,
|
||||
INotificationHandler<InferenceStatusEvent>,
|
||||
INotificationHandler<InferenceDoneEvent>
|
||||
|
||||
@@ -13,7 +13,7 @@ public class InferenceDataEvent(AnnotationImage annotationImage) : INotification
|
||||
public class InferenceStatusEvent : INotification
|
||||
{
|
||||
[Key("mn")]
|
||||
public string MediaName { get; set; }
|
||||
public string? MediaName { get; set; }
|
||||
|
||||
[Key("dc")]
|
||||
public int DetectionsCount { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user