mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 13:26:30 +00:00
rework to Azaion.Suite
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using MediatR;
|
||||
using Azaion.Common.DTO;
|
||||
using MediatR;
|
||||
|
||||
namespace Azaion.Annotator.DTO;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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(" ", "");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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,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
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Azaion.Annotator.DTO;
|
||||
|
||||
public enum SelectionState
|
||||
{
|
||||
None = 0,
|
||||
NewAnnCreating = 1,
|
||||
AnnResizing = 2,
|
||||
AnnMoving = 3
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Azaion.Annotator.DTO;
|
||||
|
||||
public enum WindowsEnum
|
||||
{
|
||||
None = 0,
|
||||
Main = 10,
|
||||
DatasetExplorer = 20
|
||||
}
|
||||
Reference in New Issue
Block a user