feat: show histogram gray peaks and threshold lines
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 16 位数据聚合因子 | 16-bit data aggregation factor
|
||||
@@ -31,13 +36,21 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
/// <param name="xAxis">X 轴 | X axis</param>
|
||||
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<HistogramDataPoint> points
|
||||
? points.Select(point => point.Frequency).DefaultIfEmpty(1).Max()
|
||||
: 1;
|
||||
UpdateThresholdLines(max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空图表,恢复初始状态 | Clear chart, restore initial state
|
||||
/// </summary>
|
||||
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<HistogramDataPoint>? 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 }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前数据点数量 | 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}";
|
||||
|
||||
/// <summary>
|
||||
/// 更新 Y 轴范围 | Update Y axis range
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
</DataTemplate>
|
||||
</telerik:ScatterPointSeries.PointTemplate>
|
||||
</telerik:ScatterPointSeries>
|
||||
<telerik:ScatterLineSeries x:Name="BgaLowThresholdSeries" XValueBinding="GrayLevel" YValueBinding="Frequency" Stroke="#FFFF7043" StrokeThickness="1" IsHitTestVisible="False" />
|
||||
<telerik:ScatterLineSeries x:Name="BgaHighThresholdSeries" XValueBinding="GrayLevel" YValueBinding="Frequency" Stroke="#FFE53935" StrokeThickness="1" IsHitTestVisible="False" />
|
||||
<telerik:ScatterLineSeries x:Name="VoidLowThresholdSeries" XValueBinding="GrayLevel" YValueBinding="Frequency" Stroke="#FFFFA000" StrokeThickness="1" IsHitTestVisible="False" />
|
||||
<telerik:ScatterLineSeries x:Name="VoidHighThresholdSeries" XValueBinding="GrayLevel" YValueBinding="Frequency" Stroke="#FF8E24AA" StrokeThickness="1" IsHitTestVisible="False" />
|
||||
</telerik:RadCartesianChart.Series>
|
||||
</telerik:RadCartesianChart>
|
||||
<TextBlock x:Name="NoDataPlaceholder"
|
||||
|
||||
@@ -207,7 +207,10 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_chartRenderer = new ChartRenderer(HistogramChart, HistogramBarSeries, PeakLabelSeries, XAxis);
|
||||
_chartRenderer = new ChartRenderer(
|
||||
HistogramChart, HistogramBarSeries, PeakLabelSeries,
|
||||
BgaLowThresholdSeries, BgaHighThresholdSeries,
|
||||
VoidLowThresholdSeries, VoidHighThresholdSeries, XAxis);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -231,7 +234,7 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateImage(byte[] rawData, int width, int height, int bitDepth)
|
||||
public void UpdateImage(byte[] rawData, int width, int height, int bitDepth)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -364,6 +367,12 @@ namespace XP.Common.Controls.ImageHistogram
|
||||
_chartRenderer = null;
|
||||
}
|
||||
|
||||
/// <summary>设置 BGA 定位和空洞分割的灰度阈值线。</summary>
|
||||
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;
|
||||
|
||||
@@ -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<ImageHistogramControl>(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<ImageHistogramControl>(mainWindow)?.SetThresholds(
|
||||
vm.BgaThresholdLow, vm.BgaThresholdHigh,
|
||||
vm.MinThreshold, vm.MaxThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
private static T FindChild<T>(DependencyObject parent) where T : DependencyObject
|
||||
|
||||
Reference in New Issue
Block a user