fix(viewport): 修复流水线处理16位图像后直方图退化为0-255的问题
- PipelinePreviewUpdatedPayload 新增 ResultImage16 字段携带16位结果图 - MainViewportService 增加 CurrentHistogramSource16 配对属性,引用配对防止过期数据 - SetManualImage/SetLiveProcessedImage 增加16位伴随图重载 - ViewportPanelViewModel 暴露 HistogramSource16 并在状态同步时更新 - ViewportPanelView 直方图刷新优先使用16位配对源统计全动态范围
This commit is contained in:
@@ -28,16 +28,22 @@ namespace XplorePlane.Events
|
||||
|
||||
public sealed class PipelinePreviewUpdatedPayload
|
||||
{
|
||||
public PipelinePreviewUpdatedPayload(BitmapSource image, string statusMessage, bool isLivePreview = false)
|
||||
public PipelinePreviewUpdatedPayload(BitmapSource image, string statusMessage, bool isLivePreview = false, BitmapSource? resultImage16 = null)
|
||||
{
|
||||
Image = image;
|
||||
StatusMessage = statusMessage;
|
||||
IsLivePreview = isLivePreview;
|
||||
ResultImage16 = resultImage16;
|
||||
}
|
||||
|
||||
public BitmapSource Image { get; }
|
||||
public string StatusMessage { get; }
|
||||
public bool IsLivePreview { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 与显示图配对的 16 位结果图(Gray16),供直方图等需要全动态范围的场景使用;可为 null。
|
||||
/// </summary>
|
||||
public BitmapSource? ResultImage16 { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,6 +23,13 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
/// </summary>
|
||||
ImageSource? LatestResultImage16 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 与当前显示图配对的 16 位直方图数据源(Gray16 BitmapSource)。
|
||||
/// 仅当当前显示图正是其配对的归一化 8 位图时返回非 null,供直方图控件按全动态范围统计。
|
||||
/// 显示图切换为原图 / 实时帧等其它来源后自动失配返回 null。
|
||||
/// </summary>
|
||||
ImageSource? CurrentHistogramSource16 { get; }
|
||||
|
||||
event EventHandler StateChanged;
|
||||
|
||||
void SetRealtimeDisplayEnabled(bool isEnabled);
|
||||
@@ -33,8 +40,19 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
|
||||
void SetManualImage(ImageSource image, string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 设置手动图像,并附带与之配对的 16 位直方图数据源。
|
||||
/// 用于流水线处理结果:显示图为归一化 8 位图,直方图使用 16 位伴随图统计全动态范围。
|
||||
/// </summary>
|
||||
void SetManualImage(ImageSource image, string filePath, ImageSource? histogramSource16);
|
||||
|
||||
void SetLiveProcessedImage(ImageSource image, string label);
|
||||
|
||||
/// <summary>
|
||||
/// 设置实时处理结果图像,并附带与之配对的 16 位直方图数据源。
|
||||
/// </summary>
|
||||
void SetLiveProcessedImage(ImageSource image, string label, ImageSource? histogramSource16);
|
||||
|
||||
void ClearLiveProcessedImage();
|
||||
|
||||
/// <summary>
|
||||
@@ -129,6 +147,11 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
private string _latestManualInfo = "未加载手动图像";
|
||||
private ImageSource? _latestResultImage16;
|
||||
|
||||
// 与显示图配对的 16 位直方图源。_histogramSource16Owner 记录该 16 位源所对应的 8 位显示图,
|
||||
// 仅当当前显示图 == owner 时,CurrentHistogramSource16 才返回该 16 位源,从而避免过期数据。
|
||||
private ImageSource? _histogramSource16;
|
||||
private ImageSource? _histogramSource16Owner;
|
||||
|
||||
public MainViewportService(ILoggerService logger)
|
||||
{
|
||||
_logger = logger?.ForModule<MainViewportService>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
@@ -229,6 +252,20 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
set { lock (_syncRoot) { _latestResultImage16 = value; } }
|
||||
}
|
||||
|
||||
public ImageSource? CurrentHistogramSource16
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
// 仅当配对的 8 位显示图仍是当前显示图时,16 位源才有效
|
||||
return ReferenceEquals(_histogramSource16Owner, _currentDisplayImage)
|
||||
? _histogramSource16
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler StateChanged;
|
||||
|
||||
public void SetRealtimeDisplayEnabled(bool isEnabled)
|
||||
@@ -360,6 +397,9 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
}
|
||||
|
||||
public void SetManualImage(ImageSource image, string filePath)
|
||||
=> SetManualImage(image, filePath, null);
|
||||
|
||||
public void SetManualImage(ImageSource image, string filePath, ImageSource? histogramSource16)
|
||||
{
|
||||
if (image == null)
|
||||
return;
|
||||
@@ -381,13 +421,17 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
_currentSourceMode = MainViewportSourceMode.ManualImage;
|
||||
_currentDisplayImage = _latestManualImage;
|
||||
_currentDisplayInfo = _latestManualInfo;
|
||||
SetHistogramSource16_NoLock(histogramSource16, _currentDisplayImage);
|
||||
}
|
||||
|
||||
_logger.Info("[图像链路] MainViewportService.SetManualImage:已更新图像 {FileName},触发 StateChanged", fileName);
|
||||
_logger.Info("[图像链路] MainViewportService.SetManualImage:已更新图像 {FileName},has16bit={Has16bit},触发 StateChanged", fileName, histogramSource16 != null);
|
||||
RaiseStateChanged();
|
||||
}
|
||||
|
||||
public void SetLiveProcessedImage(ImageSource image, string label)
|
||||
=> SetLiveProcessedImage(image, label, null);
|
||||
|
||||
public void SetLiveProcessedImage(ImageSource image, string label, ImageSource? histogramSource16)
|
||||
{
|
||||
if (image == null)
|
||||
return;
|
||||
@@ -405,6 +449,12 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
{
|
||||
_currentDisplayImage = _latestLiveProcessedImage;
|
||||
_currentDisplayInfo = _latestLiveProcessedInfo;
|
||||
SetHistogramSource16_NoLock(histogramSource16, _currentDisplayImage);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未启用实时显示时,结果图不上屏,清除配对避免误用
|
||||
SetHistogramSource16_NoLock(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,6 +604,16 @@ namespace XplorePlane.Services.Main.Viewport
|
||||
RaiseStateChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置与显示图配对的 16 位直方图源。owner 为该 16 位源对应的 8 位显示图;
|
||||
/// 传入 null 表示清除配对(当前显示图无对应 16 位数据)。
|
||||
/// </summary>
|
||||
private void SetHistogramSource16_NoLock(ImageSource? histogramSource16, ImageSource? owner)
|
||||
{
|
||||
_histogramSource16 = histogramSource16;
|
||||
_histogramSource16Owner = histogramSource16 != null ? owner : null;
|
||||
}
|
||||
|
||||
private void ApplyLiveDetectorDisplay_NoLock()
|
||||
{
|
||||
if (_isLiveProcessedImageActive && _latestLiveProcessedImage != null)
|
||||
|
||||
@@ -567,7 +567,7 @@ namespace XplorePlane.ViewModels.ImageProcessing
|
||||
_logger.Info("[LivePipelineDiag] ExecutePipelineAsync completed: isLiveExecution={IsLiveExecution}, output={OutputSize}",
|
||||
isLiveExecution,
|
||||
DescribeBitmap(execResult.Image));
|
||||
PublishPipelinePreviewUpdated(execResult.Image, StatusMessage, isLiveExecution);
|
||||
PublishPipelinePreviewUpdated(execResult.Image, StatusMessage, isLiveExecution, execResult.ResultImage16);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -748,7 +748,7 @@ namespace XplorePlane.ViewModels.ImageProcessing
|
||||
.Publish(new ManualImageLoadedPayload(bitmap, filePath));
|
||||
}
|
||||
|
||||
private void PublishPipelinePreviewUpdated(BitmapSource bitmap, string statusMessage, bool isLivePreview = false)
|
||||
private void PublishPipelinePreviewUpdated(BitmapSource bitmap, string statusMessage, bool isLivePreview = false, BitmapSource resultImage16 = null)
|
||||
{
|
||||
if (bitmap == null)
|
||||
{
|
||||
@@ -756,12 +756,13 @@ namespace XplorePlane.ViewModels.ImageProcessing
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info("[LivePipelineDiag] PublishPipelinePreviewUpdated: isLivePreview={IsLivePreview}, image={ImageSize}, statusMessage={Msg}",
|
||||
_logger.Info("[LivePipelineDiag] PublishPipelinePreviewUpdated: isLivePreview={IsLivePreview}, image={ImageSize}, has16bit={Has16bit}, statusMessage={Msg}",
|
||||
isLivePreview,
|
||||
DescribeBitmap(bitmap),
|
||||
resultImage16 != null,
|
||||
statusMessage);
|
||||
_eventAggregator.GetEvent<PipelinePreviewUpdatedEvent>()
|
||||
.Publish(new PipelinePreviewUpdatedPayload(bitmap, statusMessage, isLivePreview));
|
||||
.Publish(new PipelinePreviewUpdatedPayload(bitmap, statusMessage, isLivePreview, resultImage16));
|
||||
}
|
||||
|
||||
private bool ShouldLogLiveSourceSkip()
|
||||
|
||||
@@ -1873,12 +1873,12 @@ namespace XplorePlane.ViewModels.Main
|
||||
if (payload.IsLivePreview && _mainViewportService.IsRealtimeDisplayEnabled)
|
||||
{
|
||||
_logger.Info("[LivePipelineDiag] Routing preview to live viewport display.");
|
||||
_mainViewportService.SetLiveProcessedImage(payload.Image, payload.StatusMessage);
|
||||
_mainViewportService.SetLiveProcessedImage(payload.Image, payload.StatusMessage, payload.ResultImage16);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("[LivePipelineDiag] Routing preview to manual image display.");
|
||||
_mainViewportService.SetManualImage(payload.Image, string.Empty);
|
||||
_mainViewportService.SetManualImage(payload.Image, string.Empty, payload.ResultImage16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace XplorePlane.ViewModels.Main
|
||||
private string _infoOverlayText = string.Empty;
|
||||
|
||||
private ImageSource _imageSource;
|
||||
private ImageSource _histogramSource16;
|
||||
private string _imageInfo = "等待探测器图像...";
|
||||
private MeasurementToolMode _currentMeasurementMode = MeasurementToolMode.None;
|
||||
private Point? _measurePoint1;
|
||||
@@ -104,6 +105,16 @@ namespace XplorePlane.ViewModels.Main
|
||||
set => SetProperty(ref _imageSource, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 与当前显示图配对的 16 位直方图数据源(Gray16);无配对时为 null。
|
||||
/// 直方图控件优先使用它按全动态范围(0-65535)统计。
|
||||
/// </summary>
|
||||
public ImageSource HistogramSource16
|
||||
{
|
||||
get => _histogramSource16;
|
||||
private set => SetProperty(ref _histogramSource16, value);
|
||||
}
|
||||
|
||||
public string ImageInfo
|
||||
{
|
||||
get => _imageInfo;
|
||||
@@ -298,6 +309,8 @@ namespace XplorePlane.ViewModels.Main
|
||||
|
||||
private void UpdateFromState(bool updateInfo)
|
||||
{
|
||||
// 先更新 16 位配对源,再更新显示图;View 监听 ImageSource 变化刷新直方图时可读到最新配对
|
||||
HistogramSource16 = _mainViewportService.CurrentHistogramSource16;
|
||||
ImageSource = _mainViewportService.CurrentDisplayImage;
|
||||
|
||||
// Task 5.8: Sync IsRealtimeEnabled from service
|
||||
|
||||
@@ -1304,6 +1304,7 @@ namespace XplorePlane.Views.Main
|
||||
private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(ViewportPanelViewModel.ImageSource)
|
||||
|| e.PropertyName == nameof(ViewportPanelViewModel.HistogramSource16)
|
||||
|| e.PropertyName == nameof(ViewportPanelViewModel.IsHistogramVisible))
|
||||
{
|
||||
UpdateHistogramFromCurrentImage();
|
||||
@@ -1321,7 +1322,10 @@ namespace XplorePlane.Views.Main
|
||||
if (!vm.IsHistogramVisible)
|
||||
return;
|
||||
|
||||
if (vm.ImageSource is not BitmapSource bitmap)
|
||||
// 优先使用与显示图配对的 16 位数据源,保证 X 轴按全动态范围(0-65535)统计;
|
||||
// 无配对时回退到当前显示图(通常为 8 位)。
|
||||
var histogramImage = vm.HistogramSource16 as BitmapSource ?? vm.ImageSource as BitmapSource;
|
||||
if (histogramImage == null)
|
||||
{
|
||||
HistogramOverlay.Clear();
|
||||
return;
|
||||
@@ -1329,7 +1333,7 @@ namespace XplorePlane.Views.Main
|
||||
|
||||
try
|
||||
{
|
||||
var (pixels, width, height, bitDepth) = CopyHistogramPixels(bitmap);
|
||||
var (pixels, width, height, bitDepth) = CopyHistogramPixels(histogramImage);
|
||||
HistogramOverlay.UpdateImage(pixels, width, height, bitDepth);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user