mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 08:06:31 +00:00
small fixes, renames
This commit is contained in:
@@ -28,7 +28,7 @@ using MediaPlayer = LibVLCSharp.Shared.MediaPlayer;
|
||||
|
||||
namespace Azaion.Annotator;
|
||||
|
||||
public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
public partial class Annotator
|
||||
{
|
||||
private readonly AppConfig _appConfig;
|
||||
private readonly LibVLC _libVLC;
|
||||
@@ -52,8 +52,8 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300);
|
||||
|
||||
|
||||
private ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
|
||||
private ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
|
||||
public ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
|
||||
public ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
|
||||
|
||||
public IntervalTree<TimeSpan, Annotation> TimedAnnotations { get; set; } = new();
|
||||
private AutodetectDialog _autoDetectDialog = new() { Topmost = true };
|
||||
@@ -96,7 +96,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
try
|
||||
{
|
||||
_appConfig.DirectoriesConfig.VideosDirectory = TbFolder.Text;
|
||||
ReloadFiles();
|
||||
await ReloadFiles();
|
||||
await SaveUserSettings();
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -335,24 +335,12 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
_formState.AnnotationResults.Insert(index, new AnnotationResult(_appConfig.AnnotationConfig.DetectionClassesDict, annotation));
|
||||
}
|
||||
|
||||
private void ReloadFiles()
|
||||
private async Task ReloadFiles()
|
||||
{
|
||||
var dir = new DirectoryInfo(_appConfig.DirectoriesConfig.VideosDirectory);
|
||||
if (!dir.Exists)
|
||||
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 =>
|
||||
{
|
||||
using var media = new Media(_libVLC, x.FullName);
|
||||
@@ -361,22 +349,32 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
{
|
||||
Name = x.Name,
|
||||
Path = x.FullName,
|
||||
MediaType = MediaTypes.Video,
|
||||
HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", ""))
|
||||
MediaType = MediaTypes.Video
|
||||
};
|
||||
media.ParsedChanged += (_, _) => fInfo.Duration = TimeSpan.FromMilliseconds(media.Duration);
|
||||
return fInfo;
|
||||
}).ToList();
|
||||
|
||||
var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray()).Select(x => new MediaFileInfo
|
||||
{
|
||||
Name = x.Name,
|
||||
Path = x.FullName,
|
||||
MediaType = MediaTypes.Image,
|
||||
HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", ""))
|
||||
});
|
||||
var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray())
|
||||
.Select(x => new MediaFileInfo
|
||||
{
|
||||
Name = x.Name,
|
||||
Path = x.FullName,
|
||||
MediaType = MediaTypes.Image
|
||||
});
|
||||
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;
|
||||
|
||||
BlinkHelp(AllMediaFiles.Count == 0
|
||||
@@ -406,6 +404,8 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
_mediaPlayer.SetPause(true);
|
||||
_mediaPlayer.Time = timeMilliseconds;
|
||||
VideoSlider.Value = _mediaPlayer.Position * 100;
|
||||
|
||||
StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}";
|
||||
}
|
||||
|
||||
private void SeekTo(TimeSpan time) =>
|
||||
@@ -435,7 +435,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
|
||||
_appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName;
|
||||
TbFolder.Text = dlg.FileName;
|
||||
ReloadFiles();
|
||||
await ReloadFiles();
|
||||
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());
|
||||
LvFiles.ItemsSource = FilteredMediaFiles;
|
||||
LvFiles.ItemsSource = FilteredMediaFiles;
|
||||
}
|
||||
|
||||
private void PlayClick(object sender, RoutedEventArgs e)
|
||||
@@ -487,7 +488,7 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
LvFiles.SelectedIndex = 0;
|
||||
|
||||
await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play));
|
||||
_mediaPlayer.SetPause(true);
|
||||
_mediaPlayer.Stop();
|
||||
|
||||
var manualCancellationSource = new CancellationTokenSource();
|
||||
var token = manualCancellationSource.Token;
|
||||
@@ -571,28 +572,34 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
Console.WriteLine($"Detect time: {timeframe.Time}");
|
||||
try
|
||||
{
|
||||
var fName = _formState.GetTimeName(timeframe.Time);
|
||||
var detections = await _aiDetector.Detect(fName, timeframe.Stream, token);
|
||||
var isValid = IsValidDetection(timeframe.Time, detections);
|
||||
var fName = _formState.GetTimeName(timeframe.Time);
|
||||
var detections = await _aiDetector.Detect(fName, timeframe.Stream, token);
|
||||
|
||||
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));
|
||||
prevSeekTime = timeframe.Time.TotalSeconds;
|
||||
prevSeekTime = timeframe.Time.TotalMilliseconds;
|
||||
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(() =>
|
||||
{
|
||||
Editor.RemoveAllAnns();
|
||||
Editor.Background = new ImageBrush { ImageSource = bitmap };
|
||||
Editor.Background = new ImageBrush
|
||||
{
|
||||
ImageSource = timeframe.Stream.OpenImage()
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -602,11 +609,12 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
|
||||
mediaInfo.HasAnnotations = true;
|
||||
await ProcessDetection(timeframe, ".jpg", detections, token);
|
||||
await timeframe.Stream.DisposeAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
await manualCancellationSource.CancelAsync();
|
||||
_logger.LogError(ex, ex.Message);
|
||||
await manualCancellationSource.CancelAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -665,7 +673,6 @@ public partial class Annotator : INotificationHandler<AnnotationsDeletedEvent>
|
||||
{
|
||||
try
|
||||
{
|
||||
var time = timeframe.Time;
|
||||
var fName = _formState.GetTimeName(timeframe.Time);
|
||||
|
||||
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}%"));
|
||||
|
||||
Dispatcher.Invoke(() => _autoDetectDialog.Log(log));
|
||||
|
||||
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ public class AnnotatorEventHandler(
|
||||
INotificationHandler<KeyEvent>,
|
||||
INotificationHandler<AnnClassSelectedEvent>,
|
||||
INotificationHandler<AnnotatorControlEvent>,
|
||||
INotificationHandler<VolumeChangedEvent>
|
||||
INotificationHandler<VolumeChangedEvent>,
|
||||
INotificationHandler<AnnotationsDeletedEvent>
|
||||
{
|
||||
private const int STEP = 20;
|
||||
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);
|
||||
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 data = outputImage.Encode(SKEncodedImageFormat.Jpeg, 85);
|
||||
using var ms = new MemoryStream();
|
||||
var ms = new MemoryStream();
|
||||
data.SaveTo(ms);
|
||||
|
||||
yield return (frameInfo.Time, ms);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.IO;
|
||||
using Azaion.Annotator.DTO;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Azaion.Annotator.Extensions;
|
||||
using Azaion.Common.DTO;
|
||||
using Azaion.Common.DTO.Config;
|
||||
using Azaion.Common.Services;
|
||||
using Azaion.CommonSecurity.Services;
|
||||
using Compunet.YoloV8;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -34,11 +33,10 @@ public class YOLODetector(IOptions<AIRecognitionConfig> recognitionConfig, IReso
|
||||
}
|
||||
|
||||
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 imageSize = new System.Windows.Size(image.Width, image.Height);
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace Azaion.Common.DTO;
|
||||
@@ -7,9 +6,7 @@ namespace Azaion.Common.DTO;
|
||||
public class FormState
|
||||
{
|
||||
public MediaFileInfo? CurrentMedia { get; set; }
|
||||
public string VideoName => string.IsNullOrEmpty(CurrentMedia?.Name)
|
||||
? ""
|
||||
: Path.GetFileNameWithoutExtension(CurrentMedia.Name).Replace(" ", "");
|
||||
public string VideoName => CurrentMedia?.FName ?? "";
|
||||
|
||||
public string CurrentMrl { get; set; } = null!;
|
||||
public Size CurrentVideoSize { get; set; }
|
||||
|
||||
@@ -8,4 +8,6 @@ public class MediaFileInfo
|
||||
public string DurationStr => $"{Duration:h\\:mm\\:ss}";
|
||||
public bool HasAnnotations { get; set; }
|
||||
public MediaTypes MediaType { get; set; }
|
||||
|
||||
public string FName => System.IO.Path.GetFileNameWithoutExtension(Name).Replace(" ", "");
|
||||
}
|
||||
@@ -32,13 +32,15 @@ public class Annotation
|
||||
public double Lat { get; set; }
|
||||
public double Lon { get; set; }
|
||||
|
||||
public List<int> Classes => Detections.Select(x => x.ClassNumber).ToList();
|
||||
public string ImagePath => Path.Combine(_imagesDir, $"{Name}{ImageExtension}");
|
||||
public string LabelPath => Path.Combine(_labelsDir, $"{Name}.txt");
|
||||
public string ThumbPath => Path.Combine(_thumbDir, $"{Name}{Constants.THUMBNAIL_PREFIX}.jpg");
|
||||
#region Calculated
|
||||
public List<int> Classes => Detections.Select(x => x.ClassNumber).ToList();
|
||||
public string ImagePath => Path.Combine(_imagesDir, $"{Name}{ImageExtension}");
|
||||
public string LabelPath => Path.Combine(_labelsDir, $"{Name}.txt");
|
||||
public string ThumbPath => Path.Combine(_thumbDir, $"{Name}{Constants.THUMBNAIL_PREFIX}.jpg");
|
||||
public string OriginalMediaName => $"{Name[..^7]}";
|
||||
|
||||
private TimeSpan? _time;
|
||||
public TimeSpan Time
|
||||
private TimeSpan? _time;
|
||||
public TimeSpan Time
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -60,6 +62,7 @@ public class Annotation
|
||||
return _time.Value;
|
||||
}
|
||||
}
|
||||
#endregion Calculated
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,10 +39,10 @@ public class DbFactory : IDbFactory
|
||||
_memoryConnection = new SQLiteConnection(MemoryConnStr);
|
||||
_memoryConnection.Open();
|
||||
_memoryDataOptions = new DataOptions()
|
||||
.UseDataProvider(SQLiteTools.GetDataProvider())
|
||||
.UseConnection(_memoryConnection)
|
||||
.UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema);
|
||||
//.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText));
|
||||
.UseDataProvider(SQLiteTools.GetDataProvider())
|
||||
.UseConnection(_memoryConnection)
|
||||
.UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema)
|
||||
;//.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText));
|
||||
|
||||
|
||||
_fileConnection = new SQLiteConnection(FileConnStr);
|
||||
@@ -50,7 +50,6 @@ public class DbFactory : IDbFactory
|
||||
.UseDataProvider(SQLiteTools.GetDataProvider())
|
||||
.UseConnection(_fileConnection)
|
||||
.UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema);
|
||||
_ = _fileDataOptions.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText));
|
||||
|
||||
if (!File.Exists(_annConfig.AnnotationsDbFile))
|
||||
CreateDb();
|
||||
@@ -122,6 +121,11 @@ public static class AnnotationsDbSchemaHolder
|
||||
.HasTableName(Constants.ANNOTATIONS_TABLENAME)
|
||||
.HasPrimaryKey(x => x.Name)
|
||||
.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);
|
||||
|
||||
builder.Entity<Detection>()
|
||||
|
||||
@@ -7,8 +7,14 @@ public static class BitmapExtensions
|
||||
{
|
||||
public static async Task<BitmapImage> OpenImage(this string imagePath)
|
||||
{
|
||||
var image = new BitmapImage();
|
||||
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.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = stream;
|
||||
|
||||
@@ -67,7 +67,22 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
||||
OffsetSpec = new OffsetTypeOffset(offset + 1),
|
||||
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
|
||||
.Where(x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE)
|
||||
.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,
|
||||
generateThumbnail: false, token);
|
||||
|
||||
//Queue (only from operators)
|
||||
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);
|
||||
}
|
||||
// //Queue (only from operators)
|
||||
// public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
|
||||
private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string fName, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream,
|
||||
RoleEnum userRole,
|
||||
@@ -168,7 +170,7 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
|
||||
if (generateThumbnail)
|
||||
await _galleryService.CreateThumbnail(annotation, token);
|
||||
|
||||
await _producer.SendToQueue(annotation, token);
|
||||
await _producer.SendToInnerQueue(annotation, token);
|
||||
|
||||
await _mediator.Publish(new AnnotationCreatedEvent(annotation), token);
|
||||
await ThrottleExt.ThrottleRunAfter(() =>
|
||||
|
||||
@@ -53,7 +53,7 @@ public class FailsafeAnnotationsProducer
|
||||
await Init(cancellationToken);
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var messages = await GetFromQueue(cancellationToken);
|
||||
var messages = await GetFromInnerQueue(cancellationToken);
|
||||
foreach (var messagesChunk in messages.Chunk(10)) //Sending by 10
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
@@ -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 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;
|
||||
|
||||
public interface IResourceLoader
|
||||
{
|
||||
Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default);
|
||||
Assembly? LoadAssembly(string asmName);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var hardwareService = new HardwareService();
|
||||
|
||||
@@ -16,7 +16,7 @@ using Color = ScottPlot.Color;
|
||||
|
||||
namespace Azaion.Dataset;
|
||||
|
||||
public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEvent>, INotificationHandler<AnnotationsDeletedEvent>
|
||||
public partial class DatasetExplorer
|
||||
{
|
||||
private readonly ILogger<DatasetExplorer> _logger;
|
||||
private readonly AnnotationConfig _annotationConfig;
|
||||
@@ -27,13 +27,13 @@ public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEve
|
||||
|
||||
public ObservableCollection<DetectionClass> AllDetectionClasses { get; set; } = new();
|
||||
public ObservableCollection<AnnotationThumbnail> SelectedAnnotations { get; set; } = new();
|
||||
|
||||
public readonly Dictionary<string, AnnotationThumbnail> SelectedAnnotationDict = new();
|
||||
|
||||
private int _tempSelectedClassIdx = 0;
|
||||
private readonly IGalleryService _galleryService;
|
||||
private readonly IDbFactory _dbFactory;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly Dictionary<string, AnnotationThumbnail> _selectedAnnotationDict = new();
|
||||
|
||||
|
||||
public bool ThumbnailLoading { get; set; }
|
||||
|
||||
@@ -144,7 +144,7 @@ public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEve
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
private void AddAnnotationToDict(Annotation annotation)
|
||||
public void AddAnnotationToDict(Annotation annotation)
|
||||
{
|
||||
foreach (var c in annotation.Classes)
|
||||
_annotationsDict[c].Add(annotation);
|
||||
@@ -292,39 +292,7 @@ public partial class DatasetExplorer : INotificationHandler<AnnotationCreatedEve
|
||||
{
|
||||
var annThumb = new AnnotationThumbnail(ann);
|
||||
SelectedAnnotations.Add(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);
|
||||
SelectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@ namespace Azaion.Dataset;
|
||||
|
||||
public class DatasetExplorerEventHandler(
|
||||
DatasetExplorer datasetExplorer,
|
||||
AnnotationService annotationService)
|
||||
: INotificationHandler<KeyEvent>,
|
||||
INotificationHandler<DatasetExplorerControlEvent>
|
||||
AnnotationService annotationService) :
|
||||
INotificationHandler<KeyEvent>,
|
||||
INotificationHandler<DatasetExplorerControlEvent>,
|
||||
INotificationHandler<AnnotationCreatedEvent>,
|
||||
INotificationHandler<AnnotationsDeletedEvent>
|
||||
{
|
||||
private readonly Dictionary<Key, PlaybackControlEnum> _keysControlEnumDict = new()
|
||||
{
|
||||
@@ -83,4 +85,35 @@ public class DatasetExplorerEventHandler(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using Azaion.Annotator;
|
||||
@@ -48,6 +49,12 @@ public partial class App
|
||||
StartLogin();
|
||||
}
|
||||
|
||||
private readonly List<string> _encryptedResources =
|
||||
[
|
||||
"Azaion.Annotator",
|
||||
"Azaion.Dataset"
|
||||
];
|
||||
|
||||
private void StartLogin()
|
||||
{
|
||||
new ConfigUpdater().CheckConfig();
|
||||
@@ -57,7 +64,30 @@ public partial class App
|
||||
_apiClient = AzaionApiClient.Create(args);
|
||||
_resourceLoader = new ResourceLoader(_apiClient, args);
|
||||
_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();
|
||||
await _host.StartAsync();
|
||||
@@ -108,7 +138,8 @@ public partial class App
|
||||
services.AddSingleton<IAIDetector, YOLODetector>();
|
||||
services.AddMediatR(c => c.RegisterServicesFromAssemblies(
|
||||
typeof(Annotator.Annotator).Assembly,
|
||||
typeof(DatasetExplorer).Assembly));
|
||||
typeof(DatasetExplorer).Assembly,
|
||||
typeof(AnnotationService).Assembly));
|
||||
services.AddSingleton<LibVLC>(_ => new LibVLC());
|
||||
services.AddSingleton<FormState>();
|
||||
services.AddSingleton<MediaPlayer>(sp =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Azaion.Common.DTO;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace Azaion.Annotator.Test;
|
||||
@@ -17,4 +18,25 @@ public class DictTest
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user