frontend: convert to function components + TanStack Query

Replace all class components with function components and move server-state
polling to @tanstack/react-query.

- add @tanstack/react-query; generate query helpers via the hey-api
  @tanstack/react-query plugin (src/client/@tanstack)
- QueryClientProvider + one-time client.setConfig({ baseUrl: '' }) in index.tsx
- App polls statistics with useQuery(getStatisticsOptions, refetchInterval),
  DataProcessingPlot uses getPreviewPlotOptions; manual setInterval polling gone
- memo() on presentational children; with TanStack structural sharing this
  re-renders a child only when its own statistics slice changes
- PreviewImage stays imperative (binary JPEG -> object URL) using useEffect +
  a ref for the object-URL lifecycle
- fix AzIntSettings correction checkboxes that mutated state in place (relied on
  the poll re-render); they now use setState
- drop dead code uncovered during the port (unused upload/deactivate handlers
  and imports in ImageFormatSettings, DetectorSettings, ROI)

Build (tsc + vite) passes; dev server transforms all entry modules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 21:32:36 +02:00
parent 7ed424a4ca
commit 237de5a0fc
31 changed files with 4408 additions and 3924 deletions
+1
View File
@@ -13,5 +13,6 @@ export default defineConfig({
enums: { mode: 'javascript' },
},
'@hey-api/sdk',
'@tanstack/react-query',
],
});
+27
View File
@@ -14,6 +14,7 @@
"@mui/icons-material": "^6.1.2",
"@mui/material": "^6.1.2",
"@mui/x-data-grid": "^7.19.0",
"@tanstack/react-query": "^5.101.0",
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
@@ -3492,6 +3493,32 @@
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.0.tgz",
"integrity": "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.0.tgz",
"integrity": "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.101.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@turf/area": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@turf/area/-/area-7.2.0.tgz",
+1
View File
@@ -11,6 +11,7 @@
"@mui/icons-material": "^6.1.2",
"@mui/material": "^6.1.2",
"@mui/x-data-grid": "^7.19.0",
"@tanstack/react-query": "^5.101.0",
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
+153 -209
View File
@@ -1,7 +1,8 @@
import React, {Component} from 'react';
import React, {useState} from 'react';
import {createTheme, CssBaseline, Grid, Stack, Switch, ThemeProvider, Typography} from "@mui/material";
import {indigo, lime} from "@mui/material/colors";
import {useQuery} from "@tanstack/react-query";
import DataProcessingSettings from "./components/DataProcessingSettings";
import DetectorSettings from "./components/DetectorSettings";
@@ -18,9 +19,8 @@ import ImageFormatSettings from "./components/ImageFormatSettings";
import InstrumentMetadata from "./components/InstrumentMetadata";
import {JFJOCH_VERSION} from "./version";
import FpgaStatus from "./components/FpgaStatus";
import {getStatistics, plot_type} from "./client";
import {client} from "./client/client.gen";
import {jfjoch_statistics} from "./client";
import {plot_type} from "./client";
import {getStatisticsOptions} from "./client/@tanstack/react-query.gen";
import DataCollection from "./components/DataCollection";
import ZeroMQPreview from "./components/ZeroMQPreview";
import PixelMask from "./components/PixelMask";
@@ -38,218 +38,162 @@ const jfjoch_theme = createTheme({
},
});
type MyState = {
show_detector_setup: boolean,
show_module_calibration: boolean,
show_preview: boolean,
show_roi_setup: boolean,
show_fpga_status: boolean,
connection_error: boolean,
show_data_collection: boolean,
s: jfjoch_statistics
function renderTitleWithSwitch(name: string, checked: boolean, func: ((event: React.ChangeEvent<HTMLInputElement>) => void)) {
return <Grid item xs={12}>
<Paper style={{textAlign: 'center'}} sx={{height: 60, width: '100%'}} component={Stack}
direction="column" justifyContent="center">
<Grid container spacing={0}>
<Grid xs={1}/>
<Grid xs={10}>
<Typography variant="h5"> {name} </Typography>
</Grid>
<Grid xs={1}>
<Switch checked={checked}
onChange={func}
name="Show detector setup"/>
</Grid>
</Grid>
</Paper>
</Grid>
}
type MyProps = {}
function App() {
const [showDetectorSetup, setShowDetectorSetup] = useState(false);
const [showModuleCalibration, setShowModuleCalibration] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const [showRoiSetup, setShowRoiSetup] = useState(false);
const [showFpgaStatus, setShowFpgaStatus] = useState(false);
const [showDataCollection, setShowDataCollection] = useState(false);
class App extends Component<MyProps, MyState> {
interval: ReturnType<typeof setInterval> | undefined;
// Poll general statistics once per second; structural sharing keeps unchanged
// slices referentially stable so memoized children below only re-render when
// their own slice changes.
const {data, isError, refetch} = useQuery({
...getStatisticsOptions(),
refetchInterval: 1000,
});
const s = isError || data === undefined ? {} : data;
state : MyState = {
show_detector_setup: false,
show_module_calibration: false,
show_preview: false,
show_roi_setup: false,
show_fpga_status: false,
connection_error: true,
show_data_collection: false,
s: {}
}
const isMeasuring = s.broker?.state === 'Measuring';
getValues = () => {
getStatistics({ throwOnError: true })
.then(({data}) => this.setState({s: data, connection_error: false}))
.catch(_ => {
this.setState({s: {}, connection_error: true});
});
}
componentWillUnmount() {
clearInterval(this.interval);
}
componentDidMount() {
client.setConfig({ baseUrl: '' });
this.getValues();
this.interval = setInterval(() => this.getValues(), 1000);
}
showPreviewToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({show_preview: event.target.checked});
}
showDetectorSetupToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({show_detector_setup: event.target.checked});
}
showModuleCalibrationToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({show_module_calibration: event.target.checked});
}
showROISetupToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({show_roi_setup: event.target.checked});
}
showFPGAStatusToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({show_fpga_status: event.target.checked});
}
showDataCollectionToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({show_data_collection: event.target.checked});
}
renderTitleWithSwitch = (name: string, checked: boolean, func: ((event: React.ChangeEvent<HTMLInputElement>) => void)) => {
return <Grid item xs={12}>
<Paper style={{textAlign: 'center'}} sx={{height: 60, width: '100%'}} component={Stack}
direction="column" justifyContent="center">
<Grid container spacing={0}>
<Grid xs={1}/>
<Grid xs={10}>
<Typography variant="h5"> {name} </Typography>
</Grid>
<Grid xs={1}>
<Switch checked={checked}
onChange={func}
name="Show detector setup"/>
</Grid>
return <ThemeProvider theme={jfjoch_theme}>
<CssBaseline enableColorScheme/>
<StatusBar s={s.broker}/> <br/><br/><br/><br/>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<Grid container
justifyContent="center"
alignItems="center"
spacing={3}
sx={{width: "95%"}}>
<Grid item xs={2}/>
<Grid item xs={8}>
<ErrorMessage s={s.broker}/>
</Grid>
</Paper>
</Grid>
};
isMeasuring() : boolean {
return (this.state.s.broker !== undefined)
&& (this.state.s.broker.state == 'Measuring');
}
render() {
return <ThemeProvider theme={jfjoch_theme}>
<CssBaseline enableColorScheme/>
<StatusBar s={this.state.s.broker}/> <br/><br/><br/><br/>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<Grid container
justifyContent="center"
alignItems="center"
spacing={3}
sx={{width: "95%"}}>
<Grid item xs={2}/>
<Grid item xs={8}>
<ErrorMessage s={this.state.s.broker}/>
</Grid>
<Grid item xs={2}/>
<Grid item xs={8}>
<DataProcessingPlots height={550} type={plot_type.BKG_ESTIMATE}/>
</Grid>
<Grid item xs={4}>
<MeasurementStatistics
s={(this.state.s.measurement === undefined) ? {} : this.state.s.measurement}/>
</Grid>
<Grid item xs={8}>
<DataProcessingPlots height={800} type={plot_type.INDEXING_RATE}/>
</Grid>
<Grid item xs={4}>
<DataProcessingSettings s={this.state.s.data_processing_settings} update={this.getValues}/>
</Grid>
{
this.renderTitleWithSwitch("Live image preview", this.state.show_preview, this.showPreviewToggle)
}
<Grid item xs={12}>
{this.state.show_preview ? <PreviewImage measuring={this.isMeasuring()}
max_image_number={this.state.s.buffer?.max_image_number}
min_image_number={this.state.s.buffer?.min_image_number}/> : ""}
</Grid>
<br/><br/>
{
this.renderTitleWithSwitch("Data collection",
this.state.show_data_collection, this.showDataCollectionToggle)
}
<Grid item xs={2}/>
<Grid item xs={8}>
{this.state.show_data_collection ? <DataCollection frame_time_us={
this.state.s.detector_settings?.frame_time_us ?? 500
}/> : ""}
</Grid>
<Grid item xs={2}/>
<br/><br/>
{
this.renderTitleWithSwitch("Jungfraujoch expert configuration",
this.state.show_detector_setup, this.showDetectorSetupToggle)
}
<Grid item xs={4}>
{this.state.show_detector_setup ? <Stack spacing={2}>
<DetectorSettings s={this.state.s.detector_settings}/>
<PixelMask s={this.state.s.pixel_mask}/>
<DarkMaskSettings s={this.state.s.dark_mask}/>
<FileWriterSettings s={this.state.s.file_writer_settings}/>
</Stack>: ""}
</Grid>
<Grid item xs={4}>
{this.state.show_detector_setup ?
<Stack spacing={2}>
<DetectorSelection s={this.state.s.detector_list}/>
<DetectorStatus s={this.state.s.detector}/>
<ZeroMQPreview s={this.state.s.zeromq_preview}/>
<IndexingSettings s={this.state.s.indexing} status={this.state.s.broker}/>
</Stack> : ""}
</Grid>
<Grid item xs={4}>
{this.state.show_detector_setup ?
<Stack spacing={2}>
<ImageFormatSettings s={this.state.s.image_format_settings}/>
<InstrumentMetadata s={this.state.s.instrument_metadata}/>
<AzIntSettings s={this.state.s.az_int}/>
<ImagePusherStatus s={this.state.s.image_pusher}/>
</Stack> : ""}
</Grid>
<br/><br/>
{
this.renderTitleWithSwitch("JUNGFRAU module calibration",
this.state.show_module_calibration, this.showModuleCalibrationToggle)
}
<Grid item xs={12}>
{this.state.show_module_calibration ? <Calibration s={this.state.s.calibration}/> : ""}
</Grid>
<br/><br/>
{
this.renderTitleWithSwitch("Region of interest (ROI)",
this.state.show_roi_setup, this.showROISetupToggle)
}
<Grid item xs={12}>
{this.state.show_roi_setup ? <ROI s={this.state.s.roi}/> : ""}
</Grid>
<br/><br/>
{
this.renderTitleWithSwitch("FPGA status",
this.state.show_fpga_status, this.showFPGAStatusToggle)
}
<Grid item xs={12}>
{this.state.show_fpga_status ? <FpgaStatus s={this.state.s.fpga}/> : ""}
</Grid>
<Grid item xs={2}/>
<Grid item xs={8}>
<DataProcessingPlots height={550} type={plot_type.BKG_ESTIMATE}/>
</Grid>
</div>
<br/>
<center>Developed at Paul Scherrer Institute (2019-2024). Main author: <a
href="mailto:filip.leonarski@psi.ch">Filip Leonarski</a> <br/>
For more information see <a href="https://doi.org/10.1107/S1600577522010268"><i>J. Synchrotron
Rad.</i> (2023). <b>30</b>, 227234</a> <br/>
Version: {JFJOCH_VERSION}&nbsp;&nbsp;&nbsp;
<a href="/frontend/openapi.html">API reference</a>&nbsp;&nbsp;&nbsp;
<a href="https://jungfraujoch.readthedocs.io/">Documentation</a></center>
<br/>
</ThemeProvider>
}
<Grid item xs={4}>
<MeasurementStatistics
s={(s.measurement === undefined) ? {} : s.measurement}/>
</Grid>
<Grid item xs={8}>
<DataProcessingPlots height={800} type={plot_type.INDEXING_RATE}/>
</Grid>
<Grid item xs={4}>
<DataProcessingSettings s={s.data_processing_settings} update={refetch}/>
</Grid>
{
renderTitleWithSwitch("Live image preview", showPreview, e => setShowPreview(e.target.checked))
}
<Grid item xs={12}>
{showPreview ? <PreviewImage measuring={isMeasuring}
max_image_number={s.buffer?.max_image_number}
min_image_number={s.buffer?.min_image_number}/> : ""}
</Grid>
<br/><br/>
{
renderTitleWithSwitch("Data collection",
showDataCollection, e => setShowDataCollection(e.target.checked))
}
<Grid item xs={2}/>
<Grid item xs={8}>
{showDataCollection ? <DataCollection frame_time_us={
s.detector_settings?.frame_time_us ?? 500
}/> : ""}
</Grid>
<Grid item xs={2}/>
<br/><br/>
{
renderTitleWithSwitch("Jungfraujoch expert configuration",
showDetectorSetup, e => setShowDetectorSetup(e.target.checked))
}
<Grid item xs={4}>
{showDetectorSetup ? <Stack spacing={2}>
<DetectorSettings s={s.detector_settings}/>
<PixelMask s={s.pixel_mask}/>
<DarkMaskSettings s={s.dark_mask}/>
<FileWriterSettings s={s.file_writer_settings}/>
</Stack>: ""}
</Grid>
<Grid item xs={4}>
{showDetectorSetup ?
<Stack spacing={2}>
<DetectorSelection s={s.detector_list}/>
<DetectorStatus s={s.detector}/>
<ZeroMQPreview s={s.zeromq_preview}/>
<IndexingSettings s={s.indexing} status={s.broker}/>
</Stack> : ""}
</Grid>
<Grid item xs={4}>
{showDetectorSetup ?
<Stack spacing={2}>
<ImageFormatSettings s={s.image_format_settings}/>
<InstrumentMetadata s={s.instrument_metadata}/>
<AzIntSettings s={s.az_int}/>
<ImagePusherStatus s={s.image_pusher}/>
</Stack> : ""}
</Grid>
<br/><br/>
{
renderTitleWithSwitch("JUNGFRAU module calibration",
showModuleCalibration, e => setShowModuleCalibration(e.target.checked))
}
<Grid item xs={12}>
{showModuleCalibration ? <Calibration s={s.calibration}/> : ""}
</Grid>
<br/><br/>
{
renderTitleWithSwitch("Region of interest (ROI)",
showRoiSetup, e => setShowRoiSetup(e.target.checked))
}
<Grid item xs={12}>
{showRoiSetup ? <ROI s={s.roi}/> : ""}
</Grid>
<br/><br/>
{
renderTitleWithSwitch("FPGA status",
showFpgaStatus, e => setShowFpgaStatus(e.target.checked))
}
<Grid item xs={12}>
{showFpgaStatus ? <FpgaStatus s={s.fpga}/> : ""}
</Grid>
</Grid>
</div>
<br/>
<center>Developed at Paul Scherrer Institute (2019-2024). Main author: <a
href="mailto:filip.leonarski@psi.ch">Filip Leonarski</a> <br/>
For more information see <a href="https://doi.org/10.1107/S1600577522010268"><i>J. Synchrotron
Rad.</i> (2023). <b>30</b>, 227234</a> <br/>
Version: {JFJOCH_VERSION}&nbsp;&nbsp;&nbsp;
<a href="/frontend/openapi.html">API reference</a>&nbsp;&nbsp;&nbsp;
<a href="https://jungfraujoch.readthedocs.io/">Documentation</a></center>
<br/>
</ThemeProvider>
}
export default App;
File diff suppressed because it is too large Load Diff
+130 -163
View File
@@ -1,7 +1,7 @@
import React from 'react';
import {ChangeEvent, memo, useEffect, useState} from 'react';
import Paper from '@mui/material/Paper';
import {FormControlLabel, Checkbox, Stack, Tooltip, ListItem, List, Radio, RadioGroup, Typography} from "@mui/material";
import {FormControlLabel, Checkbox, Stack, Radio, RadioGroup, Typography} from "@mui/material";
import NumberTextField from "./NumberTextField";
import {azim_int_settings} from "../client";
import _ from "lodash";
@@ -12,15 +12,6 @@ type MyProps = {
s?: azim_int_settings
};
type MyState = {
s: azim_int_settings
last_downloaded_s: azim_int_settings
download_counter: number
low_q_error: boolean
high_q_error: boolean
q_spacing_error: boolean
};
const default_azim_int_settings : azim_int_settings = {
solid_angle_corr: true,
polarization_corr: true,
@@ -31,163 +22,139 @@ const default_azim_int_settings : azim_int_settings = {
force_cpu: false
}
class AzIntSettings extends React.Component<MyProps, MyState> {
state : MyState = {
s: default_azim_int_settings,
last_downloaded_s: default_azim_int_settings,
download_counter: 0,
low_q_error: false,
high_q_error: false,
q_spacing_error: false,
}
function AzIntSettings({s: serverS}: MyProps) {
const [s, setS] = useState<azim_int_settings>(default_azim_int_settings);
const [lastDownloadedS, setLastDownloadedS] = useState<azim_int_settings>(default_azim_int_settings);
const [downloadCounter, setDownloadCounter] = useState(0);
const [lowQError, setLowQError] = useState(false);
const [highQError, setHighQError] = useState(false);
const [qSpacingError, setQSpacingError] = useState(false);
getValues = () => {
if (this.props.s !== undefined) {
let format_set: azim_int_settings = this.props.s;
if (!_.isEqual(format_set, this.state.last_downloaded_s)) {
this.setState(prevState => ({
s: format_set,
last_downloaded_s: format_set,
download_counter: prevState.download_counter + 1
}));
}
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
setDownloadCounter(c => c + 1);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
marginLeft: '8%',
marginRight: '8%',
}}>
<div><strong>Azimuthal integration settings </strong></div>
<Stack spacing={2} direction="row">
<NumberTextField
default={1.0}
start_val={s.q_spacing}
label={"Q spacing"}
min={1e-5}
units={"A-1"}
float={true}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, q_spacing: val}));
setQSpacingError(err);
}}
fullWidth/>
componentDidUpdate() {
this.getValues();
}
<NumberTextField
default={0.1}
start_val={s.low_q_recipA}
label={"Low Q"}
min={1e-5}
max={10.0}
units={"A-1"}
float={true}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, low_q_recipA: val}));
setLowQError(err);
}}
fullWidth/>
render() {
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
marginLeft: '8%',
marginRight: '8%',
}}>
<div><strong>Azimuthal integration settings </strong></div>
<Stack spacing={2} direction="row">
<NumberTextField
default={1.0}
start_val={this.state.s.q_spacing}
label={"Q spacing"}
min={1e-5}
units={"A-1"}
float={true}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, q_spacing: val},
q_spacing_error: err
}));
}}
fullWidth/>
<NumberTextField
default={0.1}
start_val={this.state.s.low_q_recipA}
label={"Low Q"}
min={1e-5}
max={10.0}
units={"A-1"}
float={true}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, low_q_recipA: val},
low_q_error: err
}));
}}
fullWidth/>
<NumberTextField
default={5}
start_val={this.state.s.high_q_recipA}
label={"High Q"}
min={1e-5}
max={10.0}
units={"A-1"}
float={true}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, high_q_recipA: val},
high_q_error: err
}));
}}
fullWidth/>
</Stack>
<FormControl>
<FormControlLabel control={
<Checkbox
checked={this.state.s.solid_angle_corr}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.state.s.solid_angle_corr = event.target.checked;
}}
/>} label="Solid angle correction"/>
<FormControlLabel control={
<Checkbox
checked={this.state.s.polarization_corr}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.state.s.polarization_corr = event.target.checked;
}}
/>} label="Polarization correction"/>
<FormControlLabel control={
<Checkbox
checked={this.state.s.force_cpu ?? false}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.state.s.force_cpu = event.target.checked;
}}
/>} label="Force CPU calculation in FPGA workflow"/>
</FormControl>
<Typography>Azimuthal bins</Typography>
<FormControl component="fieldset">
<RadioGroup
row
value={this.state.s.azimuthal_bins}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({
s: {...prevState.s, azimuthal_bins: Number(event.target.value)}
}));
}}
>
{[1, 2, 4, 8, 16, 32, 64, 128].map((value) => (
<FormControlLabel
key={value}
value={value}
control={<Radio/>}
label={value.toString()}
/>
))}
</RadioGroup>
</FormControl>
<ButtonWithSnackbar
color={"primary"}
path={"/config/azim_int"}
input={JSON.stringify(this.state.s)}
method={"PUT"}
text={"Upload"}
disabled={
(this.state.s.high_q_recipA <= this.state.s.low_q_recipA)
|| this.state.high_q_error
|| this.state.low_q_error
|| this.state.q_spacing_error
}
/>
<NumberTextField
default={5}
start_val={s.high_q_recipA}
label={"High Q"}
min={1e-5}
max={10.0}
units={"A-1"}
float={true}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, high_q_recipA: val}));
setHighQError(err);
}}
fullWidth/>
</Stack>
<br/>
</Paper>
}
<FormControl>
<FormControlLabel control={
<Checkbox
checked={s.solid_angle_corr}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, solid_angle_corr: event.target.checked}));
}}
/>} label="Solid angle correction"/>
<FormControlLabel control={
<Checkbox
checked={s.polarization_corr}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, polarization_corr: event.target.checked}));
}}
/>} label="Polarization correction"/>
<FormControlLabel control={
<Checkbox
checked={s.force_cpu ?? false}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, force_cpu: event.target.checked}));
}}
/>} label="Force CPU calculation in FPGA workflow"/>
</FormControl>
<Typography>Azimuthal bins</Typography>
<FormControl component="fieldset">
<RadioGroup
row
value={s.azimuthal_bins}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, azimuthal_bins: Number(event.target.value)}));
}}
>
{[1, 2, 4, 8, 16, 32, 64, 128].map((value) => (
<FormControlLabel
key={value}
value={value}
control={<Radio/>}
label={value.toString()}
/>
))}
</RadioGroup>
</FormControl>
<ButtonWithSnackbar
color={"primary"}
path={"/config/azim_int"}
input={JSON.stringify(s)}
method={"PUT"}
text={"Upload"}
disabled={
(s.high_q_recipA <= s.low_q_recipA)
|| highQError
|| lowQError
|| qSpacingError
}
/>
</Stack>
<br/>
</Paper>
}
export default AzIntSettings;
export default memo(AzIntSettings);
+49 -60
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {memo, SyntheticEvent, useState} from 'react';
import {
Alert, SnackbarCloseReason
@@ -15,86 +15,75 @@ type MyProps = {
method?: "GET" | "POST" | "PUT"
}
type MyState = {
snackbar_open: boolean,
start_success: boolean,
start_error?: string
}
function ButtonWithSnackbar({input, disabled, path, text, color, method}: MyProps) {
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [startSuccess, setStartSuccess] = useState(true);
const [startError, setStartError] = useState<string>();
class ButtonWithSnackbar extends Component<MyProps, MyState> {
state : MyState = {
snackbar_open: false,
start_success: true
}
const printError = (msg: string) => {
setSnackbarOpen(true);
setStartSuccess(false);
setStartError(msg);
};
printError = (msg: string) => {
this.setState({
snackbar_open: true,
start_success: false,
start_error: msg
});
}
handleResponse = (response : Response) => {
const handleResponse = (response : Response) => {
if (response.ok) {
this.setState({snackbar_open: true, start_success: true});
setSnackbarOpen(true);
setStartSuccess(true);
} else {
if (response.status == 404)
this.printError("404: Service not found");
printError("404: Service not found");
else if (response.status == 400)
this.printError("400: Input parsing or validation error");
printError("400: Input parsing or validation error");
else if (response.status == 500) {
response.text().then((val: string) => {
try {
this.printError(JSON.parse(val).msg);
printError(JSON.parse(val).msg);
} catch (e) {
this.printError("500: Unknown error");
printError("500: Unknown error");
}
});
}
}
}
};
startButton = () => {
fetch(this.props.path, {
method: (this.props.method === undefined) ? "POST" : this.props.method,
body: this.props.input
}).then(this.handleResponse);
}
const startButton = () => {
fetch(path, {
method: (method === undefined) ? "POST" : method,
body: input
}).then(handleResponse);
};
handleClose = (event?: React.SyntheticEvent | Event, reason?: SnackbarCloseReason) => {
const handleClose = (event?: SyntheticEvent | Event, reason?: SnackbarCloseReason) => {
if (reason === 'clickaway') {
return;
}
this.setState({snackbar_open: false});
setSnackbarOpen(false);
};
render() {
return <>
<Button
color={this.props.color}
onClick={this.startButton}
variant="contained"
disableElevation
disabled={this.props.disabled}
return <>
<Button
color={color}
onClick={startButton}
variant="contained"
disableElevation
disabled={disabled}
>
{text}
</Button>
<Snackbar open={snackbarOpen}
autoHideDuration={5000}
onClose={handleClose}>
<Alert
onClose={handleClose}
severity={startSuccess ? "success" : "error"}
variant="filled"
sx={{ width: '100%' }}
>
{this.props.text}
</Button>
<Snackbar open={this.state.snackbar_open}
autoHideDuration={5000}
onClose={this.handleClose}>
<Alert
onClose={this.handleClose}
severity={this.state.start_success ? "success" : "error"}
variant="filled"
sx={{ width: '100%' }}
>
{this.state.start_success ? "Ok!" : this.state.start_error}
</Alert>
</Snackbar>
</>
}
{startSuccess ? "Ok!" : startError}
</Alert>
</Snackbar>
</>
}
export default ButtonWithSnackbar;
export default memo(ButtonWithSnackbar);
+27 -33
View File
@@ -1,5 +1,4 @@
import React from 'react';
import {memo} from 'react';
import Paper from '@mui/material/Paper';
import {DataGrid, GridColDef} from "@mui/x-data-grid";
@@ -9,37 +8,32 @@ type MyProps = {
s?: calibration_statistics
};
type MyState = {};
const columns : GridColDef[] = [
{ field: 'module_number', type: 'number', headerName: 'Module' },
{ field: 'storage_cell_number', type: 'number', headerName: 'SC'},
{ field: 'masked_pixels', headerName: 'Masked pxls', type: 'number'},
{ field: 'pedestal_g0_mean', headerName: 'Pedestal G0', type: 'number'},
{ field: 'pedestal_g1_mean', headerName: 'Pedestal G1', type: 'number'},
{ field: 'pedestal_g2_mean', headerName: 'Pedestal G2', type: 'number'},
{ field: 'gain_g0_mean', headerName: 'Gain G0', type: 'number'},
{ field: 'gain_g1_mean', headerName: 'Gain G1', type: 'number'},
{ field: 'gain_g2_mean', headerName: 'Gain G2', type: 'number'}
];
class Calibration extends React.Component<MyProps, MyState> {
columns : GridColDef[] = [
{ field: 'module_number', type: 'number', headerName: 'Module' },
{ field: 'storage_cell_number', type: 'number', headerName: 'SC'},
{ field: 'masked_pixels', headerName: 'Masked pxls', type: 'number'},
{ field: 'pedestal_g0_mean', headerName: 'Pedestal G0', type: 'number'},
{ field: 'pedestal_g1_mean', headerName: 'Pedestal G1', type: 'number'},
{ field: 'pedestal_g2_mean', headerName: 'Pedestal G2', type: 'number'},
{ field: 'gain_g0_mean', headerName: 'Gain G0', type: 'number'},
{ field: 'gain_g1_mean', headerName: 'Gain G1', type: 'number'},
{ field: 'gain_g2_mean', headerName: 'Gain G2', type: 'number'}
];
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: 527, width: '100%' }}>
{((this.props.s !== undefined) && (this.props.s.length > 0)) ?
<DataGrid
getRowId={(row) => Number(row.module_number) * 64 + Number(row.storage_cell_number)}
rows={this.props.s}
columns={this.columns}
initialState={{
pagination: { paginationModel: { pageSize: 8 } },
}}
pageSizeOptions={[4,8,16]}
/> : <div/>
}
</Paper>
}
function Calibration({s}: MyProps) {
return <Paper style={{textAlign: 'center'}} sx={{ height: 527, width: '100%' }}>
{((s !== undefined) && (s.length > 0)) ?
<DataGrid
getRowId={(row) => Number(row.module_number) * 64 + Number(row.storage_cell_number)}
rows={s}
columns={columns}
initialState={{
pagination: { paginationModel: { pageSize: 8 } },
}}
pageSizeOptions={[4,8,16]}
/> : <div/>
}
</Paper>
}
export default Calibration;
export default memo(Calibration);
+145 -199
View File
@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import {memo, useEffect, useState} from 'react';
import Paper from '@mui/material/Paper';
import {Checkbox, FormControlLabel, Stack} from '@mui/material';
import _ from 'lodash';
@@ -10,18 +10,6 @@ type MyProps = {
s?: dark_mask_settings;
};
type MyState = {
s: dark_mask_settings;
last_downloaded_s: dark_mask_settings;
download_counter: number;
threshold_err: boolean;
frame_time_err: boolean;
number_of_frames_err: boolean;
number_of_frames_old: number;
max_pixel_count_err: boolean;
max_frames_with_signal_err: boolean;
};
const default_dark_mask_settings: dark_mask_settings = {
detector_threshold_keV: 4.0,
frame_time_us: 1000,
@@ -30,203 +18,161 @@ const default_dark_mask_settings: dark_mask_settings = {
max_frames_with_signal: 10,
};
class DarkMaskSettings extends Component<MyProps, MyState> {
state: MyState = {
s: default_dark_mask_settings,
last_downloaded_s: default_dark_mask_settings,
download_counter: 0,
threshold_err: false,
frame_time_err: false,
number_of_frames_err: false,
max_pixel_count_err: false,
max_frames_with_signal_err: false,
number_of_frames_old: 1000
};
function DarkMaskSettings({s: serverS}: MyProps) {
const [s, setS] = useState<dark_mask_settings>(default_dark_mask_settings);
const [lastDownloadedS, setLastDownloadedS] = useState<dark_mask_settings>(default_dark_mask_settings);
const [downloadCounter, setDownloadCounter] = useState(0);
const [thresholdErr, setThresholdErr] = useState(false);
const [frameTimeErr, setFrameTimeErr] = useState(false);
const [numberOfFramesErr, setNumberOfFramesErr] = useState(false);
const [numberOfFramesOld, setNumberOfFramesOld] = useState(1000);
const [maxPixelCountErr, setMaxPixelCountErr] = useState(false);
const [maxFramesWithSignalErr, setMaxFramesWithSignalErr] = useState(false);
getValues = () => {
if (this.props.s !== undefined) {
let incoming: dark_mask_settings = this.props.s;
if (!_.isEqual(incoming, this.state.last_downloaded_s)) {
this.setState((prev) => ({
s: incoming,
last_downloaded_s: incoming,
download_counter: prev.download_counter + 1,
threshold_err: false,
frame_time_err: false,
number_of_frames_err: false,
number_of_frames_old:
incoming.number_of_frames === 0 ? prev.number_of_frames_old : incoming.number_of_frames,
max_pixel_count_err: false,
max_frames_with_signal_err: false,
enable: (this.props.s?.number_of_frames !== 0)
}));
}
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
setDownloadCounter(c => c + 1);
setThresholdErr(false);
setFrameTimeErr(false);
setNumberOfFramesErr(false);
setNumberOfFramesOld(serverS.number_of_frames === 0 ? numberOfFramesOld : serverS.number_of_frames);
setMaxPixelCountErr(false);
setMaxFramesWithSignalErr(false);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
const hasError = () =>
thresholdErr ||
frameTimeErr ||
numberOfFramesErr ||
maxPixelCountErr ||
maxFramesWithSignalErr;
componentDidUpdate() {
this.getValues();
}
return (
<Paper style={{ textAlign: 'center' }} sx={{ width: '100%' }}>
<br />
<Stack
spacing={3}
sx={{
justifyContent: 'center',
alignItems: 'center',
}}
>
<strong>Dark data collection settings for mask</strong>
<div>(DECTRIS detectors only)</div>
hasError = () =>
this.state.threshold_err ||
this.state.frame_time_err ||
this.state.number_of_frames_err ||
this.state.max_pixel_count_err ||
this.state.max_frames_with_signal_err;
<FormControlLabel
control={
<Checkbox
checked={s.number_of_frames !== 0}
onChange={(event) => {
setDownloadCounter(c => c + 1);
setNumberOfFramesErr(false);
if (!event.target.checked)
setS(prev => ({...prev, number_of_frames: 0}));
else
setS(prev => ({...prev, number_of_frames: numberOfFramesOld}));
}}
/>
}
label="Enable dark data collection"
/>
render() {
return (
<Paper style={{ textAlign: 'center' }} sx={{ width: '100%' }}>
<br />
<Stack
spacing={3}
sx={{
justifyContent: 'center',
alignItems: 'center',
}}
>
<strong>Dark data collection settings for mask</strong>
<div>(DECTRIS detectors only)</div>
<Stack spacing={3} direction="row" sx={{ width: '80%' }}>
<FormControlLabel
control={
<Checkbox
checked={this.state.s.number_of_frames !== 0}
onChange={(event) => {
if (!event.target.checked)
this.setState(prevState => ({
download_counter: this.state.download_counter + 1,
number_of_frames_err: false,
s: {
...prevState.s,
number_of_frames: 0
}
}));
else
this.setState(prevState => ({
download_counter: this.state.download_counter + 1,
number_of_frames_err: false,
s: {
...prevState.s,
number_of_frames: this.state.number_of_frames_old
}
}));
}}
/>
}
label="Enable dark data collection"
<NumberTextField
start_val={s.detector_threshold_keV}
label={'Detector threshold'}
units={'keV'}
float={true}
min={0.001}
default={4.0}
counter={downloadCounter}
disabled={s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
setThresholdErr(err);
setS(prev => ({ ...prev, detector_threshold_keV: val }));
}}
/>
<Stack spacing={3} direction="row" sx={{ width: '80%' }}>
<NumberTextField
start_val={s.frame_time_us / 1000.0}
label={'Frame time'}
units={'ms'}
float={false}
min={1}
max={1000}
default={10}
counter={downloadCounter}
disabled={s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
setFrameTimeErr(err);
setS(prev => ({ ...prev, frame_time_us: val * 1000 }));
}}
/>
<NumberTextField
start_val={s.number_of_frames}
label={'Number of frames'}
float={false}
min={1}
default={1000}
counter={downloadCounter}
disabled={s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
if (!err)
setNumberOfFramesOld(val);
setNumberOfFramesErr(err);
setS(prev => ({...prev, number_of_frames: val}));
}}
<NumberTextField
start_val={this.state.s.detector_threshold_keV}
label={'Detector threshold'}
units={'keV'}
float={true}
min={0.001}
default={4.0}
counter={this.state.download_counter}
disabled={this.state.s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
this.setState((prev) => ({
threshold_err: err,
s: { ...prev.s, detector_threshold_keV: val },
}));
}}
/>
<NumberTextField
start_val={this.state.s.frame_time_us / 1000.0}
label={'Frame time'}
units={'ms'}
float={false}
min={1}
max={1000}
default={10}
counter={this.state.download_counter}
disabled={this.state.s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
this.setState((prev) => ({
frame_time_err: err,
s: { ...prev.s, frame_time_us: val * 1000 },
}));
}}
/>
<NumberTextField
start_val={this.state.s.number_of_frames}
label={'Number of frames'}
float={false}
min={1}
default={1000}
counter={this.state.download_counter}
disabled={this.state.s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
let old: number = this.state.number_of_frames_old;
if (!err)
old = val;
this.setState(prevState => ({
number_of_frames_err: err,
number_of_frames_old: old,
s: {...prevState.s, number_of_frames: val}
}));
}}
/>
</Stack>
<Stack spacing={3} direction="row" sx={{ width: '80%' }}>
<NumberTextField
start_val={this.state.s.max_allowed_pixel_count}
label={'Max allowed pixel count for valid pixel'}
float={false}
min={0}
default={1}
fullWidth={true}
disabled={this.state.s.number_of_frames === 0}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState((prev) => ({
max_pixel_count_err: err,
s: { ...prev.s, max_allowed_pixel_count: val },
}));
}}
/>
<NumberTextField
start_val={this.state.s.max_frames_with_signal}
label={'Max frames with invalid pixel'}
float={false}
min={0}
default={0}
fullWidth={true}
counter={this.state.download_counter}
disabled={this.state.s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
this.setState((prev) => ({
max_frames_with_signal_err: err,
s: { ...prev.s, max_frames_with_signal: val },
}));
}}
/>
</Stack>
<ButtonWithSnackbar
path={'/config/dark_mask'}
color={'primary'}
method={'PUT'}
disabled={this.hasError()}
text={'Upload'}
input={JSON.stringify(this.state.s)}
/>
</Stack>
<br />
</Paper>
);
}
<Stack spacing={3} direction="row" sx={{ width: '80%' }}>
<NumberTextField
start_val={s.max_allowed_pixel_count}
label={'Max allowed pixel count for valid pixel'}
float={false}
min={0}
default={1}
fullWidth={true}
disabled={s.number_of_frames === 0}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setMaxPixelCountErr(err);
setS(prev => ({ ...prev, max_allowed_pixel_count: val }));
}}
/>
<NumberTextField
start_val={s.max_frames_with_signal}
label={'Max frames with invalid pixel'}
float={false}
min={0}
default={0}
fullWidth={true}
counter={downloadCounter}
disabled={s.number_of_frames === 0}
callback={(val: number, err: boolean) => {
setMaxFramesWithSignalErr(err);
setS(prev => ({ ...prev, max_frames_with_signal: val }));
}}
/>
</Stack>
<ButtonWithSnackbar
path={'/config/dark_mask'}
color={'primary'}
method={'PUT'}
disabled={hasError()}
text={'Upload'}
input={JSON.stringify(s)}
/>
</Stack>
<br />
</Paper>
);
}
export default DarkMaskSettings;
export default memo(DarkMaskSettings);
+250 -314
View File
@@ -1,7 +1,7 @@
import React from 'react';
import {ChangeEvent, memo, useState} from 'react';
import Paper from '@mui/material/Paper';
import {Stack, TextField, Radio, RadioGroup, FormControlLabel, FormControl, FormLabel, Checkbox} from "@mui/material";
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";
@@ -10,325 +10,261 @@ type MyProps = {
frame_time_us: number
};
type MyState = {
s: dataset_settings,
beam_x_pxl_err: boolean,
beam_y_pxl_err: boolean,
detector_distance_mm_err: boolean,
incident_energy_kev_err: boolean,
images_per_trigger_err: boolean,
images_per_file_err: boolean,
image_time_us_err: boolean,
ntrigger_err: boolean,
mode: 'still' | 'rotation' | 'grid',
goniometer: rotation_axis,
grid: grid_scan,
};
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');
class DataCollection extends React.Component<MyProps, MyState> {
state : MyState = {
s : {
beam_x_pxl: 0,
beam_y_pxl: 0,
detector_distance_mm: 100,
incident_energy_keV: 12.398,
images_per_trigger: 1,
image_time_us: this.props.frame_time_us,
ntrigger: 1,
images_per_file: 1000
},
goniometer : {
start: 0,
step: 0.1,
name: "omega",
vector: [0,1,0]
},
grid: {
n_fast: 10,
step_x_um: 10.0,
step_y_um: 10.0,
vertical: false,
snake: false
},
beam_x_pxl_err: false,
beam_y_pxl_err: false,
detector_distance_mm_err: false,
incident_energy_kev_err: false,
images_per_trigger_err: false,
image_time_us_err: false,
ntrigger_err: false,
images_per_file_err: false,
mode: 'still',
}
const error = () : boolean =>
beamXPxlErr
|| beamYPxlErr
|| detectorDistanceMmErr
|| incidentEnergyKevErr
|| imageTimeUsErr
|| imagesPerTriggerErr
|| imagesPerFileErr
|| ntriggerErr;
error() : boolean {
return this.state.beam_x_pxl_err
|| this.state.beam_y_pxl_err
|| this.state.detector_distance_mm_err
|| this.state.incident_energy_kev_err
|| this.state.image_time_us_err
|| this.state.images_per_trigger_err
|| this.state.images_per_file_err
|| this.state.ntrigger_err;
}
getDatasetSettings = () => {
let d = this.state.s;
if (this.state.mode === 'rotation')
d.goniometer = this.state.goniometer;
else if (this.state.mode === 'grid')
d.grid_scan = this.state.grid;
const getDatasetSettings = () : dataset_settings => {
let d = {...s};
if (mode === 'rotation')
d.goniometer = goniometer;
else if (mode === 'grid')
d.grid_scan = grid;
return d;
}
};
render() {
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: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({
s: {...prevState.s, file_prefix: event.target.value}
}));
}
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={this.state.s.file_prefix}
sx={{width:"90%"}}
}
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}
/>
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label={"Number of images per trigger"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, images_per_trigger: val},
images_per_trigger_err: err
}
));
}}
min={1}
default={this.state.s.images_per_trigger}
/>
<NumberTextField
label={"Number of triggers"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, ntrigger: val},
ntrigger_err: err
}
));
}}
min={1}
default={this.state.s.ntrigger}
/>
<NumberTextField
label={"Image time"}
callback={(val: number, err: boolean) => {
let image_time_us = Math.round(val * 1000.0);
this.setState(prevState => ({
s: {...prevState.s,
image_time_us: image_time_us
},
image_time_us_err: err
}
));
}}
units={"ms"}
float={true}
min={0.01}
default={(this.state.s.image_time_us ?? 500) / 1000.0}
/>
<NumberTextField
label={"Images per file"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s,
images_per_file: val
},
images_per_file_err: err
}
));
}}
float={false}
min={1}
default={this.state.s.images_per_file}
/>
</Stack>
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label={"Beam X [pxl]"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, beam_x_pxl: val},
beam_x_pxl_err: err
}
));
}}
units={"pxl"}
float={true}
default={this.state.s.beam_x_pxl}
/>
<NumberTextField
label={"Beam Y"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, beam_y_pxl: val},
beam_y_pxl_err: err
}
));
}}
units={"pxl"}
float={true}
default={this.state.s.beam_y_pxl}
/>
<NumberTextField
label={"Detector distance"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, detector_distance_mm: val},
detector_distance_mm_err: err
}
));
}}
units={"mm"}
min={0.1}
float={true}
default={this.state.s.detector_distance_mm}
/>
<NumberTextField
label={"Energy"}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, incident_energy_keV: val},
incident_energy_kev_err: err
}
));
}}
min={0.1}
max={500.0}
units={"keV"}
float={true}
default={this.state.s.incident_energy_keV}
/>
</Stack>
<FormControl sx={{width: '90%'}}>
<RadioGroup
row
value={this.state.mode}
onChange={(event) => this.setState({mode: 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>
{this.state.mode === 'rotation' && (
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label="Start angle"
callback={(val: number) => this.setState(prevState => (
{goniometer : {...prevState.goniometer, start: val}}
))}
units="°"
float={true}
default={this.state.goniometer.start}
/>
<NumberTextField
label="Step"
callback={(val: number) => this.setState(prevState => (
{goniometer : {...prevState.goniometer, step: val}}
))}
units="°"
float={true}
min={0.001}
default={this.state.goniometer.step}
/>
<TextField
label="Axis name"
value={this.state.goniometer.name}
onChange={(e) => this.setState(prevState => (
{goniometer : {...prevState.goniometer, name: e.target.value}}
))}
/>
</Stack>
)}
{this.state.mode === 'grid' && (
<Stack direction="row" spacing={2} sx={{width: '90%'}}>
<NumberTextField
label="Grid elements (fast dim.)"
callback={(val: number) => this.setState(
prevState => ({grid : {...prevState.grid, n_fast: val}}))}
min={1}
default={this.state.grid.n_fast}
/>
<NumberTextField
label="X step"
callback={(val: number) => this.setState(
prevState => ({grid : {...prevState.grid, step_x_um: val}}))}
units="μm"
float={true}
default={this.state.grid.step_x_um}
/>
<NumberTextField
label="Y step"
callback={(val: number) => this.setState(
prevState => ({grid : {...prevState.grid, step_y_um: val}}))}
units="μm"
float={true}
default={this.state.grid.step_y_um}
/>
<FormControlLabel
control={
<Checkbox
checked={this.state.grid.snake}
onChange={(e) => this.setState(prevState => (
{grid : {...prevState.grid, snake: e.target.checked}}
))}
/>
}
label="Snake scan"
/>
<FormControlLabel
control={
<Checkbox
checked={this.state.grid.vertical}
onChange={(e) => this.setState(prevState => (
{grid : {...prevState.grid, vertical: e.target.checked}}
))}
/>
}
label="Vertical scan"
/>
</Stack>
)}
<Stack direction="row" spacing={2}>
<ButtonWithSnackbar
path={"/start"}
text={"start"}
disabled={this.error()}
input={JSON.stringify(this.getDatasetSettings())}
color={"primary"}
/>
<ButtonWithSnackbar
path={"/trigger"}
text={"soft trigger"}
disabled={this.error()}
color={"primary"}
/>
</Stack>
<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>
<br/>
</Paper>
}
<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 DataCollection;
export default memo(DataCollection);
+42 -75
View File
@@ -1,6 +1,8 @@
import React, {Component, ReactNode} from 'react';
import {ReactNode} from 'react';
import {azint_unit, getPreviewPlot, plot_type, plot_unit_x, plots} from "../client";
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 = {
@@ -10,11 +12,6 @@ type MyProps = {
azint: azint_unit;
};
type MyState = {
plots : plots,
connection_error: boolean
}
type PlotlyPlot = {
x: number[],
y: (number | null)[],
@@ -97,79 +94,49 @@ function AxisTypeY(plot: plot_type) : string | ReactNode {
}
}
class DataProcessingPlot extends Component<MyProps, MyState> {
interval: ReturnType<typeof setInterval> | undefined;
function DataProcessingPlot({type, binning, angle, azint}: MyProps) {
const {data: plots, isError} = useQuery({
...getPreviewPlotOptions({ query: { type, binning, experimental_coord: angle, azint_unit: azint } }),
refetchInterval: 1000,
});
state: MyState = {
plots: {
plot : [
{
x: [0, 100, 200, 300, 400, 500],
y: [0.1, 0.3, 0.5, 0.2, 0.0, 0.1],
title: "Example"
}
],
unit_x: plot_unit_x.IMAGE_NUMBER
},
connection_error: true
}
if (isError
|| (plots === undefined)
|| (plots.plot === null)
|| (plots.plot.length === 0))
return <div>No plots available</div>;
getValues() {
getPreviewPlot({ throwOnError: true, query: { type: this.props.type, binning: this.props.binning, experimental_coord: this.props.angle, azint_unit: this.props.azint } })
.then(({data}) => this.setState({plots: data, connection_error: false}))
.catch(error => {
this.setState({connection_error: true});
});
}
let data: PlotlyData = [];
if ((plots.plot[0].z !== undefined)
&& (plots.plot[0].z.length > 0)) {
componentDidMount() {
this.getValues();
this.interval = setInterval(() => this.getValues(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
if (this.state.connection_error
|| (this.state.plots === undefined)
|| (this.state.plots.plot === null)
|| (this.state.plots.plot.length === 0))
return <div>No plots available</div>;
let data: PlotlyData = [];
if ((this.state.plots.plot[0].z !== undefined)
&& (this.state.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: this.state.plots.plot[0].x,
y: this.state.plots.plot[0].y,
z: this.state.plots.plot[0].z,
type: "heatmap",
colorscale: "Viridis"
})
return <MultiLinePlotWrapper xaxis={AxisTypeX(this.state.plots.unit_x)}
yaxis={AxisTypeY(this.props.type)}
data={data}
grid_scan={true}
range_x={this.state.plots.size_x}
range_y={this.state.plots.size_y}/>
} else {
this.state.plots.plot.map(d =>
data.push({
x: d.x,
y: d.y,
type: "scatter",
mode: "line",
name: d.title
}));
return <MultiLinePlotWrapper xaxis={AxisTypeX(this.state.plots.unit_x)}
yaxis={AxisTypeY(this.props.type)}
data={data}
grid_scan={false}/>
}
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}/>
}
}
+106 -118
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {memo, useState} from 'react';
import Paper from '@mui/material/Paper';
import {Box, Checkbox, FormControlLabel, Grid} from "@mui/material";
@@ -14,131 +14,119 @@ type MyProps = {
height: number
}
type MyState = {
type: plot_type,
binning: string,
tab : String,
angle_units: boolean,
azint_unit: azint_unit
}
function DataProcessingPlots({type: initialType, height}: MyProps) {
const [type, setType] = useState<plot_type>(initialType);
const [binning, setBinning] = useState("0");
const [tab, setTab] = useState<String>(initialType);
const [angleUnits, setAngleUnits] = useState(true);
const [azintUnit, setAzintUnit] = useState<azint_unit>(azint_unit.Q_RECIP_A);
class DataProcessingPlots extends Component<MyProps, MyState> {
state: MyState = {
type: this.props.type,
binning: "0",
tab: this.props.type,
angle_units: true,
azint_unit: azint_unit.Q_RECIP_A
}
plotTypeChange = (event : SelectChangeEvent<String>) => {
const plotTypeChange = (event : SelectChangeEvent<String>) => {
let val = String(event.target.value).toString();
this.setState({tab: val, type: val as plot_type});
setTab(val);
setType(val as plot_type);
};
handleChange = (event : SelectChangeEvent<String>) => {
this.setState({ binning: String(event.target.value).toString() });
const handleChange = (event : SelectChangeEvent<String>) => {
setBinning(String(event.target.value).toString());
};
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: this.props.height, width: "100%" }}>
<Toolbar>
return <Paper style={{textAlign: 'center'}} sx={{ height: height, width: "100%" }}>
<Toolbar>
<Grid container sx={{ minWidth: 500 }} >
<FormControl variant="standard" sx={{ m: 1, minWidth: 500 }}>
<Grid container sx={{ minWidth: 500 }} >
<FormControl variant="standard" sx={{ m: 1, minWidth: 500 }}>
<Select
value={this.state.tab}
onChange={this.plotTypeChange}
label="Plot category"
sx={{fontWeight: "bold"}}
>
<MenuItem value={plot_type.INDEXING_RATE}>Indexing rate</MenuItem>
<MenuItem value={plot_type.INDEXING_UNIT_CELL_LENGTH}>Indexing unit cell (length)</MenuItem>
<MenuItem value={plot_type.INDEXING_UNIT_CELL_ANGLE}>Indexing unit cell (angle)</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT}>Spot count</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT_LOW_RES}>Spot count low res.</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT_INDEXED}>Spot count indexed</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT_ICE}>Spot count ice ring</MenuItem>
<MenuItem value={plot_type.AZINT}>Azimuthal integration profile</MenuItem>
<MenuItem value={plot_type.AZINT_1D}>Azimuthal integration profile (1D)</MenuItem>
<MenuItem value={plot_type.BKG_ESTIMATE}>Background estimate</MenuItem>
<MenuItem value={plot_type.RESOLUTION_ESTIMATE}>Diffraction resolution estimate</MenuItem>
<MenuItem value={plot_type.ROI_SUM}>ROI area sum</MenuItem>
<MenuItem value={plot_type.ROI_MEAN}>ROI area mean</MenuItem>
<MenuItem value={plot_type.ROI_WEIGHTED_X}>ROI area weighted X position</MenuItem>
<MenuItem value={plot_type.ROI_WEIGHTED_Y}>ROI area weighted Y position</MenuItem>
<MenuItem value={plot_type.ROI_MAX_COUNT}>ROI area max count</MenuItem>
<MenuItem value={plot_type.ERROR_PIXELS}>Error pixels</MenuItem>
<MenuItem value={plot_type.SATURATED_PIXELS}>Saturated pixels</MenuItem>
<MenuItem value={plot_type.STRONG_PIXELS}>Strong pixels (for spot finding)</MenuItem>
<MenuItem value={plot_type.MAX_PIXEL_VALUE}>Maximum pixel value (excl. overloads)</MenuItem>
<MenuItem value={plot_type.PROFILE_RADIUS}>Profile radius</MenuItem>
<MenuItem value={plot_type.B_FACTOR}>Wilson B-factor</MenuItem>
<MenuItem value={plot_type.BEAM_CENTER_X}>Beam center X (post-indexing geometry refinement)</MenuItem>
<MenuItem value={plot_type.BEAM_CENTER_Y}>Beam center Y (post-indexing geometry refinement)</MenuItem>
<MenuItem value={plot_type.INDEXING_LATTICE_COUNT}>Indexed lattice count</MenuItem>
<MenuItem value={plot_type.INTEGRATED_REFLECTIONS}>Integrated reflections</MenuItem>
<MenuItem value={plot_type.IMAGE_COLLECTION_EFFICIENCY}>Image collection efficiency</MenuItem>
<MenuItem value={plot_type.COMPRESSION_RATIO}>Compression ratio</MenuItem>
<MenuItem value={plot_type.PROCESSING_TIME}> Processing time</MenuItem>
<MenuItem value={plot_type.RECEIVER_DELAY}>Receiver delay (internal debugging)</MenuItem>
<MenuItem value={plot_type.RECEIVER_FREE_SEND_BUF}>Receiver free send buffers (internal debugging)</MenuItem>
</Select>
<Select
value={tab}
onChange={plotTypeChange}
label="Plot category"
sx={{fontWeight: "bold"}}
>
<MenuItem value={plot_type.INDEXING_RATE}>Indexing rate</MenuItem>
<MenuItem value={plot_type.INDEXING_UNIT_CELL_LENGTH}>Indexing unit cell (length)</MenuItem>
<MenuItem value={plot_type.INDEXING_UNIT_CELL_ANGLE}>Indexing unit cell (angle)</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT}>Spot count</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT_LOW_RES}>Spot count low res.</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT_INDEXED}>Spot count indexed</MenuItem>
<MenuItem value={plot_type.SPOT_COUNT_ICE}>Spot count ice ring</MenuItem>
<MenuItem value={plot_type.AZINT}>Azimuthal integration profile</MenuItem>
<MenuItem value={plot_type.AZINT_1D}>Azimuthal integration profile (1D)</MenuItem>
<MenuItem value={plot_type.BKG_ESTIMATE}>Background estimate</MenuItem>
<MenuItem value={plot_type.RESOLUTION_ESTIMATE}>Diffraction resolution estimate</MenuItem>
<MenuItem value={plot_type.ROI_SUM}>ROI area sum</MenuItem>
<MenuItem value={plot_type.ROI_MEAN}>ROI area mean</MenuItem>
<MenuItem value={plot_type.ROI_WEIGHTED_X}>ROI area weighted X position</MenuItem>
<MenuItem value={plot_type.ROI_WEIGHTED_Y}>ROI area weighted Y position</MenuItem>
<MenuItem value={plot_type.ROI_MAX_COUNT}>ROI area max count</MenuItem>
<MenuItem value={plot_type.ERROR_PIXELS}>Error pixels</MenuItem>
<MenuItem value={plot_type.SATURATED_PIXELS}>Saturated pixels</MenuItem>
<MenuItem value={plot_type.STRONG_PIXELS}>Strong pixels (for spot finding)</MenuItem>
<MenuItem value={plot_type.MAX_PIXEL_VALUE}>Maximum pixel value (excl. overloads)</MenuItem>
<MenuItem value={plot_type.PROFILE_RADIUS}>Profile radius</MenuItem>
<MenuItem value={plot_type.B_FACTOR}>Wilson B-factor</MenuItem>
<MenuItem value={plot_type.BEAM_CENTER_X}>Beam center X (post-indexing geometry refinement)</MenuItem>
<MenuItem value={plot_type.BEAM_CENTER_Y}>Beam center Y (post-indexing geometry refinement)</MenuItem>
<MenuItem value={plot_type.INDEXING_LATTICE_COUNT}>Indexed lattice count</MenuItem>
<MenuItem value={plot_type.INTEGRATED_REFLECTIONS}>Integrated reflections</MenuItem>
<MenuItem value={plot_type.IMAGE_COLLECTION_EFFICIENCY}>Image collection efficiency</MenuItem>
<MenuItem value={plot_type.COMPRESSION_RATIO}>Compression ratio</MenuItem>
<MenuItem value={plot_type.PROCESSING_TIME}> Processing time</MenuItem>
<MenuItem value={plot_type.RECEIVER_DELAY}>Receiver delay (internal debugging)</MenuItem>
<MenuItem value={plot_type.RECEIVER_FREE_SEND_BUF}>Receiver free send buffers (internal debugging)</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid container justifyContent="flex-end">
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<FormControlLabel
control={
<Checkbox
checked={this.state.angle_units}
onChange={(e) => this.setState(
{angle_units: e.target.checked})}
/>
}
label="X-axis exp. units"
/></FormControl>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<FormControlLabel
control={
<Checkbox
checked={this.state.azint_unit == azint_unit.TWO_THETA_DEG}
onChange={(e) => this.setState({
azint_unit: e.target.checked ? azint_unit.TWO_THETA_DEG : azint_unit.Q_RECIP_A
})}
/>
}
label="2θ (az. int.)"
/>
</FormControl>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<Select
value={this.state.binning}
onChange={this.handleChange}
label="Binning"
>
<MenuItem value={0}>
<em>Auto binning</em>
</MenuItem>
<MenuItem value={1}>1 image</MenuItem>
<MenuItem value={10}>10 images</MenuItem>
<MenuItem value={100}>100 images</MenuItem>
<MenuItem value={500}>500 images</MenuItem>
<MenuItem value={1000}>1000 images</MenuItem>
<MenuItem value={5000}>5000 images</MenuItem>
<MenuItem value={10000}>10000 images</MenuItem>
</Select>
</FormControl>
</Grid>
</Toolbar>
<Box sx={{width:"95%", height: this.props.height - 150}} >
<DataProcessingPlot type={this.state.type} binning={Number(this.state.binning)} angle={this.state.angle_units}
azint={this.state.azint_unit}/>
</Box>
</Paper>
}
</FormControl>
</Grid>
<Grid container justifyContent="flex-end">
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<FormControlLabel
control={
<Checkbox
checked={angleUnits}
onChange={(e) => setAngleUnits(e.target.checked)}
/>
}
label="X-axis exp. units"
/></FormControl>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<FormControlLabel
control={
<Checkbox
checked={azintUnit == azint_unit.TWO_THETA_DEG}
onChange={(e) => setAzintUnit(
e.target.checked ? azint_unit.TWO_THETA_DEG : azint_unit.Q_RECIP_A
)}
/>
}
label="2θ (az. int.)"
/>
</FormControl>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<Select
value={binning}
onChange={handleChange}
label="Binning"
>
<MenuItem value={0}>
<em>Auto binning</em>
</MenuItem>
<MenuItem value={1}>1 image</MenuItem>
<MenuItem value={10}>10 images</MenuItem>
<MenuItem value={100}>100 images</MenuItem>
<MenuItem value={500}>500 images</MenuItem>
<MenuItem value={1000}>1000 images</MenuItem>
<MenuItem value={5000}>5000 images</MenuItem>
<MenuItem value={10000}>10000 images</MenuItem>
</Select>
</FormControl>
</Grid>
</Toolbar>
<Box sx={{width:"95%", height: height - 150}} >
<DataProcessingPlot type={type} binning={Number(binning)} angle={angleUnits}
azint={azintUnit}/>
</Box>
</Paper>
}
export default DataProcessingPlots;
export default memo(DataProcessingPlots);
+161 -223
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {ChangeEvent, memo, useEffect, useState} from 'react';
import Paper from '@mui/material/Paper';
import {Grid, Slider, Switch, Typography} from "@mui/material";
@@ -10,12 +10,6 @@ type MyProps = {
update: () => void
};
type MyState = {
s: spot_finding_settings,
last_downloaded_s: spot_finding_settings,
high_res_gap_Q_recipA: number
}
const default_spot_finding_settings: spot_finding_settings = {
enable: true,
indexing: true,
@@ -31,221 +25,165 @@ const default_spot_finding_settings: spot_finding_settings = {
high_res_gap_Q_recipA: 1.5
};
class DataProcessingSettings extends Component<MyProps, MyState> {
state : MyState = {
s: default_spot_finding_settings,
last_downloaded_s: default_spot_finding_settings,
high_res_gap_Q_recipA: 1.5
}
putValues(x: spot_finding_settings) {
putConfigSpotFinding({ body: x, throwOnError: true })
.catch(error => console.log(error) );
}
getValues() {
const incoming = this.props.s;
// Only adopt the server copy when it actually changed, otherwise the
// 1 s statistics poll would overwrite edits the user is making.
if ((incoming !== undefined) && !_.isEqual(incoming, this.state.last_downloaded_s))
this.setState(prevState => ({
s: incoming,
last_downloaded_s: incoming,
high_res_gap_Q_recipA: incoming.high_res_gap_Q_recipA ?? prevState.high_res_gap_Q_recipA
}));
}
componentDidMount() {
this.getValues();
}
componentDidUpdate() {
this.getValues();
}
setPhotonCountThreshold = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {...prevState.s, photon_count_threshold: newValue as number}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
setSignalToNoiseThreshold = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {...prevState.s, signal_to_noise_threshold: newValue as number}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
setMinPixPerSpot = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {...prevState.s, min_pix_per_spot: newValue as number}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
setLowResolutionLimit = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {...prevState.s, low_resolution_limit: newValue as number}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
setHighResolutionLimit = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {...prevState.s, high_resolution_limit: newValue as number}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
setHighResolutionLimitForCountingLowResSpots = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {
...prevState.s,
high_resolution_limit_for_spot_count_low_res: newValue as number
}}), () => {this.putValues(this.state.s);});
this.props.update();
}
setIceRingWidth = (event: Event, newValue: number | number[]) => {
this.setState(prevState => ({s: {
...prevState.s,
ice_ring_width_q_recipA: newValue as number
}}), () => {this.putValues(this.state.s);});
this.props.update();
}
enableSpotFindingToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({s: {...prevState.s, enable: event.target.checked}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
enableIndexingToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({s: {...prevState.s, indexing: event.target.checked}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
enableQuickIntegrationToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({s: {...prevState.s, quick_integration: event.target.checked}}),
() => {this.putValues(this.state.s);});
this.props.update();
}
enableHighResGapToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
const checked = event.target.checked;
this.setState(prevState => {
return {
s: {
...prevState.s,
high_res_gap_Q_recipA: checked ? prevState.high_res_gap_Q_recipA : undefined
}
};
}, () => { this.putValues(this.state.s); });
this.props.update();
}
setHighResGap = (event: Event, newValue: number | number[]) => {
const v = newValue as number;
this.setState(prevState => ({
s: {
...prevState.s,
high_res_gap_Q_recipA: v
},
high_res_gap_Q_recipA: v
}), () => { this.putValues(this.state.s); });
this.props.update();
}
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: 800, width: '100%' }}>
<Grid container spacing={0}>
<Grid item xs={1}/>
<Grid item xs={10}>
<br/><strong>Spot finding parameters</strong><br/><br/>
<Switch onChange={this.enableSpotFindingToggle} checked={this.state.s.enable}/>
Spot finding
<br/><br/>
<Typography gutterBottom> Count threshold </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.photon_count_threshold)}
onChange={this.setPhotonCountThreshold}
min={1} max={50} step={1} valueLabelDisplay="auto"/>
<br/><Typography> Signal-to-noise threshold </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.signal_to_noise_threshold)}
onChange={this.setSignalToNoiseThreshold}
min={2}
max={10}
step={0.5}
valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<br/><Typography> Minimum pixel / spot </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.min_pix_per_spot)}
onChange={this.setMinPixPerSpot}
min={1} max={8} step={1} valueLabelDisplay="auto"/>
<Typography> Low resolution limit [&#8491;] </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.low_resolution_limit)}
onChange={this.setLowResolutionLimit}
min={10} max={100} step={0.1} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<Typography> High resolution limit [&#8491;] </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.high_resolution_limit)}
onChange={this.setHighResolutionLimit}
min={1} max={5} step={0.1} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<Typography> High resolution limit for counting low resolution spots [&#8491;] </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.high_resolution_limit_for_spot_count_low_res)}
onChange={this.setHighResolutionLimitForCountingLowResSpots}
min={2} max={8} step={0.1} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<Typography> Ice ring width in Q-space [&#8491;<sup>-1</sup>] </Typography>
<Slider disabled={!this.state.s.enable}
value={Number(this.state.s.ice_ring_width_q_recipA)}
onChange={this.setIceRingWidth}
min={0.0} max={0.2} step={0.001} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(3)}
/>
<Switch
onChange={this.enableHighResGapToggle}
checked={this.state.s.high_res_gap_Q_recipA !== undefined}
disabled={!this.state.s.enable}
/>
Highres gap Q-space filter [&#8491;<sup>-1</sup>]
<Typography/>
<Slider
disabled={!this.state.s.enable || this.state.s.high_res_gap_Q_recipA === undefined}
value={Number(this.state.s.high_res_gap_Q_recipA ?? this.state.high_res_gap_Q_recipA)}
onChange={this.setHighResGap}
min={0.1}
max={5.0}
step={0.01}
valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(2)}
/>
<br/><br/>
<Switch onChange={this.enableIndexingToggle}
checked={this.state.s.indexing}
disabled={!this.state.s.enable}/>
Indexing <br/><br/>
<Switch onChange={this.enableQuickIntegrationToggle}
checked={this.state.s.quick_integration}
disabled={!this.state.s.enable}/>
Quick MX integration
</Grid>
<Grid item xs={1}/>
</Grid>
</Paper>
}
function putValues(x: spot_finding_settings) {
putConfigSpotFinding({ body: x, throwOnError: true })
.catch(error => console.log(error) );
}
export default DataProcessingSettings;
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);
// Only adopt the server copy when it actually changed, otherwise the
// 1 s statistics poll would overwrite edits the user is making.
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
setHighResGap(serverS.high_res_gap_Q_recipA ?? highResGap);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
const apply = (next: spot_finding_settings) => {
setS(next);
putValues(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[]) =>
apply({...s, high_resolution_limit: newValue as number});
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});
const enableIndexingToggle = (event: ChangeEvent<HTMLInputElement>) =>
apply({...s, indexing: event.target.checked});
const enableQuickIntegrationToggle = (event: ChangeEvent<HTMLInputElement>) =>
apply({...s, quick_integration: event.target.checked});
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 <Paper style={{textAlign: 'center'}} sx={{ height: 800, width: '100%' }}>
<Grid container spacing={0}>
<Grid item xs={1}/>
<Grid item xs={10}>
<br/><strong>Spot finding parameters</strong><br/><br/>
<Switch onChange={enableSpotFindingToggle} checked={s.enable}/>
Spot finding
<br/><br/>
<Typography gutterBottom> Count threshold </Typography>
<Slider disabled={!s.enable}
value={Number(s.photon_count_threshold)}
onChange={setPhotonCountThreshold}
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}
min={2}
max={10}
step={0.5}
valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<br/><Typography> Minimum pixel / spot </Typography>
<Slider disabled={!s.enable}
value={Number(s.min_pix_per_spot)}
onChange={setMinPixPerSpot}
min={1} max={8} step={1} valueLabelDisplay="auto"/>
<Typography> Low resolution limit [&#8491;] </Typography>
<Slider disabled={!s.enable}
value={Number(s.low_resolution_limit)}
onChange={setLowResolutionLimit}
min={10} max={100} step={0.1} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<Typography> High resolution limit [&#8491;] </Typography>
<Slider disabled={!s.enable}
value={Number(s.high_resolution_limit)}
onChange={setHighResolutionLimit}
min={1} max={5} step={0.1} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<Typography> High resolution limit for counting low resolution spots [&#8491;] </Typography>
<Slider disabled={!s.enable}
value={Number(s.high_resolution_limit_for_spot_count_low_res)}
onChange={setHighResolutionLimitForCountingLowResSpots}
min={2} max={8} step={0.1} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(1)}
/>
<Typography> Ice ring width in Q-space [&#8491;<sup>-1</sup>] </Typography>
<Slider disabled={!s.enable}
value={Number(s.ice_ring_width_q_recipA)}
onChange={setIceRingWidth}
min={0.0} max={0.2} step={0.001} valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(3)}
/>
<Switch
onChange={enableHighResGapToggle}
checked={s.high_res_gap_Q_recipA !== undefined}
disabled={!s.enable}
/>
Highres gap Q-space filter [&#8491;<sup>-1</sup>]
<Typography/>
<Slider
disabled={!s.enable || s.high_res_gap_Q_recipA === undefined}
value={Number(s.high_res_gap_Q_recipA ?? highResGap)}
onChange={handleHighResGap}
min={0.1}
max={5.0}
step={0.01}
valueLabelDisplay="auto"
valueLabelFormat={(value) => value.toFixed(2)}
/>
<br/><br/>
<Switch onChange={enableIndexingToggle}
checked={s.indexing}
disabled={!s.enable}/>
Indexing <br/><br/>
<Switch onChange={enableQuickIntegrationToggle}
checked={s.quick_integration}
disabled={!s.enable}/>
Quick MX integration
</Grid>
<Grid item xs={1}/>
</Grid>
</Paper>
}
export default memo(DataProcessingSettings);
+57 -65
View File
@@ -1,23 +1,18 @@
import React, {Component} from 'react';
import {memo, ReactNode, useEffect, useState} from 'react';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import {Grid,Stack, Table, TableBody, TableCell, TableContainer, TableRow} from "@mui/material";
import {Stack, Table, TableBody, TableCell, TableContainer, TableRow} from "@mui/material";
import Paper from "@mui/material/Paper";
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import {detector_list, detector_list_element} from "../client";
import ButtonWithSnackbar from "./ButtonWithSnackbar";
import { ReactNode } from "react";
type MyProps = {
s?: detector_list
}
type MyState = {
choice: string
};
type MapElement = {
id: number
name: string
@@ -42,30 +37,29 @@ const default_detector_element: detector_list_element = {
min_count_time_ns: 0
}
class DetectorSelection extends Component<MyProps, MyState> {
state : MyState = {
choice: "0"
}
function DetectorSelection({s}: MyProps) {
const [choice, setChoice] = useState("0");
componentDidMount() {
if (this.props.s !== undefined)
this.setState({choice: this.props.s.current_id.toString()});
}
useEffect(() => {
if (s !== undefined)
setChoice(s.current_id.toString());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
handleChange = (event : SelectChangeEvent<String>) => {
this.setState({ choice: String(event.target.value).toString() });
const handleChange = (event : SelectChangeEvent<String>) => {
setChoice(String(event.target.value).toString());
};
current_detector_info = () : detector_list_element => {
if (this.props.s === undefined)
const current_detector_info = () : detector_list_element => {
if (s === undefined)
return default_detector_element;
if (this.props.s.detectors.length <= this.props.s.current_id)
if (s.detectors.length <= s.current_id)
return default_detector_element;
return this.props.s.detectors[this.props.s.current_id];
}
return s.detectors[s.current_id];
};
detector_info = () : ReactNode => {
let x : detector_list_element = this.current_detector_info();
const detector_info = () : ReactNode => {
let x : detector_list_element = current_detector_info();
let arr: DetectorTableElement[] = [
{title: "Current Detector", val: x.description},
@@ -95,58 +89,56 @@ class DetectorSelection extends Component<MyProps, MyState> {
</TableBody>
</Table>
</TableContainer>
}
};
detector_list() : MapElement[] {
const detector_options = () : MapElement[] => {
let v: MapElement[] = [];
if (this.props.s !== undefined) {
let id: number = this.props.s.current_id;
v = this.props.s.detectors.map(d => ({
if (s !== undefined) {
let id: number = s.current_id;
v = s.detectors.map(d => ({
id: d.id,
name: `${d.description} (${d.width}x${d.height} pxl) ` + ((d.id === id) ? "*** CURRENT ***" : "")
}));
}
return v;
}
};
render() {
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center"
}}>
<strong>Detector selection </strong>
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center"
}}>
<strong>Detector selection </strong>
{this.detector_info()}
{detector_info()}
<FormControl sx={{width: '80%'}}>
<InputLabel id="detector-select-label">Detector</InputLabel>
<Select
labelId="detector-select-label"
id="detector-select"
value={this.state.choice}
label="Detector"
onChange={this.handleChange}
disabled={this.props.s === undefined}
>
{this.detector_list().map(d => (<MenuItem value={d.id}> {d.name} </MenuItem>))}
</Select>
</FormControl>
<ButtonWithSnackbar
color={"primary"}
path={"/config/select_detector"}
input={JSON.stringify({id: Number(this.state.choice)})}
method={"PUT"}
text={"Select detector"}
disabled={this.props.s === undefined}
/>
</Stack>
<br/>
</Paper>
}
<FormControl sx={{width: '80%'}}>
<InputLabel id="detector-select-label">Detector</InputLabel>
<Select
labelId="detector-select-label"
id="detector-select"
value={choice}
label="Detector"
onChange={handleChange}
disabled={s === undefined}
>
{detector_options().map(d => (<MenuItem value={d.id}> {d.name} </MenuItem>))}
</Select>
</FormControl>
<ButtonWithSnackbar
color={"primary"}
path={"/config/select_detector"}
input={JSON.stringify({id: Number(choice)})}
method={"PUT"}
text={"Select detector"}
disabled={s === undefined}
/>
</Stack>
<br/>
</Paper>
}
export default DetectorSelection;
export default memo(DetectorSelection);
+356 -439
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {ChangeEvent, memo, useEffect, useState} from 'react';
import {Checkbox, FormControlLabel, FormGroup, Grid, List, ListItem, Switch, Tooltip} from "@mui/material";
import Paper from "@mui/material/Paper";
@@ -6,7 +6,7 @@ import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import Select, {SelectChangeEvent} from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import {detector_settings, detector_timing, postDeactivate} from "../client";
import {detector_settings, detector_timing} from "../client";
import NumberTextField from "./NumberTextField";
import _ from "lodash";
import ButtonWithSnackbar from "./ButtonWithSnackbar";
@@ -15,26 +15,6 @@ type MyProps = {
s?: detector_settings
}
type MyState = {
s: detector_settings,
last_downloaded_s: detector_settings,
bit_width_selection: string,
storage_cell_list_value: string,
timing_mode_list_value: detector_timing,
frame_time_err: boolean,
count_time_err: boolean,
storage_cell_delay_err: boolean,
detector_trigger_delay_err: boolean,
internal_frame_generator_images_err: boolean,
pedestal_g0_frames_err: boolean,
pedestal_g1_frames_err: boolean,
pedestal_g2_frames_err: boolean,
pedestal_min_image_count_err: boolean,
eiger_threshold_err: boolean,
eiger_threshold_keV_old: number,
download_counter: number
}
function extractDepthForSelection(input: detector_settings) : string {
if (input.eiger_bit_depth === undefined)
return 'a';
@@ -48,64 +28,75 @@ function extractDepthForSelection(input: detector_settings) : string {
return "";
}
class DetectorSettings extends Component<MyProps, MyState> {
state : MyState = {
s: {
frame_time_us: 1000
},
last_downloaded_s: {
frame_time_us: 1000
},
bit_width_selection: "a",
timing_mode_list_value: detector_timing.TRIGGER,
storage_cell_list_value: "1",
pedestal_g0_frames_err: false,
pedestal_g1_frames_err: false,
pedestal_g2_frames_err: false,
pedestal_min_image_count_err: false,
storage_cell_delay_err: false,
detector_trigger_delay_err: false,
frame_time_err: false,
count_time_err: false,
internal_frame_generator_images_err: false,
eiger_threshold_err: false,
eiger_threshold_keV_old: 6.0,
download_counter: 0
}
function DetectorSettings({s: serverS}: MyProps) {
const [s, setS] = useState<detector_settings>({ frame_time_us: 1000 });
const [lastDownloadedS, setLastDownloadedS] = useState<detector_settings>({ frame_time_us: 1000 });
const [bitWidthSelection, setBitWidthSelection] = useState("a");
const [storageCellListValue, setStorageCellListValue] = useState("1");
const [timingModeListValue, setTimingModeListValue] = useState<detector_timing>(detector_timing.TRIGGER);
const [frameTimeErr, setFrameTimeErr] = useState(false);
const [countTimeErr, setCountTimeErr] = useState(false);
const [storageCellDelayErr, setStorageCellDelayErr] = useState(false);
const [detectorTriggerDelayErr, setDetectorTriggerDelayErr] = useState(false);
const [internalFrameGeneratorImagesErr, setInternalFrameGeneratorImagesErr] = useState(false);
const [pedestalG0FramesErr, setPedestalG0FramesErr] = useState(false);
const [pedestalG1FramesErr, setPedestalG1FramesErr] = useState(false);
const [pedestalG2FramesErr, setPedestalG2FramesErr] = useState(false);
const [pedestalMinImageCountErr, setPedestalMinImageCountErr] = useState(false);
const [eigerThresholdErr, setEigerThresholdErr] = useState(false);
const [eigerThresholdKeVOld, setEigerThresholdKeVOld] = useState(6.0);
const [downloadCounter, setDownloadCounter] = useState(0);
eigerThresholdToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setDownloadCounter(c => c + 1);
setS(serverS);
setBitWidthSelection(extractDepthForSelection(serverS));
setLastDownloadedS(serverS);
setStorageCellListValue(String(serverS.jungfrau_storage_cell_count ?? "1"));
setTimingModeListValue(serverS.timing ?? detector_timing.TRIGGER);
setPedestalG0FramesErr(false);
setPedestalG1FramesErr(false);
setPedestalG2FramesErr(false);
setPedestalMinImageCountErr(false);
setStorageCellDelayErr(false);
setDetectorTriggerDelayErr(false);
setFrameTimeErr(false);
setCountTimeErr(false);
setInternalFrameGeneratorImagesErr(false);
setEigerThresholdErr(false);
setEigerThresholdKeVOld(6.0);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
const eigerThresholdToggle = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.checked)
this.setState(prevState => ({s : {...prevState.s,
eiger_threshold_keV: prevState.eiger_threshold_keV_old}}));
setS(prev => ({...prev, eiger_threshold_keV: eigerThresholdKeVOld}));
else
this.setState(prevState => ({s: {...prevState.s, eiger_threshold_keV: undefined}}));
}
setS(prev => ({...prev, eiger_threshold_keV: undefined}));
};
countTimeToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
const countTimeToggle = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.checked)
this.setState(prevState => ({s : {...prevState.s, count_time_us: prevState.s.frame_time_us - 20}}));
setS(prev => ({...prev, count_time_us: prev.frame_time_us - 20}));
else
this.setState(prevState => ({s : {...prevState.s, count_time_us: undefined}}));
}
setS(prev => ({...prev, count_time_us: undefined}));
};
internalFrameGeneratorToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({s : {...prevState.s, internal_frame_generator: event.target.checked}}));
}
const internalFrameGeneratorToggle = (event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, internal_frame_generator: event.target.checked}));
};
useGainHG0Toggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({s : {...prevState.s, jungfrau_use_gain_hg0: event.target.checked}}));
}
const useGainHG0Toggle = (event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, jungfrau_use_gain_hg0: event.target.checked}));
};
fixedGainG1Toggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({s : {...prevState.s, jungfrau_fixed_gain_g1: event.target.checked}}));
}
const fixedGainG1Toggle = (event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, jungfrau_fixed_gain_g1: event.target.checked}));
};
deactivate = () => {
postDeactivate({ throwOnError: true })
.catch(error => {} );
}
handleBitWidthChange = (event: SelectChangeEvent) => {
const handleBitWidthChange = (event: SelectChangeEvent) => {
let val: detector_settings['eiger_bit_depth'] = undefined;
if (event.target.value === "a")
@@ -117,16 +108,11 @@ class DetectorSettings extends Component<MyProps, MyState> {
else if (event.target.value === "32")
val = 32;
this.setState(prevState => ({
bit_width_selection: event.target.value,
s: {
...prevState.s,
eiger_bit_depth: val
}
}));
setBitWidthSelection(event.target.value);
setS(prev => ({...prev, eiger_bit_depth: val}));
};
handleTimingChange = (event : SelectChangeEvent<detector_timing>) => {
const handleTimingChange = (event : SelectChangeEvent<detector_timing>) => {
let d : detector_timing = detector_timing.TRIGGER;
switch (event.target.value) {
@@ -144,381 +130,312 @@ class DetectorSettings extends Component<MyProps, MyState> {
break;
}
this.setState(prevState => ({
timing_mode_list_value: d,
s : {...prevState.s,
timing: d
}
})
);
setTimingModeListValue(d);
setS(prev => ({...prev, timing: d}));
};
handleChange = (event : SelectChangeEvent<String>) => {
this.setState(prevState => ({
storage_cell_list_value: String(event.target.value).toString(),
s : {...prevState.s,
jungfrau_storage_cell_count: Number(event.target.value)
}
})
);
const handleChange = (event : SelectChangeEvent<String>) => {
setStorageCellListValue(String(event.target.value).toString());
setS(prev => ({...prev, jungfrau_storage_cell_count: Number(event.target.value)}));
};
getValues() {
if (this.props.s !== undefined) {
let det_set: detector_settings = this.props.s;
if (!_.isEqual(det_set, this.state.last_downloaded_s)) {
this.setState(prevState => ({
download_counter: prevState.download_counter + 1,
s: det_set,
bit_width_selection: extractDepthForSelection(det_set),
last_downloaded_s: det_set,
storage_cell_list_value: String(det_set.jungfrau_storage_cell_count ?? "1"),
timing_mode_list_value: det_set.timing ?? detector_timing.TRIGGER,
pedestal_g0_frames_err: false,
pedestal_g1_frames_err: false,
pedestal_g2_frames_err: false,
pedestal_min_image_count_err: false,
storage_cell_delay_err: false,
detector_trigger_delay_err: false,
frame_time_err: false,
count_time_err: false,
internal_frame_generator_images_err: false,
eiger_threshold_err: false,
eiger_threshold_keV_old: 6.0
}));
}
}
}
return <Paper style={{textAlign: 'center'}} sx={{ height: 1150, width: '100%' }}>
componentDidMount() {
this.getValues();
}
<Grid container>
componentDidUpdate() {
this.getValues();
}
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: 1150, width: '100%' }}>
<Grid container>
<Grid item xs={1}/>
<Grid item xs={10}>
<br/><strong>Detector settings </strong>
<br/><br/><br/>
<NumberTextField start_val={this.state.s.frame_time_us}
label={"Internal frame time"}
units={"us"}
min={450}
fullWidth
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
frame_time_err: err,
s: {...prevState.s, frame_time_us: val}
}));
}}/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={2}/>
<Grid item xs={8}>
<List>
<Grid item xs={1}/>
<Grid item xs={10}>
<br/><strong>Detector settings </strong>
<br/><br/><br/>
<NumberTextField start_val={s.frame_time_us}
label={"Internal frame time"}
units={"us"}
min={450}
fullWidth
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setFrameTimeErr(err);
setS(prev => ({...prev, frame_time_us: val}));
}}/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={2}/>
<Grid item xs={8}>
<List>
<ListItem>
<Switch onChange={countTimeToggle}
checked={s.count_time_us !== undefined}/>
<NumberTextField
start_val={s.count_time_us}
disabled={s.count_time_us === undefined}
label={"Count time"}
units={"us"}
min={0}
max={1980}
fullWidth
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setCountTimeErr(err);
setS(prev => ({...prev, count_time_us: val}));
}}/>
</ListItem>
</List>
</Grid>
<Grid item xs={2}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<NumberTextField start_val={s.detector_trigger_delay_ns}
label={"Detector delay"}
units={"ns"}
default={0}
min={0}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setDetectorTriggerDelayErr(err);
setS(prev => ({...prev, detector_trigger_delay_ns: val}));
}}
sx={{width: 120}}
/>&nbsp;&nbsp;
<FormControl>
<InputLabel id="timing-select-label">Timing mode</InputLabel>
<Select
sx={{width: 120}}
labelId="timing-select-label"
id="timing-select"
value={timingModeListValue}
label="Storage cell count"
onChange={handleTimingChange}
>
<MenuItem value={"trigger"}>Trigger</MenuItem>
<MenuItem value={"auto"}>Auto</MenuItem>
<MenuItem value={"burst"}>Burst (EIGER)</MenuItem>
<MenuItem value={"gated"}>Gated (EIGER)</MenuItem>
</Select>
</FormControl>
<br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>Internal image generator (FPGA) </b><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={3}/>
<Grid item xs={6}>
<br/>
<List>
<Tooltip title="Internal generator allows to test Jungfraujoch without having detector connected.">
<ListItem>
<Switch onChange={this.countTimeToggle}
checked={this.state.s.count_time_us !== undefined}/>
<NumberTextField
start_val={this.state.s.count_time_us}
disabled={this.state.s.count_time_us === undefined}
label={"Count time"}
units={"us"}
min={0}
max={1980}
fullWidth
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
count_time_err: err,
s: {...prevState.s, count_time_us: val}
}));
}}/>
<Checkbox onChange={internalFrameGeneratorToggle}
checked={s.internal_frame_generator === true}/>
<FormControl fullWidth>
<NumberTextField start_val={s.internal_frame_generator_images}
label={"Images"}
min={1}
default={1}
max={128}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setInternalFrameGeneratorImagesErr(err);
setS(prev => ({...prev, internal_frame_generator_images: val}));
}}
disabled={!s.internal_frame_generator}
fullWidth/>
</FormControl>
</ListItem>
</List>
</Grid>
<Grid item xs={2}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<NumberTextField start_val={this.state.s.detector_trigger_delay_ns}
label={"Detector delay"}
units={"ns"}
default={0}
min={0}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
detector_trigger_delay_err: err,
s: {...prevState.s, detector_trigger_delay_ns: val}
}));
}}
sx={{width: 120}}
/>&nbsp;&nbsp;
</Tooltip>
</List>
</Grid>
<Grid item xs={3}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>JUNGFRAU settings </b><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={3}/>
<Grid item xs={6}>
<FormGroup>
<FormControlLabel control={
<Checkbox onChange={useGainHG0Toggle} checked={s.jungfrau_use_gain_hg0 === true}/>
} label="HG0"/>
<FormControlLabel control={
<Checkbox onChange={fixedGainG1Toggle} checked={s.jungfrau_fixed_gain_g1 === true}/>
} label="Fixed G1"/>
</FormGroup><br/>
</Grid>
<Grid item xs={3}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>JUNGFRAU storage cells </b><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<FormControl>
<InputLabel id="demo-simple-select-label">Count</InputLabel>
<Select
sx={{width: 120}}
labelId="demo-simple-select-label"
id="demo-simple-select"
value={storageCellListValue}
label="Storage cell count"
onChange={handleChange}
>
<MenuItem value={"1"}>1</MenuItem>
<MenuItem value={"2"}>2</MenuItem>
<MenuItem value={"4"}>4</MenuItem>
<MenuItem value={"8"}>8</MenuItem>
<MenuItem value={"15"}>15</MenuItem>
<MenuItem value={"16"}>16</MenuItem>
</Select>
</FormControl>&nbsp;&nbsp;
<NumberTextField start_val={s.jungfrau_storage_cell_delay_ns}
label={"Delay"}
units={"ns"}
min={2100}
default={2100}
counter={downloadCounter}
sx={{ width: "140px" }}
callback={(val: number, err: boolean) => {
setStorageCellDelayErr(err);
setS(prev => ({...prev, jungfrau_storage_cell_delay_ns: val}));
}}
disabled={storageCellListValue === "1"}
fullWidth/>
<br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>JUNGFRAU Pedestal</b><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<NumberTextField start_val={s.jungfrau_pedestal_g0_frames}
label={"G0"}
min={0}
default={2000}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setPedestalG0FramesErr(err);
setS(prev => ({...prev, jungfrau_pedestal_g0_frames: val}));
}}
sx={{width: "80px"}}/>&nbsp;
<NumberTextField start_val={s.jungfrau_pedestal_g1_frames}
label={"G1"}
min={0}
default={300}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setPedestalG1FramesErr(err);
setS(prev => ({...prev, jungfrau_pedestal_g1_frames: val}));
}}
sx={{width: "80px"}}/>&nbsp;
<NumberTextField start_val={s.jungfrau_pedestal_g2_frames}
label={"G2"}
min={0}
default={300}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setPedestalG2FramesErr(err);
setS(prev => ({...prev, jungfrau_pedestal_g2_frames: val}));
}}
sx={{width: "80px"}}/>
&nbsp;<br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<NumberTextField start_val={s.jungfrau_pedestal_min_image_count}
label={"Min. image count"}
min={0}
default={128}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setPedestalMinImageCountErr(err);
setS(prev => ({...prev, jungfrau_pedestal_min_image_count: val}));
}}
sx={{width: "120px"}}/><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>EIGER settings </b><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<ListItem>
<Checkbox onChange={eigerThresholdToggle}
checked={s.eiger_threshold_keV !== undefined}/>
<FormControl fullWidth>
<NumberTextField
default={eigerThresholdKeVOld}
start_val={s.eiger_threshold_keV}
label={"Energy threshold"}
min={1.0}
max={100.0}
units={"keV"}
float={true}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
if (!err)
setEigerThresholdKeVOld(val);
setEigerThresholdErr(err);
setS(prev => ({...prev, eiger_threshold_keV: val}));
}}
disabled={s.eiger_threshold_keV === undefined}
/>
</FormControl>&nbsp;
<FormControl>
<InputLabel id="timing-select-label">Timing mode</InputLabel>
<InputLabel id="demo-simple-select-label">Read-out</InputLabel>
<Select
sx={{width: 120}}
labelId="timing-select-label"
id="timing-select"
value={this.state.timing_mode_list_value}
label="Storage cell count"
onChange={this.handleTimingChange}
>
<MenuItem value={"trigger"}>Trigger</MenuItem>
<MenuItem value={"auto"}>Auto</MenuItem>
<MenuItem value={"burst"}>Burst (EIGER)</MenuItem>
<MenuItem value={"gated"}>Gated (EIGER)</MenuItem>
</Select>
</FormControl>
<br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>Internal image generator (FPGA) </b><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={3}/>
<Grid item xs={6}>
<br/>
<List>
<Tooltip title="Internal generator allows to test Jungfraujoch without having detector connected.">
<ListItem>
<Checkbox onChange={this.internalFrameGeneratorToggle}
checked={this.state.s.internal_frame_generator === true}/>
<FormControl fullWidth>
<NumberTextField start_val={this.state.s.internal_frame_generator_images}
label={"Images"}
min={1}
default={1}
max={128}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
internal_frame_generator_images_err: err,
s: {...prevState.s, internal_frame_generator_images: val}
}));
}}
disabled={!this.state.s.internal_frame_generator}
fullWidth/>
</FormControl>
</ListItem>
</Tooltip>
</List>
</Grid>
<Grid item xs={3}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>JUNGFRAU settings </b><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={3}/>
<Grid item xs={6}>
<FormGroup>
<FormControlLabel control={
<Checkbox onChange={this.useGainHG0Toggle} checked={this.state.s.jungfrau_use_gain_hg0 === true}/>
} label="HG0"/>
<FormControlLabel control={
<Checkbox onChange={this.fixedGainG1Toggle} checked={this.state.s.jungfrau_fixed_gain_g1 === true}/>
} label="Fixed G1"/>
</FormGroup><br/>
</Grid>
<Grid item xs={3}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>JUNGFRAU storage cells </b><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<FormControl>
<InputLabel id="demo-simple-select-label">Count</InputLabel>
<Select
sx={{width: 120}}
labelId="demo-simple-select-label"
id="demo-simple-select"
value={this.state.storage_cell_list_value}
label="Storage cell count"
onChange={this.handleChange}
label="Detector"
value={bitWidthSelection}
onChange={handleBitWidthChange}
sx={{minWidth:106}}
>
<MenuItem value={"1"}>1</MenuItem>
<MenuItem value={"2"}>2</MenuItem>
<MenuItem value={"4"}>4</MenuItem>
<MenuItem value={"8"}>8</MenuItem>
<MenuItem value={"15"}>15</MenuItem>
<MenuItem value={"16"}>16</MenuItem>
<MenuItem value={"a"}>Auto</MenuItem>
<MenuItem value={"8"}>8-bit</MenuItem>
<MenuItem value={"16"}>16-bit</MenuItem>
<MenuItem value={"32"}>32-bit</MenuItem>
</Select>
</FormControl>&nbsp;&nbsp;
<NumberTextField start_val={this.state.s.jungfrau_storage_cell_delay_ns}
label={"Delay"}
units={"ns"}
min={2100}
default={2100}
counter={this.state.download_counter}
sx={{ width: "140px" }}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
storage_cell_delay_err: err,
s: {...prevState.s, jungfrau_storage_cell_delay_ns: val}
}));
}}
disabled={this.state.storage_cell_list_value === "1"}
fullWidth/>
<br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>JUNGFRAU Pedestal</b><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<NumberTextField start_val={this.state.s.jungfrau_pedestal_g0_frames}
label={"G0"}
min={0}
default={2000}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
pedestal_g0_frames_err: err,
s: {...prevState.s, jungfrau_pedestal_g0_frames: val}
}));
}}
sx={{width: "80px"}}/>&nbsp;
<NumberTextField start_val={this.state.s.jungfrau_pedestal_g1_frames}
label={"G1"}
min={0}
default={300}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
pedestal_g1_frames_err: err,
s: {...prevState.s, jungfrau_pedestal_g1_frames: val}
}));
}}
sx={{width: "80px"}}/>&nbsp;
<NumberTextField start_val={this.state.s.jungfrau_pedestal_g2_frames}
label={"G2"}
min={0}
default={300}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
pedestal_g2_frames_err: err,
s: {...prevState.s, jungfrau_pedestal_g2_frames: val}
}));
}}
sx={{width: "80px"}}/>
&nbsp;<br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<NumberTextField start_val={this.state.s.jungfrau_pedestal_min_image_count}
label={"Min. image count"}
min={0}
default={128}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
pedestal_min_image_count_err: err,
s: {...prevState.s, jungfrau_pedestal_min_image_count: val}
}));
}}
sx={{width: "120px"}}/><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<b>EIGER settings </b><br/><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<ListItem>
<Checkbox onChange={this.eigerThresholdToggle}
checked={this.state.s.eiger_threshold_keV !== undefined}/>
<FormControl fullWidth>
<NumberTextField
default={this.state.eiger_threshold_keV_old}
start_val={this.state.s.eiger_threshold_keV}
label={"Energy threshold"}
min={1.0}
max={100.0}
units={"keV"}
float={true}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
let old: number = this.state.eiger_threshold_keV_old;
if (!err)
old = val;
this.setState(prevState => ({
eiger_threshold_err: err,
eiger_threshold_keV_old: old,
s: {...prevState.s, eiger_threshold_keV: val}
}));
}}
disabled={this.state.s.eiger_threshold_keV === undefined}
/>
</FormControl>&nbsp;
<FormControl>
<InputLabel id="demo-simple-select-label">Read-out</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Detector"
value={this.state.bit_width_selection}
onChange={this.handleBitWidthChange}
sx={{minWidth:106}}
>
<MenuItem value={"a"}>Auto</MenuItem>
<MenuItem value={"8"}>8-bit</MenuItem>
<MenuItem value={"16"}>16-bit</MenuItem>
<MenuItem value={"32"}>32-bit</MenuItem>
</Select>
</FormControl><br/><br/>
</ListItem><br/>
</Grid>
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<ButtonWithSnackbar
color={"primary"}
path={"/config/detector"}
disabled={this.state.count_time_err
|| this.state.frame_time_err
|| this.state.internal_frame_generator_images_err
|| this.state.detector_trigger_delay_err
|| this.state.storage_cell_delay_err
|| this.state.pedestal_g0_frames_err
|| this.state.pedestal_g1_frames_err
|| this.state.pedestal_g2_frames_err
|| this.state.pedestal_min_image_count_err}
text={"Upload"}
input={JSON.stringify(this.state.s)}
method={"PUT"}
/>&nbsp;&nbsp;
<ButtonWithSnackbar
color={"primary"}
path={"/deactivate"}
text={"Deactivate"}
/>
<br/><br/>
</Grid>
<Grid item xs={1}/>
</FormControl><br/><br/>
</ListItem><br/>
</Grid>
</Paper>
}
<Grid item xs={1}/>
<Grid item xs={1}/>
<Grid item xs={10}>
<ButtonWithSnackbar
color={"primary"}
path={"/config/detector"}
disabled={countTimeErr
|| frameTimeErr
|| internalFrameGeneratorImagesErr
|| detectorTriggerDelayErr
|| storageCellDelayErr
|| pedestalG0FramesErr
|| pedestalG1FramesErr
|| pedestalG2FramesErr
|| pedestalMinImageCountErr}
text={"Upload"}
input={JSON.stringify(s)}
method={"PUT"}
/>&nbsp;&nbsp;
<ButtonWithSnackbar
color={"primary"}
path={"/deactivate"}
text={"Deactivate"}
/>
<br/><br/>
</Grid>
<Grid item xs={1}/>
</Grid>
</Paper>
}
export default DetectorSettings;
export default memo(DetectorSettings);
+55 -64
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {memo} from 'react';
import Paper from '@mui/material/Paper';
import {Stack, Table, TableBody, TableCell, TableContainer, TableRow,} from "@mui/material";
@@ -8,8 +8,6 @@ type MyProps = {
s?: detector_status
}
type MyState = {}
function powerchipToString(s : detector_status) : string {
switch (s.powerchip) {
case detector_power_state.POWER_ON:
@@ -24,7 +22,7 @@ function powerchipToString(s : detector_status) : string {
function reduce_array(arr: number[], unit: string) : string {
if (arr.length == 0)
return "N/A";
if (arr.every(val => val === arr[0]))
return arr[0].toString() + " " + unit;
else {
@@ -34,69 +32,62 @@ function reduce_array(arr: number[], unit: string) : string {
}
}
class DetectorStatus extends Component<MyProps, MyState> {
det() : detector_status {
if (this.props.s === undefined)
return {
state: detector_state.NOT_CONNECTED,
powerchip: detector_power_state.POWER_OFF,
server_version: "N/A",
number_of_triggers_left: 0,
fpga_temp_degC: [],
high_voltage_V: []
}
else
return this.props.s;
}
function DetectorStatus({s}: MyProps) {
const det : detector_status = s ?? {
state: detector_state.NOT_CONNECTED,
powerchip: detector_power_state.POWER_OFF,
server_version: "N/A",
number_of_triggers_left: 0,
fpga_temp_degC: [],
high_voltage_V: []
};
render() {
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center"
}}>
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center"
}}>
<strong>Detector status</strong>
<strong>Detector status</strong>
<TableContainer component={Paper}
style={{marginLeft: "auto", marginRight: "auto"}}
sx={{width: '80%'}}
>
<Table size="small" aria-label="simple table">
<TableBody>
<TableRow>
<TableCell component="th" scope="row"> Detector state: </TableCell>
<TableCell align="right">{(this.det().state.toString())}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Detector ASIC power: </TableCell>
<TableCell align="right">{powerchipToString(this.det())}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Triggers remaining: </TableCell>
<TableCell align="right">{this.det().number_of_triggers_left}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> FPGA temperature: </TableCell>
<TableCell align="right">{reduce_array(this.det().fpga_temp_degC, "degC")}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> High voltage: </TableCell>
<TableCell align="right">{reduce_array(this.det().high_voltage_V, "V")}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Detector server version: </TableCell>
<TableCell align="right">{this.det().server_version} </TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<TableContainer component={Paper}
style={{marginLeft: "auto", marginRight: "auto"}}
sx={{width: '80%'}}
>
<Table size="small" aria-label="simple table">
<TableBody>
<TableRow>
<TableCell component="th" scope="row"> Detector state: </TableCell>
<TableCell align="right">{(det.state.toString())}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Detector ASIC power: </TableCell>
<TableCell align="right">{powerchipToString(det)}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Triggers remaining: </TableCell>
<TableCell align="right">{det.number_of_triggers_left}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> FPGA temperature: </TableCell>
<TableCell align="right">{reduce_array(det.fpga_temp_degC, "degC")}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> High voltage: </TableCell>
<TableCell align="right">{reduce_array(det.high_voltage_V, "V")}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Detector server version: </TableCell>
<TableCell align="right">{det.server_version} </TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Stack>
<br/>
</Paper>
}
</Stack>
<br/>
</Paper>
}
export default DetectorStatus;
export default memo(DetectorStatus);
+27 -32
View File
@@ -1,45 +1,40 @@
import React, {Component} from 'react';
import {memo, ReactNode} from 'react';
import {broker_status} from "../client";
import {Alert, AlertColor} from "@mui/material";
import { ReactNode } from "react";
type MyProps = {
s?: broker_status
}
type MyState = {}
function severity(input?: broker_status['message_severity']) : AlertColor {
if (input === undefined)
return "error";
class ErrorMessage extends Component<MyProps, MyState> {
severity(input?: broker_status['message_severity']) : AlertColor {
if (input === undefined)
switch (input) {
case 'info':
return "info";
case 'warning':
return "warning";
case 'error':
return "error";
switch (input) {
case 'info':
return "info";
case 'warning':
return "warning";
case 'error':
return "error";
case 'success':
return "success";
}
}
alert(input: AlertColor, message: string) : ReactNode {
return <Alert severity={input}>{message}</Alert>
}
render() {
if (this.props.s === undefined)
return this.alert("error", "Not connected to Jungfraujoch instance; check if jfjoch_broker is running");
if (this.props.s.message === undefined)
return this.alert("info", "");
return this.alert(this.severity(this.props.s.message_severity),this.props.s.message);
case 'success':
return "success";
}
}
export default ErrorMessage;
function alert(input: AlertColor, message: string) : ReactNode {
return <Alert severity={input}>{message}</Alert>
}
function ErrorMessage({s}: MyProps) {
if (s === undefined)
return alert("error", "Not connected to Jungfraujoch instance; check if jfjoch_broker is running");
if (s.message === undefined)
return alert("info", "");
return alert(severity(s.message_severity), s.message);
}
export default memo(ErrorMessage);
+60 -97
View File
@@ -1,4 +1,4 @@
import React from 'react';
import {memo, useEffect, useState} from 'react';
import Paper from '@mui/material/Paper';
import {Checkbox, FormControlLabel, FormGroup, Select, Stack} from "@mui/material";
@@ -13,12 +13,6 @@ type MyProps = {
s?: file_writer_settings
};
type MyState = {
s: file_writer_settings,
last_downloaded_s: file_writer_settings,
download_counter: number
};
const default_file_writer_settings : file_writer_settings = {
overwrite: false,
format: file_writer_format.N_XMX_LEGACY
@@ -34,99 +28,68 @@ function stringToEnum(value: string): file_writer_format {
return enumValue || file_writer_format.N_XMX_ONLY_DATA;
}
function FileWriterSettings({s: serverS}: MyProps) {
const [s, setS] = useState<file_writer_settings>(default_file_writer_settings);
const [lastDownloadedS, setLastDownloadedS] = useState<file_writer_settings>(default_file_writer_settings);
class FileWriterSettings extends React.Component<MyProps, MyState> {
state : MyState = {
s: default_file_writer_settings,
last_downloaded_s: default_file_writer_settings,
download_counter: 0
}
getValues = () => {
if (this.props.s !== undefined) {
let format_set : file_writer_settings = this.props.s;
if (!_.isEqual(format_set, this.state.last_downloaded_s)) {
this.setState(prevState => ({
s: format_set,
last_downloaded_s: format_set,
download_counter: prevState.download_counter + 1,
}));
}
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
componentDidUpdate() {
this.getValues();
}
render() {
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<div><strong>File Writer settings </strong></div>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={this.state.s.overwrite}
onChange={(event) => {
this.setState(prevState => ({
s: {
...prevState.s,
overwrite: event.target.checked
}
}));
}}
/>
}
label="Overwrite existing files"
/>
</FormGroup>
<FormControl sx = {{width: "80%"}}>
<InputLabel id="file-format-label">File format</InputLabel>
<Select
labelId="file-format-label"
label="File format"
value={this.state.s.format}
variant="outlined"
onChange={(event) => {
this.setState(prevState => ({
s: {
...prevState.s,
format: stringToEnum(event.target.value)
}
}));
}}
>
<MenuItem value={file_writer_format.N_XMX_LEGACY}>NXmx HDF5 master file with soft links (DECTRIS file writer compatibility)</MenuItem>
<MenuItem value={file_writer_format.N_XMX_VDS}>NXmx HDF5 master file with virtual datasets</MenuItem>
<MenuItem value={file_writer_format.N_XMX_INTEGRATED}>Single HDF5 file with data and metadata</MenuItem>
<MenuItem value={file_writer_format.N_XMX_ONLY_DATA}>No NXmx HDF5 master file (only data files)</MenuItem>
<MenuItem value={file_writer_format.CBF}>miniCBF (only data files; limited metadata)</MenuItem>
<MenuItem value={file_writer_format.TIFF}>TIFF (only data files; no metadata)</MenuItem>
<MenuItem value={file_writer_format.NO_FILE_WRITTEN}>No files saved</MenuItem>
</Select>
</FormControl>
<ButtonWithSnackbar
color={"primary"}
path={"/config/file_writer"}
input={JSON.stringify(this.state.s)}
method={"PUT"}
text={"Upload"}
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<div><strong>File Writer settings </strong></div>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={s.overwrite}
onChange={(event) => {
setS(prev => ({...prev, overwrite: event.target.checked}));
}}
/>
}
label="Overwrite existing files"
/>
</Stack>
<br/>
</Paper>
}
</FormGroup>
<FormControl sx = {{width: "80%"}}>
<InputLabel id="file-format-label">File format</InputLabel>
<Select
labelId="file-format-label"
label="File format"
value={s.format}
variant="outlined"
onChange={(event) => {
setS(prev => ({...prev, format: stringToEnum(event.target.value)}));
}}
>
<MenuItem value={file_writer_format.N_XMX_LEGACY}>NXmx HDF5 master file with soft links (DECTRIS file writer compatibility)</MenuItem>
<MenuItem value={file_writer_format.N_XMX_VDS}>NXmx HDF5 master file with virtual datasets</MenuItem>
<MenuItem value={file_writer_format.N_XMX_INTEGRATED}>Single HDF5 file with data and metadata</MenuItem>
<MenuItem value={file_writer_format.N_XMX_ONLY_DATA}>No NXmx HDF5 master file (only data files)</MenuItem>
<MenuItem value={file_writer_format.CBF}>miniCBF (only data files; limited metadata)</MenuItem>
<MenuItem value={file_writer_format.TIFF}>TIFF (only data files; no metadata)</MenuItem>
<MenuItem value={file_writer_format.NO_FILE_WRITTEN}>No files saved</MenuItem>
</Select>
</FormControl>
<ButtonWithSnackbar
color={"primary"}
path={"/config/file_writer"}
input={JSON.stringify(s)}
method={"PUT"}
text={"Upload"}
/>
</Stack>
<br/>
</Paper>
}
export default FileWriterSettings;
export default memo(FileWriterSettings);
+35 -43
View File
@@ -1,10 +1,8 @@
import React from 'react';
import {memo, ReactNode} from 'react';
import Paper from '@mui/material/Paper';
import {DataGrid, GridCellParams, GridColDef} from "@mui/x-data-grid";
import { fpga_status} from "../client";
import { ReactNode } from "react";
function Circles(val: boolean) : ReactNode {
if (val)
@@ -63,46 +61,40 @@ type MyProps = {
s?: fpga_status;
};
type MyState = {};
const columns : GridColDef[] = [
{ field: 'pci_dev_id', type: 'string', headerName: 'PCIe ID' },
{ field: 'base_mac_addr', type: 'string', headerName: 'MAC address', width: 200 },
{ field: 'eth_link_status', type: 'string', headerName: 'Link status', align: `center`,
renderCell: (params : GridCellParams) => EthLinkStatus(params.row.eth_link_count,
params.row.eth_link_status)
},
{ field: 'idle', type: 'string', headerName: 'Idle', align: `center`,
renderCell: (params : GridCellParams) => CardIdle(params.row.idle)
},
{ field: 'power_usage_W', type: 'number', headerName: 'Power usage [W]', width: 150},
{ field: 'fpga_temp_C', type: 'number', headerName: 'FPGA temperature [C]', width: 200 },
{ field: 'hbm_temp_C', type: 'number', headerName: 'HBM temperature [C]', width: 200 },
{ field: 'packets_sls', type: 'number', headerName: 'Data (current data collection)', width: 150,
valueGetter: (params, row) => DataVolume(row.packets_sls)},
{ field: "fw_version", type: 'string', headerName: 'Firmware version', width: 150 },
{ field: "pcie_link", type: 'string', headerName: 'PCIe link', width: 100, align: `center`,
renderCell: (params : GridCellParams) => LinkSpeed(params.row.pcie_link_speed, params.row.pcie_link_width)}
];
class FpgaStatus extends React.Component<MyProps, MyState> {
columns : GridColDef[] = [
{ field: 'pci_dev_id', type: 'string', headerName: 'PCIe ID' },
{ field: 'base_mac_addr', type: 'string', headerName: 'MAC address', width: 200 },
{ field: 'eth_link_status', type: 'string', headerName: 'Link status', align: `center`,
renderCell: (params : GridCellParams) => EthLinkStatus(params.row.eth_link_count,
params.row.eth_link_status)
},
{ field: 'idle', type: 'string', headerName: 'Idle', align: `center`,
renderCell: (params : GridCellParams) => CardIdle(params.row.idle)
},
{ field: 'power_usage_W', type: 'number', headerName: 'Power usage [W]', width: 150},
{ field: 'fpga_temp_C', type: 'number', headerName: 'FPGA temperature [C]', width: 200 },
{ field: 'hbm_temp_C', type: 'number', headerName: 'HBM temperature [C]', width: 200 },
{ field: 'packets_sls', type: 'number', headerName: 'Data (current data collection)', width: 150,
valueGetter: (params, row) => DataVolume(row.packets_sls)},
{ field: "fw_version", type: 'string', headerName: 'Firmware version', width: 150 },
{ field: "pcie_link", type: 'string', headerName: 'PCIe link', width: 100, align: `center`,
renderCell: (params : GridCellParams) => LinkSpeed(params.row.pcie_link_speed, params.row.pcie_link_width)}
];
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: 500, width: '100%' }}>
{((this.props.s !== undefined) && (this.props.s.length > 0)) ?
<DataGrid
rows={this.props.s}
columns={this.columns}
getRowId={(row) => row.pci_dev_id}
autosizeOnMount={true}
initialState={{
pagination: { paginationModel: { pageSize: 8 } },
}}
/> : <div> No FPGA available</div>
}
</Paper>
}
function FpgaStatus({s}: MyProps) {
return <Paper style={{textAlign: 'center'}} sx={{ height: 500, width: '100%' }}>
{((s !== undefined) && (s.length > 0)) ?
<DataGrid
rows={s}
columns={columns}
getRowId={(row) => row.pci_dev_id}
autosizeOnMount={true}
initialState={{
pagination: { paginationModel: { pageSize: 8 } },
}}
/> : <div> No FPGA available</div>
}
</Paper>
}
export default FpgaStatus;
export default memo(FpgaStatus);
+186 -316
View File
@@ -1,13 +1,12 @@
import React, {Component} from 'react';
import {ChangeEvent, memo, useEffect, useState} from 'react';
import {Checkbox, FormControlLabel, FormGroup, Grid, List, ListItem, Stack, Switch, Tooltip} from "@mui/material";
import Button from "@mui/material/Button";
import {Checkbox, FormControlLabel, FormGroup, List, ListItem, Stack, Tooltip} from "@mui/material";
import Paper from "@mui/material/Paper";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import Select, {SelectChangeEvent} from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import {image_format_settings, postConfigImageFormatConversion, postConfigImageFormatRaw, putConfigImageFormat} from "../client";
import {image_format_settings} from "../client";
import NumberTextField from "./NumberTextField";
import _ from "lodash";
import ButtonWithSnackbar from "./ButtonWithSnackbar";
@@ -16,17 +15,6 @@ type MyProps = {
s?: image_format_settings
}
type MyState = {
s: image_format_settings,
last_downloaded_s: image_format_settings,
bit_width_selection: string,
sign_selection: string,
jungfrau_conversion_factor_err: boolean,
jungfrau_conversion_factor_old: number,
download_counter: number,
pedestal_g0_rms_limit_err: boolean
}
const default_image_format_settings: image_format_settings = {
summation: true,
geometry_transform: true,
@@ -63,65 +51,29 @@ function extractDepthForSelection(input: image_format_settings) : string {
return "";
}
class ImageFormatSettings extends Component<MyProps, MyState> {
state : MyState = {
s: default_image_format_settings,
last_downloaded_s: default_image_format_settings,
jungfrau_conversion_factor_err: false,
jungfrau_conversion_factor_old: 12.4,
bit_width_selection: "a",
sign_selection: "a",
download_counter: 0,
pedestal_g0_rms_limit_err: false
}
function ImageFormatSettings({s: serverS}: MyProps) {
const [s, setS] = useState<image_format_settings>(default_image_format_settings);
const [lastDownloadedS, setLastDownloadedS] = useState<image_format_settings>(default_image_format_settings);
const [bitWidthSelection, setBitWidthSelection] = useState("a");
const [signSelection, setSignSelection] = useState("a");
const [jungfrauConversionFactorErr, setJungfrauConversionFactorErr] = useState(false);
const [jungfrauConversionFactorOld, setJungfrauConversionFactorOld] = useState(12.4);
const [downloadCounter, setDownloadCounter] = useState(0);
raw = () => {
postConfigImageFormatRaw({ throwOnError: true })
.catch(error => console.log(error) );
this.getValues();
}
conv = () => {
postConfigImageFormatConversion({ throwOnError: true })
.catch(error => console.log(error) );
this.getValues();
}
uploadButton = () => { this.putValues(); }
putValues = () => {
putConfigImageFormat({ body: this.state.s, throwOnError: true })
.catch(error => console.log(error) );
}
getValues = () => {
if (this.props.s !== undefined) {
let format_set: image_format_settings = this.props.s;
if (!_.isEqual(format_set, this.state.last_downloaded_s)) {
this.setState(prevState => ({
s: format_set,
last_downloaded_s: format_set,
jungfrau_conversion_factor_err: false,
download_counter: prevState.download_counter + 1,
jungfrau_conversion_factor_old: format_set.jungfrau_conversion_factor_keV
?? prevState.jungfrau_conversion_factor_old,
bit_width_selection: extractDepthForSelection(format_set),
sign_selection: extractSignForSelection(format_set)
}));
}
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
setJungfrauConversionFactorErr(false);
setDownloadCounter(c => c + 1);
setJungfrauConversionFactorOld(serverS.jungfrau_conversion_factor_keV ?? jungfrauConversionFactorOld);
setBitWidthSelection(extractDepthForSelection(serverS));
setSignSelection(extractSignForSelection(serverS));
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
componentDidUpdate() {
this.getValues();
}
handleBitWidthChange = (event: SelectChangeEvent) => {
const handleBitWidthChange = (event: SelectChangeEvent) => {
let val: image_format_settings['bit_depth_image'] = undefined;
if (event.target.value === "a")
@@ -133,16 +85,11 @@ class ImageFormatSettings extends Component<MyProps, MyState> {
else if (event.target.value === "32")
val = 32;
this.setState(prevState => ({
bit_width_selection: event.target.value,
s: {
...prevState.s,
bit_depth_image: val
}
}));
setBitWidthSelection(event.target.value);
setS(prev => ({...prev, bit_depth_image: val}));
};
handleSignChange = (event: SelectChangeEvent) => {
const handleSignChange = (event: SelectChangeEvent) => {
let val: boolean|undefined = undefined;
if (event.target.value === "a")
@@ -152,250 +99,173 @@ class ImageFormatSettings extends Component<MyProps, MyState> {
else if (event.target.value === "s")
val = true;
this.setState(prevState => ({
sign_selection: event.target.value,
s: {
...prevState.s,
signed_output: val
}
}));
setSignSelection(event.target.value);
setS(prev => ({...prev, signed_output: val}));
};
render() {
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<strong>Output image format settings </strong>
<FormGroup>
<FormControlLabel control={
<Checkbox checked={this.state.s.geometry_transform}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {
...prevState.s,
geometry_transform: event.target.checked
}
})
);
}}/>
} label="Geometry transformation"/>
<FormControlLabel control={
<Checkbox checked={this.state.s.summation}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({s: {...prevState.s, summation: event.target.checked}})
);
}}/>
} label="Image summation"/>
<FormControlLabel control={
<Checkbox checked={this.state.s.mask_chip_edges}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {
...prevState.s,
mask_chip_edges: event.target.checked
}
})
);
}}/>
} label="Mask chip edges"/>
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<strong>Output image format settings </strong>
<FormGroup>
<FormControlLabel control={
<Checkbox checked={s.geometry_transform}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, geometry_transform: event.target.checked}));
}}/>
} label="Geometry transformation"/>
<FormControlLabel control={
<Checkbox checked={s.summation}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, summation: event.target.checked}));
}}/>
} label="Image summation"/>
<FormControlLabel control={
<Checkbox checked={s.mask_chip_edges}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, mask_chip_edges: event.target.checked}));
}}/>
} label="Mask chip edges"/>
<FormControlLabel control={
<Checkbox checked={this.state.s.mask_module_edges}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {
...prevState.s,
mask_module_edges: event.target.checked
}
})
);
}}/>
} label="Mask module edges"/>
<FormControlLabel control={
<Checkbox checked={this.state.s.apply_mask}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {
...prevState.s,
apply_mask: event.target.checked
}
})
);
}}/>
} label="Apply pixel masks on images"/>
</FormGroup>
<FormControlLabel control={
<Checkbox checked={s.mask_module_edges}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, mask_module_edges: event.target.checked}));
}}/>
} label="Mask module edges"/>
<FormControlLabel control={
<Checkbox checked={s.apply_mask}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, apply_mask: event.target.checked}));
}}/>
} label="Apply pixel masks on images"/>
</FormGroup>
<b>Output pixel value</b>
<Stack spacing={2} direction="row">
<FormControl>
<InputLabel id="demo-simple-select-label">Bit-width</InputLabel>
<Select
sx={{width: 150}}
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Detector"
value={this.state.bit_width_selection}
onChange={this.handleBitWidthChange}
>
<MenuItem value={"a"}>Auto</MenuItem>
<MenuItem value={"8"}>8-bit</MenuItem>
<MenuItem value={"16"}>16-bit</MenuItem>
<MenuItem value={"32"}>32-bit</MenuItem>
</Select>
</FormControl>
<FormControl>
<InputLabel id="demo-simple-select-label">Sign</InputLabel>
<Select
sx={{width: 150}}
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Sign"
value={this.state.sign_selection}
onChange={this.handleSignChange}
>
<MenuItem value={"a"}>Auto</MenuItem>
<MenuItem value={"s"}>Signed</MenuItem>
<MenuItem value={"u"}>Unsigned</MenuItem>
</Select>
</FormControl>
</Stack>
<b>JUNGFRAU specific</b>
<FormGroup>
<FormControlLabel control={
<Checkbox checked={this.state.s.jungfrau_mask_pixels_without_g0}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {
...prevState.s,
jungfrau_mask_pixels_without_g0: event.target.checked
}
}));
}}/>
} label="Mask pixels not switching to G0"/>
<FormControlLabel control={
<Checkbox checked={this.state.s.jungfrau_conversion}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
this.setState(
prevState => ({
jungfrau_conversion_factor_err: false,
s: {
...prevState.s,
jungfrau_conversion: true,
jungfrau_conversion_factor_keV: undefined
}
}));
} else {
this.setState(
prevState => ({
jungfrau_conversion_factor_err: false,
s: {
...prevState.s,
jungfrau_conversion: false,
jungfrau_conversion_factor_keV: undefined
}
}));
}
}}/>
} label="Conversion to photons"/>
<b>Output pixel value</b>
<Stack spacing={2} direction="row">
<FormControl>
<InputLabel id="demo-simple-select-label">Bit-width</InputLabel>
<Select
sx={{width: 150}}
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Detector"
value={bitWidthSelection}
onChange={handleBitWidthChange}
>
<MenuItem value={"a"}>Auto</MenuItem>
<MenuItem value={"8"}>8-bit</MenuItem>
<MenuItem value={"16"}>16-bit</MenuItem>
<MenuItem value={"32"}>32-bit</MenuItem>
</Select>
</FormControl>
<FormControl>
<InputLabel id="demo-simple-select-label">Sign</InputLabel>
<Select
sx={{width: 150}}
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Sign"
value={signSelection}
onChange={handleSignChange}
>
<MenuItem value={"a"}>Auto</MenuItem>
<MenuItem value={"s"}>Signed</MenuItem>
<MenuItem value={"u"}>Unsigned</MenuItem>
</Select>
</FormControl>
</Stack>
<b>JUNGFRAU specific</b>
<FormGroup>
<FormControlLabel control={
<Checkbox checked={s.jungfrau_mask_pixels_without_g0}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, jungfrau_mask_pixels_without_g0: event.target.checked}));
}}/>
} label="Mask pixels not switching to G0"/>
<FormControlLabel control={
<Checkbox checked={s.jungfrau_conversion}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setJungfrauConversionFactorErr(false);
setS(prev => ({
...prev,
jungfrau_conversion: event.target.checked,
jungfrau_conversion_factor_keV: undefined
}));
}}/>
} label="Conversion to photons"/>
</FormGroup>
<List>
<Tooltip
title="JUNGFRAU conversion factor allows to get different unit than 1 photon count. If not provided incident_energy is used.">
<ListItem>
<Checkbox
checked={this.state.s.jungfrau_conversion_factor_keV !== undefined}
disabled={!this.state.s.jungfrau_conversion}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
if (!event.target.checked)
this.setState(prevState => ({
download_counter: this.state.download_counter + 1,
jungfrau_conversion_factor_err: false,
s: {
...prevState.s,
jungfrau_conversion_factor_keV: undefined
}
}));
else
this.setState(prevState => ({
download_counter: this.state.download_counter + 1,
jungfrau_conversion_factor_err: false,
s: {
...prevState.s,
jungfrau_conversion_factor_keV: this.state.jungfrau_conversion_factor_old
}
}));
}}
/>
<FormControl fullWidth>
<NumberTextField
start_val={this.state.s.jungfrau_conversion_factor_keV}
disabled={this.state.s.jungfrau_conversion_factor_keV === undefined}
label={"JUNGFRAU conversion factor"}
units={"keV"}
float={true}
min={0.001}
fullWidth
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
let old: number = this.state.jungfrau_conversion_factor_old;
if (!err)
old = val;
this.setState(prevState => ({
jungfrau_conversion_factor_err: err,
jungfrau_conversion_factor_old: old,
s: {...prevState.s, jungfrau_conversion_factor_keV: val}
}));
}}/>
</FormControl>
</FormGroup>
<List>
<Tooltip
title="JUNGFRAU conversion factor allows to get different unit than 1 photon count. If not provided incident_energy is used.">
<ListItem>
<Checkbox
checked={s.jungfrau_conversion_factor_keV !== undefined}
disabled={!s.jungfrau_conversion}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setDownloadCounter(c => c + 1);
setJungfrauConversionFactorErr(false);
if (!event.target.checked)
setS(prev => ({...prev, jungfrau_conversion_factor_keV: undefined}));
else
setS(prev => ({...prev, jungfrau_conversion_factor_keV: jungfrauConversionFactorOld}));
}}
/>
<FormControl fullWidth>
<NumberTextField
start_val={s.jungfrau_conversion_factor_keV}
disabled={s.jungfrau_conversion_factor_keV === undefined}
label={"JUNGFRAU conversion factor"}
units={"keV"}
float={true}
min={0.001}
fullWidth
counter={downloadCounter}
callback={(val: number, err: boolean) => {
if (!err)
setJungfrauConversionFactorOld(val);
setJungfrauConversionFactorErr(err);
setS(prev => ({...prev, jungfrau_conversion_factor_keV: val}));
}}/>
</FormControl>
</ListItem>
</Tooltip>
</List>
<NumberTextField start_val={this.state.s.jungfrau_pedestal_g0_rms_limit}
label={"G0 max RMS"}
min={0}
default={100}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
pedestal_g0_rms_limit_err: err,
s: {...prevState.s, jungfrau_pedestal_g0_rms_limit: val}
}));
}}
/>
<ButtonWithSnackbar
path={"/config/image_format"}
color={"primary"}
method={"PUT"}
disabled={this.state.jungfrau_conversion_factor_err}
text={"Upload"}
input={JSON.stringify(this.state.s)}
</ListItem>
</Tooltip>
</List>
<NumberTextField start_val={s.jungfrau_pedestal_g0_rms_limit}
label={"G0 max RMS"}
min={0}
default={100}
counter={downloadCounter}
callback={(val: number, _err: boolean) => {
setS(prev => ({...prev, jungfrau_pedestal_g0_rms_limit: val}));
}}
/>
</Stack>
<br/>
</Paper>
}
<ButtonWithSnackbar
path={"/config/image_format"}
color={"primary"}
method={"PUT"}
disabled={jungfrauConversionFactorErr}
text={"Upload"}
input={JSON.stringify(s)}
/>
</Stack>
<br/>
</Paper>
}
export default ImageFormatSettings;
export default memo(ImageFormatSettings);
+34 -37
View File
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
import React, {Component} from 'react';
import {memo} from 'react';
import Paper from '@mui/material/Paper';
import {Stack, Table, TableBody, TableCell, TableContainer, TableRow} from "@mui/material";
import {image_pusher_status, image_pusher_type} from "../client";
@@ -32,41 +32,38 @@ function renderPusherType(type?: image_pusher_type): string {
}
}
class ImagePusherStatus extends Component<MyProps> {
render() {
const { s } = this.props;
return (
<Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={2} sx={{justifyContent: "center", alignItems: "center"}}>
<strong>Image pusher status</strong>
<TableContainer component={Paper} sx={{width: '90%'}} style={{marginLeft: "auto", marginRight: "auto"}}>
<Table size="small">
<TableBody>
<TableRow>
<TableCell component="th" scope="row">Pusher type:</TableCell>
<TableCell align="right">{renderPusherType(s?.pusher_type)}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row">Images written:</TableCell>
<TableCell align="right">{s?.images_written ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row">Images not written due to errors:</TableCell>
<TableCell align="right">{s?.images_write_error ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row">FIFO utilization:</TableCell>
<TableCell align="right">{renderFifo(s?.writer_fifo_utilization)}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Stack>
<br/>
</Paper>
);
}
function ImagePusherStatus({s}: MyProps) {
return (
<Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={2} sx={{justifyContent: "center", alignItems: "center"}}>
<strong>Image pusher status</strong>
<TableContainer component={Paper} sx={{width: '90%'}} style={{marginLeft: "auto", marginRight: "auto"}}>
<Table size="small">
<TableBody>
<TableRow>
<TableCell component="th" scope="row">Pusher type:</TableCell>
<TableCell align="right">{renderPusherType(s?.pusher_type)}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row">Images written:</TableCell>
<TableCell align="right">{s?.images_written ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row">Images not written due to errors:</TableCell>
<TableCell align="right">{s?.images_write_error ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row">FIFO utilization:</TableCell>
<TableCell align="right">{renderFifo(s?.writer_fifo_utilization)}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Stack>
<br/>
</Paper>
);
}
export default ImagePusherStatus;
export default memo(ImagePusherStatus);
+250 -323
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {memo, useEffect, useState} from 'react';
import {Stack, Typography, FormControlLabel, Checkbox} from "@mui/material";
import Paper from "@mui/material/Paper";
@@ -16,22 +16,6 @@ type MyProps = {
status?: broker_status
}
type MyState = {
s: indexing_settings,
last_downloaded_s: indexing_settings,
download_counter: number,
viable_cell_min_spots_error: boolean,
fft_high_resolution_A_error: boolean,
fft_max_unit_cell_A_error: boolean,
fft_min_unit_cell_A_error: boolean,
fft_num_vectors_error: boolean,
tolerance_error: boolean,
thread_count_error: boolean,
unit_cell_dist_tolerance_error: boolean,
rotation_indexing_min_angular_range_deg_error: boolean,
rotation_indexing_angular_stride_deg_error: boolean
}
const default_indexing_settings: indexing_settings = {
algorithm: indexing_algorithm.FFBIDX,
geom_refinement_algorithm: geom_refinement_algorithm.NONE,
@@ -50,331 +34,274 @@ const default_indexing_settings: indexing_settings = {
blocking: false
};
class IndexingSettings extends Component<MyProps, MyState> {
state : MyState = {
s: default_indexing_settings,
last_downloaded_s: default_indexing_settings,
download_counter: 0,
fft_high_resolution_A_error: false,
fft_max_unit_cell_A_error: false,
fft_min_unit_cell_A_error: false,
fft_num_vectors_error: false,
tolerance_error: false,
thread_count_error: false,
unit_cell_dist_tolerance_error: false,
viable_cell_min_spots_error: false,
rotation_indexing_min_angular_range_deg_error: false,
rotation_indexing_angular_stride_deg_error: false
}
function IndexingSettings({s: serverS, status}: MyProps) {
const [s, setS] = useState<indexing_settings>(default_indexing_settings);
const [lastDownloadedS, setLastDownloadedS] = useState<indexing_settings>(default_indexing_settings);
const [downloadCounter, setDownloadCounter] = useState(0);
const [viableCellMinSpotsError, setViableCellMinSpotsError] = useState(false);
const [fftHighResolutionAError, setFftHighResolutionAError] = useState(false);
const [fftMaxUnitCellAError, setFftMaxUnitCellAError] = useState(false);
const [fftMinUnitCellAError, setFftMinUnitCellAError] = useState(false);
const [fftNumVectorsError, setFftNumVectorsError] = useState(false);
const [toleranceError, setToleranceError] = useState(false);
const [threadCountError, setThreadCountError] = useState(false);
const [unitCellDistToleranceError, setUnitCellDistToleranceError] = useState(false);
const [rotationIndexingMinAngularRangeDegError, setRotationIndexingMinAngularRangeDegError] = useState(false);
const [rotationIndexingAngularStrideDegError, setRotationIndexingAngularStrideDegError] = useState(false);
getValues = () => {
if (this.props.s !== undefined) {
let format_set: indexing_settings = this.props.s;
if (!_.isEqual(format_set, this.state.last_downloaded_s)) {
this.setState(prevState => ({
s: format_set,
last_downloaded_s: format_set,
fft_high_resolution_A_error: false,
fft_max_unit_cell_A_error: false,
fft_min_unit_cell_A_error: false,
fft_num_vectors_error: false,
tolerance_error: false,
thread_count_error: false,
unit_cell_dist_tolerance_error: false,
viable_cell_min_spots_error: false,
download_counter: prevState.download_counter + 1,
}));
}
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
setFftHighResolutionAError(false);
setFftMaxUnitCellAError(false);
setFftMinUnitCellAError(false);
setFftNumVectorsError(false);
setToleranceError(false);
setThreadCountError(false);
setUnitCellDistToleranceError(false);
setViableCellMinSpotsError(false);
setDownloadCounter(c => c + 1);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
componentDidUpdate() {
this.getValues();
}
handleAlgorithmChange = (event: SelectChangeEvent) => {
this.setState(prevState => ({
s: {
...prevState.s,
algorithm: event.target.value as indexing_algorithm
}
}));
const handleAlgorithmChange = (event: SelectChangeEvent) => {
setS(prev => ({...prev, algorithm: event.target.value as indexing_algorithm}));
};
handleRefinementAlgorithmChange = (event: SelectChangeEvent) => {
this.setState(prevState => ({
s: {
...prevState.s,
geom_refinement_algorithm: event.target.value as geom_refinement_algorithm
}
}));
const handleRefinementAlgorithmChange = (event: SelectChangeEvent) => {
setS(prev => ({...prev, geom_refinement_algorithm: event.target.value as geom_refinement_algorithm}));
};
isError() {
return this.state.tolerance_error
|| this.state.fft_high_resolution_A_error
|| this.state.fft_min_unit_cell_A_error
|| this.state.fft_max_unit_cell_A_error
|| this.state.fft_num_vectors_error
|| this.state.thread_count_error
|| this.state.unit_cell_dist_tolerance_error
|| this.state.viable_cell_min_spots_error
|| this.state.rotation_indexing_angular_stride_deg_error
|| this.state.rotation_indexing_min_angular_range_deg_error;
}
const isError = () =>
toleranceError
|| fftHighResolutionAError
|| fftMinUnitCellAError
|| fftMaxUnitCellAError
|| fftNumVectorsError
|| threadCountError
|| unitCellDistToleranceError
|| viableCellMinSpotsError
|| rotationIndexingAngularStrideDegError
|| rotationIndexingMinAngularRangeDegError;
render() {
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<strong>Indexing settings </strong>
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<strong>Indexing settings </strong>
<b>Algorithm</b>
<FormControl sx = {{width: "80%"}}>
<InputLabel id="demo-simple-select-label">Algorithm</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Algorithm"
value={this.state.s.algorithm}
onChange={this.handleAlgorithmChange}
>
<MenuItem value={indexing_algorithm.FFBIDX}>FFBIDX (known unit cell) - GPU</MenuItem>
<MenuItem value={indexing_algorithm.FFT}>FFT (guess unit cell) - GPU with CuFFT library</MenuItem>
<MenuItem value={indexing_algorithm.FFTW}>FFT (guess unit cell) - CPU with FFTW library</MenuItem>
<MenuItem value={indexing_algorithm.AUTO}>Auto (FFBIDX if unit cell provided, FFT otherwise)</MenuItem>
<MenuItem value={indexing_algorithm.NONE}>No indexing</MenuItem>
</Select>
</FormControl>
<Stack spacing={2} direction="row" sx={{width: '80%'}}>
<NumberTextField start_val={this.state.s.tolerance}
label={"HKL tol."}
float={true}
min={0}
default={0.5}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
tolerance_error: err,
s: {...prevState.s, tolerance: val}
}));
}}/>
<NumberTextField start_val={this.state.s.unit_cell_dist_tolerance * 100.0}
label={"UC dist. tol."}
float={true}
min={0.1}
default={5.0}
max={20.0}
units={"%"}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
tolerance_error: err,
s: {...prevState.s,
unit_cell_dist_tolerance: val / 100.0}
}));
}}/>
<NumberTextField start_val={this.state.s.viable_cell_min_spots}
label={"Min spots "}
float={false}
min={5}
default={10}
max={1000}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
viable_cell_min_spots_error: err,
s: {...prevState.s,
viable_cell_min_spots: val}
}));
}}/></Stack>&nbsp;
<b>Algorithm</b>
<FormControl sx = {{width: "80%"}}>
<InputLabel>Geometry refinement</InputLabel>
<InputLabel id="demo-simple-select-label">Algorithm</InputLabel>
<Select
label="Refinement algorithm"
value={this.state.s.geom_refinement_algorithm}
onChange={this.handleRefinementAlgorithmChange}
labelId="demo-simple-select-label"
id="demo-simple-select"
label="Algorithm"
value={s.algorithm}
onChange={handleAlgorithmChange}
>
<MenuItem value={geom_refinement_algorithm.NONE}>No refinement</MenuItem>
<MenuItem value={geom_refinement_algorithm.ORIENTATION_ONLY}>Lattice orientation</MenuItem>
<MenuItem value={geom_refinement_algorithm.BEAM_CENTER}>Beam center, lattice orientation and dimensions</MenuItem>
<MenuItem value={indexing_algorithm.FFBIDX}>FFBIDX (known unit cell) - GPU</MenuItem>
<MenuItem value={indexing_algorithm.FFT}>FFT (guess unit cell) - GPU with CuFFT library</MenuItem>
<MenuItem value={indexing_algorithm.FFTW}>FFT (guess unit cell) - CPU with FFTW library</MenuItem>
<MenuItem value={indexing_algorithm.AUTO}>Auto (FFBIDX if unit cell provided, FFT otherwise)</MenuItem>
<MenuItem value={indexing_algorithm.NONE}>No indexing</MenuItem>
</Select>
</FormControl>
<FormControlLabel
control={
<Checkbox
checked={this.state.s.index_ice_rings}
onChange={(event) => {
this.setState(prevState => ({
s: {...prevState.s, index_ice_rings: event.target.checked}
}));
}}
/>
}
label="Index ice rings"
/>
<FormControlLabel
control={
<Checkbox
checked={this.state.s.blocking}
onChange={(event) => {
this.setState(prevState => ({
s: {...prevState.s, blocking: event.target.checked}
}));
}}
/>
}
label="Blocking thread pool"
/>
<b>Rotation (3D) indexing settings</b>
<Stack spacing={2} direction="row" sx={{width: '80%'}}>
<FormControlLabel
control={
<Checkbox
checked={this.state.s.rotation_indexing}
onChange={(event) => {
this.setState(prevState => ({
s: {...prevState.s, rotation_indexing: event.target.checked}
}));
}}
/>
}
label="Enable"
/>
<NumberTextField start_val={this.state.s.rotation_indexing_min_angular_range_deg}
label={"Angular range"}
float={true}
units={"°"}
min={1.0}
max={360.0}
default={20.0}
counter={this.state.download_counter}
disabled={!this.state.s.rotation_indexing}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
rotation_indexing_min_angular_range_deg_error: err,
s: {...prevState.s, rotation_indexing_min_angular_range_deg: val}
}));
}}/>&nbsp;
<NumberTextField start_val={this.state.s.rotation_indexing_angular_stride_deg}
label={"Angular stride"}
float={true}
units={"°"}
min={0.0001}
max={100.0}
default={0.1}
counter={this.state.download_counter}
disabled={!this.state.s.rotation_indexing}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
rotation_indexing_angular_stride_deg_error: err,
s: {...prevState.s, rotation_indexing_angular_stride_deg: val}
}));
}}/>&nbsp;
</Stack>
<b>FFT indexing settings</b>
<Stack spacing={2} direction="row" sx={{width: '80%'}}>
<NumberTextField start_val={this.state.s.fft_min_unit_cell_A}
label={"Min Unit cell"}
float={true}
units={"Å"}
min={1.0}
max={40.0}
default={10.0}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
fft_min_unit_cell_A_error: err,
s: {...prevState.s, fft_min_unit_cell_A: val}
}));
}}/>&nbsp;
<NumberTextField start_val={this.state.s.fft_max_unit_cell_A}
label={"Max Unit cell"}
float={true}
units={"Å"}
min={50.0}
max={400.0}
default={250.0}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
fft_max_unit_cell_A_error: err,
s: {...prevState.s, fft_max_unit_cell_A: val}
}));
}}/>&nbsp;
<NumberTextField start_val={this.state.s.fft_high_resolution_A}
label={"High res."}
float={true}
units={"Å"}
min={0.5}
max={6.0}
default={2.0}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
fft_high_resolution_A_error: err,
s: {...prevState.s, fft_high_resolution_A: val}
}));
}}/>&nbsp;
</Stack>
<NumberTextField start_val={this.state.s.fft_num_vectors}
label={"# vectors"}
<Stack spacing={2} direction="row" sx={{width: '80%'}}>
<NumberTextField start_val={s.tolerance}
label={"HKL tol."}
float={true}
min={0}
default={0.5}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setToleranceError(err);
setS(prev => ({...prev, tolerance: val}));
}}/>
<NumberTextField start_val={s.unit_cell_dist_tolerance * 100.0}
label={"UC dist. tol."}
float={true}
min={0.1}
default={5.0}
max={20.0}
units={"%"}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setToleranceError(err);
setS(prev => ({...prev, unit_cell_dist_tolerance: val / 100.0}));
}}/>
<NumberTextField start_val={s.viable_cell_min_spots}
label={"Min spots "}
float={false}
min={128}
default={16384}
counter={this.state.download_counter}
min={5}
default={10}
max={1000}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
fft_num_vectors_error: err,
s: {...prevState.s, fft_num_vectors: val}
}));
}}
sx={{width: '80%'}}/>&nbsp;
<b>Computing resources</b>
<Typography> GPU count: {this.props.status !== undefined ? this.props.status.gpu_count : "N/A"}</Typography>
setViableCellMinSpotsError(err);
setS(prev => ({...prev, viable_cell_min_spots: val}));
}}/></Stack>&nbsp;
<FormControl sx = {{width: "80%"}}>
<InputLabel>Geometry refinement</InputLabel>
<Select
label="Refinement algorithm"
value={s.geom_refinement_algorithm}
onChange={handleRefinementAlgorithmChange}
>
<MenuItem value={geom_refinement_algorithm.NONE}>No refinement</MenuItem>
<MenuItem value={geom_refinement_algorithm.ORIENTATION_ONLY}>Lattice orientation</MenuItem>
<MenuItem value={geom_refinement_algorithm.BEAM_CENTER}>Beam center, lattice orientation and dimensions</MenuItem>
</Select>
</FormControl>
<FormControlLabel
control={
<Checkbox
checked={s.index_ice_rings}
onChange={(event) => {
setS(prev => ({...prev, index_ice_rings: event.target.checked}));
}}
/>
}
label="Index ice rings"
/>
<FormControlLabel
control={
<Checkbox
checked={s.blocking}
onChange={(event) => {
setS(prev => ({...prev, blocking: event.target.checked}));
}}
/>
}
label="Blocking thread pool"
/>
<b>Rotation (3D) indexing settings</b>
<Stack spacing={2} direction="row" sx={{width: '80%'}}>
<NumberTextField start_val={this.state.s.thread_count}
label={"CPU Thread count"}
float={false}
min={1}
max={64}
default={this.state.s.thread_count}
counter={this.state.download_counter}
<FormControlLabel
control={
<Checkbox
checked={s.rotation_indexing}
onChange={(event) => {
setS(prev => ({...prev, rotation_indexing: event.target.checked}));
}}
/>
}
label="Enable"
/>
<NumberTextField start_val={s.rotation_indexing_min_angular_range_deg}
label={"Angular range"}
float={true}
units={"°"}
min={1.0}
max={360.0}
default={20.0}
counter={downloadCounter}
disabled={!s.rotation_indexing}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
thread_count_error: err,
s: {...prevState.s, thread_count: val}
}));
}}
sx={{width: '80%'}}/>
<ButtonWithSnackbar
path={"/config/indexing"}
color={"primary"}
method={"PUT"}
disabled={this.isError()}
text={"Upload"}
input={JSON.stringify(this.state.s)}
/>
setRotationIndexingMinAngularRangeDegError(err);
setS(prev => ({...prev, rotation_indexing_min_angular_range_deg: val}));
}}/>&nbsp;
<NumberTextField start_val={s.rotation_indexing_angular_stride_deg}
label={"Angular stride"}
float={true}
units={"°"}
min={0.0001}
max={100.0}
default={0.1}
counter={downloadCounter}
disabled={!s.rotation_indexing}
callback={(val: number, err: boolean) => {
setRotationIndexingAngularStrideDegError(err);
setS(prev => ({...prev, rotation_indexing_angular_stride_deg: val}));
}}/>&nbsp;
</Stack>
<br/>
</Paper>
}
<b>FFT indexing settings</b>
<Stack spacing={2} direction="row" sx={{width: '80%'}}>
<NumberTextField start_val={s.fft_min_unit_cell_A}
label={"Min Unit cell"}
float={true}
units={"Å"}
min={1.0}
max={40.0}
default={10.0}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setFftMinUnitCellAError(err);
setS(prev => ({...prev, fft_min_unit_cell_A: val}));
}}/>&nbsp;
<NumberTextField start_val={s.fft_max_unit_cell_A}
label={"Max Unit cell"}
float={true}
units={"Å"}
min={50.0}
max={400.0}
default={250.0}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setFftMaxUnitCellAError(err);
setS(prev => ({...prev, fft_max_unit_cell_A: val}));
}}/>&nbsp;
<NumberTextField start_val={s.fft_high_resolution_A}
label={"High res."}
float={true}
units={"Å"}
min={0.5}
max={6.0}
default={2.0}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setFftHighResolutionAError(err);
setS(prev => ({...prev, fft_high_resolution_A: val}));
}}/>&nbsp;
</Stack>
<NumberTextField start_val={s.fft_num_vectors}
label={"# vectors"}
float={false}
min={128}
default={16384}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setFftNumVectorsError(err);
setS(prev => ({...prev, fft_num_vectors: val}));
}}
sx={{width: '80%'}}/>&nbsp;
<b>Computing resources</b>
<Typography> GPU count: {status !== undefined ? status.gpu_count : "N/A"}</Typography>
<NumberTextField start_val={s.thread_count}
label={"CPU Thread count"}
float={false}
min={1}
max={64}
default={s.thread_count}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setThreadCountError(err);
setS(prev => ({...prev, thread_count: val}));
}}
sx={{width: '80%'}}/>
<ButtonWithSnackbar
path={"/config/indexing"}
color={"primary"}
method={"PUT"}
disabled={isError()}
text={"Upload"}
input={JSON.stringify(s)}
/>
</Stack>
<br/>
</Paper>
}
export default IndexingSettings;
export default memo(IndexingSettings);
+75 -107
View File
@@ -1,10 +1,10 @@
import React, {Component} from 'react';
import {ChangeEvent, memo, useEffect, useState} from 'react';
import {
Autocomplete,
Checkbox,
FormControlLabel, FormGroup,
Grid, Stack,
Stack,
TextField
} from "@mui/material";
import Paper from "@mui/material/Paper";
@@ -16,11 +16,6 @@ type MyProps = {
s?: instrument_metadata
}
type MyState = {
s: instrument_metadata
last_downloaded_s: instrument_metadata
};
const default_instrument_metadata: instrument_metadata = {
source_name: "Swiss Light Source",
instrument_name: "X06SA",
@@ -39,113 +34,86 @@ const source_types : string[] = [
"Metal Jet X-ray"
];
class ImageFormatSettings extends Component<MyProps, MyState> {
state : MyState = {
s: default_instrument_metadata,
last_downloaded_s: default_instrument_metadata
}
function InstrumentMetadata({s: serverS}: MyProps) {
const [s, setS] = useState<instrument_metadata>(default_instrument_metadata);
const [lastDownloadedS, setLastDownloadedS] = useState<instrument_metadata>(default_instrument_metadata);
getValues () {
if (this.props.s !== undefined) {
let instr_metadata: instrument_metadata = this.props.s;
if (!_.isEqual(instr_metadata, this.state.last_downloaded_s))
this.setState({
s: instr_metadata,
last_downloaded_s: instr_metadata
});
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setS(serverS);
setLastDownloadedS(serverS);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
componentDidUpdate() {
this.getValues();
}
render() {
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%' }}>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
marginLeft: '8%',
marginRight: '8%',
}}>
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%' }}>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
marginLeft: '8%',
marginRight: '8%',
}}>
<div><strong>Source and instrument metadata</strong></div>
<TextField id="source_name" label="Source name" variant="outlined"
fullWidth
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({
s: {...prevState.s, source_name: event.target.value}
}))
}}
value={this.state.s.source_name}/>
<TextField id="instrument_name" label="Instrument name" variant="outlined"
fullWidth
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(prevState => ({
s: {...prevState.s, instrument_name: event.target.value}
}))
}}
value={this.state.s.instrument_name}/>
<Autocomplete
fullWidth
freeSolo
multiple={false}
onChange={(event, newValue: string | null | undefined) => {
if (typeof newValue === "string")
this.setState(prevState => ({s: {...prevState.s, source_type: newValue}}));
}}
<div><strong>Source and instrument metadata</strong></div>
<TextField id="source_name" label="Source name" variant="outlined"
fullWidth
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, source_name: event.target.value}));
}}
value={s.source_name}/>
<TextField id="instrument_name" label="Instrument name" variant="outlined"
fullWidth
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, instrument_name: event.target.value}));
}}
value={s.instrument_name}/>
<Autocomplete
fullWidth
freeSolo
multiple={false}
onChange={(event, newValue: string | null | undefined) => {
if (typeof newValue === "string")
setS(prev => ({...prev, source_type: newValue}));
}}
inputValue={this.state.s.source_type}
inputValue={s.source_type}
onInputChange={(_, newInputValue: string) => {
this.setState(prevState => ({
s: {...prevState.s, source_type: newInputValue}
}));
}}
selectOnFocus
clearOnBlur
value={this.state.s.source_type}
options={source_types}
renderInput={(params) => <TextField {...params} label="Source type" variant="outlined"/>}
/>
<FormGroup>
<FormControlLabel control={
<Checkbox checked={this.state.s.pulsed_source === true}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {...prevState.s, pulsed_source: event.target.checked}
}));
}}/>
} label="Pulsed source (XFEL)"/>
<FormControlLabel control={
<Checkbox checked={this.state.s.electron_source === true}
onChange={
(event: React.ChangeEvent<HTMLInputElement>) => {
this.setState(
prevState => ({
s: {...prevState.s, electron_source: event.target.checked}
}));
}}/>
} label="Electron source"/>
</FormGroup>
<ButtonWithSnackbar
color={"primary"}
path={"/config/instrument"}
input={JSON.stringify(this.state.s)}
method={"PUT"}
text={"Upload"}
onInputChange={(_event, newInputValue: string) => {
setS(prev => ({...prev, source_type: newInputValue}));
}}
selectOnFocus
clearOnBlur
value={s.source_type}
options={source_types}
renderInput={(params) => <TextField {...params} label="Source type" variant="outlined"/>}
/>
</Stack>
<br/>
</Paper>
}
<FormGroup>
<FormControlLabel control={
<Checkbox checked={s.pulsed_source === true}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, pulsed_source: event.target.checked}));
}}/>
} label="Pulsed source (XFEL)"/>
<FormControlLabel control={
<Checkbox checked={s.electron_source === true}
onChange={
(event: ChangeEvent<HTMLInputElement>) => {
setS(prev => ({...prev, electron_source: event.target.checked}));
}}/>
} label="Electron source"/>
</FormGroup>
<ButtonWithSnackbar
color={"primary"}
path={"/config/instrument"}
input={JSON.stringify(s)}
method={"PUT"}
text={"Upload"}
/>
</Stack>
<br/>
</Paper>
}
export default ImageFormatSettings;
export default memo(InstrumentMetadata);
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {memo} from 'react';
import Paper from '@mui/material/Paper';
import {Grid, Table, TableBody, TableCell, TableContainer, TableRow,} from "@mui/material";
@@ -8,83 +8,78 @@ type MyProps = {
s: measurement_statistics,
};
type MyState = {};
function MeasurementStatistics({s}: MyProps) {
return <Paper style={{textAlign: 'center'}} sx={{ height: 550, width: '100%' }}>
<Grid container spacing={0}>
<Grid item xs={1}/>
<Grid item xs={10}>
<br/><strong>Measurement statistics</strong><br/><br/>
class MeasurementStatistics extends Component<MyProps, MyState> {
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: 550, width: '100%' }}>
<Grid container spacing={0}>
<Grid item xs={1}/>
<Grid item xs={10}>
<br/><strong>Measurement statistics</strong><br/><br/>
<TableContainer component={Paper} style={{marginLeft: "auto", marginRight: "auto"}}>
<Table size="small" aria-label="simple table">
<TableBody>
<TableRow>
<TableCell component="th" scope="row"> File prefix: </TableCell>
<TableCell align="right">{(s.file_prefix !== undefined) ? s.file_prefix : "(images not written)"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Run number: </TableCell>
<TableCell align="right">{(s.run_number !== undefined) ? s.run_number : "(not set)"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Experiment group: </TableCell>
<TableCell align="right">{s.experiment_group}</TableCell>
</TableRow>
<TableContainer component={Paper} style={{marginLeft: "auto", marginRight: "auto"}}>
<Table size="small" aria-label="simple table">
<TableBody>
<TableRow>
<TableCell component="th" scope="row"> File prefix: </TableCell>
<TableCell align="right">{(this.props.s.file_prefix !== undefined) ? this.props.s.file_prefix : "(images not written)"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Run number: </TableCell>
<TableCell align="right">{(this.props.s.run_number !== undefined) ? this.props.s.run_number : "(not set)"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Experiment group: </TableCell>
<TableCell align="right">{this.props.s.experiment_group}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images expected: </TableCell>
<TableCell align="right">{s.images_expected}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images collected: </TableCell>
<TableCell align="right">{s.images_collected}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images sent to writer: </TableCell>
<TableCell align="right">{s.images_sent}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images discarded by lossy compression: </TableCell>
<TableCell align="right">{s.images_discarded_lossy_compression}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Compression ratio: </TableCell>
<TableCell align="right">{(s.compression_ratio !== undefined)
? (s.compression_ratio.toFixed(1)) + "x" : "-" } </TableCell>
<TableRow>
<TableCell component="th" scope="row"> Images expected: </TableCell>
<TableCell align="right">{this.props.s.images_expected}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images collected: </TableCell>
<TableCell align="right">{this.props.s.images_collected}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images sent to writer: </TableCell>
<TableCell align="right">{this.props.s.images_sent}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Images discarded by lossy compression: </TableCell>
<TableCell align="right">{this.props.s.images_discarded_lossy_compression}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Compression ratio: </TableCell>
<TableCell align="right">{(this.props.s.compression_ratio !== undefined)
? (this.props.s.compression_ratio.toFixed(1)) + "x" : "-" } </TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Data acquisition efficiency: </TableCell>
<TableCell align="right">{(this.props.s.collection_efficiency !== undefined)
? (Math.floor(this.props.s.collection_efficiency * 1000.0) / 1000.0).toFixed(3) : "-"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Indexing rate: </TableCell>
<TableCell align="right">{(this.props.s.indexing_rate !== undefined)
? this.props.s.indexing_rate.toFixed(2) : "-"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Background estimate: </TableCell>
<TableCell align="right">{(this.props.s.bkg_estimate !== undefined)
? this.props.s.bkg_estimate.toPrecision(7) : "-"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Unit cell: </TableCell>
<TableCell align="right">{this.props.s.unit_cell}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<br/>
</Grid>
<Grid item xs={1}/>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Data acquisition efficiency: </TableCell>
<TableCell align="right">{(s.collection_efficiency !== undefined)
? (Math.floor(s.collection_efficiency * 1000.0) / 1000.0).toFixed(3) : "-"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Indexing rate: </TableCell>
<TableCell align="right">{(s.indexing_rate !== undefined)
? s.indexing_rate.toFixed(2) : "-"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Background estimate: </TableCell>
<TableCell align="right">{(s.bkg_estimate !== undefined)
? s.bkg_estimate.toPrecision(7) : "-"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Unit cell: </TableCell>
<TableCell align="right">{s.unit_cell}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
<br/>
</Grid>
</Paper>
}
<Grid item xs={1}/>
</Grid>
</Paper>
}
export default MeasurementStatistics;
export default memo(MeasurementStatistics);
+60 -79
View File
@@ -1,10 +1,9 @@
import React, {Component} from 'react';
import {ChangeEvent, memo, ReactNode, useEffect, useState} from 'react';
import {
InputAdornment, SxProps,
TextField, Theme
} from "@mui/material";
import { ReactNode } from "react";
type MyProps = {
start_val?: number,
@@ -21,94 +20,76 @@ type MyProps = {
float?: boolean
}
type MyState = {
text: string,
err: boolean,
val: number
function unitsNode(str: string | undefined): ReactNode | null {
if (str === undefined)
return null;
else if (str === "us")
return <InputAdornment position="end">&micro;s</InputAdornment>;
else if (str === "A-1")
return <InputAdornment position="end">
<span>&#8491;<sup>-1</sup></span>
</InputAdornment>;
else
return <InputAdornment position="end">{str}</InputAdornment>;
}
class NumberTextField extends Component<MyProps, MyState> {
state : MyState = {
text: ".",
err: true,
val: 0
}
function NumberTextField({start_val, default: defaultValue, label, callback, counter, min, max, sx, units, disabled, float}: MyProps) {
const [text, setText] = useState(".");
const [err, setErr] = useState(true);
const [val, setVal] = useState(0);
setup() {
let val : number = 0;
if (this.props.start_val !== undefined)
val = this.props.start_val;
else if (this.props.default !== undefined)
val = this.props.default;
// Something has to change in parent to trigger setup => otherwise this makes infinite loop.
// In case of error state start_val could stay same, but we want to return to "safe" set
// so there is external counter that if changed reset is triggered.
useEffect(() => {
let v : number = 0;
if (start_val !== undefined)
v = start_val;
else if (defaultValue !== undefined)
v = defaultValue;
if (this.props.float !== true)
val = Math.round(val);
if (float !== true)
v = Math.round(v);
else
val = Math.round(val * 1000.0) / 1000.0
v = Math.round(v * 1000.0) / 1000.0
this.setState({
err: false,
val: val,
text: val.toString()
});
}
setErr(false);
setVal(v);
setText(v.toString());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [counter, start_val]);
componentDidMount() {
this.setup();
}
const updateValue = (event: ChangeEvent<HTMLInputElement>) => {
let new_text = event.target.value;
if (!new_text) new_text = "0";
componentDidUpdate(prevProps: Readonly<MyProps>, prevState: Readonly<MyState>, snapshot?: any) {
// Something has to change in parent function to trigger setup => otherwise this makes infinite loop
// In case of error state start_val could stay same, but we want to return to "safe" set
// so there is external counter that if changed reset is triggered
if ((prevProps.counter !== this.props.counter) || (prevProps.start_val !== this.props.start_val))
this.setup();
}
let num_val : number = Number(new_text);
updateValue = (event: React.ChangeEvent<HTMLInputElement>) => {
let text = event.target.value;
if (!text) text = "0";
let num_val : number = Number(text);
let err : boolean = !Number.isFinite(num_val)
|| (!this.props.float && !Number.isInteger(num_val))
|| ((this.props.min !== undefined) && (num_val < this.props.min))
|| ((this.props.max !== undefined) && (num_val > this.props.max));
let new_err : boolean = !Number.isFinite(num_val)
|| (!float && !Number.isInteger(num_val))
|| ((min !== undefined) && (num_val < min))
|| ((max !== undefined) && (num_val > max));
// If error I keep old numeric value to return (so there is always a proper value kept)
let new_val : number = err ? this.state.val : num_val;
this.setState({err: err, val: new_val, text: text});
this.props.callback(new_val, err);
}
let new_val : number = new_err ? val : num_val;
setErr(new_err);
setVal(new_val);
setText(new_text);
callback(new_val, new_err);
};
units(str: string | undefined): ReactNode | null {
if (str === undefined)
return null;
else if (str === "us")
return <InputAdornment position="end">&micro;s</InputAdornment>;
else if (str === "A-1")
return <InputAdornment position="end">
<span>&#8491;<sup>-1</sup></span>
</InputAdornment>;
else
return <InputAdornment position="end">{str}</InputAdornment>;
}
render() {
return <TextField id="frame_time" label={this.props.label} variant="outlined"
sx = {this.props.sx}
error={this.state.err}
onChange={this.updateValue}
value={this.state.text}
disabled={this.props.disabled}
InputProps={{
inputProps: {
style: {textAlign: 'right'}
},
endAdornment: this.units(this.props.units)
}}/>
}
return <TextField id="frame_time" label={label} variant="outlined"
sx = {sx}
error={err}
onChange={updateValue}
value={text}
disabled={disabled}
InputProps={{
inputProps: {
style: {textAlign: 'right'}
},
endAdornment: unitsNode(units)
}}/>
}
export default NumberTextField;
export default memo(NumberTextField);
+49 -55
View File
@@ -1,4 +1,4 @@
import React from 'react';
import {ChangeEvent, memo, useState} from 'react';
import Paper from '@mui/material/Paper';
import {
@@ -16,67 +16,61 @@ type MyProps = {
s?: pixel_mask_statistics
};
type MyState = {
file?: File
};
function PixelMask({s}: MyProps) {
const [file, setFile] = useState<File>();
class PixelMask extends React.Component<MyProps, MyState> {
state: MyState = {};
handleUpload = () => {
if (this.state.file)
putConfigUserMaskTiff({ body: this.state.file });
const handleUpload = () => {
if (file)
putConfigUserMaskTiff({ body: file });
};
handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files[0]) {
this.setState({file: event.target.files[0]});
setFile(event.target.files[0]);
}
};
render() {
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/><strong>Pixel mask</strong><br/><br/>
<Grid container spacing={0}>
<Grid item xs={1}/>
<Grid item xs={10}>
<TableContainer component={Paper} style={{marginLeft: "auto", marginRight: "auto"}}>
<Table size="small" aria-label="simple table">
<TableBody>
<TableRow>
<TableCell component="th" scope="row"> User mask: </TableCell>
<TableCell align="right">{this.props.s?.user_mask ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Error pixels: </TableCell>
<TableCell align="right">{this.props.s?.wrong_gain ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Noisy pixels: </TableCell>
<TableCell align="right">{this.props.s?.too_high_pedestal_rms ?? "N/A"}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
<Grid item xs={1}/>
return <Paper style={{textAlign: 'center'}} sx={{width: '100%'}}>
<br/><strong>Pixel mask</strong><br/><br/>
<Grid container spacing={0}>
<Grid item xs={1}/>
<Grid item xs={10}>
<TableContainer component={Paper} style={{marginLeft: "auto", marginRight: "auto"}}>
<Table size="small" aria-label="simple table">
<TableBody>
<TableRow>
<TableCell component="th" scope="row"> User mask: </TableCell>
<TableCell align="right">{s?.user_mask ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Error pixels: </TableCell>
<TableCell align="right">{s?.wrong_gain ?? "N/A"}</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row"> Noisy pixels: </TableCell>
<TableCell align="right">{s?.too_high_pedestal_rms ?? "N/A"}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
<br/>
<a href="/config/mask.tiff">Download pixel mask</a><br/>
<a href="/config/user_mask.tiff">Download user pixel mask</a><br/><br/>
<TextField
type="file"
inputProps={{accept: '.tiff'}}
onChange={this.handleFileChange}
/><br/><br/>
<Button onClick={this.handleUpload}
variant="contained"
disableElevation
disabled={!this.state.file}
color={"primary"}>Upload</Button>
<br/><br/>
</Paper>
}
<Grid item xs={1}/>
</Grid>
<br/>
<a href="/config/mask.tiff">Download pixel mask</a><br/>
<a href="/config/user_mask.tiff">Download user pixel mask</a><br/><br/>
<TextField
type="file"
inputProps={{accept: '.tiff'}}
onChange={handleFileChange}
/><br/><br/>
<Button onClick={handleUpload}
variant="contained"
disableElevation
disabled={!file}
color={"primary"}>Upload</Button>
<br/><br/>
</Paper>
}
export default PixelMask;
export default memo(PixelMask);
+327 -368
View File
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import {ChangeEvent, memo, useEffect, useRef, useState} from 'react';
import Paper from "@mui/material/Paper";
import {
Checkbox,
@@ -37,16 +37,6 @@ type MyProps = {
max_image_number: number | undefined
}
type MyState = {
settings: preview_settings,
s: Blob | null,
s_url: string | null,
update: boolean,
connection_error: boolean,
image_id_mode: string,
image_id: number
}
function handleErrors(response: Response): Response {
if (!response.ok) {
throw Error(response.statusText);
@@ -63,197 +53,35 @@ function stringToEnum(value: string): color_scale {
return enumValue || color_scale.INDIGO;
}
class PreviewImage extends Component<MyProps, MyState> {
interval: ReturnType<typeof setInterval> | undefined;
const default_preview_settings: preview_settings = {
saturation: 10,
jpeg_quality: 90,
show_spots: true,
show_roi: false,
show_user_mask: false,
show_beam_center: true,
resolution_ring: 0.5,
scale: color_scale.INDIGO,
image_id: -1,
auto_contrast: true,
res_estimate: true
};
state : MyState = {
s: null,
settings: {
saturation: 10,
jpeg_quality: 90,
show_spots: true,
show_roi: false,
show_user_mask: false,
show_beam_center: true,
resolution_ring: 0.5,
scale: color_scale.INDIGO,
image_id: -1,
auto_contrast: true,
res_estimate: true
},
s_url: null,
update: true,
connection_error: true,
image_id_mode: 'last',
image_id: 0
}
function PreviewImage({measuring, min_image_number, max_image_number}: MyProps) {
const [settings, setSettings] = useState<preview_settings>(default_preview_settings);
const [sUrl, setSUrl] = useState<string | null>(null);
const [update, setUpdate] = useState(true);
const [connectionError, setConnectionError] = useState(true);
const [imageIdMode, setImageIdMode] = useState('last');
const [imageId, setImageId] = useState(0);
update_image_id_mode = (event: React.ChangeEvent<HTMLInputElement>) => {
let image_id : number;
switch (event.target.value) {
case "last":
image_id = -1;
break;
case "last_indexed":
image_id = -2;
break;
case "select":
image_id = this.state.image_id;
break;
default:
return;
}
// Refs let the polling interval read the latest settings without being
// re-created on every settings change, and let cleanup revoke the live URL.
const sUrlRef = useRef<string | null>(null);
const settingsRef = useRef(settings);
settingsRef.current = settings;
let s : preview_settings = {...this.state.settings, image_id};
this.setState({settings: s, image_id_mode: event.target.value});
this.getValues(s);
}
updateToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
update: event.target.checked
});
if (event.target.checked)
this.getValues();
}
setSaturation = (event: Event, newValue: number | number[]) => {
let s : preview_settings = {
...this.state.settings,
saturation: newValue as number
};
this.setState({settings: s});
this.getValues(s);
}
setSaturationText = (event: React.ChangeEvent<HTMLInputElement>) => {
let newValue = 0;
if (event.target.value)
newValue = Number(event.target.value);
if (newValue < 0)
newValue = 0;
let s : preview_settings = {
...this.state.settings,
saturation: newValue as number
};
this.setState({settings: s});
this.getValues(s);
};
showSpotsToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {
...this.state.settings,
show_spots: event.target.checked
};
this.setState({settings: s});
this.getValues(s);
}
resEstToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
let s: preview_settings = {
...this.state.settings,
res_estimate: event.target.checked
}
this.setState({settings: s});
this.getValues(s);
}
autoContrastToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
let s: preview_settings = {
...this.state.settings,
auto_contrast: event.target.checked
}
this.setState({settings: s});
this.getValues(s);
}
showROIToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {
...this.state.settings,
show_roi: event.target.checked
};
this.setState({settings: s});
this.getValues(s);
}
showBeamCenterToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {
...this.state.settings,
show_beam_center: event.target.checked
};
this.setState({settings: s});
this.getValues(s);
}
setResolutionRing = (event: Event, newValue: number | number[]) => {
let s : preview_settings = {
...this.state.settings,
resolution_ring: newValue as number
};
this.setState({settings: s});
this.getValues(s);
}
setResolutionRingText = (event: React.ChangeEvent<HTMLInputElement>) => {
let newValue = 0.5;
if (event.target.value)
newValue = Number(event.target.value);
if (newValue < 0.5)
newValue = 0.5;
if (newValue > 50.0)
newValue = 50.0;
let s : preview_settings = {
...this.state.settings,
resolution_ring: newValue as number
};
this.setState({settings: s});
this.getValues(s);
};
setImageID = (event: Event, newValue: number | number[]) => {
if (this.state.image_id_mode == "select") {
let s : preview_settings = {
...this.state.settings,
image_id: newValue as number
};
this.setState({settings: s, image_id: newValue as number});
this.getValues(s);
} else {
this.setState({image_id: newValue as number});
}
}
setImageIdText = (event: React.ChangeEvent<HTMLInputElement>) => {
let newValue = 0;
if (event.target.value)
newValue = Number(event.target.value);
if (newValue < 0)
newValue = 0;
if (this.state.image_id_mode == "select") {
let s : preview_settings = {
...this.state.settings,
image_id: newValue as number
};
this.setState({settings: s, image_id: newValue as number});
this.getValues(s);
} else {
this.setState({image_id: newValue as number});
}
};
showUserMaskToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {
...this.state.settings,
show_user_mask: event.target.checked
};
this.setState({settings: s});
this.getValues(s);
}
getImage(s: preview_settings,) {
const getImage = (s: preview_settings) => {
let url = `/image_buffer/image.jpeg`;
url += `?id=${s.image_id}`;
if (!s.auto_contrast)
@@ -273,190 +101,321 @@ class PreviewImage extends Component<MyProps, MyState> {
.then(handleErrors)
.then(data => data.blob())
.then(data => {
const url = URL.createObjectURL(data);
let tmp = this.state.s_url;
this.setState({s: data, s_url: url, connection_error: false});
const objectUrl = URL.createObjectURL(data);
const tmp = sUrlRef.current;
sUrlRef.current = objectUrl;
setSUrl(objectUrl);
setConnectionError(false);
if (tmp !== null)
URL.revokeObjectURL(tmp);
}).catch(error => {
this.setState({connection_error: true});
setConnectionError(true);
})
}
};
getValues(s: preview_settings = this.state.settings) {
this.getImage(s);
}
const getValues = (s: preview_settings = settings) => {
getImage(s);
};
componentDidMount() {
this.getValues();
this.interval = setInterval(() => {
if (this.state.update && this.props.measuring)
this.getValues()
// Initial fetch + revoke the live object URL on unmount.
useEffect(() => {
getImage(settingsRef.current);
return () => {
if (sUrlRef.current !== null)
URL.revokeObjectURL(sUrlRef.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Auto-update poll; reads the latest settings via ref.
useEffect(() => {
const id = setInterval(() => {
if (update && measuring)
getImage(settingsRef.current);
}, 2000);
}
return () => clearInterval(id);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [update, measuring]);
componentWillUnmount() {
clearInterval(this.interval);
let tmp = this.state.s_url;
if (tmp !== null)
URL.revokeObjectURL(tmp);
}
const update_image_id_mode = (event: ChangeEvent<HTMLInputElement>) => {
let image_id : number;
switch (event.target.value) {
case "last":
image_id = -1;
break;
case "last_indexed":
image_id = -2;
break;
case "select":
image_id = imageId;
break;
default:
return;
}
render() {
return <div>
let s : preview_settings = {...settings, image_id};
setSettings(s);
setImageIdMode(event.target.value);
getValues(s);
};
<Grid container spacing={3}>
const updateToggle = (event: ChangeEvent<HTMLInputElement>) => {
setUpdate(event.target.checked);
if (event.target.checked)
getValues();
};
<Grid item xs={8}>
<Paper sx={{height: 1250, minWidth: 1200} }>
{(!this.state.connection_error && (this.state.s_url !== null)) ?
<Stack
direction="row"
justifyContent="center"
alignItems="center"
>
<TransformWrapper maxScale={32}>
<TransformComponent>
<img src={this.state.s_url} alt="Live preview"
style={{maxWidth: "100%", maxHeight: 900}}/>
</TransformComponent>
</TransformWrapper>
</Stack> : <div>Preview image not available</div>
}
</Paper>
</Grid>
<Grid item xs={4}>
<Paper sx={{width: "100%", height: "100%"}}>
<br/>
<Stack spacing={2} direction="column" sx={{
justifyContent: "center",
alignItems: "center"}} alignItems="center">
<div><strong>Preview image selection </strong></div>
<FormControl sx={{width:"83%"}}>
<RadioGroup value={this.state.image_id_mode} onChange={this.update_image_id_mode}>
<FormControlLabel value="last" control={<Radio />} label="Last image" />
<FormControlLabel value="last_indexed" control={<Radio />} label="Last indexed image" />
<FormControlLabel value="select" control={<Radio />} label="Choose image" />
</RadioGroup>
</FormControl>
<Stack spacing={3} direction="row" sx={{width:"83%"}}>
<Slider
value={this.state.image_id}
min={this.props.min_image_number ?? 0}
max={this.props.max_image_number ?? 0}
step={1}
onChange={this.setImageID}
disabled={this.state.image_id_mode !== "select"}
valueLabelDisplay="auto"/>
<Input
value={this.state.image_id}
onChange={this.setImageIdText}
disabled={this.state.image_id_mode !== "select"}
inputProps={{
step: 1,
min: this.props.min_image_number ?? 0,
max: this.props.max_image_number ?? 0,
type: 'number',
}}
/>
</Stack>
<div><strong>Preview image settings </strong></div>
<FormControl sx = {{width: "83%"}}>
const setSaturation = (event: Event, newValue: number | number[]) => {
let s : preview_settings = {...settings, saturation: newValue as number};
setSettings(s);
getValues(s);
};
<FormControlLabel control={
<Checkbox checked={this.state.update} onChange={this.updateToggle} name="Update"/>
} label="Auto-update image"/>
<FormControlLabel control={
<Checkbox checked={this.state.settings.auto_contrast} onChange={this.autoContrastToggle} name="Auto-contrast"/>
} label="Auto-contrast"/>
<FormControlLabel control={
<Checkbox checked={this.state.settings.show_spots} onChange={this.showSpotsToggle} name="Show spots"/>
} label="Show spots"/>
<FormControlLabel control={
<Checkbox checked={this.state.settings.show_roi} onChange={this.showROIToggle} name="Show ROI"/>
} label="Show ROI"/>
<FormControlLabel control={
<Checkbox checked={this.state.settings.res_estimate} onChange={this.resEstToggle} name="Show resolution estimation (auto ring)"/>
} label="Show resolution estimate (auto ring)"/>
<FormControlLabel control={
<Checkbox checked={this.state.settings.show_beam_center} onChange={this.showBeamCenterToggle} name="Show beam center"/>
} label="Show beam center"/>
<FormControlLabel control={
<Checkbox checked={this.state.settings.show_user_mask} onChange={this.showUserMaskToggle} name="Show user mask"/>
} label="Show user mask"/>
</FormControl>
<FormControl sx = {{width: "83%"}}>
<InputLabel id="color-map">Color map</InputLabel>
<Select
value={this.state.settings.scale}
variant="outlined"
label="Color map"
onChange={(event) => {
let s : preview_settings = {
...this.state.settings,
scale: stringToEnum(event.target.value)
};
this.setState({settings: s});
this.getValues(s);
}}
fullWidth
>
<MenuItem value={color_scale.INDIGO}>Indigo/white</MenuItem>
<MenuItem value={color_scale.HEAT}>Heat</MenuItem>
<MenuItem value={color_scale.VIRIDIS}>Viridis</MenuItem>
<MenuItem value={color_scale.MAGMA}>Magma</MenuItem>
<MenuItem value={color_scale.INFERNO}>Inferno</MenuItem>
<MenuItem value={color_scale.BW}>Black/white</MenuItem>
<MenuItem value={color_scale.WB}>White/black</MenuItem>
<MenuItem value={color_scale.GREEN}>Green</MenuItem>
</Select>
</FormControl>
<Stack spacing={3} direction="row" sx={{width:"83%"}}>
<Slider value={Number(this.state.settings.saturation)} min={1} max={200}
onChange={this.setSaturation}
disabled={this.state.settings.auto_contrast}
valueLabelDisplay="auto"/>
const setSaturationText = (event: ChangeEvent<HTMLInputElement>) => {
let newValue = 0;
if (event.target.value)
newValue = Number(event.target.value);
if (newValue < 0)
newValue = 0;
<Input
value={this.state.settings.saturation}
onChange={this.setSaturationText}
disabled={this.state.settings.auto_contrast}
inputProps={{
step: 1,
min: 0,
max: 200,
type: 'number',
}}
/>
</Stack><br/>Saturation level
let s : preview_settings = {...settings, saturation: newValue as number};
setSettings(s);
getValues(s);
};
<Stack spacing={3} direction="row" sx={{width:"83%"}}>
<Slider
value={(this.state.settings.resolution_ring === undefined) ? 0.5 : Number(this.state.settings.resolution_ring)}
min={0.5} max={10.0} step={0.1}
onChange={this.setResolutionRing} valueLabelDisplay="auto"
disabled={this.state.settings.res_estimate}/>
<Input
value={this.state.settings.resolution_ring}
disabled={this.state.settings.res_estimate}
onChange={this.setResolutionRingText}
endAdornment="&nbsp;&nbsp;Å"
inputProps={{
step: 0.1,
min: 0.5,
max: 50.0,
type: 'number',
}}
/>
</Stack>
<br/>Resolution Ring
</Stack>
</Paper>
</Grid>
const showSpotsToggle = (event: ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {...settings, show_spots: event.target.checked};
setSettings(s);
getValues(s);
};
const resEstToggle = (event: ChangeEvent<HTMLInputElement>) => {
let s: preview_settings = {...settings, res_estimate: event.target.checked};
setSettings(s);
getValues(s);
};
const autoContrastToggle = (event: ChangeEvent<HTMLInputElement>) => {
let s: preview_settings = {...settings, auto_contrast: event.target.checked};
setSettings(s);
getValues(s);
};
const showROIToggle = (event: ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {...settings, show_roi: event.target.checked};
setSettings(s);
getValues(s);
};
const showBeamCenterToggle = (event: ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {...settings, show_beam_center: event.target.checked};
setSettings(s);
getValues(s);
};
const setResolutionRing = (event: Event, newValue: number | number[]) => {
let s : preview_settings = {...settings, resolution_ring: newValue as number};
setSettings(s);
getValues(s);
};
const setResolutionRingText = (event: ChangeEvent<HTMLInputElement>) => {
let newValue = 0.5;
if (event.target.value)
newValue = Number(event.target.value);
if (newValue < 0.5)
newValue = 0.5;
if (newValue > 50.0)
newValue = 50.0;
let s : preview_settings = {...settings, resolution_ring: newValue as number};
setSettings(s);
getValues(s);
};
const setImageIDSlider = (event: Event, newValue: number | number[]) => {
if (imageIdMode == "select") {
let s : preview_settings = {...settings, image_id: newValue as number};
setSettings(s);
setImageId(newValue as number);
getValues(s);
} else {
setImageId(newValue as number);
}
};
const setImageIdText = (event: ChangeEvent<HTMLInputElement>) => {
let newValue = 0;
if (event.target.value)
newValue = Number(event.target.value);
if (newValue < 0)
newValue = 0;
if (imageIdMode == "select") {
let s : preview_settings = {...settings, image_id: newValue as number};
setSettings(s);
setImageId(newValue as number);
getValues(s);
} else {
setImageId(newValue as number);
}
};
const showUserMaskToggle = (event: ChangeEvent<HTMLInputElement>) => {
let s : preview_settings = {...settings, show_user_mask: event.target.checked};
setSettings(s);
getValues(s);
};
return <div>
<Grid container spacing={3}>
<Grid item xs={8}>
<Paper sx={{height: 1250, minWidth: 1200} }>
{(!connectionError && (sUrl !== null)) ?
<Stack
direction="row"
justifyContent="center"
alignItems="center"
>
<TransformWrapper maxScale={32}>
<TransformComponent>
<img src={sUrl} alt="Live preview"
style={{maxWidth: "100%", maxHeight: 900}}/>
</TransformComponent>
</TransformWrapper>
</Stack> : <div>Preview image not available</div>
}
</Paper>
</Grid>
</div>
<Grid item xs={4}>
<Paper sx={{width: "100%", height: "100%"}}>
<br/>
<Stack spacing={2} direction="column" sx={{
justifyContent: "center",
alignItems: "center"}} alignItems="center">
<div><strong>Preview image selection </strong></div>
<FormControl sx={{width:"83%"}}>
<RadioGroup value={imageIdMode} onChange={update_image_id_mode}>
<FormControlLabel value="last" control={<Radio />} label="Last image" />
<FormControlLabel value="last_indexed" control={<Radio />} label="Last indexed image" />
<FormControlLabel value="select" control={<Radio />} label="Choose image" />
</RadioGroup>
</FormControl>
<Stack spacing={3} direction="row" sx={{width:"83%"}}>
<Slider
value={imageId}
min={min_image_number ?? 0}
max={max_image_number ?? 0}
step={1}
onChange={setImageIDSlider}
disabled={imageIdMode !== "select"}
valueLabelDisplay="auto"/>
<Input
value={imageId}
onChange={setImageIdText}
disabled={imageIdMode !== "select"}
inputProps={{
step: 1,
min: min_image_number ?? 0,
max: max_image_number ?? 0,
type: 'number',
}}
/>
</Stack>
<div><strong>Preview image settings </strong></div>
<FormControl sx = {{width: "83%"}}>
}
<FormControlLabel control={
<Checkbox checked={update} onChange={updateToggle} name="Update"/>
} label="Auto-update image"/>
<FormControlLabel control={
<Checkbox checked={settings.auto_contrast} onChange={autoContrastToggle} name="Auto-contrast"/>
} label="Auto-contrast"/>
<FormControlLabel control={
<Checkbox checked={settings.show_spots} onChange={showSpotsToggle} name="Show spots"/>
} label="Show spots"/>
<FormControlLabel control={
<Checkbox checked={settings.show_roi} onChange={showROIToggle} name="Show ROI"/>
} label="Show ROI"/>
<FormControlLabel control={
<Checkbox checked={settings.res_estimate} onChange={resEstToggle} name="Show resolution estimation (auto ring)"/>
} label="Show resolution estimate (auto ring)"/>
<FormControlLabel control={
<Checkbox checked={settings.show_beam_center} onChange={showBeamCenterToggle} name="Show beam center"/>
} label="Show beam center"/>
<FormControlLabel control={
<Checkbox checked={settings.show_user_mask} onChange={showUserMaskToggle} name="Show user mask"/>
} label="Show user mask"/>
</FormControl>
<FormControl sx = {{width: "83%"}}>
<InputLabel id="color-map">Color map</InputLabel>
<Select
value={settings.scale}
variant="outlined"
label="Color map"
onChange={(event) => {
let s : preview_settings = {...settings, scale: stringToEnum(event.target.value)};
setSettings(s);
getValues(s);
}}
fullWidth
>
<MenuItem value={color_scale.INDIGO}>Indigo/white</MenuItem>
<MenuItem value={color_scale.HEAT}>Heat</MenuItem>
<MenuItem value={color_scale.VIRIDIS}>Viridis</MenuItem>
<MenuItem value={color_scale.MAGMA}>Magma</MenuItem>
<MenuItem value={color_scale.INFERNO}>Inferno</MenuItem>
<MenuItem value={color_scale.BW}>Black/white</MenuItem>
<MenuItem value={color_scale.WB}>White/black</MenuItem>
<MenuItem value={color_scale.GREEN}>Green</MenuItem>
</Select>
</FormControl>
<Stack spacing={3} direction="row" sx={{width:"83%"}}>
<Slider value={Number(settings.saturation)} min={1} max={200}
onChange={setSaturation}
disabled={settings.auto_contrast}
valueLabelDisplay="auto"/>
<Input
value={settings.saturation}
onChange={setSaturationText}
disabled={settings.auto_contrast}
inputProps={{
step: 1,
min: 0,
max: 200,
type: 'number',
}}
/>
</Stack><br/>Saturation level
<Stack spacing={3} direction="row" sx={{width:"83%"}}>
<Slider
value={(settings.resolution_ring === undefined) ? 0.5 : Number(settings.resolution_ring)}
min={0.5} max={10.0} step={0.1}
onChange={setResolutionRing} valueLabelDisplay="auto"
disabled={settings.res_estimate}/>
<Input
value={settings.resolution_ring}
disabled={settings.res_estimate}
onChange={setResolutionRingText}
endAdornment="&nbsp;&nbsp;Å"
inputProps={{
step: 0.1,
min: 0.5,
max: 50.0,
type: 'number',
}}
/>
</Stack>
<br/>Resolution Ring
</Stack>
</Paper>
</Grid>
</Grid>
</div>
}
export default PreviewImage;
export default memo(PreviewImage);
+224 -269
View File
@@ -1,10 +1,8 @@
import React from 'react';
import {memo, ReactNode, useEffect, useRef, useState} from 'react';
import Paper from '@mui/material/Paper';
import {
putConfigRoi,
instrument_metadata,
pixel_mask_statistics, roi_azimuthal,
roi_azimuthal,
roi_box,
roi_circle,
roi_definitions
@@ -16,7 +14,6 @@ import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import ErrorIcon from '@mui/icons-material/Error';
import ButtonWithSnackbar from "./ButtonWithSnackbar";
import _ from "lodash";
import { ReactNode } from "react";
type MyProps = {
s?: roi_definitions
@@ -26,247 +23,214 @@ type BoxWrapper = roi_box & {id: number};
type CircleWrapper = roi_circle & {id: number};
type AzimWrapper = roi_azimuthal & {id: number};
type MyState = {
last_downloaded_s: roi_definitions;
box: BoxWrapper[];
circle: CircleWrapper[];
azim: AzimWrapper[];
box_err?: string;
circle_err?: string;
azim_err?: string | ReactNode;
};
function getRandomInt(max : number) {
return Math.floor(Math.random() * max);
}
class ROI extends React.Component<MyProps, MyState> {
counter: number = 0;
function render_err(msg?: string | ReactNode) : ReactNode {
if (msg === undefined)
return <div><br/><br/></div>
return <div><br/><Typography color="error"><ErrorIcon color="error"/>&nbsp;{msg}</Typography><br/></div>;
}
state : MyState = {
last_downloaded_s: {
box: {rois: []},
circle: {rois: []},
azim: {rois: []}
},
box: [],
circle: [],
azim: []
}
const default_roi_definitions: roi_definitions = {
box: {rois: []},
circle: {rois: []},
azim: {rois: []}
};
getValues () {
if (this.props.s !== undefined) {
let tmp: roi_definitions = this.props.s;
if (!_.isEqual(tmp, this.state.last_downloaded_s))
this.setState({
circle_err: undefined,
circle: (this.props.s.circle.rois === undefined) ? [] :
this.props.s.circle.rois.map(
(elem) : CircleWrapper => {return {id: this.counter++, ...elem}}
),
box_err: undefined,
box: (this.props.s.box.rois === undefined) ? [] :
this.props.s.box.rois.map(
(elem) : BoxWrapper => {return {id: this.counter++, ...elem}}
),
azim: (this.props.s.azim.rois === undefined) ? [] :
this.props.s.azim.rois.map(
(elem) : AzimWrapper => {return {id: this.counter++, ...elem}}
),
azim_err: undefined,
last_downloaded_s: tmp
});
function ROI({s: serverS}: MyProps) {
const counter = useRef(0);
const [lastDownloadedS, setLastDownloadedS] = useState<roi_definitions>(default_roi_definitions);
const [box, setBox] = useState<BoxWrapper[]>([]);
const [circle, setCircle] = useState<CircleWrapper[]>([]);
const [azim, setAzim] = useState<AzimWrapper[]>([]);
const [boxErr, setBoxErr] = useState<string>();
const [circleErr, setCircleErr] = useState<string>();
const [azimErr, setAzimErr] = useState<string | ReactNode>();
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
setCircleErr(undefined);
setCircle((serverS.circle.rois === undefined) ? [] :
serverS.circle.rois.map((elem) : CircleWrapper => ({id: counter.current++, ...elem})));
setBoxErr(undefined);
setBox((serverS.box.rois === undefined) ? [] :
serverS.box.rois.map((elem) : BoxWrapper => ({id: counter.current++, ...elem})));
setAzim((serverS.azim.rois === undefined) ? [] :
serverS.azim.rois.map((elem) : AzimWrapper => ({id: counter.current++, ...elem})));
setAzimErr(undefined);
setLastDownloadedS(serverS);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
componentDidUpdate() {
this.getValues();
}
checkDuplicatesBox(input : BoxWrapper[]) {
const checkDuplicatesBox = (input : BoxWrapper[]) => {
let roi_names : string[] = [];
input.map(element => roi_names.push(element.name));
this.state.circle.map(element => roi_names.push(element.name));
this.state.azim.map(element => roi_names.push(element.name));
circle.map(element => roi_names.push(element.name));
azim.map(element => roi_names.push(element.name));
const duplicates = roi_names.filter((item, index) => roi_names.indexOf(item) !== index);
return (duplicates.length !== 0);
}
};
checkDuplicatesCircle(input : CircleWrapper[]) {
const checkDuplicatesCircle = (input : CircleWrapper[]) => {
let roi_names : string[] = [];
input.map(element => roi_names.push(element.name));
this.state.box.map(element => roi_names.push(element.name));
this.state.azim.map(element => roi_names.push(element.name));
box.map(element => roi_names.push(element.name));
azim.map(element => roi_names.push(element.name));
const duplicates = roi_names.filter((item, index) => roi_names.indexOf(item) !== index);
return (duplicates.length !== 0);
}
};
checkDuplicatesAzim(input : AzimWrapper[]) {
const checkDuplicatesAzim = (input : AzimWrapper[]) => {
let roi_names : string[] = [];
input.map(element => roi_names.push(element.name));
this.state.box?.map(element => roi_names.push(element.name));
this.state.circle.map(element => roi_names.push(element.name));
box?.map(element => roi_names.push(element.name));
circle.map(element => roi_names.push(element.name));
const duplicates = roi_names.filter((item, index) => roi_names.indexOf(item) !== index);
return (duplicates.length !== 0);
}
};
validateBoxInteger(input: BoxWrapper[]) {
return input.every((x) : boolean =>
const validateBoxInteger = (input: BoxWrapper[]) =>
input.every((x) : boolean =>
Number.isInteger(x.min_x_pxl) && Number.isInteger(x.max_x_pxl) &&
Number.isInteger(x.min_y_pxl) && Number.isInteger(x.max_y_pxl));
}
validateBoxBounds(input: BoxWrapper[]) {
return input.every((x) : boolean =>
const validateBoxBounds = (input: BoxWrapper[]) =>
input.every((x) : boolean =>
(x.min_x_pxl >= 0) && (x.max_x_pxl >= x.min_x_pxl) &&
(x.min_y_pxl >= 0) && (x.max_y_pxl >= x.min_y_pxl));
}
validateCircleValues(input: CircleWrapper[]) {
return input.every((x) : boolean => x.radius_pxl >= 0);
}
const validateCircleValues = (input: CircleWrapper[]) =>
input.every((x) : boolean => x.radius_pxl >= 0);
validateAzimValues(input: AzimWrapper[]) {
return input.every((x) : boolean => x.q_min_recipA >= 0 && x.q_max_recipA > x.q_min_recipA);
}
const validateAzimValues = (input: AzimWrapper[]) =>
input.every((x) : boolean => x.q_min_recipA >= 0 && x.q_max_recipA > x.q_min_recipA);
validateBox (input:BoxWrapper[]) : undefined | string {
if (this.checkDuplicatesBox(input))
const validateBox = (input:BoxWrapper[]) : undefined | string => {
if (checkDuplicatesBox(input))
return "Duplicate ROI names.";
if (!this.validateBoxInteger(input))
if (!validateBoxInteger(input))
return "Box bounds are not integer values.";
if (!this.validateBoxBounds(input))
if (!validateBoxBounds(input))
return "Box bounds are not correct (0 <= min <= max)";
return undefined;
}
};
validateCircle (input:CircleWrapper[]) : undefined | string {
if (this.checkDuplicatesCircle(input))
const validateCircle = (input:CircleWrapper[]) : undefined | string => {
if (checkDuplicatesCircle(input))
return "Duplicate ROI names.";
if (!this.validateCircleValues(input))
if (!validateCircleValues(input))
return "Circle ROI radius must be positive.";
return undefined;
}
};
validateAzim (input:AzimWrapper[]) : undefined | string | ReactNode {
if (this.checkDuplicatesAzim(input))
const validateAzim = (input:AzimWrapper[]) : undefined | string | ReactNode => {
if (checkDuplicatesAzim(input))
return "Duplicate ROI names.";
if (!this.validateAzimValues(input))
if (!validateAzimValues(input))
return <div>Azimuthal integration parameters must be 0 &lt; Q<sub>min</sub> &lt; Q<sub>max</sub>.</div>;
return undefined;
}
};
uploadButton = () => { this.putValues(); }
downloadButton = () => { this.getValues(); }
addBoxButton = () => {
this.setState({
box: [...this.state.box,
{
id: this.counter++,
name: "roi" + getRandomInt(99999),
min_x_pxl: 0,
max_x_pxl: 0,
min_y_pxl: 0,
max_y_pxl: 0
}
]
});
}
addCircleButton = () => {
this.setState({
circle: [
...this.state.circle,
{
id: this.counter++,
name: "roi" + getRandomInt(65536),
center_x_pxl: 0,
center_y_pxl: 0,
radius_pxl: 10
}
]
});
}
addAzimButton = () => {
this.setState({
azim: [
...this.state.azim,
{
id: this.counter++,
name: "roi" + getRandomInt(65536),
q_min_recipA: 1,
q_max_recipA: 3
}
]
});
}
roiStruct = () : roi_definitions => {
return {
box: {
rois: this.state.box.map((elem): roi_box => elem)
},
circle: {
rois: this.state.circle.map((elem): roi_circle => elem)
},
azim: {
rois: this.state.azim.map((elem): roi_azimuthal => elem)
const addBoxButton = () => {
setBox([...box,
{
id: counter.current++,
name: "roi" + getRandomInt(99999),
min_x_pxl: 0,
max_x_pxl: 0,
min_y_pxl: 0,
max_y_pxl: 0
}
]);
};
const addCircleButton = () => {
setCircle([
...circle,
{
id: counter.current++,
name: "roi" + getRandomInt(65536),
center_x_pxl: 0,
center_y_pxl: 0,
radius_pxl: 10
}
]);
};
const addAzimButton = () => {
setAzim([
...azim,
{
id: counter.current++,
name: "roi" + getRandomInt(65536),
q_min_recipA: 1,
q_max_recipA: 3
}
]);
};
const roiStruct = () : roi_definitions => ({
box: {
rois: box.map((elem): roi_box => elem)
},
circle: {
rois: circle.map((elem): roi_circle => elem)
},
azim: {
rois: azim.map((elem): roi_azimuthal => elem)
}
}
});
putValues = () => {
putConfigRoi({ body: this.roiStruct(), throwOnError: true }).catch(error => {} );
}
handleDeleteBoxROIClick = (id: GridRowModel) => () => {
let new_state : BoxWrapper[] = this.state.box.filter((row) => row.id !== id.id);
let err = this.validateBox(new_state);
this.setState({box_err: err, box: new_state});
const handleDeleteBoxROIClick = (id: GridRowModel) => () => {
let new_state : BoxWrapper[] = box.filter((row) => row.id !== id.id);
let err = validateBox(new_state);
setBoxErr(err);
setBox(new_state);
};
handleDeleteCircleROIClick = (id: GridRowModel) => () => {
let new_state : CircleWrapper[] = this.state.circle.filter((row) => row.id !== id.id);
let err = this.validateCircle(new_state);
this.setState({circle_err: err, circle: new_state});
const handleDeleteCircleROIClick = (id: GridRowModel) => () => {
let new_state : CircleWrapper[] = circle.filter((row) => row.id !== id.id);
let err = validateCircle(new_state);
setCircleErr(err);
setCircle(new_state);
};
handleDeleteAzimROIClick = (id: GridRowModel) => () => {
let new_state : AzimWrapper[] = this.state.azim.filter((row) => row.id !== id.id);
let err = this.validateAzim(new_state);
this.setState({azim_err: err, azim: new_state});
const handleDeleteAzimROIClick = (id: GridRowModel) => () => {
let new_state : AzimWrapper[] = azim.filter((row) => row.id !== id.id);
let err = validateAzim(new_state);
setAzimErr(err);
setAzim(new_state);
};
processRowBoxUpdate = (newRow: BoxWrapper, oldRow: BoxWrapper) => {
let new_state = this.state.box.map((item) => item.id === oldRow.id ? newRow : item);
let err = this.validateBox(new_state);
this.setState({ box_err: err, box: new_state });
const processRowBoxUpdate = (newRow: BoxWrapper, oldRow: BoxWrapper) => {
let new_state = box.map((item) => item.id === oldRow.id ? newRow : item);
let err = validateBox(new_state);
setBoxErr(err);
setBox(new_state);
return newRow;
};
processRowCircleUpdate = (newRow: CircleWrapper, oldRow: CircleWrapper) => {
let new_state = this.state.circle.map((item) => item === oldRow ? newRow : item);
let err = this.validateCircle(new_state);
this.setState({ circle_err: err, circle: new_state });
const processRowCircleUpdate = (newRow: CircleWrapper, oldRow: CircleWrapper) => {
let new_state = circle.map((item) => item === oldRow ? newRow : item);
let err = validateCircle(new_state);
setCircleErr(err);
setCircle(new_state);
return newRow;
};
processRowAzimUpdate = (newRow: AzimWrapper, oldRow: AzimWrapper) => {
let new_state = this.state.azim.map((item) => item === oldRow ? newRow : item);
let err = this.validateAzim(new_state);
this.setState({ azim_err: err, azim: new_state });
const processRowAzimUpdate = (newRow: AzimWrapper, oldRow: AzimWrapper) => {
let new_state = azim.map((item) => item === oldRow ? newRow : item);
let err = validateAzim(new_state);
setAzimErr(err);
setAzim(new_state);
return newRow;
};
columns_box : GridColDef[] = [
const columns_box : GridColDef[] = [
{ field: 'name', type: 'string', headerName: 'Name' , editable: true, width: 200},
{ field: 'min_x_pxl', type: 'number', headerName: 'Min X', editable: true},
{ field: 'max_x_pxl', type: 'number', headerName: 'Max X', editable: true},
@@ -278,14 +242,14 @@ class ROI extends React.Component<MyProps, MyState> {
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={this.handleDeleteBoxROIClick(id)}
onClick={handleDeleteBoxROIClick(id)}
color="inherit"
/>,
];
}}
];
columns_circle : GridColDef[] = [
const columns_circle : GridColDef[] = [
{ field: 'name', type: 'string', headerName: 'Name' , editable: true, width: 200},
{ field: 'center_x_pxl', type: 'number', headerName: 'X [pxl]', editable: true},
{ field: 'center_y_pxl', type: 'number', headerName: 'Y [pxl]', editable: true},
@@ -296,14 +260,14 @@ class ROI extends React.Component<MyProps, MyState> {
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={this.handleDeleteCircleROIClick(id)}
onClick={handleDeleteCircleROIClick(id)}
color="inherit"
/>,
];
}}
];
columns_azim : GridColDef[] = [
const columns_azim : GridColDef[] = [
{ field: 'name', type: 'string', headerName: 'Name' , editable: true, width: 200},
{ field: 'q_min_recipA', type: 'number', editable: true,
renderHeader: () => (<div>Q<sub>min</sub> [Ź]</div>) },
@@ -357,94 +321,85 @@ class ROI extends React.Component<MyProps, MyState> {
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={this.handleDeleteAzimROIClick(id)}
onClick={handleDeleteAzimROIClick(id)}
color="inherit"
/>,
];
}}
];
render_err(msg?: string | ReactNode) : ReactNode {
if (msg === undefined)
return <div><br/><br/></div>
return <div><br/><Typography color="error"><ErrorIcon color="error"/>&nbsp;{msg}</Typography><br/></div>;
}
render() {
return <Paper style={{textAlign: 'center'}} sx={{ height: 530, width: '100%' }}>
<br/>
<Grid container spacing={0}>
<Grid item xs={0.25}/>
<Grid item xs={3.5} >
<strong>Box ROI</strong>
<br/><br/><br/>
<Paper sx={{height: 300, width: '100%'}} elevation={0}>
<DataGrid
getRowId={row => row.id}
rows={this.state.box}
columns={this.columns_box}
editMode={"row"}
processRowUpdate={this.processRowBoxUpdate}
/>
{this.render_err(this.state.box_err)}
</Paper>
</Grid>
<Grid item xs={0.5}/>
<Grid item xs={3.5}>
<strong>Radial ROI</strong>
<br/><br/><br/>
<Paper sx={{height: 300, width: '100%'}} elevation={0}>
<DataGrid
getRowId={row => row.id}
rows={this.state.circle}
columns={this.columns_circle}
editMode={"row"}
processRowUpdate={this.processRowCircleUpdate}
/>{this.render_err(this.state.circle_err)}
</Paper>
</Grid>
<Grid item xs={0.5}/>
<Grid item xs={3.5}>
<strong>Azimuthal ROI</strong>
<br/><br/><br/>
<Paper sx={{height: 300, width: '100%'}} elevation={0}>
<DataGrid
getRowId={row => row.id}
rows={this.state.azim}
columns={this.columns_azim}
editMode={"row"}
processRowUpdate={this.processRowAzimUpdate}
/>{this.render_err(this.state.azim_err)}
</Paper>
</Grid>
<Grid item xs={0.25}/>
<Grid item xs={1}/>
<Grid item xs={10}><br/><br/><br/>
<Button color="secondary" onClick={this.addBoxButton} variant="contained"
disableElevation>Add Box ROI</Button>&nbsp;&nbsp;
<Button color="secondary" onClick={this.addCircleButton} variant="contained"
disableElevation>Add Circle ROI</Button>&nbsp;&nbsp;
<Button color="secondary" onClick={this.addAzimButton} variant="contained"
disableElevation>Add Azimuthal ROI</Button>&nbsp;&nbsp;
<ButtonWithSnackbar
path={"/config/roi"}
color={"primary"}
method={"PUT"}
disabled={(this.state.box_err !== undefined) || (this.state.circle_err !== undefined)}
text={"Upload"}
input={JSON.stringify(this.roiStruct())}
return <Paper style={{textAlign: 'center'}} sx={{ height: 530, width: '100%' }}>
<br/>
<Grid container spacing={0}>
<Grid item xs={0.25}/>
<Grid item xs={3.5} >
<strong>Box ROI</strong>
<br/><br/><br/>
<Paper sx={{height: 300, width: '100%'}} elevation={0}>
<DataGrid
getRowId={row => row.id}
rows={box}
columns={columns_box}
editMode={"row"}
processRowUpdate={processRowBoxUpdate}
/>
<br/><br/>
</Grid>
<Grid item xs={1}/>
{render_err(boxErr)}
</Paper>
</Grid>
</Paper>
}
<Grid item xs={0.5}/>
<Grid item xs={3.5}>
<strong>Radial ROI</strong>
<br/><br/><br/>
<Paper sx={{height: 300, width: '100%'}} elevation={0}>
<DataGrid
getRowId={row => row.id}
rows={circle}
columns={columns_circle}
editMode={"row"}
processRowUpdate={processRowCircleUpdate}
/>{render_err(circleErr)}
</Paper>
</Grid>
<Grid item xs={0.5}/>
<Grid item xs={3.5}>
<strong>Azimuthal ROI</strong>
<br/><br/><br/>
<Paper sx={{height: 300, width: '100%'}} elevation={0}>
<DataGrid
getRowId={row => row.id}
rows={azim}
columns={columns_azim}
editMode={"row"}
processRowUpdate={processRowAzimUpdate}
/>{render_err(azimErr)}
</Paper>
</Grid>
<Grid item xs={0.25}/>
<Grid item xs={1}/>
<Grid item xs={10}><br/><br/><br/>
<Button color="secondary" onClick={addBoxButton} variant="contained"
disableElevation>Add Box ROI</Button>&nbsp;&nbsp;
<Button color="secondary" onClick={addCircleButton} variant="contained"
disableElevation>Add Circle ROI</Button>&nbsp;&nbsp;
<Button color="secondary" onClick={addAzimButton} variant="contained"
disableElevation>Add Azimuthal ROI</Button>&nbsp;&nbsp;
<ButtonWithSnackbar
path={"/config/roi"}
color={"primary"}
method={"PUT"}
disabled={(boxErr !== undefined) || (circleErr !== undefined)}
text={"Upload"}
input={JSON.stringify(roiStruct())}
/>
<br/><br/>
</Grid>
<Grid item xs={1}/>
</Grid>
</Paper>
}
export default ROI;
export default memo(ROI);
+36 -42
View File
@@ -1,18 +1,15 @@
import React, {Component} from 'react';
import {memo, ReactNode} from 'react';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import {broker_status} from "../client";
import ButtonWithSnackbar from "./ButtonWithSnackbar";
import { ReactNode } from "react";
type MyProps = {
s?: broker_status
}
type MyState = {}
function FormatNumber(x: number) : string {
if (x === undefined)
return "";
@@ -22,50 +19,47 @@ function FormatNumber(x: number) : string {
return x.toFixed(1);
}
class StatusBar extends Component<MyProps, MyState> {
statusDescription() : ReactNode {
if (this.props.s === undefined)
function StatusBar({s}: MyProps) {
const statusDescription = (): ReactNode => {
if (s === undefined)
return <>Not connected</>;
else
return <div>
State: {this.props.s.state.toString()}
{(this.props.s.progress !== undefined) ? " (" + FormatNumber(this.props.s.progress * 100.0) + " %)" : ""}
State: {s.state.toString()}
{(s.progress !== undefined) ? " (" + FormatNumber(s.progress * 100.0) + " %)" : ""}
</div>
}
};
render() {
return <AppBar>
<Toolbar>
<Typography variant="h6" style={{flexGrow: 0.5}}>
PSI Jungfraujoch
</Typography>
return <AppBar>
<Toolbar>
<Typography variant="h6" style={{flexGrow: 0.5}}>
PSI Jungfraujoch
</Typography>
<Typography variant="h6" style={{flexGrow: 2.0}}>
{this.statusDescription()}
</Typography>
<Typography variant="h6" style={{flexGrow: 2.0}}>
{statusDescription()}
</Typography>
<Typography variant="h6" style={{flexGrow: 2.0}}>
</Typography>
<ButtonWithSnackbar
text={"Pedestal"}
path={"/pedestal"}
color={"secondary"}
disabled={(this.props.s === undefined) || (this.props.s.state !== 'Idle')}
/>&nbsp;&nbsp;
<ButtonWithSnackbar
text={"Cancel"}
path={"/cancel"}
color={"secondary"}
/>&nbsp;&nbsp;
<ButtonWithSnackbar
text={"Initialize"}
path={"/initialize"}
color={"secondary"}
/>
</Toolbar>
</AppBar>
}
<Typography variant="h6" style={{flexGrow: 2.0}}>
</Typography>
<ButtonWithSnackbar
text={"Pedestal"}
path={"/pedestal"}
color={"secondary"}
disabled={(s === undefined) || (s.state !== 'Idle')}
/>&nbsp;&nbsp;
<ButtonWithSnackbar
text={"Cancel"}
path={"/cancel"}
color={"secondary"}
/>&nbsp;&nbsp;
<ButtonWithSnackbar
text={"Initialize"}
path={"/initialize"}
color={"secondary"}
/>
</Toolbar>
</AppBar>
}
export default StatusBar;
export default memo(StatusBar);
+84 -118
View File
@@ -1,4 +1,4 @@
import React from 'react';
import {memo, useEffect, useState} from 'react';
import Paper from '@mui/material/Paper';
import {FormControlLabel, Radio, RadioGroup, Stack} from "@mui/material";
@@ -12,142 +12,108 @@ type MyProps = {
s?: zeromq_preview_settings
};
type MyState = {
s: zeromq_preview_settings,
last_downloaded_s: zeromq_preview_settings,
period_err: boolean,
download_counter: number,
freq_choice: string,
period_ms: number
};
const default_zeromq_preview_settings : zeromq_preview_settings = {
socket_address: "",
enabled: true,
period_ms: 1000
}
class ZeroMQPreview extends React.Component<MyProps, MyState> {
state : MyState = {
s: default_zeromq_preview_settings,
last_downloaded_s: default_zeromq_preview_settings,
period_err: false,
download_counter: 0,
freq_choice: "custom",
period_ms: default_zeromq_preview_settings.period_ms
}
function ZeroMQPreview({s: serverS}: MyProps) {
const [s, setS] = useState<zeromq_preview_settings>(default_zeromq_preview_settings);
const [lastDownloadedS, setLastDownloadedS] = useState<zeromq_preview_settings>(default_zeromq_preview_settings);
const [periodErr, setPeriodErr] = useState(false);
const [downloadCounter, setDownloadCounter] = useState(0);
const [freqChoice, setFreqChoice] = useState("custom");
const [periodMs, setPeriodMs] = useState(default_zeromq_preview_settings.period_ms);
update_freq = (freq_choice: string) => {
switch (freq_choice) {
const update_freq = (choice: string) => {
switch (choice) {
case "custom":
this.setState(prevState => ({
s: {...prevState.s, enabled: true, period_ms: this.state.period_ms},
freq_choice: "custom"
}));
setS(prev => ({...prev, enabled: true, period_ms: periodMs}));
setFreqChoice("custom");
break;
case "all":
this.setState(prevState => ({
s: {...prevState.s, enabled: true, period_ms: 0},
freq_choice: "all"
}));
setS(prev => ({...prev, enabled: true, period_ms: 0}));
setFreqChoice("all");
break;
default:
this.setState(prevState => ({
s: {...prevState.s, enabled: false},
freq_choice: "none"
}));
setS(prev => ({...prev, enabled: false}));
setFreqChoice("none");
break;
}
}
};
getValues = () => {
if (this.props.s !== undefined) {
let format_set: zeromq_preview_settings = this.props.s;
if (!_.isEqual(format_set, this.state.last_downloaded_s)) {
let x : number = this.state.period_ms;
let choice : string = "none";
if (this.props.s.enabled) {
if (this.props.s.period_ms === 0)
choice = "all";
else {
choice = "custom";
x = this.props.s.period_ms;
}
useEffect(() => {
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
let x : number = periodMs;
let choice : string = "none";
if (serverS.enabled) {
if (serverS.period_ms === 0)
choice = "all";
else {
choice = "custom";
x = serverS.period_ms;
}
this.setState(prevState => ({
s: format_set,
last_downloaded_s: format_set,
download_counter: prevState.download_counter + 1,
period_err: false,
freq_choice: choice,
period_ms: x
}));
}
setS(serverS);
setLastDownloadedS(serverS);
setDownloadCounter(c => c + 1);
setPeriodErr(false);
setFreqChoice(choice);
setPeriodMs(x);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [serverS]);
componentDidMount() {
this.getValues();
}
const empty = () : boolean =>
(s.socket_address === undefined) || (s.socket_address === "");
componentDidUpdate() {
this.getValues();
}
empty() : boolean {
return (this.state.s.socket_address === undefined) || (this.state.s.socket_address === "");
}
render() {
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<div><strong>ZeroMQ preview settings </strong></div>
<div>
{this.empty() ? "No preview available" : `ZeroMQ socket: ${this.state.s.socket_address}`}
</div>
<FormControl>
<RadioGroup
value={this.state.freq_choice}
onChange={(event) => {this.update_freq(event.target.value)}}
>
<FormControlLabel value="none" control={<Radio />} label="None" />
<FormControlLabel value="all" control={<Radio />} label="All images" />
<FormControlLabel value="custom" control={<Radio />} label="Frequency" />
</RadioGroup>
</FormControl>
<NumberTextField
default={1000}
start_val={this.state.period_ms}
label={"Period"}
min={0}
units={"ms"}
counter={this.state.download_counter}
callback={(val: number, err: boolean) => {
this.setState(prevState => ({
s: {...prevState.s, period_ms: val},
period_err: err,
period_ms: val
}));
}}
disabled={this.state.freq_choice != "custom"}
fullWidth/>
<ButtonWithSnackbar
color={"primary"}
path={"/config/zeromq_preview"}
input={JSON.stringify(this.state.s)}
method={"PUT"}
text={"Upload"}
disabled={((this.state.freq_choice == "custom") && this.state.period_err)}
/>
</Stack>
<br/>
</Paper>
}
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
<br/>
<Stack spacing={3} sx={{
justifyContent: "center",
alignItems: "center",
}}>
<div><strong>ZeroMQ preview settings </strong></div>
<div>
{empty() ? "No preview available" : `ZeroMQ socket: ${s.socket_address}`}
</div>
<FormControl>
<RadioGroup
value={freqChoice}
onChange={(event) => {update_freq(event.target.value)}}
>
<FormControlLabel value="none" control={<Radio />} label="None" />
<FormControlLabel value="all" control={<Radio />} label="All images" />
<FormControlLabel value="custom" control={<Radio />} label="Frequency" />
</RadioGroup>
</FormControl>
<NumberTextField
default={1000}
start_val={periodMs}
label={"Period"}
min={0}
units={"ms"}
counter={downloadCounter}
callback={(val: number, err: boolean) => {
setS(prev => ({...prev, period_ms: val}));
setPeriodErr(err);
setPeriodMs(val);
}}
disabled={freqChoice != "custom"}
fullWidth/>
<ButtonWithSnackbar
color={"primary"}
path={"/config/zeromq_preview"}
input={JSON.stringify(s)}
method={"PUT"}
text={"Upload"}
disabled={((freqChoice == "custom") && periodErr)}
/>
</Stack>
<br/>
</Paper>
}
export default ZeroMQPreview;
export default memo(ZeroMQPreview);
+14 -1
View File
@@ -1,6 +1,17 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
import { client } from "./client/client.gen";
// Talk to the same origin that serves the frontend (was OpenAPI.BASE='').
client.setConfig({ baseUrl: '' });
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false, refetchOnWindowFocus: false },
},
});
const rootElement = document.getElementById("root");
@@ -9,6 +20,8 @@ const root = createRoot(rootElement!);
root.render(
<StrictMode>
<App />
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>
);