diff --git a/Splitter-UI/Program.cs b/Splitter-UI/Program.cs index 85d83c7..9764329 100644 --- a/Splitter-UI/Program.cs +++ b/Splitter-UI/Program.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Media; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Splitter_UI; @@ -31,12 +32,28 @@ internal sealed class Program services.AddTransient(); services.AddTransient(); + // splitter services + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton( x => new SingleThreadedDetector(x.GetRequiredService()) ); + services.AddSingleton( x => new SingleThreadedDetector(x.GetRequiredService())); + services.AddSingleton>( x => detectorName => + { + return detectorName switch + { + "face" => x.GetRequiredService>(), + "body" => x.GetRequiredService>(), + _ => new DummyDetector() + }; + }); + services.AddSingleton(); + // Domain services (your pipeline) - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); diff --git a/Splitter-UI/Services/AvaloniaBitmapExtensions.cs b/Splitter-UI/Services/AvaloniaBitmapExtensions.cs new file mode 100644 index 0000000..f48e98d --- /dev/null +++ b/Splitter-UI/Services/AvaloniaBitmapExtensions.cs @@ -0,0 +1,43 @@ +using System.Runtime.InteropServices; +using Avalonia; +using Avalonia.Media.Imaging; +using OpenCvSharp; + +namespace Splitter_UI.Services; + +public static class AvaloniaBitmapExtensions +{ + public static Mat ToMatContinuous(this Bitmap bmp) + { + var w = bmp.PixelSize.Width; + var h = bmp.PixelSize.Height; + var stride = w * 4; + var size = h * stride; + + var buffer = new byte[size]; + var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + + try + { + bmp.CopyPixels( + new PixelRect(0, 0, w, h), + handle.AddrOfPinnedObject(), + size, + stride); + + return Mat.FromPixelData(h, w, MatType.CV_8UC4, buffer); + } + finally + { + handle.Free(); + } + } + + public static Mat ToMatBgrContinuous(this Bitmap bmp) + { + using var bgra = bmp.ToMatContinuous(); + var bgr = new Mat(); + Cv2.CvtColor(bgra, bgr, ColorConversionCodes.BGRA2BGR); + return bgr; + } +} \ No newline at end of file diff --git a/Splitter-UI/Services/DummyDetector.cs b/Splitter-UI/Services/DummyDetector.cs new file mode 100644 index 0000000..14f8285 --- /dev/null +++ b/Splitter-UI/Services/DummyDetector.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using OpenCvSharp; + +namespace Splitter_UI.Services; + +internal class DummyDetector : IObjectDetector +{ + public List<(Rect box, splitter.Point2f center)> DetectAll(Mat frameCont) => []; + public void Dispose() {} +} diff --git a/Splitter-UI/Services/GlobalLogger.cs b/Splitter-UI/Services/GlobalLogger.cs new file mode 100644 index 0000000..531e1b6 --- /dev/null +++ b/Splitter-UI/Services/GlobalLogger.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Splitter_UI.Services; + +internal class GlobalLogger(ILogService _logService) : ILogger +{ + public void ClearProgress(int progressLevel) { } + public void DrawProgress(string name, int progressLine, double progress, TimeSpan eta, double speed) { } + public void Log(string prefix, ConsoleColor color, string msg) + { + _logService.Write($"[{prefix}] {msg}"); + } +} diff --git a/Splitter-UI/Services/SingleThreadedDetector.cs b/Splitter-UI/Services/SingleThreadedDetector.cs new file mode 100644 index 0000000..0d5bc65 --- /dev/null +++ b/Splitter-UI/Services/SingleThreadedDetector.cs @@ -0,0 +1,23 @@ +using OpenCvSharp; + +namespace Splitter_UI.Services; + +public class SingleThreadedDetector(IObjectDetector _detector) : IObjectDetector + where T : IObjectDetector +{ + private Lock _lock = new(); + + public List<(Rect box, splitter.Point2f center)> DetectAll(Mat frameCont) + { + lock (_lock) + { + return _detector.DetectAll(frameCont); + } + } + + public void Dispose() + { + if ( _detector is IDisposable d ) + d.Dispose(); + } +} diff --git a/Splitter-UI/ViewModels/JobViewModel.cs b/Splitter-UI/ViewModels/JobViewModel.cs index 6c37ad7..5b019ea 100644 --- a/Splitter-UI/ViewModels/JobViewModel.cs +++ b/Splitter-UI/ViewModels/JobViewModel.cs @@ -35,9 +35,11 @@ public partial class JobViewModel : ObservableObject public IRelayCommand StepForwardCommand { get; } public IRelayCommand StepBackwardCommand { get; } - private readonly IThumbnailService _thumbnails; - private readonly IFileProbeService _fileProbe; - private readonly DispatcherTimer _debounceTimer; + private readonly IThumbnailService _thumbnails; + private readonly IFileProbeService _fileProbe; + private readonly DispatcherTimer _debounceTimer; + private readonly Func _detectorFactory; + private readonly ILogger _log; public string FileName => Path.GetFileName(Job.InputFile); @@ -115,11 +117,13 @@ public partial class JobViewModel : ObservableObject } } - public JobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe) + public JobViewModel(SingleJob job, IThumbnailService thumbnails, IFileProbeService fileProbe, Func detectorFactory, ILogger log) { - Job = job; - _thumbnails = thumbnails; - _fileProbe = fileProbe; + Job = job; + _thumbnails = thumbnails; + _fileProbe = fileProbe; + _detectorFactory = detectorFactory; + _log = log; ParametersList.Add(new ParameterEntry("DropoutToleranceFrames", "")); ParametersList.Add(new ParameterEntry("EmaFactor", "")); @@ -164,11 +168,21 @@ public partial class JobViewModel : ObservableObject return; try { - var frame = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe, TimeSpan.FromSeconds(PositionSeconds), Probe.Width, Probe.Height, Job.Rotate); - Preview = new PreviewData(frame, [], null); + var frame = await _thumbnails.CreateThumbnailAsync(Job.InputFile, Probe, TimeSpan.FromSeconds(PositionSeconds), Probe.Width, Probe.Height, Job.Rotate); + if ( frame == null ) + return; + + Preview = new PreviewData(frame, [], null); + + var detector = _detectorFactory(Job.Detect ?? ""); + var detections = detector.DetectAll(frame.ToMatContinuous()); + + var boxes = detections.Select(x => new Avalonia.Rect(x.box.X, x.box.Y, x.box.Width, x.box.Height)).ToList(); + Preview = new PreviewData(frame, boxes, null); } catch (Exception ex) { + _log.LogError($"Error creating preview for {FileName}: {ex.Message}"); } }