From 99bb254a2ba325e46f0970189dd5231c3e426a57 Mon Sep 17 00:00:00 2001 From: Douglas Winter Date: Thu, 21 May 2026 08:45:45 +0000 Subject: [PATCH 1/4] Let useSpectroscopyData read data from Tiled Specifically, the binned plugin. The consuming component, in this case RawSpectroscopyData, no longer needs to pass any args to the hook, as the endpoint returns every relevant dataset related to the UUID, already in useScanEvents. --- apps/visr/helm/values.yaml | 12 +- .../spectroscopy/SpectroscopyPlots.tsx | 93 +----- .../spectroscopy/useSpectroscopyData.ts | 150 +++++++--- apps/visr/src/mocks/handlers.ts | 14 +- apps/visr/src/mocks/mock_data.ts | 278 ++++++++++++++++++ 5 files changed, 430 insertions(+), 117 deletions(-) diff --git a/apps/visr/helm/values.yaml b/apps/visr/helm/values.yaml index 1e034b9e..bb16a35f 100644 --- a/apps/visr/helm/values.yaml +++ b/apps/visr/helm/values.yaml @@ -22,9 +22,17 @@ ui-base: external: uri: https://workflows.diamond.ac.uk - - id: data + - id: tiled path: /api/data/ - rewriteTarget: / + rewriteTarget: /api/v1/ + target: + service: + name: b01-1-tiled + port: 8000 + + - id: dataserver + path: /api/data/events + rewriteTarget: /events target: service: name: dataserver diff --git a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx index 2a2429ce..cc84f0b6 100644 --- a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx +++ b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx @@ -1,87 +1,15 @@ -import { ImagePlot, type NDT } from "@diamondlightsource/davidia"; -import ndarray from "ndarray"; -import { useSpectroscopyData, type RGBColour } from "./useSpectroscopyData"; +import { ImagePlot } from "@diamondlightsource/davidia"; +import { useSpectroscopyData } from "./useSpectroscopyData"; import ReactGridLayout, { useContainerWidth } from "react-grid-layout"; -import { useMemo, type ComponentProps } from "react"; +import { useMemo } from "react"; import { Box } from "@mui/material"; -function toNDT(matrix: (number | null)[][], colour: RGBColour): NDT { - if (!matrix?.length || !matrix[0]?.length) { - return EMPTY_NDT; // skip invalid input - } - const height = matrix.length; - const width = matrix[0].length; - - // Flatten and filter out nulls for normalisation - const flat = matrix.flat(); - const valid = flat.filter((v): v is number => v !== null && !isNaN(v)); - - // Avoid crashes when no valid values - const min = valid.length ? Math.min(...valid) : 0; - const max = valid.length ? Math.max(...valid) : 1; - const scale = max > min ? 255 / (max - min) : 1; - - const rgb = new Uint8Array(width * height * 3); - - for (let i = 0; i < flat.length; i++) { - const v = flat[i]; - let scaled = 0; - if (v !== null && !isNaN(v)) { - scaled = Math.round((v - min) * scale); - } // else stays 0 (black) - - switch (colour) { - case "red": - rgb[i * 3] = scaled; - break; - case "green": - rgb[i * 3 + 1] = scaled; - break; - case "blue": - rgb[i * 3 + 2] = scaled; - break; - case "gray": - rgb[i * 3] = scaled; - rgb[i * 3 + 1] = scaled; - rgb[i * 3 + 2] = scaled; - break; - } - } - - return ndarray(rgb, [height, width, 3]) as NDT; -} -/** Placeholder empty gray dataset */ -const EMPTY_NDT = toNDT([[0]], "gray"); - -/** Return type of `/api/data/map` */ -interface MapResponse { - values: (number | null)[][]; -} - -async function fetchMap( - filepath: string, - datapath: string, - colour: RGBColour, - snake: boolean, -) { - const url = `/api/data/map?filepath=${encodeURIComponent(filepath)}&datapath=${encodeURIComponent(datapath)}&snake=${encodeURIComponent(snake)}`; - const resp = await fetch(url); - if (!resp.ok) throw new Error(resp.statusText); - const mapResponse: MapResponse = await resp.json(); - return toNDT(mapResponse.values, colour); -} - const CHANNELS = [ { key: "red", label: "Red channel" }, { key: "green", label: "Green channel" }, { key: "blue", label: "Blue channel" }, - //{ key: "gray", label: "Gray channel" }, // using gray channel to stop typing errors ] as const; -type ChannelKey = (typeof CHANNELS)[number]["key"]; -type PlotValues = ComponentProps["values"]; -export type SpectroscopyData = Partial>; - interface SpectroscopyPlotsProps { expanded: boolean; plotAspectRatio: number; @@ -91,7 +19,7 @@ function SpectroscopyPlots({ expanded, plotAspectRatio, }: SpectroscopyPlotsProps) { - const { data: channels } = useSpectroscopyData(fetchMap); + const { data: channels } = useSpectroscopyData(); const { width, containerRef, mounted } = useContainerWidth(); const h = 10; const w = 1; @@ -107,12 +35,11 @@ function SpectroscopyPlots({ h: h, static: true, }, - // { i: "3", x: !expanded ? 3 : 1, !expanded ? 0 : h, w: w, h: h, static: true }, ]; const plots = useMemo( () => - CHANNELS.map(({ key }, i) => ( + CHANNELS.map(({ key, label }, i) => ( )), diff --git a/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts b/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts index 5c6fe7c1..f318adfd 100644 --- a/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts +++ b/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts @@ -1,33 +1,118 @@ -import { useEffect, useRef, useState } from "react"; import { type NDT } from "@diamondlightsource/davidia"; +import ndarray from "ndarray"; +import { useEffect, useRef, useState } from "react"; import { useScanEvents } from "../../hooks/scanEvents/useScanEvents"; -export type RGBColour = "red" | "green" | "blue" | "gray"; +/** SpectroscopyData wrapped as NDT for ease of Davidia plotting */ +export interface DataChannels { + red: NDT; + green: NDT; + blue: NDT; + xValues: NDT; + yValues: NDT; +} + +/** Return type of `/api/data/binned` */ +type SpectroscopyData = { + values: { + RedTotal: number[][]; + GreenTotal: number[][]; + BlueTotal: number[][]; + /** X bin edges with size of the above datasets + 1 */ + x_limits: number[]; + /** Y bin edges with size of the above datasets + 1 */ + y_limits: number[]; + }; +}; -export type FetchMapFunction = ( - filepath: string, - datapath: string, - colour: RGBColour, - snake: boolean, -) => Promise; +type RGBColour = "red" | "green" | "blue" | "gray"; -export interface DataChannels { - red: NDT | null; - green: NDT | null; - blue: NDT | null; - // gray: NDT | null; +function toRgbNdt(matrix: (number | null)[][], colour: RGBColour): NDT { + if (!matrix?.length || !matrix[0]?.length) { + return EMPTY_NDT; // skip invalid input + } + const height = matrix.length; + const width = matrix[0].length; + + // Flatten and filter out nulls for normalisation + const flat = matrix.flat(); + const valid = flat.filter((v): v is number => v !== null && !isNaN(v)); + + // Avoid crashes when no valid values + const min = valid.length ? Math.min(...valid) : 0; + const max = valid.length ? Math.max(...valid) : 1; + const scale = max > min ? 255 / (max - min) : 1; + + const rgb = new Uint8Array(width * height * 3); + + for (let i = 0; i < flat.length; i++) { + const v = flat[i]; + let scaled = 0; + if (v !== null && !isNaN(v)) { + scaled = Math.round((v - min) * scale); + } // else stays 0 (black) + + switch (colour) { + case "red": + rgb[i * 3] = scaled; + break; + case "green": + rgb[i * 3 + 1] = scaled; + break; + case "blue": + rgb[i * 3 + 2] = scaled; + break; + case "gray": + rgb[i * 3] = scaled; + rgb[i * 3 + 1] = scaled; + rgb[i * 3 + 2] = scaled; + break; + } + } + + return ndarray(rgb, [height, width, 3]) as NDT; +} +/** Placeholder empty gray dataset */ +const EMPTY_NDT = toRgbNdt([[0, 0, 0]], "gray"); + +/** given array of edges size L, returns the centre points, size L-1, as NDT */ +export function binEdgesToCentrePoints(edges: number[]): NDT { + if (edges.length < 2) { + throw new Error("At least two bin edges are required"); + } + + const centres = new Float64Array(edges.length - 1); + + for (let i = 0; i < centres.length; i++) { + centres[i] = (edges[i] + edges[i + 1]) / 2; + } + + return ndarray(centres, [centres.length]); } -export function useSpectroscopyData(fetchMap: FetchMapFunction) { +async function fetchData(uuid: string): Promise { + const url = `/api/data/binned/${uuid}`; + const resp = await fetch(url); + if (!resp.ok) throw new Error(resp.statusText); + return await resp.json(); // here we should use zod +} + +// initial axes must have min three points... +const initialAxes = binEdgesToCentrePoints([-0.25, 0.25, 0.5, 0.75]); + +export function useSpectroscopyData(): { + data: DataChannels; + running: boolean; +} { const scanEvent = useScanEvents(); const [running, setRunning] = useState(false); - const [filepath, setFilepath] = useState(null); - const [snake, setSnake] = useState(false); + const [uuid, setUuid] = useState(null); const [data, setData] = useState({ - red: null, - green: null, - blue: null, - // gray: null, + red: EMPTY_NDT, + green: EMPTY_NDT, + blue: EMPTY_NDT, + xValues: initialAxes, + yValues: initialAxes, }); /** Cached interval id */ @@ -39,8 +124,7 @@ export function useSpectroscopyData(fetchMap: FetchMapFunction) { if (scanEvent.status === "running") { setRunning(true); - setFilepath(scanEvent.filepath); - setSnake(scanEvent.snake); + setUuid(scanEvent.uuid); } else if ( scanEvent.status === "finished" || scanEvent.status === "failed" @@ -52,22 +136,22 @@ export function useSpectroscopyData(fetchMap: FetchMapFunction) { // Poll during scan + once more afterwards useEffect(() => { async function poll() { - if (!filepath) return; + if (!uuid) return; try { - const basePath = "/entry/instrument/spectroscopy_detector/"; - const [red, green, blue] = await Promise.all([ - fetchMap(filepath, basePath + "RedTotal", "red", snake), - fetchMap(filepath, basePath + "GreenTotal", "green", snake), - fetchMap(filepath, basePath + "BlueTotal", "blue", snake), - // fetchMap(filepath, basePath + "GrayTotal", "gray", snake), - ]); - setData({ red, green, blue }); + const resp: SpectroscopyData = await fetchData(uuid); + setData({ + red: toRgbNdt(resp.values.RedTotal, "red"), + green: toRgbNdt(resp.values.GreenTotal, "green"), + blue: toRgbNdt(resp.values.BlueTotal, "blue"), + xValues: binEdgesToCentrePoints(resp.values.x_limits), + yValues: binEdgesToCentrePoints(resp.values.y_limits), + }); } catch (err) { console.error("Polling error:", err); } } - if (running && filepath) { + if (running && uuid) { // start polling pollInterval.current = setInterval(poll, 500); // 2 Hz } else if (!running && pollInterval.current) { @@ -84,7 +168,7 @@ export function useSpectroscopyData(fetchMap: FetchMapFunction) { pollInterval.current = null; } }; - }, [running, filepath, snake, fetchMap]); + }, [running, uuid]); return { data, running }; } diff --git a/apps/visr/src/mocks/handlers.ts b/apps/visr/src/mocks/handlers.ts index 7e90eff5..b5193f13 100644 --- a/apps/visr/src/mocks/handlers.ts +++ b/apps/visr/src/mocks/handlers.ts @@ -1,7 +1,7 @@ import { http, HttpResponse, graphql } from "msw"; import workflowsResponse from "./workflows-response.json"; import plansResponse from "./plans-response.json"; -import { mapData } from "./mock_data"; +import { binnedReadback, binnedSetpoint, mapData } from "./mock_data"; import type { ScanEventMessage } from "../hooks/scanEvents"; import instrumentSessionResponse from "./instrumentSessions-response.json"; @@ -58,6 +58,18 @@ export const handlers = [ return HttpResponse.json("IDLE"); }), + http.get("/api/data/binned/:uuid", ({ request }) => { + const url = new URL(request.url); + const setPoints = url.searchParams.get("setpoints"); + let data; + if (String(setPoints).toLowerCase() !== "true") { + data = binnedSetpoint; + } else { + data = binnedReadback; + } + return HttpResponse.json({ values: data }); + }), + http.get("/api/data/map", ({ request }) => { const url = new URL(request.url); const filepath = url.searchParams.get("filepath"); diff --git a/apps/visr/src/mocks/mock_data.ts b/apps/visr/src/mocks/mock_data.ts index 45d7e44d..a18454a3 100644 --- a/apps/visr/src/mocks/mock_data.ts +++ b/apps/visr/src/mocks/mock_data.ts @@ -39,3 +39,281 @@ export function mapData(snake: boolean): (number | null)[][] { return data; } + +type SpectroscopyData = { + RedTotal: number[][]; + GreenTotal: number[][]; + BlueTotal: number[][]; + x_limits: number[]; + y_limits: number[]; +}; + +export const binnedReadback: SpectroscopyData = { + RedTotal: [ + [ + 5356642.0, 5361896.0, 5362905.0, 5363115.0, 5363075.0, 5363008.0, + 5362948.0, 5362958.0, 5362965.0, 5362983.0, + ], + [ + 5356716.0, 5361494.0, 5363048.0, 5363051.0, 5363084.0, 5363084.0, + 5362859.0, 5362954.0, 5362893.0, 5362898.0, + ], + [ + 5356606.0, 5361128.0, 5362973.0, 5363038.0, 5363053.0, 5363003.0, + 5362971.0, 5362943.0, 5362952.0, 5362963.0, + ], + [ + 5358041.0, 5361196.0, 5362879.0, 5362929.0, 5362988.0, 5362963.0, + 5362868.0, 5362997.0, 5363003.0, 5362891.0, + ], + [ + 5361441.0, 5362536.0, 5363009.0, 5363093.0, 5362958.0, 5362787.0, + 5362731.0, 5362938.0, 5362910.0, 5362803.0, + ], + [ + 5361416.0, 5362913.0, 5362983.0, 5362880.0, 5362918.0, 5362880.0, + 5362840.0, 5362934.0, 5362912.0, 5362762.0, + ], + [ + 5362230.0, 5362908.0, 5363108.0, 5362918.0, 5363028.0, 5363046.0, + 5362839.0, 5362811.0, 5362907.0, 5362788.0, + ], + [ + 5362886.0, 5362951.0, 5363030.0, 5362938.0, 5362878.0, 5362945.0, + 5362809.0, 5362789.0, 5362985.0, 5362673.0, + ], + [ + 5362296.0, 5362586.0, 5363113.0, 5362978.0, 5362910.0, 5363094.0, + 5362854.0, 5363025.0, 5362870.0, 5362569.0, + ], + [ + 5362527.0, 5362635.0, 5362874.0, 5362946.0, 5363080.0, 5363161.0, + 5363068.0, 5363054.0, 5362906.0, 5362833.0, + ], + ], + GreenTotal: [ + [ + 5359999.0, 5366248.0, 5369356.0, 5369713.0, 5369738.0, 5369578.0, + 5369539.0, 5369620.0, 5369568.0, 5369469.0, + ], + [ + 5359837.0, 5365687.0, 5369454.0, 5369739.0, 5369820.0, 5369620.0, + 5369503.0, 5369541.0, 5369476.0, 5369488.0, + ], + [ + 5359697.0, 5365712.0, 5369615.0, 5369662.0, 5369737.0, 5369726.0, + 5369647.0, 5369624.0, 5369524.0, 5369535.0, + ], + [ + 5362116.0, 5367078.0, 5369566.0, 5369623.0, 5369578.0, 5369370.0, + 5369565.0, 5369557.0, 5369506.0, 5369474.0, + ], + [ + 5367739.0, 5369221.0, 5369619.0, 5369619.0, 5369604.0, 5369333.0, + 5369210.0, 5369318.0, 5369468.0, 5369463.0, + ], + [ + 5366934.0, 5369530.0, 5369637.0, 5369498.0, 5369541.0, 5369353.0, + 5369410.0, 5369433.0, 5369598.0, 5369540.0, + ], + [ + 5368690.0, 5369469.0, 5369706.0, 5369608.0, 5369603.0, 5369535.0, + 5369377.0, 5369297.0, 5369390.0, 5369330.0, + ], + [ + 5369536.0, 5369656.0, 5369681.0, 5369556.0, 5369651.0, 5369658.0, + 5369274.0, 5369383.0, 5369191.0, 5369137.0, + ], + [ + 5368718.0, 5368863.0, 5369505.0, 5369757.0, 5369749.0, 5369782.0, + 5369420.0, 5369426.0, 5369321.0, 5368917.0, + ], + [ + 5368802.0, 5368772.0, 5369342.0, 5369842.0, 5369781.0, 5369973.0, + 5369754.0, 5369617.0, 5369502.0, 5369183.0, + ], + ], + BlueTotal: [ + [ + 5406561.0, 5426254.0, 5435585.0, 5436282.0, 5436498.0, 5436152.0, + 5436077.0, 5436164.0, 5436158.0, 5435789.0, + ], + [ + 5406758.0, 5425829.0, 5435595.0, 5436201.0, 5436474.0, 5436183.0, + 5435912.0, 5435898.0, 5435974.0, 5435893.0, + ], + [ + 5409401.0, 5427377.0, 5435828.0, 5436395.0, 5436466.0, 5436474.0, + 5436056.0, 5436051.0, 5435957.0, 5435982.0, + ], + [ + 5419218.0, 5432416.0, 5436186.0, 5436237.0, 5436449.0, 5435685.0, + 5436228.0, 5436248.0, 5436005.0, 5435950.0, + ], + [ + 5427947.0, 5436039.0, 5436456.0, 5436237.0, 5436140.0, 5435293.0, + 5435312.0, 5435506.0, 5435906.0, 5435746.0, + ], + [ + 5428852.0, 5436287.0, 5436285.0, 5436093.0, 5436040.0, 5435452.0, + 5435746.0, 5435583.0, 5435858.0, 5435928.0, + ], + [ + 5435102.0, 5436116.0, 5435913.0, 5436102.0, 5436219.0, 5435679.0, + 5435735.0, 5435559.0, 5435595.0, 5435770.0, + ], + [ + 5436120.0, 5436402.0, 5436033.0, 5436143.0, 5436286.0, 5436068.0, + 5435598.0, 5435679.0, 5435117.0, 5435155.0, + ], + [ + 5433927.0, 5435173.0, 5436234.0, 5436438.0, 5436612.0, 5436497.0, + 5435827.0, 5435780.0, 5434975.0, 5434771.0, + ], + [ + 5432784.0, 5434193.0, 5435645.0, 5436745.0, 5436849.0, 5436900.0, + 5436661.0, 5436145.0, 5435786.0, 5434724.0, + ], + ], + x_limits: [ + -0.04026740746438049, 0.46780691515354156, 0.9758812377714636, + 1.4839555603893857, 1.9920298830073078, 2.50010420562523, 3.008178528243152, + 3.5162528508610738, 4.024327173478996, 4.532401496096918, 5.04047581871484, + ], + y_limits: [ + 4.999995352981426, 5.500232308309942, 6.000469263638458, 6.5007062189669735, + 7.0009431742954895, 7.5011801296240055, 8.001417084952521, + 8.501654040281037, 9.001890995609553, 9.50212795093807, 10.002364906266585, + ], +}; + +export const binnedSetpoint: SpectroscopyData = { + RedTotal: [ + [ + 5356642.0, 5356716.0, 5356606.0, 5358041.0, 5361441.0, 5361416.0, + 5362230.0, 5362886.0, 5362296.0, 5362527.0, + ], + [ + 5361896.0, 5361494.0, 5361128.0, 5361196.0, 5362536.0, 5362913.0, + 5362908.0, 5362951.0, 5362586.0, 5362635.0, + ], + [ + 5362905.0, 5363048.0, 5362973.0, 5362879.0, 5363009.0, 5362983.0, + 5363108.0, 5363030.0, 5363113.0, 5362874.0, + ], + [ + 5363115.0, 5363051.0, 5363038.0, 5362929.0, 5363093.0, 5362880.0, + 5362918.0, 5362938.0, 5362978.0, 5362946.0, + ], + [ + 5363075.0, 5363084.0, 5363053.0, 5362988.0, 5362958.0, 5362918.0, + 5363028.0, 5362878.0, 5362910.0, 5363080.0, + ], + [ + 5363008.0, 5363084.0, 5363003.0, 5362963.0, 5362787.0, 5362880.0, + 5363046.0, 5362945.0, 5363094.0, 5363161.0, + ], + [ + 5362948.0, 5362859.0, 5362971.0, 5362868.0, 5362731.0, 5362840.0, + 5362839.0, 5362809.0, 5362854.0, 5363068.0, + ], + [ + 5362958.0, 5362954.0, 5362943.0, 5362997.0, 5362938.0, 5362934.0, + 5362811.0, 5362789.0, 5363025.0, 5363054.0, + ], + [ + 5362965.0, 5362893.0, 5362952.0, 5363003.0, 5362910.0, 5362912.0, + 5362907.0, 5362985.0, 5362870.0, 5362906.0, + ], + [ + 5362983.0, 5362898.0, 5362963.0, 5362891.0, 5362803.0, 5362762.0, + 5362788.0, 5362673.0, 5362569.0, 5362833.0, + ], + ], + GreenTotal: [ + [ + 5359999.0, 5359837.0, 5359697.0, 5362116.0, 5367739.0, 5366934.0, + 5368690.0, 5369536.0, 5368718.0, 5368802.0, + ], + [ + 5366248.0, 5365687.0, 5365712.0, 5367078.0, 5369221.0, 5369530.0, + 5369469.0, 5369656.0, 5368863.0, 5368772.0, + ], + [ + 5369356.0, 5369454.0, 5369615.0, 5369566.0, 5369619.0, 5369637.0, + 5369706.0, 5369681.0, 5369505.0, 5369342.0, + ], + [ + 5369713.0, 5369739.0, 5369662.0, 5369623.0, 5369619.0, 5369498.0, + 5369608.0, 5369556.0, 5369757.0, 5369842.0, + ], + [ + 5369738.0, 5369820.0, 5369737.0, 5369578.0, 5369604.0, 5369541.0, + 5369603.0, 5369651.0, 5369749.0, 5369781.0, + ], + [ + 5369578.0, 5369620.0, 5369726.0, 5369370.0, 5369333.0, 5369353.0, + 5369535.0, 5369658.0, 5369782.0, 5369973.0, + ], + [ + 5369539.0, 5369503.0, 5369647.0, 5369565.0, 5369210.0, 5369410.0, + 5369377.0, 5369274.0, 5369420.0, 5369754.0, + ], + [ + 5369620.0, 5369541.0, 5369624.0, 5369557.0, 5369318.0, 5369433.0, + 5369297.0, 5369383.0, 5369426.0, 5369617.0, + ], + [ + 5369568.0, 5369476.0, 5369524.0, 5369506.0, 5369468.0, 5369598.0, + 5369390.0, 5369191.0, 5369321.0, 5369502.0, + ], + [ + 5369469.0, 5369488.0, 5369535.0, 5369474.0, 5369463.0, 5369540.0, + 5369330.0, 5369137.0, 5368917.0, 5369183.0, + ], + ], + BlueTotal: [ + [ + 5406561.0, 5406758.0, 5409401.0, 5419218.0, 5427947.0, 5428852.0, + 5435102.0, 5436120.0, 5433927.0, 5432784.0, + ], + [ + 5426254.0, 5425829.0, 5427377.0, 5432416.0, 5436039.0, 5436287.0, + 5436116.0, 5436402.0, 5435173.0, 5434193.0, + ], + [ + 5435585.0, 5435595.0, 5435828.0, 5436186.0, 5436456.0, 5436285.0, + 5435913.0, 5436033.0, 5436234.0, 5435645.0, + ], + [ + 5436282.0, 5436201.0, 5436395.0, 5436237.0, 5436237.0, 5436093.0, + 5436102.0, 5436143.0, 5436438.0, 5436745.0, + ], + [ + 5436498.0, 5436474.0, 5436466.0, 5436449.0, 5436140.0, 5436040.0, + 5436219.0, 5436286.0, 5436612.0, 5436849.0, + ], + [ + 5436152.0, 5436183.0, 5436474.0, 5435685.0, 5435293.0, 5435452.0, + 5435679.0, 5436068.0, 5436497.0, 5436900.0, + ], + [ + 5436077.0, 5435912.0, 5436056.0, 5436228.0, 5435312.0, 5435746.0, + 5435735.0, 5435598.0, 5435827.0, 5436661.0, + ], + [ + 5436164.0, 5435898.0, 5436051.0, 5436248.0, 5435506.0, 5435583.0, + 5435559.0, 5435679.0, 5435780.0, 5436145.0, + ], + [ + 5436158.0, 5435974.0, 5435957.0, 5436005.0, 5435906.0, 5435858.0, + 5435595.0, 5435117.0, 5434975.0, 5435786.0, + ], + [ + 5435789.0, 5435893.0, 5435982.0, 5435950.0, 5435746.0, 5435928.0, + 5435770.0, 5435155.0, 5434771.0, 5434724.0, + ], + ], + x_limits: [5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0], + y_limits: [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0], +}; From 6df4f542280d75bdac7ae75e2eda8643587c0378 Mon Sep 17 00:00:00 2001 From: Douglas Winter Date: Wed, 19 Aug 2026 08:10:55 +0000 Subject: [PATCH 2/4] 10Hz polling & more debug logging --- .../src/components/spectroscopy/SpectroscopyPlots.tsx | 11 +++++++++++ .../components/spectroscopy/useSpectroscopyData.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx index cc84f0b6..6c633971 100644 --- a/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx +++ b/apps/visr/src/components/spectroscopy/SpectroscopyPlots.tsx @@ -20,6 +20,17 @@ function SpectroscopyPlots({ plotAspectRatio, }: SpectroscopyPlotsProps) { const { data: channels } = useSpectroscopyData(); + console.debug( + "channel shapes (r, g, b)", + channels.red.shape, + channels.green.shape, + channels.blue.shape, + ); + console.debug( + "x, y axes sizes", + channels.xValues.size, + channels.yValues.size, + ); const { width, containerRef, mounted } = useContainerWidth(); const h = 10; const w = 1; diff --git a/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts b/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts index f318adfd..a49c800c 100644 --- a/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts +++ b/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts @@ -153,7 +153,7 @@ export function useSpectroscopyData(): { if (running && uuid) { // start polling - pollInterval.current = setInterval(poll, 500); // 2 Hz + pollInterval.current = setInterval(poll, 100); // 10 Hz } else if (!running && pollInterval.current) { // poll once more then clear interval poll().finally(() => { From 82bb7caa5b18741ea870d39544b94ac6ffb32380 Mon Sep 17 00:00:00 2001 From: Douglas Winter Date: Wed, 19 Aug 2026 08:44:18 +0000 Subject: [PATCH 3/4] Update visr's ajv to match jsonforms' --- apps/visr/package.json | 2 +- pnpm-lock.yaml | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/apps/visr/package.json b/apps/visr/package.json index 700bfa31..d5a10a51 100644 --- a/apps/visr/package.json +++ b/apps/visr/package.json @@ -49,7 +49,7 @@ "@types/relay-runtime": "^19.0.2", "@types/three": "^0.164.0", "@vitejs/plugin-react-swc": "^3.11.0", - "ajv": "^8.17.1", + "ajv": "^8.20.0", "babel-plugin-relay": "^20.1.1", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48ba0dd1..3275127c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -355,8 +355,8 @@ importers: specifier: ^3.11.0 version: 3.11.0(vite@7.3.1(@types/node@25.9.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) ajv: - specifier: ^8.17.1 - version: 8.18.0 + specifier: ^8.20.0 + version: 8.20.0 babel-plugin-relay: specifier: ^20.1.1 version: 20.1.1 @@ -492,7 +492,7 @@ importers: dependencies: '@diamondlightsource/cs-web-lib': specifier: 0.10.19 - version: 0.10.19(d61807e829453f439b0a625b99a7eaae) + version: 0.10.19(b24c5f51dba915ae6a679a7010c5c2b9) '@mui/material': specifier: ^7.0.0 version: 7.3.11(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2544,9 +2544,6 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -5663,7 +5660,7 @@ snapshots: transitivePeerDependencies: - graphql - '@diamondlightsource/cs-web-lib@0.10.19(d61807e829453f439b0a625b99a7eaae)': + '@diamondlightsource/cs-web-lib@0.10.19(b24c5f51dba915ae6a679a7010c5c2b9)': dependencies: '@mui/icons-material': 7.3.11(@mui/material@7.3.11(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@mui/material': 7.3.11(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7823,13 +7820,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 From 29f6697daf2cced0e63a9078d94220289d35d58a Mon Sep 17 00:00:00 2001 From: Douglas Winter Date: Wed, 19 Aug 2026 09:55:26 +0000 Subject: [PATCH 4/4] Fix tiled binned endpoint response structure --- .../spectroscopy/useSpectroscopyData.ts | 26 +++++++++---------- apps/visr/src/mocks/handlers.ts | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts b/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts index a49c800c..8e8b7db5 100644 --- a/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts +++ b/apps/visr/src/components/spectroscopy/useSpectroscopyData.ts @@ -14,15 +14,13 @@ export interface DataChannels { /** Return type of `/api/data/binned` */ type SpectroscopyData = { - values: { - RedTotal: number[][]; - GreenTotal: number[][]; - BlueTotal: number[][]; - /** X bin edges with size of the above datasets + 1 */ - x_limits: number[]; - /** Y bin edges with size of the above datasets + 1 */ - y_limits: number[]; - }; + RedTotal: number[][]; + GreenTotal: number[][]; + BlueTotal: number[][]; + /** X bin edges with size of the above datasets + 1 */ + x_limits: number[]; + /** Y bin edges with size of the above datasets + 1 */ + y_limits: number[]; }; type RGBColour = "red" | "green" | "blue" | "gray"; @@ -140,11 +138,11 @@ export function useSpectroscopyData(): { try { const resp: SpectroscopyData = await fetchData(uuid); setData({ - red: toRgbNdt(resp.values.RedTotal, "red"), - green: toRgbNdt(resp.values.GreenTotal, "green"), - blue: toRgbNdt(resp.values.BlueTotal, "blue"), - xValues: binEdgesToCentrePoints(resp.values.x_limits), - yValues: binEdgesToCentrePoints(resp.values.y_limits), + red: toRgbNdt(resp.RedTotal, "red"), + green: toRgbNdt(resp.GreenTotal, "green"), + blue: toRgbNdt(resp.BlueTotal, "blue"), + xValues: binEdgesToCentrePoints(resp.x_limits), + yValues: binEdgesToCentrePoints(resp.y_limits), }); } catch (err) { console.error("Polling error:", err); diff --git a/apps/visr/src/mocks/handlers.ts b/apps/visr/src/mocks/handlers.ts index b5193f13..5ef848fa 100644 --- a/apps/visr/src/mocks/handlers.ts +++ b/apps/visr/src/mocks/handlers.ts @@ -67,7 +67,7 @@ export const handlers = [ } else { data = binnedReadback; } - return HttpResponse.json({ values: data }); + return HttpResponse.json(data); }), http.get("/api/data/map", ({ request }) => {