diff --git a/.github/workflows/ghpages.yml b/.github/workflows/ghpages.yml
new file mode 100644
index 0000000..3780672
--- /dev/null
+++ b/.github/workflows/ghpages.yml
@@ -0,0 +1,21 @@
+name: Build and Deploy
+on:
+ push:
+ branches:
+ - main
+jobs:
+ build-and-publish-live-demo:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install and Build
+ run: |
+ npm install
+ npm run build
+ - name: Deploy
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ branch: demo # The branch the action should deploy to.
+ folder: dist # The folder the action should deploy.
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0738cb5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+.DS_Store
+dist
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e69de29
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..eb6ffd0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# NiiVue + ScribblePromt
+
+TBD
\ No newline at end of file
diff --git a/docs/export.py b/docs/export.py
new file mode 100644
index 0000000..7ea2422
--- /dev/null
+++ b/docs/export.py
@@ -0,0 +1,69 @@
+import torch
+import torch.nn.functional as F
+from scribbleprompt.unet import UNet
+
+class Predictor:
+ """
+ wrapper for ScribblePrompt-UNet model with ONNX export functionality.
+ """
+ def __init__(self, path: str, verbose: bool = True):
+ self.path = path
+ self.verbose = verbose
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ self.build_model()
+ self.load()
+ self.model.eval()
+ self.to_device()
+
+ def build_model(self):
+ """
+ build the ScribblePrompt-UNet model.
+ """
+ self.model = UNet(
+ in_channels=5,
+ out_channels=1,
+ features=[192, 192, 192, 192],
+ )
+
+ def load(self):
+ """
+ load the state of the model from a checkpoint file.
+ """
+ with open(self.path, "rb") as f:
+ state = torch.load(f, map_location=self.device)
+ self.model.load_state_dict(state, strict=True)
+ if self.verbose:
+ print(f"loaded checkpoint from {self.path} to {self.device}")
+
+ def to_device(self):
+ """
+ move the model to the appropriate device.
+ """
+ self.model = self.model.to(self.device)
+
+ def export_to_onnx(self, onnx_path="model.onnx"):
+ """
+ export the model to ONNX format with dynamic H and W (height and width).
+ """
+ # prepare a dummy input with arbitrary H and W, as ONNX export requires a concrete input shape
+ dummy_input = torch.randn(1, 5, 256, 256).to(self.device)
+ torch.onnx.export(
+ self.model,
+ dummy_input,
+ onnx_path,
+ export_params=True,
+ opset_version=20,
+ do_constant_folding=True,
+ input_names=['input'],
+ output_names=['output'],
+ # make H and W dynamic, along with the batch size
+ dynamic_axes={'input': {0: 'batch_size', 2: 'height', 3: 'width'},
+ 'output': {0: 'batch_size', 2: 'height', 3: 'width'}}
+ )
+ print(f"model exported to {onnx_path}")
+
+# usage from CLI
+if __name__ == "__main__":
+ checkpoint_path = "../checkpoints/ScribblePrompt_unet_v1_nf192_res128.pt"
+ predictor = Predictor(checkpoint_path)
+ predictor.export_to_onnx("scribbleprompt_unet.onnx")
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..3dff415
--- /dev/null
+++ b/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+ Niivue ScribblePromt
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..5fea992
--- /dev/null
+++ b/main.js
@@ -0,0 +1,384 @@
+import { Niivue, DRAG_MODE, SLICE_TYPE, MULTIPLANAR_TYPE, SHOW_RENDER } from '@niivue/niivue'
+import * as ort from "./node_modules/onnxruntime-web/dist/ort.all.mjs"
+
+const nv = new Niivue()
+const isiPad = navigator.userAgent.match(/iPad/i) != null
+
+const onnxOptions = {
+ executionProviders: [
+ {
+ name: 'webgpu',
+ },
+ {
+ name: 'wasm',
+ }
+ ],
+ graphOptimizationLevel: 'disabled'
+} // n.b. in future graphOptimizationLevel extended
+
+const onClipChange = (e) => {
+ if (e.target.checked) {
+ nv.setClipPlane([0, 0, 90])
+ } else {
+ nv.setClipPlane([2, 0, 90])
+ }
+}
+
+const onBackOpacityChange = (e) => {
+ nv.setOpacity(0, e.target.value / 255)
+ nv.updateGLVolume()
+}
+
+const onOverlayOpacityChange = (e) => {
+ nv.setOpacity(1, e.target.value / 255)
+ nv.updateGLVolume()
+}
+
+const onDrawingOpacityChange = (e) => {
+ nv.drawOpacity = e.target.value / 255
+ nv.drawScene()
+}
+
+const onImageLoaded = async () => {
+ // if more than one image aleady, then just return. We only run
+ // this if the user drags and drops a new image.
+ if (nv.volumes.length > 1) {
+ return
+ }
+ // check input image dimensions. If they are not equal then
+ // give the user a diaglog to conform the image.
+ const inDims = nv.volumes[0].dims
+ const isEqualDims = inDims[1] === inDims[2] && inDims[2] === inDims[3]
+ // if not equal then give the user a dialog to conform the image
+ if (!isEqualDims) {
+ const userConfirmed = window.confirm('The input image dimensions are not equal. Would you like to conform the image?')
+ if (userConfirmed) {
+ // fire off the conform check event
+ conform.checked = true
+ await onConformChange({ target: { checked: true } })
+ } else {
+ window.alert('Please conform the image before proceeding. The segmentation will not work correctly.')
+ }
+ } else {
+ // clone the loaded image to create an overlay that stores the segmentation
+ const overlay = nv.cloneVolume(0)
+ overlay.img.fill(0) // fill with zeros since it will hold binary segmentation later
+ overlay.opacity = 0.8
+ // add the overlay to niivue
+ nv.addVolume(overlay)
+ }
+ // set drawing enabled to true
+ // if ipad, set to false by default
+ // if (isiPad) {
+ // nv.setDrawingEnabled(false)
+ // } else {
+ // nv.setDrawingEnabled(true)
+ // }
+ nv.setDrawingEnabled(true)
+}
+
+const onConformChange = async (e) => {
+ if (nv.volumes.length < 1) {
+ return
+ }
+ if (e.target.checked) {
+ closeAllOverlays()
+ await ensureConformed()
+ nv.closeDrawing()
+ nv.createEmptyDrawing()
+ // clone the loaded image to create an overlay that stores the segmentation
+ const overlay = nv.cloneVolume(0)
+ overlay.img.fill(0) // fill with zeros since it will hold binary segmentation later
+ overlay.opacity = 0.5
+ // add the overlay to niivue
+ nv.addVolume(overlay)
+ }
+}
+
+const onSaveImageClick = () => {
+ nv.volumes[1].saveToDisk('segmentation.nii')
+}
+
+const ensureConformed = async () => {
+ const nii = nv.volumes[0]
+ let isConformed = nii.dims[1] === 256 && nii.dims[2] === 256 && nii.dims[3] === 256
+ if (nii.permRAS[0] !== -1 || nii.permRAS[1] !== 3 || nii.permRAS[2] !== -2) {
+ isConformed = false
+ }
+ if (isConformed) {
+ return
+ }
+ const nii2 = await nv.conform(nii, true, true, false)
+ nv.removeVolume(nv.volumes[0])
+ nv.addVolume(nii2)
+}
+
+const closeAllOverlays = () => {
+ while (nv.volumes.length > 1) {
+ nv.removeVolume(nv.volumes[1])
+ }
+}
+
+const showLoadingCircle = () => {
+ loadingCircle.classList.remove('hidden')
+}
+
+const hideLoadingCircle = () => {
+ loadingCircle.classList.add('hidden')
+}
+
+const getSlice = (volIdx, start, end) => {
+ const slice = nv.volumes[volIdx].getVolumeData(start, end)
+ const data = slice[0]
+ const dims = slice[1]
+ const axcorsag = nv.tileIndex(...nv.mousePos)
+ const ax = 0
+ const cor = 1
+ const sag = 2
+ // dims will hold H and W, but we need to determine which is which based
+ // on the plane the user is drawing on.
+ // if the user is drawing on the axial plane, then the first dimension is W
+ // and the second dimension is H.
+ // if the user is drawing on the coronal plane, then the first dimension is W
+ // and the third dimension is H
+ // if the user is drawing on the sagittal plane, then the second dimension is W
+ // and the third dimension is H.
+ // we need to determine which plane the user is drawing on, and then set the
+ // dimensions accordingly.
+ if (axcorsag === ax) {
+ return { data, dims: [dims[0], dims[1]] }
+ } else if (axcorsag === cor) {
+ return { data, dims: [dims[0], dims[2]] }
+ } else if (axcorsag === sag) {
+ return { data, dims: [dims[2], dims[1]] }
+ }
+}
+
+const copyDrawingToOverlay = (drawing, overlay) => {
+ for (let i = 0; i < drawing.length; i++) {
+ if (drawing[i] > 0) {
+ overlay.img[i] = 1
+ } else {
+ overlay.img[i] = overlay.img[i] === 1 ? 1 : 0
+ }
+ }
+}
+
+const normalize = (img) => {
+ // TODO: ONNX not JavaScript https://onnx.ai/onnx/operators/onnx_aionnxml_Normalizer.html
+ let mx = img[0]
+ let mn = mx
+ for (let i = 0; i < img.length; i++) {
+ mx = Math.max(mx, img[i])
+ mn = Math.min(mn, img[i])
+ }
+ let scale = 1 / (mx - mn)
+ for (let i = 0; i < img.length; i++) {
+ img[i] = (img[i] - mn) * scale
+ }
+}
+
+// drawing pen can be 1, 2, 3, (e.g. red, green, blue)
+const binarizeDrawing = (drawing) => {
+ for (let i = 0; i < drawing.length; i++) {
+ drawing[i] = drawing[i] > 0 ? 1 : 0
+ }
+}
+
+const sigmoid = (x) => {
+ return 1 / (1 + Math.exp(-x))
+}
+
+// when user is done making scribbles, run the segmentation
+const doSegment = async (start, end) => {
+ // show loading circle
+ showLoadingCircle()
+ // background image index
+ const backIdx = 0
+ // get the slice data from the active slice the user was drawing on
+ const inputSlice = getSlice(backIdx, start, end)
+ // get W, H, D of the slice
+ const [W, H] = inputSlice.dims
+ // get reference to the overlay volume for later
+ const overlay = nv.volumes[1]
+ // make sure slice data is a Float32Array
+ const inputSlice32 = new Float32Array(inputSlice.data)
+ // get reference to drawing from user clicks
+ const drawing = nv.drawBitmap
+ // is there a drawing? detect if any elements are greater than 0
+ const hasDrawing = drawing.some(v => v > 0)
+ // if no drawing, then return
+ if (!hasDrawing) {
+ hideLoadingCircle()
+ return
+ }
+ copyDrawingToOverlay(drawing, overlay)
+ // get the the slice data from the drawing overlay that
+ // has now been populated from the drawBitmap (which came from user clicks)
+ const drawingSlice = getSlice(1, start, end)
+ const drawingSlice32 = new Float32Array(drawingSlice.data)
+ // binarize the drawing
+ binarizeDrawing(drawingSlice32)
+ // normalize input data to range 0..1
+ normalize(inputSlice32) // no return value since it does this in place
+
+ // create onnx runtime session for running inference
+ const session = await ort.InferenceSession.create(
+ './scribbleprompt_unet.onnx',
+ onnxOptions
+ )
+ // expected scribble shape = [B, 5, H, W]
+ const HW = H * W
+ const shape = [1, 5, H, W]
+ const componentShape = [1, 1, H, W]
+ // create tensor with correct shape filled with zeros
+ const nvox = shape.reduce((a, b) => a * b)
+ const inputTensor = new ort.Tensor('float32', new Float32Array(nvox), shape)
+ // make 5 tensors: img, box, click, scribble, mask
+ // img is the input slice data from the background image
+ const imgTensor = new ort.Tensor('float32', inputSlice32, componentShape)
+ // box not used
+ const boxTensor = new ort.Tensor('float32', new Float32Array(HW).fill(0), componentShape)
+ // clicks are the user clicks (the drawing from niivue)
+ const clickTensor = new ort.Tensor('float32', drawingSlice32, componentShape)
+ // scribble is not used. Not sure what the difference between clicks and scribble is,
+ // but using drawingSlice32 for scribbles does not work as expected.
+ const scribbleTensor = new ort.Tensor('float32', new Float32Array(HW).fill(0), componentShape)
+ // mask is the previous mask from scribble inference. Not used here, but perhaps could be
+ const maskTensor = new ort.Tensor('float32', new Float32Array(HW).fill(0), componentShape)
+
+ // now concatenate the tensors for input to the model
+ inputTensor.data.set(imgTensor.data, 0)
+ inputTensor.data.set(boxTensor.data, HW)
+ inputTensor.data.set(clickTensor.data, 2 * HW)
+ inputTensor.data.set(scribbleTensor.data, 3 * HW)
+ inputTensor.data.set(maskTensor.data, 4 * HW)
+ const modelInputs = { "input": inputTensor }
+
+ // run onnx inference
+ // get time stamp for timing
+ const t0 = performance.now()
+ const results = await session.run(modelInputs)
+ const t1 = performance.now()
+ const inferenceTime = t1 - t0
+ console.log(`inference time: ${inferenceTime} ms`)
+
+ // output has shape [1, 1, H, W]
+ const outSliceData = results.output.cpuData
+ const threshold = 0.5
+ // model returns logits, so we need to apply sigmoid.
+ for (let i = 0; i < outSliceData.length; i++) {
+ outSliceData[i] = sigmoid(outSliceData[i])
+ outSliceData[i] = outSliceData[i] < threshold ? 0 : 1
+ // combine the segmentation with the the original drawing
+ if (drawingSlice32[i] === 1) {
+ outSliceData[i] = 1
+ }
+ }
+
+ // update the overlay slice with the segmentation
+ overlay.setVolumeData(start, end, outSliceData)
+ // make sure the overlay colormap is red
+ overlay.setColormap('red')
+ nv.updateGLVolume()
+ // reset the drawing for the next call
+ nv.closeDrawing()
+ nv.createEmptyDrawing()
+ // hide the loading circle
+ hideLoadingCircle()
+
+}
+
+const onSegmentClick = async () => {
+ if (nv.volumes.length < 1) {
+ window.alert('Please open a voxel-based image')
+ return
+ }
+ const vox = nv.frac2vox(nv.scene.crosshairPos, 0)
+ const dims = nv.volumes[0].dims
+ // const start = [0, 0, vox[2]]
+ // const end = [dims[1] - 1, dims[2] - 1, vox[2]]
+ const axcorsag = nv.tileIndex(...nv.mousePos)
+ const ax = 0
+ const cor = 1
+ const sag = 2
+ // determine which plane the user is drawing on, and set the start and end
+ // to the full slice of that plane
+ let start, end = [0, 0, 0]
+ if (axcorsag === ax) {
+ start = [0, 0, vox[2]]
+ end = [dims[1] - 1, dims[2] - 1, vox[2]]
+ } else if (axcorsag === cor) {
+ start = [0, vox[1], 0]
+ end = [dims[1] - 1, vox[1], dims[3] - 1]
+ } else if (axcorsag === sag) {
+ start = [vox[0], 0, 0]
+ end = [vox[0], dims[2] - 1, dims[3] - 1]
+ }
+ // run the segmentation and send the slice info
+ await doSegment(start, end)
+}
+
+const handleLocationChange = (data) => {
+ document.getElementById("intensity").innerHTML = data.string
+}
+
+async function main() {
+ // set callback for clip plane checkbox
+ clipCheck.onchange = onClipChange
+ // set callback for back opacity slider
+ opacitySlider0.onchange = onBackOpacityChange
+ // set callback for overlay opacity slider
+ opacitySlider1.onchange = onOverlayOpacityChange
+ // set callback for drawing opacity slider
+ opacitySlider2.onchange = onDrawingOpacityChange
+ // set callback for save image button (saves the segmentation)
+ saveImgBtn.onclick = onSaveImageClick
+ // set callback for segment button
+ segmentBtn.onclick = onSegmentClick
+ // set callback for conform checkbox
+ conform.onchange = onConformChange
+ // bind "s" key up to onSegmentClick also
+ document.addEventListener('keyup', (e) => {
+ if (e.key === 's') {
+ onSegmentClick()
+ }
+ })
+ // bind shift + z to undo
+ document.addEventListener('keyup', (e) => {
+ if (e.key === 'z') {
+ nv.drawUndo()
+ }
+ })
+ // set callback for when the crosshair moves
+ // to report the intensity of the voxel under the crosshair
+ // and the location.
+ nv.onLocationChange = handleLocationChange
+ // get canvas element
+ const gl1 = document.getElementById('gl1')
+ nv.attachToCanvas(gl1)
+ nv.setDrawingEnabled(false)
+ nv.opts.maxDrawUndoBitmaps = 30
+ nv.opts.multiplanarForceRender = true
+ nv.opts.yoke3Dto2DZoom = true
+ nv.opts.crosshairGap = 5
+ nv.setInterpolation(false) // use nearest neighbor
+ nv.onImageLoaded = onImageLoaded
+ await nv.loadVolumes([{ url: './stroke.nii.gz' }])
+ nv.setPenValue(2, false)
+ nv.drawOpacity = 1
+ nv.setMultiplanarLayout(MULTIPLANAR_TYPE.GRID)
+ nv.setSliceType(SLICE_TYPE.MULTIPLANAR)
+ nv.opts.multiplanarShowRender = SHOW_RENDER.ALWAYS
+ nv.opts.dragMode = DRAG_MODE.slicer3D
+
+ // on mouse up dosegment
+ window.addEventListener('pointerup', async function(event) {
+ if (event.pointerType === 'pen') {
+ await onSegmentClick()
+ }
+ })
+
+}
+
+main()
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..b94ce46
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1155 @@
+{
+ "name": "niivue-scribble-prompt",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "niivue-scribble-prompt",
+ "version": "1.0.0",
+ "dependencies": {
+ "@niivue/niivue": "^0.45.1",
+ "onnxruntime-web": "^1.19.2"
+ },
+ "devDependencies": {
+ "vite": "^5.2.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@lukeed/csprng": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
+ "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@lukeed/uuid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz",
+ "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==",
+ "license": "MIT",
+ "dependencies": {
+ "@lukeed/csprng": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@niivue/niivue": {
+ "version": "0.45.1",
+ "resolved": "https://registry.npmjs.org/@niivue/niivue/-/niivue-0.45.1.tgz",
+ "integrity": "sha512-1KfuiIbdJr0fi9Odv/Wjrej580B295q+fE+PAIW9L5ZrJhCmsMzYgxYecJGcj67tu8mcwQkKjbLaO6VxdVS3+g==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@lukeed/uuid": "^2.0.1",
+ "@ungap/structured-clone": "^1.2.0",
+ "array-equal": "^1.0.2",
+ "daikon": "^1.2.46",
+ "fflate": "^0.8.2",
+ "gl-matrix": "^3.4.3",
+ "nifti-reader-js": "^0.6.8",
+ "rxjs": "^7.8.1"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-gnu": "^4.18.1"
+ }
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
+ "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
+ "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
+ "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
+ "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
+ "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
+ "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
+ "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
+ "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
+ "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
+ "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
+ "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
+ "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
+ "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
+ "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
+ "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
+ "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.7.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.0.tgz",
+ "integrity": "sha512-MOdOibwBs6KW1vfqz2uKMlxq5xAfAZ98SZjO8e3XnAbFnTJtAspqhWk7hrdSAs9/Y14ZWMiy7/MxMUzAOadYEw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "license": "ISC"
+ },
+ "node_modules/@wearemothership/dicom-character-set": {
+ "version": "1.0.4-opt.1",
+ "resolved": "https://registry.npmjs.org/@wearemothership/dicom-character-set/-/dicom-character-set-1.0.4-opt.1.tgz",
+ "integrity": "sha512-stqhnpawYHY2UZKj4RHTF71ab3q3z8S1SO9ToQKjsHQwowUdFVo6YFea93psFux3yqNbRlQjwoCdPjHcD0YQzw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/array-equal": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.2.tgz",
+ "integrity": "sha512-gUHx76KtnhEgB3HOuFYiCm3FIdEs6ocM2asHvNTkfu/Y09qQVrrVVaOKENmS2KkSaGoxgXNqC+ZVtR/n0MOkSA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/cssfilter": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
+ "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
+ "license": "MIT"
+ },
+ "node_modules/daikon": {
+ "version": "1.2.46",
+ "resolved": "https://registry.npmjs.org/daikon/-/daikon-1.2.46.tgz",
+ "integrity": "sha512-S8dTTlsWYTH3LQztjTW9KnNvxDeL2mr2cau0auLdYMJe4TrocYP1PmidHizO3rXUs+gXpBWI1PQ2qvB4b21QFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@wearemothership/dicom-character-set": "^1.0.4-opt.1",
+ "fflate": "*",
+ "jpeg-lossless-decoder-js": "2.0.7",
+ "pako": "^2.1",
+ "xss": "1.0.14"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
+ "node_modules/flatbuffers": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz",
+ "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==",
+ "license": "SEE LICENSE IN LICENSE.txt"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gl-matrix": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
+ "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==",
+ "license": "MIT"
+ },
+ "node_modules/guid-typescript": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
+ "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
+ "license": "ISC"
+ },
+ "node_modules/jpeg-lossless-decoder-js": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/jpeg-lossless-decoder-js/-/jpeg-lossless-decoder-js-2.0.7.tgz",
+ "integrity": "sha512-tbZlhFkKmx+JaqVMkq47SKWGuXLkIaV8fTbnhO39dYEnQrSShLGuLCGb0n6ntXjtmk6oAWGiIriWOLwj9od0yQ==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
+ "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/nifti-reader-js": {
+ "version": "0.6.8",
+ "resolved": "https://registry.npmjs.org/nifti-reader-js/-/nifti-reader-js-0.6.8.tgz",
+ "integrity": "sha512-yIKNVzYFiUcSHazoR+sd6Ka7sUmZTabaVqJRFxbdlAKR1hnPBuNP71g3AyApo37nJ3k41c632QPij5q7gF1YPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fflate": "*"
+ }
+ },
+ "node_modules/onnxruntime-common": {
+ "version": "1.19.2",
+ "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.19.2.tgz",
+ "integrity": "sha512-a4R7wYEVFbZBlp0BfhpbFWqe4opCor3KM+5Wm22Az3NGDcQMiU2hfG/0MfnBs+1ZrlSGmlgWeMcXQkDk1UFb8Q==",
+ "license": "MIT"
+ },
+ "node_modules/onnxruntime-web": {
+ "version": "1.19.2",
+ "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.19.2.tgz",
+ "integrity": "sha512-r0ok6KpTUXR4WA+rHvUiZn7JoH02e8iS7XE1p5bXk7q3E0UaRFfYvpMNUHqEPiTBMuIssfBxDCQjUihV8dDFPg==",
+ "license": "MIT",
+ "dependencies": {
+ "flatbuffers": "^1.12.0",
+ "guid-typescript": "^1.0.9",
+ "long": "^5.2.3",
+ "onnxruntime-common": "1.19.2",
+ "platform": "^1.3.6",
+ "protobufjs": "^7.2.4"
+ }
+ },
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
+ "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/platform": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
+ "license": "MIT"
+ },
+ "node_modules/postcss": {
+ "version": "8.4.47",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+ "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.0",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
+ "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.22.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
+ "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.22.4",
+ "@rollup/rollup-android-arm64": "4.22.4",
+ "@rollup/rollup-darwin-arm64": "4.22.4",
+ "@rollup/rollup-darwin-x64": "4.22.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.22.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.22.4",
+ "@rollup/rollup-linux-arm64-musl": "4.22.4",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.22.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.22.4",
+ "@rollup/rollup-linux-x64-gnu": "4.22.4",
+ "@rollup/rollup-linux-x64-musl": "4.22.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.22.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.22.4",
+ "@rollup/rollup-win32-x64-msvc": "4.22.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
+ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
+ "license": "0BSD"
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
+ "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xss": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
+ "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^2.20.3",
+ "cssfilter": "0.0.10"
+ },
+ "bin": {
+ "xss": "bin/xss"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3d1091c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "niivue-scribble-prompt",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build && npm run copyMJS && npm run copyWASM",
+ "copyMJS": "cp ./node_modules/onnxruntime-web/dist/*.mjs ./dist/assets/",
+ "copyWASM": "cp ./node_modules/onnxruntime-web/dist/*.wasm ./dist/assets/",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@niivue/niivue": "^0.45.1",
+ "onnxruntime-web": "^1.19.2"
+ },
+ "devDependencies": {
+ "vite": "^5.2.0"
+ }
+ }
+
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..67916f4
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/niivue.css b/public/niivue.css
new file mode 100644
index 0000000..4c13be6
--- /dev/null
+++ b/public/niivue.css
@@ -0,0 +1,130 @@
+html {
+ height: auto;
+ min-height: 100%;
+ margin: 0;
+}
+body {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ min-height: 100%;
+ width: 100%;
+ position: absolute;
+ font-family: system-ui, Arial, Helvetica, sans-serif;
+ user-select: none; /* Standard syntax */
+ color: white;
+ background: #303030;
+}
+header {
+ margin: 10px;
+}
+main {
+ flex: 1;
+ background: #000000;
+ position: relative;
+}
+footer {
+ margin: 10px;
+}
+canvas {
+ position: absolute;
+ cursor: crosshair;
+}
+canvas:focus {
+ outline: 0px;
+}
+div {
+ display: table-row;
+}
+.dropdown {
+ float: left;
+ overflow: hidden;
+}
+.dropdown .dropbtn {
+ font-size: 16px;
+ border: none;
+ outline: none;
+ color: white;
+ padding: 12px 12px;
+ background-color: #303030;
+ font-family: inherit;
+ margin: 0;
+}
+.dropdown:hover .dropbtn {
+ background-color: #9a9;
+}
+.dropdown-content {
+ display: none;
+ position: absolute;
+ background-color: #303030;
+ min-width: 160px;
+ border-radius: 5px;
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+ z-index: 1;
+}
+.dropdown-content a {
+ float: none;
+ color: white;
+ padding: 12px 16px;
+ text-decoration: none;
+ display: block;
+ text-align: left;
+ line-height: 6px;
+}
+.dropdown-content a:hover {
+ background-color: #aba;
+}
+.dropdown:hover .dropdown-content {
+ display: block;
+}
+.dropdown-item-checked::before {
+ position: absolute;
+ left: 0.2rem;
+ content: "\2022"; /* or '✓' */
+ font-weight: 600;
+}
+.divider {
+ border-top: 1px solid grey;
+}
+.vertical-divider {
+ border-left: 1px solid grey;
+ height: 40px;
+}
+.help-text {
+ margin: auto;
+ max-width: 150px;
+ padding: 0 10px;
+}
+.slidecontainer {
+ padding: 10px 10px;
+ white-space: normal;
+ word-break: break-word;
+ display: flex;
+ align-items: center;
+ flex: 0 0 auto;
+}
+
+div.footer { width: 100%; display: block; background: #303030;}
+table.footer { width: 100%;height: 100%; table-layout: fixed;}
+
+.loading-circle {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 3px solid rgba(255, 255, 255, 0.3);
+ border-radius: 50%;
+ border-top-color: #fff;
+ animation: spin 1s ease-in-out infinite;
+ margin-left: 10px;
+ vertical-align: middle;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.hidden {
+ display: none;
+}
\ No newline at end of file
diff --git a/public/scribbleprompt_unet.onnx b/public/scribbleprompt_unet.onnx
new file mode 100644
index 0000000..95f22a8
Binary files /dev/null and b/public/scribbleprompt_unet.onnx differ
diff --git a/public/stroke.nii.gz b/public/stroke.nii.gz
new file mode 100644
index 0000000..72b9ec1
Binary files /dev/null and b/public/stroke.nii.gz differ
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..2be600b
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,15 @@
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ root: '.',
+ base: './',
+ server: {
+ open: 'index.html',
+ },
+ optimizeDeps: {
+ exclude: ['onnxruntime-web']
+ },
+ worker: {
+ format: 'es'
+ }
+})
\ No newline at end of file