diff --git a/XP.Common/Controls/ImageHistogram/ChartRenderer.cs b/XP.Common/Controls/ImageHistogram/ChartRenderer.cs index c51fa7bf..5e9cec85 100644 --- a/XP.Common/Controls/ImageHistogram/ChartRenderer.cs +++ b/XP.Common/Controls/ImageHistogram/ChartRenderer.cs @@ -15,8 +15,13 @@ namespace XP.Common.Controls.ImageHistogram { private readonly RadCartesianChart _chart; private readonly ScatterAreaSeries _areaSeries; - private readonly ScatterPointSeries _peakSeries; - private readonly LinearAxis _xAxis; + private readonly ScatterPointSeries _peakSeries; + private readonly ScatterLineSeries _bgaLowThresholdSeries; + private readonly ScatterLineSeries _bgaHighThresholdSeries; + private readonly ScatterLineSeries _voidLowThresholdSeries; + private readonly ScatterLineSeries _voidHighThresholdSeries; + private readonly LinearAxis _xAxis; + private (double? BgaLow, double? BgaHigh, double? VoidLow, double? VoidHigh) _thresholds; /// /// 16 位数据聚合因子 | 16-bit data aggregation factor @@ -31,13 +36,21 @@ namespace XP.Common.Controls.ImageHistogram /// X 轴 | X axis public ChartRenderer( RadCartesianChart chart, - ScatterAreaSeries areaSeries, - ScatterPointSeries peakSeries, - LinearAxis xAxis) + ScatterAreaSeries areaSeries, + ScatterPointSeries peakSeries, + ScatterLineSeries bgaLowThresholdSeries, + ScatterLineSeries bgaHighThresholdSeries, + ScatterLineSeries voidLowThresholdSeries, + ScatterLineSeries voidHighThresholdSeries, + LinearAxis xAxis) { _chart = chart ?? throw new ArgumentNullException(nameof(chart)); _areaSeries = areaSeries ?? throw new ArgumentNullException(nameof(areaSeries)); - _peakSeries = peakSeries ?? throw new ArgumentNullException(nameof(peakSeries)); + _peakSeries = peakSeries ?? throw new ArgumentNullException(nameof(peakSeries)); + _bgaLowThresholdSeries = bgaLowThresholdSeries ?? throw new ArgumentNullException(nameof(bgaLowThresholdSeries)); + _bgaHighThresholdSeries = bgaHighThresholdSeries ?? throw new ArgumentNullException(nameof(bgaHighThresholdSeries)); + _voidLowThresholdSeries = voidLowThresholdSeries ?? throw new ArgumentNullException(nameof(voidLowThresholdSeries)); + _voidHighThresholdSeries = voidHighThresholdSeries ?? throw new ArgumentNullException(nameof(voidHighThresholdSeries)); _xAxis = xAxis ?? throw new ArgumentNullException(nameof(xAxis)); } @@ -116,16 +129,31 @@ namespace XP.Common.Controls.ImageHistogram _peakSeries.ItemsSource = DetectPeaks(displayData, histogram.Length == 65536); // 设置 Y 轴范围 | Set Y axis range - UpdateYAxis(displayData, isLogarithmic); - } + UpdateYAxis(displayData, isLogarithmic); + UpdateThresholdLines(displayData.Max()); + } + + public void SetThresholds(double? bgaLow, double? bgaHigh, double? voidLow, double? voidHigh) + { + _thresholds = (bgaLow, bgaHigh, voidLow, voidHigh); + var max = _areaSeries.ItemsSource is IEnumerable points + ? points.Select(point => point.Frequency).DefaultIfEmpty(1).Max() + : 1; + UpdateThresholdLines(max); + } /// /// 清空图表,恢复初始状态 | Clear chart, restore initial state /// - public void Clear() - { - _areaSeries.ItemsSource = null; - _peakSeries.ItemsSource = null; + public void Clear() + { + _thresholds = (null, null, null, null); + _areaSeries.ItemsSource = null; + _peakSeries.ItemsSource = null; + _bgaLowThresholdSeries.ItemsSource = null; + _bgaHighThresholdSeries.ItemsSource = null; + _voidLowThresholdSeries.ItemsSource = null; + _voidHighThresholdSeries.ItemsSource = null; // X 轴范围重置为 0-255 | Reset X axis range to 0-255 _xAxis.Minimum = 0; @@ -133,7 +161,28 @@ namespace XP.Common.Controls.ImageHistogram // Y 轴范围重置为 0-1 | Reset Y axis range to 0-1 SetYAxisRange(0, 1, isLogarithmic: false); - } + } + + private void UpdateThresholdLines(long maximumFrequency) + { + var max = Math.Max(1, maximumFrequency); + _bgaLowThresholdSeries.ItemsSource = CreateVerticalLine(_thresholds.BgaLow, max); + _bgaHighThresholdSeries.ItemsSource = CreateVerticalLine(_thresholds.BgaHigh, max); + _voidLowThresholdSeries.ItemsSource = CreateVerticalLine(_thresholds.VoidLow, max); + _voidHighThresholdSeries.ItemsSource = CreateVerticalLine(_thresholds.VoidHigh, max); + } + + private static IReadOnlyList? CreateVerticalLine(double? grayLevel, long maximumFrequency) + { + if (!grayLevel.HasValue || grayLevel.Value < 0) + return null; + + return new[] + { + new HistogramDataPoint { GrayLevel = grayLevel.Value, Frequency = 0 }, + new HistogramDataPoint { GrayLevel = grayLevel.Value, Frequency = maximumFrequency } + }; + } /// /// 获取当前数据点数量 | Get current data point count @@ -218,7 +267,9 @@ namespace XP.Common.Controls.ImageHistogram ? item.Index * AggregationFactor + AggregationFactor / 2.0 : item.Index, Frequency = item.Frequency, - Label = FormatFrequency(item.Frequency), + Label = FormatGrayLevel(is16Bit + ? item.Index * AggregationFactor + AggregationFactor / 2.0 + : item.Index), LabelMargin = item.Frequency >= maximum * 0.75 ? new Thickness(0, 4 + (index % 2) * 14, 0, 0) : new Thickness(0, -18 - (index % 2) * 14, 0, 0) @@ -226,14 +277,7 @@ namespace XP.Common.Controls.ImageHistogram .ToArray(); } - private static string FormatFrequency(long value) - { - if (value >= 1_000_000) - return $"{value / 1_000_000d:0.#}M"; - if (value >= 1_000) - return $"{value / 1_000d:0.#}K"; - return value.ToString(); - } + private static string FormatGrayLevel(double value) => $"{Math.Round(value):0}"; /// /// 更新 Y 轴范围 | Update Y axis range diff --git a/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml b/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml index 4ac00ee6..77721a34 100644 --- a/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml +++ b/XP.Common/Controls/ImageHistogram/ImageHistogramControl.xaml @@ -65,6 +65,10 @@ + + + + 设置 BGA 定位和空洞分割的灰度阈值线。 + public void SetThresholds(double? bgaLow, double? bgaHigh, double? voidLow, double? voidHigh) + { + Dispatcher.InvokeAsync(() => _chartRenderer?.SetThresholds(bgaLow, bgaHigh, voidLow, voidHigh)); + } + private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e) { _isExpanded = !_isExpanded; diff --git a/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml.cs b/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml.cs index 7c460796..fa3aae71 100644 --- a/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml.cs +++ b/XplorePlane/Views/ImageProcessing/BgaDetectionPanel.xaml.cs @@ -1,4 +1,5 @@ using System.Windows; +using XP.Common.Controls.ImageHistogram; using XP.ImageProcessing.RoiControl.Controls; using XplorePlane.Services.Main.Viewport; using XplorePlane.ViewModels.Cnc; @@ -20,6 +21,12 @@ namespace XplorePlane.Views.ImageProcessing Loaded += (s, e) => { + if (DataContext is BgaDetectionViewModel loadedVm) + { + loadedVm.PropertyChanged += BgaVmPropertyChanged; + UpdateHistogramThresholds(loadedVm); + } + var mainWin = Owner as MainWindow; if (mainWin != null) { @@ -40,10 +47,36 @@ namespace XplorePlane.Views.ImageProcessing Closed += (s, e) => { if (DataContext is BgaDetectionViewModel vm) + { + vm.PropertyChanged -= BgaVmPropertyChanged; + FindChild(Owner)?.SetThresholds(null, null, null, null); vm.RestoreContextMenu(); + } }; } + private void BgaVmPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (sender is BgaDetectionViewModel vm && + (e.PropertyName == nameof(BgaDetectionViewModel.BgaThresholdLow) || + e.PropertyName == nameof(BgaDetectionViewModel.BgaThresholdHigh) || + e.PropertyName == nameof(BgaDetectionViewModel.MinThreshold) || + e.PropertyName == nameof(BgaDetectionViewModel.MaxThreshold))) + { + UpdateHistogramThresholds(vm); + } + } + + private void UpdateHistogramThresholds(BgaDetectionViewModel vm) + { + if (Owner is MainWindow mainWindow) + { + FindChild(mainWindow)?.SetThresholds( + vm.BgaThresholdLow, vm.BgaThresholdHigh, + vm.MinThreshold, vm.MaxThreshold); + } + } + private void Close_Click(object sender, RoutedEventArgs e) => Close(); private static T FindChild(DependencyObject parent) where T : DependencyObject