diff --git a/package-lock.json b/package-lock.json index 9cb8f0047..090351e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6174,7 +6174,7 @@ } }, "node_modules/niivue-react": { - "resolved": "git+ssh://git@github.com/niivue/niivue-react.git#e07626fcfe5eb2dc2907fce91c87bfde8848a40e", + "resolved": "git+ssh://git@github.com/niivue/niivue-react.git#9ef21bd3f8ea810b63f45b49155f71bf7f8bf255", "peerDependencies": { "@niivue/niivue": "^0.39.0", "react": "^17 || ^18", diff --git a/src/api/model.ts b/src/api/model.ts index 0463025f4..c347e360f 100644 --- a/src/api/model.ts +++ b/src/api/model.ts @@ -326,14 +326,14 @@ export const fileViewerMap: any = { gif: "ImageDisplay", dcm: "DcmDisplay", default: "CatchallDisplay", - nii: "DcmDisplay", + nii: "NiiVueDisplay", gz: "CatchallDisplay", - mgz: "XtkDisplay", + mgz: "NiiVueDisplay", fsm: "XtkDisplay", crv: "XtkDisplay", smoothwm: "XtkDisplay", pial: "XtkDisplay", - "nii.gz": "DcmDisplay", + "nii.gz": "NiiVueDisplay", }; // Description: get file type by file extension diff --git a/src/components/Preview/displays/NiiVueDisplay.module.css b/src/components/Preview/displays/NiiVueDisplay.module.css new file mode 100644 index 000000000..091241a60 --- /dev/null +++ b/src/components/Preview/displays/NiiVueDisplay.module.css @@ -0,0 +1,12 @@ +.container { + width: 100%; + height: 100%; +} + + +.controlBar { + position: absolute; + top: -10px; + height: 8px; + +} diff --git a/src/components/Preview/displays/NiiVueDisplay.tsx b/src/components/Preview/displays/NiiVueDisplay.tsx new file mode 100644 index 000000000..f20e9124b --- /dev/null +++ b/src/components/Preview/displays/NiiVueDisplay.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import { IFileBlob } from "../../../api/model.ts"; +import { NVROptions, NVRVolume } from "niivue-react/src/model.ts"; +import SizedNiivueCanvas from "../../SizedNiivueCanvas"; +import { SLICE_TYPE } from "@niivue/niivue"; +import styles from "./NiiVueDisplay.module.css"; + +type NiiVueDisplayProps = { + fileItem: IFileBlob; +}; + +type PreviewOptions = Required< + Pick< + NVROptions, + | "sliceType" + | "isColorbar" + | "backColor" + | "isRadiologicalConvention" + | "crosshairWidth" + > +>; + +const SLICE_TYPES = { + A: SLICE_TYPE.AXIAL, + C: SLICE_TYPE.CORONAL, + S: SLICE_TYPE.SAGITTAL, + M: SLICE_TYPE.MULTIPLANAR, +}; + +const NiiVueDisplay: React.FC = ({ fileItem }) => { + const [colormap, setColormap] = React.useState("gray"); + const [sliceTypeName, setSliceTypeName] = + React.useState("M"); + + const volumes: NVRVolume[] = []; + + const options: PreviewOptions = { + backColor: [0, 0, 0], + isRadiologicalConvention: true, + sliceType: SLICE_TYPES[sliceTypeName], + isColorbar: false, + crosshairWidth: sliceTypeName === "M" ? 0.5 : 0, + }; + + if (fileItem.blob !== undefined && fileItem.file !== undefined) { + volumes.push({ + // NiiVue gets the file extension from name + name: fileItem.file.data.fname, + url: window.URL.createObjectURL(fileItem.blob), + colormap: colormap, + }); + } + + const toggleColormap = () => { + setColormap(colormap === "gray" ? "freesurfer" : "gray"); + }; + + const rotateSliceType = () => { + const names = Object.keys(SLICE_TYPES) as (keyof typeof SLICE_TYPES)[]; + const i = names.indexOf(sliceTypeName); + const next = i + 1 >= names.length ? 0 : i + 1; + setSliceTypeName(names[next]); + }; + + return ( + <> + {volumes.length === 0 ? ( +

error

+ ) : ( +
+
+ + +
+ +
+ )} + + ); +}; + +const MemoedNiiVueDisplay = React.memo(NiiVueDisplay); +export default MemoedNiiVueDisplay; diff --git a/src/components/Preview/displays/ViewerDisplay.tsx b/src/components/Preview/displays/ViewerDisplay.tsx index fc372ee6d..77e656904 100644 --- a/src/components/Preview/displays/ViewerDisplay.tsx +++ b/src/components/Preview/displays/ViewerDisplay.tsx @@ -9,6 +9,7 @@ import { PdfDisplay, XtkDisplay, TextDisplay, + NiiVueDisplay, } from "./index"; import { ActionState } from "../FileDetailView"; @@ -21,6 +22,7 @@ const components = { PdfDisplay, XtkDisplay, TextDisplay, + NiiVueDisplay, }; interface ViewerDisplayProps { diff --git a/src/components/Preview/displays/index.ts b/src/components/Preview/displays/index.ts index cc921eea2..10f7d292f 100644 --- a/src/components/Preview/displays/index.ts +++ b/src/components/Preview/displays/index.ts @@ -7,3 +7,4 @@ export { default as DcmDisplay } from "./DcmDisplay"; export { default as PdfDisplay } from "./PdfDisplay"; export { default as XtkDisplay } from "./XtkDisplay"; export { default as TextDisplay } from "./TextDisplay"; +export { default as NiiVueDisplay } from "./NiiVueDisplay.tsx"; diff --git a/src/components/VisualDatasets/components/SizedNiivueCanvas.module.css b/src/components/SizedNiivueCanvas/index.module.css similarity index 100% rename from src/components/VisualDatasets/components/SizedNiivueCanvas.module.css rename to src/components/SizedNiivueCanvas/index.module.css diff --git a/src/components/VisualDatasets/components/SizedNiivueCanvas.tsx b/src/components/SizedNiivueCanvas/index.tsx similarity index 95% rename from src/components/VisualDatasets/components/SizedNiivueCanvas.tsx rename to src/components/SizedNiivueCanvas/index.tsx index 57bb937de..a8b94f23e 100644 --- a/src/components/VisualDatasets/components/SizedNiivueCanvas.tsx +++ b/src/components/SizedNiivueCanvas/index.tsx @@ -4,7 +4,7 @@ import { NiivueCanvasProps, NiivueCanvas, } from "niivue-react/src/NiivueCanvas.tsx"; -import styles from "./SizedNiivueCanvas.module.css"; +import styles from "./index.module.css"; /** * Type emitted by Niivue.onLocationChange @@ -16,8 +16,8 @@ type CrosshairLocation = { }; type SizedNiivueCanvasProps = NiivueCanvasProps & { - size: number; - isScaling: boolean; + size?: number; + isScaling?: boolean; onLocationChange?: (location: CrosshairLocation) => void; }; @@ -69,7 +69,7 @@ const SizedNiivueCanvas: React.FC = ({ const baseTextHeight = isScaling ? 0.06 : textHeightModel(canvasWidth, canvasHeight); - const textHeight = (size / 10) * baseTextHeight; + const textHeight = ((size || 10) / 10) * baseTextHeight; const fullOptions = options ? { ...options, textHeight } : { textHeight }; const fullOnStart = (nv: Niivue) => { @@ -132,4 +132,4 @@ function textHeightModel(canvasWidth: number, canvasHeight: number): number { } export type { CrosshairLocation }; -export { SizedNiivueCanvas }; +export default SizedNiivueCanvas; diff --git a/src/components/VisualDatasets/index.tsx b/src/components/VisualDatasets/index.tsx index f191be3e4..34daaca7b 100644 --- a/src/components/VisualDatasets/index.tsx +++ b/src/components/VisualDatasets/index.tsx @@ -34,10 +34,9 @@ import { DEFAULT_OPTIONS } from "./defaults.ts"; import preval from "preval.macro"; import HeaderOptionBar from "./components/HeaderOptionBar.tsx"; import FeedButton from "./components/FeedButton.tsx"; -import { +import SizedNiivueCanvas, { CrosshairLocation, - SizedNiivueCanvas, -} from "./components/SizedNiivueCanvas.tsx"; +} from "../SizedNiivueCanvas/index"; import { Problem, VisualDataset } from "./types.ts"; import VisualDatasetsClient from "./client.tsx"; import ProblemsManager from "./problems.ts";