Files
Jungfraujoch/frontend/src/components/DataProcessingPlot.tsx
T
leonarski_fandClaude Fable 5 f422988cd2 ice score: per-image ice-ring indicator plumbed through all layers
A single per-image ice_ring_score - the strongest hexagonal-ice ring band/shoulder
intensity ratio from the azimuthal profile (1 = no ice) - computed in the CPU and
FPGA analysis paths and the offline azint worker, then plumbed through every layer:
DataMessage/EndMessage, CBOR (frame_serialize), HDF5 (/entry/MX/iceRingScore),
ScanResult, receiver plots (PlotType::IceRingScore), the OpenAPI spec (plot_type +
scan_result schema, with regenerated broker/gen and frontend client) and
OpenAPIConvert, the reader + Qt viewer, and the React frontend plot. Documented in
docs/CBOR.md, docs/HDF5.md and docs/CPU_DATA_ANALYSIS.md, with the general
"add a per-image quantity" recipe added to CLAUDE.md.

Verified in HDF5: lysoC (weak ice) mean 1.23, EP_cs_01-17 (heavy ice) mean 1.67 /
max 2.23. This is a monitoring quantity - it does not gate scaling (which already
excludes all ice rings) or merging (handled by the CC1/2 ring mask).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:37:12 +02:00

146 lines
4.6 KiB
TypeScript

import {ReactNode} from 'react';
import {useQuery} from "@tanstack/react-query";
import {azint_unit, plot_type, plot_unit_x} from "../client";
import {getPreviewPlotOptions} from "../client/@tanstack/react-query.gen";
import MultiLinePlotWrapper from "./MultiLinePlotWrapper";
type MyProps = {
type: plot_type;
binning: number;
angle: boolean;
azint: azint_unit;
};
type PlotlyPlot = {
x: number[],
y: (number | null)[],
z?: (number | null)[],
type: string,
mode?: string,
name?: string,
colorscale?: string
}
type PlotlyData = PlotlyPlot[]
function AxisTypeX(unit: plot_unit_x) : string | ReactNode {
if (unit == plot_unit_x.Q_RECIP_A)
return "Q [&#8491;<sup>-1</sup>]";
else if (unit == plot_unit_x.ANGLE_DEG)
return "Rotation angle [deg]";
else if (unit == plot_unit_x.ADU)
return "ADU";
else if (unit == plot_unit_x.D_A)
return "d [&#8491;]";
else if (unit == plot_unit_x.GRID_UM)
return "Grid position [µm]";
else
return "Image number";
}
function AxisTypeY(plot: plot_type) : string | ReactNode {
switch (plot) {
case plot_type.IMAGE_COLLECTION_EFFICIENCY:
return "Efficiency";
case plot_type.INDEXING_LATTICE_COUNT:
case plot_type.INTEGRATED_REFLECTIONS:
case plot_type.SPOT_COUNT:
case plot_type.SPOT_COUNT_LOW_RES:
case plot_type.SPOT_COUNT_INDEXED:
case plot_type.SPOT_COUNT_ICE:
return "Count";
case plot_type.ICE_RING_SCORE:
return "Ratio";
case plot_type.AZINT:
case plot_type.AZINT_1D:
case plot_type.BKG_ESTIMATE:
return "Arbitrary units";
case plot_type.ROI_MAX_COUNT:
case plot_type.ROI_SUM:
case plot_type.ROI_MEAN:
return "Photon count";
case plot_type.INDEXING_RATE:
return "Indexing rate";
case plot_type.ERROR_PIXELS:
case plot_type.STRONG_PIXELS:
return "Pixel count";
case plot_type.RECEIVER_DELAY:
return "Delay";
case plot_type.RECEIVER_FREE_SEND_BUF:
return "Free buffers";
case plot_type.INDEXING_UNIT_CELL_LENGTH:
return "Length [&#8491;]";
case plot_type.RESOLUTION_ESTIMATE:
return "Resolution [&#8491;]";
case plot_type.INDEXING_UNIT_CELL_ANGLE:
return "Angle [deg]";
case plot_type.MAX_PIXEL_VALUE:
return "Max pixel value";
case plot_type.ROI_WEIGHTED_X:
case plot_type.ROI_WEIGHTED_Y:
return "Position [pixel]";
case plot_type.PROCESSING_TIME:
return "Time [s]";
case plot_type.PROFILE_RADIUS:
return "Profile radius [&#8491;<sup>-1</sup>]";
case plot_type.B_FACTOR:
return "B-factor [&#8491;<sup>2</sup>]";
case plot_type.BEAM_CENTER_X:
case plot_type.BEAM_CENTER_Y:
return "Beam center [pixel]";
case plot_type.COMPRESSION_RATIO:
return "Compression ratio";
default:
return "?";
}
}
function DataProcessingPlot({type, binning, angle, azint}: MyProps) {
const {data: plots, isError} = useQuery({
...getPreviewPlotOptions({ query: { type, binning, experimental_coord: angle, azint_unit: azint } }),
refetchInterval: 1000,
});
if (isError
|| (plots === undefined)
|| (plots.plot === null)
|| (plots.plot.length === 0))
return <div>No plots available</div>;
let data: PlotlyData = [];
if ((plots.plot[0].z !== undefined)
&& (plots.plot[0].z.length > 0)) {
data.push({
x: plots.plot[0].x,
y: plots.plot[0].y,
z: plots.plot[0].z,
type: "heatmap",
colorscale: "Viridis"
})
return <MultiLinePlotWrapper xaxis={AxisTypeX(plots.unit_x)}
yaxis={AxisTypeY(type)}
data={data}
grid_scan={true}
range_x={plots.size_x}
range_y={plots.size_y}/>
} else {
plots.plot.map(d =>
data.push({
x: d.x,
y: d.y,
type: "scatter",
mode: "line",
name: d.title
}));
return <MultiLinePlotWrapper xaxis={AxisTypeX(plots.unit_x)}
yaxis={AxisTypeY(type)}
data={data}
grid_scan={false}/>
}
}
export default DataProcessingPlot;