Files
Jungfraujoch/frontend/src/components/DataCollection.tsx
T
leonarski_f 75e401f0e5
Build Packages / Unit tests (push) Successful in 1h31m59s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 8m43s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m27s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m56s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m24s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 10m27s
Build Packages / build:rpm (rocky8) (push) Successful in 9m20s
Build Packages / build:rpm (rocky9) (push) Successful in 10m50s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 9m54s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m38s
Build Packages / DIALS test (push) Successful in 12m13s
Build Packages / XDS test (durin plugin) (push) Successful in 7m8s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 7m8s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m50s
Build Packages / Generate python client (push) Successful in 16s
Build Packages / Build documentation (push) Successful in 50s
Build Packages / Create release (push) Skipped
v1.0.0-rc.153 (#63)
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* jfjoch_broker: Add EXPERIMENTAL pixelrefine mode for image processing
* jfjoch_broker: Allow to load user mask from 8-bit and 16-bit TIFF files
* jfjoch_broker: Add ROI calculation in non-FPGA workflow
* jfjoch_broker: Fixes to TCP image pusher
* jfjoch_broker: Remove NUMA bindings
* jfjoch_broker: Improvements to indexing
* jfjoch_broker: For PSI EIGER, trimming energies are taken from the detector configuration (now compulsory) instead of hardcoded values
* jfjoch_writer: Save ROI definitions and the per-pixel ROI bitmap in the master file; azimuthal ROIs support phi (angular) sectors
* jfjoch_viewer: Major redesign with dockable panels and saved layouts, plus on-canvas creation/move/resize of box, circle and azimuthal ROIs
* jfjoch_viewer: Run jfjoch_process reprocessing jobs from inside the GUI and overlay per-run results

Reviewed-on: #63
2026-06-23 20:29:49 +02:00

271 lines
10 KiB
TypeScript

import {ChangeEvent, memo, useState} from 'react';
import Paper from '@mui/material/Paper';
import {Stack, TextField, Radio, RadioGroup, FormControlLabel, FormControl, Checkbox} from "@mui/material";
import NumberTextField from "./NumberTextField";
import {dataset_settings, grid_scan, rotation_axis} from "../client";
import ButtonWithSnackbar from "./ButtonWithSnackbar";
type MyProps = {
frame_time_us: number
};
function DataCollection({frame_time_us}: MyProps) {
const [s, setS] = useState<dataset_settings>(() => ({
beam_x_pxl: 0,
beam_y_pxl: 0,
detector_distance_mm: 100,
incident_energy_keV: 12.398,
images_per_trigger: 1,
image_time_us: frame_time_us,
ntrigger: 1,
images_per_file: 1000
}));
const [goniometer, setGoniometer] = useState<rotation_axis>({
start: 0,
step: 0.1,
name: "omega",
vector: [0,1,0]
});
const [grid, setGrid] = useState<grid_scan>({
n_fast: 10,
step_x_um: 10.0,
step_y_um: 10.0,
vertical: false,
snake: false
});
const [beamXPxlErr, setBeamXPxlErr] = useState(false);
const [beamYPxlErr, setBeamYPxlErr] = useState(false);
const [detectorDistanceMmErr, setDetectorDistanceMmErr] = useState(false);
const [incidentEnergyKevErr, setIncidentEnergyKevErr] = useState(false);
const [imagesPerTriggerErr, setImagesPerTriggerErr] = useState(false);
const [imageTimeUsErr, setImageTimeUsErr] = useState(false);
const [ntriggerErr, setNtriggerErr] = useState(false);
const [imagesPerFileErr, setImagesPerFileErr] = useState(false);
const [mode, setMode] = useState<'still' | 'rotation' | 'grid'>('still');
const error = () : boolean =>
beamXPxlErr
|| beamYPxlErr
|| detectorDistanceMmErr
|| incidentEnergyKevErr
|| imageTimeUsErr
|| imagesPerTriggerErr
|| imagesPerFileErr
|| ntriggerErr;
const getDatasetSettings = () : dataset_settings => {
let d = {...s};
if (mode === 'rotation')
d.goniometer = goniometer;
else if (mode === 'grid')
d.grid_scan = grid;
return d;
};
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={5} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<TextField label={"File prefix"}
variant="outlined"
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, file_prefix: event.target.value}));
}
}
value={s.file_prefix}
sx={{width:"90%"}}
/>
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label={"Number of images per trigger"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, images_per_trigger: val}));
setImagesPerTriggerErr(err);
}}
min={1}
default={s.images_per_trigger}
/>
<NumberTextField
label={"Number of triggers"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, ntrigger: val}));
setNtriggerErr(err);
}}
min={1}
default={s.ntrigger}
/>
<NumberTextField
label={"Image time"}
callback={(val: number, err: boolean) => {
let image_time_us = Math.round(val * 1000.0);
setS(prev => ({...prev, image_time_us: image_time_us}));
setImageTimeUsErr(err);
}}
units={"ms"}
float={true}
min={0.01}
default={(s.image_time_us ?? 500) / 1000.0}
/>
<NumberTextField
label={"Images per file"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, images_per_file: val}));
setImagesPerFileErr(err);
}}
float={false}
min={1}
default={s.images_per_file}
/>
</Stack>
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label={"Beam X [pxl]"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, beam_x_pxl: val}));
setBeamXPxlErr(err);
}}
units={"pxl"}
float={true}
default={s.beam_x_pxl}
/>
<NumberTextField
label={"Beam Y"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, beam_y_pxl: val}));
setBeamYPxlErr(err);
}}
units={"pxl"}
float={true}
default={s.beam_y_pxl}
/>
<NumberTextField
label={"Detector distance"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, detector_distance_mm: val}));
setDetectorDistanceMmErr(err);
}}
units={"mm"}
min={0.1}
float={true}
default={s.detector_distance_mm}
/>
<NumberTextField
label={"Energy"}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, incident_energy_keV: val}));
setIncidentEnergyKevErr(err);
}}
min={0.1}
max={500.0}
units={"keV"}
float={true}
default={s.incident_energy_keV}
/>
</Stack>
<FormControl sx={{width: '90%'}}>
<RadioGroup
row
value={mode}
onChange={(event) => setMode(event.target.value as 'still' | 'rotation' | 'grid')}>
<FormControlLabel value="still" control={<Radio/>} label="Still"/>
<FormControlLabel value="rotation" control={<Radio/>} label="Rotation"/>
<FormControlLabel value="grid" control={<Radio/>} label="Grid scan"/>
</RadioGroup>
</FormControl>
{mode === 'rotation' && (
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label="Start angle"
callback={(val: number) => setGoniometer(prev => ({...prev, start: val}))}
units="°"
float={true}
default={goniometer.start}
/>
<NumberTextField
label="Step"
callback={(val: number) => setGoniometer(prev => ({...prev, step: val}))}
units="°"
float={true}
min={0.001}
default={goniometer.step}
/>
<TextField
label="Axis name"
value={goniometer.name}
onChange={(e) => setGoniometer(prev => ({...prev, name: e.target.value}))}
/>
</Stack>
)}
{mode === 'grid' && (
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label="Grid elements (fast dim.)"
callback={(val: number) => setGrid(prev => ({...prev, n_fast: val}))}
min={1}
default={grid.n_fast}
/>
<NumberTextField
label="X step"
callback={(val: number) => setGrid(prev => ({...prev, step_x_um: val}))}
units="μm"
float={true}
default={grid.step_x_um}
/>
<NumberTextField
label="Y step"
callback={(val: number) => setGrid(prev => ({...prev, step_y_um: val}))}
units="μm"
float={true}
default={grid.step_y_um}
/>
<FormControlLabel
control={
<Checkbox
checked={grid.snake}
onChange={(e) => setGrid(prev => ({...prev, snake: e.target.checked}))}
/>
}
label="Snake scan"
/>
<FormControlLabel
control={
<Checkbox
checked={grid.vertical}
onChange={(e) => setGrid(prev => ({...prev, vertical: e.target.checked}))}
/>
}
label="Vertical scan"
/>
</Stack>
)}
<Stack direction="row" spacing={2}>
<ButtonWithSnackbar
path={"/start"}
text={"start"}
disabled={error()}
input={JSON.stringify(getDatasetSettings())}
color={"primary"}
/>
<ButtonWithSnackbar
path={"/trigger"}
text={"soft trigger"}
disabled={error()}
color={"primary"}
/>
</Stack>
</Stack>
<br/>
</Paper>
}
export default memo(DataCollection);