rework to Azaion.Suite

This commit is contained in:
Alex Bezdieniezhnykh
2024-11-21 13:41:32 +02:00
parent 2cf69f4e4e
commit 5a592e9dbf
76 changed files with 1739 additions and 882 deletions
@@ -1,4 +1,5 @@
using MediatR;
using Azaion.Common.DTO;
using MediatR;
namespace Azaion.Annotator.DTO;
-22
View File
@@ -1,22 +0,0 @@
using System.Text.Json.Serialization;
using System.Windows.Media;
using Azaion.Annotator.Extensions;
namespace Azaion.Annotator.DTO;
public class AnnotationClass
{
public int Id { get; set; }
public string Name { get; set; }
public string ShortName { get; set; }
[JsonIgnore]
public Color Color => Id.ToColor();
[JsonIgnore]
public int ClassNumber => Id + 1;
[JsonIgnore]
public SolidColorBrush ColorBrush => new(Color);
}
+13 -47
View File
@@ -1,13 +1,14 @@
using System.Windows.Media;
using Azaion.Annotator.Extensions;
using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions;
using Newtonsoft.Json;
namespace Azaion.Annotator.DTO;
public class AnnotationResult
{
private readonly Config _config = null!;
[JsonProperty(PropertyName = "f")]
public string Image { get; set; } = null!;
@@ -18,61 +19,26 @@ public class AnnotationResult
public double Lon { get; set; }
public List<Detection> Detections { get; set; } = new();
#region For Display in the grid
#region For XAML Form
[JsonIgnore]
//For XAML Form
public string TimeStr => $"{Time:h\\:mm\\:ss}";
private List<int>? _detectionClasses = null!;
//For Form
[JsonIgnore]
public string ClassName
{
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;
}
}
public string ClassName { get; set; } = null!;
[JsonIgnore]
public Color ClassColor1 => GetAnnotationClass(0);
public Color ClassColor0 { get; set; }
[JsonIgnore]
public Color ClassColor2 => GetAnnotationClass(1);
public Color ClassColor1 { get; set; }
[JsonIgnore]
public Color ClassColor3 => GetAnnotationClass(2);
public Color ClassColor2 { get; set; }
[JsonIgnore]
public Color ClassColor4 => GetAnnotationClass(3);
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;
}
public Color ClassColor3 { get; set; }
#endregion
public AnnotationResult() { }
public AnnotationResult(TimeSpan time, string timeName, List<Detection> detections, Config config)
{
_config = config;
Detections = detections;
Time = time;
Image = $"{timeName}.jpg";
}
}
-151
View File
@@ -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);
}
}
+1 -20
View File
@@ -11,32 +11,13 @@ public class FormState
? ""
: Path.GetFileNameWithoutExtension(CurrentMedia.Name).Replace(" ", "");
public string CurrentMrl { get; set; }
public string CurrentMrl { get; set; } = null!;
public Size CurrentVideoSize { get; set; }
public TimeSpan CurrentVideoLength { get; set; }
public TimeSpan? BackgroundTime { get; set; }
public int CurrentVolume { get; set; } = 100;
public ObservableCollection<AnnotationResult> AnnotationResults { get; set; } = [];
public WindowsEnum ActiveWindow { get; set; }
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);
}
}
-189
View File
@@ -1,189 +0,0 @@
using System.Drawing;
using System.Globalization;
using System.IO;
using Newtonsoft.Json;
using Size = System.Windows.Size;
namespace Azaion.Annotator.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; }
}
-10
View File
@@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace Azaion.Annotator.DTO;
public class LabelInfo
{
[JsonProperty("c")] public List<int> Classes { get; set; } = null!;
[JsonProperty("d")] public DateTime ImageDateTime { get; set; }
}
+1 -6
View File
@@ -1,14 +1,9 @@
using System.Windows.Input;
using Azaion.Common.DTO;
using MediatR;
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 PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum;
@@ -1,19 +0,0 @@
namespace Azaion.Annotator.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
}
-9
View File
@@ -1,9 +0,0 @@
namespace Azaion.Annotator.DTO;
public enum SelectionState
{
None = 0,
NewAnnCreating = 1,
AnnResizing = 2,
AnnMoving = 3
}
-40
View File
@@ -1,40 +0,0 @@
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows.Media.Imaging;
using Azaion.Annotator.Extensions;
namespace Azaion.Annotator.DTO;
public class ThumbnailDto : INotifyPropertyChanged
{
public string ThumbnailPath { get; set; }
public string ImagePath { get; set; }
public string LabelPath { get; set; }
public DateTime ImageDate { get; set; }
private BitmapImage? _image;
public BitmapImage? Image
{
get
{
if (_image == null)
Task.Run(async () => Image = await ThumbnailPath.OpenImage());
return _image;
}
set
{
_image = value;
OnPropertyChanged();
}
}
public string ImageName => Path.GetFileName(ImagePath);
public void UpdateImage() => _image = null;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace Azaion.Annotator.DTO;
public enum WindowsEnum
{
None = 0,
Main = 10,
DatasetExplorer = 20
}