From 3b40bd601e90e730b0aa6dc15870fc477af7c016 Mon Sep 17 00:00:00 2001 From: Alex Bezdieniezhnykh Date: Sat, 23 Nov 2024 08:53:12 +0200 Subject: [PATCH] rework to Azaion.Suite, show tabs with annotator and dataset explorer --- Azaion.Annotator/Annotator.xaml.cs | 6 +- Azaion.Annotator/AnnotatorEventHandler.cs | 1 + Azaion.Annotator/Azaion.Annotator.csproj | 9 +-- Azaion.Annotator/DTO/AnnotationResult.cs | 3 - Azaion.Annotator/HelpWindow.xaml.cs | 14 ++-- Azaion.Annotator/YOLODetector.cs | 29 +++++--- Azaion.Annotator/config.json | 47 ------------- Azaion.Common/Azaion.Common.csproj | 1 + Azaion.Common/Constants.cs | 57 ++++++++++----- Azaion.Common/DTO/AnnotationClass.cs | 4 +- .../DTO/Config/AIRecognitionConfig.cs | 3 +- Azaion.Common/DTO/Config/ApiConfig.cs | 7 +- Azaion.Common/DTO/Config/AppConfig.cs | 16 +++-- Azaion.Common/DTO/Config/DirectoriesConfig.cs | 2 +- .../DTO}/HardwareInfo.cs | 3 +- .../Extensions/ServiceCollectionExtensions.cs | 10 +++ .../Services/AzaionApiClient.cs | 16 +++-- .../Services/HardwareService.cs | 3 +- Azaion.Common/Services/ResourceLoader.cs | 22 ++++++ .../Services/Security.cs | 2 +- Azaion.Dataset/DatasetExplorerEventHandler.cs | 1 + Azaion.Launcher/App.xaml | 8 +++ Azaion.Launcher/App.xaml.cs | 3 + Azaion.Launcher/AssemblyInfo.cs | 10 +++ Azaion.Launcher/Azaion.Launcher.csproj | 19 +++++ {Azaion.Suite => Azaion.Launcher}/Loader.xaml | 2 +- Azaion.Launcher/Loader.xaml.cs | 30 ++++++++ Azaion.Annotator.sln => Azaion.Suite.sln | 6 ++ Azaion.Suite/App.xaml.cs | 69 ++++++++++++++++--- Azaion.Suite/Azaion.Suite.csproj | 11 ++- Azaion.Suite/DynamicAssemblyLoader.cs | 29 -------- Azaion.Suite/Loader.xaml.cs | 58 ---------------- Azaion.Suite/MainSuite.xaml | 7 +- Azaion.Suite/MainSuite.xaml.cs | 21 +++++- Azaion.Suite/ResourceLoader.cs | 38 ---------- Azaion.Suite/Services/DTO/ResourceEnum.cs | 9 --- Azaion.Suite/SuiteCommandLineOptions.cs | 12 ++++ Azaion.Suite/appsettings.json | 10 --- Azaion.Suite/appsettings_manual.json | 57 +++++++++++++++ Azaion.Test/HardwareServiceTest.cs | 3 +- 40 files changed, 374 insertions(+), 284 deletions(-) delete mode 100644 Azaion.Annotator/config.json rename {Azaion.Suite => Azaion.Common/DTO}/HardwareInfo.cs (72%) create mode 100644 Azaion.Common/Extensions/ServiceCollectionExtensions.cs rename {Azaion.Suite => Azaion.Common}/Services/AzaionApiClient.cs (87%) rename {Azaion.Suite => Azaion.Common}/Services/HardwareService.cs (98%) create mode 100644 Azaion.Common/Services/ResourceLoader.cs rename {Azaion.Suite => Azaion.Common}/Services/Security.cs (98%) create mode 100644 Azaion.Launcher/App.xaml create mode 100644 Azaion.Launcher/App.xaml.cs create mode 100644 Azaion.Launcher/AssemblyInfo.cs create mode 100644 Azaion.Launcher/Azaion.Launcher.csproj rename {Azaion.Suite => Azaion.Launcher}/Loader.xaml (99%) create mode 100644 Azaion.Launcher/Loader.xaml.cs rename Azaion.Annotator.sln => Azaion.Suite.sln (84%) delete mode 100644 Azaion.Suite/DynamicAssemblyLoader.cs delete mode 100644 Azaion.Suite/Loader.xaml.cs delete mode 100644 Azaion.Suite/ResourceLoader.cs delete mode 100644 Azaion.Suite/Services/DTO/ResourceEnum.cs create mode 100644 Azaion.Suite/SuiteCommandLineOptions.cs delete mode 100644 Azaion.Suite/appsettings.json create mode 100644 Azaion.Suite/appsettings_manual.json diff --git a/Azaion.Annotator/Annotator.xaml.cs b/Azaion.Annotator/Annotator.xaml.cs index d952501..e570b93 100644 --- a/Azaion.Annotator/Annotator.xaml.cs +++ b/Azaion.Annotator/Annotator.xaml.cs @@ -102,9 +102,6 @@ public partial class Annotator if (LvFiles.Items.IsEmpty) BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.Initial]); - - if (_appConfig.WindowConfig.ShowHelpOnStart) - _helpWindow.Show(); } public void BlinkHelp(string helpText, int times = 2) @@ -541,7 +538,6 @@ public partial class Annotator _ = Task.Run(async () => { - using var detector = new YOLODetector(_appConfig.AIRecognitionConfig); Dispatcher.Invoke(() => _autoDetectDialog.Log("Ініціалізація AI...")); var prevSeekTime = 0.0; @@ -549,7 +545,7 @@ public partial class Annotator { try { - var detections = _aiDetector.Detect(timeframe.Stream); + var detections = await _aiDetector.Detect(timeframe.Stream, token); if (timeframe.Time.TotalSeconds > prevSeekTime + 1) { Dispatcher.Invoke(() => SeekTo(timeframe.Time)); diff --git a/Azaion.Annotator/AnnotatorEventHandler.cs b/Azaion.Annotator/AnnotatorEventHandler.cs index 6a7e0c2..b5e2412 100644 --- a/Azaion.Annotator/AnnotatorEventHandler.cs +++ b/Azaion.Annotator/AnnotatorEventHandler.cs @@ -5,6 +5,7 @@ using System.Windows.Input; using System.Windows.Media; using Azaion.Annotator.DTO; using Azaion.Common.DTO; +using Azaion.Common.DTO.Config; using LibVLCSharp.Shared; using MediatR; using Microsoft.Extensions.Logging; diff --git a/Azaion.Annotator/Azaion.Annotator.csproj b/Azaion.Annotator/Azaion.Annotator.csproj index 28262f2..58e9046 100644 --- a/Azaion.Annotator/Azaion.Annotator.csproj +++ b/Azaion.Annotator/Azaion.Annotator.csproj @@ -10,8 +10,8 @@ - - + + @@ -26,16 +26,13 @@ - + - - Always - diff --git a/Azaion.Annotator/DTO/AnnotationResult.cs b/Azaion.Annotator/DTO/AnnotationResult.cs index 0d808a3..dff2929 100644 --- a/Azaion.Annotator/DTO/AnnotationResult.cs +++ b/Azaion.Annotator/DTO/AnnotationResult.cs @@ -1,8 +1,5 @@ using System.Windows.Media; -using Azaion.Annotator.Extensions; using Azaion.Common.DTO; -using Azaion.Common.DTO.Config; -using Azaion.Common.Extensions; using Newtonsoft.Json; namespace Azaion.Annotator.DTO; diff --git a/Azaion.Annotator/HelpWindow.xaml.cs b/Azaion.Annotator/HelpWindow.xaml.cs index b41b764..b9e3518 100644 --- a/Azaion.Annotator/HelpWindow.xaml.cs +++ b/Azaion.Annotator/HelpWindow.xaml.cs @@ -1,5 +1,6 @@ using System.Windows; using Azaion.Common.DTO.Config; +using Microsoft.Extensions.Options; namespace Azaion.Annotator; @@ -7,14 +8,19 @@ public partial class HelpWindow : Window { private readonly WindowConfig _windowConfig; - public HelpWindow(WindowConfig windowConfig) + public HelpWindow(IOptions windowConfig) { - _windowConfig = windowConfig; - Loaded += (_, _) => CbShowHelp.IsChecked = windowConfig.ShowHelpOnStart; + _windowConfig = windowConfig.Value; + Loaded += (_, _) => CbShowHelp.IsChecked = _windowConfig.ShowHelpOnStart; + Closing += (sender, args) => + { + args.Cancel = true; + Visibility = Visibility.Hidden; + }; InitializeComponent(); } - private void Close(object sender, RoutedEventArgs e) => Close(); private void CbShowHelp_OnChecked(object sender, RoutedEventArgs e) => _windowConfig.ShowHelpOnStart = true; private void CbShowHelp_OnUnchecked(object sender, RoutedEventArgs e) => _windowConfig.ShowHelpOnStart = false; + } \ No newline at end of file diff --git a/Azaion.Annotator/YOLODetector.cs b/Azaion.Annotator/YOLODetector.cs index c304007..4ef77b0 100644 --- a/Azaion.Annotator/YOLODetector.cs +++ b/Azaion.Annotator/YOLODetector.cs @@ -2,6 +2,8 @@ using Azaion.Annotator.DTO; using Azaion.Annotator.Extensions; using Azaion.Common.DTO; +using Azaion.Common.DTO.Config; +using Azaion.Common.Services; using Compunet.YoloV8; using Microsoft.Extensions.Options; using SixLabors.ImageSharp; @@ -12,18 +14,27 @@ namespace Azaion.Annotator; public interface IAIDetector { - List Detect(Stream stream); + Task> Detect(Stream imageStream, CancellationToken cancellationToken = default); } -public class YOLODetector(AIRecognitionConfig recognitionConfig) : IAIDetector, IDisposable +public class YOLODetector(IOptions recognitionConfig, IResourceLoader resourceLoader) : IAIDetector, IDisposable { - private readonly YoloPredictor _predictor = new(recognitionConfig.AIModelPath); + private readonly AIRecognitionConfig _recognitionConfig = recognitionConfig.Value; + private YoloPredictor? _predictor; + private const string YOLO_MODEL = "azaion.onnx"; - public List Detect(Stream stream) + + public async Task> Detect(Stream imageStream, CancellationToken cancellationToken) { - stream.Seek(0, SeekOrigin.Begin); - var image = Image.Load(stream); - var result = _predictor.Detect(image); + if (_predictor == null) + { + await using var stream = await resourceLoader.Load(YOLO_MODEL, cancellationToken); + _predictor = new YoloPredictor(stream.ToArray()); + } + + imageStream.Seek(0, SeekOrigin.Begin); + var image = Image.Load(imageStream); + var result = await _predictor.DetectAsync(image); var imageSize = new System.Windows.Size(image.Width, image.Height); @@ -38,7 +49,7 @@ public class YOLODetector(AIRecognitionConfig recognitionConfig) : IAIDetector, private List FilterOverlapping(List detections) { - var k = recognitionConfig.TrackingIntersectionThreshold; + var k = _recognitionConfig.TrackingIntersectionThreshold; var filteredDetections = new List(); for (var i = 0; i < detections.Count; i++) { @@ -73,5 +84,5 @@ public class YOLODetector(AIRecognitionConfig recognitionConfig) : IAIDetector, return filteredDetections; } - public void Dispose() => _predictor.Dispose(); + public void Dispose() => _predictor?.Dispose(); } diff --git a/Azaion.Annotator/config.json b/Azaion.Annotator/config.json deleted file mode 100644 index 5dd0ea1..0000000 --- a/Azaion.Annotator/config.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "VideosDirectory": "E:\\Azaion1\\Videos", - "LabelsDirectory": "E:\\labels", - "ImagesDirectory": "E:\\images", - "ThumbnailsDirectory": "E:\\thumbnails", - "ResultsDirectory": "E:\\results", - "UnknownImages": "E:\\unknown", - "AnnotationClasses": [ - { "Id": 0, "Name": "Броньована техніка", "ShortName": "Бронь" }, - { "Id": 1, "Name": "Вантажівка", "ShortName": "Вантаж" }, - { "Id": 2, "Name": "Машина легкова", "ShortName": "Машина" }, - { "Id": 3, "Name": "Артилерія", "ShortName": "Арта" }, - { "Id": 4, "Name": "Тінь від техніки", "ShortName": "Тінь" }, - { "Id": 5, "Name": "Окопи", "ShortName": "Окопи" }, - { "Id": 6, "Name": "Військовий", "ShortName": "Військов" }, - { "Id": 7, "Name": "Накати", "ShortName": "Накати" }, - { "Id": 8, "Name": "Танк з захистом", "ShortName": "Танк захист" }, - { "Id": 9, "Name": "Дим", "ShortName": "Дим" }, - { "Id": 10, "Name": "Літак", "ShortName": "Літак" } - ], - "MainWindowConfig": { - "WindowSize": "1920,1080", - "WindowLocation": "50,50", - "FullScreen": true - }, - "DatasetExplorerConfig": { - "WindowSize": "1920,1080", - "WindowLocation": "50,50", - "FullScreen": true - }, - "ThumbnailConfig": { - "Size": "480,270", - "Border": 10 - }, - "LeftPanelWidth": 300, - "RightPanelWidth": 300, - "ShowHelpOnStart": false, - "VideoFormats": ["mov", "mp4"], - "ImageFormats": ["jpg", "jpeg", "png", "bmp", "gif"], - "AIRecognitionConfig": { - "AIModelPath": "azaion.onnx", - "FrameRecognitionSeconds": 2, - "TrackingDistanceConfidence": 0.15, - "TrackingProbabilityIncrease": 15, - "TrackingIntersectionThreshold": 0.8 - } -} \ No newline at end of file diff --git a/Azaion.Common/Azaion.Common.csproj b/Azaion.Common/Azaion.Common.csproj index b1c00d5..66fdfdf 100644 --- a/Azaion.Common/Azaion.Common.csproj +++ b/Azaion.Common/Azaion.Common.csproj @@ -10,6 +10,7 @@ + diff --git a/Azaion.Common/Constants.cs b/Azaion.Common/Constants.cs index 24028c1..f5295ec 100644 --- a/Azaion.Common/Constants.cs +++ b/Azaion.Common/Constants.cs @@ -5,9 +5,17 @@ namespace Azaion.Common; public class Constants { - #region DefaultConfig - public const string CONFIG_PATH = "config.json"; + public const string DEFAULT_DLL_CACHE_DIR = "DllCache"; + + #region ApiConfig + + public const string DEFAULT_API_URL = "https://api.azaion.com/"; + public const int DEFAULT_API_RETRY_COUNT = 3; + public const int DEFAULT_API_TIMEOUT_SECONDS = 40; + #endregion ApiConfig + + #region DirectoriesConfig public const string DEFAULT_VIDEO_DIR = "video"; public const string DEFAULT_LABELS_DIR = "labels"; @@ -15,24 +23,9 @@ public class Constants public const string DEFAULT_RESULTS_DIR = "results"; public const string DEFAULT_THUMBNAILS_DIR = "thumbnails"; - public const int DEFAULT_THUMBNAIL_BORDER = 10; - public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2; - public const double TRACKING_DISTANCE_CONFIDENCE = 0.15; - public const double TRACKING_PROBABILITY_INCREASE = 15; - public const double TRACKING_INTERSECTION_THRESHOLD = 0.8; - - public static readonly Size DefaultWindowSize = new(1280, 720); - public static readonly Point DefaultWindowLocation = new(100, 100); - public static readonly Size DefaultThumbnailSize = new(240, 135); - #endregion - #region Thumbnails - - public const string THUMBNAIL_PREFIX = "_thumb"; - public const string THUMBNAILS_CACHE_FILE = "thumbnails.cache"; - - #endregion + #region AnnotatorConfig public static readonly List DefaultAnnotationClasses = [ @@ -52,6 +45,34 @@ public class Constants public static readonly List DefaultVideoFormats = ["mp4", "mov", "avi"]; public static readonly List DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"]; + # endregion AnnotatorConfig + + # region AIRecognitionConfig + + public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2; + public const double TRACKING_DISTANCE_CONFIDENCE = 0.15; + public const double TRACKING_PROBABILITY_INCREASE = 15; + public const double TRACKING_INTERSECTION_THRESHOLD = 0.8; + + # endregion AIRecognitionConfig + + # region WindowConfig + + public static readonly Size DefaultWindowSize = new(1280, 720); + public static readonly Point DefaultWindowLocation = new(100, 100); + public static readonly Size DefaultThumbnailSize = new(240, 135); + + #endregion + + #region Thumbnails + + public const int DEFAULT_THUMBNAIL_BORDER = 10; + + public const string THUMBNAIL_PREFIX = "_thumb"; + public const string THUMBNAILS_CACHE_FILE = "thumbnails.cache"; + + #endregion + public static TimeSpan? GetTime(string imagePath) { var timeStr = imagePath.Split("_").LastOrDefault(); diff --git a/Azaion.Common/DTO/AnnotationClass.cs b/Azaion.Common/DTO/AnnotationClass.cs index edda2bf..3411eab 100644 --- a/Azaion.Common/DTO/AnnotationClass.cs +++ b/Azaion.Common/DTO/AnnotationClass.cs @@ -1,6 +1,6 @@ -using System.Text.Json.Serialization; -using System.Windows.Media; +using System.Windows.Media; using Azaion.Common.Extensions; +using Newtonsoft.Json; namespace Azaion.Common.DTO; diff --git a/Azaion.Common/DTO/Config/AIRecognitionConfig.cs b/Azaion.Common/DTO/Config/AIRecognitionConfig.cs index 5d7f5d4..fdcfe7d 100644 --- a/Azaion.Common/DTO/Config/AIRecognitionConfig.cs +++ b/Azaion.Common/DTO/Config/AIRecognitionConfig.cs @@ -1,8 +1,7 @@ -namespace Azaion.Annotator.DTO; +namespace Azaion.Common.DTO.Config; public class AIRecognitionConfig { - public string AIModelPath { get; set; } = null!; public double FrameRecognitionSeconds { get; set; } public double TrackingDistanceConfidence { get; set; } public double TrackingProbabilityIncrease { get; set; } diff --git a/Azaion.Common/DTO/Config/ApiConfig.cs b/Azaion.Common/DTO/Config/ApiConfig.cs index 144cb68..607c68d 100644 --- a/Azaion.Common/DTO/Config/ApiConfig.cs +++ b/Azaion.Common/DTO/Config/ApiConfig.cs @@ -1,4 +1,4 @@ -namespace Azaion.Suite.Services.DTO; +namespace Azaion.Common.DTO.Config; public class ApiConfig { @@ -6,8 +6,3 @@ public class ApiConfig public int RetryCount {get;set;} public double TimeoutSeconds { get; set; } } - -public class LocalFilesConfig -{ - public string DllPath { get; set; } = null!; -} \ No newline at end of file diff --git a/Azaion.Common/DTO/Config/AppConfig.cs b/Azaion.Common/DTO/Config/AppConfig.cs index ae8bac6..84528cb 100644 --- a/Azaion.Common/DTO/Config/AppConfig.cs +++ b/Azaion.Common/DTO/Config/AppConfig.cs @@ -1,8 +1,5 @@ using System.IO; using System.Text; -using Azaion.Annotator.DTO; -using Azaion.Suite.Services.DTO; -using Microsoft.Extensions.Configuration; using Newtonsoft.Json; namespace Azaion.Common.DTO.Config; @@ -11,14 +8,14 @@ public class AppConfig { public ApiConfig ApiConfig { get; set; } = null!; + public DirectoriesConfig DirectoriesConfig { get; set; } = null!; + public AnnotationConfig AnnotationConfig { get; set; } = null!; public WindowConfig WindowConfig { get; set; } = null!; public AIRecognitionConfig AIRecognitionConfig { get; set; } = null!; - public DirectoriesConfig DirectoriesConfig { get; set; } = null!; - public ThumbnailConfig ThumbnailConfig { get; set; } = null!; } @@ -40,6 +37,13 @@ public class ConfigUpdater : IConfigUpdater var appConfig = new AppConfig { + ApiConfig = new ApiConfig + { + Url = Constants.DEFAULT_API_URL, + RetryCount = Constants.DEFAULT_API_RETRY_COUNT, + TimeoutSeconds = Constants.DEFAULT_API_TIMEOUT_SECONDS + }, + AnnotationConfig = new AnnotationConfig { AnnotationClasses = Constants.DefaultAnnotationClasses, @@ -51,7 +55,6 @@ public class ConfigUpdater : IConfigUpdater { WindowSize = Constants.DefaultWindowSize, WindowLocation = Constants.DefaultWindowLocation, - ShowHelpOnStart = true, FullScreen = true, LeftPanelWidth = 250, RightPanelWidth = 250, @@ -74,7 +77,6 @@ public class ConfigUpdater : IConfigUpdater AIRecognitionConfig = new AIRecognitionConfig { - AIModelPath = "azaion.onnx", FrameRecognitionSeconds = Constants.DEFAULT_FRAME_RECOGNITION_SECONDS, TrackingDistanceConfidence = Constants.TRACKING_DISTANCE_CONFIDENCE, TrackingProbabilityIncrease = Constants.TRACKING_PROBABILITY_INCREASE, diff --git a/Azaion.Common/DTO/Config/DirectoriesConfig.cs b/Azaion.Common/DTO/Config/DirectoriesConfig.cs index 5bdd95c..7813875 100644 --- a/Azaion.Common/DTO/Config/DirectoriesConfig.cs +++ b/Azaion.Common/DTO/Config/DirectoriesConfig.cs @@ -1,4 +1,4 @@ -namespace Azaion.Common.DTO; +namespace Azaion.Common.DTO.Config; public class DirectoriesConfig { diff --git a/Azaion.Suite/HardwareInfo.cs b/Azaion.Common/DTO/HardwareInfo.cs similarity index 72% rename from Azaion.Suite/HardwareInfo.cs rename to Azaion.Common/DTO/HardwareInfo.cs index 1e7fe91..b219de8 100644 --- a/Azaion.Suite/HardwareInfo.cs +++ b/Azaion.Common/DTO/HardwareInfo.cs @@ -1,9 +1,10 @@ -namespace Azaion.Suite; +namespace Azaion.Common.DTO; public class HardwareInfo { public string CPU { get; set; } = null!; public string GPU { get; set; } = null!; + public string MacAddress { get; set; } = null!; public string Memory { get; set; } = null!; public string Hash { get; set; } = null!; diff --git a/Azaion.Common/Extensions/ServiceCollectionExtensions.cs b/Azaion.Common/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..582c7d5 --- /dev/null +++ b/Azaion.Common/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Azaion.Common.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection ConfigureSection(this IServiceCollection services, IConfiguration config) where T: class => + services.Configure(config.GetSection(typeof(T).Name)); +} \ No newline at end of file diff --git a/Azaion.Suite/Services/AzaionApiClient.cs b/Azaion.Common/Services/AzaionApiClient.cs similarity index 87% rename from Azaion.Suite/Services/AzaionApiClient.cs rename to Azaion.Common/Services/AzaionApiClient.cs index ccc3f6a..3e91363 100644 --- a/Azaion.Suite/Services/AzaionApiClient.cs +++ b/Azaion.Common/Services/AzaionApiClient.cs @@ -4,12 +4,12 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Security; using System.Text; -using Azaion.Suite.Services.DTO; +using Azaion.Common.DTO; using Newtonsoft.Json; -namespace Azaion.Suite.Services; +namespace Azaion.Common.Services; -public class AzaionApiClient(HttpClient httpClient) +public class AzaionApiClient(HttpClient httpClient) : IDisposable { const string JSON_MEDIA = "application/json"; @@ -26,11 +26,11 @@ public class AzaionApiClient(HttpClient httpClient) Password = password.ToSecureString(); } - public async Task GetResource(string password, HardwareInfo hardware, ResourceEnum resourceEnum) + public async Task GetResource(string fileName, string password, HardwareInfo hardware) { var response = await Send(httpClient, new HttpRequestMessage(HttpMethod.Post, "/resources/get") { - Content = new StringContent(JsonConvert.SerializeObject(new { password, hardware, resourceEnum }), Encoding.UTF8, JSON_MEDIA) + Content = new StringContent(JsonConvert.SerializeObject(new { fileName, password, hardware }), Encoding.UTF8, JSON_MEDIA) }); return await response.Content.ReadAsStreamAsync(); } @@ -82,4 +82,10 @@ public class AzaionApiClient(HttpClient httpClient) var result = await response.Content.ReadAsStringAsync(); throw new Exception($"Failed: {response.StatusCode}! Result: {result}"); } + + public void Dispose() + { + httpClient.Dispose(); + Password.Dispose(); + } } diff --git a/Azaion.Suite/Services/HardwareService.cs b/Azaion.Common/Services/HardwareService.cs similarity index 98% rename from Azaion.Suite/Services/HardwareService.cs rename to Azaion.Common/Services/HardwareService.cs index 8adfbb1..2e6de21 100644 --- a/Azaion.Suite/Services/HardwareService.cs +++ b/Azaion.Common/Services/HardwareService.cs @@ -2,8 +2,9 @@ using System.Net.NetworkInformation; using System.Security.Cryptography; using System.Text; +using Azaion.Common.DTO; -namespace Azaion.Suite.Services; +namespace Azaion.Common.Services; public interface IHardwareService { diff --git a/Azaion.Common/Services/ResourceLoader.cs b/Azaion.Common/Services/ResourceLoader.cs new file mode 100644 index 0000000..515eaac --- /dev/null +++ b/Azaion.Common/Services/ResourceLoader.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace Azaion.Common.Services; + +public interface IResourceLoader +{ + Task Load(string fileName, CancellationToken cancellationToken = default); +} + +public class ResourceLoader(string email, string password, AzaionApiClient api, IHardwareService hardwareService) : IResourceLoader +{ + public async Task Load(string fileName, CancellationToken cancellationToken = default) + { + var hardwareInfo = await hardwareService.GetHardware(); + var encryptedStream = await api.GetResource(fileName, password, hardwareInfo); + + var key = Security.MakeEncryptionKey(email, password, hardwareInfo.Hash); + var stream = new MemoryStream(); + await encryptedStream.DecryptTo(stream, key, cancellationToken); + return stream; + } +} \ No newline at end of file diff --git a/Azaion.Suite/Services/Security.cs b/Azaion.Common/Services/Security.cs similarity index 98% rename from Azaion.Suite/Services/Security.cs rename to Azaion.Common/Services/Security.cs index f06e678..cc0ec33 100644 --- a/Azaion.Suite/Services/Security.cs +++ b/Azaion.Common/Services/Security.cs @@ -4,7 +4,7 @@ using System.Security; using System.Security.Cryptography; using System.Text; -namespace Azaion.Suite.Services; +namespace Azaion.Common.Services; public static class Security { diff --git a/Azaion.Dataset/DatasetExplorerEventHandler.cs b/Azaion.Dataset/DatasetExplorerEventHandler.cs index 0b8e39d..72eff92 100644 --- a/Azaion.Dataset/DatasetExplorerEventHandler.cs +++ b/Azaion.Dataset/DatasetExplorerEventHandler.cs @@ -1,6 +1,7 @@ using System.IO; using System.Windows.Input; using Azaion.Common.DTO; +using Azaion.Common.DTO.Config; using MediatR; using Microsoft.Extensions.Options; diff --git a/Azaion.Launcher/App.xaml b/Azaion.Launcher/App.xaml new file mode 100644 index 0000000..1af3995 --- /dev/null +++ b/Azaion.Launcher/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/Azaion.Launcher/App.xaml.cs b/Azaion.Launcher/App.xaml.cs new file mode 100644 index 0000000..d26963a --- /dev/null +++ b/Azaion.Launcher/App.xaml.cs @@ -0,0 +1,3 @@ +namespace Azaion.Launcher; + +public partial class App; \ No newline at end of file diff --git a/Azaion.Launcher/AssemblyInfo.cs b/Azaion.Launcher/AssemblyInfo.cs new file mode 100644 index 0000000..4a05c7d --- /dev/null +++ b/Azaion.Launcher/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] \ No newline at end of file diff --git a/Azaion.Launcher/Azaion.Launcher.csproj b/Azaion.Launcher/Azaion.Launcher.csproj new file mode 100644 index 0000000..b2660a7 --- /dev/null +++ b/Azaion.Launcher/Azaion.Launcher.csproj @@ -0,0 +1,19 @@ + + + + WinExe + net8.0-windows + enable + enable + true + + + + + MSBuild:Compile + Wpf + Designer + + + + diff --git a/Azaion.Suite/Loader.xaml b/Azaion.Launcher/Loader.xaml similarity index 99% rename from Azaion.Suite/Loader.xaml rename to Azaion.Launcher/Loader.xaml index 493f826..18bbea9 100644 --- a/Azaion.Suite/Loader.xaml +++ b/Azaion.Launcher/Loader.xaml @@ -1,4 +1,4 @@ - Close(); + + private void MainMouseMove(object sender, MouseEventArgs e) + { + if (e.OriginalSource is Button || e.OriginalSource is TextBox) + return; + + if (e.LeftButton == MouseButtonState.Pressed) + DragMove(); + } +} diff --git a/Azaion.Annotator.sln b/Azaion.Suite.sln similarity index 84% rename from Azaion.Annotator.sln rename to Azaion.Suite.sln index 92e8a40..a9c8b4f 100644 --- a/Azaion.Annotator.sln +++ b/Azaion.Suite.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Common", "Azaion.Com EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Dataset", "Azaion.Dataset\Azaion.Dataset.csproj", "{01A5CA37-A62E-4EF3-8678-D72CD9525677}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Launcher", "Azaion.Launcher\Azaion.Launcher.csproj", "{00CC9AFE-2952-4943-BCBA-976AE03DE841}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,5 +38,9 @@ Global {01A5CA37-A62E-4EF3-8678-D72CD9525677}.Debug|Any CPU.Build.0 = Debug|Any CPU {01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.ActiveCfg = Release|Any CPU {01A5CA37-A62E-4EF3-8678-D72CD9525677}.Release|Any CPU.Build.0 = Release|Any CPU + {00CC9AFE-2952-4943-BCBA-976AE03DE841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00CC9AFE-2952-4943-BCBA-976AE03DE841}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00CC9AFE-2952-4943-BCBA-976AE03DE841}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00CC9AFE-2952-4943-BCBA-976AE03DE841}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs index 75f6436..3265eb1 100644 --- a/Azaion.Suite/App.xaml.cs +++ b/Azaion.Suite/App.xaml.cs @@ -1,16 +1,21 @@ -using System.Reflection; +using System.IO; +using System.Net.Http; +using System.Reflection; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using Azaion.Annotator; using Azaion.Annotator.DTO; using Azaion.Annotator.Extensions; +using Azaion.Common; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; using Azaion.Common.Extensions; +using Azaion.Common.Services; using Azaion.Suite.Services; -using Azaion.Suite.Services.DTO; using Azaion.Dataset; +using Azaion.Suite.Services.DTO; +using CommandLine; using LibVLCSharp.Shared; using MediatR; using Microsoft.Extensions.Configuration; @@ -18,16 +23,53 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using Serilog; namespace Azaion.Suite; -public partial class App : Application +public partial class App { private readonly IHost _host; private readonly ILogger _logger; private readonly IMediator _mediator; + private static readonly List EncryptedResources = + [ + "Azaion.Annotator.dll", + "Azaion.Dataset.dll" + ]; + + private static readonly IResourceLoader? ResourceLoader; + + static App() + { + var result = Parser.Default.ParseArguments(Environment.GetCommandLineArgs()); + if (result.Errors.Any()) + return; + + var configStr = File.ReadAllText(Constants.CONFIG_PATH); + var apiConfig = JsonConvert.DeserializeObject(configStr)!.ApiConfig; + var api = new AzaionApiClient(new HttpClient + { + BaseAddress = new Uri(apiConfig.Url), + Timeout = TimeSpan.FromSeconds(apiConfig.TimeoutSeconds) + }); + var email = result.Value.Email; + var password = result.Value.Password; + + api.Login(email, password); + + ResourceLoader = new ResourceLoader(email, password, api, new HardwareService()); + foreach (var resource in EncryptedResources) + { + var stream = ResourceLoader.Load(resource).GetAwaiter().GetResult(); + Assembly.Load(stream.ToArray()); + } + + new ConfigUpdater().CheckConfig(); + } + public App() { Log.Logger = new LoggerConfiguration() @@ -42,21 +84,30 @@ public partial class App : Application _host = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((context, config) => config .AddCommandLine(Environment.GetCommandLineArgs()) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)) + .AddJsonFile(Constants.CONFIG_PATH, optional: true, reloadOnChange: true)) .ConfigureServices((context, services) => { - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(ResourceLoader!); + + services.Configure(context.Configuration); + services.ConfigureSection(context.Configuration); + services.ConfigureSection(context.Configuration); + services.ConfigureSection(context.Configuration); + services.ConfigureSection(context.Configuration); + services.ConfigureSection(context.Configuration); + services.ConfigureSection(context.Configuration); - services.Configure(context.Configuration.GetSection(nameof(ApiConfig))); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddMediatR(c => c.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); + services.AddMediatR(c => c.RegisterServicesFromAssemblies( + typeof(Annotator.Annotator).Assembly, + typeof(DatasetExplorer).Assembly)); services.AddSingleton(_ => new LibVLC()); services.AddSingleton(); services.AddSingleton(sp => @@ -93,7 +144,7 @@ public partial class App : Application { EventManager.RegisterClassHandler(typeof(UIElement), UIElement.KeyDownEvent, new RoutedEventHandler(GlobalClick)); await _host.StartAsync(); - _host.Services.GetRequiredService().Show(); + _host.Services.GetRequiredService().Show(); base.OnStartup(e); } diff --git a/Azaion.Suite/Azaion.Suite.csproj b/Azaion.Suite/Azaion.Suite.csproj index 2c21c5a..da1416d 100644 --- a/Azaion.Suite/Azaion.Suite.csproj +++ b/Azaion.Suite/Azaion.Suite.csproj @@ -10,6 +10,9 @@ + + + @@ -19,13 +22,7 @@ - - - - - - PreserveNewest - + diff --git a/Azaion.Suite/DynamicAssemblyLoader.cs b/Azaion.Suite/DynamicAssemblyLoader.cs deleted file mode 100644 index 847849b..0000000 --- a/Azaion.Suite/DynamicAssemblyLoader.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using Azaion.Suite.Services.DTO; -using Microsoft.Extensions.Options; - -namespace Azaion.Suite; - -public class DynamicAssemblyLoader(IOptions localFilesConfig) : AssemblyLoadContext -{ - private static readonly Dictionary LoadedAssemblies = new(); - - static DynamicAssemblyLoader() - { - LoadedAssemblies = Default.Assemblies.ToDictionary(a => a.GetName().Name, a => a); - } - - protected override Assembly Load(AssemblyName assemblyName) - { - var assembly = LoadedAssemblies.GetValueOrDefault(assemblyName.Name); - if (assembly != null) - return assembly; - - var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - - var asm = Assembly.LoadFile(Path.Combine(currentLocation, localFilesConfig.Value.DllPath, $"{assemblyName.Name!}.dll")); - return asm; - } -} \ No newline at end of file diff --git a/Azaion.Suite/Loader.xaml.cs b/Azaion.Suite/Loader.xaml.cs deleted file mode 100644 index ecb2890..0000000 --- a/Azaion.Suite/Loader.xaml.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.IO; -using System.Reflection; -using System.Runtime.Loader; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using Azaion.Suite.Services.DTO; -using Microsoft.Extensions.Options; - -namespace Azaion.Suite; - -public partial class Loader : Window -{ - private readonly IResourceLoader _resourceLoader; - private readonly IOptions _localFilesConfig; - - public Loader(IResourceLoader resourceLoader, IOptions localFilesConfig) - { - _resourceLoader = resourceLoader; - _localFilesConfig = localFilesConfig; - InitializeComponent(); - } - - private async void RunClick(object sender, RoutedEventArgs e) - { - var stream = new MemoryStream(); - await _resourceLoader.LoadAnnotator(TbEmail.Text, TbPassword.Password, stream); - stream.Seek(0, SeekOrigin.Begin); - var loader = new AssemblyLoadContext("DynamicContext", isCollectible: true); - var annotatorAssembly = loader.LoadFromStream(stream); - - var appType = annotatorAssembly.GetType("Azaion.Annotator.App"); - var appInstance = Activator.CreateInstance(appType); - var runMethod = appType.GetMethod("Run", BindingFlags.Public | BindingFlags.Instance); - if (runMethod != null) - { - runMethod.Invoke(appInstance, null); - } - - // var entryPoint = annotatorAssembly.EntryPoint; - // if (entryPoint == null) - // return; - // - // var o = annotatorAssembly.CreateInstance(entryPoint.Name); - // entryPoint.Invoke(o, null); - } - - private void CloseClick(object sender, RoutedEventArgs e) => Close(); - - private void MainMouseMove(object sender, MouseEventArgs e) - { - if (e.OriginalSource is Button || e.OriginalSource is TextBox) - return; - - if (e.LeftButton == MouseButtonState.Pressed) - DragMove(); - } -} diff --git a/Azaion.Suite/MainSuite.xaml b/Azaion.Suite/MainSuite.xaml index 10485a8..4479a5f 100644 --- a/Azaion.Suite/MainSuite.xaml +++ b/Azaion.Suite/MainSuite.xaml @@ -3,10 +3,13 @@ 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.Suite" mc:Ignorable="d" Title="MainSuite" Height="450" Width="800"> - + + diff --git a/Azaion.Suite/MainSuite.xaml.cs b/Azaion.Suite/MainSuite.xaml.cs index 8f6158d..b8c1060 100644 --- a/Azaion.Suite/MainSuite.xaml.cs +++ b/Azaion.Suite/MainSuite.xaml.cs @@ -1,19 +1,25 @@ using System.IO; using System.Windows; +using System.Windows.Controls; using Azaion.Annotator.Extensions; using Azaion.Common.DTO.Config; +using Azaion.Dataset; using Microsoft.Extensions.Options; namespace Azaion.Suite; -public partial class MainSuite : Window +public partial class MainSuite { private readonly AppConfig _appConfig; private readonly IConfigUpdater _configUpdater; + private readonly Annotator.Annotator _annotator; + private readonly DatasetExplorer _datasetExplorer; - public MainSuite(IOptions appConfig, IConfigUpdater configUpdater) + public MainSuite(IOptions appConfig, IConfigUpdater configUpdater, Annotator.Annotator annotator, DatasetExplorer datasetExplorer) { _configUpdater = configUpdater; + _annotator = annotator; + _datasetExplorer = datasetExplorer; _appConfig = appConfig.Value; InitializeComponent(); Loaded += OnLoaded; @@ -41,6 +47,17 @@ public partial class MainSuite : Window if (_appConfig.WindowConfig.FullScreen) WindowState = WindowState.Maximized; + + MainTabControl.Items.Add(new TabItem + { + Header = "Annotator", + Content = _annotator.Content + }); + MainTabControl.Items.Add(new TabItem + { + Header = "Dataset Explorer", + Content = _datasetExplorer.Content + }); } private async Task SaveUserSettings() diff --git a/Azaion.Suite/ResourceLoader.cs b/Azaion.Suite/ResourceLoader.cs deleted file mode 100644 index 44b2337..0000000 --- a/Azaion.Suite/ResourceLoader.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.IO; -using System.Reflection; -using Azaion.Suite.Services; -using Azaion.Suite.Services.DTO; -using Microsoft.Extensions.Options; - -namespace Azaion.Suite; - -public interface IResourceLoader -{ - Task LoadAnnotator(string email, string password, Stream outStream, CancellationToken cancellationToken = default); - Assembly LoadAssembly(string name, CancellationToken cancellationToken = default); -} - -public class ResourceLoader(AzaionApiClient azaionApi, IHardwareService hardwareService, IOptions localFilesConfig) : IResourceLoader -{ - public async Task LoadAnnotator(string email, string password, Stream outStream, CancellationToken cancellationToken = default) - { - var hardwareInfo = await hardwareService.GetHardware(); - azaionApi.Login(email, password); - var key = Security.MakeEncryptionKey(email, password, hardwareInfo.Hash); - - var encryptedStream = await azaionApi.GetResource(password, hardwareInfo, ResourceEnum.AnnotatorDll); - - await encryptedStream.DecryptTo(outStream, key, cancellationToken); - //return Assembly.Load(stream.ToArray()); - } - - public Assembly LoadAssembly(string name, CancellationToken cancellationToken = default) - { - var dllValues = name.Split(","); - var dllName = $"{dllValues[0]}.dll"; - var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - - var asm = Assembly.LoadFile(Path.Combine(currentLocation, localFilesConfig.Value.DllPath, dllName)); - return asm; - } -} \ No newline at end of file diff --git a/Azaion.Suite/Services/DTO/ResourceEnum.cs b/Azaion.Suite/Services/DTO/ResourceEnum.cs deleted file mode 100644 index 3ba3058..0000000 --- a/Azaion.Suite/Services/DTO/ResourceEnum.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Azaion.Suite.Services.DTO; - -public enum ResourceEnum -{ - None = 0, - AnnotatorDll = 10, - AIModelRKNN = 20, - AIModelONNX = 30, -} diff --git a/Azaion.Suite/SuiteCommandLineOptions.cs b/Azaion.Suite/SuiteCommandLineOptions.cs new file mode 100644 index 0000000..b825694 --- /dev/null +++ b/Azaion.Suite/SuiteCommandLineOptions.cs @@ -0,0 +1,12 @@ +using CommandLine; + +namespace Azaion.Suite.Services.DTO; + +public class SuiteCommandLineOptions +{ + [Option('e', "email", Required = true, HelpText = "The email for authorization.")] + public string Email { get; set; } = null!; + + [Option('p', "password", Required = true, HelpText = "The password for authorization.")] + public string Password { get; set; } = null!; +} \ No newline at end of file diff --git a/Azaion.Suite/appsettings.json b/Azaion.Suite/appsettings.json deleted file mode 100644 index d385971..0000000 --- a/Azaion.Suite/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ApiConfig": { - "Url": "https://api.azaion.com", - "TimeoutSeconds": 20, - "RetryCount": 3 - }, - "LocalFilesConfig": { - "DllPath": "AzaionSuite" - } -} \ No newline at end of file diff --git a/Azaion.Suite/appsettings_manual.json b/Azaion.Suite/appsettings_manual.json new file mode 100644 index 0000000..493ee7d --- /dev/null +++ b/Azaion.Suite/appsettings_manual.json @@ -0,0 +1,57 @@ +{ + "ApiConfig": { + "Url": "https://api.azaion.com", + "TimeoutSeconds": 20, + "RetryCount": 3 + }, + + "DirectoriesConfig": { + "VideosDirectory" : "E:\\Azaion1\\Videos", + "LabelsDirectory" : "E:\\labels", + "ImagesDirectory" : "E:\\images", + "ResultsDirectory" : "E:\\results", + "ThumbnailsDirectory" : "E:\\thumbnails", + + "DllCacheDirectory" : "Cache" + }, + + "AnnotationConfig" : { + "AnnotationClasses": [ + { "Id": 0, "Name": "Броньована техніка", "ShortName": "Бронь" }, + { "Id": 1, "Name": "Вантажівка", "ShortName": "Вантаж" }, + { "Id": 2, "Name": "Машина легкова", "ShortName": "Машина" }, + { "Id": 3, "Name": "Артилерія", "ShortName": "Арта" }, + { "Id": 4, "Name": "Тінь від техніки", "ShortName": "Тінь" }, + { "Id": 5, "Name": "Окопи", "ShortName": "Окопи" }, + { "Id": 6, "Name": "Військовий", "ShortName": "Військов" }, + { "Id": 7, "Name": "Накати", "ShortName": "Накати" }, + { "Id": 8, "Name": "Танк з захистом", "ShortName": "Танк захист" }, + { "Id": 9, "Name": "Дим", "ShortName": "Дим" }, + { "Id": 10, "Name": "Літак", "ShortName": "Літак" } + ], + "LastSelectedExplorerClass": 1, + "VideoFormats": ["mov", "mp4"], + "ImageFormats": ["jpg", "jpeg", "png", "bmp", "gif"] + }, + + "WindowConfig": { + "WindowSize": "1920,1080", + "WindowLocation": "50,50", + "FullScreen": true, + "LeftPanelWidth": 220, + "RightPanelWidth": 220, + "ShowHelpOnStart": false + }, + + "AIRecognitionConfig": { + "FrameRecognitionSeconds" : 2, + "TrackingDistanceConfidence" : 0.15, + "TrackingProbabilityIncrease" : 15, + "TrackingIntersectionThreshold" : 0.8 + }, + + "ThumbnailConfig": { + "Size" : "240,135", + "Border" : 10 + } +} \ No newline at end of file diff --git a/Azaion.Test/HardwareServiceTest.cs b/Azaion.Test/HardwareServiceTest.cs index b13497e..b299bb6 100644 --- a/Azaion.Test/HardwareServiceTest.cs +++ b/Azaion.Test/HardwareServiceTest.cs @@ -1,4 +1,5 @@ -using Azaion.Suite; +using Azaion.Common.Services; +using Azaion.Suite; using Azaion.Suite.Services; using Xunit;