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:
@@ -13,5 +13,6 @@ export default defineConfig({
|
||||
enums: { mode: 'javascript' },
|
||||
},
|
||||
'@hey-api/sdk',
|
||||
'@tanstack/react-query',
|
||||
],
|
||||
});
|
||||
|
||||
Generated
+27
@@ -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",
|
||||
|
||||
@@ -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
@@ -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>, 227–234</a> <br/>
|
||||
Version: {JFJOCH_VERSION}
|
||||
<a href="/frontend/openapi.html">API reference</a>
|
||||
<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>, 227–234</a> <br/>
|
||||
Version: {JFJOCH_VERSION}
|
||||
<a href="/frontend/openapi.html">API reference</a>
|
||||
<a href="https://jungfraujoch.readthedocs.io/">Documentation</a></center>
|
||||
<br/>
|
||||
</ThemeProvider>
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}/>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 [Å] </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 [Å] </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 [Å] </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 [Å<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}
|
||||
/>
|
||||
High‑res gap Q-space filter [Å<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 [Å] </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 [Å] </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 [Å] </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 [Å<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}
|
||||
/>
|
||||
High‑res gap Q-space filter [Å<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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}}
|
||||
/>
|
||||
<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}}
|
||||
/>
|
||||
</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>
|
||||
<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"}}/>
|
||||
<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"}}/>
|
||||
<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"}}/>
|
||||
<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>
|
||||
<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>
|
||||
<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"}}/>
|
||||
<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"}}/>
|
||||
<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"}}/>
|
||||
<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>
|
||||
<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"}
|
||||
/>
|
||||
<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"}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
color={"primary"}
|
||||
path={"/deactivate"}
|
||||
text={"Deactivate"}
|
||||
/>
|
||||
<br/><br/>
|
||||
</Grid>
|
||||
<Grid item xs={1}/>
|
||||
</Grid>
|
||||
</Paper>
|
||||
}
|
||||
|
||||
export default DetectorSettings;
|
||||
export default memo(DetectorSettings);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
<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}
|
||||
}));
|
||||
}}/>
|
||||
<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}
|
||||
}));
|
||||
}}/>
|
||||
|
||||
</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}
|
||||
}));
|
||||
}}/>
|
||||
<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}
|
||||
}));
|
||||
}}/>
|
||||
|
||||
<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}
|
||||
}));
|
||||
}}/>
|
||||
</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%'}}/>
|
||||
<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>
|
||||
<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}));
|
||||
}}/>
|
||||
<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}));
|
||||
}}/>
|
||||
|
||||
</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}));
|
||||
}}/>
|
||||
<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}));
|
||||
}}/>
|
||||
|
||||
<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}));
|
||||
}}/>
|
||||
</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%'}}/>
|
||||
<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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">µs</InputAdornment>;
|
||||
else if (str === "A-1")
|
||||
return <InputAdornment position="end">
|
||||
<span>Å<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">µs</InputAdornment>;
|
||||
else if (str === "A-1")
|
||||
return <InputAdornment position="end">
|
||||
<span>Å<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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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=" Å"
|
||||
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=" Å"
|
||||
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
@@ -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"/> {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 < Q<sub>min</sub> < 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"/> {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>
|
||||
<Button color="secondary" onClick={this.addCircleButton} variant="contained"
|
||||
disableElevation>Add Circle ROI</Button>
|
||||
<Button color="secondary" onClick={this.addAzimButton} variant="contained"
|
||||
disableElevation>Add Azimuthal ROI</Button>
|
||||
<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>
|
||||
<Button color="secondary" onClick={addCircleButton} variant="contained"
|
||||
disableElevation>Add Circle ROI</Button>
|
||||
<Button color="secondary" onClick={addAzimButton} variant="contained"
|
||||
disableElevation>Add Azimuthal ROI</Button>
|
||||
<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);
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Cancel"}
|
||||
path={"/cancel"}
|
||||
color={"secondary"}
|
||||
/>
|
||||
<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')}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Cancel"}
|
||||
path={"/cancel"}
|
||||
color={"secondary"}
|
||||
/>
|
||||
<ButtonWithSnackbar
|
||||
text={"Initialize"}
|
||||
path={"/initialize"}
|
||||
color={"secondary"}
|
||||
/>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
}
|
||||
|
||||
export default StatusBar;
|
||||
export default memo(StatusBar);
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user