Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3579261
Add starting page and route for JF rotations
noemifrisina Oct 10, 2025
7ce2b13
Add link to navbar
noemifrisina Oct 10, 2025
59346f0
Add jungfrau do darks
noemifrisina Oct 13, 2025
208ba8e
Without file path it works... sigh
noemifrisina Oct 13, 2025
a1d1cc2
New changes
noemifrisina Oct 27, 2025
a836780
Merge branch 'main' into 59_jf-rotation
noemifrisina Oct 27, 2025
7e1afc4
Update
noemifrisina Oct 27, 2025
ad61895
Merge branch 'main' into 59_jf-rotation
noemifrisina Oct 31, 2025
dc6d505
Merge branch 'main' into 59_jf-rotation
noemifrisina Nov 3, 2025
7dfa14a
Hopefully fix lint
noemifrisina Nov 12, 2025
c4a7a6b
Wrap everything in the fallback just in case
noemifrisina Nov 19, 2025
e7bf064
And maybe save everything
noemifrisina Nov 19, 2025
cfaa9dc
Start using context and filling in params
noemifrisina Nov 19, 2025
2361432
Finish filling everything in
noemifrisina Nov 19, 2025
601fe68
Run prettier on changes
noemifrisina Nov 20, 2025
fda3f13
Fix the contrast text in light mode
noemifrisina Nov 20, 2025
e886bfa
Merge branch 'main' into 59_jf-rotation
noemifrisina Jan 20, 2026
ea842ed
Merge branch 'main' into 59_jf-rotation
noemifrisina Jan 21, 2026
c7af193
Update darks collection
noemifrisina Jan 21, 2026
dbff12c
Update JF context
noemifrisina Jan 21, 2026
565310c
Add a visit provider and update the JF rotation parameters
noemifrisina Jan 21, 2026
ac18c1f
Save everything
noemifrisina Jan 21, 2026
4cb6289
Increase spacing a bit
noemifrisina Jan 21, 2026
94784b8
Add warning to tooltip
noemifrisina Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { QueryClient, QueryClientProvider } from "react-query";
import { BeamlineI24 } from "routes/BeamlineI24.tsx";
import { FixedTarget } from "routes/FixedTarget.tsx";
import { Extruder } from "routes/Extruder.tsx";
import { JfRotation } from "routes/JungfrauRotation.tsx";
import { loadConfig } from "./config.ts";
import { useEffect, useState } from "react";

