From a4559e576d7e853fa25af5c7f2c547ee5e393a01 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 9 Jul 2026 12:04:00 +0200 Subject: [PATCH] Preview: geometry-following arcs, true beam center, unified colours, predictions Draw the resolution ring and azimuthal-ROI outlines by sweeping ResPhiToPxl through the geometry (a shared DrawArc/DrawThickLine helper), so they trace the true conic on a tilted detector instead of PONI-centred circles. Draw the beam crosshair at GetDirectBeam_pxl() (the real beam/detector intersection) rather than the stored PONI. Unify the spot overlay colours with the viewer: green = not indexed, magenta = indexed (primary lattice), cyan = ice ring, coral = secondary/further lattice. Add Bragg-prediction overlay (dark-red circles, skipping systematic absences) gated on a new show_predictions preview setting, wired through the OpenAPI spec, the image.jpeg HTTP handler, and the frontend toggle (+regenerated TS client). Co-Authored-By: Claude Fable 5 --- broker/JFJochBrokerHttp.cpp | 3 + broker/JFJochBrokerHttp.h | 1 + broker/jfjoch_api.yaml | 8 ++ frontend/src/client/index.ts | 2 +- frontend/src/client/types.gen.ts | 9 ++ frontend/src/client/zod.gen.ts | 6 + frontend/src/components/PreviewImage.tsx | 12 ++ preview/PreviewImage.cpp | 170 ++++++++++++++++------- preview/PreviewImage.h | 15 +- 9 files changed, 173 insertions(+), 53 deletions(-) diff --git a/broker/JFJochBrokerHttp.cpp b/broker/JFJochBrokerHttp.cpp index f54c6dab..25316a96 100644 --- a/broker/JFJochBrokerHttp.cpp +++ b/broker/JFJochBrokerHttp.cpp @@ -263,6 +263,7 @@ void JFJochBrokerHttp::register_routes(httplib::Server &server) { parse_query_value(req, "show_user_mask"), parse_query_value(req, "show_roi"), parse_query_value(req, "show_spots"), + parse_query_value(req, "show_predictions"), parse_query_value(req, "show_beam_center"), parse_query_value(req, "saturation"), parse_query_value(req, "jpeg_quality"), @@ -679,6 +680,7 @@ void JFJochBrokerHttp::image_buffer_image_jpeg_get(const std::optional const std::optional &showUserMask, const std::optional &showRoi, const std::optional &showSpots, + const std::optional &showPredictions, const std::optional &showBeamCenter, const std::optional &saturation, const std::optional &jpegQuality, @@ -692,6 +694,7 @@ void JFJochBrokerHttp::image_buffer_image_jpeg_get(const std::optional settings.show_user_mask = showUserMask.value_or(false); settings.show_roi = showRoi.value_or(false); settings.show_spots = showSpots.value_or(true); + settings.show_predictions = showPredictions.value_or(false); settings.saturation_value = saturation; settings.background_value = 0.0; settings.jpeg_quality = jpegQuality.value_or(100); diff --git a/broker/JFJochBrokerHttp.h b/broker/JFJochBrokerHttp.h index 329798c7..6d6ddc85 100644 --- a/broker/JFJochBrokerHttp.h +++ b/broker/JFJochBrokerHttp.h @@ -136,6 +136,7 @@ class JFJochBrokerHttp { void image_buffer_image_cbor_get(const std::optional &id, httplib::Response &response); void image_buffer_image_jpeg_get(const std::optional &id, const std::optional &showUserMask, const std::optional &showRoi, const std::optional &showSpots, + const std::optional &showPredictions, const std::optional &showBeamCenter, const std::optional &saturation, const std::optional &jpegQuality, const std::optional &showResRing, const std::optional &color, const std::optional &showResEst, diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index f814d709..ec966344 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -168,6 +168,13 @@ components: type: boolean default: true description: "Show spot finding results on the image" + show_predictions: + in: query + name: show_predictions + schema: + type: boolean + default: false + description: "Show Bragg spot predictions on the image (if available for the image)" show_beam_center: in: query name: show_beam_center @@ -3566,6 +3573,7 @@ paths: - $ref: '#/components/parameters/show_user_mask' - $ref: '#/components/parameters/show_roi' - $ref: '#/components/parameters/show_spots' + - $ref: '#/components/parameters/show_predictions' - $ref: '#/components/parameters/show_beam_center' - $ref: '#/components/parameters/saturation' - $ref: '#/components/parameters/jpeg_quality' diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts index 4bb48fbe..5a529849 100644 --- a/frontend/src/client/index.ts +++ b/frontend/src/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export { getConfigAzimInt, getConfigBraggIntegration, getConfigDarkMask, getConfigDetector, getConfigFileWriter, getConfigImageFormat, getConfigIndexing, getConfigInstrument, getConfigMask, getConfigMaskTiff, getConfigRoi, getConfigSelectDetector, getConfigSpotFinding, getConfigUserMask, getConfigUserMaskTiff, getConfigZeromqMetadata, getConfigZeromqPreview, getDetectorStatus, getFpgaStatus, getImageBufferImageCbor, getImageBufferImageJpeg, getImageBufferImageTiff, getImageBufferStartCbor, getImageBufferStatus, getImagePusherStatus, getPreviewPedestalTiff, getPreviewPlot, getPreviewPlotBin, getResultScan, getStatistics, getStatisticsCalibration, getStatisticsDataCollection, getStatus, getVersion, getXfelEventCode, getXfelPulseId, type Options, postCancel, postConfigImageFormatConversion, postConfigImageFormatRaw, postDeactivate, postImageBufferClear, postInitialize, postPedestal, postStart, postTrigger, postWaitTillDone, postWaitUntilRunning, putConfigAzimInt, putConfigBraggIntegration, putConfigDarkMask, putConfigDetector, putConfigFileWriter, putConfigImageFormat, putConfigIndexing, putConfigInstrument, putConfigInternalGeneratorImage, putConfigInternalGeneratorImageTiff, putConfigRoi, putConfigSelectDetector, putConfigSpotFinding, putConfigUserMask, putConfigUserMaskTiff, putConfigZeromqMetadata, putConfigZeromqPreview } from './sdk.gen'; -export { type azim_int_settings, azint_unit, type background, type binning, type bragg_integration_settings, type broker_status, type calibration_statistics, type ClientOptions, color_scale, type crystal_lattice, type dark_mask_settings, type dataset_settings, type detector, type detector_list, type detector_list_element, type detector_module, detector_module_direction, detector_power_state, type detector_selection, type detector_settings, detector_state, type detector_status, detector_timing, detector_type, type error_message, type experimental_coord, file_writer_format, type file_writer_settings, type fill_value, type fpga_status, geom_refinement_algorithm, type getConfigAzimIntData, type getConfigAzimIntResponse, type getConfigAzimIntResponses, type getConfigBraggIntegrationData, type getConfigBraggIntegrationResponse, type getConfigBraggIntegrationResponses, type getConfigDarkMaskData, type getConfigDarkMaskResponse, type getConfigDarkMaskResponses, type getConfigDetectorData, type getConfigDetectorResponse, type getConfigDetectorResponses, type getConfigFileWriterData, type getConfigFileWriterResponse, type getConfigFileWriterResponses, type getConfigImageFormatData, type getConfigImageFormatResponse, type getConfigImageFormatResponses, type getConfigIndexingData, type getConfigIndexingResponse, type getConfigIndexingResponses, type getConfigInstrumentData, type getConfigInstrumentResponse, type getConfigInstrumentResponses, type getConfigMaskData, type getConfigMaskResponse, type getConfigMaskResponses, type getConfigMaskTiffData, type getConfigMaskTiffResponse, type getConfigMaskTiffResponses, type getConfigRoiData, type getConfigRoiResponse, type getConfigRoiResponses, type getConfigSelectDetectorData, type getConfigSelectDetectorResponse, type getConfigSelectDetectorResponses, type getConfigSpotFindingData, type getConfigSpotFindingResponse, type getConfigSpotFindingResponses, type getConfigUserMaskData, type getConfigUserMaskResponse, type getConfigUserMaskResponses, type getConfigUserMaskTiffData, type getConfigUserMaskTiffResponse, type getConfigUserMaskTiffResponses, type getConfigZeromqMetadataData, type getConfigZeromqMetadataResponse, type getConfigZeromqMetadataResponses, type getConfigZeromqPreviewData, type getConfigZeromqPreviewResponse, type getConfigZeromqPreviewResponses, type getDetectorStatusData, type getDetectorStatusError, type getDetectorStatusErrors, type getDetectorStatusResponse, type getDetectorStatusResponses, type getFpgaStatusData, type getFpgaStatusResponse, type getFpgaStatusResponses, type getImageBufferImageCborData, type getImageBufferImageCborError, type getImageBufferImageCborErrors, type getImageBufferImageCborResponse, type getImageBufferImageCborResponses, type getImageBufferImageJpegData, type getImageBufferImageJpegError, type getImageBufferImageJpegErrors, type getImageBufferImageJpegResponse, type getImageBufferImageJpegResponses, type getImageBufferImageTiffData, type getImageBufferImageTiffErrors, type getImageBufferImageTiffResponse, type getImageBufferImageTiffResponses, type getImageBufferStartCborData, type getImageBufferStartCborError, type getImageBufferStartCborErrors, type getImageBufferStartCborResponse, type getImageBufferStartCborResponses, type getImageBufferStatusData, type getImageBufferStatusError, type getImageBufferStatusErrors, type getImageBufferStatusResponse, type getImageBufferStatusResponses, type getImagePusherStatusData, type getImagePusherStatusError, type getImagePusherStatusErrors, type getImagePusherStatusResponse, type getImagePusherStatusResponses, type getPreviewPedestalTiffData, type getPreviewPedestalTiffErrors, type getPreviewPedestalTiffResponse, type getPreviewPedestalTiffResponses, type getPreviewPlotBinData, type getPreviewPlotBinError, type getPreviewPlotBinErrors, type getPreviewPlotBinResponse, type getPreviewPlotBinResponses, type getPreviewPlotData, type getPreviewPlotError, type getPreviewPlotErrors, type getPreviewPlotResponse, type getPreviewPlotResponses, type getResultScanData, type getResultScanError, type getResultScanErrors, type getResultScanResponse, type getResultScanResponses, type getStatisticsCalibrationData, type getStatisticsCalibrationResponse, type getStatisticsCalibrationResponses, type getStatisticsData, type getStatisticsDataCollectionData, type getStatisticsDataCollectionErrors, type getStatisticsDataCollectionResponse, type getStatisticsDataCollectionResponses, type getStatisticsResponse, type getStatisticsResponses, type getStatusData, type getStatusResponse, type getStatusResponses, type getVersionData, type getVersionResponse, type getVersionResponses, type getXfelEventCodeData, type getXfelEventCodeErrors, type getXfelEventCodeResponse, type getXfelEventCodeResponses, type getXfelPulseIdData, type getXfelPulseIdErrors, type getXfelPulseIdResponse, type getXfelPulseIdResponses, type grid_scan, type image_buffer_status, type image_format_settings, type image_id, type image_pusher_status, image_pusher_type, indexing_algorithm, type indexing_settings, type instrument_metadata, integration_model, type jfjoch_settings, type jfjoch_statistics, type jpeg_quality, type measurement_statistics, type pcie_devices, type pixel_mask_statistics, type plot, plot_type, plot_unit_x, type plots, type postCancelData, type postCancelResponses, type postConfigImageFormatConversionData, type postConfigImageFormatConversionError, type postConfigImageFormatConversionErrors, type postConfigImageFormatConversionResponses, type postConfigImageFormatRawData, type postConfigImageFormatRawError, type postConfigImageFormatRawErrors, type postConfigImageFormatRawResponses, type postDeactivateData, type postDeactivateError, type postDeactivateErrors, type postDeactivateResponses, type postImageBufferClearData, type postImageBufferClearError, type postImageBufferClearErrors, type postImageBufferClearResponses, type postInitializeData, type postInitializeError, type postInitializeErrors, type postInitializeResponses, type postPedestalData, type postPedestalError, type postPedestalErrors, type postPedestalResponses, type postStartData, type postStartError, type postStartErrors, type postStartResponses, type postTriggerData, type postTriggerResponses, type postWaitTillDoneData, type postWaitTillDoneError, type postWaitTillDoneErrors, type postWaitTillDoneResponses, type postWaitUntilRunningData, type postWaitUntilRunningError, type postWaitUntilRunningErrors, type postWaitUntilRunningResponses, type putConfigAzimIntData, type putConfigAzimIntError, type putConfigAzimIntErrors, type putConfigAzimIntResponses, type putConfigBraggIntegrationData, type putConfigBraggIntegrationError, type putConfigBraggIntegrationErrors, type putConfigBraggIntegrationResponses, type putConfigDarkMaskData, type putConfigDarkMaskError, type putConfigDarkMaskErrors, type putConfigDarkMaskResponses, type putConfigDetectorData, type putConfigDetectorError, type putConfigDetectorErrors, type putConfigDetectorResponses, type putConfigFileWriterData, type putConfigFileWriterError, type putConfigFileWriterErrors, type putConfigFileWriterResponses, type putConfigImageFormatData, type putConfigImageFormatError, type putConfigImageFormatErrors, type putConfigImageFormatResponses, type putConfigIndexingData, type putConfigIndexingError, type putConfigIndexingErrors, type putConfigIndexingResponses, type putConfigInstrumentData, type putConfigInstrumentError, type putConfigInstrumentErrors, type putConfigInstrumentResponses, type putConfigInternalGeneratorImageData, type putConfigInternalGeneratorImageError, type putConfigInternalGeneratorImageErrors, type putConfigInternalGeneratorImageResponses, type putConfigInternalGeneratorImageTiffData, type putConfigInternalGeneratorImageTiffError, type putConfigInternalGeneratorImageTiffErrors, type putConfigInternalGeneratorImageTiffResponses, type putConfigRoiData, type putConfigRoiError, type putConfigRoiErrors, type putConfigRoiResponses, type putConfigSelectDetectorData, type putConfigSelectDetectorError, type putConfigSelectDetectorErrors, type putConfigSelectDetectorResponses, type putConfigSpotFindingData, type putConfigSpotFindingError, type putConfigSpotFindingErrors, type putConfigSpotFindingResponses, type putConfigUserMaskData, type putConfigUserMaskError, type putConfigUserMaskErrors, type putConfigUserMaskResponses, type putConfigUserMaskTiffData, type putConfigUserMaskTiffError, type putConfigUserMaskTiffErrors, type putConfigUserMaskTiffResponses, type putConfigZeromqMetadataData, type putConfigZeromqMetadataError, type putConfigZeromqMetadataErrors, type putConfigZeromqMetadataResponses, type putConfigZeromqPreviewData, type putConfigZeromqPreviewError, type putConfigZeromqPreviewErrors, type putConfigZeromqPreviewResponses, type resolution_estimate, type resolution_ring, type roi, type roi_azim_list, type roi_azimuthal, type roi_box, type roi_box_list, type roi_circle, type roi_circle_list, type roi_definitions, type rotation_axis, type saturation, type scan_result, type show_beam_center, type show_roi, type show_spots, type show_user_mask, type spot_finding_settings, type standard_detector_geometry, type tcp_settings, type unit_cell, type zeromq_metadata_settings, type zeromq_preview_settings, type zeromq_settings } from './types.gen'; +export { type azim_int_settings, azint_unit, type background, type binning, type bragg_integration_settings, type broker_status, type calibration_statistics, type ClientOptions, color_scale, type crystal_lattice, type dark_mask_settings, type dataset_settings, type detector, type detector_list, type detector_list_element, type detector_module, detector_module_direction, detector_power_state, type detector_selection, type detector_settings, detector_state, type detector_status, detector_timing, detector_type, type error_message, type experimental_coord, file_writer_format, type file_writer_settings, type fill_value, type fpga_status, geom_refinement_algorithm, type getConfigAzimIntData, type getConfigAzimIntResponse, type getConfigAzimIntResponses, type getConfigBraggIntegrationData, type getConfigBraggIntegrationResponse, type getConfigBraggIntegrationResponses, type getConfigDarkMaskData, type getConfigDarkMaskResponse, type getConfigDarkMaskResponses, type getConfigDetectorData, type getConfigDetectorResponse, type getConfigDetectorResponses, type getConfigFileWriterData, type getConfigFileWriterResponse, type getConfigFileWriterResponses, type getConfigImageFormatData, type getConfigImageFormatResponse, type getConfigImageFormatResponses, type getConfigIndexingData, type getConfigIndexingResponse, type getConfigIndexingResponses, type getConfigInstrumentData, type getConfigInstrumentResponse, type getConfigInstrumentResponses, type getConfigMaskData, type getConfigMaskResponse, type getConfigMaskResponses, type getConfigMaskTiffData, type getConfigMaskTiffResponse, type getConfigMaskTiffResponses, type getConfigRoiData, type getConfigRoiResponse, type getConfigRoiResponses, type getConfigSelectDetectorData, type getConfigSelectDetectorResponse, type getConfigSelectDetectorResponses, type getConfigSpotFindingData, type getConfigSpotFindingResponse, type getConfigSpotFindingResponses, type getConfigUserMaskData, type getConfigUserMaskResponse, type getConfigUserMaskResponses, type getConfigUserMaskTiffData, type getConfigUserMaskTiffResponse, type getConfigUserMaskTiffResponses, type getConfigZeromqMetadataData, type getConfigZeromqMetadataResponse, type getConfigZeromqMetadataResponses, type getConfigZeromqPreviewData, type getConfigZeromqPreviewResponse, type getConfigZeromqPreviewResponses, type getDetectorStatusData, type getDetectorStatusError, type getDetectorStatusErrors, type getDetectorStatusResponse, type getDetectorStatusResponses, type getFpgaStatusData, type getFpgaStatusResponse, type getFpgaStatusResponses, type getImageBufferImageCborData, type getImageBufferImageCborError, type getImageBufferImageCborErrors, type getImageBufferImageCborResponse, type getImageBufferImageCborResponses, type getImageBufferImageJpegData, type getImageBufferImageJpegError, type getImageBufferImageJpegErrors, type getImageBufferImageJpegResponse, type getImageBufferImageJpegResponses, type getImageBufferImageTiffData, type getImageBufferImageTiffErrors, type getImageBufferImageTiffResponse, type getImageBufferImageTiffResponses, type getImageBufferStartCborData, type getImageBufferStartCborError, type getImageBufferStartCborErrors, type getImageBufferStartCborResponse, type getImageBufferStartCborResponses, type getImageBufferStatusData, type getImageBufferStatusError, type getImageBufferStatusErrors, type getImageBufferStatusResponse, type getImageBufferStatusResponses, type getImagePusherStatusData, type getImagePusherStatusError, type getImagePusherStatusErrors, type getImagePusherStatusResponse, type getImagePusherStatusResponses, type getPreviewPedestalTiffData, type getPreviewPedestalTiffErrors, type getPreviewPedestalTiffResponse, type getPreviewPedestalTiffResponses, type getPreviewPlotBinData, type getPreviewPlotBinError, type getPreviewPlotBinErrors, type getPreviewPlotBinResponse, type getPreviewPlotBinResponses, type getPreviewPlotData, type getPreviewPlotError, type getPreviewPlotErrors, type getPreviewPlotResponse, type getPreviewPlotResponses, type getResultScanData, type getResultScanError, type getResultScanErrors, type getResultScanResponse, type getResultScanResponses, type getStatisticsCalibrationData, type getStatisticsCalibrationResponse, type getStatisticsCalibrationResponses, type getStatisticsData, type getStatisticsDataCollectionData, type getStatisticsDataCollectionErrors, type getStatisticsDataCollectionResponse, type getStatisticsDataCollectionResponses, type getStatisticsResponse, type getStatisticsResponses, type getStatusData, type getStatusResponse, type getStatusResponses, type getVersionData, type getVersionResponse, type getVersionResponses, type getXfelEventCodeData, type getXfelEventCodeErrors, type getXfelEventCodeResponse, type getXfelEventCodeResponses, type getXfelPulseIdData, type getXfelPulseIdErrors, type getXfelPulseIdResponse, type getXfelPulseIdResponses, type grid_scan, type image_buffer_status, type image_format_settings, type image_id, type image_pusher_status, image_pusher_type, indexing_algorithm, type indexing_settings, type instrument_metadata, integration_model, type jfjoch_settings, type jfjoch_statistics, type jpeg_quality, type measurement_statistics, type pcie_devices, type pixel_mask_statistics, type plot, plot_type, plot_unit_x, type plots, type postCancelData, type postCancelResponses, type postConfigImageFormatConversionData, type postConfigImageFormatConversionError, type postConfigImageFormatConversionErrors, type postConfigImageFormatConversionResponses, type postConfigImageFormatRawData, type postConfigImageFormatRawError, type postConfigImageFormatRawErrors, type postConfigImageFormatRawResponses, type postDeactivateData, type postDeactivateError, type postDeactivateErrors, type postDeactivateResponses, type postImageBufferClearData, type postImageBufferClearError, type postImageBufferClearErrors, type postImageBufferClearResponses, type postInitializeData, type postInitializeError, type postInitializeErrors, type postInitializeResponses, type postPedestalData, type postPedestalError, type postPedestalErrors, type postPedestalResponses, type postStartData, type postStartError, type postStartErrors, type postStartResponses, type postTriggerData, type postTriggerResponses, type postWaitTillDoneData, type postWaitTillDoneError, type postWaitTillDoneErrors, type postWaitTillDoneResponses, type postWaitUntilRunningData, type postWaitUntilRunningError, type postWaitUntilRunningErrors, type postWaitUntilRunningResponses, type putConfigAzimIntData, type putConfigAzimIntError, type putConfigAzimIntErrors, type putConfigAzimIntResponses, type putConfigBraggIntegrationData, type putConfigBraggIntegrationError, type putConfigBraggIntegrationErrors, type putConfigBraggIntegrationResponses, type putConfigDarkMaskData, type putConfigDarkMaskError, type putConfigDarkMaskErrors, type putConfigDarkMaskResponses, type putConfigDetectorData, type putConfigDetectorError, type putConfigDetectorErrors, type putConfigDetectorResponses, type putConfigFileWriterData, type putConfigFileWriterError, type putConfigFileWriterErrors, type putConfigFileWriterResponses, type putConfigImageFormatData, type putConfigImageFormatError, type putConfigImageFormatErrors, type putConfigImageFormatResponses, type putConfigIndexingData, type putConfigIndexingError, type putConfigIndexingErrors, type putConfigIndexingResponses, type putConfigInstrumentData, type putConfigInstrumentError, type putConfigInstrumentErrors, type putConfigInstrumentResponses, type putConfigInternalGeneratorImageData, type putConfigInternalGeneratorImageError, type putConfigInternalGeneratorImageErrors, type putConfigInternalGeneratorImageResponses, type putConfigInternalGeneratorImageTiffData, type putConfigInternalGeneratorImageTiffError, type putConfigInternalGeneratorImageTiffErrors, type putConfigInternalGeneratorImageTiffResponses, type putConfigRoiData, type putConfigRoiError, type putConfigRoiErrors, type putConfigRoiResponses, type putConfigSelectDetectorData, type putConfigSelectDetectorError, type putConfigSelectDetectorErrors, type putConfigSelectDetectorResponses, type putConfigSpotFindingData, type putConfigSpotFindingError, type putConfigSpotFindingErrors, type putConfigSpotFindingResponses, type putConfigUserMaskData, type putConfigUserMaskError, type putConfigUserMaskErrors, type putConfigUserMaskResponses, type putConfigUserMaskTiffData, type putConfigUserMaskTiffError, type putConfigUserMaskTiffErrors, type putConfigUserMaskTiffResponses, type putConfigZeromqMetadataData, type putConfigZeromqMetadataError, type putConfigZeromqMetadataErrors, type putConfigZeromqMetadataResponses, type putConfigZeromqPreviewData, type putConfigZeromqPreviewError, type putConfigZeromqPreviewErrors, type putConfigZeromqPreviewResponses, type resolution_estimate, type resolution_ring, type roi, type roi_azim_list, type roi_azimuthal, type roi_box, type roi_box_list, type roi_circle, type roi_circle_list, type roi_definitions, type rotation_axis, type saturation, type scan_result, type show_beam_center, type show_predictions, type show_roi, type show_spots, type show_user_mask, type spot_finding_settings, type standard_detector_geometry, type tcp_settings, type unit_cell, type zeromq_metadata_settings, type zeromq_preview_settings, type zeromq_settings } from './types.gen'; diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 347b6ecd..d69b6dc3 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -1773,6 +1773,11 @@ export type background = number; */ export type show_spots = boolean; +/** + * Show Bragg spot predictions on the image (if available for the image) + */ +export type show_predictions = boolean; + /** * Show beam center on the image */ @@ -3221,6 +3226,10 @@ export type getImageBufferImageJpegData = { * Show spot finding results on the image */ show_spots?: boolean; + /** + * Show Bragg spot predictions on the image (if available for the image) + */ + show_predictions?: boolean; /** * Show beam center on the image */ diff --git a/frontend/src/client/zod.gen.ts b/frontend/src/client/zod.gen.ts index 92984401..80c7afb3 100644 --- a/frontend/src/client/zod.gen.ts +++ b/frontend/src/client/zod.gen.ts @@ -868,6 +868,11 @@ export const zBackground = z.number().gte(-32767).lte(32767).default(0); */ export const zShowSpots = z.boolean().default(true); +/** + * Show Bragg spot predictions on the image (if available for the image) + */ +export const zShowPredictions = z.boolean().default(false); + /** * Show beam center on the image */ @@ -1235,6 +1240,7 @@ export const zGetImageBufferImageJpegQuery = z.object({ show_user_mask: z.boolean().optional().default(false), show_roi: z.boolean().optional().default(false), show_spots: z.boolean().optional().default(true), + show_predictions: z.boolean().optional().default(false), show_beam_center: z.boolean().optional().default(true), saturation: z.number().gte(-32767).lte(32767).optional(), jpeg_quality: z.coerce.bigint().gte(BigInt(0)).lte(BigInt(100)).optional().default(BigInt(100)), diff --git a/frontend/src/components/PreviewImage.tsx b/frontend/src/components/PreviewImage.tsx index 319f5487..b7c389f6 100644 --- a/frontend/src/components/PreviewImage.tsx +++ b/frontend/src/components/PreviewImage.tsx @@ -20,6 +20,7 @@ import FormControl from "@mui/material/FormControl"; type preview_settings = { saturation: number; show_spots: boolean; + show_predictions: boolean; show_roi: boolean; jpeg_quality: number; show_user_mask: boolean; @@ -57,6 +58,7 @@ const default_preview_settings: preview_settings = { saturation: 10, jpeg_quality: 90, show_spots: true, + show_predictions: false, show_roi: false, show_user_mask: false, show_beam_center: true, @@ -88,6 +90,7 @@ function PreviewImage({measuring, min_image_number, max_image_number}: MyProps) url += `&saturation=${s.saturation}`; url += `&jpeg_quality=${s.jpeg_quality}` url += `&show_spots=${s.show_spots}` + url += `&show_predictions=${s.show_predictions}` url += `&show_roi=${s.show_roi}` url += `&show_user_mask=${s.show_user_mask}`; if (s.res_estimate) @@ -189,6 +192,12 @@ function PreviewImage({measuring, min_image_number, max_image_number}: MyProps) getValues(s); }; + const showPredictionsToggle = (event: ChangeEvent) => { + let s : preview_settings = {...settings, show_predictions: event.target.checked}; + setSettings(s); + getValues(s); + }; + const resEstToggle = (event: ChangeEvent) => { let s: preview_settings = {...settings, res_estimate: event.target.checked}; setSettings(s); @@ -338,6 +347,9 @@ function PreviewImage({measuring, min_image_number, max_image_number}: MyProps) } label="Show spots"/> + + } label="Show predictions"/> } label="Show ROI"/> diff --git a/preview/PreviewImage.cpp b/preview/PreviewImage.cpp index 37076154..be38a7bb 100644 --- a/preview/PreviewImage.cpp +++ b/preview/PreviewImage.cpp @@ -1,18 +1,22 @@ // SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only -#include +#include #include #include +#include +#include #include "PreviewImage.h" #include "JFJochJPEG.h" #include "JFJochTIFF.h" #include "../common/JFJochException.h" +#include "../common/JFJochMath.h" #include "../common/DiffractionGeometry.h" #include "../frame_serialize/CBORStream2Deserializer.h" #include "../compression/JFJochDecompress.h" +#include "../image_analysis/bragg_integration/SystematicAbsence.h" constexpr const static rgb lime = {.r = 0xcd, .g = 0xdc, .b = 0x39}; constexpr const static rgb pink = {.r = 0xe9, .g = 0x1e, .b = 0x63}; @@ -23,6 +27,14 @@ constexpr const static rgb amber = {.r =0xff, .g = 0xc1, .b = 0x07}; constexpr const static rgb blue = {.r = 0x0d, .g = 0x47, .b = 0xa1}; constexpr const static rgb cyan = {.r = 0x00, .g = 0xff, .b = 0xff}; // "ice" color +// Spot/prediction overlay palette, matching the jfjoch_viewer diffraction image so the two +// front-ends look identical: green = not indexed, magenta = indexed (primary lattice), +// cyan = on an ice ring, coral = secondary/further lattice, dark red = Bragg prediction. +constexpr const static rgb green = {.r = 0x00, .g = 0xff, .b = 0x00}; +constexpr const static rgb magenta = {.r = 0xff, .g = 0x00, .b = 0xff}; +constexpr const static rgb coral = {.r = 0xfa, .g = 0x72, .b = 0x68}; +constexpr const static rgb dark_red = {.r = 0x80, .g = 0x00, .b = 0x00}; + constexpr const static rgb plotly[] = {{0x1f, 0x77, 0xb4}, {0xff, 0x7f, 0x0e}, {0x2c, 0xa0, 0x2c}, @@ -36,20 +48,6 @@ constexpr const static rgb plotly[] = {{0x1f, 0x77, 0xb4}, constexpr const static rgb gray = {.r = 0xbe, .g = 0xbe, .b = 0xbe}; -// Smallest difference between two angles in degrees, accounting for wrap-around. -static float AngularDistance_deg(float a, float b) { - float d = std::fabs(a - b); - if (d > 180.0f) - d = 360.0f - d; - return d; -} - -static bool InPhiSector(float phi_deg, float phi_min, float phi_max) { - if (phi_min <= phi_max) - return phi_deg >= phi_min && phi_deg <= phi_max; - return phi_deg >= phi_min || phi_deg <= phi_max; // sector wraps across 0 -} - void PreviewImage::color_pixel(std::vector &ret, int64_t in_xpixel, int64_t in_ypixel,const rgb &color) const { if ((in_xpixel >= 0) && (in_xpixel < xpixel) && (in_ypixel >= 0) && (in_ypixel < ypixel)) ret[(in_ypixel * xpixel + in_xpixel)] = color; @@ -133,8 +131,13 @@ std::vector PreviewImage::GenerateRGB(const uint8_t *value_8, } void PreviewImage::AddBeamCenter(std::vector &rgb_image) const { - size_t beam_x_int = std::lround(beam_x); - size_t beam_y_int = std::lround(beam_y); + // The true direct beam is where the primary beam hits the detector, which differs from the + // stored beam origin (PONI) whenever the detector is tilted. + auto [bx, by] = experiment.GetDiffractionGeometry().GetDirectBeam_pxl(); + if (!std::isfinite(bx) || !std::isfinite(by)) + return; + int64_t beam_x_int = std::lround(bx); + int64_t beam_y_int = std::lround(by); int crosshair_size = 30; int crosshair_width = 3; @@ -155,9 +158,9 @@ void PreviewImage::AddSpots(std::vector &rgb_image, int rectangle_size = 4; int rectangle_width = 3; - rgb color = pink; + rgb color = green; // not indexed if (s.indexed) - color = amber; + color = (s.lattice >= 1) ? coral : magenta; // secondary lattice vs primary else if (s.ice_ring) color = cyan; @@ -215,46 +218,110 @@ void PreviewImage::AddROI(std::vector &rgb_image) const { DiffractionGeometry geom = experiment.GetDiffractionGeometry(); for (const auto &az: experiment.ROI().GetROIDefinition().azimuthal) { - const int width = 5; - const float r_inner = geom.ResToPxl(az.GetDMax_A()); // larger d -> smaller radius - const float r_outer = geom.ResToPxl(az.GetDMin_A()); - const bool has_phi = az.HasPhi(); + const rgb color = plotly[roi_counter % 10]; + const float d_inner = az.GetDMax_A(); // larger d -> smaller radius (inner arc) + const float d_outer = az.GetDMin_A(); // smaller d -> larger radius (outer arc) + constexpr float deg2rad = static_cast(PI) / 180.0f; - for (int64_t y = 0; y <= ypixel; y++) { - for (int64_t x = 0; x <= xpixel; x++) { - const float dx = x - beam_x; - const float dy = y - beam_y; - const float dist = sqrtf(dx * dx + dy * dy); - const float phi_deg = geom.Phi_rad(x, y) * 180.0f / static_cast(PI); + if (az.HasPhi()) { + float phi0 = az.GetPhiMin_deg() * deg2rad; + float phi1 = az.GetPhiMax_deg() * deg2rad; + if (phi1 < phi0) + phi1 += 2.0f * static_cast(PI); // unwrap a sector that crosses 0 - const bool in_sector = !has_phi || InPhiSector(phi_deg, az.GetPhiMin_deg(), az.GetPhiMax_deg()); - const bool on_arc = (std::fabs(dist - r_inner) < width) || (std::fabs(dist - r_outer) < width); + DrawArc(rgb_image, geom, d_outer, phi0, phi1, color, 2); + DrawArc(rgb_image, geom, d_inner, phi0, phi1, color, 2); - // Radial edges of a sector: angular tolerance scaled to keep ~constant pixel width. - const float tol_deg = (dist > 1.0f) ? width / dist * 180.0f / static_cast(PI) : 180.0f; - const bool on_edge = has_phi && (dist >= r_inner) && (dist <= r_outer) - && (AngularDistance_deg(phi_deg, az.GetPhiMin_deg()) < tol_deg - || AngularDistance_deg(phi_deg, az.GetPhiMax_deg()) < tol_deg); - - if ((on_arc && in_sector) || on_edge) - roi(rgb_image, x, y, roi_counter); - } + // Straight radial edges joining the inner and outer arc at each sector limit. + auto radial_edge = [&](float phi) { + try { + auto [ax, ay] = geom.ResPhiToPxl(d_outer, phi); + auto [bx, by] = geom.ResPhiToPxl(d_inner, phi); + if (std::isfinite(ax) && std::isfinite(ay) && std::isfinite(bx) && std::isfinite(by)) + DrawThickLine(rgb_image, ax, ay, bx, by, color, 2); + } catch (...) {} + }; + radial_edge(phi0); + radial_edge(phi1); + } else { + const float two_pi = 2.0f * static_cast(PI); + DrawArc(rgb_image, geom, d_outer, 0.0f, two_pi, color, 2); + DrawArc(rgb_image, geom, d_inner, 0.0f, two_pi, color, 2); } roi_counter++; } } void PreviewImage::AddResolutionRing(std::vector &rgb_image, float d) const { - DiffractionGeometry geom = experiment.GetDiffractionGeometry(); - int width = 3; - float radius = geom.ResToPxl(d); - for (int64_t y = 0; y <= ypixel; y++) { - for (int64_t x = 0; x <= xpixel; x++) { - float dist = sqrtf((x - beam_x) * (x - beam_x) + (y - beam_y) * (y - beam_y)); + DrawArc(rgb_image, experiment.GetDiffractionGeometry(), d, 0.0f, 2.0f * static_cast(PI), orange, 1); +} - if ((dist > radius) && (dist < radius + width)) - color_pixel(rgb_image, x, y, orange); +void PreviewImage::DrawThickLine(std::vector &rgb_image, float x0, float y0, float x1, float y1, + const rgb &color, int halfwidth) const { + const float dx = x1 - x0, dy = y1 - y0; + int n = static_cast(std::ceil(std::max(std::fabs(dx), std::fabs(dy)))); + if (n < 1) n = 1; + for (int i = 0; i <= n; i++) { + const float t = static_cast(i) / static_cast(n); + const int64_t px = std::lround(x0 + t * dx); + const int64_t py = std::lround(y0 + t * dy); + for (int a = -halfwidth; a <= halfwidth; a++) + for (int b = -halfwidth; b <= halfwidth; b++) + color_pixel(rgb_image, px + a, py + b, color); + } +} + +void PreviewImage::DrawArc(std::vector &rgb_image, const DiffractionGeometry &geom, float d, + float phi_start, float phi_end, const rgb &color, int halfwidth) const { + // Sample the constant-d arc finely enough that neighbouring samples stay a few pixels apart, + // then join them with straight segments. ResPhiToPxl carries the detector tilt, so this traces + // the true conic instead of a PONI-centred circle. It throws when d is too high for the + // wavelength, and returns NaN where the contour leaves the detector plane - break there. + const float r_est = geom.ResToPxl(d); + const float span = std::fabs(phi_end - phi_start); + const int steps = std::clamp(static_cast(std::lround(std::fabs(r_est) * span * 0.5f)), 60, 8192); + std::optional> prev; + for (int i = 0; i <= steps; i++) { + const float phi = phi_start + (phi_end - phi_start) * static_cast(i) / static_cast(steps); + std::pair pt; + try { + pt = geom.ResPhiToPxl(d, phi); + } catch (...) { + return; // d too high for the wavelength - nothing to draw } + if (!std::isfinite(pt.first) || !std::isfinite(pt.second)) { + prev.reset(); + continue; + } + if (prev) + DrawThickLine(rgb_image, prev->first, prev->second, pt.first, pt.second, color, halfwidth); + prev = pt; + } +} + +void PreviewImage::DrawCircleOutline(std::vector &rgb_image, float cx, float cy, float radius, + int width, const rgb &color) const { + const int64_t x_lo = std::floor(cx - radius - width); + const int64_t x_hi = std::ceil(cx + radius + width); + const int64_t y_lo = std::floor(cy - radius - width); + const int64_t y_hi = std::ceil(cy + radius + width); + for (int64_t y = y_lo; y <= y_hi; y++) { + for (int64_t x = x_lo; x <= x_hi; x++) { + const float dist = std::sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); + if (dist >= radius && dist <= radius + width) + color_pixel(rgb_image, x, y, color); + } + } +} + +void PreviewImage::AddPredictions(std::vector &rgb_image, const std::vector &reflections, + char centering) const { + // Draw predictions as dark-red circles (spots are squares), matching the viewer overlay. + // Reflections absent under the lattice centering are integrated but not real predictions - skip them. + for (const auto &s : reflections) { + if (systematic_absence(s.h, s.k, s.l, centering)) + continue; + DrawCircleOutline(rgb_image, s.predicted_x, s.predicted_y, 5.0f, 2, dark_red); } } @@ -289,8 +356,6 @@ void PreviewImage::Configure(const DiffractionExperiment &in_experiment, const P experiment = in_experiment; xpixel = experiment.GetXPixelsNum(); ypixel = experiment.GetYPixelsNum(); - beam_x = experiment.GetBeamX_pxl(); - beam_y = experiment.GetBeamY_pxl(); pixel_depth_bytes = experiment.GetByteDepthImage(); pixel_is_signed = experiment.IsPixelSigned(); @@ -359,6 +424,11 @@ std::vector PreviewImage::GenerateRGB(const PreviewImageSettings &settings, if (settings.show_spots) AddSpots(v, msg.spots); + if (settings.show_predictions) { + const char centering = msg.lattice_type.has_value() ? msg.lattice_type->centering : 'P'; + AddPredictions(v, msg.reflections, centering); + } + if (settings.show_roi) AddROI(v); diff --git a/preview/PreviewImage.h b/preview/PreviewImage.h index 2598ee06..001847da 100644 --- a/preview/PreviewImage.h +++ b/preview/PreviewImage.h @@ -7,6 +7,7 @@ #include #include "../common/SpotToSave.h" +#include "../common/Reflection.h" #include "../common/DiffractionExperiment.h" #include "../common/PixelMask.h" #include "PreviewCounter.h" @@ -20,6 +21,7 @@ struct PreviewImageSettings { int64_t jpeg_quality = 70; bool show_beam_center = true; bool show_spots = true; + bool show_predictions = false; bool show_roi = false; bool show_user_mask = true; bool show_res_est = false; @@ -39,8 +41,6 @@ class PreviewImage { size_t ypixel = 0; size_t pixel_depth_bytes = 2; bool pixel_is_signed = false; - float beam_x = 0; - float beam_y = 0; constexpr static uint8_t MaskGap = 1; constexpr static uint8_t MaskUsr = 2; @@ -59,8 +59,19 @@ class PreviewImage { void AddResolutionRing(std::vector &rgb_image, float d) const; void AddBeamCenter(std::vector &rgb_image) const; void AddSpots(std::vector &rgb_image, const std::vector& spots) const; + void AddPredictions(std::vector &rgb_image, const std::vector& reflections, char centering) const; void AddROI(std::vector &rgb_image) const; + // Trace a fixed-resolution arc through the geometry (a circular arc on an untilted detector, + // a conic one on a tilted detector) by sweeping azimuth from phi_start to phi_end and joining + // the samples. A full ring uses phi_start=0, phi_end=2*pi. + void DrawArc(std::vector &rgb_image, const DiffractionGeometry &geom, float d, + float phi_start, float phi_end, const rgb &color, int halfwidth) const; + void DrawThickLine(std::vector &rgb_image, float x0, float y0, float x1, float y1, + const rgb &color, int halfwidth) const; + void DrawCircleOutline(std::vector &rgb_image, float cx, float cy, float radius, + int width, const rgb &color) const; + void color_pixel(std::vector& ret, int64_t xpixel, int64_t ypixel, const rgb &color) const; void spot(std::vector& ret, int64_t xpixel, int64_t ypixel, const rgb &color) const; void roi(std::vector& ret, int64_t xpixel, int64_t ypixel, int64_t roi_number) const;