Skip to content
40 changes: 36 additions & 4 deletions apps/visr/src/components/ControlsDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Drawer, Box, IconButton, Typography } from "@mui/material";
import UnfoldLessIcon from "@mui/icons-material/UnfoldLess";
import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore";
import { Children, Fragment, type ReactNode } from "react";

interface ControlsDrawerProps {
open: boolean;
collapsedHeight: number;
onToggle: () => void;
controls: JSX.Element;
controls: ReactNode | ReactNode[];
}

function ControlsDrawer({
Expand All @@ -15,6 +16,8 @@ function ControlsDrawer({
onToggle,
controls,
}: ControlsDrawerProps) {
const items = Children.toArray(controls);

return (
<Drawer
anchor="bottom"
Expand All @@ -27,28 +30,57 @@ function ControlsDrawer({
transition: "flex-grow 0.4s ease, height 0.4s ease",
boxSizing: "border-box",
bgcolor: "transparent",
overflowY: "auto",
},
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
px: 2,
height: 64,
flexShrink: 0,
}}
>
<Typography variant="overline">Controls</Typography>
<IconButton onClick={onToggle}>
<IconButton
onClick={onToggle}
aria-label={open ? "Collapse" : "Expand"}
aria-expanded={open}
>
{open ? <UnfoldLessIcon /> : <UnfoldMoreIcon />}
</IconButton>
</Box>

<Box
sx={{
px: 10,
display: open ? "flex" : "none",
flexDirection: { xs: "column", md: "row" },
gap: 3,
alignItems: "flex-start",
px: { xs: 3, md: 6 },
pb: 3,
}}
>
{open ? controls : <Box />}
{items.map((control, index) => (
<Fragment key={index}>
{index > 0 && (
<Box
sx={{
alignSelf: "stretch",
borderTop: { xs: 1, md: 0 },
borderLeft: { xs: 0, md: 1 },
borderColor: "divider",
mx: { xs: 6, md: 0 },
my: { xs: 0, md: 4 },
}}
/>
)}
<Box sx={{ flex: 1, minWidth: 0 }}>{control}</Box>
</Fragment>
))}
</Box>
</Drawer>
);
Expand Down
132 changes: 50 additions & 82 deletions apps/visr/src/components/tomography/TomographyControls.tsx
Original file line number Diff line number Diff line change
@@ -1,118 +1,86 @@
import {
Box,
Button,
FormControlLabel,
LinearProgress,
Radio,
RadioGroup,
Slider,
Stack,
TextField,
Typography,
} from "@mui/material";
import { Plane } from "./PlaneEnum";

interface Props {
onRun: () => void;
onReset: () => void;
onSlide: (event: Event, newValue: number | number[]) => void;
onSetDirection: (event: React.ChangeEvent<HTMLInputElement>) => void;
plane: Plane;
progress: number;
slice: number;
volumeShape: [number, number, number];
}

export default function Controls({
onRun,
onReset,
onSlide,
onSetDirection,
plane,
progress,
slice,
volumeShape,
}: Props) {
const maxSlice: Record<Plane, number> = {
[Plane.Z]: volumeShape[0] - 1,
[Plane.Y]: volumeShape[1] - 1,
[Plane.X]: volumeShape[2] - 1,
};

return (
<Box
sx={{
display: "flex",
flexDirection: "column",
borderColor: "divider",
gap: 3,
p: 3,
}}
>
<Stack direction="row" spacing={1} alignItems="center">
<Box sx={{ flex: 1 }} />
<Box sx={{ flex: 1 }}>
<Typography variant="overline" color="primary" padding={1}>
Reconstruction
</Typography>
<Stack direction="row" spacing={1} alignItems="center">
<TextField
label="angles"
type="number"
size="small"
defaultValue={360}
sx={{ width: 100, padding: 1 }}
/>
<Button variant="contained" size="small" onClick={onRun}>
Run
</Button>
<Button variant="contained" size="small" onClick={onReset}>
Reset
</Button>
</Stack>
<LinearProgress
variant="determinate"
value={progress}
sx={{ height: 8, borderRadius: 100 }}
/>
</Box>
<Box sx={{ flex: 0.2 }} />
<Box sx={{ flex: 1 }}>
<Typography variant="overline" color="primary">
Slice View
</Typography>
<Typography variant="body1" color="primary">
Slice
</Typography>
<Slider
shiftStep={1}
step={1}
min={0}
max={
plane === Plane.Z
? volumeShape[0] - 1
: plane === Plane.Y
? volumeShape[1] - 1
: volumeShape[2] - 1
}
marks
onChange={onSlide}
value={slice}
></Slider>
<Typography variant="body1" color="primary">
Axis
</Typography>
<RadioGroup row value={plane} onChange={onSetDirection}>
<FormControlLabel value={Plane.X} control={<Radio />} label="X" />
<FormControlLabel value={Plane.Y} control={<Radio />} label="Y" />
<FormControlLabel value={Plane.Z} control={<Radio />} label="Z" />
</RadioGroup>
</Box>
</Stack>
{/* <Box sx={{ flex: 1 }} /> */}
<Typography variant="overline" color="primary">
Slice View
</Typography>

<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Typography variant="body2" color="primary">
Slice
</Typography>
<Slider
shiftStep={1}
step={1}
min={0}
max={maxSlice[plane]}
marks
valueLabelDisplay="auto"
onChange={onSlide}
value={slice}
/>
</Box>

{/* <ToggleButton
value="revolve"
selected={revolve}
size="small"
onChange={() => onRevolveChange(!revolve)}
color="secondary"
sx={{ textTransform: "none" }}
>
Revolve
</ToggleButton> */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Typography variant="body2" color="primary">
Axis
</Typography>
<RadioGroup row value={plane} onChange={onSetDirection}>
<FormControlLabel value={Plane.X} control={<Radio />} label="X" />
<FormControlLabel value={Plane.Y} control={<Radio />} label="Y" />
<FormControlLabel value={Plane.Z} control={<Radio />} label="Z" />
</RadioGroup>
</Box>
</Box>
);
}

{
/* <ToggleButton
value="revolve"
selected={revolve}
size="small"
onChange={() => onRevolveChange(!revolve)}
color="secondary"
sx={{ textTransform: "none" }}
>
Revolve
</ToggleButton> */
}
141 changes: 141 additions & 0 deletions apps/visr/src/components/tomography/TomographyForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession";
import { RunPlanButton } from "@atlas/blueapi-ui";
import AbortButton from "../AbortButton";
import { useState } from "react";
import { NumberInput } from "@diamondlightsource/sci-react-ui";
import {
Box,
FormControl,
FormLabel,
ToggleButton,
ToggleButtonGroup,
} from "@mui/material";
import { visitToText, VisitInput } from "@diamondlightsource/sci-react-ui";
import { visitTextToVisit } from "../../utils/common";

enum LightSource {
LED = "led",
SR = "sr",
DARK = "dark",
}

export type TomographyFormData = {
number_of_projections: number;
light_source: LightSource;
};

export function TomographyForm() {
const { instrumentSession, setInstrumentSession } = useInstrumentSession();
const [formData, setFormData] = useState<TomographyFormData>({
number_of_projections: 360,
light_source: LightSource.LED,
});
const minProjections = 30;
const maxProjections = 1440;

return (
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 3,
p: 3,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 2,
}}
>
<NumberInput
label="Number of Projections"
defaultValue={formData["number_of_projections"]}
numberMode="natural"
onCommit={parsedValue => {
setFormData({
...formData,
["number_of_projections"]: parsedValue,
});
}}
minValue={minProjections}
maxValue={maxProjections}
/>
<VisitInput
visit={
visitTextToVisit(instrumentSession) ??
visitTextToVisit("cm12345-1") ??
undefined
}
onSubmit={visit => setInstrumentSession(visitToText(visit))}
submitButton={false}
/>
<FormControl>
<FormLabel id="light-source-label" sx={{ mb: 1 }}>
Light Source
</FormLabel>
<ToggleButtonGroup
exclusive
aria-labelledby="light-source-label"
value={formData.light_source}
onChange={(_, value: LightSource | null) =>
value && setFormData({ ...formData, light_source: value })
}
>
<ToggleButton value={LightSource.LED}>LED</ToggleButton>
<ToggleButton value={LightSource.SR}>Synchrotron</ToggleButton>
<ToggleButton value={LightSource.DARK}>Off</ToggleButton>
</ToggleButtonGroup>
</FormControl>
</Box>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
gap: 2,
pt: 1,
borderTop: 1,
borderColor: "divider",
}}
>
<RunPlanButton
name="tomography"
params={{ ...formData, fly: false }}
instrumentSession={instrumentSession}
buttonText="Step Scan"
/>
<RunPlanButton
name="tomography"
params={{ ...formData, fly: true }}
instrumentSession={instrumentSession}
buttonText="Fly Scan"
/>
<Box
sx={{
ml: "auto",
"& .MuiButton-root": {
backgroundColor: "secondary.main",
"&:hover": { backgroundColor: "secondary.dark" },
},
}}
>
<RunPlanButton
name="correction"
params={{ light_source: formData.light_source }}
instrumentSession={instrumentSession}
buttonText="Acquire Correction Image"
/>
</Box>
</Box>

<Box
sx={{
display: "flex",
}}
>
<AbortButton />
</Box>
</Box>
);
}
Loading
Loading