Expand All @@ -33,6 +34,10 @@ const router = createBrowserRouter(
path: "extruder",
element: <Extruder />,
},
{
path: "jungfrau",
element: <JfRotation />,
},
],
},
],
Expand Down
5 changes: 4 additions & 1 deletion src/CustomTheme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ const I24DiamondThemeOptions = mergeThemeOptions(
colorSchemes: {
light: {
palette: {
primary: {
contrastText: "#000000",
},
custom: {
main: "#1976d2",
light: "#68a0e2",
dark: "#10569b",
contrastText: "#ffffff", // white
contrastText: "#ffffff",
},
},
},
Expand Down
150 changes: 150 additions & 0 deletions src/components/JungFrau/CollectDarksPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
Box,
Card,
CardContent,
FormControl,
InputLabel,
MenuItem,
Select,
Stack,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import React from "react";
import { ParameterInput } from "../ParameterInputs";
import { RunPlanButton } from "../../blueapi/BlueapiComponents";
import { GainModes } from "./constants";

export function CollectDarksPanel() {
const theme = useTheme();
const [filename, setFilename] = React.useState<string>("darks");
const [expTime, setExpTime] = React.useState<number>(0.001);
const [pedestalFrames, setPedestalFrames] = React.useState<number>(20);
const [pedestalLoops, setPedestalLoops] = React.useState<number>(200);
const [totTriggers, setTotTriggers] = React.useState<number>(1000);
const [gainMode, setGainMode] = React.useState<string>(GainModes[0]);

return (
<Box sx={{ flexGrow: 1, marginLeft: 15, marginRight: 5 }}>
<Card variant="outlined" sx={{ minWidth: 300, minHeight: 600 }}>
<CardContent>
<Typography
variant="h2"
sx={{
color: theme.palette.primary.contrastText,
fontSize: 24,
fontWeight: "fontWeightBold",
}}
>
Collect Darks
</Typography>
<Stack spacing={4} margin={4} alignItems={"center"}>
<Stack direction={"row"} spacing={2}>
<ParameterInput
value={filename}
onSet={setFilename}
label="Filename"
tooltip="Set filename for darks collection."
/>
<ParameterInput
value={expTime}
onSet={setExpTime}
label="Exposure time (s)"
tooltip="Set exposure time for darks collection."
/>
</Stack>
<Typography
variant="body1"
sx={{
color: theme.palette.primary.contrastText,
fontSize: 20,
fontWeight: "fontWeightBold",
}}
>
Pedestal
</Typography>
<Stack direction={"row"} spacing={2}>
<ParameterInput
value={pedestalFrames}
onSet={setPedestalFrames}
label="Pedestal frames"
tooltip="Number of pedestal frames to collect."
/>
<ParameterInput
value={pedestalLoops}
onSet={setPedestalLoops}
label="Pedestal loops"
tooltip="Number of pedestal loops."
/>
</Stack>
<RunPlanButton
btnLabel="Pedestal darks"
planName="do_pedestal_darks"
planParams={{
exp_time_s: expTime,
pedestal_frame: pedestalFrames,
pedestal_loops: pedestalLoops,
filename: filename,
}}
title="Collect pedestal darks"
btnSize="large"
/>
<Typography
variant="body1"
sx={{
color: theme.palette.primary.contrastText,
fontSize: 20,
fontWeight: "fontWeightBold",
}}
>
Standard
</Typography>
<Stack direction={"row"} spacing={2}>
<ParameterInput
value={totTriggers}
onSet={setTotTriggers}
label="Total Triggers"
tooltip="Total triggers for the dark scan."
/>
<Tooltip
title="Select gain mode before starting the darks acquisition.
WARNING. FixGO may damage the detector - use with caution."
placement="right"
>
<FormControl size="small" style={{ width: 180 }}>
<InputLabel id="gain-label">Gain Mode</InputLabel>
<Select
labelId="gain-label"
id="gain"
value={gainMode}
label="Gain Mode"
onChange={(e) => setGainMode(e.target.value)}
>
{GainModes.map((choice) => (
<MenuItem key={choice} value={choice}>
{choice}
</MenuItem>
))}
</Select>
</FormControl>
</Tooltip>
</Stack>
<RunPlanButton
btnLabel="Standard darks"
planName="do_non_pedestal_darks"
planParams={{
gain_mode: gainMode,
exp_time_s: expTime,
total_triggers: totTriggers,
filename: filename,
}}
title="Collect non pedestal darks"
btnSize="large"
/>
</Stack>
</CardContent>
</Card>
</Box>
);
}
136 changes: 136 additions & 0 deletions src/components/JungFrau/CollectionSetupJf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
Box,
Grid2 as Grid,
Stack,
TextField,
Typography,
useTheme,
} from "@mui/material";
import { AbortButton, RunPlanButton } from "blueapi/BlueapiComponents";
import { ParameterInput } from "components/ParameterInputs";
import { JungfrauRotationContext } from "context/jungfrau/JungfrauRotationContext";
import { VisitContext } from "context/VisitContext";
import React from "react";
import { useContext } from "react";

function fullStorageDirectory(visit: string): string {
const date = new Date();
const year = date.getFullYear();
return `/dls/i24/data/${year}/${visit}/jungfrau/`;
}

function RunButtons(): JSX.Element {
const {
expTime,
detDist,
fileName,
omegaStart,
omegaIncrement,
transFract,
sampleId,
} = useContext(JungfrauRotationContext);
console.log(transFract);
return (
<React.Fragment>
<Stack direction={"row"} spacing={4} justifyContent={"center"}>
<RunPlanButton
btnLabel="Run rotation scan"
planName="gui_run_jf_rotation_scan"
planParams={{
exposure_time_s: expTime,
omega_start_deg: omegaStart,
omega_increment_deg: omegaIncrement,
detector_distance_mm: detDist,
filename: fileName,
transmissions: transFract,
sample_id: sampleId,
}}
title="Run the jungfrau rotation scan plan"
btnSize="large"
/>
<AbortButton />
</Stack>
</React.Fragment>
);
}

export function CollectionSetupJf() {
const theme = useTheme();
const context = useContext(JungfrauRotationContext);
const { visit } = useContext(VisitContext);
const storageDirectory = fullStorageDirectory(visit);
return (
<Box sx={{ flexGrow: 1 }}>
<Stack direction={"column"} alignItems={"center"} spacing={5}>
<Typography
variant="h2"
sx={{
color: theme.palette.primary.contrastText,
fontSize: 24,
fontWeight: "fontWeightBold",
}}
>
Collection Parameters
</Typography>
<TextField
size="small"
label="Storage Directory"
value={storageDirectory}
style={{ width: 570 }}
slotProps={{
input: { readOnly: true },
}}
/>
<Grid container spacing={2} marginTop={3} justifyContent={"center"}>
<ParameterInput
value={context.fileName}
onSet={context.setFileName}
label="File Name"
tooltip="Name to use to save the data"
/>
<ParameterInput
value={context.expTime}
onSet={context.setExpTime}
label="Exposure Time (s)"
tooltip="Exposure time for each window, in seconds"
/>
<ParameterInput
value={context.detDist}
onSet={context.setDetDist}
label="Detector Distance (mm)"
tooltip="Sample detector distance, in millimeters"
/>
</Grid>
<Grid container spacing={2} marginTop={3} justifyContent={"center"}>
<ParameterInput
value={context.omegaStart}
onSet={context.setOmegaStart}
label="Omega start (deg)"
tooltip="Rotation start value, in deg"
/>
<ParameterInput
value={context.omegaIncrement}
onSet={context.setOmegaIncrement}
label="Omega increment (deg)"
tooltip="Rotation increment step, in deg"
/>
<ParameterInput
value={context.sampleId}
onSet={context.setSampleId}
label="Sample ID"
tooltip="Sample id"
/>
</Grid>
<Grid container spacing={2} marginTop={3} justifyContent={"center"}>
<ParameterInput
value={context.transFract}
onSet={context.setTransFract}
label="Transmission (fraction)"
tooltip="Request transmission value(s) for collection, expressed as a fraction. If running a single rotation, just input one value, if running multiples please pass a list."
/>
</Grid>
<RunButtons />
</Stack>
</Box>
);
}
16 changes: 16 additions & 0 deletions src/components/JungFrau/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type GainModeType =
| "Dynamic"
| "ForceSwitchG1"
| "ForceSwitchG2"
| "FixG1"
| "FixG2"
| "FixG0";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the ophyd-async Enum I put a comment before the FIX_G0: # Use with caution - this may damage the detector. (I got this from an operations guide for the Jungfrau which is now deprecated).

Could put a similar message on the screen around this option

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add to the tooltip


export const GainModes = [
"Dynamic",
"ForceSwitchG1",
"ForceSwitchG2",
"FixG1",
"FixG2",
"FixG0",
];
3 changes: 3 additions & 0 deletions src/components/SerialNavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export function SerialNavBar() {
<NavLink linkComponent={Link} to="/extruder">
Extruder
</NavLink>
<NavLink linkComponent={Link} to="/jungfrau">
JF Rotation
</NavLink>
</NavLinks>
</Navbar>
);
Expand Down
10 changes: 10 additions & 0 deletions src/context/VisitContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { createContext } from "react";

export type VisitContextType = {
visit: string;
setVisit: React.Dispatch<React.SetStateAction<string>>;
};

export const VisitContext = createContext<VisitContextType>(
null as unknown as VisitContextType,
);
14 changes: 14 additions & 0 deletions src/context/VisitProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ReactNode, useState } from "react";
import { VisitContext } from "./VisitContext";

const defaultVisit = "cm44177-1"; // Until we can grab it after authentication

export const VisitProvider = ({ children }: { children: ReactNode }) => {
const [visit, setVisit] = useState<string>(defaultVisit);

return (
<VisitContext.Provider value={{ visit, setVisit }}>
{children}
</VisitContext.Provider>
);
};
23 changes: 23 additions & 0 deletions src/context/jungfrau/JungfrauRotationContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext } from "react";

export type JungfrauRotationContextType = {
fileName: string;
expTime: number;
detDist: number;
transFract: number[];
omegaStart: number;
omegaIncrement: number;
sampleId: number;
setFileName: React.Dispatch<React.SetStateAction<string>>;
setExpTime: React.Dispatch<React.SetStateAction<number>>;
setDetDist: React.Dispatch<React.SetStateAction<number>>;
setTransFract: React.Dispatch<React.SetStateAction<number[]>>;
setOmegaStart: React.Dispatch<React.SetStateAction<number>>;
setOmegaIncrement: React.Dispatch<React.SetStateAction<number>>;
setSampleId: React.Dispatch<React.SetStateAction<number>>;
};

export const JungfrauRotationContext =
createContext<JungfrauRotationContextType>(
null as unknown as JungfrauRotationContextType,
);
Loading