frontend: surface spot-finding upload failures and send sliders on release
This was the only panel firing the generated call directly, with the rejection routed to console.log. Because the poll then kept returning the unchanged server value - which still equalled lastDownloadedS - the resync effect never fired, so the dashboard showed a threshold the broker was not using, indefinitely and silently. It now goes through useUpload like its siblings, so a failure raises the snackbar, and onError puts the server's value back in the panel. The eight sliders also applied from onChange, which MUI fires for every intermediate position while dragging: one drag across the ice-ring width sent ~200 PUTs plus ~200 forced /statistics refetches, each reconfiguring spot finding on the running acquisition. Dragging now only moves the panel; the value is sent from onChangeCommitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import {ChangeEvent, memo, useEffect, useState} from 'react';
|
||||
|
||||
import {Grid, Slider, Switch, Typography} from "@mui/material";
|
||||
import {putConfigSpotFinding, spot_finding_settings} from "../client";
|
||||
import {spot_finding_settings} from "../client";
|
||||
import {putConfigSpotFindingMutation} from "../client/@tanstack/react-query.gen";
|
||||
import SettingsPanel from "./SettingsPanel";
|
||||
import {useUpload} from "./useUpload";
|
||||
import _ from "lodash";
|
||||
|
||||
type MyProps = {
|
||||
@@ -25,17 +27,20 @@ const default_spot_finding_settings: spot_finding_settings = {
|
||||
high_res_gap_Q_recipA: 1.5
|
||||
};
|
||||
|
||||
function putValues(x: spot_finding_settings) {
|
||||
putConfigSpotFinding({ body: x, throwOnError: true })
|
||||
.catch(error => console.log(error) );
|
||||
}
|
||||
|
||||
function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
const [s, setS] = useState<spot_finding_settings>(default_spot_finding_settings);
|
||||
const [lastDownloadedS, setLastDownloadedS] = useState<spot_finding_settings>(default_spot_finding_settings);
|
||||
const [highResGap, setHighResGap] = useState(1.5);
|
||||
const [highResLimit, setHighResLimit] = useState(2.5);
|
||||
|
||||
const { submit, snackbar } = useUpload({
|
||||
...putConfigSpotFindingMutation(),
|
||||
// A rejected PUT would otherwise leave the panel showing a value the broker is not using:
|
||||
// the poll keeps returning the old value, which still equals lastDownloadedS, so the
|
||||
// effect below never corrects it. Put the server's value back.
|
||||
onError: () => { if (serverS !== undefined) setS(serverS); },
|
||||
});
|
||||
|
||||
// Only adopt the server copy when it actually changed, otherwise the
|
||||
// 1 s statistics poll would overwrite edits the user is making.
|
||||
useEffect(() => {
|
||||
@@ -50,38 +55,20 @@ function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
|
||||
const apply = (next: spot_finding_settings) => {
|
||||
setS(next);
|
||||
putValues(next);
|
||||
submit(next);
|
||||
update();
|
||||
};
|
||||
|
||||
const setPhotonCountThreshold = (event: Event, newValue: number | number[]) =>
|
||||
apply({...s, photon_count_threshold: newValue as number});
|
||||
|
||||
const setSignalToNoiseThreshold = (event: Event, newValue: number | number[]) =>
|
||||
apply({...s, signal_to_noise_threshold: newValue as number});
|
||||
|
||||
const setMinPixPerSpot = (event: Event, newValue: number | number[]) =>
|
||||
apply({...s, min_pix_per_spot: newValue as number});
|
||||
|
||||
const setLowResolutionLimit = (event: Event, newValue: number | number[]) =>
|
||||
apply({...s, low_resolution_limit: newValue as number});
|
||||
|
||||
const setHighResolutionLimit = (event: Event, newValue: number | number[]) => {
|
||||
const v = newValue as number;
|
||||
setHighResLimit(v);
|
||||
apply({...s, high_resolution_limit: v});
|
||||
};
|
||||
// While the knob is dragged only the panel moves; the value is sent on release. MUI fires
|
||||
// onChange for every intermediate position, and each one is a PUT that reconfigures spot
|
||||
// finding on the running acquisition - one drag across the ice-ring width was ~200 of them.
|
||||
const preview = (patch: Partial<spot_finding_settings>) =>
|
||||
setS(prev => ({...prev, ...patch}));
|
||||
|
||||
// No high-resolution limit means spot finding goes as far as the detector reaches.
|
||||
const autoHighResolutionLimitToggle = (event: ChangeEvent<HTMLInputElement>) =>
|
||||
apply({...s, high_resolution_limit: event.target.checked ? undefined : highResLimit});
|
||||
|
||||
const setHighResolutionLimitForCountingLowResSpots = (event: Event, newValue: number | number[]) =>
|
||||
apply({...s, high_resolution_limit_for_spot_count_low_res: newValue as number});
|
||||
|
||||
const setIceRingWidth = (event: Event, newValue: number | number[]) =>
|
||||
apply({...s, ice_ring_width_q_recipA: newValue as number});
|
||||
|
||||
const enableSpotFindingToggle = (event: ChangeEvent<HTMLInputElement>) =>
|
||||
apply({...s, enable: event.target.checked});
|
||||
|
||||
@@ -94,14 +81,8 @@ function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
const enableHighResGapToggle = (event: ChangeEvent<HTMLInputElement>) =>
|
||||
apply({...s, high_res_gap_Q_recipA: event.target.checked ? highResGap : undefined});
|
||||
|
||||
const handleHighResGap = (event: Event, newValue: number | number[]) => {
|
||||
const v = newValue as number;
|
||||
setHighResGap(v);
|
||||
apply({...s, high_res_gap_Q_recipA: v});
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsPanel title="Spot finding parameters" sx={{ minHeight: 800 }}>
|
||||
<SettingsPanel title="Spot finding parameters" sx={{ minHeight: 800 }} snackbar={snackbar}>
|
||||
<Grid container spacing={0}>
|
||||
|
||||
<Grid item xs={1}/>
|
||||
@@ -113,13 +94,15 @@ function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
<Typography gutterBottom> Count threshold </Typography>
|
||||
<Slider disabled={!s.enable}
|
||||
value={Number(s.photon_count_threshold)}
|
||||
onChange={setPhotonCountThreshold}
|
||||
onChange={(e, v) => preview({photon_count_threshold: v as number})}
|
||||
onChangeCommitted={(e, v) => apply({...s, photon_count_threshold: v as number})}
|
||||
min={1} max={50} step={1} valueLabelDisplay="auto"/>
|
||||
|
||||
<br/><Typography> Signal-to-noise threshold </Typography>
|
||||
<Slider disabled={!s.enable}
|
||||
value={Number(s.signal_to_noise_threshold)}
|
||||
onChange={setSignalToNoiseThreshold}
|
||||
onChange={(e, v) => preview({signal_to_noise_threshold: v as number})}
|
||||
onChangeCommitted={(e, v) => apply({...s, signal_to_noise_threshold: v as number})}
|
||||
min={2}
|
||||
max={10}
|
||||
step={0.5}
|
||||
@@ -130,12 +113,14 @@ function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
<br/><Typography> Minimum pixel / spot </Typography>
|
||||
<Slider disabled={!s.enable}
|
||||
value={Number(s.min_pix_per_spot)}
|
||||
onChange={setMinPixPerSpot}
|
||||
onChange={(e, v) => preview({min_pix_per_spot: v as number})}
|
||||
onChangeCommitted={(e, v) => apply({...s, min_pix_per_spot: v as number})}
|
||||
min={1} max={8} step={1} valueLabelDisplay="auto"/>
|
||||
<Typography> Low resolution limit [Å] </Typography>
|
||||
<Slider disabled={!s.enable}
|
||||
value={Number(s.low_resolution_limit)}
|
||||
onChange={setLowResolutionLimit}
|
||||
onChange={(e, v) => preview({low_resolution_limit: v as number})}
|
||||
onChangeCommitted={(e, v) => apply({...s, low_resolution_limit: v as number})}
|
||||
min={10} max={100} step={0.1} valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => value.toFixed(1)}
|
||||
/>
|
||||
@@ -147,21 +132,24 @@ function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
<Typography> High resolution limit [Å] </Typography>
|
||||
<Slider disabled={!s.enable || s.high_resolution_limit === undefined}
|
||||
value={Number(s.high_resolution_limit ?? highResLimit)}
|
||||
onChange={setHighResolutionLimit}
|
||||
onChange={(e, v) => { setHighResLimit(v as number); preview({high_resolution_limit: v as number}); }}
|
||||
onChangeCommitted={(e, v) => apply({...s, high_resolution_limit: v as number})}
|
||||
min={1} max={5} step={0.1} valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => value.toFixed(1)}
|
||||
/>
|
||||
<Typography> High resolution limit for counting low resolution spots [Å] </Typography>
|
||||
<Slider disabled={!s.enable}
|
||||
value={Number(s.high_resolution_limit_for_spot_count_low_res)}
|
||||
onChange={setHighResolutionLimitForCountingLowResSpots}
|
||||
onChange={(e, v) => preview({high_resolution_limit_for_spot_count_low_res: v as number})}
|
||||
onChangeCommitted={(e, v) => apply({...s, high_resolution_limit_for_spot_count_low_res: v as number})}
|
||||
min={2} max={8} step={0.1} valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => value.toFixed(1)}
|
||||
/>
|
||||
<Typography> Ice ring width in Q-space [Å<sup>-1</sup>] </Typography>
|
||||
<Slider disabled={!s.enable}
|
||||
value={Number(s.ice_ring_width_q_recipA)}
|
||||
onChange={setIceRingWidth}
|
||||
onChange={(e, v) => preview({ice_ring_width_q_recipA: v as number})}
|
||||
onChangeCommitted={(e, v) => apply({...s, ice_ring_width_q_recipA: v as number})}
|
||||
min={0.0} max={0.2} step={0.001} valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => value.toFixed(3)}
|
||||
/>
|
||||
@@ -176,7 +164,8 @@ function DataProcessingSettings({s: serverS, update}: MyProps) {
|
||||
<Slider
|
||||
disabled={!s.enable || s.high_res_gap_Q_recipA === undefined}
|
||||
value={Number(s.high_res_gap_Q_recipA ?? highResGap)}
|
||||
onChange={handleHighResGap}
|
||||
onChange={(e, v) => { setHighResGap(v as number); preview({high_res_gap_Q_recipA: v as number}); }}
|
||||
onChangeCommitted={(e, v) => apply({...s, high_res_gap_Q_recipA: v as number})}
|
||||
min={0.1}
|
||||
max={5.0}
|
||||
step={0.01}
|
||||
|
||||
Reference in New Issue
Block a user