mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 22:26:31 +00:00
small fixes, renames
This commit is contained in:
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,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; }
|
||||||
|
|||||||
@@ -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(" ", "");
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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(() =>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 =>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user