Files
XplorePlane/XplorePlane/Services/ImageProcessingService.cs
T
2026-03-14 20:39:34 +08:00

193 lines
7.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Emgu.CV;
using Emgu.CV.Structure;
using ImageProcessing.Core;
using ImageProcessing.Processors;
using Serilog;
namespace XplorePlane.Services
{
public class ImageProcessingService : IImageProcessingService
{
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, ImageProcessorBase> _processorRegistry;
private readonly ConcurrentDictionary<string, ImageProcessorBase16> _processorRegistry16;
public ImageProcessingService(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_processorRegistry = new ConcurrentDictionary<string, ImageProcessorBase>();
_processorRegistry16 = new ConcurrentDictionary<string, ImageProcessorBase16>();
RegisterBuiltInProcessors();
}
private void RegisterBuiltInProcessors()
{
// 8-bit processors
_processorRegistry["GaussianBlur"] = new GaussianBlurProcessor();
_processorRegistry["Threshold"] = new ThresholdProcessor();
_processorRegistry["Division"] = new DivisionProcessor();
_processorRegistry["Contrast"] = new ContrastProcessor();
_processorRegistry["Gamma"] = new GammaProcessor();
_processorRegistry["Morphology"] = new MorphologyProcessor();
_processorRegistry["Contour"] = new ContourProcessor();
_processorRegistry["ShockFilter"] = new ShockFilterProcessor();
_processorRegistry["BandPassFilter"] = new BandPassFilterProcessor();
// 16-bit processors (separate registry due to different base class)
_processorRegistry16["GaussianBlur16"] = new GaussianBlurProcessor16();
_processorRegistry16["FlatFieldCorrection16"] = new FlatFieldCorrectionProcessor16();
_logger.Information("Registered {Count8} 8-bit and {Count16} 16-bit built-in image processors",
_processorRegistry.Count, _processorRegistry16.Count);
}
public IReadOnlyList<string> GetAvailableProcessors()
{
var all = new List<string>(_processorRegistry.Keys);
all.AddRange(_processorRegistry16.Keys);
return all.AsReadOnly();
}
public void RegisterProcessor(string name, ImageProcessorBase processor)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Processor name cannot be empty", nameof(name));
if (processor == null) throw new ArgumentNullException(nameof(processor));
_processorRegistry[name] = processor;
_logger.Information("Registered processor: {ProcessorName}", name);
}
public IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName)
{
if (_processorRegistry.TryGetValue(processorName, out var processor))
return processor.GetParameters().AsReadOnly();
if (_processorRegistry16.TryGetValue(processorName, out var processor16))
return processor16.GetParameters().AsReadOnly();
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
}
public ImageProcessorBase GetProcessor(string processorName)
{
if (_processorRegistry.TryGetValue(processorName, out var processor))
return processor;
throw new ArgumentException($"Processor not registered or is 16-bit only: {processorName}", nameof(processorName));
}
public async Task<BitmapSource> ProcessImageAsync(
BitmapSource source,
string processorName,
IDictionary<string, object> parameters,
IProgress<double> progress = null,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (!_processorRegistry.TryGetValue(processorName, out var processor))
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
return await Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var emguImage = ImageConverter.ToEmguCV(source);
if (parameters != null)
{
foreach (var kvp in parameters)
processor.SetParameter(kvp.Key, kvp.Value);
}
progress?.Report(0.1);
var processedEmgu = processor.Process(emguImage);
progress?.Report(0.9);
var result = ImageConverter.ToBitmapSource(processedEmgu);
progress?.Report(1.0);
return result;
}
catch (OperationCanceledException)
{
throw;
}
catch (ArgumentException)
{
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Image processing failed for processor: {ProcessorName}", processorName);
throw new ImageProcessingException($"Image processing failed: {ex.Message}", ex);
}
}, cancellationToken);
}
public async Task<ushort[]> ProcessRawFrameAsync(
ushort[] pixelData,
int width,
int height,
string processorName,
IDictionary<string, object> parameters,
CancellationToken cancellationToken = default)
{
if (pixelData == null)
throw new ArgumentException("pixelData cannot be null", nameof(pixelData));
if (pixelData.Length != width * height)
throw new ArgumentException(
$"pixelData length {pixelData.Length} does not match width*height {width * height}");
if (!_processorRegistry16.TryGetValue(processorName, out var processor))
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
return await Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
var image = new Image<Gray, ushort>(width, height);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
image.Data[y, x, 0] = pixelData[y * width + x];
if (parameters != null)
{
foreach (var kvp in parameters)
processor.SetParameter(kvp.Key, kvp.Value);
}
var processed = processor.Process(image);
var result = new ushort[width * height];
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
result[y * width + x] = processed.Data[y, x, 0];
return result;
}, cancellationToken);
}
public void Dispose()
{
foreach (var processor in _processorRegistry.Values)
{
if (processor is IDisposable disposable)
disposable.Dispose();
}
_processorRegistry.Clear();
foreach (var processor in _processorRegistry16.Values)
{
if (processor is IDisposable disposable)
disposable.Dispose();
}
_processorRegistry16.Clear();
}
}
}