#0015 初步集成图像库
This commit is contained in:
@@ -20,6 +20,7 @@ XplorePlane/Libs/Hardware/*.dll
|
||||
XplorePlane/Libs/Hardware/*.pdb
|
||||
XplorePlane/Libs/Native/*.dll
|
||||
XplorePlane/Libs/Native/*.pdb
|
||||
XplorePlane/XplorePlane/Libs/ImageProcessing/*.dll
|
||||
|
||||
# 保留 .gitkeep 文件以维持目录结构
|
||||
!XplorePlane/Libs/**/.gitkeep
|
||||
|
||||
+32
-9
@@ -2,6 +2,7 @@
|
||||
using System.Windows;
|
||||
using XplorePlane.Views;
|
||||
using XplorePlane.ViewModels;
|
||||
using XplorePlane.Services;
|
||||
using Prism.Ioc;
|
||||
using Prism.DryIoc;
|
||||
using Prism.Modularity;
|
||||
@@ -9,8 +10,6 @@ using Serilog;
|
||||
using XP.Common.Module;
|
||||
using XP.Hardware.RaySource.Module;
|
||||
using XP.Hardware.RaySource.Services;
|
||||
using XP.Hardware.RaySource.ViewModels;
|
||||
using XP.Hardware.RaySource.Views;
|
||||
|
||||
namespace XplorePlane
|
||||
{
|
||||
@@ -28,11 +27,34 @@ namespace XplorePlane
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
|
||||
base.OnStartup(e);
|
||||
try
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
// Initialize Prism with DryIoc
|
||||
var bootstrapper = new AppBootstrapper();
|
||||
bootstrapper.Run();
|
||||
// Initialize Prism with DryIoc
|
||||
var bootstrapper = new AppBootstrapper();
|
||||
bootstrapper.Run();
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Log.Fatal(ex, "Required DLL not found: {FileName}", ex.FileName);
|
||||
MessageBox.Show(
|
||||
$"Required library not found: {ex.FileName}\n\nPlease ensure all required DLLs are present in the Libs/ImageProcessing/ directory.",
|
||||
"Missing Library",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
Shutdown(1);
|
||||
}
|
||||
catch (TypeLoadException ex)
|
||||
{
|
||||
Log.Fatal(ex, "Failed to load type from DLL: {TypeName}", ex.TypeName);
|
||||
MessageBox.Show(
|
||||
$"Failed to load required type: {ex.TypeName}\n\nPlease ensure the correct version of DLLs are present in the Libs/ImageProcessing/ directory.",
|
||||
"Library Load Error",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
Shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureLogging()
|
||||
@@ -137,9 +159,10 @@ namespace XplorePlane
|
||||
containerRegistry.RegisterForNavigation<MainWindow>();
|
||||
containerRegistry.Register<MainViewModel>();
|
||||
|
||||
// 手动注册 RaySourceOperateView 的 View-ViewModel 映射
|
||||
// (库内 RaySourceModule 中此注册被注释掉了,需要在主项目补充)
|
||||
containerRegistry.RegisterForNavigation<RaySourceOperateView, RaySourceOperateViewModel>();
|
||||
// 注册图像处理服务与视图
|
||||
containerRegistry.RegisterSingleton<IImageProcessingService, ImageProcessingService>();
|
||||
containerRegistry.Register<ImageProcessingViewModel>();
|
||||
containerRegistry.RegisterForNavigation<ImageProcessingPanelView>();
|
||||
|
||||
Log.Information("依赖注入容器配置完成");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ImageProcessing.Core;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public interface IImageProcessingService : IDisposable
|
||||
{
|
||||
IReadOnlyList<string> GetAvailableProcessors();
|
||||
IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName);
|
||||
void RegisterProcessor(string name, ImageProcessorBase processor);
|
||||
|
||||
Task<BitmapSource> ProcessImageAsync(
|
||||
BitmapSource source,
|
||||
string processorName,
|
||||
IDictionary<string, object> parameters,
|
||||
IProgress<double> progress = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<ushort[]> ProcessRawFrameAsync(
|
||||
ushort[] pixelData,
|
||||
int width,
|
||||
int height,
|
||||
string processorName,
|
||||
IDictionary<string, object> parameters,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Structure;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public static class ImageConverter
|
||||
{
|
||||
public static Image<Gray, byte> ToEmguCV(BitmapSource bitmapSource)
|
||||
{
|
||||
if (bitmapSource == null) throw new ArgumentNullException(nameof(bitmapSource));
|
||||
|
||||
var formatted = new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray8, null, 0);
|
||||
int width = formatted.PixelWidth;
|
||||
int height = formatted.PixelHeight;
|
||||
int stride = width;
|
||||
byte[] pixels = new byte[height * stride];
|
||||
formatted.CopyPixels(pixels, stride, 0);
|
||||
|
||||
var image = new Image<Gray, byte>(width, height);
|
||||
image.Bytes = pixels;
|
||||
return image;
|
||||
}
|
||||
|
||||
public static BitmapSource ToBitmapSource(Image<Gray, byte> emguImage)
|
||||
{
|
||||
if (emguImage == null) throw new ArgumentNullException(nameof(emguImage));
|
||||
|
||||
int width = emguImage.Width;
|
||||
int height = emguImage.Height;
|
||||
int stride = width;
|
||||
byte[] pixels = emguImage.Bytes;
|
||||
|
||||
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Gray8, null, pixels, stride);
|
||||
}
|
||||
|
||||
public static Image<Gray, ushort> ToEmguCV16(BitmapSource bitmapSource)
|
||||
{
|
||||
if (bitmapSource == null) throw new ArgumentNullException(nameof(bitmapSource));
|
||||
|
||||
var formatted = new FormatConvertedBitmap(bitmapSource, PixelFormats.Gray16, null, 0);
|
||||
int width = formatted.PixelWidth;
|
||||
int height = formatted.PixelHeight;
|
||||
int stride = width * 2; // 2 bytes per pixel for 16-bit
|
||||
byte[] rawBytes = new byte[height * stride];
|
||||
formatted.CopyPixels(rawBytes, stride, 0);
|
||||
|
||||
ushort[] pixels = new ushort[width * height];
|
||||
Buffer.BlockCopy(rawBytes, 0, pixels, 0, rawBytes.Length);
|
||||
|
||||
var image = new Image<Gray, ushort>(width, height);
|
||||
// Copy pixel data row by row
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
image.Data[y, x, 0] = pixels[y * width + x];
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
public class ImageProcessingException : Exception
|
||||
{
|
||||
public ImageProcessingException(string message) : base(message) { }
|
||||
public ImageProcessingException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using Serilog;
|
||||
using XplorePlane.Services;
|
||||
|
||||
namespace XplorePlane.ViewModels
|
||||
{
|
||||
public class ImageProcessingViewModel : BindableBase
|
||||
{
|
||||
private readonly IImageProcessingService _imageProcessingService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private string _selectedProcessor;
|
||||
private BitmapSource _currentImage;
|
||||
private BitmapSource _originalImage;
|
||||
private double _processingProgress;
|
||||
private bool _isProcessing;
|
||||
private string _statusMessage;
|
||||
|
||||
public ImageProcessingViewModel(IImageProcessingService imageProcessingService, ILogger logger)
|
||||
{
|
||||
_imageProcessingService = imageProcessingService;
|
||||
_logger = logger;
|
||||
|
||||
AvailableProcessors = new ObservableCollection<string>();
|
||||
CurrentParameters = new ObservableCollection<ProcessorParameterVM>();
|
||||
|
||||
// Populate available processors
|
||||
foreach (var name in _imageProcessingService.GetAvailableProcessors())
|
||||
AvailableProcessors.Add(name);
|
||||
|
||||
// Initialize commands (stubs - implemented in tasks 7.2, 7.4, 7.7)
|
||||
SelectProcessorCommand = new DelegateCommand<string>(OnSelectProcessor);
|
||||
ApplyProcessingCommand = new DelegateCommand(OnApplyProcessing);
|
||||
ResetImageCommand = new DelegateCommand(OnResetImage);
|
||||
}
|
||||
|
||||
public ObservableCollection<string> AvailableProcessors { get; }
|
||||
public ObservableCollection<ProcessorParameterVM> CurrentParameters { get; }
|
||||
|
||||
public string SelectedProcessor
|
||||
{
|
||||
get => _selectedProcessor;
|
||||
set => SetProperty(ref _selectedProcessor, value);
|
||||
}
|
||||
|
||||
public BitmapSource CurrentImage
|
||||
{
|
||||
get => _currentImage;
|
||||
set => SetProperty(ref _currentImage, value);
|
||||
}
|
||||
|
||||
public BitmapSource OriginalImage
|
||||
{
|
||||
get => _originalImage;
|
||||
set => SetProperty(ref _originalImage, value);
|
||||
}
|
||||
|
||||
public double ProcessingProgress
|
||||
{
|
||||
get => _processingProgress;
|
||||
set => SetProperty(ref _processingProgress, value);
|
||||
}
|
||||
|
||||
public bool IsProcessing
|
||||
{
|
||||
get => _isProcessing;
|
||||
set => SetProperty(ref _isProcessing, value);
|
||||
}
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
set => SetProperty(ref _statusMessage, value);
|
||||
}
|
||||
|
||||
public DelegateCommand<string> SelectProcessorCommand { get; }
|
||||
public DelegateCommand ApplyProcessingCommand { get; }
|
||||
public DelegateCommand ResetImageCommand { get; }
|
||||
|
||||
private void OnSelectProcessor(string processorName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(processorName)) return;
|
||||
|
||||
try
|
||||
{
|
||||
SelectedProcessor = processorName;
|
||||
var parameters = _imageProcessingService.GetProcessorParameters(processorName);
|
||||
CurrentParameters.Clear();
|
||||
foreach (var param in parameters)
|
||||
CurrentParameters.Add(new ProcessorParameterVM(param));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
StatusMessage = $"Error loading parameters: {ex.Message}";
|
||||
_logger.Warning(ex, "Failed to load parameters for processor: {ProcessorName}", processorName);
|
||||
}
|
||||
}
|
||||
private async void OnApplyProcessing()
|
||||
{
|
||||
if (CurrentImage == null || string.IsNullOrEmpty(SelectedProcessor)) return;
|
||||
|
||||
IsProcessing = true;
|
||||
ProcessingProgress = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var parameters = new Dictionary<string, object>();
|
||||
foreach (var param in CurrentParameters)
|
||||
parameters[param.Name] = param.Value;
|
||||
|
||||
var progress = new Progress<double>(p => ProcessingProgress = p);
|
||||
|
||||
var result = await _imageProcessingService.ProcessImageAsync(
|
||||
CurrentImage,
|
||||
SelectedProcessor,
|
||||
parameters,
|
||||
progress);
|
||||
|
||||
CurrentImage = result;
|
||||
StatusMessage = $"Processing complete: {SelectedProcessor}";
|
||||
_logger.Information("Image processing completed: {ProcessorName}", SelectedProcessor);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
StatusMessage = $"Processing error: {ex.Message}";
|
||||
_logger.Warning(ex, "Processing failed for processor: {ProcessorName}", SelectedProcessor);
|
||||
// CurrentImage unchanged
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
StatusMessage = "Processing cancelled";
|
||||
_logger.Information("Image processing cancelled");
|
||||
// CurrentImage unchanged
|
||||
}
|
||||
catch (ImageProcessingException ex)
|
||||
{
|
||||
StatusMessage = $"Processing failed: {ex.Message}";
|
||||
_logger.Error(ex, "Image processing exception for processor: {ProcessorName}", SelectedProcessor);
|
||||
// CurrentImage unchanged
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsProcessing = false;
|
||||
}
|
||||
}
|
||||
private void OnResetImage()
|
||||
{
|
||||
CurrentImage = OriginalImage;
|
||||
StatusMessage = "Image reset to original";
|
||||
ProcessingProgress = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using ImageProcessing.Core;
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace XplorePlane.ViewModels
|
||||
{
|
||||
public class ProcessorParameterVM : BindableBase
|
||||
{
|
||||
private object _value;
|
||||
|
||||
public ProcessorParameterVM(ProcessorParameter parameter)
|
||||
{
|
||||
Name = parameter.Name;
|
||||
DisplayName = parameter.DisplayName;
|
||||
_value = parameter.DefaultValue;
|
||||
MinValue = parameter.MinValue;
|
||||
MaxValue = parameter.MaxValue;
|
||||
ParameterType = parameter.ValueType?.Name?.ToLower() switch
|
||||
{
|
||||
"int32" or "int" => "int",
|
||||
"double" => "double",
|
||||
"boolean" or "bool" => "bool",
|
||||
_ => "enum"
|
||||
};
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string DisplayName { get; }
|
||||
public object MinValue { get; }
|
||||
public object MaxValue { get; }
|
||||
public string ParameterType { get; }
|
||||
|
||||
public object Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetProperty(ref _value, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<UserControl x:Class="XplorePlane.Views.ImageProcessingPanelView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left panel: processor selection + parameters -->
|
||||
<Grid Grid.Column="0" Margin="5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="200"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Processors" FontWeight="Bold" Margin="0,0,0,5"/>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
ItemsSource="{Binding AvailableProcessors}"
|
||||
SelectedItem="{Binding SelectedProcessor}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<EventSetter Event="Selected"
|
||||
Handler="OnProcessorSelected"/>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Grid.Row="2" Text="Parameters" FontWeight="Bold" Margin="0,10,0,5"/>
|
||||
|
||||
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{Binding CurrentParameters}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0,2">
|
||||
<TextBlock Text="{Binding DisplayName}"/>
|
||||
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="0,10,0,5">
|
||||
<Button Content="Apply" Command="{Binding ApplyProcessingCommand}" Margin="0,0,5,0" Width="80"/>
|
||||
<Button Content="Reset" Command="{Binding ResetImageCommand}" Width="80"/>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar Grid.Row="5" Value="{Binding ProcessingProgress}" Minimum="0" Maximum="1" Height="10"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Right panel: image preview + status -->
|
||||
<Grid Grid.Column="1" Margin="5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1">
|
||||
<Image Source="{Binding CurrentImage}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="1" Text="{Binding StatusMessage}" Margin="0,5,0,0" TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
using XplorePlane.ViewModels;
|
||||
|
||||
namespace XplorePlane.Views
|
||||
{
|
||||
public partial class ImageProcessingPanelView : UserControl
|
||||
{
|
||||
public ImageProcessingPanelView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnProcessorSelected(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ImageProcessingViewModel vm && sender is ListBoxItem item)
|
||||
vm.SelectProcessorCommand.Execute(item.Content as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@
|
||||
FontWeight="SemiBold" Foreground="#333333" Text="射线源" />
|
||||
</Border>
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||||
<rayViews:RaySourceOperateView />
|
||||
<!-- TODO: 暂时屏蔽,待 DI 容器解析问题修复后恢复 -->
|
||||
<!-- <rayViews:RaySourceOperateView /> -->
|
||||
<TextBlock Text="射线源控件(待集成)" Margin="8" Foreground="#999999" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -47,6 +47,16 @@
|
||||
<HintPath>Libs\Native\BR.AN.PviServices.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
|
||||
<!-- 图像处理库 DLL 引用 -->
|
||||
<Reference Include="ImageProcessing.Core">
|
||||
<HintPath>Libs\ImageProcessing\ImageProcessing.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImageProcessing.Processors">
|
||||
<HintPath>Libs\ImageProcessing\ImageProcessing.Processors.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Telerik.Windows.Controls">
|
||||
@@ -149,6 +159,11 @@
|
||||
<Link>zh-TW\%(Filename)%(Extension)</Link>
|
||||
</None>
|
||||
|
||||
<!-- 图像处理外部运行时 DLL -->
|
||||
<None Include="Libs\ImageProcessing\ExternalLibraries\*.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
<!-- 配置文件 -->
|
||||
<None Update="App.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user