diff --git a/XP.ImageProcessing.Core/ImageProcessorBase.cs b/XP.ImageProcessing.Core/ImageProcessorBase.cs index bafd8f0..0ed4c1e 100644 --- a/XP.ImageProcessing.Core/ImageProcessorBase.cs +++ b/XP.ImageProcessing.Core/ImageProcessorBase.cs @@ -15,6 +15,7 @@ using Emgu.CV; using Emgu.CV.Structure; using Emgu.CV.Util; +using System.Globalization; namespace XP.ImageProcessing.Core; @@ -164,11 +165,45 @@ public abstract class ImageProcessorBase /// public T GetParameter(string name) { - if (Parameters.ContainsKey(name)) + if (!Parameters.ContainsKey(name)) + throw new ArgumentException($"参数 {name} 不存在"); + + var parameter = Parameters[name]; + + try { - return (T)Convert.ChangeType(Parameters[name].Value, typeof(T))!; + 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)!; } - throw new ArgumentException($"参数 {name} 不存在"); + catch (Exception ex) + { + throw new ArgumentException( + $"参数 {name} 的值 '{parameter.Value}' 无法转换为 {typeof(T).Name}", + ex); + } + } + + private static string NormalizeText(string value) + { + return value.Trim().TrimEnd('、', ',', ',', '。', '.', ';', ';', ':', ':'); } /// @@ -178,4 +213,4 @@ public abstract class ImageProcessorBase { return Parameters.ContainsKey(name) ? Parameters[name] : null; } -} \ No newline at end of file +} diff --git a/XP.ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs b/XP.ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs index 28141c8..2dc0bf7 100644 --- a/XP.ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs +++ b/XP.ImageProcessing.Processors/图像增强/SuperResolutionProcessor.cs @@ -68,7 +68,7 @@ public class SuperResolutionProcessor : ImageProcessorBase public override Image Process(Image inputImage) { string model = GetParameter("Model"); - int scale = int.Parse(GetParameter("Scale")); + int scale = GetParameter("Scale"); // 查找模型文件 string modelPath = FindModelFile(model, scale); @@ -316,4 +316,4 @@ public class SuperResolutionProcessor : ImageProcessorBase _logger.Warning("Model file not found: {Model}_x{Scale}.onnx", model, scale); return string.Empty; } -} \ No newline at end of file +} diff --git a/XplorePlane/Services/Pipeline/PipelineExecutionService.cs b/XplorePlane/Services/Pipeline/PipelineExecutionService.cs index 4173732..6f5af09 100644 --- a/XplorePlane/Services/Pipeline/PipelineExecutionService.cs +++ b/XplorePlane/Services/Pipeline/PipelineExecutionService.cs @@ -46,6 +46,19 @@ namespace XplorePlane.Services cancellationToken.ThrowIfCancellationRequested(); 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 .Where(p => p.IsValueValid) .ToDictionary(p => p.Name, p => p.Value); @@ -98,4 +111,4 @@ namespace XplorePlane.Services return scaled; } } -} \ No newline at end of file +} diff --git a/XplorePlane/ViewModels/ImageProcessing/ProcessorParameterVM.cs b/XplorePlane/ViewModels/ImageProcessing/ProcessorParameterVM.cs index 4f01210..a5119df 100644 --- a/XplorePlane/ViewModels/ImageProcessing/ProcessorParameterVM.cs +++ b/XplorePlane/ViewModels/ImageProcessing/ProcessorParameterVM.cs @@ -1,5 +1,7 @@ -using Prism.Mvvm; +using Prism.Mvvm; using System; +using System.Globalization; +using System.Linq; using XP.ImageProcessing.Core; namespace XplorePlane.ViewModels @@ -16,7 +18,8 @@ namespace XplorePlane.ViewModels _value = parameter.Value; MinValue = parameter.MinValue; MaxValue = parameter.MaxValue; - ParameterType = parameter.ValueType?.Name?.ToLower() switch + Options = parameter.Options; + ParameterType = parameter.ValueType?.Name?.ToLowerInvariant() switch { "int32" or "int" => "int", "double" => "double", @@ -30,6 +33,7 @@ namespace XplorePlane.ViewModels public string DisplayName { get; } public object MinValue { get; } public object MaxValue { get; } + public string[]? Options { get; } public string ParameterType { get; } public bool IsValueValid @@ -43,29 +47,161 @@ namespace XplorePlane.ViewModels get => _value; set { - if (SetProperty(ref _value, value)) - ValidateValue(value); + var normalizedValue = NormalizeValue(value); + if (SetProperty(ref _value, normalizedValue)) + ValidateValue(normalizedValue); } } private void ValidateValue(object value) { - if (value == null || MinValue == null || MaxValue == null) + if (value == null) { - IsValueValid = true; + IsValueValid = false; return; } - try + + if (ParameterType == "int") { - double dVal = Convert.ToDouble(value); - double dMin = Convert.ToDouble(MinValue); - double dMax = Convert.ToDouble(MaxValue); - IsValueValid = dVal >= dMin && dVal <= dMax; + IsValueValid = TryConvertToInt(value, out var intValue) && IsWithinRange(intValue); + return; } - catch + + if (ParameterType == "double") { - IsValueValid = true; // 非数值类型不做范围校? + 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 + { + result = Convert.ToInt32(value, CultureInfo.InvariantCulture); + return true; + } + catch + { + 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; + } } } } -} \ No newline at end of file +} diff --git a/XplorePlane/Views/Main/MainWindow.xaml b/XplorePlane/Views/Main/MainWindow.xaml index 1421896..5a34313 100644 --- a/XplorePlane/Views/Main/MainWindow.xaml +++ b/XplorePlane/Views/Main/MainWindow.xaml @@ -319,7 +319,7 @@ telerik:ScreenTip.Title="模块" Size="Medium" SmallImage="/Assets/Icons/Module.png" - Text="模块" /> + Text="检测模块" /> + Text="消息弹窗" /> + Text="插入等待" />