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>
146 lines
4.6 KiB
TypeScript
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 [Å<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 [Å]";
|
|
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 [Å]";
|
|
case plot_type.RESOLUTION_ESTIMATE:
|
|
return "Resolution [Å]";
|
|
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 [Å<sup>-1</sup>]";
|
|
case plot_type.B_FACTOR:
|
|
return "B-factor [Å<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;
|