mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 14:46:31 +00:00
rework to Azaion.Suite
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<DataGrid x:Class="Azaion.Common.Controls.AnnotationClasses"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300"
|
||||
Background="Black"
|
||||
RowBackground="#252525"
|
||||
Foreground="White"
|
||||
RowHeaderWidth="0"
|
||||
Padding="2 0 0 0"
|
||||
AutoGenerateColumns="False"
|
||||
SelectionMode="Single"
|
||||
CellStyle="{DynamicResource DataGridCellStyle1}"
|
||||
IsReadOnly="True"
|
||||
CanUserResizeRows="False"
|
||||
CanUserResizeColumns="False">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn
|
||||
Width="50"
|
||||
Header="Клавіша"
|
||||
CanUserSort="False">
|
||||
<DataGridTemplateColumn.HeaderStyle>
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#252525"></Setter>
|
||||
</Style>
|
||||
</DataGridTemplateColumn.HeaderStyle>
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{Binding Path=ColorBrush}">
|
||||
<TextBlock Text="{Binding Path=ClassNumber}"></TextBlock>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn
|
||||
Width="*"
|
||||
Header="Назва"
|
||||
Binding="{Binding Path=Name}"
|
||||
CanUserSort="False">
|
||||
<DataGridTextColumn.HeaderStyle>
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#252525"></Setter>
|
||||
</Style>
|
||||
</DataGridTextColumn.HeaderStyle>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Azaion.Common.Controls;
|
||||
|
||||
public partial class AnnotationClasses
|
||||
{
|
||||
public AnnotationClasses()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Azaion.Common.DTO;
|
||||
using Label = System.Windows.Controls.Label;
|
||||
|
||||
namespace Azaion.Common.Controls;
|
||||
|
||||
public class AnnotationControl : Border
|
||||
{
|
||||
private readonly Action<object, MouseButtonEventArgs> _resizeStart;
|
||||
private const double RESIZE_RECT_SIZE = 9;
|
||||
|
||||
private readonly Grid _grid;
|
||||
private readonly TextBlock _classNameLabel;
|
||||
private readonly Label _probabilityLabel;
|
||||
public TimeSpan? Time { get; set; }
|
||||
|
||||
private AnnotationClass _annotationClass = null!;
|
||||
public AnnotationClass AnnotationClass
|
||||
{
|
||||
get => _annotationClass;
|
||||
set
|
||||
{
|
||||
_grid.Background = value.ColorBrush;
|
||||
_probabilityLabel.Background = value.ColorBrush;
|
||||
_classNameLabel.Text = value.Name;
|
||||
_annotationClass = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Rectangle _selectionFrame;
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
_selectionFrame.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
|
||||
_isSelected = value;
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationControl(AnnotationClass annotationClass, TimeSpan? time, Action<object, MouseButtonEventArgs> resizeStart, double? probability = null)
|
||||
{
|
||||
Time = time;
|
||||
_resizeStart = resizeStart;
|
||||
_classNameLabel = new TextBlock
|
||||
{
|
||||
Text = annotationClass.Name,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(0, 15, 0, 0),
|
||||
FontSize = 14,
|
||||
Cursor = Cursors.SizeAll
|
||||
};
|
||||
_probabilityLabel = new Label
|
||||
{
|
||||
Content = probability.HasValue ? $"{probability.Value:F0}%" : string.Empty,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(0, -32, 0, 0),
|
||||
FontSize = 16,
|
||||
Visibility = Visibility.Visible
|
||||
};
|
||||
_selectionFrame = new Rectangle
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
Stroke = new SolidColorBrush(Colors.Black),
|
||||
StrokeThickness = 2,
|
||||
Visibility = Visibility.Collapsed
|
||||
};
|
||||
|
||||
_grid = new Grid
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
Children =
|
||||
{
|
||||
_selectionFrame,
|
||||
_classNameLabel,
|
||||
AddRect("rLT", HorizontalAlignment.Left, VerticalAlignment.Top, Cursors.SizeNWSE),
|
||||
AddRect("rCT", HorizontalAlignment.Center, VerticalAlignment.Top, Cursors.SizeNS),
|
||||
AddRect("rRT", HorizontalAlignment.Right, VerticalAlignment.Top, Cursors.SizeNESW),
|
||||
AddRect("rLC", HorizontalAlignment.Left, VerticalAlignment.Center, Cursors.SizeWE),
|
||||
AddRect("rRC", HorizontalAlignment.Right, VerticalAlignment.Center, Cursors.SizeWE),
|
||||
AddRect("rLB", HorizontalAlignment.Left, VerticalAlignment.Bottom, Cursors.SizeNESW),
|
||||
AddRect("rCB", HorizontalAlignment.Center, VerticalAlignment.Bottom, Cursors.SizeNS),
|
||||
AddRect("rRB", HorizontalAlignment.Right, VerticalAlignment.Bottom, Cursors.SizeNWSE)
|
||||
}
|
||||
};
|
||||
if (probability.HasValue)
|
||||
_grid.Children.Add(_probabilityLabel);
|
||||
Child = _grid;
|
||||
Cursor = Cursors.SizeAll;
|
||||
AnnotationClass = annotationClass;
|
||||
}
|
||||
|
||||
//small corners
|
||||
private Rectangle AddRect(string name, HorizontalAlignment ha, VerticalAlignment va, Cursor crs)
|
||||
{
|
||||
var rect = new Rectangle() // small rectangles at the corners and sides
|
||||
{
|
||||
HorizontalAlignment = ha,
|
||||
VerticalAlignment = va,
|
||||
Width = RESIZE_RECT_SIZE,
|
||||
Height = RESIZE_RECT_SIZE,
|
||||
Stroke = new SolidColorBrush(Color.FromArgb(230, 40, 40, 40)), // small rectangles color
|
||||
Fill = new SolidColorBrush(Color.FromArgb(1, 255, 255, 255)),
|
||||
Cursor = crs,
|
||||
Name = name,
|
||||
};
|
||||
rect.MouseDown += (sender, args) => _resizeStart(sender, args);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public CanvasLabel Info => new(AnnotationClass.Id, Canvas.GetLeft(this), Canvas.GetTop(this), Width, Height);
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Azaion.Annotator.DTO;
|
||||
using Azaion.Common.DTO;
|
||||
using MediatR;
|
||||
using Color = System.Windows.Media.Color;
|
||||
using Rectangle = System.Windows.Shapes.Rectangle;
|
||||
|
||||
namespace Azaion.Common.Controls;
|
||||
|
||||
public class CanvasEditor : Canvas
|
||||
{
|
||||
private Point _lastPos;
|
||||
public SelectionState SelectionState { get; set; } = SelectionState.None;
|
||||
|
||||
private readonly Rectangle _newAnnotationRect;
|
||||
private Point _newAnnotationStartPos;
|
||||
|
||||
private readonly Line _horizontalLine;
|
||||
private readonly Line _verticalLine;
|
||||
private readonly TextBlock _classNameHint;
|
||||
|
||||
private Rectangle _curRec = new();
|
||||
private AnnotationControl _curAnn = null!;
|
||||
|
||||
private const int MIN_SIZE = 20;
|
||||
private readonly TimeSpan _viewThreshold = TimeSpan.FromMilliseconds(400);
|
||||
|
||||
public IMediator Mediator { get; set; } = null!;
|
||||
|
||||
public static readonly DependencyProperty GetTimeFuncProp =
|
||||
DependencyProperty.Register(
|
||||
nameof(GetTimeFunc),
|
||||
typeof(Func<TimeSpan?>),
|
||||
typeof(CanvasEditor),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public Func<TimeSpan?> GetTimeFunc
|
||||
{
|
||||
get => (Func<TimeSpan?>)GetValue(GetTimeFuncProp);
|
||||
set => SetValue(GetTimeFuncProp, value);
|
||||
}
|
||||
|
||||
private AnnotationClass _currentAnnClass = null!;
|
||||
public AnnotationClass CurrentAnnClass
|
||||
{
|
||||
get => _currentAnnClass;
|
||||
set
|
||||
{
|
||||
_verticalLine.Stroke = value.ColorBrush;
|
||||
_verticalLine.Fill = value.ColorBrush;
|
||||
_horizontalLine.Stroke = value.ColorBrush;
|
||||
_horizontalLine.Fill = value.ColorBrush;
|
||||
_classNameHint.Text = value.Name;
|
||||
|
||||
_newAnnotationRect.Stroke = value.ColorBrush;
|
||||
_newAnnotationRect.Fill = value.ColorBrush;
|
||||
_currentAnnClass = value;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<AnnotationControl> CurrentAnns = new();
|
||||
|
||||
public CanvasEditor()
|
||||
{
|
||||
_horizontalLine = new Line
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
Stroke = new SolidColorBrush(Colors.Blue),
|
||||
Fill = new SolidColorBrush(Colors.Blue),
|
||||
StrokeDashArray = [5],
|
||||
StrokeThickness = 2
|
||||
};
|
||||
_verticalLine = new Line
|
||||
{
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
Stroke = new SolidColorBrush(Colors.Blue),
|
||||
Fill = new SolidColorBrush(Colors.Blue),
|
||||
StrokeDashArray = [5],
|
||||
StrokeThickness = 2
|
||||
};
|
||||
_classNameHint = new TextBlock
|
||||
{
|
||||
Text = CurrentAnnClass?.Name ?? "asd",
|
||||
Foreground = new SolidColorBrush(Colors.Black),
|
||||
Cursor = Cursors.Arrow,
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold
|
||||
};
|
||||
_newAnnotationRect = new Rectangle
|
||||
{
|
||||
Name = "selector",
|
||||
Height = 0,
|
||||
Width = 0,
|
||||
Stroke = new SolidColorBrush(Colors.Gray),
|
||||
Fill = new SolidColorBrush(Color.FromArgb(128, 128, 128, 128)),
|
||||
};
|
||||
|
||||
KeyDown += (_, args) =>
|
||||
{
|
||||
Console.WriteLine($"pressed {args.Key}");
|
||||
};
|
||||
MouseDown += CanvasMouseDown;
|
||||
MouseMove += CanvasMouseMove;
|
||||
MouseUp += CanvasMouseUp;
|
||||
SizeChanged += CanvasResized;
|
||||
Cursor = Cursors.Cross;
|
||||
|
||||
Children.Add(_newAnnotationRect);
|
||||
Children.Add(_horizontalLine);
|
||||
Children.Add(_verticalLine);
|
||||
Children.Add(_classNameHint);
|
||||
|
||||
Loaded += Init;
|
||||
}
|
||||
|
||||
private void Init(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_horizontalLine.X1 = 0;
|
||||
_horizontalLine.X2 = ActualWidth;
|
||||
_verticalLine.Y1 = 0;
|
||||
_verticalLine.Y2 = ActualHeight;
|
||||
}
|
||||
|
||||
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ClearSelections();
|
||||
NewAnnotationStart(sender, e);
|
||||
}
|
||||
|
||||
private void CanvasMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
var pos = e.GetPosition(this);
|
||||
_horizontalLine.Y1 = _horizontalLine.Y2 = pos.Y;
|
||||
_verticalLine.X1 = _verticalLine.X2 = pos.X;
|
||||
SetLeft(_classNameHint, pos.X + 10);
|
||||
SetTop(_classNameHint, pos.Y - 30);
|
||||
|
||||
if (e.LeftButton != MouseButtonState.Pressed)
|
||||
return;
|
||||
if (SelectionState == SelectionState.NewAnnCreating)
|
||||
NewAnnotationCreatingMove(sender, e);
|
||||
|
||||
if (SelectionState == SelectionState.AnnResizing)
|
||||
AnnotationResizeMove(sender, e);
|
||||
|
||||
if (SelectionState == SelectionState.AnnMoving)
|
||||
AnnotationPositionMove(sender, e);
|
||||
}
|
||||
|
||||
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (SelectionState == SelectionState.NewAnnCreating)
|
||||
CreateAnnotation(e.GetPosition(this));
|
||||
|
||||
SelectionState = SelectionState.None;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void CanvasResized(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
_horizontalLine.X2 = e.NewSize.Width;
|
||||
_verticalLine.Y2 = e.NewSize.Height;
|
||||
}
|
||||
|
||||
#region Annotation Resizing & Moving
|
||||
|
||||
private void AnnotationResizeStart(object sender, MouseEventArgs e)
|
||||
{
|
||||
SelectionState = SelectionState.AnnResizing;
|
||||
_lastPos = e.GetPosition(this);
|
||||
_curRec = (Rectangle)sender;
|
||||
_curAnn = (AnnotationControl)((Grid)_curRec.Parent).Parent;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void AnnotationResizeMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (SelectionState != SelectionState.AnnResizing)
|
||||
return;
|
||||
|
||||
var currentPos = e.GetPosition(this);
|
||||
|
||||
var x = GetLeft(_curAnn);
|
||||
var y = GetTop(_curAnn);
|
||||
var offsetX = currentPos.X - _lastPos.X;
|
||||
var offsetY = currentPos.Y - _lastPos.Y;
|
||||
switch (_curRec.HorizontalAlignment, _curRec.VerticalAlignment)
|
||||
{
|
||||
case (HorizontalAlignment.Left, VerticalAlignment.Top):
|
||||
_curAnn.Width = Math.Max(MIN_SIZE, _curAnn.Width - offsetX);
|
||||
_curAnn.Height = Math.Max(MIN_SIZE, _curAnn.Height - offsetY);
|
||||
SetLeft(_curAnn, x + offsetX);
|
||||
SetTop(_curAnn, y + offsetY);
|
||||
break;
|
||||
case (HorizontalAlignment.Center, VerticalAlignment.Top):
|
||||
_curAnn.Height = Math.Max(MIN_SIZE, _curAnn.Height - offsetY);
|
||||
SetTop(_curAnn, y + offsetY);
|
||||
break;
|
||||
case (HorizontalAlignment.Right, VerticalAlignment.Top):
|
||||
_curAnn.Width = Math.Max(MIN_SIZE, _curAnn.Width + offsetX);
|
||||
_curAnn.Height = Math.Max(MIN_SIZE, _curAnn.Height - offsetY);
|
||||
SetTop(_curAnn, y + offsetY);
|
||||
break;
|
||||
|
||||
case (HorizontalAlignment.Left, VerticalAlignment.Center):
|
||||
_curAnn.Width = Math.Max(MIN_SIZE, _curAnn.Width - offsetX);
|
||||
SetLeft(_curAnn, x + offsetX);
|
||||
break;
|
||||
case (HorizontalAlignment.Right, VerticalAlignment.Center):
|
||||
_curAnn.Width = Math.Max(MIN_SIZE, _curAnn.Width + offsetX);
|
||||
break;
|
||||
|
||||
case (HorizontalAlignment.Left, VerticalAlignment.Bottom):
|
||||
_curAnn.Width = Math.Max(MIN_SIZE, _curAnn.Width - offsetX);
|
||||
_curAnn.Height = Math.Max(MIN_SIZE, _curAnn.Height + offsetY);
|
||||
SetLeft(_curAnn, x + offsetX);
|
||||
break;
|
||||
case (HorizontalAlignment.Center, VerticalAlignment.Bottom):
|
||||
_curAnn.Height = Math.Max(MIN_SIZE, _curAnn.Height + offsetY);
|
||||
break;
|
||||
case (HorizontalAlignment.Right, VerticalAlignment.Bottom):
|
||||
_curAnn.Width = Math.Max(MIN_SIZE, _curAnn.Width + offsetX);
|
||||
_curAnn.Height = Math.Max(MIN_SIZE, _curAnn.Height + offsetY);
|
||||
break;
|
||||
}
|
||||
_lastPos = currentPos;
|
||||
}
|
||||
|
||||
private void AnnotationPositionStart(object sender, MouseEventArgs e)
|
||||
{
|
||||
_lastPos = e.GetPosition(this);
|
||||
_curAnn = (AnnotationControl)sender;
|
||||
|
||||
if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
ClearSelections();
|
||||
|
||||
_curAnn.IsSelected = true;
|
||||
|
||||
SelectionState = SelectionState.AnnMoving;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void AnnotationPositionMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (SelectionState != SelectionState.AnnMoving)
|
||||
return;
|
||||
|
||||
var currentPos = e.GetPosition(this);
|
||||
var offsetX = currentPos.X - _lastPos.X;
|
||||
var offsetY = currentPos.Y - _lastPos.Y;
|
||||
|
||||
SetLeft(_curAnn, GetLeft(_curAnn) + offsetX);
|
||||
SetTop(_curAnn, GetTop(_curAnn) + offsetY);
|
||||
_lastPos = currentPos;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NewAnnotation
|
||||
|
||||
private void NewAnnotationStart(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_newAnnotationStartPos = e.GetPosition(this);
|
||||
|
||||
SetLeft(_newAnnotationRect, _newAnnotationStartPos.X);
|
||||
SetTop(_newAnnotationRect, _newAnnotationStartPos.Y);
|
||||
_newAnnotationRect.MouseMove += NewAnnotationCreatingMove;
|
||||
|
||||
SelectionState = SelectionState.NewAnnCreating;
|
||||
}
|
||||
|
||||
private void NewAnnotationCreatingMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (SelectionState != SelectionState.NewAnnCreating)
|
||||
return;
|
||||
|
||||
var currentPos = e.GetPosition(this);
|
||||
var diff = currentPos - _newAnnotationStartPos;
|
||||
|
||||
_newAnnotationRect.Height = Math.Abs(diff.Y);
|
||||
_newAnnotationRect.Width = Math.Abs(diff.X);
|
||||
|
||||
if (diff.X < 0)
|
||||
SetLeft(_newAnnotationRect, currentPos.X);
|
||||
if (diff.Y < 0)
|
||||
SetTop(_newAnnotationRect, currentPos.Y);
|
||||
}
|
||||
|
||||
private void CreateAnnotation(Point endPos)
|
||||
{
|
||||
_newAnnotationRect.Width = 0;
|
||||
_newAnnotationRect.Height = 0;
|
||||
var width = Math.Abs(endPos.X - _newAnnotationStartPos.X);
|
||||
var height = Math.Abs(endPos.Y - _newAnnotationStartPos.Y);
|
||||
if (width < MIN_SIZE || height < MIN_SIZE)
|
||||
return;
|
||||
|
||||
var time = GetTimeFunc();
|
||||
CreateAnnotation(CurrentAnnClass, time, new CanvasLabel
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
X = Math.Min(endPos.X, _newAnnotationStartPos.X),
|
||||
Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y)
|
||||
});
|
||||
}
|
||||
|
||||
public AnnotationControl CreateAnnotation(AnnotationClass annClass, TimeSpan? time, CanvasLabel canvasLabel)
|
||||
{
|
||||
var annotationControl = new AnnotationControl(annClass, time, AnnotationResizeStart, canvasLabel.Probability)
|
||||
{
|
||||
Width = canvasLabel.Width,
|
||||
Height = canvasLabel.Height
|
||||
};
|
||||
annotationControl.MouseDown += AnnotationPositionStart;
|
||||
SetLeft(annotationControl, canvasLabel.X );
|
||||
SetTop(annotationControl, canvasLabel.Y);
|
||||
Children.Add(annotationControl);
|
||||
CurrentAnns.Add(annotationControl);
|
||||
_newAnnotationRect.Fill = new SolidColorBrush(annClass.Color);
|
||||
return annotationControl;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void RemoveAnnotations(IEnumerable<AnnotationControl> listToRemove)
|
||||
{
|
||||
foreach (var ann in listToRemove)
|
||||
{
|
||||
Children.Remove(ann);
|
||||
CurrentAnns.Remove(ann);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAllAnns()
|
||||
{
|
||||
foreach (var ann in CurrentAnns)
|
||||
Children.Remove(ann);
|
||||
CurrentAnns.Clear();
|
||||
}
|
||||
|
||||
public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected).ToList());
|
||||
|
||||
private void ClearSelections()
|
||||
{
|
||||
foreach (var ann in CurrentAnns)
|
||||
ann.IsSelected = false;
|
||||
}
|
||||
|
||||
public void ClearExpiredAnnotations(TimeSpan time)
|
||||
{
|
||||
var expiredAnns = CurrentAnns.Where(x =>
|
||||
x.Time.HasValue &&
|
||||
Math.Abs((time - x.Time.Value).TotalMilliseconds) > _viewThreshold.TotalMilliseconds)
|
||||
.ToList();
|
||||
RemoveAnnotations(expiredAnns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Azaion.Annotator.Controls
|
||||
{
|
||||
public class UpdatableProgressBar : ProgressBar
|
||||
{
|
||||
public delegate void ValueChange(double oldValue, double newValue);
|
||||
|
||||
public new event ValueChange? ValueChanged;
|
||||
|
||||
public UpdatableProgressBar() : base()
|
||||
{
|
||||
MouseDown += OnMouseDown;
|
||||
MouseMove += OnMouseMove;
|
||||
}
|
||||
|
||||
private double SetProgressBarValue(double mousePos)
|
||||
{
|
||||
Value = Minimum;
|
||||
var pbValue = mousePos / ActualWidth * Maximum;
|
||||
ValueChanged?.Invoke(Value, pbValue);
|
||||
return pbValue;
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Value = SetProgressBarValue(e.GetPosition(this).X);
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
Value = SetProgressBarValue(e.GetPosition(this).X);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows.Media;
|
||||
using Azaion.Common.Extensions;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public class AnnotationClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; } = null!;
|
||||
public string ShortName { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public Color Color => Id.ToColor();
|
||||
|
||||
[JsonIgnore]
|
||||
public int ClassNumber => Id + 1;
|
||||
|
||||
[JsonIgnore]
|
||||
public SolidColorBrush ColorBrush => new(Color);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Size = System.Windows.Size;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public abstract class Label
|
||||
{
|
||||
[JsonProperty(PropertyName = "cl")] public int ClassNumber { get; set; }
|
||||
|
||||
protected Label()
|
||||
{
|
||||
}
|
||||
|
||||
protected Label(int classNumber)
|
||||
{
|
||||
ClassNumber = classNumber;
|
||||
}
|
||||
}
|
||||
|
||||
public class CanvasLabel : Label
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public double? Probability { get; }
|
||||
|
||||
public CanvasLabel()
|
||||
{
|
||||
}
|
||||
|
||||
public CanvasLabel(int classNumber, double x, double y, double width, double height, double? probability = null) : base(classNumber)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Probability = probability;
|
||||
}
|
||||
|
||||
public CanvasLabel(YoloLabel label, Size canvasSize, Size videoSize, double? probability = null)
|
||||
{
|
||||
var cw = canvasSize.Width;
|
||||
var ch = canvasSize.Height;
|
||||
var canvasAr = cw / ch;
|
||||
var videoAr = videoSize.Width / videoSize.Height;
|
||||
|
||||
ClassNumber = label.ClassNumber;
|
||||
|
||||
var left = label.CenterX - label.Width / 2;
|
||||
var top = label.CenterY - label.Height / 2;
|
||||
|
||||
if (videoAr > canvasAr) //100% width
|
||||
{
|
||||
var realHeight = cw / videoAr; //real video height in pixels on canvas
|
||||
var blackStripHeight = (ch - realHeight) / 2.0; //height of black strips at the top and bottom
|
||||
|
||||
X = left * cw;
|
||||
Y = top * realHeight + blackStripHeight;
|
||||
Width = label.Width * cw;
|
||||
Height = label.Height * realHeight;
|
||||
}
|
||||
else //100% height
|
||||
{
|
||||
var realWidth = ch * videoAr; //real video width in pixels on canvas
|
||||
var blackStripWidth = (cw - realWidth) / 2.0; //height of black strips at the top and bottom
|
||||
|
||||
X = left * realWidth + blackStripWidth;
|
||||
Y = top * ch;
|
||||
Width = label.Width * realWidth;
|
||||
Height = label.Height * ch;
|
||||
}
|
||||
Probability = probability;
|
||||
}
|
||||
}
|
||||
|
||||
public class YoloLabel : Label
|
||||
{
|
||||
[JsonProperty(PropertyName = "x")] public double CenterX { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "y")] public double CenterY { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "w")] public double Width { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "h")] public double Height { get; set; }
|
||||
|
||||
public YoloLabel()
|
||||
{
|
||||
}
|
||||
|
||||
public YoloLabel(int classNumber, double centerX, double centerY, double width, double height) : base(classNumber)
|
||||
{
|
||||
CenterX = centerX;
|
||||
CenterY = centerY;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public RectangleF ToRectangle() =>
|
||||
new((float)(CenterX - Width / 2.0), (float)(CenterY - Height / 2.0), (float)Width, (float)Height);
|
||||
|
||||
public YoloLabel(CanvasLabel canvasLabel, Size canvasSize, Size videoSize)
|
||||
{
|
||||
var cw = canvasSize.Width;
|
||||
var ch = canvasSize.Height;
|
||||
var canvasAr = cw / ch;
|
||||
var videoAr = videoSize.Width / videoSize.Height;
|
||||
|
||||
ClassNumber = canvasLabel.ClassNumber;
|
||||
|
||||
double left, top;
|
||||
if (videoAr > canvasAr) //100% width
|
||||
{
|
||||
left = canvasLabel.X / cw;
|
||||
Width = canvasLabel.Width / cw;
|
||||
var realHeight = cw / videoAr; //real video height in pixels on canvas
|
||||
var blackStripHeight = (ch - realHeight) / 2.0; //height of black strips at the top and bottom
|
||||
top = (canvasLabel.Y - blackStripHeight) / realHeight;
|
||||
Height = canvasLabel.Height / realHeight;
|
||||
}
|
||||
else //100% height
|
||||
{
|
||||
top = canvasLabel.Y / ch;
|
||||
Height = canvasLabel.Height / ch;
|
||||
var realWidth = ch * videoAr; //real video width in pixels on canvas
|
||||
var blackStripWidth = (cw - realWidth) / 2.0; //height of black strips at the top and bottom
|
||||
left = (canvasLabel.X - blackStripWidth) / realWidth;
|
||||
Width = canvasLabel.Width / realWidth;
|
||||
}
|
||||
|
||||
CenterX = left + Width / 2.0;
|
||||
CenterY = top + Height / 2.0;
|
||||
}
|
||||
|
||||
public static YoloLabel? Parse(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return null;
|
||||
|
||||
var strings = s.Replace(',', '.').Split(' ');
|
||||
if (strings.Length != 5)
|
||||
throw new Exception("Wrong labels format!");
|
||||
|
||||
var res = new YoloLabel
|
||||
{
|
||||
ClassNumber = int.Parse(strings[0], CultureInfo.InvariantCulture),
|
||||
CenterX = double.Parse(strings[1], CultureInfo.InvariantCulture),
|
||||
CenterY = double.Parse(strings[2], CultureInfo.InvariantCulture),
|
||||
Width = double.Parse(strings[3], CultureInfo.InvariantCulture),
|
||||
Height = double.Parse(strings[4], CultureInfo.InvariantCulture)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
public static async Task<List<YoloLabel>> ReadFromFile(string filename, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var str = await File.ReadAllTextAsync(filename, cancellationToken);
|
||||
|
||||
return str.Split('\n')
|
||||
.Select(Parse)
|
||||
.Where(ann => ann != null)
|
||||
.ToList()!;
|
||||
}
|
||||
|
||||
public static async Task WriteToFile(IEnumerable<YoloLabel> labels, string filename, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var labelsStr = string.Join(Environment.NewLine, labels.Select(x => x.ToString()));
|
||||
await File.WriteAllTextAsync(filename, labelsStr, cancellationToken);
|
||||
}
|
||||
|
||||
public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.');
|
||||
}
|
||||
|
||||
public class Detection : YoloLabel
|
||||
{
|
||||
public Detection(YoloLabel label, double? probability = null)
|
||||
{
|
||||
ClassNumber = label.ClassNumber;
|
||||
CenterX = label.CenterX;
|
||||
CenterY = label.CenterY;
|
||||
Height = label.Height;
|
||||
Width = label.Width;
|
||||
Probability = probability;
|
||||
}
|
||||
public double? Probability { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public class LabelInfo
|
||||
{
|
||||
[JsonProperty("c")] public List<int> Classes { get; set; } = null!;
|
||||
|
||||
[JsonProperty("d")] public DateTime ImageDateTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public enum PlaybackControlEnum
|
||||
{
|
||||
None = 0,
|
||||
Play = 1,
|
||||
Pause = 2,
|
||||
Stop = 3,
|
||||
PreviousFrame = 4,
|
||||
NextFrame = 5,
|
||||
SaveAnnotations = 6,
|
||||
RemoveSelectedAnns = 7,
|
||||
RemoveAllAnns = 8,
|
||||
TurnOffVolume = 9,
|
||||
TurnOnVolume = 10,
|
||||
Previous = 11,
|
||||
Next = 12,
|
||||
Close = 13
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Azaion.Annotator.DTO;
|
||||
|
||||
public enum SelectionState
|
||||
{
|
||||
None = 0,
|
||||
NewAnnCreating = 1,
|
||||
AnnResizing = 2,
|
||||
AnnMoving = 3
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Azaion.Common.DTO;
|
||||
|
||||
public enum WindowEnum
|
||||
{
|
||||
None = 0,
|
||||
Annotator = 10,
|
||||
DatasetExplorer = 20
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Azaion.Common.Extensions;
|
||||
|
||||
public static class ColorExtensions
|
||||
{
|
||||
public static Color ToColor(this int id)
|
||||
{
|
||||
var index = id % ColorValues.Length;
|
||||
var hex = index == -1
|
||||
? "#40DDDDDD"
|
||||
: $"#40{ColorValues[index]}";
|
||||
var color =(Color)ColorConverter.ConvertFromString(hex);
|
||||
return color;
|
||||
}
|
||||
|
||||
private static readonly string[] ColorValues =
|
||||
[
|
||||
"FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", "000000",
|
||||
"800000", "008000", "000080", "808000", "800080", "008080", "808080",
|
||||
"C00000", "00C000", "0000C0", "C0C000", "C000C0", "00C0C0", "C0C0C0",
|
||||
"400000", "004000", "000040", "404000", "400040", "004040", "404040",
|
||||
"200000", "002000", "000020", "202000", "200020", "002020", "202020",
|
||||
"600000", "006000", "000060", "606000", "600060", "006060", "606060",
|
||||
"A00000", "00A000", "0000A0", "A0A000", "A000A0", "00A0A0", "A0A0A0",
|
||||
"E00000", "00E000", "0000E0", "E0E000", "E000E0", "00E0E0", "E0E0E0"
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Azaion.Common.Extensions;
|
||||
|
||||
public class DenseDateTimeConverter : IsoDateTimeConverter
|
||||
{
|
||||
public DenseDateTimeConverter()
|
||||
{
|
||||
DateTimeFormat = "yy-MM-dd HH:mm:ss";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Azaion.Annotator.Extensions;
|
||||
|
||||
public static class DirectoryInfoExtensions
|
||||
{
|
||||
public static IEnumerable<FileInfo> GetFiles(this DirectoryInfo dir, params string[] searchExtensions) =>
|
||||
dir.GetFiles("*.*", SearchOption.AllDirectories)
|
||||
.Where(f => searchExtensions.Any(s => f.Name.Contains(s, StringComparison.CurrentCultureIgnoreCase))).ToList();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
namespace Azaion.Annotator.Extensions;
|
||||
|
||||
public class ParallelOptions
|
||||
{
|
||||
public int ProgressUpdateInterval { get; set; } = 100;
|
||||
public Func<int, Task> ProgressFn { get; set; } = null!;
|
||||
public double CpuUtilPercent { get; set; } = 100;
|
||||
}
|
||||
|
||||
public class ParallelExt
|
||||
{
|
||||
public static async Task ForEachAsync<T>(ICollection<T> source,
|
||||
Func<T, CancellationToken, ValueTask> processFn,
|
||||
ParallelOptions? parallelOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
parallelOptions ??= new ParallelOptions
|
||||
{
|
||||
CpuUtilPercent = 100,
|
||||
ProgressUpdateInterval = 100,
|
||||
ProgressFn = i =>
|
||||
{
|
||||
Console.WriteLine($"Processed {i} item by Task {Task.CurrentId}%");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
var threadsCount = (int)(Environment.ProcessorCount * parallelOptions.CpuUtilPercent / 100.0);
|
||||
|
||||
var processedCount = 0;
|
||||
var chunkSize = Math.Max(1, (int)(source.Count / (decimal)threadsCount));
|
||||
var chunks = source.Chunk(chunkSize).ToList();
|
||||
if (chunks.Count > threadsCount)
|
||||
{
|
||||
chunks[^2] = chunks[^2].Concat(chunks.Last()).ToArray();
|
||||
chunks.RemoveAt(chunks.Count - 1);
|
||||
}
|
||||
var progressUpdateLock = new SemaphoreSlim(1);
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
tasks.Add(await Task.Factory.StartNew(async () =>
|
||||
{
|
||||
foreach (var item in chunk)
|
||||
{
|
||||
await processFn(item, cancellationToken);
|
||||
Interlocked.Increment(ref processedCount);
|
||||
if (processedCount % parallelOptions.ProgressUpdateInterval == 0 && parallelOptions.ProgressFn != null)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await progressUpdateLock.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await parallelOptions.ProgressFn(processedCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
progressUpdateLock.Release();
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
}, TaskCreationOptions.LongRunning));
|
||||
}
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Azaion.Annotator.Extensions;
|
||||
|
||||
public static class ThrottleExt
|
||||
{
|
||||
private static bool _throttleOn;
|
||||
public static async Task Throttle(Func<Task> func, TimeSpan? throttleTime = null)
|
||||
{
|
||||
if (_throttleOn)
|
||||
return;
|
||||
|
||||
_throttleOn = true;
|
||||
await func();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500));
|
||||
_throttleOn = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user