445 lines
17 KiB
TypeScript
445 lines
17 KiB
TypeScript
import React, {Component} from 'react';
|
|
import Paper from "@mui/material/Paper";
|
|
import {
|
|
Checkbox,
|
|
FormControlLabel,
|
|
Grid,
|
|
Input,
|
|
Radio,
|
|
RadioGroup,
|
|
Select,
|
|
Slider,
|
|
Stack
|
|
} from "@mui/material";
|
|
import {TransformComponent, TransformWrapper} from "react-zoom-pan-pinch";
|
|
import {color_scale} from "../openapi";
|
|
import InputLabel from "@mui/material/InputLabel";
|
|
import MenuItem from "@mui/material/MenuItem";
|
|
import FormControl from "@mui/material/FormControl";
|
|
|
|
type preview_settings = {
|
|
saturation: number;
|
|
show_spots: boolean;
|
|
show_roi: boolean;
|
|
jpeg_quality: number;
|
|
show_user_mask: boolean;
|
|
show_beam_center: boolean;
|
|
resolution_ring: number;
|
|
scale: color_scale;
|
|
image_id: number;
|
|
use_png: boolean;
|
|
};
|
|
|
|
type MyProps = {
|
|
measuring: boolean,
|
|
min_image_number: number | undefined,
|
|
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);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
function stringToEnum(value: string): color_scale {
|
|
const enumValue = Object.values(color_scale).find(
|
|
(v) => v === value
|
|
) as color_scale;
|
|
|
|
// If no match is found, default to file_writer_format.NONE
|
|
return enumValue || color_scale.INDIGO;
|
|
}
|
|
|
|
class PreviewImage extends Component<MyProps, MyState> {
|
|
interval: ReturnType<typeof setInterval> | undefined;
|
|
|
|
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,
|
|
use_png: true
|
|
},
|
|
s_url: null,
|
|
update: true,
|
|
connection_error: true,
|
|
image_id_mode: 'last',
|
|
image_id: 0
|
|
}
|
|
|
|
update_image_id_mode = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
let s : preview_settings = this.state.settings;
|
|
|
|
switch (event.target.value) {
|
|
case "last":
|
|
s.image_id = -1;
|
|
this.setState({settings: s, image_id_mode: event.target.value});
|
|
this.getValues(s);
|
|
break;
|
|
case "last_indexed":
|
|
s.image_id = -2;
|
|
this.setState({settings: s, image_id_mode: event.target.value});
|
|
this.getValues(s);
|
|
break;
|
|
case "select":
|
|
s.image_id = this.state.image_id;
|
|
this.setState({settings: s, image_id_mode: event.target.value});
|
|
this.getValues(s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
showROIToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
let s : preview_settings = {
|
|
...this.state.settings,
|
|
show_roi: event.target.checked
|
|
};
|
|
this.setState({settings: s});
|
|
this.getValues(s);
|
|
}
|
|
|
|
usePNGToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
let s : preview_settings = {
|
|
...this.state.settings,
|
|
use_png: 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,) {
|
|
let url = `/image_buffer/image.`;
|
|
|
|
if (s.use_png)
|
|
url += "png";
|
|
else
|
|
url += "jpeg";
|
|
|
|
url += `?id=${s.image_id}`;
|
|
url += `&saturation=${s.saturation}`;
|
|
url += `&jpeg_quality=${s.jpeg_quality}`
|
|
url += `&show_spots=${s.show_spots}`
|
|
url += `&show_roi=${s.show_roi}`
|
|
url += `&show_user_mask=${s.show_user_mask}`;
|
|
url += `&show_res_ring=${s.resolution_ring}`;
|
|
url += `&show_beam_center=${s.show_beam_center}`;
|
|
url += `&color=${s.scale}`;
|
|
|
|
fetch(url, {method: "GET"})
|
|
.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});
|
|
if (tmp !== null)
|
|
URL.revokeObjectURL(tmp);
|
|
}).catch(error => {
|
|
this.setState({connection_error: true});
|
|
})
|
|
}
|
|
|
|
getValues(s: preview_settings = this.state.settings) {
|
|
this.getImage(s);
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.getValues();
|
|
this.interval = setInterval(() => {
|
|
if (this.state.update && this.props.measuring)
|
|
this.getValues()
|
|
}, 2000);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
clearInterval(this.interval);
|
|
let tmp = this.state.s_url;
|
|
if (tmp !== null)
|
|
URL.revokeObjectURL(tmp);
|
|
}
|
|
|
|
render() {
|
|
return <div>
|
|
|
|
<Grid container spacing={3}>
|
|
|
|
<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%"}}>
|
|
|
|
<FormControlLabel control={
|
|
<Checkbox checked={this.state.update} onChange={this.updateToggle} name="Update"/>
|
|
} label="Auto-update image"/>
|
|
<FormControlLabel control={
|
|
<Checkbox checked={this.state.settings.use_png} onChange={this.usePNGToggle} name="Use PNG format"/>
|
|
} label="PNG image format (slower, but lossless)"/>
|
|
<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.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.BW}>Black/white</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} valueLabelDisplay="auto"/>
|
|
|
|
<Input
|
|
value={this.state.settings.saturation}
|
|
onChange={this.setSaturationText}
|
|
inputProps={{
|
|
step: 1,
|
|
min: 0,
|
|
max: 200,
|
|
type: 'number',
|
|
}}
|
|
/>
|
|
</Stack><br/>Saturation level
|
|
|
|
<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"/>
|
|
<Input
|
|
value={this.state.settings.resolution_ring}
|
|
onChange={this.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;
|