From 5ad0d154542dec7cff20e97d9b73c1825cf70b9b Mon Sep 17 00:00:00 2001 From: Tom Kane Date: Wed, 21 Oct 2026 15:51:58 +0000 Subject: [PATCH 1/3] move cam pva to its own file, use for overview camera that only appear when controls are open --- apps/visr/src/components/CamPva.tsx | 50 ++++++++ .../spectroscopy/SpectroscopyPlots.tsx | 97 ++++++++++----- .../components/tomography/TomographyPlots.tsx | 117 ++++++++++-------- 3 files changed, 182 insertions(+), 82 deletions(-) create mode 100644 apps/visr/src/components/CamPva.tsx diff --git a/apps/visr/src/components/CamPva.tsx b/apps/visr/src/components/CamPva.tsx new file mode 100644 index 00000000..8f10cf3b --- /dev/null +++ b/apps/visr/src/components/CamPva.tsx @@ -0,0 +1,50 @@ +import { useState, useEffect } from "react"; +import { Box } from "@mui/material"; +import VideocamOffIcon from "@mui/icons-material/VideocamOff"; + +interface CamPvaProps { + src: string; + alt?: string; +} + +function CamPva({ src, alt = "Camera view" }: CamPvaProps) { + const [connected, setConnected] = useState(false); + + // Start disconnected again if a different stream is passed in + useEffect(() => { + setConnected(false); + }, [src]); + + return ( + + {!connected && ( + + + + )} + setConnected(true)} + onError={() => setConnected(false)} + sx={{ + width: "100%", + height: "100%", + objectFit: "contain", + visibility: connected ? "visible" : "hidden", + }} + /> + + ); +} + +export default CamPva; diff --git a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx index bdc2b66e..289b70c3 100644 --- a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx +++ b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx @@ -1,8 +1,17 @@ import { Box, Typography } from "@mui/material"; import { ImagePlot } from "@diamondlightsource/davidia"; import { ReactGridLayout, useContainerWidth } from "react-grid-layout"; -import { useLayoutEffect, useState, type ComponentProps } from "react"; +import { + useLayoutEffect, + useState, + type ComponentProps, + type ReactNode, +} from "react"; import { useSpectroscopyData } from "./useSpectroscopyData"; +import CamPva from "../CamPva"; + +const CAMERA_SRC = + "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-04:PVA:OUTPUT"; const CHANNELS = [ { key: "red", label: "Red channel" }, @@ -14,6 +23,12 @@ type ChannelKey = (typeof CHANNELS)[number]["key"]; type PlotValues = ComponentProps["values"]; export type SpectroscopyData = Partial>; +interface Cell { + key: string; + title: string; + content: ReactNode; +} + // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- @@ -36,7 +51,7 @@ function SpectroscopyPlots({ const totalRows = 10; const rowsPerPlot = expanded ? totalRows / 2 : totalRows; - const w = 1; + const cols = expanded ? 2 : 5; const minimumRowHeight = 20; const minimumGridHeight = minimumRowHeight * totalRows; const [containerHeight, setContainerHeight] = useState(0); @@ -62,45 +77,61 @@ function SpectroscopyPlots({ const gridHeight = rowHeight * totalRows; // ------------------------------------------------------------------------- - // Layout: 3x1 collapsed, 2x2 expanded + // Cells // ------------------------------------------------------------------------- - const layout = [ - { i: "0", x: 0, y: 0, w: w, h: rowsPerPlot, static: true }, - { i: "1", x: 1, y: 0, w: w, h: rowsPerPlot, static: true }, - { - i: "2", - x: expanded ? 0 : 2, - y: expanded ? rowsPerPlot : 0, - w: w, - h: rowsPerPlot, - static: true, - }, - ]; + const cameraCell: Cell = { + key: "overview", + title: "Overview", + content: , + }; + + const channelCells: Cell[] = CHANNELS.map(({ key, label }) => ({ + key, + title: label, + content: ( + + ), + })); + + // Placeholder for the composite image + const compositeCell: Cell = { + key: "composite", + title: "Composite image", + content: null, + }; + + // Drawer open: camera + 3 channels + composite in one row (5x1) + // Drawer closed: 3 channels + composite in a 2x2 + const cells = expanded + ? [...channelCells, compositeCell] + : [cameraCell, ...channelCells, compositeCell]; // ------------------------------------------------------------------------- - // Views + // Layout // ------------------------------------------------------------------------- - const views = CHANNELS.map(({ key }) => ( - - )); - - const titles = CHANNELS.map(({ label }) => label); + const layout = cells.map(({ key }, i) => ({ + i: key, + x: i % cols, + y: Math.floor(i / cols) * rowsPerPlot, + w: 1, + h: rowsPerPlot, + static: true, + })); // ------------------------------------------------------------------------- // Plot cells // ------------------------------------------------------------------------- - const plots = views.map((view, i) => ( + const plots = cells.map(({ key, title, content }) => ( - {titles[i]} + {title} div": { height: "100%" }, }} > - {view} + {content} )); @@ -156,7 +187,7 @@ function SpectroscopyPlots({ width={width} style={{ height: gridHeight }} gridConfig={{ - cols: expanded ? 2 : 3, + cols: cols, rowHeight: rowHeight, margin: [0, 0], }} diff --git a/apps/visr/src/components/tomography/TomographyPlots.tsx b/apps/visr/src/components/tomography/TomographyPlots.tsx index 598d0f3b..046664d9 100644 --- a/apps/visr/src/components/tomography/TomographyPlots.tsx +++ b/apps/visr/src/components/tomography/TomographyPlots.tsx @@ -1,9 +1,15 @@ import { Box, Typography } from "@mui/material"; import { ReactGridLayout, useContainerWidth } from "react-grid-layout"; -import { useLayoutEffect, useState } from "react"; +import { useLayoutEffect, useState, type ReactNode } from "react"; import SliceViewer from "./SliceViewer"; import VolumeRenderer from "./VolumeRenderer"; import { Plane } from "./PlaneEnum"; +import CamPva from "../CamPva"; + +const OVERVIEW_SRC = + "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-01:PVA:OUTPUT"; +const DETECTOR_SRC = + "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-05:PVA:OUTPUT"; // --------------------------------------------------------------------------- // Types @@ -19,24 +25,10 @@ interface Props { threshold: number; } -// --------------------------------------------------------------------------- -// Camera view -// --------------------------------------------------------------------------- - -function CamPva() { - const [imgSrc, setImgSrc] = useState( - "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-05:PVA:OUTPUT", - ); - - return ( - setImgSrc("../../../test-data/seal.png")} - alt="Camera view" - /> - ); +interface Cell { + key: string; + title: string; + content: ReactNode; } // --------------------------------------------------------------------------- @@ -57,7 +49,6 @@ function TomographyPlots({ // Grid sizing // ------------------------------------------------------------------------- const h = 10; - const w = 1; const minimumRowHeight = 30; const minimumGridHeight = minimumRowHeight * h; const [containerHeight, setContainerHeight] = useState(0); @@ -82,44 +73,72 @@ function TomographyPlots({ : 50; const gridHeight = rowHeight * h; + if (!volumeData) return ; + // ------------------------------------------------------------------------- - // Layout: 3x1 + // Cells // ------------------------------------------------------------------------- - const layout = [ - { i: "0", x: 0, y: 0, w: w, h: h, static: true }, - { i: "1", x: 1, y: 0, w: w, h: h, static: true }, - { i: "2", x: 2, y: 0, w: w, h: h, static: true }, + const overviewCell: Cell = { + key: "overview", + title: "Overview", + content: , + }; + + const plotCells: Cell[] = [ + { + key: "detector", + title: "Detector", + content: , + }, + { + key: "reconstruction", + title: "Reconstruction", + content: ( + + ), + }, + { + key: "slice", + title: "Slice View", + content: ( + + ), + }, ]; - if (!volumeData) return ; + // Drawer open: overview + 3 plots (4x1) + // Drawer closed: 3 plots only (3x1) + const cells = drawerOpen ? [overviewCell, ...plotCells] : plotCells; + const cols = cells.length; // ------------------------------------------------------------------------- - // Views + // Layout // ------------------------------------------------------------------------- - const views = [ - , - , - , - ]; - - const titles = ["Camera View", "Reconstruction", "Slice View"]; + const layout = cells.map(({ key }, i) => ({ + i: key, + x: i, + y: 0, + w: 1, + h: h, + static: true, + })); // ------------------------------------------------------------------------- // Plot cells // ------------------------------------------------------------------------- - const plots = views.map((view, i) => ( + const plots = cells.map(({ key, title, content }) => ( - {titles[i]} + {title} div": { height: "100%" }, }} > - {view} + {content} )); @@ -178,7 +197,7 @@ function TomographyPlots({ width={width} style={{ height: gridHeight }} gridConfig={{ - cols: 3, + cols: cols, rowHeight: rowHeight, margin: [0, 0], }} From c7dbbb6c2a6a6c1e3ab020b287154574dfd36660 Mon Sep 17 00:00:00 2001 From: Tom Kane Date: Wed, 21 Oct 2026 15:58:28 +0000 Subject: [PATCH 2/3] rename cell in spectroscopy --- .../src/components/spectroscopy/SpectroscopyPlots.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx index 289b70c3..74d34e2e 100644 --- a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx +++ b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx @@ -10,7 +10,7 @@ import { import { useSpectroscopyData } from "./useSpectroscopyData"; import CamPva from "../CamPva"; -const CAMERA_SRC = +const OVERVIEW_SRC = "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-04:PVA:OUTPUT"; const CHANNELS = [ @@ -79,10 +79,10 @@ function SpectroscopyPlots({ // ------------------------------------------------------------------------- // Cells // ------------------------------------------------------------------------- - const cameraCell: Cell = { + const overviewCell: Cell = { key: "overview", title: "Overview", - content: , + content: , }; const channelCells: Cell[] = CHANNELS.map(({ key, label }) => ({ @@ -110,7 +110,7 @@ function SpectroscopyPlots({ // Drawer closed: 3 channels + composite in a 2x2 const cells = expanded ? [...channelCells, compositeCell] - : [cameraCell, ...channelCells, compositeCell]; + : [overviewCell, ...channelCells, compositeCell]; // ------------------------------------------------------------------------- // Layout From e6370bb2513443eb98f9173ad68a0b6bfdb6d3b0 Mon Sep 17 00:00:00 2001 From: Tom Kane Date: Mon, 9 Nov 2026 10:49:39 +0000 Subject: [PATCH 3/3] move Cell type to its own file --- apps/visr/src/components/cell.ts | 7 +++++++ .../components/spectroscopy/SpectroscopyPlots.tsx | 14 ++------------ .../src/components/tomography/TomographyPlots.tsx | 9 ++------- 3 files changed, 11 insertions(+), 19 deletions(-) create mode 100644 apps/visr/src/components/cell.ts diff --git a/apps/visr/src/components/cell.ts b/apps/visr/src/components/cell.ts new file mode 100644 index 00000000..ba887914 --- /dev/null +++ b/apps/visr/src/components/cell.ts @@ -0,0 +1,7 @@ +import type { ReactNode } from "react"; + +export interface Cell { + key: string; + title: string; + content: ReactNode; +} diff --git a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx index 74d34e2e..f39bee79 100644 --- a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx +++ b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx @@ -1,14 +1,10 @@ import { Box, Typography } from "@mui/material"; import { ImagePlot } from "@diamondlightsource/davidia"; import { ReactGridLayout, useContainerWidth } from "react-grid-layout"; -import { - useLayoutEffect, - useState, - type ComponentProps, - type ReactNode, -} from "react"; +import { useLayoutEffect, useState, type ComponentProps } from "react"; import { useSpectroscopyData } from "./useSpectroscopyData"; import CamPva from "../CamPva"; +import type { Cell } from "../cell"; const OVERVIEW_SRC = "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-04:PVA:OUTPUT"; @@ -23,12 +19,6 @@ type ChannelKey = (typeof CHANNELS)[number]["key"]; type PlotValues = ComponentProps["values"]; export type SpectroscopyData = Partial>; -interface Cell { - key: string; - title: string; - content: ReactNode; -} - // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- diff --git a/apps/visr/src/components/tomography/TomographyPlots.tsx b/apps/visr/src/components/tomography/TomographyPlots.tsx index 046664d9..a3626c36 100644 --- a/apps/visr/src/components/tomography/TomographyPlots.tsx +++ b/apps/visr/src/components/tomography/TomographyPlots.tsx @@ -1,10 +1,11 @@ import { Box, Typography } from "@mui/material"; import { ReactGridLayout, useContainerWidth } from "react-grid-layout"; -import { useLayoutEffect, useState, type ReactNode } from "react"; +import { useLayoutEffect, useState } from "react"; import SliceViewer from "./SliceViewer"; import VolumeRenderer from "./VolumeRenderer"; import { Plane } from "./PlaneEnum"; import CamPva from "../CamPva"; +import type { Cell } from "../cell"; const OVERVIEW_SRC = "https://visr-pvws.diamond.ac.uk/mjpg/BL01C-DI-DCAM-01:PVA:OUTPUT"; @@ -25,12 +26,6 @@ interface Props { threshold: number; } -interface Cell { - key: string; - title: string; - content: ReactNode; -} - // --------------------------------------------------------------------------- // Component // ---------------------------------------------------------------------------