small fixes, renames

This commit is contained in:
Alex Bezdieniezhnykh
2025-01-15 16:41:42 +02:00
parent ae2c62350a
commit 1bc1d81fde
16 changed files with 234 additions and 181 deletions
+45 -50
View File
@@ -28,7 +28,7 @@ using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
namespace Azaion.Annotator; namespace Azaion.Annotator;
public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent> public partial class Annotator
{ {
private readonly AppConfig _appConfig; private readonly AppConfig _appConfig;
private readonly LibVLC _libVLC; private readonly LibVLC _libVLC;
@@ -52,8 +52,8 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300); private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300);
private ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new(); public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
private ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new(); public ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
public IntervalTree<TimeSpan, Annotation> TimedAnnotations { get; set; } = new(); public IntervalTree<TimeSpan, Annotation> TimedAnnotations { get; set; } = new();
private AutodetectDialog _autoDetectDialog = new() { Topmost = true }; private AutodetectDialog _autoDetectDialog = new() { Topmost = true };
@@ -96,7 +96,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
try try
{ {
_appConfig.DirectoriesConfig.VideosDirectory = TbFolder.Text; _appConfig.DirectoriesConfig.VideosDirectory = TbFolder.Text;
ReloadFiles(); await ReloadFiles();
await SaveUserSettings(); await SaveUserSettings();
} }
catch (Exception e) catch (Exception e)
@@ -335,24 +335,12 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
_formState.AnnotationResults.Insert(index, new AnnotationResult(_appConfig.AnnotationConfig.DetectionClassesDict, annotation)); _formState.AnnotationResults.Insert(index, new AnnotationResult(_appConfig.AnnotationConfig.DetectionClassesDict, annotation));
} }
private void ReloadFiles() private async Task ReloadFiles()
{ {
var dir = new DirectoryInfo(_appConfig.DirectoriesConfig.VideosDirectory); var dir = new DirectoryInfo(_appConfig.DirectoriesConfig.VideosDirectory);
if (!dir.Exists) if (!dir.Exists)
return; return;
var labelNames = new DirectoryInfo(_appConfig.DirectoriesConfig.LabelsDirectory).GetFiles()
.Select(x =>
{
var name = Path.GetFileNameWithoutExtension(x.Name);
return name.Length > 8
? name[..^7]
: name;
})
.GroupBy(x => x)
.Select(gr => gr.Key)
.ToDictionary(x => x);
var videoFiles = dir.GetFiles(_appConfig.AnnotationConfig.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);
@@ -361,22 +349,32 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
{ {
Name = x.Name, Name = x.Name,
Path = x.FullName, Path = x.FullName,
MediaType = MediaTypes.Video, MediaType = MediaTypes.Video
HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", ""))
}; };
media.ParsedChanged += (_, _) => fInfo.Duration = TimeSpan.FromMilliseconds(media.Duration); media.ParsedChanged += (_, _) => fInfo.Duration = TimeSpan.FromMilliseconds(media.Duration);
return fInfo; return fInfo;
}).ToList(); }).ToList();
var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.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,
MediaType = MediaTypes.Image, MediaType = MediaTypes.Image
HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", ""))
}); });
var allFiles = videoFiles.Concat(imageFiles).ToList();
AllMediaFiles = new ObservableCollection<MediaFileInfo>(videoFiles.Concat(imageFiles).ToList()); var allFileNames = allFiles.Select(x => x.FName).ToList();
var labelsDict = await _dbFactory.Run(async db => await db.Annotations
.GroupBy(x => x.Name.Substring(0, x.Name.Length - 7))
.Where(x => allFileNames.Contains(x.Key))
.ToDictionaryAsync(x => x.Key, x => x.Key));
foreach (var mediaFile in allFiles)
mediaFile.HasAnnotations = labelsDict.ContainsKey(mediaFile.FName);
AllMediaFiles = new ObservableCollection<MediaFileInfo>(allFiles);
LvFiles.ItemsSource = AllMediaFiles; LvFiles.ItemsSource = AllMediaFiles;
BlinkHelp(AllMediaFiles.Count == 0 BlinkHelp(AllMediaFiles.Count == 0
@@ -406,6 +404,8 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
_mediaPlayer.SetPause(true); _mediaPlayer.SetPause(true);
_mediaPlayer.Time = timeMilliseconds; _mediaPlayer.Time = timeMilliseconds;
VideoSlider.Value = _mediaPlayer.Position * 100; VideoSlider.Value = _mediaPlayer.Position * 100;
StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}";
} }
private void SeekTo(TimeSpan time) => private void SeekTo(TimeSpan time) =>
@@ -435,7 +435,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
_appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName; _appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName;
TbFolder.Text = dlg.FileName; TbFolder.Text = dlg.FileName;
ReloadFiles(); await ReloadFiles();
await SaveUserSettings(); await SaveUserSettings();
} }
@@ -443,6 +443,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
{ {
FilteredMediaFiles = new ObservableCollection<MediaFileInfo>(AllMediaFiles.Where(x => x.Name.ToLower().Contains(TbFilter.Text.ToLower())).ToList()); FilteredMediaFiles = new ObservableCollection<MediaFileInfo>(AllMediaFiles.Where(x => x.Name.ToLower().Contains(TbFilter.Text.ToLower())).ToList());
LvFiles.ItemsSource = FilteredMediaFiles; LvFiles.ItemsSource = FilteredMediaFiles;
LvFiles.ItemsSource = FilteredMediaFiles;
} }
private void PlayClick(object sender, RoutedEventArgs e) private void PlayClick(object sender, RoutedEventArgs e)
@@ -487,7 +488,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
LvFiles.SelectedIndex = 0; LvFiles.SelectedIndex = 0;
await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play)); await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play));
_mediaPlayer.SetPause(true); _mediaPlayer.Stop();
var manualCancellationSource = new CancellationTokenSource(); var manualCancellationSource = new CancellationTokenSource();
var token = manualCancellationSource.Token; var token = manualCancellationSource.Token;
@@ -573,26 +574,32 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
{ {
var fName = _formState.GetTimeName(timeframe.Time); var fName = _formState.GetTimeName(timeframe.Time);
var detections = await _aiDetector.Detect(fName, timeframe.Stream, token); var detections = await _aiDetector.Detect(fName, timeframe.Stream, token);
var isValid = IsValidDetection(timeframe.Time, detections);
if (timeframe.Time.TotalSeconds > prevSeekTime + 1) var isValid = IsValidDetection(timeframe.Time, detections);
Console.WriteLine($"Detection time: {timeframe.Time}");
var log = string.Join(Environment.NewLine, detections.Select(det =>
$"{_appConfig.AnnotationConfig.DetectionClassesDict[det.ClassNumber].Name}: " +
$"xy=({det.CenterX:F2},{det.CenterY:F2}), " +
$"size=({det.Width:F2}, {det.Height:F2}), " +
$"prob: {det.Probability:F1}%"));
log = $"Detection time: {timeframe.Time}, Valid: {isValid}. {Environment.NewLine} {log}";
Dispatcher.Invoke(() => _autoDetectDialog.Log(log));
if (timeframe.Time.TotalMilliseconds > prevSeekTime + 250)
{ {
Dispatcher.Invoke(() => SeekTo(timeframe.Time)); Dispatcher.Invoke(() => SeekTo(timeframe.Time));
prevSeekTime = timeframe.Time.TotalSeconds; prevSeekTime = timeframe.Time.TotalMilliseconds;
if (!isValid) //Show frame anyway if (!isValid) //Show frame anyway
{ {
var bitmap = new BitmapImage();
bitmap.BeginInit();
timeframe.Stream.Seek(0, SeekOrigin.Begin);
bitmap.StreamSource = timeframe.Stream;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
Editor.RemoveAllAnns(); Editor.RemoveAllAnns();
Editor.Background = new ImageBrush { ImageSource = bitmap }; Editor.Background = new ImageBrush
{
ImageSource = timeframe.Stream.OpenImage()
};
}); });
} }
} }
@@ -602,6 +609,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
mediaInfo.HasAnnotations = true; mediaInfo.HasAnnotations = true;
await ProcessDetection(timeframe, ".jpg", detections, token); await ProcessDetection(timeframe, ".jpg", detections, token);
await timeframe.Stream.DisposeAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -665,7 +673,6 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
{ {
try try
{ {
var time = timeframe.Time;
var fName = _formState.GetTimeName(timeframe.Time); var fName = _formState.GetTimeName(timeframe.Time);
var annotation = await _annotationService.SaveAnnotation(fName, imageExtension, detections, SourceEnum.AI, timeframe.Stream, token); var annotation = await _annotationService.SaveAnnotation(fName, imageExtension, detections, SourceEnum.AI, timeframe.Stream, token);
@@ -682,8 +689,6 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
$"prob: {det.Probability:F1}%")); $"prob: {det.Probability:F1}%"));
Dispatcher.Invoke(() => _autoDetectDialog.Log(log)); Dispatcher.Invoke(() => _autoDetectDialog.Log(log));
} }
catch (Exception e) catch (Exception e)
{ {
@@ -691,14 +696,4 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
} }
}); });
} }
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken)
{
var annResDict = _formState.AnnotationResults.ToDictionary(x => x.Annotation.Name, x => x);
foreach (var ann in notification.Annotations)
{
_formState.AnnotationResults.Remove(annResDict[ann.Name]);
TimedAnnotations.Remove(ann);
}
}
} }
+26 -1
View File
@@ -28,7 +28,8 @@ public class AnnotatorEventHandler(
INotificationHandler<KeyEvent>, INotificationHandler<KeyEvent>,
INotificationHandler<AnnClassSelectedEvent>, INotificationHandler<AnnClassSelectedEvent>,
INotificationHandler<AnnotatorControlEvent>, INotificationHandler<AnnotatorControlEvent>,
INotificationHandler<VolumeChangedEvent> INotificationHandler<VolumeChangedEvent>,
INotificationHandler<AnnotationsDeletedEvent>
{ {
private const int STEP = 20; private const int STEP = 20;
private const int LARGE_STEP = 5000; private const int LARGE_STEP = 5000;
@@ -269,4 +270,28 @@ public class AnnotatorEventHandler(
var annotation = await annotationService.SaveAnnotation(fName, imageExtension, currentDetections, SourceEnum.Manual, token: cancellationToken); var annotation = await annotationService.SaveAnnotation(fName, imageExtension, currentDetections, SourceEnum.Manual, token: cancellationToken);
mainWindow.AddAnnotation(annotation); mainWindow.AddAnnotation(annotation);
} }
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken)
{
var annResDict = formState.AnnotationResults.ToDictionary(x => x.Annotation.Name, x => x);
foreach (var ann in notification.Annotations)
{
if (!annResDict.TryGetValue(ann.Name, out var value))
continue;
formState.AnnotationResults.Remove(value);
mainWindow.TimedAnnotations.Remove(ann);
}
if (formState.AnnotationResults.Count == 0)
{
var media = mainWindow.AllMediaFiles.FirstOrDefault(x => x.Name == formState.CurrentMedia?.Name);
if (media != null)
{
media.HasAnnotations = false;
mainWindow.LvFiles.Items.Refresh();
}
}
}
} }
@@ -73,7 +73,7 @@ public class VLCFrameExtractor(LibVLC libVLC, IOptions<AIRecognitionConfig> conf
using var outputImage = surface.Snapshot(); using var outputImage = surface.Snapshot();
using var data = outputImage.Encode(SKEncodedImageFormat.Jpeg, 85); using var data = outputImage.Encode(SKEncodedImageFormat.Jpeg, 85);
using var ms = new MemoryStream(); var ms = new MemoryStream();
data.SaveTo(ms); data.SaveTo(ms);
yield return (frameInfo.Time, ms); yield return (frameInfo.Time, ms);
+4 -6
View File
@@ -1,9 +1,8 @@
using System.IO; using System.Diagnostics;
using Azaion.Annotator.DTO; using System.IO;
using Azaion.Annotator.Extensions; using Azaion.Annotator.Extensions;
using Azaion.Common.DTO; using Azaion.Common.DTO;
using Azaion.Common.DTO.Config; using Azaion.Common.DTO.Config;
using Azaion.Common.Services;
using Azaion.CommonSecurity.Services; using Azaion.CommonSecurity.Services;
using Compunet.YoloV8; using Compunet.YoloV8;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -34,11 +33,10 @@ public class YOLODetector(IOptions<AIRecognitionConfig> recognitionConfig, IReso
} }
imageStream.Seek(0, SeekOrigin.Begin); imageStream.Seek(0, SeekOrigin.Begin);
var image = Image.Load<Rgb24>(imageStream);
using var image = Image.Load<Rgb24>(imageStream);
var result = await _predictor.DetectAsync(image); var result = await _predictor.DetectAsync(image);
var imageSize = new System.Windows.Size(image.Width, image.Height); var imageSize = new System.Windows.Size(image.Width, image.Height);
var detections = result.Select(d => var detections = result.Select(d =>
{ {
var label = new YoloLabel(new CanvasLabel(d.Name.Id, d.Bounds.X, d.Bounds.Y, d.Bounds.Width, d.Bounds.Height), imageSize, imageSize); var label = new YoloLabel(new CanvasLabel(d.Name.Id, d.Bounds.X, d.Bounds.Y, d.Bounds.Width, d.Bounds.Height), imageSize, imageSize);
+1 -4
View File
@@ -1,5 +1,4 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Windows; using System.Windows;
namespace Azaion.Common.DTO; namespace Azaion.Common.DTO;
@@ -7,9 +6,7 @@ namespace Azaion.Common.DTO;
public class FormState public class FormState
{ {
public MediaFileInfo? CurrentMedia { get; set; } public MediaFileInfo? CurrentMedia { get; set; }
public string VideoName => string.IsNullOrEmpty(CurrentMedia?.Name) public string VideoName => CurrentMedia?.FName ?? "";
? ""
: Path.GetFileNameWithoutExtension(CurrentMedia.Name).Replace(" ", "");
public string CurrentMrl { get; set; } = null!; public string CurrentMrl { get; set; } = null!;
public Size CurrentVideoSize { get; set; } public Size CurrentVideoSize { get; set; }
+2
View File
@@ -8,4 +8,6 @@ public class MediaFileInfo
public string DurationStr => $"{Duration:h\\:mm\\:ss}"; public string DurationStr => $"{Duration:h\\:mm\\:ss}";
public bool HasAnnotations { get; set; } public bool HasAnnotations { get; set; }
public MediaTypes MediaType { get; set; } public MediaTypes MediaType { get; set; }
public string FName => System.IO.Path.GetFileNameWithoutExtension(Name).Replace(" ", "");
} }
+3
View File
@@ -32,10 +32,12 @@ public class Annotation
public double Lat { get; set; } public double Lat { get; set; }
public double Lon { get; set; } public double Lon { get; set; }
#region Calculated
public List<int> Classes => Detections.Select(x => x.ClassNumber).ToList(); public List<int> Classes => Detections.Select(x => x.ClassNumber).ToList();
public string ImagePath => Path.Combine(_imagesDir, $"{Name}{ImageExtension}"); public string ImagePath => Path.Combine(_imagesDir, $"{Name}{ImageExtension}");
public string LabelPath => Path.Combine(_labelsDir, $"{Name}.txt"); public string LabelPath => Path.Combine(_labelsDir, $"{Name}.txt");
public string ThumbPath => Path.Combine(_thumbDir, $"{Name}{Constants.THUMBNAIL_PREFIX}.jpg"); public string ThumbPath => Path.Combine(_thumbDir, $"{Name}{Constants.THUMBNAIL_PREFIX}.jpg");
public string OriginalMediaName => $"{Name[..^7]}";
private TimeSpan? _time; private TimeSpan? _time;
public TimeSpan Time public TimeSpan Time
@@ -60,6 +62,7 @@ public class Annotation
return _time.Value; return _time.Value;
} }
} }
#endregion Calculated
} }
+7 -3
View File
@@ -41,8 +41,8 @@ public class DbFactory : IDbFactory
_memoryDataOptions = new DataOptions() _memoryDataOptions = new DataOptions()
.UseDataProvider(SQLiteTools.GetDataProvider()) .UseDataProvider(SQLiteTools.GetDataProvider())
.UseConnection(_memoryConnection) .UseConnection(_memoryConnection)
.UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema); .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema)
//.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText)); ;//.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText));
_fileConnection = new SQLiteConnection(FileConnStr); _fileConnection = new SQLiteConnection(FileConnStr);
@@ -50,7 +50,6 @@ public class DbFactory : IDbFactory
.UseDataProvider(SQLiteTools.GetDataProvider()) .UseDataProvider(SQLiteTools.GetDataProvider())
.UseConnection(_fileConnection) .UseConnection(_fileConnection)
.UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema); .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema);
_ = _fileDataOptions.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText));
if (!File.Exists(_annConfig.AnnotationsDbFile)) if (!File.Exists(_annConfig.AnnotationsDbFile))
CreateDb(); CreateDb();
@@ -122,6 +121,11 @@ public static class AnnotationsDbSchemaHolder
.HasTableName(Constants.ANNOTATIONS_TABLENAME) .HasTableName(Constants.ANNOTATIONS_TABLENAME)
.HasPrimaryKey(x => x.Name) .HasPrimaryKey(x => x.Name)
.Ignore(x => x.Time) .Ignore(x => x.Time)
.Ignore(x => x.Classes)
.Ignore(x => x.ImagePath)
.Ignore(x => x.LabelPath)
.Ignore(x => x.ThumbPath)
.Ignore(x => x.OriginalMediaName)
.Association(a => a.Detections, (a, d) => a.Name == d.AnnotationName); .Association(a => a.Detections, (a, d) => a.Name == d.AnnotationName);
builder.Entity<Detection>() builder.Entity<Detection>()
+7 -1
View File
@@ -7,8 +7,14 @@ public static class BitmapExtensions
{ {
public static async Task<BitmapImage> OpenImage(this string imagePath) public static async Task<BitmapImage> OpenImage(this string imagePath)
{ {
var image = new BitmapImage();
await using var stream = File.OpenRead(imagePath); await using var stream = File.OpenRead(imagePath);
return OpenImage(stream);
}
public static BitmapImage OpenImage(this Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
var image = new BitmapImage();
image.BeginInit(); image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad; image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream; image.StreamSource = stream;
+22 -20
View File
@@ -67,7 +67,22 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
OffsetSpec = new OffsetTypeOffset(offset + 1), OffsetSpec = new OffsetTypeOffset(offset + 1),
MessageHandler = async (stream, consumer, context, message) => MessageHandler = async (stream, consumer, context, message) =>
{ {
await Consume(MessagePackSerializer.Deserialize<AnnotationCreatedMessage>(message.Data.Contents), cancellationToken); var msg = MessagePackSerializer.Deserialize<AnnotationCreatedMessage>(message.Data.Contents);
if (msg.CreatedRole != RoleEnum.Operator) //Process only operator's messages
return;
await SaveAnnotationInner(
msg.CreatedDate,
msg.Name,
msg.ImageExtension,
JsonConvert.DeserializeObject<List<Detection>>(msg.Detections) ?? [],
msg.Source,
new MemoryStream(msg.Image),
msg.CreatedRole,
msg.CreatedEmail,
generateThumbnail: true,
cancellationToken);
await _dbFactory.Run(async db => await db.QueueOffsets await _dbFactory.Run(async db => await db.QueueOffsets
.Where(x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE) .Where(x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE)
.Set(x => x.Offset, context.Offset) .Set(x => x.Offset, context.Offset)
@@ -92,24 +107,11 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
await SaveAnnotationInner(DateTime.UtcNow, annotation.Name, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null, _apiClient.User.Role, _apiClient.User.Email, await SaveAnnotationInner(DateTime.UtcNow, annotation.Name, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null, _apiClient.User.Role, _apiClient.User.Email,
generateThumbnail: false, token); generateThumbnail: false, token);
//Queue (only from operators) // //Queue (only from operators)
public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default) // public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default)
{ // {
if (message.CreatedRole != RoleEnum.Operator) //Process only operator's messages //
return; // }
await SaveAnnotationInner(
message.CreatedDate,
message.Name,
message.ImageExtension,
JsonConvert.DeserializeObject<List<Detection>>(message.Detections) ?? [],
message.Source,
new MemoryStream(message.Image),
message.CreatedRole,
message.CreatedEmail,
generateThumbnail: true,
cancellationToken);
}
private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string fName, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream, private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string fName, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream,
RoleEnum userRole, RoleEnum userRole,
@@ -168,7 +170,7 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
if (generateThumbnail) if (generateThumbnail)
await _galleryService.CreateThumbnail(annotation, token); await _galleryService.CreateThumbnail(annotation, token);
await _producer.SendToQueue(annotation, token); await _producer.SendToInnerQueue(annotation, token);
await _mediator.Publish(new AnnotationCreatedEvent(annotation), token); await _mediator.Publish(new AnnotationCreatedEvent(annotation), token);
await ThrottleExt.ThrottleRunAfter(() => await ThrottleExt.ThrottleRunAfter(() =>
+3 -3
View File
@@ -53,7 +53,7 @@ public class FailsafeAnnotationsProducer
await Init(cancellationToken); await Init(cancellationToken);
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var messages = await GetFromQueue(cancellationToken); var messages = await GetFromInnerQueue(cancellationToken);
foreach (var messagesChunk in messages.Chunk(10)) //Sending by 10 foreach (var messagesChunk in messages.Chunk(10)) //Sending by 10
{ {
var sent = false; var sent = false;
@@ -91,7 +91,7 @@ public class FailsafeAnnotationsProducer
} }
} }
private async Task<List<AnnotationCreatedMessage>> GetFromQueue(CancellationToken cancellationToken = default) private async Task<List<AnnotationCreatedMessage>> GetFromInnerQueue(CancellationToken cancellationToken = default)
{ {
return await _dbFactory.Run(async db => return await _dbFactory.Run(async db =>
{ {
@@ -124,7 +124,7 @@ public class FailsafeAnnotationsProducer
}); });
} }
public async Task SendToQueue(Annotation annotation, CancellationToken cancellationToken = default) public async Task SendToInnerQueue(Annotation annotation, CancellationToken cancellationToken = default)
{ {
await _dbFactory.Run(async db => await _dbFactory.Run(async db =>
await db.InsertAsync(new AnnotationName { Name = annotation.Name }, token: cancellationToken)); await db.InsertAsync(new AnnotationName { Name = annotation.Name }, token: cancellationToken));
@@ -1,47 +1,14 @@
using System.Reflection; using Azaion.CommonSecurity.DTO;
using Azaion.CommonSecurity.DTO;
namespace Azaion.CommonSecurity.Services; namespace Azaion.CommonSecurity.Services;
public interface IResourceLoader public interface IResourceLoader
{ {
Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default); Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default);
Assembly? LoadAssembly(string asmName);
} }
public class ResourceLoader(AzaionApiClient api, ApiCredentials credentials) : IResourceLoader public class ResourceLoader(AzaionApiClient api, ApiCredentials credentials) : IResourceLoader
{ {
private static readonly List<string> EncryptedResources =
[
"Azaion.Annotator",
"Azaion.Dataset"
];
public Assembly? LoadAssembly(string resourceName)
{
var assemblyName = resourceName.Split(',').First();
if (EncryptedResources.Contains(assemblyName))
{
try
{
var stream = Load($"{assemblyName}.dll").GetAwaiter().GetResult();
return Assembly.Load(stream.ToArray());
}
catch (Exception e)
{
Console.WriteLine(e);
var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var dllPath = Path.Combine(currentLocation, SecurityConstants.DUMMY_DIR, $"{assemblyName}.dll");
return Assembly.LoadFile(dllPath);
}
}
var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == assemblyName);
return loadedAssembly;
}
public async Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default) public async Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default)
{ {
var hardwareService = new HardwareService(); var hardwareService = new HardwareService();
+5 -37
View File
@@ -16,7 +16,7 @@ using Color = ScottPlot.Color;
namespace Azaion.Dataset; namespace Azaion.Dataset;
public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEvent>, INotificationHandler<AnnotationsDeletedEvent> public partial class DatasetExplorer
{ {
private readonly ILogger<DatasetExplorer> _logger; private readonly ILogger<DatasetExplorer> _logger;
private readonly AnnotationConfig _annotationConfig; private readonly AnnotationConfig _annotationConfig;
@@ -27,13 +27,13 @@ public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEve
public ObservableCollection<DetectionClass> AllDetectionClasses { get; set; } = new(); public ObservableCollection<DetectionClass> AllDetectionClasses { get; set; } = new();
public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; set; } = new(); public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; set; } = new();
public readonly Dictionary<string, AnnotationThumbnail> SelectedAnnotationDict = new();
private int _tempSelectedClassIdx = 0; private int _tempSelectedClassIdx = 0;
private readonly IGalleryService _galleryService; private readonly IGalleryService _galleryService;
private readonly IDbFactory _dbFactory; private readonly IDbFactory _dbFactory;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly Dictionary<string, AnnotationThumbnail> _selectedAnnotationDict = new();
public bool ThumbnailLoading { get; set; } public bool ThumbnailLoading { get; set; }
@@ -144,7 +144,7 @@ public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEve
DataContext = this; DataContext = this;
} }
private void AddAnnotationToDict(Annotation annotation) public void AddAnnotationToDict(Annotation annotation)
{ {
foreach (var c in annotation.Classes) foreach (var c in annotation.Classes)
_annotationsDict[c].Add(annotation); _annotationsDict[c].Add(annotation);
@@ -292,39 +292,7 @@ public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEve
{ {
var annThumb = new AnnotationThumbnail(ann); var annThumb = new AnnotationThumbnail(ann);
SelectedAnnotations.Add(annThumb); SelectedAnnotations.Add(annThumb);
_selectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb); SelectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb);
}
}
public async Task Handle(AnnotationCreatedEvent notification, CancellationToken cancellationToken)
{
var annotation = notification.Annotation;
var selectedClass = ((DetectionClass?)LvClasses.SelectedItem)?.Id;
if (selectedClass == null)
return;
//TODO: For editing existing need to handle updates
AddAnnotationToDict(annotation);
if (annotation.Classes.Contains(selectedClass.Value))
{
var annThumb = new AnnotationThumbnail(annotation);
SelectedAnnotations.Add(annThumb);
_selectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb);
}
}
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken)
{
var names = notification.Annotations.Select(x => x.Name).ToList();
var annThumbs = _selectedAnnotationDict
.Where(x => names.Contains(x.Key))
.Select(x => x.Value)
.ToList();
foreach (var annThumb in annThumbs)
{
SelectedAnnotations.Remove(annThumb);
_selectedAnnotationDict.Remove(annThumb.Annotation.Name);
} }
} }
+36 -3
View File
@@ -10,9 +10,11 @@ namespace Azaion.Dataset;
public class DatasetExplorerEventHandler( public class DatasetExplorerEventHandler(
DatasetExplorer datasetExplorer, DatasetExplorer datasetExplorer,
AnnotationService annotationService) AnnotationService annotationService) :
: INotificationHandler<KeyEvent>, INotificationHandler<KeyEvent>,
INotificationHandler<DatasetExplorerControlEvent> INotificationHandler<DatasetExplorerControlEvent>,
INotificationHandler<AnnotationCreatedEvent>,
INotificationHandler<AnnotationsDeletedEvent>
{ {
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new() private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
{ {
@@ -83,4 +85,35 @@ public class DatasetExplorerEventHandler(
break; break;
} }
} }
public async Task Handle(AnnotationCreatedEvent notification, CancellationToken cancellationToken)
{
var annotation = notification.Annotation;
var selectedClass = ((DetectionClass?)datasetExplorer.LvClasses.SelectedItem)?.Id;
if (selectedClass == null)
return;
//TODO: For editing existing need to handle updates
datasetExplorer.AddAnnotationToDict(annotation);
if (annotation.Classes.Contains(selectedClass.Value))
{
var annThumb = new AnnotationThumbnail(annotation);
datasetExplorer.SelectedAnnotations.Add(annThumb);
datasetExplorer.SelectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb);
}
}
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken)
{
var names = notification.Annotations.Select(x => x.Name).ToList();
var annThumbs = datasetExplorer.SelectedAnnotationDict
.Where(x => names.Contains(x.Key))
.Select(x => x.Value)
.ToList();
foreach (var annThumb in annThumbs)
{
datasetExplorer.SelectedAnnotations.Remove(annThumb);
datasetExplorer.SelectedAnnotationDict.Remove(annThumb.Annotation.Name);
}
}
} }
+33 -2
View File
@@ -1,4 +1,5 @@
using System.IO; using System.IO;
using System.Reflection;
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
using Azaion.Annotator; using Azaion.Annotator;
@@ -48,6 +49,12 @@ public partial class App
StartLogin(); StartLogin();
} }
private readonly List<string> _encryptedResources =
[
"Azaion.Annotator",
"Azaion.Dataset"
];
private void StartLogin() private void StartLogin()
{ {
new ConfigUpdater().CheckConfig(); new ConfigUpdater().CheckConfig();
@@ -57,7 +64,30 @@ public partial class App
_apiClient = AzaionApiClient.Create(args); _apiClient = AzaionApiClient.Create(args);
_resourceLoader = new ResourceLoader(_apiClient, args); _resourceLoader = new ResourceLoader(_apiClient, args);
_securedConfig = await _resourceLoader.Load("secured-config.json"); _securedConfig = await _resourceLoader.Load("secured-config.json");
AppDomain.CurrentDomain.AssemblyResolve += (_, a) => _resourceLoader.LoadAssembly(a.Name); AppDomain.CurrentDomain.AssemblyResolve += (_, a) =>
{
var assemblyName = a.Name.Split(',').First();
if (_encryptedResources.Contains(assemblyName))
{
try
{
var stream = _resourceLoader.Load($"{assemblyName}.dll").GetAwaiter().GetResult();
return Assembly.Load(stream.ToArray());
}
catch (Exception e)
{
Console.WriteLine(e);
var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var dllPath = Path.Combine(currentLocation, SecurityConstants.DUMMY_DIR, $"{assemblyName}.dll");
return Assembly.LoadFile(dllPath);
}
}
var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == assemblyName);
return loadedAssembly;
};
StartMain(); StartMain();
await _host.StartAsync(); await _host.StartAsync();
@@ -108,7 +138,8 @@ public partial class App
services.AddSingleton<IAIDetector, YOLODetector>(); services.AddSingleton<IAIDetector, YOLODetector>();
services.AddMediatR(c => c.RegisterServicesFromAssemblies( services.AddMediatR(c => c.RegisterServicesFromAssemblies(
typeof(Annotator.Annotator).Assembly, typeof(Annotator.Annotator).Assembly,
typeof(DatasetExplorer).Assembly)); typeof(DatasetExplorer).Assembly,
typeof(AnnotationService).Assembly));
services.AddSingleton<LibVLC>(_ => new LibVLC()); services.AddSingleton<LibVLC>(_ => new LibVLC());
services.AddSingleton<FormState>(); services.AddSingleton<FormState>();
services.AddSingleton<MediaPlayer>(sp => services.AddSingleton<MediaPlayer>(sp =>
+22
View File
@@ -1,4 +1,5 @@
using Azaion.Common.DTO; using Azaion.Common.DTO;
using FluentAssertions;
using Xunit; using Xunit;
namespace Azaion.Annotator.Test; namespace Azaion.Annotator.Test;
@@ -17,4 +18,25 @@ public class DictTest
new YoloLabel(0, 1, 3, 2, 1) new YoloLabel(0, 1, 3, 2, 1)
]; ];
} }
[Theory]
[InlineData(null, 0)]
[InlineData(new int[]{}, 0)]
[InlineData(new int[]{1, 2, 5}, 1)]
[InlineData(new int[]{3, -2, 5}, -2)]
[InlineData(new int[]{3, -2, 2, 4}, 2)]
[InlineData(new int[]{3, -2, -100, 2, 4}, 2)]
public void ComputeClosestToZeroTest(int[] ts, int expected) =>
ComputeClosestToZero(ts).Should().Be(expected);
private int ComputeClosestToZero(int[]? ts)
{
if (ts is null || ts.Length == 0)
return 0;
return ts
.OrderBy(Math.Abs)
.ThenByDescending(x => x) // 2 -2 3 4 -10
.FirstOrDefault();
}
} }