已合并 PR 25: 合并最新图像库,打通加载图像和算子流程图拖拽功能

This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
using Emgu.CV;
|
using Emgu.CV;
|
||||||
using Emgu.CV.Structure;
|
using Emgu.CV.Structure;
|
||||||
using Emgu.CV.Util;
|
using Emgu.CV.Util;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace XP.ImageProcessing.Core;
|
namespace XP.ImageProcessing.Core;
|
||||||
|
|
||||||
@@ -164,11 +165,45 @@ public abstract class ImageProcessorBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public T GetParameter<T>(string name)
|
public T GetParameter<T>(string name)
|
||||||
{
|
{
|
||||||
if (Parameters.ContainsKey(name))
|
if (!Parameters.ContainsKey(name))
|
||||||
{
|
|
||||||
return (T)Convert.ChangeType(Parameters[name].Value, typeof(T))!;
|
|
||||||
}
|
|
||||||
throw new ArgumentException($"参数 {name} 不存在");
|
throw new ArgumentException($"参数 {name} 不存在");
|
||||||
|
|
||||||
|
var parameter = Parameters[name];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (parameter.Value is T typedValue)
|
||||||
|
return typedValue;
|
||||||
|
|
||||||
|
if (parameter.Value is string textValue)
|
||||||
|
{
|
||||||
|
var normalizedText = NormalizeText(textValue);
|
||||||
|
if (typeof(T) == typeof(string))
|
||||||
|
return (T)(object)textValue;
|
||||||
|
|
||||||
|
if (typeof(T) == typeof(int) && int.TryParse(normalizedText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
|
||||||
|
return (T)(object)intValue;
|
||||||
|
|
||||||
|
if (typeof(T) == typeof(double) && double.TryParse(normalizedText, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
|
||||||
|
return (T)(object)doubleValue;
|
||||||
|
|
||||||
|
if (typeof(T) == typeof(bool) && bool.TryParse(normalizedText, out var boolValue))
|
||||||
|
return (T)(object)boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)Convert.ChangeType(parameter.Value, typeof(T), CultureInfo.InvariantCulture)!;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"参数 {name} 的值 '{parameter.Value}' 无法转换为 {typeof(T).Name}",
|
||||||
|
ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeText(string value)
|
||||||
|
{
|
||||||
|
return value.Trim().TrimEnd('、', ',', ',', '。', '.', ';', ';', ':', ':');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class SuperResolutionProcessor : ImageProcessorBase
|
|||||||
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
||||||
{
|
{
|
||||||
string model = GetParameter<string>("Model");
|
string model = GetParameter<string>("Model");
|
||||||
int scale = int.Parse(GetParameter<string>("Scale"));
|
int scale = GetParameter<int>("Scale");
|
||||||
|
|
||||||
// 查找模型文件
|
// 查找模型文件
|
||||||
string modelPath = FindModelFile(model, scale);
|
string modelPath = FindModelFile(model, scale);
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
using Moq;
|
using Moq;
|
||||||
|
using Prism.Events;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
using XplorePlane.Services;
|
using XplorePlane.Services;
|
||||||
@@ -29,7 +33,7 @@ namespace XplorePlane.Tests.Pipeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PipelineEditorViewModel CreateVm() =>
|
private PipelineEditorViewModel CreateVm() =>
|
||||||
new PipelineEditorViewModel(_mockImageSvc.Object, _mockExecSvc.Object, _mockPersistSvc.Object, _mockLogger.Object);
|
new PipelineEditorViewModel(_mockImageSvc.Object, _mockExecSvc.Object, _mockPersistSvc.Object, new EventAggregator(), _mockLogger.Object);
|
||||||
|
|
||||||
// ── 6.1 AddOperatorCommand ────────────────────────────────────
|
// ── 6.1 AddOperatorCommand ────────────────────────────────────
|
||||||
|
|
||||||
@@ -44,6 +48,18 @@ namespace XplorePlane.Tests.Pipeline
|
|||||||
Assert.Equal(0, vm.PipelineNodes[0].Order);
|
Assert.Equal(0, vm.PipelineNodes[0].Order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddOperator_KnownKey_SetsIconPath()
|
||||||
|
{
|
||||||
|
_mockImageSvc.Setup(s => s.GetAvailableProcessors()).Returns(new[] { "ShockFilter" });
|
||||||
|
|
||||||
|
var vm = CreateVm();
|
||||||
|
vm.AddOperatorCommand.Execute("ShockFilter");
|
||||||
|
|
||||||
|
Assert.Single(vm.PipelineNodes);
|
||||||
|
Assert.Equal("⚡", vm.PipelineNodes[0].IconPath);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AddOperator_InvalidKey_NodeNotAdded()
|
public void AddOperator_InvalidKey_NodeNotAdded()
|
||||||
{
|
{
|
||||||
@@ -92,6 +108,36 @@ namespace XplorePlane.Tests.Pipeline
|
|||||||
Assert.Equal(i, vm.PipelineNodes[i].Order);
|
Assert.Equal(i, vm.PipelineNodes[i].Order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadImageFromFile_SetsSourceImage()
|
||||||
|
{
|
||||||
|
var vm = CreateVm();
|
||||||
|
var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".png");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bitmap = TestHelpers.CreateTestBitmap(8, 8);
|
||||||
|
var encoder = new PngBitmapEncoder();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||||
|
|
||||||
|
using (var stream = File.Create(tempPath))
|
||||||
|
{
|
||||||
|
encoder.Save(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.LoadImageFromFile(tempPath);
|
||||||
|
|
||||||
|
Assert.NotNull(vm.SourceImage);
|
||||||
|
Assert.NotNull(vm.PreviewImage);
|
||||||
|
Assert.Contains(Path.GetFileName(tempPath), vm.StatusMessage);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (File.Exists(tempPath))
|
||||||
|
File.Delete(tempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── 6.2 RemoveOperatorCommand ─────────────────────────────────
|
// ── 6.2 RemoveOperatorCommand ─────────────────────────────────
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using FsCheck;
|
|||||||
using FsCheck.Fluent;
|
using FsCheck.Fluent;
|
||||||
using FsCheck.Xunit;
|
using FsCheck.Xunit;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
using Prism.Events;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -31,7 +32,7 @@ namespace XplorePlane.Tests.Pipeline
|
|||||||
var mockPersistSvc = new Mock<IPipelinePersistenceService>();
|
var mockPersistSvc = new Mock<IPipelinePersistenceService>();
|
||||||
var mockLogger = new Mock<ILoggerService>();
|
var mockLogger = new Mock<ILoggerService>();
|
||||||
mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(mockLogger.Object);
|
mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(mockLogger.Object);
|
||||||
return new PipelineEditorViewModel(mockImageSvc.Object, mockExecSvc.Object, mockPersistSvc.Object, mockLogger.Object);
|
return new PipelineEditorViewModel(mockImageSvc.Object, mockExecSvc.Object, mockPersistSvc.Object, new EventAggregator(), mockLogger.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Moq;
|
||||||
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XplorePlane.Services;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace XplorePlane.Tests.Services
|
||||||
|
{
|
||||||
|
public class ImageProcessingServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DiscoverProcessors_LoadsKnownProcessors()
|
||||||
|
{
|
||||||
|
var logger = new Mock<ILoggerService>();
|
||||||
|
logger.Setup(l => l.ForModule<ImageProcessingService>()).Returns(logger.Object);
|
||||||
|
|
||||||
|
using var service = new ImageProcessingService(logger.Object);
|
||||||
|
|
||||||
|
var processors = service.GetAvailableProcessors();
|
||||||
|
|
||||||
|
Assert.Contains("GaussianBlur", processors);
|
||||||
|
Assert.Contains("ShockFilter", processors);
|
||||||
|
Assert.Contains("BandPassFilter", processors);
|
||||||
|
Assert.Contains("Division", processors);
|
||||||
|
Assert.Contains("Contour", processors);
|
||||||
|
Assert.True(processors.Count >= 20, $"Expected many discovered processors, got {processors.Count}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -268,7 +268,6 @@ namespace XplorePlane
|
|||||||
|
|
||||||
// 注册视图和视图模型
|
// 注册视图和视图模型
|
||||||
containerRegistry.RegisterForNavigation<MainWindow>();
|
containerRegistry.RegisterForNavigation<MainWindow>();
|
||||||
containerRegistry.RegisterForNavigation<MainWindowB>();
|
|
||||||
containerRegistry.Register<MainViewModel>();
|
containerRegistry.Register<MainViewModel>();
|
||||||
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
|
containerRegistry.RegisterSingleton<NavigationPropertyPanelViewModel>();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Prism.Events;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace XplorePlane.Events
|
||||||
|
{
|
||||||
|
public sealed class ManualImageLoadedEvent : PubSubEvent<ManualImageLoadedPayload>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ManualImageLoadedPayload
|
||||||
|
{
|
||||||
|
public ManualImageLoadedPayload(BitmapSource image, string filePath)
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
FilePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapSource Image { get; }
|
||||||
|
public string FilePath { get; }
|
||||||
|
public string FileName => System.IO.Path.GetFileName(FilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Prism.Events;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace XplorePlane.Events
|
||||||
|
{
|
||||||
|
public sealed class PipelinePreviewUpdatedEvent : PubSubEvent<PipelinePreviewUpdatedPayload>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PipelinePreviewUpdatedPayload
|
||||||
|
{
|
||||||
|
public PipelinePreviewUpdatedPayload(BitmapSource image, string statusMessage)
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
StatusMessage = statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapSource Image { get; }
|
||||||
|
public string StatusMessage { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("XplorePlane.Tests")]
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XP.ImageProcessing.Core;
|
using XP.ImageProcessing.Core;
|
||||||
using XP.ImageProcessing.Processors;
|
|
||||||
|
|
||||||
namespace XplorePlane.Services
|
namespace XplorePlane.Services
|
||||||
{
|
{
|
||||||
@@ -18,40 +21,180 @@ namespace XplorePlane.Services
|
|||||||
public ImageProcessingService(ILoggerService logger)
|
public ImageProcessingService(ILoggerService logger)
|
||||||
{
|
{
|
||||||
_logger = logger?.ForModule<ImageProcessingService>() ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger?.ForModule<ImageProcessingService>() ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_processorRegistry = new ConcurrentDictionary<string, ImageProcessorBase>();
|
_processorRegistry = new ConcurrentDictionary<string, ImageProcessorBase>(StringComparer.OrdinalIgnoreCase);
|
||||||
RegisterBuiltInProcessors();
|
DiscoverProcessors();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterBuiltInProcessors()
|
private void DiscoverProcessors()
|
||||||
{
|
{
|
||||||
// 8-bit processors
|
var assemblies = LoadCandidateAssemblies().ToList();
|
||||||
_processorRegistry["GaussianBlur"] = new GaussianBlurProcessor();
|
var discoveredCount = 0;
|
||||||
_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();
|
|
||||||
|
|
||||||
_logger.Info("Registered {Count} built-in image processors", _processorRegistry.Count);
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
foreach (var processorType in GetProcessorTypes(assembly))
|
||||||
|
{
|
||||||
|
if (!TryCreateProcessor(processorType, out var processor))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var key = GetProcessorKey(processorType);
|
||||||
|
RegisterProcessorInternal(key, processor, discovered: true);
|
||||||
|
discoveredCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<string> GetAvailableProcessors() => new List<string>(_processorRegistry.Keys).AsReadOnly();
|
_logger.Info(
|
||||||
|
"Discovered {ProcessorCount} image processors from {AssemblyCount} assemblies",
|
||||||
|
discoveredCount,
|
||||||
|
assemblies.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<Assembly> LoadCandidateAssemblies()
|
||||||
|
{
|
||||||
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
.Where(a => !a.IsDynamic)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var loadedNames = new HashSet<string>(
|
||||||
|
assemblies.Select(a => a.GetName().Name ?? string.Empty),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var path in EnumerateProcessorAssemblyPaths())
|
||||||
|
{
|
||||||
|
var assemblyName = Path.GetFileNameWithoutExtension(path);
|
||||||
|
if (string.IsNullOrWhiteSpace(assemblyName) || loadedNames.Contains(assemblyName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(path));
|
||||||
|
loadedNames.Add(assembly.GetName().Name ?? assemblyName);
|
||||||
|
assemblies.Add(assembly);
|
||||||
|
_logger.Debug("Loaded processor assembly from {Path}", path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Failed to load processor assembly from {Path}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assemblies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> EnumerateProcessorAssemblyPaths()
|
||||||
|
{
|
||||||
|
var baseDirectory = AppContext.BaseDirectory;
|
||||||
|
var candidateDirectories = new[]
|
||||||
|
{
|
||||||
|
baseDirectory,
|
||||||
|
Path.Combine(baseDirectory, "Processors"),
|
||||||
|
Path.Combine(baseDirectory, "Plugins"),
|
||||||
|
Path.Combine(baseDirectory, "Libs", "ImageProcessing")
|
||||||
|
};
|
||||||
|
|
||||||
|
var paths = new List<string>();
|
||||||
|
|
||||||
|
foreach (var directory in candidateDirectories.Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (string.Equals(directory, baseDirectory, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var processorDll = Path.Combine(directory, "XP.ImageProcessing.Processors.dll");
|
||||||
|
if (File.Exists(processorDll))
|
||||||
|
paths.Add(processorDll);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paths.AddRange(Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Type> GetProcessorTypes(Assembly assembly)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return assembly
|
||||||
|
.GetTypes()
|
||||||
|
.Where(type => type is { IsClass: true, IsAbstract: false }
|
||||||
|
&& typeof(ImageProcessorBase).IsAssignableFrom(type)
|
||||||
|
&& type.GetConstructor(Type.EmptyTypes) != null);
|
||||||
|
}
|
||||||
|
catch (ReflectionTypeLoadException ex)
|
||||||
|
{
|
||||||
|
return ex.Types
|
||||||
|
.Where(type => type != null
|
||||||
|
&& type.IsClass
|
||||||
|
&& !type.IsAbstract
|
||||||
|
&& typeof(ImageProcessorBase).IsAssignableFrom(type)
|
||||||
|
&& type.GetConstructor(Type.EmptyTypes) != null)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetProcessorKey(Type processorType)
|
||||||
|
{
|
||||||
|
const string suffix = "Processor";
|
||||||
|
return processorType.Name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)
|
||||||
|
? processorType.Name[..^suffix.Length]
|
||||||
|
: processorType.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryCreateProcessor(Type processorType, out ImageProcessorBase processor)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
processor = (ImageProcessorBase)Activator.CreateInstance(processorType)!;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
processor = null;
|
||||||
|
_logger.Error(ex, "Failed to create processor instance for {ProcessorType}", processorType.FullName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterProcessorInternal(string name, ImageProcessorBase processor, bool discovered)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
throw new ArgumentException("Processor name cannot be empty", nameof(name));
|
||||||
|
if (processor == null)
|
||||||
|
throw new ArgumentNullException(nameof(processor));
|
||||||
|
|
||||||
|
_processorRegistry.AddOrUpdate(
|
||||||
|
name,
|
||||||
|
processor,
|
||||||
|
(_, _) => processor);
|
||||||
|
|
||||||
|
if (discovered)
|
||||||
|
_logger.Debug("Discovered processor: {ProcessorName}", name);
|
||||||
|
else
|
||||||
|
_logger.Info("Registered processor: {ProcessorName}", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> GetAvailableProcessors()
|
||||||
|
{
|
||||||
|
return _processorRegistry.Keys
|
||||||
|
.OrderBy(key => ProcessorUiMetadata.GetCategoryOrder(ProcessorUiMetadata.Get(key).Category))
|
||||||
|
.ThenBy(key => GetProcessorDisplayName(key), StringComparer.CurrentCultureIgnoreCase)
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
public void RegisterProcessor(string name, ImageProcessorBase processor)
|
public void RegisterProcessor(string name, ImageProcessorBase processor)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Processor name cannot be empty", nameof(name));
|
RegisterProcessorInternal(name, processor, discovered: false);
|
||||||
if (processor == null) throw new ArgumentNullException(nameof(processor));
|
|
||||||
_processorRegistry[name] = processor;
|
|
||||||
_logger.Info("Registered processor: {ProcessorName}", name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName)
|
public IReadOnlyList<ProcessorParameter> GetProcessorParameters(string processorName)
|
||||||
{
|
{
|
||||||
if (_processorRegistry.TryGetValue(processorName, out var processor))
|
if (_processorRegistry.TryGetValue(processorName, out var processor))
|
||||||
return processor.GetParameters().AsReadOnly();
|
return processor.GetParameters().AsReadOnly();
|
||||||
|
|
||||||
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,13 +202,15 @@ namespace XplorePlane.Services
|
|||||||
{
|
{
|
||||||
if (_processorRegistry.TryGetValue(processorName, out var processor))
|
if (_processorRegistry.TryGetValue(processorName, out var processor))
|
||||||
return processor;
|
return processor;
|
||||||
|
|
||||||
throw new ArgumentException($"Processor not registered or is 16-bit only: {processorName}", nameof(processorName));
|
throw new ArgumentException($"Processor not registered or is 16-bit only: {processorName}", nameof(processorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetProcessorDisplayName(string processorName)
|
public string GetProcessorDisplayName(string processorName)
|
||||||
{
|
{
|
||||||
if (_processorRegistry.TryGetValue(processorName, out var p))
|
if (_processorRegistry.TryGetValue(processorName, out var processor))
|
||||||
return string.IsNullOrWhiteSpace(p.Name) ? processorName : p.Name;
|
return string.IsNullOrWhiteSpace(processor.Name) ? processorName : processor.Name;
|
||||||
|
|
||||||
return processorName;
|
return processorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +226,6 @@ namespace XplorePlane.Services
|
|||||||
if (!_processorRegistry.TryGetValue(processorName, out var processor))
|
if (!_processorRegistry.TryGetValue(processorName, out var processor))
|
||||||
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
throw new ArgumentException($"Processor not registered: {processorName}", nameof(processorName));
|
||||||
|
|
||||||
// Extract pixels on the UI thread (BitmapSource / FormatConvertedBitmap are DependencyObjects)
|
|
||||||
var rawPixels = ImageConverter.ExtractGray8Pixels(source, out int imgWidth, out int imgHeight);
|
var rawPixels = ImageConverter.ExtractGray8Pixels(source, out int imgWidth, out int imgHeight);
|
||||||
|
|
||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
@@ -103,7 +247,7 @@ namespace XplorePlane.Services
|
|||||||
progress?.Report(0.9);
|
progress?.Report(0.9);
|
||||||
|
|
||||||
var result = ImageConverter.ToBitmapSource(processedEmgu);
|
var result = ImageConverter.ToBitmapSource(processedEmgu);
|
||||||
result.Freeze(); // must freeze before crossing thread boundary
|
result.Freeze();
|
||||||
progress?.Report(1.0);
|
progress?.Report(1.0);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -131,6 +275,7 @@ namespace XplorePlane.Services
|
|||||||
if (processor is IDisposable disposable)
|
if (processor is IDisposable disposable)
|
||||||
disposable.Dispose();
|
disposable.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_processorRegistry.Clear();
|
_processorRegistry.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace XplorePlane.Services
|
||||||
|
{
|
||||||
|
internal static class ProcessorUiMetadata
|
||||||
|
{
|
||||||
|
private static readonly (string Category, string CategoryIcon, int Order)[] CategoryDefinitions =
|
||||||
|
{
|
||||||
|
("滤波与平滑", "🌀", 0),
|
||||||
|
("图像增强", "✨", 1),
|
||||||
|
("图像变换", "🔁", 2),
|
||||||
|
("数学运算", "➗", 3),
|
||||||
|
("形态学处理", "⬚", 4),
|
||||||
|
("边缘检测", "📐", 5),
|
||||||
|
("检测分析", "🔎", 6),
|
||||||
|
("其他", "⚙", 99),
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static (string Category, string CategoryIcon, string OperatorIcon) Get(string operatorKey)
|
||||||
|
{
|
||||||
|
var category = GetCategory(operatorKey);
|
||||||
|
return (category, GetCategoryIcon(category), GetOperatorIcon(operatorKey, category));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetCategory(string operatorKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(operatorKey))
|
||||||
|
return "其他";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Blur", "Filter", "Shock"))
|
||||||
|
return "滤波与平滑";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Contrast", "Gamma", "Retinex", "Histogram", "Sharpen", "Layer",
|
||||||
|
"SubPixel", "SuperResolution", "HDR", "Effect", "PseudoColor", "Color"))
|
||||||
|
return "图像增强";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Mirror", "Rotate", "Grayscale", "Threshold"))
|
||||||
|
return "图像变换";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Division", "Multiplication", "Difference", "Integral", "Or"))
|
||||||
|
return "数学运算";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Morphology"))
|
||||||
|
return "形态学处理";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Edge"))
|
||||||
|
return "边缘检测";
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Measurement", "Detection", "Contour", "FillRate", "Void", "Line", "PointToLine", "Ellipse", "Bga"))
|
||||||
|
return "检测分析";
|
||||||
|
|
||||||
|
return "其他";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int GetCategoryOrder(string category)
|
||||||
|
{
|
||||||
|
foreach (var definition in CategoryDefinitions)
|
||||||
|
{
|
||||||
|
if (string.Equals(definition.Category, category, StringComparison.Ordinal))
|
||||||
|
return definition.Order;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetCategoryIcon(string category)
|
||||||
|
{
|
||||||
|
foreach (var definition in CategoryDefinitions)
|
||||||
|
{
|
||||||
|
if (string.Equals(definition.Category, category, StringComparison.Ordinal))
|
||||||
|
return definition.CategoryIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "⚙";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string GetOperatorIcon(string operatorKey) => GetOperatorIcon(operatorKey, GetCategory(operatorKey));
|
||||||
|
|
||||||
|
private static string GetOperatorIcon(string operatorKey, string category)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(operatorKey))
|
||||||
|
return GetCategoryIcon(category);
|
||||||
|
|
||||||
|
if (ContainsAny(operatorKey, "Shock"))
|
||||||
|
return "⚡";
|
||||||
|
if (ContainsAny(operatorKey, "BandPass"))
|
||||||
|
return "📶";
|
||||||
|
if (ContainsAny(operatorKey, "GaussianBlur", "MeanFilter", "MedianFilter", "BilateralFilter", "LowPassFilter", "HighPassFilter"))
|
||||||
|
return "🌀";
|
||||||
|
if (ContainsAny(operatorKey, "Contrast"))
|
||||||
|
return "🌗";
|
||||||
|
if (ContainsAny(operatorKey, "Gamma"))
|
||||||
|
return "γ";
|
||||||
|
if (ContainsAny(operatorKey, "Retinex"))
|
||||||
|
return "🎛";
|
||||||
|
if (ContainsAny(operatorKey, "Histogram"))
|
||||||
|
return "📊";
|
||||||
|
if (ContainsAny(operatorKey, "Sharpen"))
|
||||||
|
return "✦";
|
||||||
|
if (ContainsAny(operatorKey, "SubPixel", "SuperResolution"))
|
||||||
|
return "🔬";
|
||||||
|
if (ContainsAny(operatorKey, "HDR"))
|
||||||
|
return "💡";
|
||||||
|
if (ContainsAny(operatorKey, "PseudoColor"))
|
||||||
|
return "🎨";
|
||||||
|
if (ContainsAny(operatorKey, "FilmEffect"))
|
||||||
|
return "🎞";
|
||||||
|
if (ContainsAny(operatorKey, "ColorLayer"))
|
||||||
|
return "🧪";
|
||||||
|
if (ContainsAny(operatorKey, "Mirror"))
|
||||||
|
return "↔";
|
||||||
|
if (ContainsAny(operatorKey, "Rotate"))
|
||||||
|
return "⟳";
|
||||||
|
if (ContainsAny(operatorKey, "Grayscale"))
|
||||||
|
return "◻";
|
||||||
|
if (ContainsAny(operatorKey, "Threshold"))
|
||||||
|
return "▣";
|
||||||
|
if (ContainsAny(operatorKey, "Division"))
|
||||||
|
return "➗";
|
||||||
|
if (ContainsAny(operatorKey, "Multiplication"))
|
||||||
|
return "✕";
|
||||||
|
if (ContainsAny(operatorKey, "Difference"))
|
||||||
|
return "Δ";
|
||||||
|
if (ContainsAny(operatorKey, "Integral"))
|
||||||
|
return "∫";
|
||||||
|
if (ContainsAny(operatorKey, "Or"))
|
||||||
|
return "∨";
|
||||||
|
if (ContainsAny(operatorKey, "Morphology"))
|
||||||
|
return "⬚";
|
||||||
|
if (ContainsAny(operatorKey, "Sobel", "Kirsch", "HorizontalEdge"))
|
||||||
|
return "📐";
|
||||||
|
if (ContainsAny(operatorKey, "Contour"))
|
||||||
|
return "✏";
|
||||||
|
if (ContainsAny(operatorKey, "Measurement"))
|
||||||
|
return "📏";
|
||||||
|
if (ContainsAny(operatorKey, "FillRate"))
|
||||||
|
return "🧮";
|
||||||
|
if (ContainsAny(operatorKey, "Void"))
|
||||||
|
return "⚪";
|
||||||
|
if (ContainsAny(operatorKey, "Ellipse"))
|
||||||
|
return "⭕";
|
||||||
|
if (ContainsAny(operatorKey, "PointToLine"))
|
||||||
|
return "📍";
|
||||||
|
if (ContainsAny(operatorKey, "Edge"))
|
||||||
|
return "📐";
|
||||||
|
|
||||||
|
return GetCategoryIcon(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsAny(string value, params string[] terms)
|
||||||
|
{
|
||||||
|
foreach (var term in terms)
|
||||||
|
{
|
||||||
|
if (value.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,19 @@ namespace XplorePlane.Services
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var node = enabledNodes[step];
|
var node = enabledNodes[step];
|
||||||
|
var invalidParameters = node.Parameters
|
||||||
|
.Where(p => !p.IsValueValid)
|
||||||
|
.Select(p => p.DisplayName)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (invalidParameters.Count > 0)
|
||||||
|
{
|
||||||
|
throw new PipelineExecutionException(
|
||||||
|
$"算子 '{node.DisplayName}' 存在无效参数:{string.Join("、", invalidParameters)}",
|
||||||
|
node.Order,
|
||||||
|
node.OperatorKey);
|
||||||
|
}
|
||||||
|
|
||||||
var parameters = node.Parameters
|
var parameters = node.Parameters
|
||||||
.Where(p => p.IsValueValid)
|
.Where(p => p.IsValueValid)
|
||||||
.ToDictionary(p => p.Name, p => p.Value);
|
.ToDictionary(p => p.Name, p => p.Value);
|
||||||
|
|||||||
@@ -37,21 +37,7 @@ namespace XplorePlane.ViewModels
|
|||||||
private readonly IImageProcessingService _imageProcessingService;
|
private readonly IImageProcessingService _imageProcessingService;
|
||||||
private string _searchText = string.Empty;
|
private string _searchText = string.Empty;
|
||||||
|
|
||||||
// 算子 Key -> (分类名, 分类图标, 算子图标) 映射
|
// UI 元数据(分类 + 图标)由 ProcessorUiMetadata 统一提供,保持工具箱与流水线图标一致
|
||||||
private static readonly Dictionary<string, (string Category, string CatIcon, string OpIcon)> CategoryMap = new()
|
|
||||||
{
|
|
||||||
["GaussianBlur"] = ("滤波与平滑", "🔵", "🌀"),
|
|
||||||
["GaussianBlur16"] = ("滤波与平滑", "🔵", "🌀"),
|
|
||||||
["BandPassFilter"] = ("滤波与平滑", "🔵", "📶"),
|
|
||||||
["ShockFilter"] = ("滤波与平滑", "🔵", "⚡"),
|
|
||||||
["Contrast"] = ("增强与校正", "🟡", "🔆"),
|
|
||||||
["Gamma"] = ("增强与校正", "🟡", "🌗"),
|
|
||||||
["FlatFieldCorrection16"] = ("增强与校正", "🟡", "📐"),
|
|
||||||
["Threshold"] = ("分割与阈值", "🟢", "📊"),
|
|
||||||
["Division"] = ("分割与阈值", "🟢", "➗"),
|
|
||||||
["Morphology"] = ("形态学与轮廓", "🔴", "🔲"),
|
|
||||||
["Contour"] = ("形态学与轮廓", "🔴", "✏️"),
|
|
||||||
};
|
|
||||||
|
|
||||||
public OperatorToolboxViewModel(IImageProcessingService imageProcessingService)
|
public OperatorToolboxViewModel(IImageProcessingService imageProcessingService)
|
||||||
{
|
{
|
||||||
@@ -82,10 +68,8 @@ namespace XplorePlane.ViewModels
|
|||||||
foreach (var key in _imageProcessingService.GetAvailableProcessors())
|
foreach (var key in _imageProcessingService.GetAvailableProcessors())
|
||||||
{
|
{
|
||||||
var displayName = _imageProcessingService.GetProcessorDisplayName(key);
|
var displayName = _imageProcessingService.GetProcessorDisplayName(key);
|
||||||
var (category, catIcon, opIcon) = CategoryMap.TryGetValue(key, out var info)
|
var (category, categoryIcon, operatorIcon) = ProcessorUiMetadata.Get(key);
|
||||||
? info
|
AvailableOperators.Add(new OperatorDescriptor(key, displayName ?? key, operatorIcon, category, categoryIcon));
|
||||||
: ("其他", "⚙", "⚙");
|
|
||||||
AvailableOperators.Add(new OperatorDescriptor(key, displayName ?? key, opIcon, category, catIcon));
|
|
||||||
}
|
}
|
||||||
ApplyFilter();
|
ApplyFilter();
|
||||||
}
|
}
|
||||||
@@ -122,9 +106,12 @@ namespace XplorePlane.ViewModels
|
|||||||
private static int GetCategoryOrder(string category) => category switch
|
private static int GetCategoryOrder(string category) => category switch
|
||||||
{
|
{
|
||||||
"滤波与平滑" => 0,
|
"滤波与平滑" => 0,
|
||||||
"增强与校正" => 1,
|
"图像增强" => 1,
|
||||||
"分割与阈值" => 2,
|
"图像变换" => 2,
|
||||||
"形态学与轮廓" => 3,
|
"数学运算" => 3,
|
||||||
|
"形态学处理" => 4,
|
||||||
|
"边缘检测" => 5,
|
||||||
|
"检测分析" => 6,
|
||||||
_ => 99
|
_ => 99
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
using Prism.Events;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
using System;
|
using System;
|
||||||
@@ -9,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XplorePlane.Events;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
using XplorePlane.Services;
|
using XplorePlane.Services;
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ namespace XplorePlane.ViewModels
|
|||||||
private readonly IImageProcessingService _imageProcessingService;
|
private readonly IImageProcessingService _imageProcessingService;
|
||||||
private readonly IPipelineExecutionService _executionService;
|
private readonly IPipelineExecutionService _executionService;
|
||||||
private readonly IPipelinePersistenceService _persistenceService;
|
private readonly IPipelinePersistenceService _persistenceService;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
private PipelineNodeViewModel _selectedNode;
|
private PipelineNodeViewModel _selectedNode;
|
||||||
@@ -40,11 +43,13 @@ namespace XplorePlane.ViewModels
|
|||||||
IImageProcessingService imageProcessingService,
|
IImageProcessingService imageProcessingService,
|
||||||
IPipelineExecutionService executionService,
|
IPipelineExecutionService executionService,
|
||||||
IPipelinePersistenceService persistenceService,
|
IPipelinePersistenceService persistenceService,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
ILoggerService logger)
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
_imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService));
|
_imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService));
|
||||||
_executionService = executionService ?? throw new ArgumentNullException(nameof(executionService));
|
_executionService = executionService ?? throw new ArgumentNullException(nameof(executionService));
|
||||||
_persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService));
|
_persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService));
|
||||||
|
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||||
_logger = logger?.ForModule<PipelineEditorViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger?.ForModule<PipelineEditorViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
|
||||||
PipelineNodes = new ObservableCollection<PipelineNodeViewModel>();
|
PipelineNodes = new ObservableCollection<PipelineNodeViewModel>();
|
||||||
@@ -60,9 +65,13 @@ namespace XplorePlane.ViewModels
|
|||||||
SaveAsPipelineCommand = new DelegateCommand(async () => await SaveAsPipelineAsync());
|
SaveAsPipelineCommand = new DelegateCommand(async () => await SaveAsPipelineAsync());
|
||||||
DeletePipelineCommand = new DelegateCommand(async () => await DeletePipelineAsync());
|
DeletePipelineCommand = new DelegateCommand(async () => await DeletePipelineAsync());
|
||||||
LoadPipelineCommand = new DelegateCommand(async () => await LoadPipelineAsync());
|
LoadPipelineCommand = new DelegateCommand(async () => await LoadPipelineAsync());
|
||||||
|
LoadImageCommand = new DelegateCommand(LoadImage);
|
||||||
OpenToolboxCommand = new DelegateCommand(OpenToolbox);
|
OpenToolboxCommand = new DelegateCommand(OpenToolbox);
|
||||||
MoveNodeUpCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeUp);
|
MoveNodeUpCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeUp);
|
||||||
MoveNodeDownCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeDown);
|
MoveNodeDownCommand = new DelegateCommand<PipelineNodeViewModel>(MoveNodeDown);
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<ManualImageLoadedEvent>()
|
||||||
|
.Subscribe(OnManualImageLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── State Properties ──────────────────────────────────────────
|
// ── State Properties ──────────────────────────────────────────
|
||||||
@@ -88,6 +97,7 @@ namespace XplorePlane.ViewModels
|
|||||||
if (SetProperty(ref _sourceImage, value))
|
if (SetProperty(ref _sourceImage, value))
|
||||||
{
|
{
|
||||||
ExecutePipelineCommand.RaiseCanExecuteChanged();
|
ExecutePipelineCommand.RaiseCanExecuteChanged();
|
||||||
|
RaisePropertyChanged(nameof(DisplayImage));
|
||||||
TriggerDebouncedExecution();
|
TriggerDebouncedExecution();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,8 +106,14 @@ namespace XplorePlane.ViewModels
|
|||||||
public BitmapSource PreviewImage
|
public BitmapSource PreviewImage
|
||||||
{
|
{
|
||||||
get => _previewImage;
|
get => _previewImage;
|
||||||
set => SetProperty(ref _previewImage, value);
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _previewImage, value))
|
||||||
|
RaisePropertyChanged(nameof(DisplayImage));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BitmapSource DisplayImage => PreviewImage ?? SourceImage;
|
||||||
|
|
||||||
public string PipelineName
|
public string PipelineName
|
||||||
{
|
{
|
||||||
@@ -142,6 +158,7 @@ namespace XplorePlane.ViewModels
|
|||||||
public DelegateCommand SaveAsPipelineCommand { get; }
|
public DelegateCommand SaveAsPipelineCommand { get; }
|
||||||
public DelegateCommand DeletePipelineCommand { get; }
|
public DelegateCommand DeletePipelineCommand { get; }
|
||||||
public DelegateCommand LoadPipelineCommand { get; }
|
public DelegateCommand LoadPipelineCommand { get; }
|
||||||
|
public DelegateCommand LoadImageCommand { get; }
|
||||||
|
|
||||||
public DelegateCommand OpenToolboxCommand { get; }
|
public DelegateCommand OpenToolboxCommand { get; }
|
||||||
|
|
||||||
@@ -183,7 +200,8 @@ namespace XplorePlane.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
var displayName = _imageProcessingService.GetProcessorDisplayName(operatorKey) ?? operatorKey;
|
var displayName = _imageProcessingService.GetProcessorDisplayName(operatorKey) ?? operatorKey;
|
||||||
var node = new PipelineNodeViewModel(operatorKey, displayName)
|
var icon = ProcessorUiMetadata.GetOperatorIcon(operatorKey);
|
||||||
|
var node = new PipelineNodeViewModel(operatorKey, displayName, icon)
|
||||||
{
|
{
|
||||||
Order = PipelineNodes.Count
|
Order = PipelineNodes.Count
|
||||||
};
|
};
|
||||||
@@ -296,6 +314,7 @@ namespace XplorePlane.ViewModels
|
|||||||
|
|
||||||
PreviewImage = result;
|
PreviewImage = result;
|
||||||
StatusMessage = "流水线执行完成";
|
StatusMessage = "流水线执行完成";
|
||||||
|
PublishPipelinePreviewUpdated(result, StatusMessage);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -315,6 +334,84 @@ namespace XplorePlane.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadImage()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "加载图像",
|
||||||
|
Filter = "图像文件|*.bmp;*.png;*.jpg;*.jpeg;*.tif;*.tiff|所有文件|*.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadImageFromFile(dialog.FileName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"加载图像失败:{ex.Message}";
|
||||||
|
_logger.Error(ex, "加载图像失败:{Path}", dialog.FileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadImageFromFile(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
|
throw new ArgumentException("图像路径不能为空", nameof(filePath));
|
||||||
|
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
bitmap.BeginInit();
|
||||||
|
bitmap.UriSource = new Uri(filePath, UriKind.Absolute);
|
||||||
|
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmap.EndInit();
|
||||||
|
bitmap.Freeze();
|
||||||
|
|
||||||
|
SourceImage = bitmap;
|
||||||
|
PreviewImage = bitmap;
|
||||||
|
StatusMessage = $"已加载图像:{Path.GetFileName(filePath)}";
|
||||||
|
PublishManualImageLoaded(bitmap, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadImageFromBitmap(BitmapSource bitmap, string filePath, bool runPipeline = true)
|
||||||
|
{
|
||||||
|
if (bitmap == null)
|
||||||
|
throw new ArgumentNullException(nameof(bitmap));
|
||||||
|
|
||||||
|
SourceImage = bitmap;
|
||||||
|
PreviewImage = bitmap;
|
||||||
|
StatusMessage = $"已加载图像:{Path.GetFileName(filePath)}";
|
||||||
|
PublishManualImageLoaded(bitmap, filePath);
|
||||||
|
|
||||||
|
if (runPipeline)
|
||||||
|
TriggerDebouncedExecution();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PublishManualImageLoaded(BitmapSource bitmap, string filePath)
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<ManualImageLoadedEvent>()
|
||||||
|
.Publish(new ManualImageLoadedPayload(bitmap, filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PublishPipelinePreviewUpdated(BitmapSource bitmap, string statusMessage)
|
||||||
|
{
|
||||||
|
if (bitmap == null) return;
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<PipelinePreviewUpdatedEvent>()
|
||||||
|
.Publish(new PipelinePreviewUpdatedPayload(bitmap, statusMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnManualImageLoaded(ManualImageLoadedPayload payload)
|
||||||
|
{
|
||||||
|
if (payload?.Image == null) return;
|
||||||
|
if (ReferenceEquals(SourceImage, payload.Image)) return;
|
||||||
|
|
||||||
|
SourceImage = payload.Image;
|
||||||
|
PreviewImage = payload.Image;
|
||||||
|
StatusMessage = $"已加载图像:{payload.FileName}";
|
||||||
|
}
|
||||||
|
|
||||||
private void CancelExecution()
|
private void CancelExecution()
|
||||||
{
|
{
|
||||||
_executionCts?.Cancel();
|
_executionCts?.Cancel();
|
||||||
@@ -442,7 +539,8 @@ namespace XplorePlane.ViewModels
|
|||||||
{
|
{
|
||||||
var displayName = _imageProcessingService.GetProcessorDisplayName(nodeModel.OperatorKey)
|
var displayName = _imageProcessingService.GetProcessorDisplayName(nodeModel.OperatorKey)
|
||||||
?? nodeModel.OperatorKey;
|
?? nodeModel.OperatorKey;
|
||||||
var node = new PipelineNodeViewModel(nodeModel.OperatorKey, displayName)
|
var icon = ProcessorUiMetadata.GetOperatorIcon(nodeModel.OperatorKey);
|
||||||
|
var node = new PipelineNodeViewModel(nodeModel.OperatorKey, displayName, icon)
|
||||||
{
|
{
|
||||||
Order = nodeModel.Order,
|
Order = nodeModel.Order,
|
||||||
IsEnabled = nodeModel.IsEnabled
|
IsEnabled = nodeModel.IsEnabled
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using XP.ImageProcessing.Core;
|
using XP.ImageProcessing.Core;
|
||||||
|
|
||||||
namespace XplorePlane.ViewModels
|
namespace XplorePlane.ViewModels
|
||||||
@@ -16,7 +18,8 @@ namespace XplorePlane.ViewModels
|
|||||||
_value = parameter.Value;
|
_value = parameter.Value;
|
||||||
MinValue = parameter.MinValue;
|
MinValue = parameter.MinValue;
|
||||||
MaxValue = parameter.MaxValue;
|
MaxValue = parameter.MaxValue;
|
||||||
ParameterType = parameter.ValueType?.Name?.ToLower() switch
|
Options = parameter.Options;
|
||||||
|
ParameterType = parameter.ValueType?.Name?.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"int32" or "int" => "int",
|
"int32" or "int" => "int",
|
||||||
"double" => "double",
|
"double" => "double",
|
||||||
@@ -30,6 +33,7 @@ namespace XplorePlane.ViewModels
|
|||||||
public string DisplayName { get; }
|
public string DisplayName { get; }
|
||||||
public object MinValue { get; }
|
public object MinValue { get; }
|
||||||
public object MaxValue { get; }
|
public object MaxValue { get; }
|
||||||
|
public string[]? Options { get; }
|
||||||
public string ParameterType { get; }
|
public string ParameterType { get; }
|
||||||
|
|
||||||
public bool IsValueValid
|
public bool IsValueValid
|
||||||
@@ -43,28 +47,160 @@ namespace XplorePlane.ViewModels
|
|||||||
get => _value;
|
get => _value;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _value, value))
|
var normalizedValue = NormalizeValue(value);
|
||||||
ValidateValue(value);
|
if (SetProperty(ref _value, normalizedValue))
|
||||||
|
ValidateValue(normalizedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateValue(object value)
|
private void ValidateValue(object value)
|
||||||
{
|
{
|
||||||
if (value == null || MinValue == null || MaxValue == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
IsValueValid = true;
|
IsValueValid = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ParameterType == "int")
|
||||||
|
{
|
||||||
|
IsValueValid = TryConvertToInt(value, out var intValue) && IsWithinRange(intValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParameterType == "double")
|
||||||
|
{
|
||||||
|
IsValueValid = TryConvertToDouble(value, out var doubleValue) && IsWithinRange(doubleValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParameterType == "bool")
|
||||||
|
{
|
||||||
|
IsValueValid = TryConvertToBool(value, out _);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Options is { Length: > 0 })
|
||||||
|
{
|
||||||
|
var stringValue = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
|
||||||
|
IsValueValid = Options.Contains(stringValue, StringComparer.OrdinalIgnoreCase);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsValueValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object NormalizeValue(object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
if (ParameterType == "int" && TryConvertToInt(value, out var intValue))
|
||||||
|
return intValue;
|
||||||
|
|
||||||
|
if (ParameterType == "double" && TryConvertToDouble(value, out var doubleValue))
|
||||||
|
return doubleValue;
|
||||||
|
|
||||||
|
if (ParameterType == "bool" && TryConvertToBool(value, out var boolValue))
|
||||||
|
return boolValue;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsWithinRange(double value)
|
||||||
|
{
|
||||||
|
if (MinValue != null && TryConvertToDouble(MinValue, out var minValue) && value < minValue)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (MaxValue != null && TryConvertToDouble(MaxValue, out var maxValue) && value > maxValue)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeNumericText(string value)
|
||||||
|
{
|
||||||
|
return value.Trim().TrimEnd('、', ',', ',', '。', '.', ';', ';', ':', ':');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryConvertToInt(object value, out int result)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case int intValue:
|
||||||
|
result = intValue;
|
||||||
|
return true;
|
||||||
|
case string stringValue:
|
||||||
|
stringValue = NormalizeNumericText(stringValue);
|
||||||
|
return int.TryParse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)
|
||||||
|
|| int.TryParse(stringValue, NumberStyles.Integer, CultureInfo.CurrentCulture, out result);
|
||||||
|
default:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
double dVal = Convert.ToDouble(value);
|
result = Convert.ToInt32(value, CultureInfo.InvariantCulture);
|
||||||
double dMin = Convert.ToDouble(MinValue);
|
return true;
|
||||||
double dMax = Convert.ToDouble(MaxValue);
|
|
||||||
IsValueValid = dVal >= dMin && dVal <= dMax;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
IsValueValid = true; // é�žæ•°å€¼ç±»åž‹ä¸�å�šèŒƒå›´æ ¡éª?
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryConvertToDouble(object value, out double result)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case double doubleValue:
|
||||||
|
result = doubleValue;
|
||||||
|
return true;
|
||||||
|
case float floatValue:
|
||||||
|
result = floatValue;
|
||||||
|
return true;
|
||||||
|
case int intValue:
|
||||||
|
result = intValue;
|
||||||
|
return true;
|
||||||
|
case long longValue:
|
||||||
|
result = longValue;
|
||||||
|
return true;
|
||||||
|
case string stringValue:
|
||||||
|
stringValue = NormalizeNumericText(stringValue);
|
||||||
|
return double.TryParse(stringValue, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result)
|
||||||
|
|| double.TryParse(stringValue, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out result);
|
||||||
|
default:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = Convert.ToDouble(value, CultureInfo.InvariantCulture);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryConvertToBool(object value, out bool result)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case bool boolValue:
|
||||||
|
result = boolValue;
|
||||||
|
return true;
|
||||||
|
case string stringValue:
|
||||||
|
return bool.TryParse(stringValue.Trim(), out result);
|
||||||
|
default:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = Convert.ToBoolean(value, CultureInfo.InvariantCulture);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
|
using Prism.Events;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
|
using Microsoft.Win32;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using XplorePlane.Events;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XP.Common.PdfViewer.Interfaces;
|
using XP.Common.PdfViewer.Interfaces;
|
||||||
using XP.Hardware.MotionControl.Abstractions;
|
using XP.Hardware.MotionControl.Abstractions;
|
||||||
@@ -16,6 +20,7 @@ namespace XplorePlane.ViewModels
|
|||||||
{
|
{
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
private readonly IContainerProvider _containerProvider;
|
private readonly IContainerProvider _containerProvider;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private string _licenseInfo = "当前时间";
|
private string _licenseInfo = "当前时间";
|
||||||
|
|
||||||
public string LicenseInfo
|
public string LicenseInfo
|
||||||
@@ -36,6 +41,7 @@ namespace XplorePlane.ViewModels
|
|||||||
|
|
||||||
// 窗口打开命令
|
// 窗口打开命令
|
||||||
public DelegateCommand OpenImageProcessingCommand { get; }
|
public DelegateCommand OpenImageProcessingCommand { get; }
|
||||||
|
public DelegateCommand LoadImageCommand { get; }
|
||||||
public DelegateCommand OpenPipelineEditorCommand { get; }
|
public DelegateCommand OpenPipelineEditorCommand { get; }
|
||||||
public DelegateCommand OpenCncEditorCommand { get; }
|
public DelegateCommand OpenCncEditorCommand { get; }
|
||||||
public DelegateCommand OpenMatrixEditorCommand { get; }
|
public DelegateCommand OpenMatrixEditorCommand { get; }
|
||||||
@@ -64,10 +70,11 @@ namespace XplorePlane.ViewModels
|
|||||||
private Window _toolboxWindow;
|
private Window _toolboxWindow;
|
||||||
private Window _raySourceConfigWindow;
|
private Window _raySourceConfigWindow;
|
||||||
|
|
||||||
public MainViewModel(ILoggerService logger, IContainerProvider containerProvider)
|
public MainViewModel(ILoggerService logger, IContainerProvider containerProvider, IEventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_logger = logger?.ForModule<MainViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger?.ForModule<MainViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
|
_containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
|
||||||
|
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||||
|
|
||||||
NavigationTree = new ObservableCollection<object>();
|
NavigationTree = new ObservableCollection<object>();
|
||||||
|
|
||||||
@@ -81,6 +88,7 @@ namespace XplorePlane.ViewModels
|
|||||||
|
|
||||||
// 窗口打开命令
|
// 窗口打开命令
|
||||||
OpenImageProcessingCommand = new DelegateCommand(() => ShowWindow(new Views.ImageProcessingWindow(), "图像处理"));
|
OpenImageProcessingCommand = new DelegateCommand(() => ShowWindow(new Views.ImageProcessingWindow(), "图像处理"));
|
||||||
|
LoadImageCommand = new DelegateCommand(ExecuteLoadImage);
|
||||||
OpenPipelineEditorCommand = new DelegateCommand(() => ShowWindow(new Views.PipelineEditorWindow(), "流水线编辑器"));
|
OpenPipelineEditorCommand = new DelegateCommand(() => ShowWindow(new Views.PipelineEditorWindow(), "流水线编辑器"));
|
||||||
OpenCncEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.CncEditorWindow(), "CNC 编辑器"));
|
OpenCncEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.CncEditorWindow(), "CNC 编辑器"));
|
||||||
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排"));
|
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排"));
|
||||||
@@ -260,6 +268,36 @@ namespace XplorePlane.ViewModels
|
|||||||
() => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "射线源配置");
|
() => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "射线源配置");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExecuteLoadImage()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "加载图像",
|
||||||
|
Filter = "图像文件|*.bmp;*.png;*.jpg;*.jpeg;*.tif;*.tiff|所有文件|*.*"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
bitmap.BeginInit();
|
||||||
|
bitmap.UriSource = new Uri(dialog.FileName, UriKind.Absolute);
|
||||||
|
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmap.EndInit();
|
||||||
|
bitmap.Freeze();
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<ManualImageLoadedEvent>()
|
||||||
|
.Publish(new ManualImageLoadedPayload(bitmap, dialog.FileName));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "加载图像失败:{Path}", dialog.FileName);
|
||||||
|
MessageBox.Show($"加载图像失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteWarmUp()
|
private void ExecuteWarmUp()
|
||||||
{
|
{
|
||||||
var messageBoxResult = MessageBox.Show("确认执行射线源暖机操作?", "暖机",
|
var messageBoxResult = MessageBox.Show("确认执行射线源暖机操作?", "暖机",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Windows.Media.Imaging;
|
|||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XP.Hardware.Detector.Abstractions;
|
using XP.Hardware.Detector.Abstractions;
|
||||||
using XP.Hardware.Detector.Abstractions.Events;
|
using XP.Hardware.Detector.Abstractions.Events;
|
||||||
|
using XplorePlane.Events;
|
||||||
|
|
||||||
namespace XplorePlane.ViewModels
|
namespace XplorePlane.ViewModels
|
||||||
{
|
{
|
||||||
@@ -39,6 +40,10 @@ namespace XplorePlane.ViewModels
|
|||||||
|
|
||||||
eventAggregator.GetEvent<ImageCapturedEvent>()
|
eventAggregator.GetEvent<ImageCapturedEvent>()
|
||||||
.Subscribe(OnImageCaptured, ThreadOption.BackgroundThread);
|
.Subscribe(OnImageCaptured, ThreadOption.BackgroundThread);
|
||||||
|
eventAggregator.GetEvent<ManualImageLoadedEvent>()
|
||||||
|
.Subscribe(OnManualImageLoaded, ThreadOption.UIThread);
|
||||||
|
eventAggregator.GetEvent<PipelinePreviewUpdatedEvent>()
|
||||||
|
.Subscribe(OnPipelinePreviewUpdated, ThreadOption.UIThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnImageCaptured(ImageCapturedEventArgs args)
|
private void OnImageCaptured(ImageCapturedEventArgs args)
|
||||||
@@ -75,6 +80,22 @@ namespace XplorePlane.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnManualImageLoaded(ManualImageLoadedPayload payload)
|
||||||
|
{
|
||||||
|
if (payload?.Image == null) return;
|
||||||
|
|
||||||
|
ImageSource = payload.Image;
|
||||||
|
ImageInfo = $"手动加载: {payload.FileName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPipelinePreviewUpdated(PipelinePreviewUpdatedPayload payload)
|
||||||
|
{
|
||||||
|
if (payload?.Image == null) return;
|
||||||
|
|
||||||
|
ImageSource = payload.Image;
|
||||||
|
ImageInfo = payload.StatusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 16 位灰度数据线性拉伸为 8 位 BitmapSource(委托给 XP.Common 通用转换器)
|
/// 16 位灰度数据线性拉伸为 8 位 BitmapSource(委托给 XP.Common 通用转换器)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="库版本信息"
|
Title="库版本信息"
|
||||||
Width="850"
|
Width="400"
|
||||||
Height="600"
|
Height="600"
|
||||||
ResizeMode="CanResizeWithGrip"
|
ResizeMode="CanResizeWithGrip"
|
||||||
WindowStartupLocation="CenterOwner">
|
WindowStartupLocation="CenterOwner">
|
||||||
@@ -4,11 +4,9 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
|
||||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
||||||
d:DesignHeight="700"
|
d:DesignHeight="700"
|
||||||
d:DesignWidth="350"
|
d:DesignWidth="350"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -48,13 +46,14 @@
|
|||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<!-- Row 0: 工具栏 -->
|
<!-- Row 0: 工具栏 -->
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<!-- Row 1: 流水线节点列表 -->
|
|
||||||
<RowDefinition Height="3*" />
|
<!-- Row 2: 流水线节点列表 -->
|
||||||
<!-- Row 2: 分隔线 -->
|
<RowDefinition Height="2*" MinHeight="180" />
|
||||||
|
<!-- Row 3: 分隔线 -->
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<!-- Row 3: 参数面板 -->
|
<!-- Row 4: 参数面板 -->
|
||||||
<RowDefinition Height="2*" MinHeight="80" />
|
<RowDefinition Height="2*" MinHeight="80" />
|
||||||
<!-- Row 4: 状态栏 -->
|
<!-- Row 5: 状态栏 -->
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
@@ -90,6 +89,14 @@
|
|||||||
Content="加载"
|
Content="加载"
|
||||||
Style="{StaticResource ToolbarBtn}"
|
Style="{StaticResource ToolbarBtn}"
|
||||||
ToolTip="加载流水线" />
|
ToolTip="加载流水线" />
|
||||||
|
<!--
|
||||||
|
<Button
|
||||||
|
Width="64"
|
||||||
|
Command="{Binding LoadImageCommand}"
|
||||||
|
Content="加载图像"
|
||||||
|
Style="{StaticResource ToolbarBtn}"
|
||||||
|
ToolTip="加载输入图像" />
|
||||||
|
-->
|
||||||
<Button
|
<Button
|
||||||
Command="{Binding ExecutePipelineCommand}"
|
Command="{Binding ExecutePipelineCommand}"
|
||||||
Content="▶"
|
Content="▶"
|
||||||
@@ -109,6 +116,9 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 流水线节点列表(拖拽目标) -->
|
<!-- 流水线节点列表(拖拽目标) -->
|
||||||
<ListBox
|
<ListBox
|
||||||
x:Name="PipelineListBox"
|
x:Name="PipelineListBox"
|
||||||
@@ -164,7 +174,7 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
FontSize="13"
|
FontSize="13"
|
||||||
Text="⚙" />
|
Text="{Binding IconPath}" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 算子名称 -->
|
<!-- 算子名称 -->
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
@@ -22,6 +23,18 @@ namespace XplorePlane.Views
|
|||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (DataContext is not PipelineEditorViewModel)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DataContext = ContainerLocator.Current?.Resolve<PipelineEditorViewModel>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Error(ex, "PipelineEditorViewModel 解析失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_logger?.Info("PipelineEditorView DataContext 类型={Type}",
|
_logger?.Info("PipelineEditorView DataContext 类型={Type}",
|
||||||
DataContext?.GetType().Name);
|
DataContext?.GetType().Name);
|
||||||
|
|
||||||
@@ -50,7 +63,7 @@ namespace XplorePlane.Views
|
|||||||
|
|
||||||
if (!e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
|
if (!e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
|
||||||
{
|
{
|
||||||
_logger?.Warn("Drop 事件触发但数据中无 {Format}", OperatorToolboxView.DragFormat);
|
_logger?.Warn("Drop 事件触发但数据中没有 {Format}", OperatorToolboxView.DragFormat);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,46 @@
|
|||||||
<Window x:Class="XplorePlane.Views.PipelineEditorWindow"
|
<Window x:Class="XplorePlane.Views.PipelineEditorWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:roi="clr-namespace:XP.ImageProcessing.RoiControl.Controls;assembly=XP.ImageProcessing.RoiControl"
|
||||||
xmlns:views="clr-namespace:XplorePlane.Views"
|
xmlns:views="clr-namespace:XplorePlane.Views"
|
||||||
Title="流水线编辑器"
|
Title="流水线编辑器"
|
||||||
Width="700" Height="750"
|
Width="1200"
|
||||||
|
Height="750"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
ShowInTaskbar="False">
|
ShowInTaskbar="False">
|
||||||
<Grid>
|
<Grid Background="#F3F3F3">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="200" />
|
<ColumnDefinition Width="240" MinWidth="200" />
|
||||||
<ColumnDefinition Width="5" />
|
<ColumnDefinition Width="*" MinWidth="400" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="250" MinWidth="250" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<views:OperatorToolboxView Grid.Column="0" />
|
|
||||||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="#E0E0E0" />
|
<Border Grid.Column="0"
|
||||||
<views:PipelineEditorView Grid.Column="2" />
|
Margin="8"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#D0D0D0"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4">
|
||||||
|
<views:OperatorToolboxView />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Column="1"
|
||||||
|
Margin="8,8,4,8"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#D0D0D0"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4">
|
||||||
|
<roi:PolygonRoiCanvas ImageSource="{Binding DisplayImage}"
|
||||||
|
Background="White" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Column="2"
|
||||||
|
Margin="4,8,8,8"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#D0D0D0"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4">
|
||||||
|
<views:PipelineEditorView />
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using XplorePlane.ViewModels;
|
||||||
|
|
||||||
namespace XplorePlane.Views
|
namespace XplorePlane.Views
|
||||||
{
|
{
|
||||||
@@ -7,6 +10,15 @@ namespace XplorePlane.Views
|
|||||||
public PipelineEditorWindow()
|
public PipelineEditorWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DataContext = ContainerLocator.Current?.Resolve<PipelineEditorViewModel>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,11 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</telerik:RadRibbonGroup>
|
</telerik:RadRibbonGroup>
|
||||||
<telerik:RadRibbonGroup Header="快捷工具">
|
<telerik:RadRibbonGroup Header="快捷工具">
|
||||||
|
<telerik:RadRibbonButton
|
||||||
|
Command="{Binding LoadImageCommand}"
|
||||||
|
Size="Large"
|
||||||
|
SmallImage="/Assets/Icons/open.png"
|
||||||
|
Text="加载图像" />
|
||||||
<!-- 快捷工具: 上下两列,带文字 -->
|
<!-- 快捷工具: 上下两列,带文字 -->
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
@@ -319,7 +323,7 @@
|
|||||||
telerik:ScreenTip.Title="模块"
|
telerik:ScreenTip.Title="模块"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
SmallImage="/Assets/Icons/Module.png"
|
SmallImage="/Assets/Icons/Module.png"
|
||||||
Text="模块" />
|
Text="检测模块" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="全部保存"
|
telerik:ScreenTip.Title="全部保存"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
@@ -331,12 +335,12 @@
|
|||||||
telerik:ScreenTip.Title="消息"
|
telerik:ScreenTip.Title="消息"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
SmallImage="/Assets/Icons/message.png"
|
SmallImage="/Assets/Icons/message.png"
|
||||||
Text="消息" />
|
Text="消息弹窗" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="等待"
|
telerik:ScreenTip.Title="等待"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
SmallImage="/Assets/Icons/wait.png"
|
SmallImage="/Assets/Icons/wait.png"
|
||||||
Text="等待" />
|
Text="插入等待" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</telerik:RadRibbonGroup>
|
</telerik:RadRibbonGroup>
|
||||||
|
|
||||||
@@ -384,7 +388,7 @@
|
|||||||
Size="Large"
|
Size="Large"
|
||||||
SmallImage="/Assets/Icons/spiral.png" />
|
SmallImage="/Assets/Icons/spiral.png" />
|
||||||
</telerik:RadRibbonGroup>
|
</telerik:RadRibbonGroup>
|
||||||
|
<!--
|
||||||
<telerik:RadRibbonGroup Header="图像处理">
|
<telerik:RadRibbonGroup Header="图像处理">
|
||||||
<telerik:RadRibbonGroup.Variants>
|
<telerik:RadRibbonGroup.Variants>
|
||||||
<telerik:GroupVariant Priority="0" Variant="Large" />
|
<telerik:GroupVariant Priority="0" Variant="Large" />
|
||||||
@@ -395,6 +399,7 @@
|
|||||||
SmallImage="/Assets/Icons/workflow.png"
|
SmallImage="/Assets/Icons/workflow.png"
|
||||||
Text="流水线编辑器" />
|
Text="流水线编辑器" />
|
||||||
</telerik:RadRibbonGroup>
|
</telerik:RadRibbonGroup>
|
||||||
|
-->
|
||||||
</telerik:RadRibbonTab>
|
</telerik:RadRibbonTab>
|
||||||
<telerik:RadRibbonTab Header="关于">
|
<telerik:RadRibbonTab Header="关于">
|
||||||
<telerik:RadRibbonGroup Header="关于">
|
<telerik:RadRibbonGroup Header="关于">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:roi="clr-namespace:XP.ImageProcessing.RoiControl.Controls;assembly=XP.ImageProcessing.RoiControl"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
d:DesignHeight="400"
|
d:DesignHeight="400"
|
||||||
d:DesignWidth="600"
|
d:DesignWidth="600"
|
||||||
@@ -22,14 +23,10 @@
|
|||||||
FontWeight="SemiBold" Foreground="#333333" Text="实时图像" />
|
FontWeight="SemiBold" Foreground="#333333" Text="实时图像" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 图像显示区域,支持滚动 -->
|
<!-- 图像显示区域,支持滚动、缩放和ROI -->
|
||||||
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
<roi:PolygonRoiCanvas Grid.Row="1"
|
||||||
<Image Source="{Binding ImageSource}"
|
ImageSource="{Binding ImageSource}"
|
||||||
Stretch="None"
|
Background="White" />
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
RenderOptions.BitmapScalingMode="NearestNeighbor" />
|
|
||||||
</ScrollViewer>
|
|
||||||
|
|
||||||
<!-- 图像信息栏 -->
|
<!-- 图像信息栏 -->
|
||||||
<Border Grid.Row="2" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,1,0,0">
|
<Border Grid.Row="2" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,1,0,0">
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Remove="MainWindow.xaml" />
|
<Page Remove="MainWindow.xaml" />
|
||||||
|
<Page Remove="Views\Main\MainWindowB.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- NuGet 包引用 -->
|
<!-- NuGet 包引用 -->
|
||||||
@@ -144,6 +145,7 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Link>Libs\Hardware\zh-TW\%(Filename)%(Extension)</Link>
|
<Link>Libs\Hardware\zh-TW\%(Filename)%(Extension)</Link>
|
||||||
</None>
|
</None>
|
||||||
|
<Compile Remove="Views\Main\MainWindowB.xaml.cs" />
|
||||||
|
|
||||||
<Resource Include="XplorerPlane.ico" />
|
<Resource Include="XplorerPlane.ico" />
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@
|
|||||||
<ProjectReference Include="..\XP.Camera\XP.Camera.csproj" />
|
<ProjectReference Include="..\XP.Camera\XP.Camera.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<Exec Command="xcopy $(SolutionDir)ExternalLibraries\*.* $(TargetDir) /d /y" />
|
<Exec Command="xcopy "$(MSBuildProjectDirectory)\..\ExternalLibraries\*.*" "$(TargetDir)" /d /y" />
|
||||||
<Exec Command="xcopy $(SolutionDir)ExternalLibraries\Models $(TargetDir)Models\ /d /y /i" />
|
<Exec Command="xcopy "$(MSBuildProjectDirectory)\..\ExternalLibraries\Models" "$(TargetDir)Models\" /d /y /i" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
---------------------------------------------------------------
|
|
||||||
__ __ _ _____ _
|
|
||||||
\ \ / / | | | __ \| |
|
|
||||||
\ V / _ __ | | ___ _ __ ___| |__) | | __ _ _ __ ___
|
|
||||||
> < | '_ \| |/ _ \| '__/ _ \ ___/| |/ _` | '_ \ / _ \
|
|
||||||
/ . \| |_) | | (_) | | | __/ | | | (_| | | | | __/
|
|
||||||
/_/ \_\ .__/|_|\___/|_| \___|_| |_|\__,_|_| |_|\___|
|
|
||||||
| |
|
|
||||||
|_|
|
|
||||||
---------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
2026.3.14
|
|
||||||
----------------------
|
|
||||||
1、主页面的布局与拆分 √
|
|
||||||
2、硬件层射线源的集成 √
|
|
||||||
3、图像层集成,包括复刻一个示例界面,优化界面布局及算子中文 √
|
|
||||||
4、浮动图像处理工具箱调研 √
|
|
||||||
5、修复图像工具箱拖拽事件,流水线列表没有生成对应的控件 √
|
|
||||||
|
|
||||||
2026.3.16
|
|
||||||
----------------------
|
|
||||||
1、优化图像处理窗体的页面布局,简洁清晰 √
|
|
||||||
2、新增打开图像工具箱(修复DataContext问题) √
|
|
||||||
3、对主界面B方案进行优化 √
|
|
||||||
|
|
||||||
2026.3.17
|
|
||||||
----------------------
|
|
||||||
1、对界面设计进行优化 ,增加了扫描模式,移除了探测器设置,增加底部工具栏 √
|
|
||||||
|
|
||||||
|
|
||||||
2026.3.18
|
|
||||||
----------------------
|
|
||||||
1、全局数据结构的考虑与设计(多个窗体可以调用公共的数据,如射线源状态,探测器状态,运动位置,图像等) √
|
|
||||||
2、将计划窗体默认隐藏,只有CNC状态下展开 √
|
|
||||||
|
|
||||||
|
|
||||||
2026.3.20
|
|
||||||
----------------------
|
|
||||||
1、软件主界面设计讨论,暂定初稿,给出效果图设计 √
|
|
||||||
2、日志该用XP.Common库和多语言的学习 √
|
|
||||||
|
|
||||||
|
|
||||||
2026.3.26
|
|
||||||
----------------------
|
|
||||||
1、各窗体间数据流的传递,全局数据结构的设计(包括一个基本的说明文档)√
|
|
||||||
2、将telerik 升级到 2024.1.408.310;调整界面和主题;引入 硬件层依赖 √
|
|
||||||
3、图像算子流程文件,保存文件后缀 .imw, image process workflow 缩写 √
|
|
||||||
4、CNC保存文件后缀为.xp, 表示 XplorePlane CNC file 的缩写
|
|
||||||
5、硬件层射线源控件的初步集成(采用库层面的自定义控件方式) √
|
|
||||||
PrismBootstrapper 的执行顺序是:RegisterTypes() → ConfigureModuleCatalog() → InitializeModules() → CreateShell()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2026.3.27
|
|
||||||
----------------------
|
|
||||||
CNC及矩阵功能的设计与实现,包含以下功能:
|
|
||||||
1、CNC功能设计与实现,包含以下功能:
|
|
||||||
a. CNC状态的定义和管理
|
|
||||||
b. CNC界面设计与实现
|
|
||||||
c. CNC相关数据的传递和处理
|
|
||||||
2、CNC相关的编排工具,如插入节点,插入位置,图像模块,等
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TO-DO
|
|
||||||
----------------------
|
|
||||||
Reference in New Issue
Block a user