From e37e692b00561841dbeedf0b55b28b32043f3e7e Mon Sep 17 00:00:00 2001 From: Andrea Liao Date: Tue, 25 Mar 2025 12:05:36 +0000 Subject: [PATCH 1/2] make zod schemas for xspress3 configuration --- .../app/schemas/xspress3.ts | 37 +++++++ .../gda-scan-definition/app/xspress3/page.tsx | 15 +++ .../reference/Xspresss3.xml | 97 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 apps/gda-scan-definition/app/schemas/xspress3.ts create mode 100644 apps/gda-scan-definition/app/xspress3/page.tsx create mode 100644 apps/gda-scan-definition/reference/Xspresss3.xml diff --git a/apps/gda-scan-definition/app/schemas/xspress3.ts b/apps/gda-scan-definition/app/schemas/xspress3.ts new file mode 100644 index 0000000..2c3126d --- /dev/null +++ b/apps/gda-scan-definition/app/schemas/xspress3.ts @@ -0,0 +1,37 @@ +import {z} from 'zod'; + +export const Xspress3DetectorElementRegion = z.object({ + roiName: z.string(), + roiStart: z.number(), + roiEnd: z.number(), +}); + + + +export const Xspress3DetectorElement = z.object({ + name: z.string(), + number: z.number(), + windowStart: z.number(), + windowEnd: z.number(), + excluded: z.boolean(), + Region: Xspress3DetectorElementRegion +}); + +export type Xspress3DetectorElement = z.infer; + +export const Xspress3ConfigSchema = z.object({ + detectorName: z.string(), + resGrade: z.string(), + regionType: z.string(), + readoutMode: z.string(), + editIndividualElements: z.boolean(), + deadtimeCorrectionEnergy: z.number(), + + onlyShowFF: z.boolean(), + showDTRawValues: z.boolean(), + saveRawSpectrum: z.boolean(), + selectedRegionNumber: z.number(), + DetectorElements: z.array(Xspress3DetectorElement) +}); + +export type Xspress3ConfigSchema = z.infer; diff --git a/apps/gda-scan-definition/app/xspress3/page.tsx b/apps/gda-scan-definition/app/xspress3/page.tsx new file mode 100644 index 0000000..82bab7c --- /dev/null +++ b/apps/gda-scan-definition/app/xspress3/page.tsx @@ -0,0 +1,15 @@ + + + +import { Typography } from '@mui/material' +import React from 'react' + +function Xspress3ConfigPage() { + return ( +
+ xpress3 config here +
+ ) +} + +export default Xspress3ConfigPage diff --git a/apps/gda-scan-definition/reference/Xspresss3.xml b/apps/gda-scan-definition/reference/Xspresss3.xml new file mode 100644 index 0000000..43c2f4a --- /dev/null +++ b/apps/gda-scan-definition/reference/Xspresss3.xml @@ -0,0 +1,97 @@ + + + xspress3X + res-none + Virtual Scaler + Scalers and MCA + false + 10.0 + + Element 0 + 0 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + + Element 1 + 1 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + + Element 2 + 2 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + + Element 3 + 3 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + + Element 4 + 4 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + + Element 5 + 5 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + + Element 6 + 6 + 742 + 850 + false + + ROI_1 + 760 + 841 + + + false + false + false + 0 + From f099076ede52cfef79242efb906b3023dbbb9a2a Mon Sep 17 00:00:00 2001 From: Andrea Liao Date: Tue, 25 Mar 2025 15:12:31 +0000 Subject: [PATCH 2/2] data loading for xspress3 draft --- .../app/schemas/xspress3.ts | 4 +- .../app/xspress3/action.ts | 17 ++++ .../components/Xspress3Configurator.tsx | 47 +++++++++++ .../app/xspress3/hooks.tsx | 77 +++++++++++++++++++ .../gda-scan-definition/app/xspress3/page.tsx | 2 + .../app/xspress3/server-xml.ts | 69 +++++++++++++++++ .../reference/{Xspresss3.xml => Xspress3.xml} | 0 .../gda-scan-definition/scripts/setupFiles.js | 1 + 8 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 apps/gda-scan-definition/app/xspress3/action.ts create mode 100644 apps/gda-scan-definition/app/xspress3/components/Xspress3Configurator.tsx create mode 100644 apps/gda-scan-definition/app/xspress3/hooks.tsx create mode 100644 apps/gda-scan-definition/app/xspress3/server-xml.ts rename apps/gda-scan-definition/reference/{Xspresss3.xml => Xspress3.xml} (100%) diff --git a/apps/gda-scan-definition/app/schemas/xspress3.ts b/apps/gda-scan-definition/app/schemas/xspress3.ts index 2c3126d..bad03da 100644 --- a/apps/gda-scan-definition/app/schemas/xspress3.ts +++ b/apps/gda-scan-definition/app/schemas/xspress3.ts @@ -6,8 +6,6 @@ export const Xspress3DetectorElementRegion = z.object({ roiEnd: z.number(), }); - - export const Xspress3DetectorElement = z.object({ name: z.string(), number: z.number(), @@ -34,4 +32,4 @@ export const Xspress3ConfigSchema = z.object({ DetectorElements: z.array(Xspress3DetectorElement) }); -export type Xspress3ConfigSchema = z.infer; +export type Xspress3ConfigSchemaType = z.infer; diff --git a/apps/gda-scan-definition/app/xspress3/action.ts b/apps/gda-scan-definition/app/xspress3/action.ts new file mode 100644 index 0000000..770b071 --- /dev/null +++ b/apps/gda-scan-definition/app/xspress3/action.ts @@ -0,0 +1,17 @@ +import { actionClient } from "../clients/actionclient"; +import { Xspress3ConfigSchema, Xspress3ConfigSchemaType } from "../schemas/xspress3"; +import { readXspress3Parameters, updateXspress3Parameters } from "./server-xml"; + +export const readXspress3Definition = actionClient.action(async (): Promise<{ + success: boolean, data: Xspress3ConfigSchemaType | null + }> => { + const result: Xspress3ConfigSchemaType = await readXspress3Parameters(); + return { success: true, data: result } +}); + +export const updateXspress3Definition = actionClient + .schema(Xspress3ConfigSchema).action(async ({ parsedInput}): Promise<{ success: boolean }> => { + console.log(`Updating xspres3 configuration: ${parsedInput}`); + await updateXspress3Parameters(parsedInput); + return { success: true } + }); diff --git a/apps/gda-scan-definition/app/xspress3/components/Xspress3Configurator.tsx b/apps/gda-scan-definition/app/xspress3/components/Xspress3Configurator.tsx new file mode 100644 index 0000000..837edb7 --- /dev/null +++ b/apps/gda-scan-definition/app/xspress3/components/Xspress3Configurator.tsx @@ -0,0 +1,47 @@ +"use client"; + +import React from 'react' +import { useXspress3Configuration } from '../hooks' +import { Box, Button, InputLabel, Typography } from '@mui/material'; +import { CheckBox } from '@mui/icons-material'; +import Input from "@mui/material/Input"; + + +function Xspress3Configurator() { + const { config, loading } = useXspress3Configuration(); + + console.dir(config); + if (loading) { + return Loading... + } + return ( + + + {config ? ( + <> + + Detector Name: + { + window.alert("not implemented yet") + }} /> + +
{JSON.stringify(config, null, 2)}
+ + + + ) : ( +

No configuration available. Please fetch the configuration.

+ )} + + +
+ ) +} + +export default Xspress3Configurator diff --git a/apps/gda-scan-definition/app/xspress3/hooks.tsx b/apps/gda-scan-definition/app/xspress3/hooks.tsx new file mode 100644 index 0000000..f33b7e7 --- /dev/null +++ b/apps/gda-scan-definition/app/xspress3/hooks.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from "react"; +import { Xspress3ConfigSchemaType } from "../schemas/xspress3"; +import { readXspress3Definition } from "./action"; + + +// export const updateConfig = async ( +// dispatch: React.Dispatch, +// newConfig: FullQexafsSchemaType +// ) => { +// dispatch({ type: "START_CONFIG_UPDATE" }); + +// try { +// const response = await updateScanDefinition(newConfig); +// if (response === undefined) { +// dispatch({ type: "CONFIG_ERROR", payload: "Error updating configuration" }); +// } else { +// dispatch({ type: "CONFIG_UPDATE_SUCCESS" }); +// } +// } catch (error) { +// dispatch({ type: "CONFIG_ERROR", payload: "Config server error" }); +// } +// }; + +// todo add the update logic +export const useXspress3Configuration = () => { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchConfig = async () => { + try { + const response = await readXspress3Definition(); + if(response?.data){ + setConfig(response.data.data); // Assuming API returns an array of strings + } + + } catch (err: any) { + console.error(err); + } finally { + setLoading(false); + } + }; + + fetchConfig(); + }, []); + + return { config, loading }; +}; + + +// export const usePlanByName = (name: string) => { +// const [plan, setPlan] = useState<{ name: string; schema: any } | null>(null); +// const [loading, setLoading] = useState(true); +// const [error, setError] = useState(null); + +// useEffect(() => { +// if (!name) return; + +// setLoading(true); +// api.get(`/plans/${name}`) +// .then((response) => { +// setPlan(response.data); +// }) +// .catch((err) => { +// console.log(`response: ${err}`) +// console.log(`name: ${name}`); +// console.dir(name); +// setError(err.message); +// }) +// .finally(() => { +// setLoading(false); +// }); +// }, [name]); + +// return { plan, loading, error }; + +// } \ No newline at end of file diff --git a/apps/gda-scan-definition/app/xspress3/page.tsx b/apps/gda-scan-definition/app/xspress3/page.tsx index 82bab7c..dc271fd 100644 --- a/apps/gda-scan-definition/app/xspress3/page.tsx +++ b/apps/gda-scan-definition/app/xspress3/page.tsx @@ -3,11 +3,13 @@ import { Typography } from '@mui/material' import React from 'react' +import Xspress3Configurator from './components/Xspress3Configurator' function Xspress3ConfigPage() { return (
xpress3 config here +
) } diff --git a/apps/gda-scan-definition/app/xspress3/server-xml.ts b/apps/gda-scan-definition/app/xspress3/server-xml.ts new file mode 100644 index 0000000..b96f341 --- /dev/null +++ b/apps/gda-scan-definition/app/xspress3/server-xml.ts @@ -0,0 +1,69 @@ +"use server"; +import { basePath } from "../actions/basePath"; +import { fixXmlToWrapInList } from "../qexafs/fixXmlToWrapInList"; +import { PREPEND_FOR_XML } from "../qexafs/server-xml"; +import { Xspress3ConfigSchema, Xspress3ConfigSchemaType } from "../schemas/xspress3"; +import { XMLBuilder, XmlBuilderOptions, XMLParser } from "fast-xml-parser"; +import fs from "fs"; + +const parserOptions = { + ignoreAttributes: false, // Keep attributes if present + // alwaysCreateTextNode: true, // Ensures text nodes are explicitly stored + isArray: (name: string, jpath) => { + return false; + // Define elements that should be arrays when duplicated + const arrayFields = ["sampleParameterMotorPosition", "detectorConfiguration"]; // Example duplicated elements + return arrayFields.includes(name); // Only convert these to arrays + }, +}; +const options: XmlBuilderOptions = { + // processEntities: false, + // preserveOrder: true, + format: true, + // ignoreAttributes: false, + // commentPropName: "phone" +}; + + +const xspress3Path = `${basePath}/Xspress3.xml`; +const parser = new XMLParser(parserOptions); +const builder = new XMLBuilder(options); + +export async function readXspress3Parameters(): Promise { + const content = fs.readFileSync(xspress3Path); + console.log(`raw content: ${content}`); + const fixed = fixXmlToWrapInList(content.toString(), "DetectorElement", "DetectorElements") + const parsedResult = parser.parse(fixed); + + console.log(fixed) + console.log(parsedResult); + const p = parsedResult.XspressParameters; + console.log(`trying to read the parameters, ${p}`) + console.dir(p) + const tmpFirstItem = p.DetectorElements; + const tmpArrayCopy = p.DetectorElements; + delete p.DetectorElements; + p.DetectorElement = tmpFirstItem; + // todo temporary reset to [] + p.DetectorElements = []; + + try { + const params:Xspress3ConfigSchemaType = Xspress3ConfigSchema.parse(p); + params.DetectorElements = tmpArrayCopy; + console.log(`should be parsed by now`) + console.dir(params); + return params + } catch (e) { + console.log("Error", e); + } + throw new Error("Failed to parse xspress3 parameters at path " + xspress3Path); +} + +export async function updateXspress3Parameters(data: Xspress3ConfigSchemaType): Promise { + const fullObject = { + "XspressParameters": data + } + const xml = builder.build(fullObject); + fs.writeFileSync(xspress3Path, `${PREPEND_FOR_XML}\n${xml}`); +} + diff --git a/apps/gda-scan-definition/reference/Xspresss3.xml b/apps/gda-scan-definition/reference/Xspress3.xml similarity index 100% rename from apps/gda-scan-definition/reference/Xspresss3.xml rename to apps/gda-scan-definition/reference/Xspress3.xml diff --git a/apps/gda-scan-definition/scripts/setupFiles.js b/apps/gda-scan-definition/scripts/setupFiles.js index 4ca1523..b372929 100644 --- a/apps/gda-scan-definition/scripts/setupFiles.js +++ b/apps/gda-scan-definition/scripts/setupFiles.js @@ -52,6 +52,7 @@ const fileNames = [ "QEXAFS_Parameters.xml", "Sample_Parameters.xml", "long.xml", + "Xspress3.xml" ]; // Array of file names to copy // const basePath = "./targetFolder"; // Base folder where files will be copied to