using System.Drawing; using System.Globalization; using System.IO; using MessagePack; using Newtonsoft.Json; using Size = System.Windows.Size; namespace Azaion.Common.DTO; [MessagePackObject] public abstract class Label { [JsonProperty(PropertyName = "cl")][Key("c")] 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 Confidence { get; set; } public CanvasLabel() { } public CanvasLabel(int classNumber, double x, double y, double width, double height, double confidence = 1) : base(classNumber) { X = x; Y = y; Width = width; Height = height; Confidence = confidence; } public CanvasLabel(YoloLabel label, Size canvasSize, Size? videoSize = null, double confidence = 1) { var cw = canvasSize.Width; var ch = canvasSize.Height; var canvasAr = cw / ch; var videoAr = videoSize.HasValue ? videoSize.Value.Width / videoSize.Value.Height : canvasAr; 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; } Confidence = confidence; } } [MessagePackObject] public class YoloLabel : Label { [JsonProperty(PropertyName = "x")][Key("x")] public double CenterX { get; set; } [JsonProperty(PropertyName = "y")][Key("y")] public double CenterY { get; set; } [JsonProperty(PropertyName = "w")][Key("w")] public double Width { get; set; } [JsonProperty(PropertyName = "h")][Key("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 = null) { var cw = canvasSize.Width; var ch = canvasSize.Height; var canvasAr = cw / ch; var videoAr = videoSize.HasValue ? videoSize.Value.Width / videoSize.Value.Height : canvasAr; 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!"); if (strings.Length > 5) strings = strings[..5]; 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> ReadFromFile(string filename, CancellationToken cancellationToken = default) { var str = await File.ReadAllTextAsync(filename, cancellationToken); return Deserialize(str); } public static async Task WriteToFile(IEnumerable labels, string filename, CancellationToken cancellationToken = default) { var labelsStr = Serialize(labels); await File.WriteAllTextAsync(filename, labelsStr, cancellationToken); } public static string Serialize(IEnumerable labels) => string.Join(Environment.NewLine, labels.Select(x => x.ToString())); public static List Deserialize(string str) => str.Split('\n') .Select(Parse) .Where(ann => ann != null) .ToList()!; public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.'); } [MessagePackObject] public class Detection : YoloLabel { [JsonProperty(PropertyName = "an")][Key("an")] public string AnnotationName { get; set; } = null!; [JsonProperty(PropertyName = "p")][Key("p")] public double Confidence { get; set; } //For db & serialization public Detection(){} public Detection(string annotationName, YoloLabel label, double confidence = 1) { AnnotationName = annotationName; ClassNumber = label.ClassNumber; CenterX = label.CenterX; CenterY = label.CenterY; Height = label.Height; Width = label.Width; Confidence = confidence; } }