mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 10:56:31 +00:00
rework to Azaion.Suite
This commit is contained in:
+19
-1
@@ -2,7 +2,13 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Annotator", "Azaion.Annotator\Azaion.Annotator.csproj", "{8E0809AF-2920-4267-B14D-84BAB334A46F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Annotator", "Azaion.Annotator\Azaion.Annotator.csproj", "{8E0809AF-2920-4267-B14D-84BAB334A46F}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Annotator.Test", "Azaion.Annotator.Test\Azaion.Annotator.Test.csproj", "{85359558-FB59-4542-A597-FD9E1B04C8E7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Test", "Azaion.Test\Azaion.Test.csproj", "{85359558-FB59-4542-A597-FD9E1B04C8E7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Suite", "Azaion.Suite\Azaion.Suite.csproj", "{BA77500E-8B66-4F31-81B0-E831FC12EDFB}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Common", "Azaion.Common\Azaion.Common.csproj", "{1D8E6F44-C64E-4DBE-8665-2101EC5BE36E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azaion.Dataset", "Azaion.Dataset\Azaion.Dataset.csproj", "{01A5CA37-A62E-4EF3-8678-D72CD9525677}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -18,5 +24,17 @@ Global
|
|||||||
{85359558-FB59-4542-A597-FD9E1B04C8E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{85359558-FB59-4542-A597-FD9E1B04C8E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{85359558-FB59-4542-A597-FD9E1B04C8E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{85359558-FB59-4542-A597-FD9E1B04C8E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{85359558-FB59-4542-A597-FD9E1B04C8E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
{85359558-FB59-4542-A597-FD9E1B04C8E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BA77500E-8B66-4F31-81B0-E831FC12EDFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BA77500E-8B66-4F31-81B0-E831FC12EDFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BA77500E-8B66-4F31-81B0-E831FC12EDFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BA77500E-8B66-4F31-81B0-E831FC12EDFB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1D8E6F44-C64E-4DBE-8665-2101EC5BE36E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D8E6F44-C64E-4DBE-8665-2101EC5BE36E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1D8E6F44-C64E-4DBE-8665-2101EC5BE36E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1D8E6F44-C64E-4DBE-8665-2101EC5BE36E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{01A5CA37-A62E-4EF3-8678-D72CD9525677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{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
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<Window x:Class="Azaion.Annotator.MainWindow"
|
<Window x:Class="Azaion.Annotator.Annotator"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:wpf="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF" xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
|
xmlns:wpf="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF" xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
|
||||||
|
xmlns:controls1="clr-namespace:Azaion.Common.Controls;assembly=Azaion.Common"
|
||||||
|
xmlns:controls2="clr-namespace:Azaion.Annotator.Controls;assembly=Azaion.Common"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Azaion Annotator" Height="450" Width="1100"
|
Title="Azaion Annotator" Height="450" Width="1100"
|
||||||
>
|
>
|
||||||
@@ -85,12 +87,6 @@
|
|||||||
<MenuItem x:Name="OpenFolderItem"
|
<MenuItem x:Name="OpenFolderItem"
|
||||||
Foreground="Black"
|
Foreground="Black"
|
||||||
IsEnabled="True" Header="Відкрити папку..." Click="OpenFolderItemClick"/>
|
IsEnabled="True" Header="Відкрити папку..." Click="OpenFolderItemClick"/>
|
||||||
<MenuItem x:Name="OpenDataExplorerItem"
|
|
||||||
Foreground="Black"
|
|
||||||
IsEnabled="True" Header="Відкрити переглядач анотацій..." Click="OpenDataExplorerItemClick"/>
|
|
||||||
<MenuItem x:Name="ReloadThumbnailsItem"
|
|
||||||
Foreground="Black"
|
|
||||||
IsEnabled="True" Header="Оновити базу іконок" Click="ReloadThumbnailsItemClick"/>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Допомога" Foreground="#FFBDBCBC" Margin="0,3,0,0">
|
<MenuItem Header="Допомога" Foreground="#FFBDBCBC" Margin="0,3,0,0">
|
||||||
<MenuItem x:Name="OpenHelpWindow"
|
<MenuItem x:Name="OpenHelpWindow"
|
||||||
@@ -179,11 +175,11 @@
|
|||||||
</GridView>
|
</GridView>
|
||||||
</ListView.View>
|
</ListView.View>
|
||||||
</ListView>
|
</ListView>
|
||||||
<controls:AnnotationClasses
|
<controls1:AnnotationClasses
|
||||||
x:Name="LvClasses"
|
x:Name="LvClasses"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="4">
|
Grid.Row="4">
|
||||||
</controls:AnnotationClasses>
|
</controls1:AnnotationClasses>
|
||||||
|
|
||||||
<GridSplitter
|
<GridSplitter
|
||||||
Background="DarkGray"
|
Background="DarkGray"
|
||||||
@@ -201,7 +197,7 @@
|
|||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.RowSpan="4"
|
Grid.RowSpan="4"
|
||||||
x:Name="VideoView">
|
x:Name="VideoView">
|
||||||
<controls:CanvasEditor x:Name="Editor"
|
<controls1:CanvasEditor x:Name="Editor"
|
||||||
Background="#01000000"
|
Background="#01000000"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
HorizontalAlignment="Stretch" />
|
HorizontalAlignment="Stretch" />
|
||||||
@@ -261,9 +257,9 @@
|
|||||||
<Setter Property="Background">
|
<Setter Property="Background">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<LinearGradientBrush StartPoint="0 0 " EndPoint="1 0">
|
<LinearGradientBrush StartPoint="0 0 " EndPoint="1 0">
|
||||||
<GradientStop Offset="0.3" Color="{Binding Path=ClassColor1}" />
|
<GradientStop Offset="0.3" Color="{Binding Path=ClassColor0}" />
|
||||||
<GradientStop Offset="0.5" Color="{Binding Path=ClassColor2}" />
|
<GradientStop Offset="0.5" Color="{Binding Path=ClassColor1}" />
|
||||||
<GradientStop Offset="0.8" Color="{Binding Path=ClassColor3}" />
|
<GradientStop Offset="0.8" Color="{Binding Path=ClassColor2}" />
|
||||||
<GradientStop Offset="0.99" Color="{Binding Path=ClassColor3}" />
|
<GradientStop Offset="0.99" Color="{Binding Path=ClassColor3}" />
|
||||||
</LinearGradientBrush>
|
</LinearGradientBrush>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
@@ -275,12 +271,12 @@
|
|||||||
</DataGrid>
|
</DataGrid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:UpdatableProgressBar x:Name="VideoSlider"
|
<controls2:UpdatableProgressBar x:Name="VideoSlider"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Background="#252525"
|
Background="#252525"
|
||||||
Foreground="LightBlue">
|
Foreground="LightBlue">
|
||||||
</controls:UpdatableProgressBar>
|
</controls2:UpdatableProgressBar>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<Grid
|
<Grid
|
||||||
@@ -469,14 +465,14 @@
|
|||||||
</Image>
|
</Image>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<controls:UpdatableProgressBar
|
<controls2:UpdatableProgressBar
|
||||||
x:Name="Volume"
|
x:Name="Volume"
|
||||||
Grid.Column="9"
|
Grid.Column="9"
|
||||||
Width="70" Height="15"
|
Width="70" Height="15"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="#252525" BorderBrush="#252525" Foreground="LightBlue"
|
Background="#252525" BorderBrush="#252525" Foreground="LightBlue"
|
||||||
Maximum="100" Minimum="0">
|
Maximum="100" Minimum="0">
|
||||||
</controls:UpdatableProgressBar>
|
</controls2:UpdatableProgressBar>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
x:Name="AIDetectBtn"
|
x:Name="AIDetectBtn"
|
||||||
@@ -7,9 +7,12 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading;
|
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
using Azaion.Common.Extensions;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||||
@@ -17,122 +20,90 @@ using Newtonsoft.Json;
|
|||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
using IntervalTree;
|
using IntervalTree;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using Microsoft.Extensions.Options;
|
||||||
using ScottPlot.TickGenerators.TimeUnits;
|
|
||||||
using Serilog;
|
|
||||||
using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
|
using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
public partial class MainWindow
|
public partial class Annotator
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
private readonly FormState _formState;
|
private readonly FormState _formState;
|
||||||
|
|
||||||
private readonly IConfigRepository _configRepository;
|
private readonly IConfigUpdater _configUpdater;
|
||||||
private readonly HelpWindow _helpWindow;
|
private readonly HelpWindow _helpWindow;
|
||||||
private readonly ILogger<MainWindow> _logger;
|
private readonly ILogger<Annotator> _logger;
|
||||||
private readonly IGalleryManager _galleryManager;
|
|
||||||
private readonly VLCFrameExtractor _vlcFrameExtractor;
|
private readonly VLCFrameExtractor _vlcFrameExtractor;
|
||||||
private readonly IAIDetector _aiDetector;
|
private readonly IAIDetector _aiDetector;
|
||||||
private CancellationTokenSource _cancellationTokenSource = new();
|
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||||
|
|
||||||
private ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
private ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
||||||
private bool _suspendLayout;
|
private bool _suspendLayout;
|
||||||
|
|
||||||
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
|
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
|
||||||
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300);
|
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300);
|
||||||
private readonly Config _config;
|
|
||||||
private readonly DatasetExplorer _datasetExplorer;
|
|
||||||
|
|
||||||
private ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
|
private ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
|
||||||
private ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
|
private ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
|
||||||
|
|
||||||
public IntervalTree<TimeSpan, List<YoloLabel>> Annotations { get; set; } = new();
|
public IntervalTree<TimeSpan, List<YoloLabel>> Annotations { get; set; } = new();
|
||||||
private AutodetectDialog _autoDetectDialog;
|
private AutodetectDialog _autoDetectDialog = new() { Topmost = true };
|
||||||
|
|
||||||
public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer,
|
public Annotator(
|
||||||
|
IConfigUpdater configUpdater,
|
||||||
|
IOptions<AppConfig> appConfig,
|
||||||
|
LibVLC libVLC, MediaPlayer mediaPlayer,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
FormState formState,
|
FormState formState,
|
||||||
IConfigRepository configRepository,
|
|
||||||
HelpWindow helpWindow,
|
HelpWindow helpWindow,
|
||||||
DatasetExplorer datasetExplorer,
|
ILogger<Annotator> logger,
|
||||||
ILogger<MainWindow> logger,
|
|
||||||
IGalleryManager galleryManager,
|
|
||||||
VLCFrameExtractor vlcFrameExtractor,
|
VLCFrameExtractor vlcFrameExtractor,
|
||||||
IAIDetector aiDetector)
|
IAIDetector aiDetector)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_appConfig = appConfig.Value;
|
||||||
|
_configUpdater = configUpdater;
|
||||||
_libVLC = libVLC;
|
_libVLC = libVLC;
|
||||||
_mediaPlayer = mediaPlayer;
|
_mediaPlayer = mediaPlayer;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_formState = formState;
|
_formState = formState;
|
||||||
_configRepository = configRepository;
|
|
||||||
_config = _configRepository.Get();
|
|
||||||
_helpWindow = helpWindow;
|
_helpWindow = helpWindow;
|
||||||
_datasetExplorer = datasetExplorer;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_galleryManager = galleryManager;
|
|
||||||
_vlcFrameExtractor = vlcFrameExtractor;
|
_vlcFrameExtractor = vlcFrameExtractor;
|
||||||
_aiDetector = aiDetector;
|
_aiDetector = aiDetector;
|
||||||
|
|
||||||
VideoView.Loaded += VideoView_Loaded;
|
VideoView.Loaded += VideoView_Loaded;
|
||||||
Closed += OnFormClosed;
|
Closed += OnFormClosed;
|
||||||
|
|
||||||
if (!Directory.Exists(_config.LabelsDirectory))
|
|
||||||
Directory.CreateDirectory(_config.LabelsDirectory);
|
|
||||||
if (!Directory.Exists(_config.ImagesDirectory))
|
|
||||||
Directory.CreateDirectory(_config.ImagesDirectory);
|
|
||||||
if (!Directory.Exists(_config.ResultsDirectory))
|
|
||||||
Directory.CreateDirectory(_config.ResultsDirectory);
|
|
||||||
|
|
||||||
Editor.GetTimeFunc = () => TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
Editor.GetTimeFunc = () => TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
||||||
|
|
||||||
Activated += (_, _) => { _formState.ActiveWindow = WindowsEnum.Main; };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VideoView_Loaded(object sender, RoutedEventArgs e)
|
private void VideoView_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Core.Initialize();
|
Core.Initialize();
|
||||||
InitControls();
|
InitControls();
|
||||||
_ = Task.Run(async () => await _galleryManager.RefreshThumbnails());
|
|
||||||
|
|
||||||
_suspendLayout = true;
|
_suspendLayout = true;
|
||||||
|
|
||||||
Left = _config.MainWindowConfig.WindowLocation.X;
|
MainGrid.ColumnDefinitions.FirstOrDefault()!.Width = new GridLength(_appConfig.WindowConfig.LeftPanelWidth);
|
||||||
Top = _config.MainWindowConfig.WindowLocation.Y;
|
MainGrid.ColumnDefinitions.LastOrDefault()!.Width = new GridLength(_appConfig.WindowConfig.RightPanelWidth);
|
||||||
Width = _config.MainWindowConfig.WindowSize.Width;
|
|
||||||
Height = _config.MainWindowConfig.WindowSize.Height;
|
|
||||||
|
|
||||||
_datasetExplorer.Left = _config.MainWindowConfig.WindowLocation.X;
|
|
||||||
_datasetExplorer.Top = _config.DatasetExplorerConfig.WindowLocation.Y;
|
|
||||||
_datasetExplorer.Width = _config.DatasetExplorerConfig.WindowSize.Width;
|
|
||||||
_datasetExplorer.Height = _config.DatasetExplorerConfig.WindowSize.Height;
|
|
||||||
if (_config.DatasetExplorerConfig.FullScreen)
|
|
||||||
_datasetExplorer.WindowState = WindowState.Maximized;
|
|
||||||
|
|
||||||
MainGrid.ColumnDefinitions.FirstOrDefault()!.Width = new GridLength(_config.LeftPanelWidth);
|
|
||||||
MainGrid.ColumnDefinitions.LastOrDefault()!.Width = new GridLength(_config.RightPanelWidth);
|
|
||||||
|
|
||||||
if (_config.MainWindowConfig.FullScreen)
|
|
||||||
WindowState = WindowState.Maximized;
|
|
||||||
|
|
||||||
_suspendLayout = false;
|
_suspendLayout = false;
|
||||||
|
|
||||||
ReloadFiles();
|
ReloadFiles();
|
||||||
if (_config.AnnotationClasses.Count == 0)
|
|
||||||
_config.AnnotationClasses.Add(new AnnotationClass{Id = 0});
|
|
||||||
|
|
||||||
AnnotationClasses = new ObservableCollection<AnnotationClass>(_config.AnnotationClasses);
|
AnnotationClasses = new ObservableCollection<AnnotationClass>(_appConfig.AnnotationConfig.AnnotationClasses);
|
||||||
LvClasses.ItemsSource = AnnotationClasses;
|
LvClasses.ItemsSource = AnnotationClasses;
|
||||||
LvClasses.SelectedIndex = 0;
|
LvClasses.SelectedIndex = 0;
|
||||||
|
|
||||||
if (LvFiles.Items.IsEmpty)
|
if (LvFiles.Items.IsEmpty)
|
||||||
BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.Initial]);
|
BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.Initial]);
|
||||||
|
|
||||||
if (_config.ShowHelpOnStart)
|
if (_appConfig.WindowConfig.ShowHelpOnStart)
|
||||||
_helpWindow.Show();
|
_helpWindow.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +165,7 @@ public partial class MainWindow
|
|||||||
_mediaPlayer.Position = (float)(newValue / VideoSlider.Maximum);
|
_mediaPlayer.Position = (float)(newValue / VideoSlider.Maximum);
|
||||||
|
|
||||||
VideoSlider.KeyDown += (sender, args) =>
|
VideoSlider.KeyDown += (sender, args) =>
|
||||||
_mediator.Publish(new KeyEvent(sender, args));
|
_mediator.Publish(new KeyEvent(sender, args, WindowEnum.Annotator));
|
||||||
|
|
||||||
Volume.ValueChanged += (_, newValue) =>
|
Volume.ValueChanged += (_, newValue) =>
|
||||||
_mediator.Publish(new VolumeChangedEvent((int)newValue));
|
_mediator.Publish(new VolumeChangedEvent((int)newValue));
|
||||||
@@ -226,9 +197,9 @@ public partial class MainWindow
|
|||||||
foreach (var annotationResult in res)
|
foreach (var annotationResult in res)
|
||||||
{
|
{
|
||||||
var imgName = Path.GetFileNameWithoutExtension(annotationResult.Image);
|
var imgName = Path.GetFileNameWithoutExtension(annotationResult.Image);
|
||||||
var thumbnailPath = Path.Combine(_config.ThumbnailsDirectory, $"{imgName}{Config.THUMBNAIL_PREFIX}.jpg");
|
var thumbnailPath = Path.Combine(_appConfig.DirectoriesConfig.ThumbnailsDirectory, $"{imgName}{Constants.THUMBNAIL_PREFIX}.jpg");
|
||||||
File.Delete(annotationResult.Image);
|
File.Delete(annotationResult.Image);
|
||||||
File.Delete(Path.Combine(_config.LabelsDirectory, $"{imgName}.txt"));
|
File.Delete(Path.Combine(_appConfig.DirectoriesConfig.LabelsDirectory, $"{imgName}.txt"));
|
||||||
File.Delete(thumbnailPath);
|
File.Delete(thumbnailPath);
|
||||||
_formState.AnnotationResults.Remove(annotationResult);
|
_formState.AnnotationResults.Remove(annotationResult);
|
||||||
Annotations.Remove(Annotations.Query(annotationResult.Time));
|
Annotations.Remove(Annotations.Query(annotationResult.Time));
|
||||||
@@ -237,7 +208,6 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Editor.FormState = _formState;
|
|
||||||
Editor.Mediator = _mediator;
|
Editor.Mediator = _mediator;
|
||||||
DgAnnotations.ItemsSource = _formState.AnnotationResults;
|
DgAnnotations.ItemsSource = _formState.AnnotationResults;
|
||||||
}
|
}
|
||||||
@@ -262,13 +232,12 @@ public partial class MainWindow
|
|||||||
if (_suspendLayout)
|
if (_suspendLayout)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_config.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
|
_appConfig.WindowConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
|
||||||
_config.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
_appConfig.WindowConfig.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
||||||
|
|
||||||
_config.MainWindowConfig = this.GetConfig();
|
|
||||||
await ThrottleExt.Throttle(() =>
|
await ThrottleExt.Throttle(() =>
|
||||||
{
|
{
|
||||||
_configRepository.Save(_config);
|
_configUpdater.Save(_appConfig);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}, TimeSpan.FromSeconds(5));
|
}, TimeSpan.FromSeconds(5));
|
||||||
}
|
}
|
||||||
@@ -295,7 +264,7 @@ public partial class MainWindow
|
|||||||
if (showImage)
|
if (showImage)
|
||||||
{
|
{
|
||||||
var fName = _formState.GetTimeName(time);
|
var fName = _formState.GetTimeName(time);
|
||||||
var imgPath = Path.Combine(_config.ImagesDirectory, $"{fName}.jpg");
|
var imgPath = Path.Combine(_appConfig.DirectoriesConfig.ImagesDirectory, $"{fName}.jpg");
|
||||||
if (File.Exists(imgPath))
|
if (File.Exists(imgPath))
|
||||||
{
|
{
|
||||||
Editor.Background = new ImageBrush { ImageSource = await imgPath.OpenImage() };
|
Editor.Background = new ImageBrush { ImageSource = await imgPath.OpenImage() };
|
||||||
@@ -305,7 +274,7 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
foreach (var label in labels)
|
foreach (var label in labels)
|
||||||
{
|
{
|
||||||
var annClass = _config.AnnotationClasses[label.ClassNumber];
|
var annClass = _appConfig.AnnotationConfig.AnnotationClasses[label.ClassNumber];
|
||||||
var canvasLabel = new CanvasLabel(label, canvasSize, videoSize, label.Probability);
|
var canvasLabel = new CanvasLabel(label, canvasSize, videoSize, label.Probability);
|
||||||
Editor.CreateAnnotation(annClass, time, canvasLabel);
|
Editor.CreateAnnotation(annClass, time, canvasLabel);
|
||||||
}
|
}
|
||||||
@@ -319,7 +288,7 @@ public partial class MainWindow
|
|||||||
Annotations.Clear();
|
Annotations.Clear();
|
||||||
Editor.RemoveAllAnns();
|
Editor.RemoveAllAnns();
|
||||||
|
|
||||||
var labelDir = new DirectoryInfo(_config.LabelsDirectory);
|
var labelDir = new DirectoryInfo(_appConfig.DirectoriesConfig.LabelsDirectory);
|
||||||
if (!labelDir.Exists)
|
if (!labelDir.Exists)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -327,7 +296,7 @@ public partial class MainWindow
|
|||||||
foreach (var file in labelFiles)
|
foreach (var file in labelFiles)
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(file.Name);
|
var name = Path.GetFileNameWithoutExtension(file.Name);
|
||||||
var time = _formState.GetTime(name);
|
var time = Constants.GetTime(name);
|
||||||
await AddAnnotations(time, await YoloLabel.ReadFromFile(file.FullName, ct), ct);
|
await AddAnnotations(time, await YoloLabel.ReadFromFile(file.FullName, ct), ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,12 +304,12 @@ public partial class MainWindow
|
|||||||
public async Task AddAnnotations(TimeSpan? time, List<YoloLabel> annotations, CancellationToken ct = default)
|
public async Task AddAnnotations(TimeSpan? time, List<YoloLabel> annotations, CancellationToken ct = default)
|
||||||
=> await AddAnnotations(time, annotations.Select(x => new Detection(x)).ToList(), ct);
|
=> await AddAnnotations(time, annotations.Select(x => new Detection(x)).ToList(), ct);
|
||||||
|
|
||||||
public async Task AddAnnotations(TimeSpan? time, List<Detection> annotations, CancellationToken ct = default)
|
public async Task AddAnnotations(TimeSpan? time, List<Detection> detections, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var timeValue = time ?? TimeSpan.FromMinutes(0);
|
var timeValue = time ?? TimeSpan.FromMinutes(0);
|
||||||
var previousAnnotations = Annotations.Query(timeValue);
|
var previousAnnotations = Annotations.Query(timeValue);
|
||||||
Annotations.Remove(previousAnnotations);
|
Annotations.Remove(previousAnnotations);
|
||||||
Annotations.Add(timeValue.Subtract(_thresholdBefore), timeValue.Add(_thresholdAfter), annotations.Cast<YoloLabel>().ToList());
|
Annotations.Add(timeValue.Subtract(_thresholdBefore), timeValue.Add(_thresholdAfter), detections.Cast<YoloLabel>().ToList());
|
||||||
|
|
||||||
var existingResult = _formState.AnnotationResults.FirstOrDefault(x => x.Time == time);
|
var existingResult = _formState.AnnotationResults.FirstOrDefault(x => x.Time == time);
|
||||||
if (existingResult != null)
|
if (existingResult != null)
|
||||||
@@ -355,17 +324,51 @@ public partial class MainWindow
|
|||||||
.Select(x => x.Value + 1)
|
.Select(x => x.Value + 1)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
_formState.AnnotationResults.Insert(index, new AnnotationResult(timeValue, _formState.GetTimeName(time), annotations, _config));
|
_formState.AnnotationResults.Insert(index, CreateAnnotationReult(timeValue, detections));
|
||||||
await File.WriteAllTextAsync($"{_config.ResultsDirectory}/{_formState.VideoName}.json", JsonConvert.SerializeObject(_formState.AnnotationResults), ct);
|
await File.WriteAllTextAsync($"{_appConfig.DirectoriesConfig.ResultsDirectory}/{_formState.VideoName}.json", JsonConvert.SerializeObject(_formState.AnnotationResults), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnnotationResult CreateAnnotationReult(TimeSpan timeValue, List<Detection> detections)
|
||||||
|
{
|
||||||
|
var annotationResult = new AnnotationResult
|
||||||
|
{
|
||||||
|
Time = timeValue,
|
||||||
|
Image = $"{_formState.GetTimeName(timeValue)}.jpg",
|
||||||
|
Detections = detections,
|
||||||
|
};
|
||||||
|
if (detections.Count <= 0)
|
||||||
|
return annotationResult;
|
||||||
|
|
||||||
|
Color GetAnnotationClass(List<int> detectionClasses, int colorNumber)
|
||||||
|
{
|
||||||
|
if (detections.Count == 0)
|
||||||
|
return (-1).ToColor();
|
||||||
|
|
||||||
|
return colorNumber >= detectionClasses.Count
|
||||||
|
? _appConfig.AnnotationConfig.AnnotationClassesDict[detectionClasses.LastOrDefault()].Color
|
||||||
|
: _appConfig.AnnotationConfig.AnnotationClassesDict[detectionClasses[colorNumber]].Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
var detectionClasses = detections.Select(x => x.ClassNumber).Distinct().ToList();
|
||||||
|
|
||||||
|
annotationResult.ClassName = detectionClasses.Count > 1
|
||||||
|
? string.Join(", ", detectionClasses.Select(x => _appConfig.AnnotationConfig.AnnotationClassesDict[x].ShortName))
|
||||||
|
: _appConfig.AnnotationConfig.AnnotationClassesDict[detectionClasses.FirstOrDefault()].Name;
|
||||||
|
|
||||||
|
annotationResult.ClassColor0 = GetAnnotationClass(detectionClasses, 0);
|
||||||
|
annotationResult.ClassColor1 = GetAnnotationClass(detectionClasses, 1);
|
||||||
|
annotationResult.ClassColor2 = GetAnnotationClass(detectionClasses, 2);
|
||||||
|
annotationResult.ClassColor3 = GetAnnotationClass(detectionClasses, 3);
|
||||||
|
return annotationResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReloadFiles()
|
private void ReloadFiles()
|
||||||
{
|
{
|
||||||
var dir = new DirectoryInfo(_config.VideosDirectory);
|
var dir = new DirectoryInfo(_appConfig.DirectoriesConfig.VideosDirectory);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var labelNames = new DirectoryInfo(_config.LabelsDirectory).GetFiles()
|
var labelNames = new DirectoryInfo(_appConfig.DirectoriesConfig.LabelsDirectory).GetFiles()
|
||||||
.Select(x =>
|
.Select(x =>
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(x.Name);
|
var name = Path.GetFileNameWithoutExtension(x.Name);
|
||||||
@@ -377,7 +380,7 @@ public partial class MainWindow
|
|||||||
.Select(gr => gr.Key)
|
.Select(gr => gr.Key)
|
||||||
.ToDictionary(x => x);
|
.ToDictionary(x => x);
|
||||||
|
|
||||||
var videoFiles = dir.GetFiles(_config.VideoFormats.ToArray()).Select(x =>
|
var videoFiles = dir.GetFiles(_appConfig.AnnotationConfig.VideoFormats.ToArray()).Select(x =>
|
||||||
{
|
{
|
||||||
using var media = new Media(_libVLC, x.FullName);
|
using var media = new Media(_libVLC, x.FullName);
|
||||||
media.Parse();
|
media.Parse();
|
||||||
@@ -392,7 +395,7 @@ public partial class MainWindow
|
|||||||
return fInfo;
|
return fInfo;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
var imageFiles = dir.GetFiles(_config.ImageFormats.ToArray()).Select(x => new MediaFileInfo
|
var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray()).Select(x => new MediaFileInfo
|
||||||
{
|
{
|
||||||
Name = x.Name,
|
Name = x.Name,
|
||||||
Path = x.FullName,
|
Path = x.FullName,
|
||||||
@@ -402,7 +405,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
AllMediaFiles = new ObservableCollection<MediaFileInfo>(videoFiles.Concat(imageFiles).ToList());
|
AllMediaFiles = new ObservableCollection<MediaFileInfo>(videoFiles.Concat(imageFiles).ToList());
|
||||||
LvFiles.ItemsSource = AllMediaFiles;
|
LvFiles.ItemsSource = AllMediaFiles;
|
||||||
TbFolder.Text = _config.VideosDirectory;
|
TbFolder.Text = _appConfig.DirectoriesConfig.VideosDirectory;
|
||||||
|
|
||||||
BlinkHelp(AllMediaFiles.Count == 0
|
BlinkHelp(AllMediaFiles.Count == 0
|
||||||
? HelpTexts.HelpTextsDict[HelpTextEnum.Initial]
|
? HelpTexts.HelpTextsDict[HelpTextEnum.Initial]
|
||||||
@@ -415,7 +418,7 @@ public partial class MainWindow
|
|||||||
_mediaPlayer.Stop();
|
_mediaPlayer.Stop();
|
||||||
_mediaPlayer.Dispose();
|
_mediaPlayer.Dispose();
|
||||||
_libVLC.Dispose();
|
_libVLC.Dispose();
|
||||||
_configRepository.Save(_config);
|
|
||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,7 +463,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(dlg.FileName))
|
if (!string.IsNullOrEmpty(dlg.FileName))
|
||||||
{
|
{
|
||||||
_config.VideosDirectory = dlg.FileName;
|
_appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName;
|
||||||
await SaveUserSettings();
|
await SaveUserSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,13 +476,6 @@ public partial class MainWindow
|
|||||||
LvFiles.ItemsSource = FilteredMediaFiles;
|
LvFiles.ItemsSource = FilteredMediaFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenDataExplorerItemClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
_datasetExplorer.Show();
|
|
||||||
_datasetExplorer.Activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void PlayClick(object sender, RoutedEventArgs e)
|
private void PlayClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_mediator.Publish(new PlaybackControlEvent(_mediaPlayer.CanPause ? PlaybackControlEnum.Pause : PlaybackControlEnum.Play));
|
_mediator.Publish(new PlaybackControlEvent(_mediaPlayer.CanPause ? PlaybackControlEnum.Pause : PlaybackControlEnum.Play));
|
||||||
@@ -506,20 +502,10 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private void Thumb_OnDragCompleted(object sender, DragCompletedEventArgs e) => _ = SaveUserSettings();
|
private void Thumb_OnDragCompleted(object sender, DragCompletedEventArgs e) => _ = SaveUserSettings();
|
||||||
|
|
||||||
private void ReloadThumbnailsItemClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var result = MessageBox.Show($"Видалити всі іконки і згенерувати нову базу іконок в {_config.ThumbnailsDirectory}?",
|
|
||||||
"Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
|
||||||
if (result != MessageBoxResult.Yes)
|
|
||||||
return;
|
|
||||||
_galleryManager.ClearThumbnails();
|
|
||||||
_galleryManager.RefreshThumbnails();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LvFilesContextOpening(object sender, ContextMenuEventArgs e)
|
private void LvFilesContextOpening(object sender, ContextMenuEventArgs e)
|
||||||
{
|
{
|
||||||
var listItem = sender as ListViewItem;
|
var listItem = sender as ListViewItem;
|
||||||
LvFilesContextMenu.DataContext = listItem.DataContext;
|
LvFilesContextMenu.DataContext = listItem!.DataContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (TimeSpan Time, List<Detection> Detections)? _previousDetection;
|
private (TimeSpan Time, List<Detection> Detections)? _previousDetection;
|
||||||
@@ -555,7 +541,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
using var detector = new YOLODetector(_config);
|
using var detector = new YOLODetector(_appConfig.AIRecognitionConfig);
|
||||||
Dispatcher.Invoke(() => _autoDetectDialog.Log("Ініціалізація AI..."));
|
Dispatcher.Invoke(() => _autoDetectDialog.Log("Ініціалізація AI..."));
|
||||||
var prevSeekTime = 0.0;
|
var prevSeekTime = 0.0;
|
||||||
|
|
||||||
@@ -601,7 +587,7 @@ public partial class MainWindow
|
|||||||
var prev = _previousDetection.Value;
|
var prev = _previousDetection.Value;
|
||||||
|
|
||||||
// Time between detections is >= than Frame Recognition Seconds, allow
|
// Time between detections is >= than Frame Recognition Seconds, allow
|
||||||
if (time >= prev.Time.Add(TimeSpan.FromSeconds(_config.AIRecognitionConfig.FrameRecognitionSeconds)))
|
if (time >= prev.Time.Add(TimeSpan.FromSeconds(_appConfig.AIRecognitionConfig.FrameRecognitionSeconds)))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Detection is earlier than previous + FrameRecognitionSeconds.
|
// Detection is earlier than previous + FrameRecognitionSeconds.
|
||||||
@@ -624,11 +610,11 @@ public partial class MainWindow
|
|||||||
.First();
|
.First();
|
||||||
|
|
||||||
// Closest object is farther than Tracking distance confidence, hence it's a different object, allow
|
// Closest object is farther than Tracking distance confidence, hence it's a different object, allow
|
||||||
if (closestObject.Distance > _config.AIRecognitionConfig.TrackingDistanceConfidence)
|
if (closestObject.Distance > _appConfig.AIRecognitionConfig.TrackingDistanceConfidence)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Since closest object within distance confidence, then it is tracking of the same object. Then if recognition probability for the object > increase from previous
|
// Since closest object within distance confidence, then it is tracking of the same object. Then if recognition probability for the object > increase from previous
|
||||||
if (det.Probability >= closestObject.Point.Probability + _config.AIRecognitionConfig.TrackingProbabilityIncrease)
|
if (det.Probability >= closestObject.Point.Probability + _appConfig.AIRecognitionConfig.TrackingProbabilityIncrease)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,10 +631,10 @@ public partial class MainWindow
|
|||||||
var time = timeframe.Time;
|
var time = timeframe.Time;
|
||||||
|
|
||||||
var fName = _formState.GetTimeName(timeframe.Time);
|
var fName = _formState.GetTimeName(timeframe.Time);
|
||||||
var imgPath = Path.Combine(_config.ImagesDirectory, $"{fName}.jpg");
|
var imgPath = Path.Combine(_appConfig.DirectoriesConfig.ImagesDirectory, $"{fName}.jpg");
|
||||||
var img = System.Drawing.Image.FromStream(timeframe.Stream);
|
var img = System.Drawing.Image.FromStream(timeframe.Stream);
|
||||||
img.Save(imgPath, ImageFormat.Jpeg);
|
img.Save(imgPath, ImageFormat.Jpeg);
|
||||||
await YoloLabel.WriteToFile(detections, Path.Combine(_config.LabelsDirectory, $"{fName}.txt"), token);
|
await YoloLabel.WriteToFile(detections, Path.Combine(_appConfig.DirectoriesConfig.LabelsDirectory, $"{fName}.txt"), token);
|
||||||
|
|
||||||
Editor.Background = new ImageBrush { ImageSource = await imgPath.OpenImage() };
|
Editor.Background = new ImageBrush { ImageSource = await imgPath.OpenImage() };
|
||||||
Editor.RemoveAllAnns();
|
Editor.RemoveAllAnns();
|
||||||
@@ -656,16 +642,14 @@ public partial class MainWindow
|
|||||||
await AddAnnotations(timeframe.Time, detections, token);
|
await AddAnnotations(timeframe.Time, detections, token);
|
||||||
|
|
||||||
var log = string.Join(Environment.NewLine, detections.Select(det =>
|
var log = string.Join(Environment.NewLine, detections.Select(det =>
|
||||||
$"{_config.AnnotationClassesDict[det.ClassNumber].Name}: " +
|
$"{_appConfig.AnnotationConfig.AnnotationClassesDict[det.ClassNumber].Name}: " +
|
||||||
$"xy=({det.CenterX:F2},{det.CenterY:F2}), " +
|
$"xy=({det.CenterX:F2},{det.CenterY:F2}), " +
|
||||||
$"size=({det.Width:F2}, {det.Height:F2}), " +
|
$"size=({det.Width:F2}, {det.Height:F2}), " +
|
||||||
$"prob: {det.Probability:F1}%"));
|
$"prob: {det.Probability:F1}%"));
|
||||||
|
|
||||||
Dispatcher.Invoke(() => _autoDetectDialog.Log(log));
|
Dispatcher.Invoke(() => _autoDetectDialog.Log(log));
|
||||||
|
|
||||||
var thumbnailDto = await _galleryManager.CreateThumbnail(imgPath, token);
|
await _mediator.Publish(new ImageCreatedEvent(imgPath), token);
|
||||||
if (thumbnailDto != null)
|
|
||||||
_datasetExplorer.AddThumbnail(thumbnailDto, detections.Select(x => x.ClassNumber));
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
using LibVLCSharp.Shared;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
|
public class AnnotatorEventHandler(
|
||||||
|
LibVLC libVLC,
|
||||||
|
MediaPlayer mediaPlayer,
|
||||||
|
Annotator mainWindow,
|
||||||
|
FormState formState,
|
||||||
|
IOptions<DirectoriesConfig> directoriesConfig,
|
||||||
|
IMediator mediator,
|
||||||
|
ILogger<AnnotatorEventHandler> logger)
|
||||||
|
:
|
||||||
|
INotificationHandler<KeyEvent>,
|
||||||
|
INotificationHandler<AnnClassSelectedEvent>,
|
||||||
|
INotificationHandler<PlaybackControlEvent>,
|
||||||
|
INotificationHandler<VolumeChangedEvent>
|
||||||
|
{
|
||||||
|
private readonly DirectoriesConfig _directoriesConfig = directoriesConfig.Value;
|
||||||
|
private const int STEP = 20;
|
||||||
|
private const int LARGE_STEP = 5000;
|
||||||
|
private const int RESULT_WIDTH = 1280;
|
||||||
|
|
||||||
|
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
|
||||||
|
{
|
||||||
|
{ Key.Space, PlaybackControlEnum.Pause },
|
||||||
|
{ Key.Left, PlaybackControlEnum.PreviousFrame },
|
||||||
|
{ Key.Right, PlaybackControlEnum.NextFrame },
|
||||||
|
{ Key.Enter, PlaybackControlEnum.SaveAnnotations },
|
||||||
|
{ Key.Delete, PlaybackControlEnum.RemoveSelectedAnns },
|
||||||
|
{ Key.X, PlaybackControlEnum.RemoveAllAnns },
|
||||||
|
{ Key.PageUp, PlaybackControlEnum.Previous },
|
||||||
|
{ Key.PageDown, PlaybackControlEnum.Next },
|
||||||
|
};
|
||||||
|
|
||||||
|
public async Task Handle(AnnClassSelectedEvent notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
SelectClass(notification.AnnotationClass);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectClass(AnnotationClass annClass)
|
||||||
|
{
|
||||||
|
mainWindow.Editor.CurrentAnnClass = annClass;
|
||||||
|
foreach (var ann in mainWindow.Editor.CurrentAnns.Where(x => x.IsSelected))
|
||||||
|
ann.AnnotationClass = annClass;
|
||||||
|
mainWindow.LvClasses.SelectedIndex = annClass.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(KeyEvent keyEvent, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (keyEvent.WindowEnum != WindowEnum.Annotator)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var key = keyEvent.Args.Key;
|
||||||
|
var keyNumber = (int?)null;
|
||||||
|
|
||||||
|
if ((int)key >= (int)Key.D1 && (int)key <= (int)Key.D9)
|
||||||
|
keyNumber = key - Key.D1;
|
||||||
|
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
|
||||||
|
keyNumber = key - Key.NumPad1;
|
||||||
|
if (keyNumber.HasValue)
|
||||||
|
SelectClass((AnnotationClass)mainWindow.LvClasses.Items[keyNumber.Value]!);
|
||||||
|
|
||||||
|
if (_keysControlEnumDict.TryGetValue(key, out var value))
|
||||||
|
await ControlPlayback(value);
|
||||||
|
|
||||||
|
if (key == Key.A)
|
||||||
|
mainWindow.AutoDetect(null!, null!);
|
||||||
|
|
||||||
|
await VolumeControl(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task VolumeControl(Key key)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case Key.VolumeMute when mediaPlayer.Volume == 0:
|
||||||
|
await ControlPlayback(PlaybackControlEnum.TurnOnVolume);
|
||||||
|
break;
|
||||||
|
case Key.VolumeMute:
|
||||||
|
await ControlPlayback(PlaybackControlEnum.TurnOffVolume);
|
||||||
|
break;
|
||||||
|
case Key.Up:
|
||||||
|
case Key.VolumeUp:
|
||||||
|
var vUp = Math.Min(100, mediaPlayer.Volume + 5);
|
||||||
|
ChangeVolume(vUp);
|
||||||
|
mainWindow.Volume.Value = vUp;
|
||||||
|
break;
|
||||||
|
case Key.Down:
|
||||||
|
case Key.VolumeDown:
|
||||||
|
var vDown = Math.Max(0, mediaPlayer.Volume - 5);
|
||||||
|
ChangeVolume(vDown);
|
||||||
|
mainWindow.Volume.Value = vDown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(PlaybackControlEvent notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await ControlPlayback(notification.PlaybackControl);
|
||||||
|
mainWindow.VideoView.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ControlPlayback(PlaybackControlEnum controlEnum)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||||
|
var step = isCtrlPressed ? LARGE_STEP : STEP;
|
||||||
|
|
||||||
|
switch (controlEnum)
|
||||||
|
{
|
||||||
|
case PlaybackControlEnum.Play:
|
||||||
|
Play();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.Pause:
|
||||||
|
mediaPlayer.Pause();
|
||||||
|
if (!mediaPlayer.IsPlaying)
|
||||||
|
mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.AnnotationHelp]);
|
||||||
|
if (formState.BackgroundTime.HasValue)
|
||||||
|
{
|
||||||
|
mainWindow.Editor.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
|
||||||
|
formState.BackgroundTime = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.Stop:
|
||||||
|
mediaPlayer.Stop();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.PreviousFrame:
|
||||||
|
mainWindow.SeekTo(mediaPlayer.Time - step);
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.NextFrame:
|
||||||
|
mainWindow.SeekTo(mediaPlayer.Time + step);
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.SaveAnnotations:
|
||||||
|
await SaveAnnotations();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||||
|
|
||||||
|
mainWindow.Editor.RemoveSelectedAnns();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.RemoveAllAnns:
|
||||||
|
mainWindow.Editor.RemoveAllAnns();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.TurnOnVolume:
|
||||||
|
mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Collapsed;
|
||||||
|
mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Visible;
|
||||||
|
mediaPlayer.Volume = formState.CurrentVolume;
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.TurnOffVolume:
|
||||||
|
mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Collapsed;
|
||||||
|
mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Visible;
|
||||||
|
formState.CurrentVolume = mediaPlayer.Volume;
|
||||||
|
mediaPlayer.Volume = 0;
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.Previous:
|
||||||
|
NextMedia(isPrevious: true);
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.Next:
|
||||||
|
NextMedia();
|
||||||
|
break;
|
||||||
|
case PlaybackControlEnum.None:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(controlEnum), controlEnum, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, e.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextMedia(bool isPrevious = false)
|
||||||
|
{
|
||||||
|
var increment = isPrevious ? -1 : 1;
|
||||||
|
var check = isPrevious ? -1 : mainWindow.LvFiles.Items.Count;
|
||||||
|
if (mainWindow.LvFiles.SelectedIndex + increment == check)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mainWindow.LvFiles.SelectedIndex += increment;
|
||||||
|
Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(VolumeChangedEvent notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ChangeVolume(notification.Volume);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeVolume(int volume)
|
||||||
|
{
|
||||||
|
formState.CurrentVolume = volume;
|
||||||
|
mediaPlayer.Volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Play()
|
||||||
|
{
|
||||||
|
if (mainWindow.LvFiles.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
var mediaInfo = (MediaFileInfo)mainWindow.LvFiles.SelectedItem;
|
||||||
|
|
||||||
|
formState.CurrentMedia = mediaInfo;
|
||||||
|
mediaPlayer.Stop();
|
||||||
|
mainWindow.Title = $"Azaion Annotator - {mediaInfo.Name}";
|
||||||
|
mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.PauseForAnnotations]);
|
||||||
|
mediaPlayer.Play(new Media(libVLC, mediaInfo.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveAnnotations()
|
||||||
|
{
|
||||||
|
var annGridSelectedIndex = mainWindow.DgAnnotations.SelectedIndex;
|
||||||
|
|
||||||
|
if (formState.CurrentMedia == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var time = formState.BackgroundTime ?? TimeSpan.FromMilliseconds(mediaPlayer.Time);
|
||||||
|
var fName = formState.GetTimeName(time);
|
||||||
|
|
||||||
|
var currentAnns = mainWindow.Editor.CurrentAnns
|
||||||
|
.Select(x => new YoloLabel(x.Info, mainWindow.Editor.RenderSize, formState.BackgroundTime.HasValue ? mainWindow.Editor.RenderSize : formState.CurrentVideoSize))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
await YoloLabel.WriteToFile(currentAnns, Path.Combine(_directoriesConfig.LabelsDirectory, $"{fName}.txt"));
|
||||||
|
await mainWindow.AddAnnotations(time, currentAnns);
|
||||||
|
|
||||||
|
formState.CurrentMedia.HasAnnotations = mainWindow.Annotations.Count != 0;
|
||||||
|
mainWindow.LvFiles.Items.Refresh();
|
||||||
|
|
||||||
|
var isVideo = formState.CurrentMedia.MediaType == MediaTypes.Video;
|
||||||
|
var destinationPath = Path.Combine(_directoriesConfig.ImagesDirectory, $"{fName}{(isVideo ? ".jpg" : Path.GetExtension(formState.CurrentMedia.Path))}");
|
||||||
|
|
||||||
|
mainWindow.Editor.RemoveAllAnns();
|
||||||
|
if (isVideo)
|
||||||
|
{
|
||||||
|
if (formState.BackgroundTime.HasValue)
|
||||||
|
{
|
||||||
|
//no need to save image, it's already there, just remove background
|
||||||
|
mainWindow.Editor.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
|
||||||
|
formState.BackgroundTime = null;
|
||||||
|
|
||||||
|
//next item
|
||||||
|
var annGrid = mainWindow.DgAnnotations;
|
||||||
|
annGrid.SelectedIndex = Math.Min(annGrid.Items.Count, annGridSelectedIndex + 1);
|
||||||
|
mainWindow.OpenAnnotationResult((AnnotationResult)annGrid.SelectedItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var resultHeight = (uint)Math.Round(RESULT_WIDTH / formState.CurrentVideoSize.Width * formState.CurrentVideoSize.Height);
|
||||||
|
mediaPlayer.TakeSnapshot(0, destinationPath, RESULT_WIDTH, resultHeight);
|
||||||
|
mediaPlayer.Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.Copy(formState.CurrentMedia.Path, destinationPath, overwrite: true);
|
||||||
|
NextMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
await mediator.Publish(new ImageCreatedEvent(destinationPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +1,53 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
<ApplicationIcon>..\logo.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="libc.translation" Version="7.1.1" />
|
<PackageReference Include="libc.translation" Version="7.1.1" />
|
||||||
<PackageReference Include="LibVLCSharp" Version="3.8.2" />
|
<PackageReference Include="LibVLCSharp" Version="3.8.2" />
|
||||||
<PackageReference Include="LibVLCSharp.WPF" Version="3.8.2" />
|
<PackageReference Include="LibVLCSharp.WPF" Version="3.8.2" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="RangeTree" Version="3.0.1" />
|
<PackageReference Include="RangeTree" Version="3.0.1" />
|
||||||
<PackageReference Include="ScottPlot.WPF" Version="5.0.39" />
|
|
||||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
<PackageReference Include="SkiaSharp" Version="2.88.9" />
|
||||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
|
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
|
||||||
<PackageReference Include="VirtualizingWrapPanel" Version="2.0.10" />
|
|
||||||
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
|
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
|
||||||
<PackageReference Include="YoloV8.Gpu" Version="5.0.4" />
|
<PackageReference Include="YoloV8.Gpu" Version="5.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="logo.ico" />
|
<None Remove="logo.ico" />
|
||||||
<Resource Include="logo.ico">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Resource>
|
|
||||||
<None Update="config.json">
|
<None Update="config.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="AutodetectDialog.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<XamlRuntime>Wpf</XamlRuntime>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Azaion.Annotator.Controls;
|
|
||||||
|
|
||||||
public partial class AnnotationClasses : DataGrid
|
|
||||||
{
|
|
||||||
public AnnotationClasses()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediatR;
|
using Azaion.Common.DTO;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
using Azaion.Common.Extensions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
public class AnnotationResult
|
public class AnnotationResult
|
||||||
{
|
{
|
||||||
private readonly Config _config = null!;
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "f")]
|
[JsonProperty(PropertyName = "f")]
|
||||||
public string Image { get; set; } = null!;
|
public string Image { get; set; } = null!;
|
||||||
|
|
||||||
@@ -18,61 +19,26 @@ public class AnnotationResult
|
|||||||
public double Lon { get; set; }
|
public double Lon { get; set; }
|
||||||
public List<Detection> Detections { get; set; } = new();
|
public List<Detection> Detections { get; set; } = new();
|
||||||
|
|
||||||
#region For Display in the grid
|
#region For XAML Form
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
//For XAML Form
|
|
||||||
public string TimeStr => $"{Time:h\\:mm\\:ss}";
|
public string TimeStr => $"{Time:h\\:mm\\:ss}";
|
||||||
|
|
||||||
private List<int>? _detectionClasses = null!;
|
|
||||||
|
|
||||||
//For Form
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ClassName
|
public string ClassName { get; set; } = null!;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Detections.Count == 0)
|
|
||||||
return "";
|
|
||||||
_detectionClasses ??= Detections.Select(x => x.ClassNumber).Distinct().ToList();
|
|
||||||
|
|
||||||
return _detectionClasses.Count > 1
|
|
||||||
? string.Join(", ", _detectionClasses.Select(x => _config.AnnotationClassesDict[x].ShortName))
|
|
||||||
: _config.AnnotationClassesDict[_detectionClasses.FirstOrDefault()].Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Color ClassColor1 => GetAnnotationClass(0);
|
public Color ClassColor0 { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Color ClassColor2 => GetAnnotationClass(1);
|
public Color ClassColor1 { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Color ClassColor3 => GetAnnotationClass(2);
|
public Color ClassColor2 { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Color ClassColor4 => GetAnnotationClass(3);
|
public Color ClassColor3 { get; set; }
|
||||||
|
|
||||||
private Color GetAnnotationClass(int colorNumber)
|
|
||||||
{
|
|
||||||
if (Detections.Count == 0)
|
|
||||||
return (-1).ToColor();
|
|
||||||
|
|
||||||
_detectionClasses ??= Detections.Select(x => x.ClassNumber).Distinct().ToList();
|
|
||||||
|
|
||||||
return colorNumber >= _detectionClasses.Count
|
|
||||||
? _config.AnnotationClassesDict[_detectionClasses.LastOrDefault()].Color
|
|
||||||
: _config.AnnotationClassesDict[_detectionClasses[colorNumber]].Color;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public AnnotationResult() { }
|
|
||||||
public AnnotationResult(TimeSpan time, string timeName, List<Detection> detections, Config config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
Detections = detections;
|
|
||||||
Time = time;
|
|
||||||
Image = $"{timeName}.jpg";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Size = System.Windows.Size;
|
|
||||||
using Point = System.Windows.Point;
|
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
|
||||||
|
|
||||||
public class Config
|
|
||||||
{
|
|
||||||
public const string THUMBNAIL_PREFIX = "_thumb";
|
|
||||||
public const string THUMBNAILS_CACHE_FILE = "thumbnails.cache";
|
|
||||||
|
|
||||||
public string VideosDirectory { get; set; } = null!;
|
|
||||||
public string LabelsDirectory { get; set; } = null!;
|
|
||||||
public string ImagesDirectory { get; set; } = null!;
|
|
||||||
public string ResultsDirectory { get; set; } = null!;
|
|
||||||
public string ThumbnailsDirectory { get; set; } = null!;
|
|
||||||
public string UnknownImages { get; set; } = null!;
|
|
||||||
|
|
||||||
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
|
||||||
|
|
||||||
private Dictionary<int, AnnotationClass>? _annotationClassesDict;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public Dictionary<int, AnnotationClass> AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id);
|
|
||||||
|
|
||||||
public WindowConfig MainWindowConfig { get; set; } = null!;
|
|
||||||
public WindowConfig DatasetExplorerConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
public double LeftPanelWidth { get; set; }
|
|
||||||
public double RightPanelWidth { get; set; }
|
|
||||||
|
|
||||||
public bool ShowHelpOnStart { get; set; }
|
|
||||||
|
|
||||||
public List<string> VideoFormats { get; set; } = null!;
|
|
||||||
public List<string> ImageFormats { get; set; } = null!;
|
|
||||||
|
|
||||||
public ThumbnailConfig ThumbnailConfig { get; set; } = null!;
|
|
||||||
public int? LastSelectedExplorerClass { get; set; }
|
|
||||||
public AIRecognitionConfig AIRecognitionConfig { get; set; } = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AIRecognitionConfig
|
|
||||||
{
|
|
||||||
public string AIModelPath { get; set; } = null!;
|
|
||||||
public double FrameRecognitionSeconds { get; set; }
|
|
||||||
public double TrackingDistanceConfidence { get; set; }
|
|
||||||
public double TrackingProbabilityIncrease { get; set; }
|
|
||||||
public double TrackingIntersectionThreshold { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class WindowConfig
|
|
||||||
{
|
|
||||||
public Size WindowSize { get; set; }
|
|
||||||
public Point WindowLocation { get; set; }
|
|
||||||
public bool FullScreen { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ThumbnailConfig
|
|
||||||
{
|
|
||||||
public Size Size { get; set; }
|
|
||||||
public int Border { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IConfigRepository
|
|
||||||
{
|
|
||||||
public Config Get();
|
|
||||||
public void Save(Config config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileConfigRepository(ILogger<FileConfigRepository> logger) : IConfigRepository
|
|
||||||
{
|
|
||||||
private const string CONFIG_PATH = "config.json";
|
|
||||||
|
|
||||||
private 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";
|
|
||||||
private const string DEFAULT_THUMBNAILS_DIR = "thumbnails";
|
|
||||||
private const string DEFAULT_UNKNOWN_IMG_DIR = "unknown";
|
|
||||||
|
|
||||||
private const int DEFAULT_THUMBNAIL_BORDER = 10;
|
|
||||||
private const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2;
|
|
||||||
private const double TRACKING_DISTANCE_CONFIDENCE = 0.15;
|
|
||||||
private const double TRACKING_PROBABILITY_INCREASE = 15;
|
|
||||||
private const double TRACKING_INTERSECTION_THRESHOLD = 0.8;
|
|
||||||
|
|
||||||
private static readonly Size DefaultWindowSize = new(1280, 720);
|
|
||||||
private static readonly Point DefaultWindowLocation = new(100, 100);
|
|
||||||
private static readonly Size DefaultThumbnailSize = new(240, 135);
|
|
||||||
|
|
||||||
private static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
|
|
||||||
private static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"];
|
|
||||||
|
|
||||||
public Config Get()
|
|
||||||
{
|
|
||||||
var exePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!;
|
|
||||||
var configFilePath = Path.Combine(exePath, CONFIG_PATH);
|
|
||||||
|
|
||||||
if (!File.Exists(configFilePath))
|
|
||||||
{
|
|
||||||
return new Config
|
|
||||||
{
|
|
||||||
VideosDirectory = Path.Combine(exePath, DEFAULT_VIDEO_DIR),
|
|
||||||
LabelsDirectory = Path.Combine(exePath, DEFAULT_LABELS_DIR),
|
|
||||||
ImagesDirectory = Path.Combine(exePath, DEFAULT_IMAGES_DIR),
|
|
||||||
ResultsDirectory = Path.Combine(exePath, DEFAULT_RESULTS_DIR),
|
|
||||||
ThumbnailsDirectory = Path.Combine(exePath, DEFAULT_THUMBNAILS_DIR),
|
|
||||||
UnknownImages = Path.Combine(exePath, DEFAULT_UNKNOWN_IMG_DIR),
|
|
||||||
|
|
||||||
MainWindowConfig = new WindowConfig
|
|
||||||
{
|
|
||||||
WindowSize = DefaultWindowSize,
|
|
||||||
WindowLocation = DefaultWindowLocation
|
|
||||||
},
|
|
||||||
DatasetExplorerConfig = new WindowConfig
|
|
||||||
{
|
|
||||||
WindowSize = DefaultWindowSize,
|
|
||||||
WindowLocation = DefaultWindowLocation
|
|
||||||
},
|
|
||||||
ShowHelpOnStart = true,
|
|
||||||
|
|
||||||
VideoFormats = DefaultVideoFormats,
|
|
||||||
ImageFormats = DefaultImageFormats,
|
|
||||||
ThumbnailConfig = new ThumbnailConfig
|
|
||||||
{
|
|
||||||
Size = DefaultThumbnailSize,
|
|
||||||
Border = DEFAULT_THUMBNAIL_BORDER
|
|
||||||
},
|
|
||||||
AIRecognitionConfig = new AIRecognitionConfig
|
|
||||||
{
|
|
||||||
AIModelPath = "azaion.onnx",
|
|
||||||
FrameRecognitionSeconds = DEFAULT_FRAME_RECOGNITION_SECONDS,
|
|
||||||
TrackingDistanceConfidence = TRACKING_DISTANCE_CONFIDENCE,
|
|
||||||
TrackingProbabilityIncrease = TRACKING_PROBABILITY_INCREASE,
|
|
||||||
TrackingIntersectionThreshold = TRACKING_INTERSECTION_THRESHOLD
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var str = File.ReadAllText(CONFIG_PATH);
|
|
||||||
return JsonConvert.DeserializeObject<Config>(str) ?? new Config();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save(Config config)
|
|
||||||
{
|
|
||||||
File.WriteAllText(CONFIG_PATH, JsonConvert.SerializeObject(config, Formatting.Indented), Encoding.UTF8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,32 +11,13 @@ public class FormState
|
|||||||
? ""
|
? ""
|
||||||
: Path.GetFileNameWithoutExtension(CurrentMedia.Name).Replace(" ", "");
|
: Path.GetFileNameWithoutExtension(CurrentMedia.Name).Replace(" ", "");
|
||||||
|
|
||||||
public string CurrentMrl { get; set; }
|
public string CurrentMrl { get; set; } = null!;
|
||||||
public Size CurrentVideoSize { get; set; }
|
public Size CurrentVideoSize { get; set; }
|
||||||
public TimeSpan CurrentVideoLength { get; set; }
|
public TimeSpan CurrentVideoLength { get; set; }
|
||||||
|
|
||||||
public TimeSpan? BackgroundTime { get; set; }
|
public TimeSpan? BackgroundTime { get; set; }
|
||||||
public int CurrentVolume { get; set; } = 100;
|
public int CurrentVolume { get; set; } = 100;
|
||||||
public ObservableCollection<AnnotationResult> AnnotationResults { get; set; } = [];
|
public ObservableCollection<AnnotationResult> AnnotationResults { get; set; } = [];
|
||||||
public WindowsEnum ActiveWindow { get; set; }
|
|
||||||
|
|
||||||
public string GetTimeName(TimeSpan? ts) => $"{VideoName}_{ts:hmmssf}";
|
public string GetTimeName(TimeSpan? ts) => $"{VideoName}_{ts:hmmssf}";
|
||||||
|
|
||||||
public TimeSpan? GetTime(string name)
|
|
||||||
{
|
|
||||||
var timeStr = name.Split("_").LastOrDefault();
|
|
||||||
if (string.IsNullOrEmpty(timeStr) || timeStr.Length < 6)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
|
|
||||||
if (!int.TryParse(timeStr[0..1], out var hours))
|
|
||||||
return null;
|
|
||||||
if (!int.TryParse(timeStr[1..3], out var minutes))
|
|
||||||
return null;
|
|
||||||
if (!int.TryParse(timeStr[3..5], out var seconds))
|
|
||||||
return null;
|
|
||||||
if (!int.TryParse(timeStr[5..6], out var milliseconds))
|
|
||||||
return null;
|
|
||||||
return new TimeSpan(0, hours, minutes, seconds, milliseconds * 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
public class KeyEvent(object sender, KeyEventArgs args) : INotification
|
|
||||||
{
|
|
||||||
public object Sender { get; set; } = sender;
|
|
||||||
public KeyEventArgs Args { get; set; } = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PlaybackControlEvent(PlaybackControlEnum playbackControlEnum) : INotification
|
public class PlaybackControlEvent(PlaybackControlEnum playbackControlEnum) : INotification
|
||||||
{
|
{
|
||||||
public PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum;
|
public PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Azaion.Annotator.DTO;
|
|
||||||
|
|
||||||
public enum WindowsEnum
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Main = 10,
|
|
||||||
DatasetExplorer = 20
|
|
||||||
}
|
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
using System.Windows;
|
// using System.Windows;
|
||||||
using System.Windows.Controls;
|
// using System.Windows.Controls;
|
||||||
using System.Windows.Controls.Primitives;
|
// using System.Windows.Controls.Primitives;
|
||||||
using System.Windows.Media;
|
// using System.Windows.Media;
|
||||||
|
//
|
||||||
namespace Azaion.Annotator.Extensions;
|
// namespace Azaion.Annotator.Extensions;
|
||||||
|
//
|
||||||
public static class DataGridExtensions
|
// public static class DataGridExtensions
|
||||||
{
|
// {
|
||||||
public static DataGridCell? GetCell(this DataGrid grid, int rowIndex, int columnIndex = 0)
|
// public static DataGridCell? GetCell(this DataGrid grid, int rowIndex, int columnIndex = 0)
|
||||||
{
|
// {
|
||||||
var row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
|
// var row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
|
||||||
if (row == null)
|
// if (row == null)
|
||||||
return null;
|
// return null;
|
||||||
|
//
|
||||||
var presenter = FindVisualChild<DataGridCellsPresenter>(row);
|
// var presenter = FindVisualChild<DataGridCellsPresenter>(row);
|
||||||
if (presenter == null)
|
// if (presenter == null)
|
||||||
return null;
|
// return null;
|
||||||
|
//
|
||||||
var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
|
// var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
|
||||||
if (cell != null) return cell;
|
// if (cell != null) return cell;
|
||||||
|
//
|
||||||
// now try to bring into view and retrieve the cell
|
// // now try to bring into view and retrieve the cell
|
||||||
grid.ScrollIntoView(row, grid.Columns[columnIndex]);
|
// grid.ScrollIntoView(row, grid.Columns[columnIndex]);
|
||||||
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
|
// cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
|
||||||
|
//
|
||||||
return cell;
|
// return cell;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject? dependencyObj) where T : DependencyObject
|
// private static IEnumerable<T> FindVisualChildren<T>(DependencyObject? dependencyObj) where T : DependencyObject
|
||||||
{
|
// {
|
||||||
if (dependencyObj == null)
|
// if (dependencyObj == null)
|
||||||
yield break;
|
// yield break;
|
||||||
|
//
|
||||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObj); i++)
|
// for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObj); i++)
|
||||||
{
|
// {
|
||||||
var child = VisualTreeHelper.GetChild(dependencyObj, i);
|
// var child = VisualTreeHelper.GetChild(dependencyObj, i);
|
||||||
if (child is T dependencyObject)
|
// if (child is T dependencyObject)
|
||||||
{
|
// {
|
||||||
yield return dependencyObject;
|
// yield return dependencyObject;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
foreach (T childOfChild in FindVisualChildren<T>(child))
|
// foreach (T childOfChild in FindVisualChildren<T>(child))
|
||||||
{
|
// {
|
||||||
yield return childOfChild;
|
// yield return childOfChild;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public static TChildItem? FindVisualChild<TChildItem>(DependencyObject? obj) where TChildItem : DependencyObject =>
|
// public static TChildItem? FindVisualChild<TChildItem>(DependencyObject? obj) where TChildItem : DependencyObject =>
|
||||||
FindVisualChildren<TChildItem>(obj).FirstOrDefault();
|
// FindVisualChildren<TChildItem>(obj).FirstOrDefault();
|
||||||
}
|
// }
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Extensions;
|
namespace Azaion.Annotator.Extensions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Common.DTO.Config;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
public partial class HelpWindow : Window
|
public partial class HelpWindow : Window
|
||||||
{
|
{
|
||||||
private readonly Config _config;
|
private readonly WindowConfig _windowConfig;
|
||||||
|
|
||||||
public HelpWindow(Config config)
|
public HelpWindow(WindowConfig windowConfig)
|
||||||
{
|
{
|
||||||
_config = config;
|
_windowConfig = windowConfig;
|
||||||
Loaded += (_, _) => CbShowHelp.IsChecked = _config.ShowHelpOnStart;
|
Loaded += (_, _) => CbShowHelp.IsChecked = windowConfig.ShowHelpOnStart;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
private void Close(object sender, RoutedEventArgs e) => Close();
|
private void Close(object sender, RoutedEventArgs e) => Close();
|
||||||
|
|
||||||
private void CbShowHelp_OnChecked(object sender, RoutedEventArgs e) => _config.ShowHelpOnStart = true;
|
private void CbShowHelp_OnChecked(object sender, RoutedEventArgs e) => _windowConfig.ShowHelpOnStart = true;
|
||||||
private void CbShowHelp_OnUnchecked(object sender, RoutedEventArgs e) => _config.ShowHelpOnStart = false;
|
private void CbShowHelp_OnUnchecked(object sender, RoutedEventArgs e) => _windowConfig.ShowHelpOnStart = false;
|
||||||
}
|
}
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using Azaion.Annotator.DTO;
|
|
||||||
using LibVLCSharp.Shared;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
|
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
|
||||||
|
|
||||||
public class MainWindowEventHandler :
|
|
||||||
INotificationHandler<KeyEvent>,
|
|
||||||
INotificationHandler<AnnClassSelectedEvent>,
|
|
||||||
INotificationHandler<PlaybackControlEvent>,
|
|
||||||
INotificationHandler<VolumeChangedEvent>
|
|
||||||
{
|
|
||||||
private readonly LibVLC _libVLC;
|
|
||||||
private readonly MediaPlayer _mediaPlayer;
|
|
||||||
private readonly MainWindow _mainWindow;
|
|
||||||
private readonly FormState _formState;
|
|
||||||
private readonly Config _config;
|
|
||||||
private readonly IMediator _mediator;
|
|
||||||
private readonly IGalleryManager _galleryManager;
|
|
||||||
private readonly DatasetExplorer _datasetExplorer;
|
|
||||||
private readonly ILogger<MainWindowEventHandler> _logger;
|
|
||||||
|
|
||||||
private const int STEP = 20;
|
|
||||||
private const int LARGE_STEP = 5000;
|
|
||||||
private const int RESULT_WIDTH = 1280;
|
|
||||||
|
|
||||||
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
|
|
||||||
{
|
|
||||||
{ Key.Space, PlaybackControlEnum.Pause },
|
|
||||||
{ Key.Left, PlaybackControlEnum.PreviousFrame },
|
|
||||||
{ Key.Right, PlaybackControlEnum.NextFrame },
|
|
||||||
{ Key.Enter, PlaybackControlEnum.SaveAnnotations },
|
|
||||||
{ Key.Delete, PlaybackControlEnum.RemoveSelectedAnns },
|
|
||||||
{ Key.X, PlaybackControlEnum.RemoveAllAnns },
|
|
||||||
{ Key.PageUp, PlaybackControlEnum.Previous },
|
|
||||||
{ Key.PageDown, PlaybackControlEnum.Next },
|
|
||||||
};
|
|
||||||
|
|
||||||
public MainWindowEventHandler(LibVLC libVLC,
|
|
||||||
MediaPlayer mediaPlayer,
|
|
||||||
MainWindow mainWindow,
|
|
||||||
FormState formState,
|
|
||||||
Config config,
|
|
||||||
IMediator mediator,
|
|
||||||
IGalleryManager galleryManager,
|
|
||||||
DatasetExplorer datasetExplorer,
|
|
||||||
ILogger<MainWindowEventHandler> logger)
|
|
||||||
{
|
|
||||||
_libVLC = libVLC;
|
|
||||||
_mediaPlayer = mediaPlayer;
|
|
||||||
_mainWindow = mainWindow;
|
|
||||||
_formState = formState;
|
|
||||||
_config = config;
|
|
||||||
_mediator = mediator;
|
|
||||||
_galleryManager = galleryManager;
|
|
||||||
_datasetExplorer = datasetExplorer;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(AnnClassSelectedEvent notification, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
SelectClass(notification.AnnotationClass);
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void SelectClass(AnnotationClass annClass)
|
|
||||||
{
|
|
||||||
_mainWindow.Editor.CurrentAnnClass = annClass;
|
|
||||||
foreach (var ann in _mainWindow.Editor.CurrentAnns.Where(x => x.IsSelected))
|
|
||||||
ann.AnnotationClass = annClass;
|
|
||||||
_mainWindow.LvClasses.SelectedIndex = annClass.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(KeyEvent notification, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_formState.ActiveWindow != WindowsEnum.Main)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var key = notification.Args.Key;
|
|
||||||
var keyNumber = (int?)null;
|
|
||||||
|
|
||||||
if ((int)key >= (int)Key.D1 && (int)key <= (int)Key.D9)
|
|
||||||
keyNumber = key - Key.D1;
|
|
||||||
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
|
|
||||||
keyNumber = key - Key.NumPad1;
|
|
||||||
if (keyNumber.HasValue)
|
|
||||||
SelectClass((AnnotationClass)_mainWindow.LvClasses.Items[keyNumber.Value]);
|
|
||||||
|
|
||||||
if (_keysControlEnumDict.TryGetValue(key, out var value))
|
|
||||||
await ControlPlayback(value);
|
|
||||||
|
|
||||||
if (key == Key.A)
|
|
||||||
_mainWindow.AutoDetect(null!, null!);
|
|
||||||
|
|
||||||
await VolumeControl(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task VolumeControl(Key key)
|
|
||||||
{
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case Key.VolumeMute when _mediaPlayer.Volume == 0:
|
|
||||||
await ControlPlayback(PlaybackControlEnum.TurnOnVolume);
|
|
||||||
break;
|
|
||||||
case Key.VolumeMute:
|
|
||||||
await ControlPlayback(PlaybackControlEnum.TurnOffVolume);
|
|
||||||
break;
|
|
||||||
case Key.Up:
|
|
||||||
case Key.VolumeUp:
|
|
||||||
var vUp = Math.Min(100, _mediaPlayer.Volume + 5);
|
|
||||||
ChangeVolume(vUp);
|
|
||||||
_mainWindow.Volume.Value = vUp;
|
|
||||||
break;
|
|
||||||
case Key.Down:
|
|
||||||
case Key.VolumeDown:
|
|
||||||
var vDown = Math.Max(0, _mediaPlayer.Volume - 5);
|
|
||||||
ChangeVolume(vDown);
|
|
||||||
_mainWindow.Volume.Value = vDown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(PlaybackControlEvent notification, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await ControlPlayback(notification.PlaybackControl);
|
|
||||||
_mainWindow.VideoView.Focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ControlPlayback(PlaybackControlEnum controlEnum)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
|
||||||
var step = isCtrlPressed ? LARGE_STEP : STEP;
|
|
||||||
|
|
||||||
switch (controlEnum)
|
|
||||||
{
|
|
||||||
case PlaybackControlEnum.Play:
|
|
||||||
Play();
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.Pause:
|
|
||||||
_mediaPlayer.Pause();
|
|
||||||
if (!_mediaPlayer.IsPlaying)
|
|
||||||
_mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.AnnotationHelp]);
|
|
||||||
if (_formState.BackgroundTime.HasValue)
|
|
||||||
{
|
|
||||||
_mainWindow.Editor.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
|
|
||||||
_formState.BackgroundTime = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.Stop:
|
|
||||||
_mediaPlayer.Stop();
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.PreviousFrame:
|
|
||||||
_mainWindow.SeekTo(_mediaPlayer.Time - step);
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.NextFrame:
|
|
||||||
_mainWindow.SeekTo(_mediaPlayer.Time + step);
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.SaveAnnotations:
|
|
||||||
await SaveAnnotations();
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.RemoveSelectedAnns:
|
|
||||||
|
|
||||||
_mainWindow.Editor.RemoveSelectedAnns();
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.RemoveAllAnns:
|
|
||||||
_mainWindow.Editor.RemoveAllAnns();
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.TurnOnVolume:
|
|
||||||
_mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Collapsed;
|
|
||||||
_mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Visible;
|
|
||||||
_mediaPlayer.Volume = _formState.CurrentVolume;
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.TurnOffVolume:
|
|
||||||
_mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Collapsed;
|
|
||||||
_mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Visible;
|
|
||||||
_formState.CurrentVolume = _mediaPlayer.Volume;
|
|
||||||
_mediaPlayer.Volume = 0;
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.Previous:
|
|
||||||
NextMedia(isPrevious: true);
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.Next:
|
|
||||||
NextMedia();
|
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.None:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(controlEnum), controlEnum, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, e.Message);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NextMedia(bool isPrevious = false)
|
|
||||||
{
|
|
||||||
var increment = isPrevious ? -1 : 1;
|
|
||||||
var check = isPrevious ? -1 : _mainWindow.LvFiles.Items.Count;
|
|
||||||
if (_mainWindow.LvFiles.SelectedIndex + increment == check)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_mainWindow.LvFiles.SelectedIndex += increment;
|
|
||||||
Play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(VolumeChangedEvent notification, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
ChangeVolume(notification.Volume);
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ChangeVolume(int volume)
|
|
||||||
{
|
|
||||||
_formState.CurrentVolume = volume;
|
|
||||||
_mediaPlayer.Volume = volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Play()
|
|
||||||
{
|
|
||||||
if (_mainWindow.LvFiles.SelectedItem == null)
|
|
||||||
return;
|
|
||||||
var mediaInfo = (MediaFileInfo)_mainWindow.LvFiles.SelectedItem;
|
|
||||||
|
|
||||||
_formState.CurrentMedia = mediaInfo;
|
|
||||||
_mediaPlayer.Stop();
|
|
||||||
_mainWindow.Title = $"Azaion Annotator - {mediaInfo.Name}";
|
|
||||||
_mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.PauseForAnnotations]);
|
|
||||||
_mediaPlayer.Play(new Media(_libVLC, mediaInfo.Path));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveAnnotations()
|
|
||||||
{
|
|
||||||
var annGridSelectedIndex = _mainWindow.DgAnnotations.SelectedIndex;
|
|
||||||
|
|
||||||
if (_formState.CurrentMedia == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var time = _formState.BackgroundTime ?? TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
|
||||||
var fName = _formState.GetTimeName(time);
|
|
||||||
|
|
||||||
var currentAnns = _mainWindow.Editor.CurrentAnns
|
|
||||||
.Select(x => new YoloLabel(x.Info, _mainWindow.Editor.RenderSize, _formState.BackgroundTime.HasValue ? _mainWindow.Editor.RenderSize : _formState.CurrentVideoSize))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
await YoloLabel.WriteToFile(currentAnns, Path.Combine(_config.LabelsDirectory, $"{fName}.txt"));
|
|
||||||
await _mainWindow.AddAnnotations(time, currentAnns);
|
|
||||||
|
|
||||||
_formState.CurrentMedia.HasAnnotations = _mainWindow.Annotations.Count != 0;
|
|
||||||
_mainWindow.LvFiles.Items.Refresh();
|
|
||||||
|
|
||||||
var isVideo = _formState.CurrentMedia.MediaType == MediaTypes.Video;
|
|
||||||
var destinationPath = Path.Combine(_config.ImagesDirectory, $"{fName}{(isVideo ? ".jpg" : Path.GetExtension(_formState.CurrentMedia.Path))}");
|
|
||||||
|
|
||||||
_mainWindow.Editor.RemoveAllAnns();
|
|
||||||
if (isVideo)
|
|
||||||
{
|
|
||||||
if (_formState.BackgroundTime.HasValue)
|
|
||||||
{
|
|
||||||
//no need to save image, it's already there, just remove background
|
|
||||||
_mainWindow.Editor.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0));
|
|
||||||
_formState.BackgroundTime = null;
|
|
||||||
|
|
||||||
//next item
|
|
||||||
var annGrid = _mainWindow.DgAnnotations;
|
|
||||||
annGrid.SelectedIndex = Math.Min(annGrid.Items.Count, annGridSelectedIndex + 1);
|
|
||||||
_mainWindow.OpenAnnotationResult((AnnotationResult)annGrid.SelectedItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var resultHeight = (uint)Math.Round(RESULT_WIDTH / _formState.CurrentVideoSize.Width * _formState.CurrentVideoSize.Height);
|
|
||||||
_mediaPlayer.TakeSnapshot(0, destinationPath, RESULT_WIDTH, resultHeight);
|
|
||||||
_mediaPlayer.Play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
File.Copy(_formState.CurrentMedia.Path, destinationPath, overwrite: true);
|
|
||||||
NextMedia();
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumbnailDto = await _galleryManager.CreateThumbnail(destinationPath);
|
|
||||||
if (thumbnailDto != null)
|
|
||||||
_datasetExplorer.AddThumbnail(thumbnailDto, currentAnns.Select(x => x.ClassNumber));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
using Compunet.YoloV8;
|
using Compunet.YoloV8;
|
||||||
using Compunet.YoloV8.Data;
|
using Microsoft.Extensions.Options;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using Detection = Azaion.Annotator.DTO.Detection;
|
using Detection = Azaion.Common.DTO.Detection;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
@@ -14,9 +15,9 @@ public interface IAIDetector
|
|||||||
List<Detection> Detect(Stream stream);
|
List<Detection> Detect(Stream stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class YOLODetector(Config config) : IAIDetector, IDisposable
|
public class YOLODetector(AIRecognitionConfig recognitionConfig) : IAIDetector, IDisposable
|
||||||
{
|
{
|
||||||
private readonly YoloPredictor _predictor = new(config.AIRecognitionConfig.AIModelPath);
|
private readonly YoloPredictor _predictor = new(recognitionConfig.AIModelPath);
|
||||||
|
|
||||||
public List<Detection> Detect(Stream stream)
|
public List<Detection> Detect(Stream stream)
|
||||||
{
|
{
|
||||||
@@ -37,7 +38,7 @@ public class YOLODetector(Config config) : IAIDetector, IDisposable
|
|||||||
|
|
||||||
private List<Detection> FilterOverlapping(List<Detection> detections)
|
private List<Detection> FilterOverlapping(List<Detection> detections)
|
||||||
{
|
{
|
||||||
var k = config.AIRecognitionConfig.TrackingIntersectionThreshold;
|
var k = recognitionConfig.TrackingIntersectionThreshold;
|
||||||
var filteredDetections = new List<Detection>();
|
var filteredDetections = new List<Detection>();
|
||||||
for (var i = 0; i < detections.Count; i++)
|
for (var i = 0; i < detections.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -48,21 +49,21 @@ public class YOLODetector(Config config) : IAIDetector, IDisposable
|
|||||||
intersect.Intersect(detections[j].ToRectangle());
|
intersect.Intersect(detections[j].ToRectangle());
|
||||||
|
|
||||||
var maxArea = Math.Max(detections[i].ToRectangle().Area(), detections[j].ToRectangle().Area());
|
var maxArea = Math.Max(detections[i].ToRectangle().Area(), detections[j].ToRectangle().Area());
|
||||||
if (intersect.Area() > k * maxArea)
|
if (!(intersect.Area() > k * maxArea))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (detections[i].Probability > detections[j].Probability)
|
||||||
{
|
{
|
||||||
if (detections[i].Probability > detections[j].Probability)
|
filteredDetections.Add(detections[i]);
|
||||||
{
|
detections.RemoveAt(j);
|
||||||
filteredDetections.Add(detections[i]);
|
|
||||||
detections.RemoveAt(j);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
filteredDetections.Add(detections[j]);
|
|
||||||
detections.RemoveAt(i);
|
|
||||||
}
|
|
||||||
detectionSelected = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filteredDetections.Add(detections[j]);
|
||||||
|
detections.RemoveAt(i);
|
||||||
|
}
|
||||||
|
detectionSelected = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detectionSelected)
|
if (!detectionSelected)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
|
||||||
|
namespace Azaion.Common;
|
||||||
|
|
||||||
|
public class Constants
|
||||||
|
{
|
||||||
|
#region DefaultConfig
|
||||||
|
|
||||||
|
public const string CONFIG_PATH = "config.json";
|
||||||
|
|
||||||
|
public const string DEFAULT_VIDEO_DIR = "video";
|
||||||
|
public const string DEFAULT_LABELS_DIR = "labels";
|
||||||
|
public const string DEFAULT_IMAGES_DIR = "images";
|
||||||
|
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
|
||||||
|
|
||||||
|
public static readonly List<AnnotationClass> DefaultAnnotationClasses =
|
||||||
|
[
|
||||||
|
new() { Id = 0, Name = "Броньована техніка", ShortName = "Бронь" },
|
||||||
|
new() { Id = 1, Name = "Вантажівка", ShortName = "Вантаж" },
|
||||||
|
new() { Id = 2, Name = "Машина легкова", ShortName = "Машина" },
|
||||||
|
new() { Id = 3, Name = "Артилерія", ShortName = "Арта" },
|
||||||
|
new() { Id = 4, Name = "Тінь від техніки", ShortName = "Тінь" },
|
||||||
|
new() { Id = 5, Name = "Окопи", ShortName = "Окопи" },
|
||||||
|
new() { Id = 6, Name = "Військовий", ShortName = "Військов" },
|
||||||
|
new() { Id = 7, Name = "Накати", ShortName = "Накати" },
|
||||||
|
new() { Id = 8, Name = "Танк з захистом", ShortName = "Танк захист" },
|
||||||
|
new() { Id = 9, Name = "Дим", ShortName = "Дим" },
|
||||||
|
new() { Id = 10, Name = "Літак", ShortName = "Літак" }
|
||||||
|
];
|
||||||
|
|
||||||
|
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
|
||||||
|
public static readonly List<string> DefaultImageFormats = ["jpg", "jpeg", "png", "bmp"];
|
||||||
|
|
||||||
|
public static TimeSpan? GetTime(string imagePath)
|
||||||
|
{
|
||||||
|
var timeStr = imagePath.Split("_").LastOrDefault();
|
||||||
|
if (string.IsNullOrEmpty(timeStr) || timeStr.Length < 6)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
|
||||||
|
if (!int.TryParse(timeStr[0..1], out var hours))
|
||||||
|
return null;
|
||||||
|
if (!int.TryParse(timeStr[1..3], out var minutes))
|
||||||
|
return null;
|
||||||
|
if (!int.TryParse(timeStr[3..5], out var seconds))
|
||||||
|
return null;
|
||||||
|
if (!int.TryParse(timeStr[5..6], out var milliseconds))
|
||||||
|
return null;
|
||||||
|
return new TimeSpan(0, hours, minutes, seconds, milliseconds * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-2
@@ -1,9 +1,8 @@
|
|||||||
<DataGrid x:Class="Azaion.Annotator.Controls.AnnotationClasses"
|
<DataGrid x:Class="Azaion.Common.Controls.AnnotationClasses"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Azaion.Annotator.Controls"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="300" d:DesignWidth="300"
|
d:DesignHeight="300" d:DesignWidth="300"
|
||||||
Background="Black"
|
Background="Black"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Azaion.Common.Controls;
|
||||||
|
|
||||||
|
public partial class AnnotationClasses
|
||||||
|
{
|
||||||
|
public AnnotationClasses()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -3,10 +3,10 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Common.DTO;
|
||||||
using Label = System.Windows.Controls.Label;
|
using Label = System.Windows.Controls.Label;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Controls;
|
namespace Azaion.Common.Controls;
|
||||||
|
|
||||||
public class AnnotationControl : Border
|
public class AnnotationControl : Border
|
||||||
{
|
{
|
||||||
@@ -4,11 +4,12 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Color = System.Windows.Media.Color;
|
using Color = System.Windows.Media.Color;
|
||||||
using Rectangle = System.Windows.Shapes.Rectangle;
|
using Rectangle = System.Windows.Shapes.Rectangle;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Controls;
|
namespace Azaion.Common.Controls;
|
||||||
|
|
||||||
public class CanvasEditor : Canvas
|
public class CanvasEditor : Canvas
|
||||||
{
|
{
|
||||||
@@ -28,7 +29,6 @@ public class CanvasEditor : Canvas
|
|||||||
private const int MIN_SIZE = 20;
|
private const int MIN_SIZE = 20;
|
||||||
private readonly TimeSpan _viewThreshold = TimeSpan.FromMilliseconds(400);
|
private readonly TimeSpan _viewThreshold = TimeSpan.FromMilliseconds(400);
|
||||||
|
|
||||||
public FormState FormState { get; set; } = null!;
|
|
||||||
public IMediator Mediator { get; set; } = null!;
|
public IMediator Mediator { get; set; } = null!;
|
||||||
|
|
||||||
public static readonly DependencyProperty GetTimeFuncProp =
|
public static readonly DependencyProperty GetTimeFuncProp =
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Common.Extensions;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
public class AnnotationClass
|
public class AnnotationClass
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = null!;
|
||||||
public string ShortName { get; set; }
|
public string ShortName { get; set; } = null!;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Color Color => Id.ToColor();
|
public Color Color => Id.ToColor();
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
|
public class AIRecognitionConfig
|
||||||
|
{
|
||||||
|
public string AIModelPath { get; set; } = null!;
|
||||||
|
public double FrameRecognitionSeconds { get; set; }
|
||||||
|
public double TrackingDistanceConfidence { get; set; }
|
||||||
|
public double TrackingProbabilityIncrease { get; set; }
|
||||||
|
public double TrackingIntersectionThreshold { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Azaion.Common.DTO.Config;
|
||||||
|
|
||||||
|
public class AnnotationConfig
|
||||||
|
{
|
||||||
|
public List<AnnotationClass> AnnotationClasses { get; set; } = null!;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
private Dictionary<int, AnnotationClass>? _annotationClassesDict;
|
||||||
|
[JsonIgnore]
|
||||||
|
public Dictionary<int, AnnotationClass> AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id);
|
||||||
|
|
||||||
|
public int? LastSelectedExplorerClass { get; set; }
|
||||||
|
|
||||||
|
public List<string> VideoFormats { get; set; } = null!;
|
||||||
|
public List<string> ImageFormats { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Azaion.Suite.Services.DTO;
|
||||||
|
|
||||||
|
public class ApiConfig
|
||||||
|
{
|
||||||
|
public string Url { get; set; } = null!;
|
||||||
|
public int RetryCount {get;set;}
|
||||||
|
public double TimeoutSeconds { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LocalFilesConfig
|
||||||
|
{
|
||||||
|
public string DllPath { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public class AppConfig
|
||||||
|
{
|
||||||
|
public ApiConfig ApiConfig { 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!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IConfigUpdater
|
||||||
|
{
|
||||||
|
void CheckConfig();
|
||||||
|
void Save(AppConfig config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConfigUpdater : IConfigUpdater
|
||||||
|
{
|
||||||
|
public void CheckConfig()
|
||||||
|
{
|
||||||
|
var exePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)!;
|
||||||
|
var configFilePath = Path.Combine(exePath, Constants.CONFIG_PATH);
|
||||||
|
|
||||||
|
if (File.Exists(configFilePath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var appConfig = new AppConfig
|
||||||
|
{
|
||||||
|
AnnotationConfig = new AnnotationConfig
|
||||||
|
{
|
||||||
|
AnnotationClasses = Constants.DefaultAnnotationClasses,
|
||||||
|
VideoFormats = Constants.DefaultVideoFormats,
|
||||||
|
ImageFormats = Constants.DefaultImageFormats,
|
||||||
|
},
|
||||||
|
|
||||||
|
WindowConfig = new WindowConfig
|
||||||
|
{
|
||||||
|
WindowSize = Constants.DefaultWindowSize,
|
||||||
|
WindowLocation = Constants.DefaultWindowLocation,
|
||||||
|
ShowHelpOnStart = true,
|
||||||
|
FullScreen = true,
|
||||||
|
LeftPanelWidth = 250,
|
||||||
|
RightPanelWidth = 250,
|
||||||
|
},
|
||||||
|
|
||||||
|
DirectoriesConfig = new DirectoriesConfig
|
||||||
|
{
|
||||||
|
VideosDirectory = Constants.DEFAULT_VIDEO_DIR,
|
||||||
|
ImagesDirectory = Constants.DEFAULT_IMAGES_DIR,
|
||||||
|
LabelsDirectory = Constants.DEFAULT_LABELS_DIR,
|
||||||
|
ResultsDirectory = Constants.DEFAULT_RESULTS_DIR,
|
||||||
|
ThumbnailsDirectory = Constants.DEFAULT_THUMBNAILS_DIR
|
||||||
|
},
|
||||||
|
|
||||||
|
ThumbnailConfig = new ThumbnailConfig
|
||||||
|
{
|
||||||
|
Size = Constants.DefaultThumbnailSize,
|
||||||
|
Border = Constants.DEFAULT_THUMBNAIL_BORDER
|
||||||
|
},
|
||||||
|
|
||||||
|
AIRecognitionConfig = new AIRecognitionConfig
|
||||||
|
{
|
||||||
|
AIModelPath = "azaion.onnx",
|
||||||
|
FrameRecognitionSeconds = Constants.DEFAULT_FRAME_RECOGNITION_SECONDS,
|
||||||
|
TrackingDistanceConfidence = Constants.TRACKING_DISTANCE_CONFIDENCE,
|
||||||
|
TrackingProbabilityIncrease = Constants.TRACKING_PROBABILITY_INCREASE,
|
||||||
|
TrackingIntersectionThreshold = Constants.TRACKING_INTERSECTION_THRESHOLD
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Save(appConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(AppConfig config)
|
||||||
|
{
|
||||||
|
File.WriteAllText(Constants.CONFIG_PATH, JsonConvert.SerializeObject(config, Formatting.Indented), Encoding.UTF8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
|
public class DirectoriesConfig
|
||||||
|
{
|
||||||
|
public string VideosDirectory { get; set; } = null!;
|
||||||
|
public string LabelsDirectory { get; set; } = null!;
|
||||||
|
public string ImagesDirectory { get; set; } = null!;
|
||||||
|
public string ResultsDirectory { get; set; } = null!;
|
||||||
|
public string ThumbnailsDirectory { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Azaion.Common.DTO.Config;
|
||||||
|
|
||||||
|
public class ThumbnailConfig
|
||||||
|
{
|
||||||
|
public Size Size { get; set; }
|
||||||
|
public int Border { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Azaion.Common.DTO.Config;
|
||||||
|
|
||||||
|
public class WindowConfig
|
||||||
|
{
|
||||||
|
public Size WindowSize { get; set; }
|
||||||
|
public Point WindowLocation { get; set; }
|
||||||
|
public bool FullScreen { get; set; }
|
||||||
|
|
||||||
|
public double LeftPanelWidth { get; set; }
|
||||||
|
public double RightPanelWidth { get; set; }
|
||||||
|
public bool ShowHelpOnStart { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
|
public class ImageCreatedEvent(string imagePath) : INotification
|
||||||
|
{
|
||||||
|
public string ImagePath { get; } = imagePath;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
|
public class KeyEvent(object sender, KeyEventArgs args, WindowEnum windowEnum) : INotification
|
||||||
|
{
|
||||||
|
public object Sender { get; set; } = sender;
|
||||||
|
public KeyEventArgs Args { get; set; } = args;
|
||||||
|
public WindowEnum WindowEnum { get; } = windowEnum;
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ using System.IO;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
public abstract class Label
|
public abstract class Label
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
public class LabelInfo
|
public class LabelInfo
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
public enum PlaybackControlEnum
|
public enum PlaybackControlEnum
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Azaion.Common.DTO;
|
||||||
|
|
||||||
|
public enum WindowEnum
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Annotator = 10,
|
||||||
|
DatasetExplorer = 20
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Extensions;
|
namespace Azaion.Common.Extensions;
|
||||||
|
|
||||||
public static class ColorExtensions
|
public static class ColorExtensions
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Extensions;
|
namespace Azaion.Common.Extensions;
|
||||||
|
|
||||||
public class DenseDateTimeConverter : IsoDateTimeConverter
|
public class DenseDateTimeConverter : IsoDateTimeConverter
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
|
||||||
|
namespace Azaion.Common.Extensions;
|
||||||
|
|
||||||
|
public static class WindowExtensions
|
||||||
|
{
|
||||||
|
public static WindowEnum GetParentWindow(this FrameworkElement? element)
|
||||||
|
{
|
||||||
|
while (element != null && element is not Window)
|
||||||
|
{
|
||||||
|
element = element.Parent as FrameworkElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is not Window)
|
||||||
|
return WindowEnum.None;
|
||||||
|
|
||||||
|
return WindowEnum.Annotator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
)]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="DatasetExplorer.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<XamlRuntime>Wpf</XamlRuntime>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||||
|
<PackageReference Include="ScottPlot.WPF" Version="5.0.46" />
|
||||||
|
<PackageReference Include="VirtualizingWrapPanel" Version="2.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System.Drawing.Common">
|
||||||
|
<HintPath>C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\8.0.8\System.Drawing.Common.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace Azaion.Dataset;
|
||||||
|
|
||||||
|
public static class BitmapExtensions
|
||||||
|
{
|
||||||
|
public static async Task<BitmapImage> OpenImage(this string imagePath)
|
||||||
|
{
|
||||||
|
var image = new BitmapImage();
|
||||||
|
await using var stream = File.OpenRead(imagePath);
|
||||||
|
image.BeginInit();
|
||||||
|
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
image.StreamSource = stream;
|
||||||
|
image.EndInit();
|
||||||
|
image.Freeze();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
<Window x:Class="Azaion.Annotator.DatasetExplorer"
|
<Window x:Class="Azaion.Dataset.DatasetExplorer"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
||||||
xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
|
xmlns:scottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
|
||||||
xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
|
xmlns:datasetExplorer="clr-namespace:Azaion.Dataset"
|
||||||
xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
|
xmlns:controls="clr-namespace:Azaion.Common.Controls;assembly=Azaion.Common"
|
||||||
|
xmlns:controls1="clr-namespace:Azaion.Annotator.Controls;assembly=Azaion.Common"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Переглядач анотацій" Height="900" Width="1200">
|
Title="Переглядач анотацій" Height="900" Width="1200">
|
||||||
|
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<DataTemplate x:Key="ThumbnailTemplate" DataType="{x:Type dto:ThumbnailDto}">
|
<DataTemplate x:Key="ThumbnailTemplate" DataType="{x:Type datasetExplorer:ThumbnailDto}">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"></RowDefinition>
|
<RowDefinition Height="*"></RowDefinition>
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
</controls:CanvasEditor>
|
</controls:CanvasEditor>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Name="ClassDistributionTab" Header="Розподіл класів">
|
<TabItem Name="ClassDistributionTab" Header="Розподіл класів">
|
||||||
<ScottPlot:WpfPlot x:Name="ClassDistribution" />
|
<scottPlot:WpfPlot x:Name="ClassDistribution" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</TabControl>
|
</TabControl>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
@@ -3,27 +3,27 @@ using System.IO;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Azaion.Annotator.DTO;
|
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using ScottPlot;
|
using ScottPlot;
|
||||||
using Color = ScottPlot.Color;
|
using Color = ScottPlot.Color;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
|
||||||
using Orientation = ScottPlot.Orientation;
|
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Dataset;
|
||||||
|
|
||||||
public partial class DatasetExplorer
|
public partial class DatasetExplorer
|
||||||
{
|
{
|
||||||
private readonly Config _config;
|
|
||||||
private readonly ILogger<DatasetExplorer> _logger;
|
private readonly ILogger<DatasetExplorer> _logger;
|
||||||
|
private readonly AnnotationConfig _annotationConfig;
|
||||||
|
private readonly DirectoriesConfig _directoriesConfig;
|
||||||
|
|
||||||
public ObservableCollection<ThumbnailDto> ThumbnailsDtos { get; set; } = new();
|
public ObservableCollection<ThumbnailDto> ThumbnailsDtos { get; set; } = new();
|
||||||
private ObservableCollection<AnnotationClass> AllAnnotationClasses { get; set; } = new();
|
private ObservableCollection<AnnotationClass> AllAnnotationClasses { get; set; } = new();
|
||||||
|
|
||||||
private int _tempSelectedClassIdx = 0;
|
private int _tempSelectedClassIdx = 0;
|
||||||
private readonly IConfigRepository _configRepository;
|
|
||||||
private readonly FormState _formState;
|
|
||||||
private readonly IGalleryManager _galleryManager;
|
private readonly IGalleryManager _galleryManager;
|
||||||
|
|
||||||
public bool ThumbnailLoading { get; set; }
|
public bool ThumbnailLoading { get; set; }
|
||||||
@@ -31,31 +31,31 @@ public partial class DatasetExplorer
|
|||||||
public ThumbnailDto? CurrentThumbnail { get; set; }
|
public ThumbnailDto? CurrentThumbnail { get; set; }
|
||||||
|
|
||||||
public DatasetExplorer(
|
public DatasetExplorer(
|
||||||
Config config,
|
IOptions<DirectoriesConfig> directoriesConfig,
|
||||||
|
IOptions<AnnotationConfig> annotationConfig,
|
||||||
ILogger<DatasetExplorer> logger,
|
ILogger<DatasetExplorer> logger,
|
||||||
IConfigRepository configRepository,
|
|
||||||
FormState formState,
|
|
||||||
IGalleryManager galleryManager)
|
IGalleryManager galleryManager)
|
||||||
{
|
{
|
||||||
_config = config;
|
_directoriesConfig = directoriesConfig.Value;
|
||||||
|
_annotationConfig = annotationConfig.Value;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configRepository = configRepository;
|
|
||||||
_formState = formState;
|
|
||||||
_galleryManager = galleryManager;
|
_galleryManager = galleryManager;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Loaded += async (_, _) =>
|
Loaded += async (_, _) =>
|
||||||
{
|
{
|
||||||
|
_ = Task.Run(async () => await _galleryManager.RefreshThumbnails());
|
||||||
|
|
||||||
AllAnnotationClasses = new ObservableCollection<AnnotationClass>(
|
AllAnnotationClasses = new ObservableCollection<AnnotationClass>(
|
||||||
new List<AnnotationClass> { new() {Id = -1, Name = "All", ShortName = "All"}}
|
new List<AnnotationClass> { new() {Id = -1, Name = "All", ShortName = "All"}}
|
||||||
.Concat(_config.AnnotationClasses));
|
.Concat(_annotationConfig.AnnotationClasses));
|
||||||
LvClasses.ItemsSource = AllAnnotationClasses;
|
LvClasses.ItemsSource = AllAnnotationClasses;
|
||||||
|
|
||||||
LvClasses.MouseUp += async (_, _) =>
|
LvClasses.MouseUp += async (_, _) =>
|
||||||
{
|
{
|
||||||
var selectedClass = (AnnotationClass)LvClasses.SelectedItem;
|
var selectedClass = (AnnotationClass)LvClasses.SelectedItem;
|
||||||
ExplorerEditor.CurrentAnnClass = selectedClass;
|
ExplorerEditor.CurrentAnnClass = selectedClass;
|
||||||
config.LastSelectedExplorerClass = selectedClass.Id;
|
_annotationConfig.LastSelectedExplorerClass = selectedClass.Id;
|
||||||
|
|
||||||
if (Switcher.SelectedIndex == 0)
|
if (Switcher.SelectedIndex == 0)
|
||||||
await ReloadThumbnails();
|
await ReloadThumbnails();
|
||||||
@@ -80,15 +80,11 @@ public partial class DatasetExplorer
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
LvClasses.SelectedIndex = config.LastSelectedExplorerClass ?? 0;
|
LvClasses.SelectedIndex = _annotationConfig.LastSelectedExplorerClass ?? 0;
|
||||||
ExplorerEditor.CurrentAnnClass = (AnnotationClass)LvClasses.SelectedItem;
|
ExplorerEditor.CurrentAnnClass = (AnnotationClass)LvClasses.SelectedItem;
|
||||||
await ReloadThumbnails();
|
await ReloadThumbnails();
|
||||||
LoadClassDistribution();
|
LoadClassDistribution();
|
||||||
|
|
||||||
SizeChanged += async (_, _) => await SaveUserSettings();
|
|
||||||
LocationChanged += async (_, _) => await SaveUserSettings();
|
|
||||||
StateChanged += async (_, _) => await SaveUserSettings();
|
|
||||||
|
|
||||||
RefreshThumbBar.Value = galleryManager.ThumbnailsPercentage;
|
RefreshThumbBar.Value = galleryManager.ThumbnailsPercentage;
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
};
|
};
|
||||||
@@ -118,9 +114,7 @@ public partial class DatasetExplorer
|
|||||||
StatusText.Text = $"Обрано: {ThumbnailsView.SelectedItems.Count} | {ThumbnailsView.SelectedIndex} / {ThumbnailsDtos.Count}";
|
StatusText.Text = $"Обрано: {ThumbnailsView.SelectedItems.Count} | {ThumbnailsView.SelectedIndex} / {ThumbnailsDtos.Count}";
|
||||||
};
|
};
|
||||||
|
|
||||||
Activated += (_, _) => { _formState.ActiveWindow = WindowsEnum.DatasetExplorer; };
|
ExplorerEditor.GetTimeFunc = () => Constants.GetTime(CurrentThumbnail!.ImagePath);
|
||||||
|
|
||||||
ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentThumbnail!.ImagePath);
|
|
||||||
galleryManager.ThumbnailsUpdate += thumbnailsPercentage =>
|
galleryManager.ThumbnailsUpdate += thumbnailsPercentage =>
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() => RefreshThumbBar.Value = thumbnailsPercentage);
|
Dispatcher.Invoke(() => RefreshThumbBar.Value = thumbnailsPercentage);
|
||||||
@@ -136,16 +130,15 @@ public partial class DatasetExplorer
|
|||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.Key,
|
x.Key,
|
||||||
_config.AnnotationClassesDict[x.Key].Name,
|
_annotationConfig.AnnotationClassesDict[x.Key].Name,
|
||||||
_config.AnnotationClassesDict[x.Key].Color,
|
_annotationConfig.AnnotationClassesDict[x.Key].Color,
|
||||||
ClassCount = x.Count()
|
ClassCount = x.Count()
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var foregroundColor = Color.FromColor(System.Drawing.Color.Black);
|
var foregroundColor = Color.FromColor(System.Drawing.Color.Black);
|
||||||
var plot = ClassDistribution.Plot;
|
|
||||||
|
|
||||||
plot.Add.Bars(data.Select(x => new Bar
|
var bars = data.Select(x => new Bar
|
||||||
{
|
{
|
||||||
Orientation = Orientation.Horizontal,
|
Orientation = Orientation.Horizontal,
|
||||||
Position = -1.5 * x.Key + 1,
|
Position = -1.5 * x.Key + 1,
|
||||||
@@ -154,22 +147,34 @@ public partial class DatasetExplorer
|
|||||||
Value = x.ClassCount,
|
Value = x.ClassCount,
|
||||||
CenterLabel = true,
|
CenterLabel = true,
|
||||||
LabelOffset = 10
|
LabelOffset = 10
|
||||||
}));
|
}).ToList();
|
||||||
|
|
||||||
|
ClassDistribution.Plot.Add.Bars(bars);
|
||||||
|
|
||||||
foreach (var x in data)
|
foreach (var x in data)
|
||||||
{
|
{
|
||||||
var label = plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1);
|
var label = ClassDistribution.Plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1);
|
||||||
label.LabelFontColor = foregroundColor;
|
label.LabelFontColor = foregroundColor;
|
||||||
label.LabelFontSize = 18;
|
label.LabelFontSize = 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
plot.Axes.AutoScale();
|
ClassDistribution.Plot.Axes.AutoScale();
|
||||||
plot.HideAxesAndGrid();
|
ClassDistribution.Plot.HideAxesAndGrid();
|
||||||
plot.FigureBackground.Color = new("#888888");
|
ClassDistribution.Plot.FigureBackground.Color = new("#888888");
|
||||||
|
|
||||||
ClassDistribution.Refresh();
|
ClassDistribution.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReloadThumbnailsItemClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show($"Видалити всі іконки і згенерувати нову базу іконок в {_directoriesConfig.ThumbnailsDirectory}?",
|
||||||
|
"Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||||
|
if (result != MessageBoxResult.Yes)
|
||||||
|
return;
|
||||||
|
_galleryManager.ClearThumbnails();
|
||||||
|
_galleryManager.RefreshThumbnails();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task EditAnnotation()
|
private async Task EditAnnotation()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -187,11 +192,11 @@ public partial class DatasetExplorer
|
|||||||
};
|
};
|
||||||
SwitchTab(toEditor: true);
|
SwitchTab(toEditor: true);
|
||||||
|
|
||||||
var time = _formState.GetTime(dto.ImagePath);
|
var time = Constants.GetTime(dto.ImagePath);
|
||||||
ExplorerEditor.RemoveAllAnns();
|
ExplorerEditor.RemoveAllAnns();
|
||||||
foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath))
|
foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath))
|
||||||
{
|
{
|
||||||
var annClass = _config.AnnotationClassesDict[ann.ClassNumber];
|
var annClass = _annotationConfig.AnnotationClassesDict[ann.ClassNumber];
|
||||||
var canvasLabel = new CanvasLabel(ann, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize);
|
var canvasLabel = new CanvasLabel(ann, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize);
|
||||||
ExplorerEditor.CreateAnnotation(annClass, time, canvasLabel);
|
ExplorerEditor.CreateAnnotation(annClass, time, canvasLabel);
|
||||||
}
|
}
|
||||||
@@ -217,7 +222,7 @@ public partial class DatasetExplorer
|
|||||||
AnnotationsTab.Visibility = Visibility.Collapsed;
|
AnnotationsTab.Visibility = Visibility.Collapsed;
|
||||||
EditorTab.Visibility = Visibility.Visible;
|
EditorTab.Visibility = Visibility.Visible;
|
||||||
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
||||||
LvClasses.ItemsSource = _config.AnnotationClasses;
|
LvClasses.ItemsSource = _annotationConfig.AnnotationClasses;
|
||||||
|
|
||||||
Switcher.SelectedIndex = 1;
|
Switcher.SelectedIndex = 1;
|
||||||
LvClasses.SelectedIndex = Math.Max(0, _tempSelectedClassIdx - 1);
|
LvClasses.SelectedIndex = Math.Max(0, _tempSelectedClassIdx - 1);
|
||||||
@@ -232,16 +237,6 @@ public partial class DatasetExplorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveUserSettings()
|
|
||||||
{
|
|
||||||
_config.DatasetExplorerConfig = this.GetConfig();
|
|
||||||
await ThrottleExt.Throttle(() =>
|
|
||||||
{
|
|
||||||
_configRepository.Save(_config);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}, TimeSpan.FromSeconds(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DeleteAnnotations()
|
private void DeleteAnnotations()
|
||||||
{
|
{
|
||||||
var tempSelected = ThumbnailsView.SelectedIndex;
|
var tempSelected = ThumbnailsView.SelectedIndex;
|
||||||
@@ -266,10 +261,10 @@ public partial class DatasetExplorer
|
|||||||
LoadingAnnsCaption.Visibility = Visibility.Visible;
|
LoadingAnnsCaption.Visibility = Visibility.Visible;
|
||||||
LoadingAnnsBar.Visibility = Visibility.Visible;
|
LoadingAnnsBar.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
if (!Directory.Exists(_config.ThumbnailsDirectory))
|
if (!Directory.Exists(_directoriesConfig.ThumbnailsDirectory))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
|
var thumbnails = Directory.GetFiles(_directoriesConfig.ThumbnailsDirectory, "*.jpg");
|
||||||
var thumbnailDtos = new List<ThumbnailDto>();
|
var thumbnailDtos = new List<ThumbnailDto>();
|
||||||
for (int i = 0; i < thumbnails.Length; i++)
|
for (int i = 0; i < thumbnails.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -293,11 +288,11 @@ public partial class DatasetExplorer
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.THUMBNAIL_PREFIX.Length];
|
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Constants.THUMBNAIL_PREFIX.Length];
|
||||||
var imagePath = Path.Combine(_config.ImagesDirectory, name);
|
var imagePath = Path.Combine(_directoriesConfig.ImagesDirectory, name);
|
||||||
var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
|
var labelPath = Path.Combine(_directoriesConfig.LabelsDirectory, $"{name}.txt");
|
||||||
|
|
||||||
foreach (var f in _config.ImageFormats)
|
foreach (var f in _annotationConfig.ImageFormats)
|
||||||
{
|
{
|
||||||
var curName = $"{imagePath}.{f}";
|
var curName = $"{imagePath}.{f}";
|
||||||
if (File.Exists(curName))
|
if (File.Exists(curName))
|
||||||
+14
-8
@@ -1,14 +1,15 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Common.DTO;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Dataset;
|
||||||
|
|
||||||
public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer,
|
public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer, IGalleryManager galleryManager, IOptions<DirectoriesConfig> directoriesConfig)
|
||||||
Config config,
|
:
|
||||||
IGalleryManager galleryManager,
|
INotificationHandler<KeyEvent>,
|
||||||
FormState formState) : INotificationHandler<KeyEvent>
|
INotificationHandler<ImageCreatedEvent>
|
||||||
{
|
{
|
||||||
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
|
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
|
||||||
{
|
{
|
||||||
@@ -20,7 +21,7 @@ public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer,
|
|||||||
|
|
||||||
public async Task Handle(KeyEvent keyEvent, CancellationToken cancellationToken)
|
public async Task Handle(KeyEvent keyEvent, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (formState.ActiveWindow != WindowsEnum.DatasetExplorer)
|
if (keyEvent.WindowEnum != WindowEnum.Annotator)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var key = keyEvent.Args.Key;
|
var key = keyEvent.Args.Key;
|
||||||
@@ -50,7 +51,7 @@ public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer,
|
|||||||
.Select(x => new YoloLabel(x.Info, datasetExplorer.ExplorerEditor.RenderSize, datasetExplorer.ExplorerEditor.RenderSize))
|
.Select(x => new YoloLabel(x.Info, datasetExplorer.ExplorerEditor.RenderSize, datasetExplorer.ExplorerEditor.RenderSize))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
await YoloLabel.WriteToFile(currentAnns, Path.Combine(config.LabelsDirectory, datasetExplorer.CurrentThumbnail!.LabelPath));
|
await YoloLabel.WriteToFile(currentAnns, Path.Combine(directoriesConfig.Value.LabelsDirectory, datasetExplorer.CurrentThumbnail!.LabelPath));
|
||||||
await galleryManager.CreateThumbnail(datasetExplorer.CurrentThumbnail.ImagePath);
|
await galleryManager.CreateThumbnail(datasetExplorer.CurrentThumbnail.ImagePath);
|
||||||
await galleryManager.SaveLabelsCache();
|
await galleryManager.SaveLabelsCache();
|
||||||
datasetExplorer.CurrentThumbnail.UpdateImage();
|
datasetExplorer.CurrentThumbnail.UpdateImage();
|
||||||
@@ -67,4 +68,9 @@ public class DatasetExplorerEventHandler(DatasetExplorer datasetExplorer,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Handle(ImageCreatedEvent notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,37 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Azaion.Annotator.DTO;
|
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
using Azaion.Common.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Color = System.Drawing.Color;
|
using Color = System.Drawing.Color;
|
||||||
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
|
using ParallelOptions = Azaion.Annotator.Extensions.ParallelOptions;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Dataset;
|
||||||
|
|
||||||
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
|
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
|
||||||
|
|
||||||
public class GalleryManager : IGalleryManager
|
public class GalleryManager(
|
||||||
|
IOptions<DirectoriesConfig> directoriesConfig,
|
||||||
|
IOptions<ThumbnailConfig> thumbnailConfig,
|
||||||
|
IOptions<AnnotationConfig> annotationConfig,
|
||||||
|
ILogger<GalleryManager> logger) : IGalleryManager
|
||||||
{
|
{
|
||||||
private readonly ILogger<GalleryManager> _logger;
|
private readonly DirectoriesConfig _dirConfig = directoriesConfig.Value;
|
||||||
|
private readonly ThumbnailConfig _thumbnailConfig = thumbnailConfig.Value;
|
||||||
|
private readonly AnnotationConfig _annotationConfig = annotationConfig.Value;
|
||||||
|
|
||||||
|
private readonly string _thumbnailsCacheFile = Path.Combine(directoriesConfig.Value.ThumbnailsDirectory, Constants.THUMBNAILS_CACHE_FILE);
|
||||||
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||||
private readonly string _thumbnailsCacheFile;
|
|
||||||
|
|
||||||
private readonly SemaphoreSlim _updateLock = new(1);
|
private readonly SemaphoreSlim _updateLock = new(1);
|
||||||
|
|
||||||
@@ -28,7 +39,6 @@ public class GalleryManager : IGalleryManager
|
|||||||
public ConcurrentDictionary<string, LabelInfo> LabelsCache { get; set; } = new();
|
public ConcurrentDictionary<string, LabelInfo> LabelsCache { get; set; } = new();
|
||||||
|
|
||||||
private DirectoryInfo? _thumbnailsDirectory;
|
private DirectoryInfo? _thumbnailsDirectory;
|
||||||
private readonly Config _config;
|
|
||||||
|
|
||||||
private DirectoryInfo ThumbnailsDirectory
|
private DirectoryInfo ThumbnailsDirectory
|
||||||
{
|
{
|
||||||
@@ -37,24 +47,17 @@ public class GalleryManager : IGalleryManager
|
|||||||
if (_thumbnailsDirectory != null)
|
if (_thumbnailsDirectory != null)
|
||||||
return _thumbnailsDirectory;
|
return _thumbnailsDirectory;
|
||||||
|
|
||||||
var dir = new DirectoryInfo(_config.ThumbnailsDirectory);
|
var dir = new DirectoryInfo(_dirConfig.ThumbnailsDirectory);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
Directory.CreateDirectory(_config.ThumbnailsDirectory);
|
Directory.CreateDirectory(_dirConfig.ThumbnailsDirectory);
|
||||||
_thumbnailsDirectory = new DirectoryInfo(_config.ThumbnailsDirectory);
|
_thumbnailsDirectory = new DirectoryInfo(_dirConfig.ThumbnailsDirectory);
|
||||||
return _thumbnailsDirectory;
|
return _thumbnailsDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GalleryManager(Config config, ILogger<GalleryManager> logger)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
_logger = logger;
|
|
||||||
_thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.THUMBNAILS_CACHE_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearThumbnails()
|
public void ClearThumbnails()
|
||||||
{
|
{
|
||||||
foreach(var file in new DirectoryInfo(_config.ThumbnailsDirectory).GetFiles())
|
foreach(var file in new DirectoryInfo(_dirConfig.ThumbnailsDirectory).GetFiles())
|
||||||
file.Delete();
|
file.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@ public class GalleryManager : IGalleryManager
|
|||||||
await _updateLock.WaitAsync();
|
await _updateLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var prefixLen = Config.THUMBNAIL_PREFIX.Length;
|
var prefixLen = Constants.THUMBNAIL_PREFIX.Length;
|
||||||
|
|
||||||
var thumbnails = ThumbnailsDirectory.GetFiles()
|
var thumbnails = ThumbnailsDirectory.GetFiles()
|
||||||
.Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen])
|
.Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen])
|
||||||
@@ -80,7 +83,7 @@ public class GalleryManager : IGalleryManager
|
|||||||
else
|
else
|
||||||
LabelsCache = new ConcurrentDictionary<string, LabelInfo>();
|
LabelsCache = new ConcurrentDictionary<string, LabelInfo>();
|
||||||
|
|
||||||
var files = new DirectoryInfo(_config.ImagesDirectory).GetFiles();
|
var files = new DirectoryInfo(_dirConfig.ImagesDirectory).GetFiles();
|
||||||
var imagesCount = files.Length;
|
var imagesCount = files.Length;
|
||||||
|
|
||||||
await ParallelExt.ForEachAsync(files, async (file, cancellationToken) =>
|
await ParallelExt.ForEachAsync(files, async (file, cancellationToken) =>
|
||||||
@@ -94,7 +97,7 @@ public class GalleryManager : IGalleryManager
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
|
logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
|
||||||
}
|
}
|
||||||
}, new ParallelOptions
|
}, new ParallelOptions
|
||||||
{
|
{
|
||||||
@@ -126,11 +129,11 @@ public class GalleryManager : IGalleryManager
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var width = (int)_config.ThumbnailConfig.Size.Width;
|
var width = (int)_thumbnailConfig.Size.Width;
|
||||||
var height = (int)_config.ThumbnailConfig.Size.Height;
|
var height = (int)_thumbnailConfig.Size.Height;
|
||||||
|
|
||||||
var imgName = Path.GetFileName(imgPath);
|
var imgName = Path.GetFileName(imgPath);
|
||||||
var labelName = Path.Combine(_config.LabelsDirectory, $"{Path.GetFileNameWithoutExtension(imgPath)}.txt");
|
var labelName = Path.Combine(_dirConfig.LabelsDirectory, $"{Path.GetFileNameWithoutExtension(imgPath)}.txt");
|
||||||
|
|
||||||
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(imgPath, cancellationToken)));
|
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(imgPath, cancellationToken)));
|
||||||
|
|
||||||
@@ -145,7 +148,7 @@ public class GalleryManager : IGalleryManager
|
|||||||
if (!File.Exists(labelName))
|
if (!File.Exists(labelName))
|
||||||
{
|
{
|
||||||
File.Delete(imgPath);
|
File.Delete(imgPath);
|
||||||
_logger.LogInformation($"No labels found for image {imgName}! Image deleted!");
|
logger.LogInformation($"No labels found for image {imgName}! Image deleted!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +160,7 @@ public class GalleryManager : IGalleryManager
|
|||||||
AddToCache(imgPath, classes);
|
AddToCache(imgPath, classes);
|
||||||
|
|
||||||
var thumbWhRatio = width / (float)height;
|
var thumbWhRatio = width / (float)height;
|
||||||
var border = _config.ThumbnailConfig.Border;
|
var border = _thumbnailConfig.Border;
|
||||||
|
|
||||||
var frameX = 0.0;
|
var frameX = 0.0;
|
||||||
var frameY = 0.0;
|
var frameY = 0.0;
|
||||||
@@ -196,14 +199,14 @@ public class GalleryManager : IGalleryManager
|
|||||||
|
|
||||||
foreach (var label in labels)
|
foreach (var label in labels)
|
||||||
{
|
{
|
||||||
var color = _config.AnnotationClassesDict[label.ClassNumber].Color;
|
var color = _annotationConfig.AnnotationClassesDict[label.ClassNumber].Color;
|
||||||
var brush = new SolidBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
|
var brush = new SolidBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
|
||||||
|
|
||||||
var rectangle = new RectangleF((float)((label.X - frameX) / scale), (float)((label.Y - frameY) / scale), (float)(label.Width / scale), (float)(label.Height / scale));
|
var rectangle = new RectangleF((float)((label.X - frameX) / scale), (float)((label.Y - frameY) / scale), (float)(label.Width / scale), (float)(label.Height / scale));
|
||||||
g.FillRectangle(brush, rectangle);
|
g.FillRectangle(brush, rectangle);
|
||||||
}
|
}
|
||||||
|
|
||||||
var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Config.THUMBNAIL_PREFIX}.jpg");
|
var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Constants.THUMBNAIL_PREFIX}.jpg");
|
||||||
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
|
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
|
||||||
|
|
||||||
return new ThumbnailDto
|
return new ThumbnailDto
|
||||||
@@ -216,7 +219,7 @@ public class GalleryManager : IGalleryManager
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, e.Message);
|
logger.LogError(e, e.Message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,15 +2,14 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using Azaion.Annotator.Extensions;
|
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Dataset;
|
||||||
|
|
||||||
public class ThumbnailDto : INotifyPropertyChanged
|
public class ThumbnailDto : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
public string ThumbnailPath { get; set; }
|
public string ThumbnailPath { get; set; } = null!;
|
||||||
public string ImagePath { get; set; }
|
public string ImagePath { get; set; } = null!;
|
||||||
public string LabelPath { get; set; }
|
public string LabelPath { get; set; } = null!;
|
||||||
public DateTime ImageDate { get; set; }
|
public DateTime ImageDate { get; set; }
|
||||||
|
|
||||||
private BitmapImage? _image;
|
private BitmapImage? _image;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Application x:Class="Azaion.Annotator.App"
|
<Application x:Class="Azaion.Suite.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
@@ -1,18 +1,26 @@
|
|||||||
using System.Data;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using Azaion.Annotator;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
using Azaion.Common.Extensions;
|
||||||
|
using Azaion.Suite.Services;
|
||||||
|
using Azaion.Suite.Services.DTO;
|
||||||
|
using Azaion.Dataset;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Azaion.Annotator.Extensions;
|
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Suite;
|
||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
@@ -32,12 +40,21 @@ public partial class App : Application
|
|||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
_host = Host.CreateDefaultBuilder()
|
_host = Host.CreateDefaultBuilder()
|
||||||
|
.ConfigureAppConfiguration((context, config) => config
|
||||||
|
.AddCommandLine(Environment.GetCommandLineArgs())
|
||||||
|
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true))
|
||||||
.ConfigureServices((context, services) =>
|
.ConfigureServices((context, services) =>
|
||||||
{
|
{
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<Loader>();
|
||||||
services.AddSingleton<HelpWindow>();
|
services.AddSingleton<IHardwareService, HardwareService>();
|
||||||
|
services.AddSingleton<IResourceLoader, ResourceLoader>();
|
||||||
|
|
||||||
|
services.Configure<ApiConfig>(context.Configuration.GetSection(nameof(ApiConfig)));
|
||||||
|
services.AddSingleton<IConfigUpdater, ConfigUpdater>();
|
||||||
|
|
||||||
|
services.AddSingleton<Annotator.Annotator>();
|
||||||
services.AddSingleton<DatasetExplorer>();
|
services.AddSingleton<DatasetExplorer>();
|
||||||
services.AddSingleton<IGalleryManager, GalleryManager>();
|
services.AddSingleton<HelpWindow>();
|
||||||
services.AddSingleton<IAIDetector, YOLODetector>();
|
services.AddSingleton<IAIDetector, YOLODetector>();
|
||||||
services.AddMediatR(c => c.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
|
services.AddMediatR(c => c.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
|
||||||
services.AddSingleton<LibVLC>(_ => new LibVLC());
|
services.AddSingleton<LibVLC>(_ => new LibVLC());
|
||||||
@@ -47,29 +64,36 @@ public partial class App : Application
|
|||||||
var libVLC = sp.GetRequiredService<LibVLC>();
|
var libVLC = sp.GetRequiredService<LibVLC>();
|
||||||
return new MediaPlayer(libVLC);
|
return new MediaPlayer(libVLC);
|
||||||
});
|
});
|
||||||
services.AddSingleton<IConfigRepository, FileConfigRepository>();
|
services.AddSingleton<AnnotatorEventHandler>();
|
||||||
services.AddSingleton<Config>(sp => sp.GetRequiredService<IConfigRepository>().Get());
|
|
||||||
services.AddSingleton<MainWindowEventHandler>();
|
|
||||||
services.AddSingleton<VLCFrameExtractor>();
|
services.AddSingleton<VLCFrameExtractor>();
|
||||||
|
|
||||||
|
services.AddHttpClient<AzaionApiClient>((sp, client) =>
|
||||||
|
{
|
||||||
|
var apiConfig = sp.GetRequiredService<IOptions<ApiConfig>>().Value;
|
||||||
|
client.BaseAddress = new Uri(apiConfig.Url);
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(apiConfig.TimeoutSeconds);
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<DatasetExplorer>();
|
||||||
|
services.AddSingleton<IGalleryManager, GalleryManager>();
|
||||||
})
|
})
|
||||||
.UseSerilog()
|
|
||||||
.Build();
|
.Build();
|
||||||
_mediator = _host.Services.GetRequiredService<IMediator>();
|
_mediator = _host.Services.GetRequiredService<IMediator>();
|
||||||
_logger = _host.Services.GetRequiredService<ILogger<App>>();
|
_logger = _host.Services.GetRequiredService<ILogger<App>>();
|
||||||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
_logger.LogError(e.Exception, e.Exception.Message);
|
_logger.LogError(e.Exception, e.Exception.Message);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override async void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.KeyDownEvent, new RoutedEventHandler(GlobalClick));
|
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.KeyDownEvent, new RoutedEventHandler(GlobalClick));
|
||||||
_host.Start();
|
await _host.StartAsync();
|
||||||
_host.Services.GetRequiredService<MainWindow>().Show();
|
_host.Services.GetRequiredService<Loader>().Show();
|
||||||
|
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
}
|
}
|
||||||
@@ -77,6 +101,14 @@ public partial class App : Application
|
|||||||
private void GlobalClick(object sender, RoutedEventArgs e)
|
private void GlobalClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var args = (KeyEventArgs)e;
|
var args = (KeyEventArgs)e;
|
||||||
_ = ThrottleExt.Throttle(() => _mediator.Publish(new KeyEvent(sender, args)), TimeSpan.FromMilliseconds(50));
|
var keyEvent = new KeyEvent(sender, args, (sender as FrameworkElement).GetParentWindow());
|
||||||
|
_ = ThrottleExt.Throttle(() => _mediator.Publish(keyEvent), TimeSpan.FromMilliseconds(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void OnExit(ExitEventArgs e)
|
||||||
|
{
|
||||||
|
await _host.StopAsync();
|
||||||
|
_host.Dispose();
|
||||||
|
base.OnExit(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
)]
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ApplicationIcon>..\logo.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.4" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="appsettings.json" />
|
||||||
|
<Content Include="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Azaion.Annotator\Azaion.Annotator.csproj" />
|
||||||
|
<ProjectReference Include="..\Azaion.Common\Azaion.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Azaion.Dataset\Azaion.Dataset.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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> localFilesConfig) : AssemblyLoadContext
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string?, Assembly> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Azaion.Suite;
|
||||||
|
|
||||||
|
public class HardwareInfo
|
||||||
|
{
|
||||||
|
public string CPU { get; set; } = null!;
|
||||||
|
public string GPU { get; set; } = null!;
|
||||||
|
public string Memory { get; set; } = null!;
|
||||||
|
|
||||||
|
public string Hash { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
<Window x:Class="Azaion.Suite.Loader"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
WindowStyle="None"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Background="Transparent"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Title="Azaion Annotator Security"
|
||||||
|
Height="280" Width="350">
|
||||||
|
<Border Width="350"
|
||||||
|
Height="280"
|
||||||
|
Background="DarkGray"
|
||||||
|
CornerRadius="15"
|
||||||
|
MouseMove="MainMouseMove">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="15"
|
||||||
|
Direction ="-90"
|
||||||
|
RenderingBias ="Quality"
|
||||||
|
ShadowDepth ="2"
|
||||||
|
Color ="Gray" />
|
||||||
|
</Border.Effect>
|
||||||
|
<StackPanel Orientation="Vertical"
|
||||||
|
Margin="20">
|
||||||
|
<Canvas>
|
||||||
|
<Button Padding="5" ToolTip="Закрити" Background="DarkGray" BorderBrush="DarkGray" Canvas.Left="290" Cursor="Hand"
|
||||||
|
Name="CloseBtn"
|
||||||
|
Click="CloseClick">
|
||||||
|
<Path Stretch="Fill" Fill="LightGray" Data="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166
|
||||||
|
4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166
|
||||||
|
19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289
|
||||||
|
17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" />
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="{x:Type Button}">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border Background="{TemplateBinding Background}" BorderThickness="1">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
</Canvas>
|
||||||
|
<TextBlock Text="Вхід"
|
||||||
|
FontSize="25"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
FontWeight="Bold"
|
||||||
|
/>
|
||||||
|
<Grid VerticalAlignment="Center">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Text="Email"
|
||||||
|
Grid.Row="0"
|
||||||
|
Margin="0, 20, 0, 5"
|
||||||
|
HorizontalAlignment="Left"/>
|
||||||
|
<TextBox
|
||||||
|
Name="TbEmail"
|
||||||
|
Grid.Row="1"
|
||||||
|
Padding="0,5"
|
||||||
|
Width="300"
|
||||||
|
FontSize="16"
|
||||||
|
Background="DarkGray"
|
||||||
|
BorderBrush="DimGray"
|
||||||
|
BorderThickness="0,0,0,1"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Text="admin@azaion.com"/>
|
||||||
|
<TextBlock Text="Пароль"
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0, 20, 0, 5"
|
||||||
|
HorizontalAlignment="Left"/>
|
||||||
|
<PasswordBox Grid.Row="3"
|
||||||
|
Name="TbPassword"
|
||||||
|
Password="Az@1on1000Odm$n"
|
||||||
|
FontSize="16"
|
||||||
|
Background="DarkGray"
|
||||||
|
BorderBrush="DimGray"
|
||||||
|
Padding="0,5"
|
||||||
|
Width="300"
|
||||||
|
BorderThickness="0,0,0,1"
|
||||||
|
HorizontalAlignment="Left"/>
|
||||||
|
</Grid>
|
||||||
|
<Button x:Name="LoginBtn"
|
||||||
|
Content="Вхід"
|
||||||
|
Foreground="White"
|
||||||
|
Background="DimGray"
|
||||||
|
Margin="0,25"
|
||||||
|
Height="35"
|
||||||
|
Width="280"
|
||||||
|
Cursor="Hand"
|
||||||
|
Click="RunClick">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border x:Name="LoginBorder" Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="16">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="LightGray" TargetName="LoginBorder" />
|
||||||
|
<Setter Property="TextBlock.Foreground" Value="Black" TargetName="LoginBorder" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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> _localFilesConfig;
|
||||||
|
|
||||||
|
public Loader(IResourceLoader resourceLoader, IOptions<LocalFilesConfig> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Window x:Class="Azaion.Suite.MainSuite"
|
||||||
|
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.Suite"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="MainSuite" Height="450" Width="800">
|
||||||
|
<Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Azaion.Common.DTO.Config;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Azaion.Suite;
|
||||||
|
|
||||||
|
public partial class MainSuite : Window
|
||||||
|
{
|
||||||
|
private readonly AppConfig _appConfig;
|
||||||
|
private readonly IConfigUpdater _configUpdater;
|
||||||
|
|
||||||
|
public MainSuite(IOptions<AppConfig> appConfig, IConfigUpdater configUpdater)
|
||||||
|
{
|
||||||
|
_configUpdater = configUpdater;
|
||||||
|
_appConfig = appConfig.Value;
|
||||||
|
InitializeComponent();
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
Closed += OnFormClosed;
|
||||||
|
|
||||||
|
SizeChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
LocationChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
StateChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(_appConfig.DirectoriesConfig.LabelsDirectory))
|
||||||
|
Directory.CreateDirectory(_appConfig.DirectoriesConfig.LabelsDirectory);
|
||||||
|
if (!Directory.Exists(_appConfig.DirectoriesConfig.ImagesDirectory))
|
||||||
|
Directory.CreateDirectory(_appConfig.DirectoriesConfig.ImagesDirectory);
|
||||||
|
if (!Directory.Exists(_appConfig.DirectoriesConfig.ResultsDirectory))
|
||||||
|
Directory.CreateDirectory(_appConfig.DirectoriesConfig.ResultsDirectory);
|
||||||
|
|
||||||
|
|
||||||
|
Left = _appConfig.WindowConfig.WindowLocation.X;
|
||||||
|
Top = _appConfig.WindowConfig.WindowLocation.Y;
|
||||||
|
Width = _appConfig.WindowConfig.WindowSize.Width;
|
||||||
|
Height = _appConfig.WindowConfig.WindowSize.Height;
|
||||||
|
|
||||||
|
if (_appConfig.WindowConfig.FullScreen)
|
||||||
|
WindowState = WindowState.Maximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveUserSettings()
|
||||||
|
{
|
||||||
|
await ThrottleExt.Throttle(() =>
|
||||||
|
{
|
||||||
|
_configUpdater.Save(_appConfig);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFormClosed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_configUpdater.Save(_appConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
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> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Security;
|
||||||
|
using System.Text;
|
||||||
|
using Azaion.Suite.Services.DTO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Azaion.Suite.Services;
|
||||||
|
|
||||||
|
public class AzaionApiClient(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
const string JSON_MEDIA = "application/json";
|
||||||
|
|
||||||
|
private string Email { get; set; } = null!;
|
||||||
|
private SecureString Password { get; set; } = new();
|
||||||
|
private string JwtToken { get; set; } = null!;
|
||||||
|
|
||||||
|
public void Login(string email, string password)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password))
|
||||||
|
throw new Exception("Email or password is empty!");
|
||||||
|
|
||||||
|
Email = email;
|
||||||
|
Password = password.ToSecureString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> GetResource(string password, HardwareInfo hardware, ResourceEnum resourceEnum)
|
||||||
|
{
|
||||||
|
var response = await Send(httpClient, new HttpRequestMessage(HttpMethod.Post, "/resources/get")
|
||||||
|
{
|
||||||
|
Content = new StringContent(JsonConvert.SerializeObject(new { password, hardware, resourceEnum }), Encoding.UTF8, JSON_MEDIA)
|
||||||
|
});
|
||||||
|
return await response.Content.ReadAsStreamAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> Authorize()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Email) || Password.Length == 0)
|
||||||
|
throw new Exception("Email or password is empty! Please do Login first!");
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
email = Email,
|
||||||
|
password = Password.ToRealString()
|
||||||
|
};
|
||||||
|
var response = await httpClient.PostAsync(
|
||||||
|
"login",
|
||||||
|
new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, JSON_MEDIA));
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new Exception($"Login failed: {response.StatusCode}");
|
||||||
|
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
var result = JsonConvert.DeserializeObject<LoginResponse>(responseData);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(result?.Token))
|
||||||
|
throw new Exception("JWT Token not found in response");
|
||||||
|
|
||||||
|
return result.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseMessage> Send(HttpClient client, HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(JwtToken))
|
||||||
|
JwtToken = await Authorize();
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken);
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
|
||||||
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
JwtToken = await Authorize();
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken);
|
||||||
|
response = await client.SendAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
return response;
|
||||||
|
|
||||||
|
var result = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new Exception($"Failed: {response.StatusCode}! Result: {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Azaion.Suite.Services.DTO;
|
||||||
|
|
||||||
|
public enum ResourceEnum
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
AnnotatorDll = 10,
|
||||||
|
AIModelRKNN = 20,
|
||||||
|
AIModelONNX = 30,
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Azaion.Suite.Services;
|
||||||
|
|
||||||
|
public interface IHardwareService
|
||||||
|
{
|
||||||
|
Task<HardwareInfo> GetHardware();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HardwareService : IHardwareService
|
||||||
|
{
|
||||||
|
private const string WIN32_GET_HARDWARE_COMMAND =
|
||||||
|
"wmic OS get TotalVisibleMemorySize /Value && " +
|
||||||
|
"wmic CPU get Name /Value && " +
|
||||||
|
"wmic path Win32_VideoController get Name /Value";
|
||||||
|
|
||||||
|
private const string UNIX_GET_HARDWARE_COMMAND =
|
||||||
|
"/bin/bash -c \"free -g | grep Mem: | awk '{print $2}' && " +
|
||||||
|
"lscpu | grep 'Model name:' | cut -d':' -f2 && " +
|
||||||
|
"lspci | grep VGA | cut -d':' -f3\"";
|
||||||
|
|
||||||
|
public async Task<HardwareInfo> GetHardware()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var output = await RunCommand(Environment.OSVersion.Platform == PlatformID.Win32NT
|
||||||
|
? WIN32_GET_HARDWARE_COMMAND
|
||||||
|
: UNIX_GET_HARDWARE_COMMAND);
|
||||||
|
|
||||||
|
var lines = output
|
||||||
|
.Replace("TotalVisibleMemorySize=", "")
|
||||||
|
.Replace("Name=", "")
|
||||||
|
.Replace(" ", " ")
|
||||||
|
.Trim()
|
||||||
|
.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
var memoryStr = "Unknown RAM";
|
||||||
|
if (lines.Length > 0)
|
||||||
|
{
|
||||||
|
memoryStr = lines[0];
|
||||||
|
if (int.TryParse(memoryStr, out var memKb))
|
||||||
|
memoryStr = $"{Math.Round(memKb / 1024.0 / 1024.0)} Gb";
|
||||||
|
}
|
||||||
|
|
||||||
|
var hardwareInfo = new HardwareInfo
|
||||||
|
{
|
||||||
|
Memory = memoryStr,
|
||||||
|
CPU = lines.Length > 1 && string.IsNullOrEmpty(lines[1])
|
||||||
|
? "Unknown RAM"
|
||||||
|
: lines[1],
|
||||||
|
GPU = lines.Length > 2 && string.IsNullOrEmpty(lines[2])
|
||||||
|
? "Unknown GPU"
|
||||||
|
: lines[2]
|
||||||
|
};
|
||||||
|
hardwareInfo.Hash = ToHash($"Azaion_{MacAddress()}_{hardwareInfo.CPU}_{hardwareInfo.GPU}");
|
||||||
|
return hardwareInfo;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MacAddress()
|
||||||
|
{
|
||||||
|
var macAddress = NetworkInterface
|
||||||
|
.GetAllNetworkInterfaces()
|
||||||
|
.Where(nic => nic.OperationalStatus == OperationalStatus.Up)
|
||||||
|
.Select(nic => nic.GetPhysicalAddress().ToString())
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return macAddress ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> RunCommand(string command)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var process = new Process();
|
||||||
|
process.StartInfo.FileName = Environment.OSVersion.Platform == PlatformID.Unix ? "/bin/bash" : "cmd.exe";
|
||||||
|
process.StartInfo.Arguments = Environment.OSVersion.Platform == PlatformID.Unix
|
||||||
|
? $"-c \"{command}\""
|
||||||
|
: $"/c {command}";
|
||||||
|
process.StartInfo.RedirectStandardOutput = true;
|
||||||
|
process.StartInfo.UseShellExecute = false;
|
||||||
|
process.StartInfo.CreateNoWindow = true;
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
var result = await process.StandardOutput.ReadToEndAsync();
|
||||||
|
await process.WaitForExitAsync();
|
||||||
|
|
||||||
|
return result.Trim();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToHash(string str) =>
|
||||||
|
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Azaion.Suite.Services;
|
||||||
|
|
||||||
|
public static class Security
|
||||||
|
{
|
||||||
|
private const int BUFFER_SIZE = 524288; // 512 KB buffer size
|
||||||
|
|
||||||
|
public static string ToHash(this string str) =>
|
||||||
|
Convert.ToBase64String(SHA384.HashData(Encoding.UTF8.GetBytes(str)));
|
||||||
|
|
||||||
|
public static string MakeEncryptionKey(string email, string password, string? hardwareHash) =>
|
||||||
|
$"{email}-{password}-{hardwareHash}-#%@AzaionKey@%#---".ToHash();
|
||||||
|
|
||||||
|
public static SecureString ToSecureString(this string str)
|
||||||
|
{
|
||||||
|
var secureString = new SecureString();
|
||||||
|
foreach (var c in str.ToCharArray())
|
||||||
|
secureString.AppendChar(c);
|
||||||
|
|
||||||
|
return secureString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ToRealString(this SecureString value)
|
||||||
|
{
|
||||||
|
var valuePtr = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
|
||||||
|
return Marshal.PtrToStringUni(valuePtr);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static async Task EncryptTo(this Stream stream, Stream toStream, string key, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (stream is { CanRead: false }) throw new ArgumentNullException(nameof(stream));
|
||||||
|
if (key is not { Length: > 0 }) throw new ArgumentNullException(nameof(key));
|
||||||
|
|
||||||
|
using var aes = Aes.Create();
|
||||||
|
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
|
||||||
|
aes.GenerateIV();
|
||||||
|
|
||||||
|
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
|
||||||
|
await using var cs = new CryptoStream(toStream, encryptor, CryptoStreamMode.Write, leaveOpen: true);
|
||||||
|
|
||||||
|
// Prepend IV to the encrypted data
|
||||||
|
await toStream.WriteAsync(aes.IV.AsMemory(0, aes.IV.Length), cancellationToken);
|
||||||
|
|
||||||
|
var buffer = new byte[BUFFER_SIZE];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await stream.ReadAsync(buffer, cancellationToken)) > 0)
|
||||||
|
await cs.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DecryptTo(this Stream encryptedStream, Stream toStream, string key, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var aes = Aes.Create();
|
||||||
|
aes.Key = SHA256.HashData(Encoding.UTF8.GetBytes(key));
|
||||||
|
|
||||||
|
// Read the IV from the start of the input stream
|
||||||
|
var iv = new byte[aes.BlockSize / 8];
|
||||||
|
_ = await encryptedStream.ReadAsync(iv, cancellationToken);
|
||||||
|
aes.IV = iv;
|
||||||
|
|
||||||
|
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||||||
|
await using var cryptoStream = new CryptoStream(encryptedStream, decryptor, CryptoStreamMode.Read, leaveOpen: true);
|
||||||
|
|
||||||
|
// Read and write in chunks
|
||||||
|
var buffer = new byte[BUFFER_SIZE];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await cryptoStream.ReadAsync(buffer, cancellationToken)) > 0)
|
||||||
|
await toStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"ApiConfig": {
|
||||||
|
"Url": "https://api.azaion.com",
|
||||||
|
"TimeoutSeconds": 20,
|
||||||
|
"RetryCount": 3
|
||||||
|
},
|
||||||
|
"LocalFilesConfig": {
|
||||||
|
"DllPath": "AzaionSuite"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<RootNamespace>Azaion.Annotator.Test</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Azaion.Annotator\Azaion.Annotator.csproj" />
|
<ProjectReference Include="..\Azaion.Annotator\Azaion.Annotator.csproj" />
|
||||||
|
<ProjectReference Include="..\Azaion.Suite\Azaion.Suite.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Common.DTO;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Azaion.Annotator.Test;
|
namespace Azaion.Annotator.Test;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Azaion.Suite;
|
||||||
|
using Azaion.Suite.Services;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.Test;
|
||||||
|
|
||||||
|
public class HardwareServiceTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task GetHardware_Test()
|
||||||
|
{
|
||||||
|
var hardwareService = new HardwareService();
|
||||||
|
var hw = await hardwareService.GetHardware();
|
||||||
|
Console.WriteLine(hw);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user