Spaces:
Configuration error
Configuration error
Commit ·
8bbab59
1
Parent(s): a8afc85
MindGrab-only WebGPU Space
Browse files- .gitattributes +1 -34
- .gitignore +3 -0
- LICENSE +24 -0
- README.md +51 -6
- index.html +40 -17
- main.js +295 -0
- net_mindgrab.js +0 -0
- net_mindgrab_tta_axial.js +0 -0
- net_mindgrab_tta_coronal.js +0 -0
- net_mindgrab_tta_sagittal.js +0 -0
- package-lock.json +953 -0
- package.json +17 -0
- public/colormap_mindgrab.json +6 -0
- public/favicon.ico +0 -0
- public/net_mindgrab.safetensors +3 -0
- public/net_mindgrab_tta_axial.safetensors +3 -0
- public/net_mindgrab_tta_coronal.safetensors +3 -0
- public/net_mindgrab_tta_sagittal.safetensors +3 -0
- public/niivue.css +109 -0
- public/t1_crop.nii.gz +3 -0
- style.css +0 -28
- tensor-utils.js +613 -0
- vite.config.js +9 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,2 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.nii.gz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.DS_Store
|
| 2 |
+
dist
|
| 3 |
+
node_modules
|
LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 2-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025, Ahmed Harmouche
|
| 4 |
+
|
| 5 |
+
Redistribution and use in source and binary forms, with or without
|
| 6 |
+
modification, are permitted provided that the following conditions are met:
|
| 7 |
+
|
| 8 |
+
1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
list of conditions and the following disclaimer.
|
| 10 |
+
|
| 11 |
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
and/or other materials provided with the distribution.
|
| 14 |
+
|
| 15 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 16 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 17 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 18 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 19 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 20 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 21 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 22 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 23 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 24 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
README.md
CHANGED
|
@@ -1,11 +1,56 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
|
|
|
|
|
|
|
|
|
| 7 |
pinned: false
|
| 8 |
-
license: mit
|
| 9 |
---
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: MindGrab WebGPU
|
| 3 |
+
emoji: 🧠
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: static
|
| 7 |
+
app_file: dist/index.html
|
| 8 |
+
app_build_command: npm run build
|
| 9 |
+
license: apache-2.0
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
+
### Overview
|
| 14 |
+
|
| 15 |
+
This Space bundles the MindGrab skull-stripping model (and its three TTA variants) exported from [brainchop](https://github.com/neuroneural/brainchop) via tinygrad. Everything runs in the browser with WebGPU; MRI data never leaves the client. The UI embeds [Niivue](https://github.com/niivue/niivue) for visualization and uses the same conform/normalization steps as the CLI.
|
| 16 |
+
|
| 17 |
+
### Local Development
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
cd hf
|
| 21 |
+
npm install # once
|
| 22 |
+
npm run dev # hot reload, http://localhost:5173
|
| 23 |
+
npm run build # produces dist/
|
| 24 |
+
npx serve dist # static preview (or npm run preview)
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### Deploying to Hugging Face Spaces
|
| 28 |
+
|
| 29 |
+
Follow the latest [Spaces overview docs](https://huggingface.co/docs/hub/spaces-overview) / [Static HTML guide](https://huggingface.co/docs/hub/spaces-sdks-static):
|
| 30 |
+
|
| 31 |
+
1. **Create or open the Space** – visit https://huggingface.co/spaces/neuroneural/mindgrab, choose SDK `static`, and ensure the README metadata matches the block above (it declares `app_file` + `app_build_command` so Hugging Face runs the same `npm run build` step you run locally).
|
| 32 |
+
2. **Authenticate once** – `pip install -U huggingface_hub` if needed, then run `huggingface-cli login` with a write-enabled token.
|
| 33 |
+
3. **Clone the Space repo (instead of initializing a fresh Git repo)**:
|
| 34 |
+
```bash
|
| 35 |
+
git clone https://huggingface.co/spaces/neuroneural/mindgrab mindgrab-space
|
| 36 |
+
cd mindgrab-space
|
| 37 |
+
git lfs install # .gitattributes already tracks .safetensors + .nii.gz
|
| 38 |
+
```
|
| 39 |
+
4. **Sync this project into the clone** (from the `brainchop-cli` root run something like):
|
| 40 |
+
```bash
|
| 41 |
+
rsync -av --delete hf/ /path/to/mindgrab-space/
|
| 42 |
+
```
|
| 43 |
+
or copy the files manually—the key is to keep `.gitattributes`, `public/`, and the built `dist/` folder together.
|
| 44 |
+
5. **Build locally before pushing** (same command Hugging Face runs server-side):
|
| 45 |
+
```bash
|
| 46 |
+
npm install
|
| 47 |
+
npm run build
|
| 48 |
+
```
|
| 49 |
+
6. **Commit + push** – every push to `main` triggers an automatic Space rebuild:
|
| 50 |
+
```bash
|
| 51 |
+
git add -A
|
| 52 |
+
git commit -m "Update MindGrab Space assets"
|
| 53 |
+
git push origin main
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Because `.gitattributes` tracks `.safetensors` and `.nii.gz`, Git LFS handles the large weights/sample volumes automatically. Once pushed, the Space serves the built `dist/` bundle and fetches weights from `/public`, so inference continues entirely client-side over WebGPU.
|
index.html
CHANGED
|
@@ -1,19 +1,42 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<link rel="stylesheet" href="./niivue.css" />
|
| 8 |
+
<title>MindGrab WebGPU</title>
|
| 9 |
+
</head>
|
| 10 |
+
|
| 11 |
+
<body>
|
| 12 |
+
<header>
|
| 13 |
+
<button id="segmentBtn">Segment</button>
|
| 14 |
+
<label for="clipCheck">Clip Plane</label>
|
| 15 |
+
<input type="checkbox" id="clipCheck" unchecked />
|
| 16 |
+
<label for="opacitySlider0">Background Opacity</label>
|
| 17 |
+
<input type="range" min="0" max="255" value="255" class="slider" id="opacitySlider0" />
|
| 18 |
+
|
| 19 |
+
<label for="opacitySlider1">Overlay Opacity</label>
|
| 20 |
+
<input type="range" min="0" max="255" value="128" class="slider" id="opacitySlider1" />
|
| 21 |
+
|
| 22 |
+
<button id="saveImgBtn">Save Overlay</button>
|
| 23 |
+
|
| 24 |
+
<button id="aboutBtn">About</button>
|
| 25 |
+
|
| 26 |
+
<div id="loadingCircle" class="loading-circle hidden"></div>
|
| 27 |
+
<label for="segmentationDropdown">MindGrab Variant:</label>
|
| 28 |
+
<select id="segmentationDropdown">
|
| 29 |
+
<option value="mindgrab">MindGrab (default)</option>
|
| 30 |
+
<option value="mindgrab_tta_sagittal">MindGrab (TTA Sagittal)</option>
|
| 31 |
+
<option value="mindgrab_tta_coronal">MindGrab (TTA Coronal)</option>
|
| 32 |
+
<option value="mindgrab_tta_axial">MindGrab (TTA Axial)</option>
|
| 33 |
+
</select>
|
| 34 |
+
</header>
|
| 35 |
+
<main id="canvas-container">
|
| 36 |
+
<canvas id="gl1"></canvas>
|
| 37 |
+
</main>
|
| 38 |
+
<footer id="intensity"> </footer>
|
| 39 |
+
<script type="module" src="/main.js"></script>
|
| 40 |
+
</body>
|
| 41 |
+
|
| 42 |
</html>
|
main.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Niivue } from '@niivue/niivue'
|
| 2 |
+
import mindgrab from "./net_mindgrab.js"
|
| 3 |
+
import mindgrab_tta_sagittal from "./net_mindgrab_tta_sagittal.js"
|
| 4 |
+
import mindgrab_tta_coronal from "./net_mindgrab_tta_coronal.js"
|
| 5 |
+
import mindgrab_tta_axial from "./net_mindgrab_tta_axial.js"
|
| 6 |
+
|
| 7 |
+
const models = {
|
| 8 |
+
"mindgrab": {
|
| 9 |
+
"net": mindgrab,
|
| 10 |
+
"weightPath": "./net_mindgrab.safetensors",
|
| 11 |
+
"colormap": "./colormap_mindgrab.json",
|
| 12 |
+
"volume": "./t1_crop.nii.gz",
|
| 13 |
+
"normalization": "qnormalize"
|
| 14 |
+
},
|
| 15 |
+
"mindgrab_tta_sagittal": {
|
| 16 |
+
"net": mindgrab_tta_sagittal,
|
| 17 |
+
"weightPath": "./net_mindgrab_tta_sagittal.safetensors",
|
| 18 |
+
"colormap": "./colormap_mindgrab.json",
|
| 19 |
+
"volume": "./t1_crop.nii.gz",
|
| 20 |
+
"normalization": "qnormalize"
|
| 21 |
+
},
|
| 22 |
+
"mindgrab_tta_coronal": {
|
| 23 |
+
"net": mindgrab_tta_coronal,
|
| 24 |
+
"weightPath": "./net_mindgrab_tta_coronal.safetensors",
|
| 25 |
+
"colormap": "./colormap_mindgrab.json",
|
| 26 |
+
"volume": "./t1_crop.nii.gz",
|
| 27 |
+
"normalization": "qnormalize"
|
| 28 |
+
},
|
| 29 |
+
"mindgrab_tta_axial": {
|
| 30 |
+
"net": mindgrab_tta_axial,
|
| 31 |
+
"weightPath": "./net_mindgrab_tta_axial.safetensors",
|
| 32 |
+
"colormap": "./colormap_mindgrab.json",
|
| 33 |
+
"volume": "./t1_crop.nii.gz",
|
| 34 |
+
"normalization": "qnormalize"
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
let selectedModel = models[document.getElementById("segmentationDropdown").value]
|
| 39 |
+
|
| 40 |
+
function qnormalize(img32, qmin = 0.02, qmax = 0.98, eps = 1e-3) {
|
| 41 |
+
// Create sorted copy to find quantiles
|
| 42 |
+
const sorted = [...img32].sort((a, b) => a - b);
|
| 43 |
+
// Calculate quantile indices
|
| 44 |
+
const n = sorted.length;
|
| 45 |
+
const qminIndex = Math.floor(qmin * (n - 1));
|
| 46 |
+
const qmaxIndex = Math.floor(qmax * (n - 1));
|
| 47 |
+
// Linear interpolation for accurate quantiles
|
| 48 |
+
const qminFrac = qmin * (n - 1) - qminIndex;
|
| 49 |
+
const qmaxFrac = qmax * (n - 1) - qmaxIndex;
|
| 50 |
+
let qlow = sorted[qminIndex];
|
| 51 |
+
if (qminIndex < n - 1) {
|
| 52 |
+
qlow += qminFrac * (sorted[qminIndex + 1] - sorted[qminIndex]);
|
| 53 |
+
}
|
| 54 |
+
let qhigh = sorted[qmaxIndex];
|
| 55 |
+
if (qmaxIndex < n - 1) {
|
| 56 |
+
qhigh += qmaxFrac * (sorted[qmaxIndex + 1] - sorted[qmaxIndex]);
|
| 57 |
+
}
|
| 58 |
+
// Normalize and clip in-place
|
| 59 |
+
const scale = 1 / (qhigh - qlow + eps);
|
| 60 |
+
for (let i = 0; i < img32.length; i++) {
|
| 61 |
+
img32[i] = Math.max(0, Math.min(1, (img32[i] - qlow) * scale));
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
async function main() {
|
| 66 |
+
clipCheck.onchange = function () {
|
| 67 |
+
if (clipCheck.checked) {
|
| 68 |
+
nv1.setClipPlane([0, 0, 90])
|
| 69 |
+
} else {
|
| 70 |
+
nv1.setClipPlane([2, 0, 90])
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
opacitySlider0.oninput = function () {
|
| 74 |
+
nv1.setOpacity(0, opacitySlider0.value / 255)
|
| 75 |
+
nv1.updateGLVolume()
|
| 76 |
+
}
|
| 77 |
+
opacitySlider1.oninput = function () {
|
| 78 |
+
nv1.setOpacity(1, opacitySlider1.value / 255)
|
| 79 |
+
}
|
| 80 |
+
function doLoadImage() {
|
| 81 |
+
opacitySlider0.oninput()
|
| 82 |
+
}
|
| 83 |
+
async function fetchJSON(fnm) {
|
| 84 |
+
const response = await fetch(fnm)
|
| 85 |
+
const js = await response.json()
|
| 86 |
+
return js
|
| 87 |
+
}
|
| 88 |
+
saveImgBtn.onclick = function () {
|
| 89 |
+
nv1.volumes[1].saveToDisk('Custom.nii')
|
| 90 |
+
}
|
| 91 |
+
aboutBtn.onclick = function () {
|
| 92 |
+
const url = "https://github.com/niivue/niivue-tinygrad"
|
| 93 |
+
window.open(url, "_blank")
|
| 94 |
+
}
|
| 95 |
+
async function ensureConformed() {
|
| 96 |
+
const nii = nv1.volumes[0]
|
| 97 |
+
let isConformed = nii.dims[1] === 256 && nii.dims[2] === 256 && nii.dims[3] === 256
|
| 98 |
+
if (nii.permRAS[0] !== -1 || nii.permRAS[1] !== 3 || nii.permRAS[2] !== -2) {
|
| 99 |
+
isConformed = false
|
| 100 |
+
}
|
| 101 |
+
if (isConformed) {
|
| 102 |
+
return
|
| 103 |
+
}
|
| 104 |
+
const nii2 = await nv1.conform(nii, false)
|
| 105 |
+
nv1.removeVolume(nv1.volumes[0])
|
| 106 |
+
nv1.addVolume(nii2)
|
| 107 |
+
}
|
| 108 |
+
async function closeAllOverlays() {
|
| 109 |
+
while (nv1.volumes.length > 1) {
|
| 110 |
+
nv1.removeVolume(nv1.volumes[1])
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
const getDevice = async () => {
|
| 114 |
+
if (!navigator.gpu) return false;
|
| 115 |
+
const adapter = await navigator.gpu.requestAdapter();
|
| 116 |
+
|
| 117 |
+
// Allow simulating lower buffer limits via URL param: ?maxBufferMB=128
|
| 118 |
+
const params = new URLSearchParams(window.location.search);
|
| 119 |
+
const simulatedMaxMB = params.get('maxBufferMB');
|
| 120 |
+
const simulatedMax = simulatedMaxMB ? parseInt(simulatedMaxMB) * 1024 * 1024 : Infinity;
|
| 121 |
+
|
| 122 |
+
const maxBufferSize = Math.min(simulatedMax, adapter.limits.maxBufferSize);
|
| 123 |
+
const maxStorageBufferBindingSize = Math.min(simulatedMax, adapter.limits.maxStorageBufferBindingSize);
|
| 124 |
+
|
| 125 |
+
console.log('Adapter limits:', adapter.limits);
|
| 126 |
+
console.log('Adapter max buffer size:', adapter.limits.maxBufferSize);
|
| 127 |
+
console.log('Requested max buffer size:', maxBufferSize, simulatedMaxMB ? `(simulated ${simulatedMaxMB}MB)` : '(using adapter max)');
|
| 128 |
+
|
| 129 |
+
return await adapter.requestDevice({
|
| 130 |
+
requiredLimits: {
|
| 131 |
+
maxBufferSize,
|
| 132 |
+
maxStorageBufferBindingSize,
|
| 133 |
+
},
|
| 134 |
+
requiredFeatures: ["shader-f16"]
|
| 135 |
+
});
|
| 136 |
+
};
|
| 137 |
+
|
| 138 |
+
const device = await getDevice();
|
| 139 |
+
|
| 140 |
+
// Buffer profiling monkeypatch
|
| 141 |
+
const bufferStats = { totalAllocated: 0, maxSingleBuffer: 0, count: 0 };
|
| 142 |
+
const _createBuffer = device.createBuffer.bind(device);
|
| 143 |
+
device.createBuffer = (descriptor) => {
|
| 144 |
+
bufferStats.totalAllocated += descriptor.size;
|
| 145 |
+
bufferStats.maxSingleBuffer = Math.max(bufferStats.maxSingleBuffer, descriptor.size);
|
| 146 |
+
bufferStats.count++;
|
| 147 |
+
return _createBuffer(descriptor);
|
| 148 |
+
};
|
| 149 |
+
window.bufferStats = bufferStats;
|
| 150 |
+
window.logBufferStats = () => console.log({
|
| 151 |
+
totalMB: (bufferStats.totalAllocated / 1024 / 1024).toFixed(2),
|
| 152 |
+
maxSingleMB: (bufferStats.maxSingleBuffer / 1024 / 1024).toFixed(2),
|
| 153 |
+
count: bufferStats.count
|
| 154 |
+
});
|
| 155 |
+
|
| 156 |
+
function convertInMemoryOrder(inverse, size, data) {
|
| 157 |
+
let output = new Float32Array(data.length)
|
| 158 |
+
let it = 0;
|
| 159 |
+
|
| 160 |
+
for (let x = 0; x < size; x++) {
|
| 161 |
+
for (let y = 0; y < size; y++) {
|
| 162 |
+
for (let z = 0; z < size; z++) {
|
| 163 |
+
let idx = x + y * size + z * size * size;
|
| 164 |
+
if (inverse) {
|
| 165 |
+
output[idx] = data[it++];
|
| 166 |
+
} else {
|
| 167 |
+
output[it++] = data[idx];
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
return output
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
segmentBtn.onclick = async function () {
|
| 177 |
+
if (nv1.volumes.length < 1) {
|
| 178 |
+
window.alert('Please open a voxel-based image')
|
| 179 |
+
return
|
| 180 |
+
}
|
| 181 |
+
const startTime = Date.now();
|
| 182 |
+
console.log('[Model] Starting segmentation...')
|
| 183 |
+
loadingCircle.classList.remove('hidden')
|
| 184 |
+
|
| 185 |
+
console.log('[Model] Phase 1: Preparing volume (closing overlays, conforming)...')
|
| 186 |
+
await closeAllOverlays()
|
| 187 |
+
await ensureConformed()
|
| 188 |
+
console.log('[Model] Phase 1: Volume preparation complete')
|
| 189 |
+
|
| 190 |
+
console.log('[Model] Phase 2: Normalizing input data...')
|
| 191 |
+
let img32 = convertInMemoryOrder(/*inverse*/ false, 256, new Float32Array(nv1.volumes[0].img))
|
| 192 |
+
|
| 193 |
+
console.log(selectedModel)
|
| 194 |
+
if (selectedModel['normalization'] === 'min-max') {
|
| 195 |
+
console.log('[Model] Phase 2: Using min-max normalization')
|
| 196 |
+
// normalize input data to range 0..1
|
| 197 |
+
let mx = img32[0]
|
| 198 |
+
let mn = mx
|
| 199 |
+
for (let i = 0; i < img32.length; i++) {
|
| 200 |
+
mx = Math.max(mx, img32[i])
|
| 201 |
+
mn = Math.min(mn, img32[i])
|
| 202 |
+
}
|
| 203 |
+
let scale32 = 1 / (mx - mn)
|
| 204 |
+
for (let i = 0; i < img32.length; i++) {
|
| 205 |
+
img32[i] = (img32[i] - mn) * scale32
|
| 206 |
+
}
|
| 207 |
+
} else {
|
| 208 |
+
console.log('[Model] Phase 2: Using quantile normalization')
|
| 209 |
+
qnormalize(img32);
|
| 210 |
+
}
|
| 211 |
+
console.log('[Model] Phase 2: Normalization complete')
|
| 212 |
+
|
| 213 |
+
console.log('[Model] Phase 3: Loading model weights...')
|
| 214 |
+
const session = await selectedModel["net"].load(device, selectedModel["weightPath"]);
|
| 215 |
+
console.log('[Model] Phase 3: Model weights loaded')
|
| 216 |
+
|
| 217 |
+
const shape = [1, 1, 256, 256, 256]
|
| 218 |
+
const nvox = shape.reduce((a, b) => a * b)
|
| 219 |
+
if (img32.length !== nvox) {
|
| 220 |
+
throw new Error(`img32 length (${img32.length}) does not match expected tensor length (${expectedLength})`)
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
console.log('[Model] Phase 4: Running inference...')
|
| 224 |
+
// Convert to Float16Array if model requires fp16 input
|
| 225 |
+
let inputData = img32;
|
| 226 |
+
if (selectedModel['fp16']) {
|
| 227 |
+
console.log('[Model] Phase 4: Converting to Float16Array for fp16 model')
|
| 228 |
+
inputData = new Float16Array(img32);
|
| 229 |
+
}
|
| 230 |
+
const results = await session(inputData);
|
| 231 |
+
console.log('[Model] Phase 4: Inference complete')
|
| 232 |
+
|
| 233 |
+
// Log label distribution in output cube
|
| 234 |
+
const outputData = results[0];
|
| 235 |
+
const totalVoxels = outputData.length;
|
| 236 |
+
const labelCounts = {};
|
| 237 |
+
for (let i = 0; i < totalVoxels; i++) {
|
| 238 |
+
const label = Math.round(outputData[i]);
|
| 239 |
+
labelCounts[label] = (labelCounts[label] || 0) + 1;
|
| 240 |
+
}
|
| 241 |
+
console.log('[Model] Label distribution in output cube:');
|
| 242 |
+
const sortedLabels = Object.keys(labelCounts).sort((a, b) => a - b);
|
| 243 |
+
for (const label of sortedLabels) {
|
| 244 |
+
const count = labelCounts[label];
|
| 245 |
+
const ratio = (count / totalVoxels * 100).toFixed(2);
|
| 246 |
+
console.log(` Label ${label}: ${count} voxels (${ratio}%)`);
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
console.log('[Model] Phase 5: Post-processing results...')
|
| 250 |
+
let segmentImg = nv1.cloneVolume(0)
|
| 251 |
+
segmentImg.img = convertInMemoryOrder(/*inverse*/ true, 256, results[0])
|
| 252 |
+
segmentImg.hdr.datatypeCode = 16
|
| 253 |
+
segmentImg.hdr.dims[4] = 1
|
| 254 |
+
segmentImg.trustCalMinMax = false
|
| 255 |
+
|
| 256 |
+
// Add the output to niivue
|
| 257 |
+
const cmap = await fetchJSON(selectedModel["colormap"])
|
| 258 |
+
segmentImg.setColormapLabel(cmap)
|
| 259 |
+
segmentImg.opacity = opacitySlider1.value / 255
|
| 260 |
+
nv1.addVolume(segmentImg)
|
| 261 |
+
console.log('[Model] Phase 5: Post-processing complete')
|
| 262 |
+
|
| 263 |
+
loadingCircle.classList.add('hidden')
|
| 264 |
+
const elapsedTime = Date.now() - startTime
|
| 265 |
+
console.log(`[Model] Segmentation complete in ${elapsedTime}ms`)
|
| 266 |
+
document.getElementById("intensity").innerHTML = `${elapsedTime}ms to segment`
|
| 267 |
+
}
|
| 268 |
+
function handleLocationChange(data) {
|
| 269 |
+
document.getElementById("intensity").innerHTML = data.string
|
| 270 |
+
}
|
| 271 |
+
const defaults = {
|
| 272 |
+
backColor: [0.4, 0.4, 0.4, 1],
|
| 273 |
+
onLocationChange: handleLocationChange,
|
| 274 |
+
}
|
| 275 |
+
const nv1 = new Niivue(defaults)
|
| 276 |
+
nv1.attachToCanvas(gl1)
|
| 277 |
+
nv1.opts.multiplanarForceRender = true
|
| 278 |
+
nv1.opts.yoke3Dto2DZoom = true
|
| 279 |
+
nv1.opts.crosshairGap = 11
|
| 280 |
+
nv1.setInterpolation(true)
|
| 281 |
+
nv1.onImageLoaded = doLoadImage
|
| 282 |
+
await nv1.loadVolumes([{ url: selectedModel["volume"] }])
|
| 283 |
+
segmentBtn.onclick()
|
| 284 |
+
|
| 285 |
+
document.getElementById("segmentationDropdown").addEventListener("change", async function () {
|
| 286 |
+
selectedModel = models[this.value]
|
| 287 |
+
if (nv1.volumes[0].url != selectedModel["volume"]) {
|
| 288 |
+
nv1.removeVolumeByIndex(0)
|
| 289 |
+
await nv1.loadVolumes([{ url: selectedModel["volume"] }])
|
| 290 |
+
}
|
| 291 |
+
segmentBtn.onclick()
|
| 292 |
+
});
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
main()
|
net_mindgrab.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
net_mindgrab_tta_axial.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
net_mindgrab_tta_coronal.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
net_mindgrab_tta_sagittal.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package-lock.json
ADDED
|
@@ -0,0 +1,953 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "niivue-tinygrad",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "niivue-tinygrad",
|
| 9 |
+
"version": "0.1.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"@niivue/niivue": "^0.48.1"
|
| 12 |
+
},
|
| 13 |
+
"devDependencies": {
|
| 14 |
+
"vite": "^5.2.0"
|
| 15 |
+
}
|
| 16 |
+
},
|
| 17 |
+
"node_modules/@esbuild/aix-ppc64": {
|
| 18 |
+
"version": "0.20.2",
|
| 19 |
+
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
| 20 |
+
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
| 21 |
+
"cpu": [
|
| 22 |
+
"ppc64"
|
| 23 |
+
],
|
| 24 |
+
"dev": true,
|
| 25 |
+
"optional": true,
|
| 26 |
+
"os": [
|
| 27 |
+
"aix"
|
| 28 |
+
],
|
| 29 |
+
"engines": {
|
| 30 |
+
"node": ">=12"
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
"node_modules/@esbuild/android-arm": {
|
| 34 |
+
"version": "0.20.2",
|
| 35 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
| 36 |
+
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
| 37 |
+
"cpu": [
|
| 38 |
+
"arm"
|
| 39 |
+
],
|
| 40 |
+
"dev": true,
|
| 41 |
+
"optional": true,
|
| 42 |
+
"os": [
|
| 43 |
+
"android"
|
| 44 |
+
],
|
| 45 |
+
"engines": {
|
| 46 |
+
"node": ">=12"
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"node_modules/@esbuild/android-arm64": {
|
| 50 |
+
"version": "0.20.2",
|
| 51 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
| 52 |
+
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
| 53 |
+
"cpu": [
|
| 54 |
+
"arm64"
|
| 55 |
+
],
|
| 56 |
+
"dev": true,
|
| 57 |
+
"optional": true,
|
| 58 |
+
"os": [
|
| 59 |
+
"android"
|
| 60 |
+
],
|
| 61 |
+
"engines": {
|
| 62 |
+
"node": ">=12"
|
| 63 |
+
}
|
| 64 |
+
},
|
| 65 |
+
"node_modules/@esbuild/android-x64": {
|
| 66 |
+
"version": "0.20.2",
|
| 67 |
+
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
| 68 |
+
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
| 69 |
+
"cpu": [
|
| 70 |
+
"x64"
|
| 71 |
+
],
|
| 72 |
+
"dev": true,
|
| 73 |
+
"optional": true,
|
| 74 |
+
"os": [
|
| 75 |
+
"android"
|
| 76 |
+
],
|
| 77 |
+
"engines": {
|
| 78 |
+
"node": ">=12"
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"node_modules/@esbuild/darwin-arm64": {
|
| 82 |
+
"version": "0.20.2",
|
| 83 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
| 84 |
+
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
| 85 |
+
"cpu": [
|
| 86 |
+
"arm64"
|
| 87 |
+
],
|
| 88 |
+
"dev": true,
|
| 89 |
+
"optional": true,
|
| 90 |
+
"os": [
|
| 91 |
+
"darwin"
|
| 92 |
+
],
|
| 93 |
+
"engines": {
|
| 94 |
+
"node": ">=12"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"node_modules/@esbuild/darwin-x64": {
|
| 98 |
+
"version": "0.20.2",
|
| 99 |
+
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
| 100 |
+
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
| 101 |
+
"cpu": [
|
| 102 |
+
"x64"
|
| 103 |
+
],
|
| 104 |
+
"dev": true,
|
| 105 |
+
"optional": true,
|
| 106 |
+
"os": [
|
| 107 |
+
"darwin"
|
| 108 |
+
],
|
| 109 |
+
"engines": {
|
| 110 |
+
"node": ">=12"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"node_modules/@esbuild/freebsd-arm64": {
|
| 114 |
+
"version": "0.20.2",
|
| 115 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
| 116 |
+
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
| 117 |
+
"cpu": [
|
| 118 |
+
"arm64"
|
| 119 |
+
],
|
| 120 |
+
"dev": true,
|
| 121 |
+
"optional": true,
|
| 122 |
+
"os": [
|
| 123 |
+
"freebsd"
|
| 124 |
+
],
|
| 125 |
+
"engines": {
|
| 126 |
+
"node": ">=12"
|
| 127 |
+
}
|
| 128 |
+
},
|
| 129 |
+
"node_modules/@esbuild/freebsd-x64": {
|
| 130 |
+
"version": "0.20.2",
|
| 131 |
+
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
| 132 |
+
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
| 133 |
+
"cpu": [
|
| 134 |
+
"x64"
|
| 135 |
+
],
|
| 136 |
+
"dev": true,
|
| 137 |
+
"optional": true,
|
| 138 |
+
"os": [
|
| 139 |
+
"freebsd"
|
| 140 |
+
],
|
| 141 |
+
"engines": {
|
| 142 |
+
"node": ">=12"
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"node_modules/@esbuild/linux-arm": {
|
| 146 |
+
"version": "0.20.2",
|
| 147 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
| 148 |
+
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
| 149 |
+
"cpu": [
|
| 150 |
+
"arm"
|
| 151 |
+
],
|
| 152 |
+
"dev": true,
|
| 153 |
+
"optional": true,
|
| 154 |
+
"os": [
|
| 155 |
+
"linux"
|
| 156 |
+
],
|
| 157 |
+
"engines": {
|
| 158 |
+
"node": ">=12"
|
| 159 |
+
}
|
| 160 |
+
},
|
| 161 |
+
"node_modules/@esbuild/linux-arm64": {
|
| 162 |
+
"version": "0.20.2",
|
| 163 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
| 164 |
+
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
| 165 |
+
"cpu": [
|
| 166 |
+
"arm64"
|
| 167 |
+
],
|
| 168 |
+
"dev": true,
|
| 169 |
+
"optional": true,
|
| 170 |
+
"os": [
|
| 171 |
+
"linux"
|
| 172 |
+
],
|
| 173 |
+
"engines": {
|
| 174 |
+
"node": ">=12"
|
| 175 |
+
}
|
| 176 |
+
},
|
| 177 |
+
"node_modules/@esbuild/linux-ia32": {
|
| 178 |
+
"version": "0.20.2",
|
| 179 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
| 180 |
+
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
| 181 |
+
"cpu": [
|
| 182 |
+
"ia32"
|
| 183 |
+
],
|
| 184 |
+
"dev": true,
|
| 185 |
+
"optional": true,
|
| 186 |
+
"os": [
|
| 187 |
+
"linux"
|
| 188 |
+
],
|
| 189 |
+
"engines": {
|
| 190 |
+
"node": ">=12"
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
"node_modules/@esbuild/linux-loong64": {
|
| 194 |
+
"version": "0.20.2",
|
| 195 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
| 196 |
+
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
| 197 |
+
"cpu": [
|
| 198 |
+
"loong64"
|
| 199 |
+
],
|
| 200 |
+
"dev": true,
|
| 201 |
+
"optional": true,
|
| 202 |
+
"os": [
|
| 203 |
+
"linux"
|
| 204 |
+
],
|
| 205 |
+
"engines": {
|
| 206 |
+
"node": ">=12"
|
| 207 |
+
}
|
| 208 |
+
},
|
| 209 |
+
"node_modules/@esbuild/linux-mips64el": {
|
| 210 |
+
"version": "0.20.2",
|
| 211 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
| 212 |
+
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
| 213 |
+
"cpu": [
|
| 214 |
+
"mips64el"
|
| 215 |
+
],
|
| 216 |
+
"dev": true,
|
| 217 |
+
"optional": true,
|
| 218 |
+
"os": [
|
| 219 |
+
"linux"
|
| 220 |
+
],
|
| 221 |
+
"engines": {
|
| 222 |
+
"node": ">=12"
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
"node_modules/@esbuild/linux-ppc64": {
|
| 226 |
+
"version": "0.20.2",
|
| 227 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
| 228 |
+
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
| 229 |
+
"cpu": [
|
| 230 |
+
"ppc64"
|
| 231 |
+
],
|
| 232 |
+
"dev": true,
|
| 233 |
+
"optional": true,
|
| 234 |
+
"os": [
|
| 235 |
+
"linux"
|
| 236 |
+
],
|
| 237 |
+
"engines": {
|
| 238 |
+
"node": ">=12"
|
| 239 |
+
}
|
| 240 |
+
},
|
| 241 |
+
"node_modules/@esbuild/linux-riscv64": {
|
| 242 |
+
"version": "0.20.2",
|
| 243 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
| 244 |
+
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
| 245 |
+
"cpu": [
|
| 246 |
+
"riscv64"
|
| 247 |
+
],
|
| 248 |
+
"dev": true,
|
| 249 |
+
"optional": true,
|
| 250 |
+
"os": [
|
| 251 |
+
"linux"
|
| 252 |
+
],
|
| 253 |
+
"engines": {
|
| 254 |
+
"node": ">=12"
|
| 255 |
+
}
|
| 256 |
+
},
|
| 257 |
+
"node_modules/@esbuild/linux-s390x": {
|
| 258 |
+
"version": "0.20.2",
|
| 259 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
| 260 |
+
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
| 261 |
+
"cpu": [
|
| 262 |
+
"s390x"
|
| 263 |
+
],
|
| 264 |
+
"dev": true,
|
| 265 |
+
"optional": true,
|
| 266 |
+
"os": [
|
| 267 |
+
"linux"
|
| 268 |
+
],
|
| 269 |
+
"engines": {
|
| 270 |
+
"node": ">=12"
|
| 271 |
+
}
|
| 272 |
+
},
|
| 273 |
+
"node_modules/@esbuild/linux-x64": {
|
| 274 |
+
"version": "0.20.2",
|
| 275 |
+
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
| 276 |
+
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
| 277 |
+
"cpu": [
|
| 278 |
+
"x64"
|
| 279 |
+
],
|
| 280 |
+
"dev": true,
|
| 281 |
+
"optional": true,
|
| 282 |
+
"os": [
|
| 283 |
+
"linux"
|
| 284 |
+
],
|
| 285 |
+
"engines": {
|
| 286 |
+
"node": ">=12"
|
| 287 |
+
}
|
| 288 |
+
},
|
| 289 |
+
"node_modules/@esbuild/netbsd-x64": {
|
| 290 |
+
"version": "0.20.2",
|
| 291 |
+
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
| 292 |
+
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
| 293 |
+
"cpu": [
|
| 294 |
+
"x64"
|
| 295 |
+
],
|
| 296 |
+
"dev": true,
|
| 297 |
+
"optional": true,
|
| 298 |
+
"os": [
|
| 299 |
+
"netbsd"
|
| 300 |
+
],
|
| 301 |
+
"engines": {
|
| 302 |
+
"node": ">=12"
|
| 303 |
+
}
|
| 304 |
+
},
|
| 305 |
+
"node_modules/@esbuild/openbsd-x64": {
|
| 306 |
+
"version": "0.20.2",
|
| 307 |
+
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
| 308 |
+
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
| 309 |
+
"cpu": [
|
| 310 |
+
"x64"
|
| 311 |
+
],
|
| 312 |
+
"dev": true,
|
| 313 |
+
"optional": true,
|
| 314 |
+
"os": [
|
| 315 |
+
"openbsd"
|
| 316 |
+
],
|
| 317 |
+
"engines": {
|
| 318 |
+
"node": ">=12"
|
| 319 |
+
}
|
| 320 |
+
},
|
| 321 |
+
"node_modules/@esbuild/sunos-x64": {
|
| 322 |
+
"version": "0.20.2",
|
| 323 |
+
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
| 324 |
+
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
| 325 |
+
"cpu": [
|
| 326 |
+
"x64"
|
| 327 |
+
],
|
| 328 |
+
"dev": true,
|
| 329 |
+
"optional": true,
|
| 330 |
+
"os": [
|
| 331 |
+
"sunos"
|
| 332 |
+
],
|
| 333 |
+
"engines": {
|
| 334 |
+
"node": ">=12"
|
| 335 |
+
}
|
| 336 |
+
},
|
| 337 |
+
"node_modules/@esbuild/win32-arm64": {
|
| 338 |
+
"version": "0.20.2",
|
| 339 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
| 340 |
+
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
| 341 |
+
"cpu": [
|
| 342 |
+
"arm64"
|
| 343 |
+
],
|
| 344 |
+
"dev": true,
|
| 345 |
+
"optional": true,
|
| 346 |
+
"os": [
|
| 347 |
+
"win32"
|
| 348 |
+
],
|
| 349 |
+
"engines": {
|
| 350 |
+
"node": ">=12"
|
| 351 |
+
}
|
| 352 |
+
},
|
| 353 |
+
"node_modules/@esbuild/win32-ia32": {
|
| 354 |
+
"version": "0.20.2",
|
| 355 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
| 356 |
+
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
| 357 |
+
"cpu": [
|
| 358 |
+
"ia32"
|
| 359 |
+
],
|
| 360 |
+
"dev": true,
|
| 361 |
+
"optional": true,
|
| 362 |
+
"os": [
|
| 363 |
+
"win32"
|
| 364 |
+
],
|
| 365 |
+
"engines": {
|
| 366 |
+
"node": ">=12"
|
| 367 |
+
}
|
| 368 |
+
},
|
| 369 |
+
"node_modules/@esbuild/win32-x64": {
|
| 370 |
+
"version": "0.20.2",
|
| 371 |
+
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
| 372 |
+
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
| 373 |
+
"cpu": [
|
| 374 |
+
"x64"
|
| 375 |
+
],
|
| 376 |
+
"dev": true,
|
| 377 |
+
"optional": true,
|
| 378 |
+
"os": [
|
| 379 |
+
"win32"
|
| 380 |
+
],
|
| 381 |
+
"engines": {
|
| 382 |
+
"node": ">=12"
|
| 383 |
+
}
|
| 384 |
+
},
|
| 385 |
+
"node_modules/@lukeed/csprng": {
|
| 386 |
+
"version": "1.1.0",
|
| 387 |
+
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
|
| 388 |
+
"integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
|
| 389 |
+
"engines": {
|
| 390 |
+
"node": ">=8"
|
| 391 |
+
}
|
| 392 |
+
},
|
| 393 |
+
"node_modules/@lukeed/uuid": {
|
| 394 |
+
"version": "2.0.1",
|
| 395 |
+
"resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz",
|
| 396 |
+
"integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==",
|
| 397 |
+
"dependencies": {
|
| 398 |
+
"@lukeed/csprng": "^1.1.0"
|
| 399 |
+
},
|
| 400 |
+
"engines": {
|
| 401 |
+
"node": ">=8"
|
| 402 |
+
}
|
| 403 |
+
},
|
| 404 |
+
"node_modules/@niivue/niivue": {
|
| 405 |
+
"version": "0.48.1",
|
| 406 |
+
"resolved": "https://registry.npmjs.org/@niivue/niivue/-/niivue-0.48.1.tgz",
|
| 407 |
+
"integrity": "sha512-xeSrJ/3BBWiQsI6tK8YL3DOGumnX4oMfMZPpUIji+2mIYlqlylLxBtzAKIBIttEmJcwx/PzYb4HeY6TQVUvMzw==",
|
| 408 |
+
"license": "BSD-2-Clause",
|
| 409 |
+
"dependencies": {
|
| 410 |
+
"@lukeed/uuid": "^2.0.1",
|
| 411 |
+
"@ungap/structured-clone": "^1.2.0",
|
| 412 |
+
"array-equal": "^1.0.2",
|
| 413 |
+
"daikon": "^1.2.46",
|
| 414 |
+
"fflate": "^0.8.2",
|
| 415 |
+
"gl-matrix": "^3.4.3",
|
| 416 |
+
"nifti-reader-js": "^0.6.8",
|
| 417 |
+
"rxjs": "^7.8.1"
|
| 418 |
+
},
|
| 419 |
+
"optionalDependencies": {
|
| 420 |
+
"@rollup/rollup-linux-x64-gnu": "^4.18.1"
|
| 421 |
+
}
|
| 422 |
+
},
|
| 423 |
+
"node_modules/@niivue/niivue/node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 424 |
+
"version": "4.31.0",
|
| 425 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz",
|
| 426 |
+
"integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==",
|
| 427 |
+
"cpu": [
|
| 428 |
+
"x64"
|
| 429 |
+
],
|
| 430 |
+
"license": "MIT",
|
| 431 |
+
"optional": true,
|
| 432 |
+
"os": [
|
| 433 |
+
"linux"
|
| 434 |
+
]
|
| 435 |
+
},
|
| 436 |
+
"node_modules/@rollup/rollup-android-arm-eabi": {
|
| 437 |
+
"version": "4.16.4",
|
| 438 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz",
|
| 439 |
+
"integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==",
|
| 440 |
+
"cpu": [
|
| 441 |
+
"arm"
|
| 442 |
+
],
|
| 443 |
+
"dev": true,
|
| 444 |
+
"optional": true,
|
| 445 |
+
"os": [
|
| 446 |
+
"android"
|
| 447 |
+
]
|
| 448 |
+
},
|
| 449 |
+
"node_modules/@rollup/rollup-android-arm64": {
|
| 450 |
+
"version": "4.16.4",
|
| 451 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz",
|
| 452 |
+
"integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==",
|
| 453 |
+
"cpu": [
|
| 454 |
+
"arm64"
|
| 455 |
+
],
|
| 456 |
+
"dev": true,
|
| 457 |
+
"optional": true,
|
| 458 |
+
"os": [
|
| 459 |
+
"android"
|
| 460 |
+
]
|
| 461 |
+
},
|
| 462 |
+
"node_modules/@rollup/rollup-darwin-arm64": {
|
| 463 |
+
"version": "4.16.4",
|
| 464 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz",
|
| 465 |
+
"integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==",
|
| 466 |
+
"cpu": [
|
| 467 |
+
"arm64"
|
| 468 |
+
],
|
| 469 |
+
"dev": true,
|
| 470 |
+
"optional": true,
|
| 471 |
+
"os": [
|
| 472 |
+
"darwin"
|
| 473 |
+
]
|
| 474 |
+
},
|
| 475 |
+
"node_modules/@rollup/rollup-darwin-x64": {
|
| 476 |
+
"version": "4.16.4",
|
| 477 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz",
|
| 478 |
+
"integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==",
|
| 479 |
+
"cpu": [
|
| 480 |
+
"x64"
|
| 481 |
+
],
|
| 482 |
+
"dev": true,
|
| 483 |
+
"optional": true,
|
| 484 |
+
"os": [
|
| 485 |
+
"darwin"
|
| 486 |
+
]
|
| 487 |
+
},
|
| 488 |
+
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
| 489 |
+
"version": "4.16.4",
|
| 490 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz",
|
| 491 |
+
"integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==",
|
| 492 |
+
"cpu": [
|
| 493 |
+
"arm"
|
| 494 |
+
],
|
| 495 |
+
"dev": true,
|
| 496 |
+
"optional": true,
|
| 497 |
+
"os": [
|
| 498 |
+
"linux"
|
| 499 |
+
]
|
| 500 |
+
},
|
| 501 |
+
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
| 502 |
+
"version": "4.16.4",
|
| 503 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz",
|
| 504 |
+
"integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==",
|
| 505 |
+
"cpu": [
|
| 506 |
+
"arm"
|
| 507 |
+
],
|
| 508 |
+
"dev": true,
|
| 509 |
+
"optional": true,
|
| 510 |
+
"os": [
|
| 511 |
+
"linux"
|
| 512 |
+
]
|
| 513 |
+
},
|
| 514 |
+
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
| 515 |
+
"version": "4.16.4",
|
| 516 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz",
|
| 517 |
+
"integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==",
|
| 518 |
+
"cpu": [
|
| 519 |
+
"arm64"
|
| 520 |
+
],
|
| 521 |
+
"dev": true,
|
| 522 |
+
"optional": true,
|
| 523 |
+
"os": [
|
| 524 |
+
"linux"
|
| 525 |
+
]
|
| 526 |
+
},
|
| 527 |
+
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
| 528 |
+
"version": "4.16.4",
|
| 529 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz",
|
| 530 |
+
"integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==",
|
| 531 |
+
"cpu": [
|
| 532 |
+
"arm64"
|
| 533 |
+
],
|
| 534 |
+
"dev": true,
|
| 535 |
+
"optional": true,
|
| 536 |
+
"os": [
|
| 537 |
+
"linux"
|
| 538 |
+
]
|
| 539 |
+
},
|
| 540 |
+
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
| 541 |
+
"version": "4.16.4",
|
| 542 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz",
|
| 543 |
+
"integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==",
|
| 544 |
+
"cpu": [
|
| 545 |
+
"ppc64"
|
| 546 |
+
],
|
| 547 |
+
"dev": true,
|
| 548 |
+
"optional": true,
|
| 549 |
+
"os": [
|
| 550 |
+
"linux"
|
| 551 |
+
]
|
| 552 |
+
},
|
| 553 |
+
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
| 554 |
+
"version": "4.16.4",
|
| 555 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz",
|
| 556 |
+
"integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==",
|
| 557 |
+
"cpu": [
|
| 558 |
+
"riscv64"
|
| 559 |
+
],
|
| 560 |
+
"dev": true,
|
| 561 |
+
"optional": true,
|
| 562 |
+
"os": [
|
| 563 |
+
"linux"
|
| 564 |
+
]
|
| 565 |
+
},
|
| 566 |
+
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
| 567 |
+
"version": "4.16.4",
|
| 568 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz",
|
| 569 |
+
"integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==",
|
| 570 |
+
"cpu": [
|
| 571 |
+
"s390x"
|
| 572 |
+
],
|
| 573 |
+
"dev": true,
|
| 574 |
+
"optional": true,
|
| 575 |
+
"os": [
|
| 576 |
+
"linux"
|
| 577 |
+
]
|
| 578 |
+
},
|
| 579 |
+
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
| 580 |
+
"version": "4.16.4",
|
| 581 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz",
|
| 582 |
+
"integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==",
|
| 583 |
+
"cpu": [
|
| 584 |
+
"x64"
|
| 585 |
+
],
|
| 586 |
+
"dev": true,
|
| 587 |
+
"optional": true,
|
| 588 |
+
"os": [
|
| 589 |
+
"linux"
|
| 590 |
+
]
|
| 591 |
+
},
|
| 592 |
+
"node_modules/@rollup/rollup-linux-x64-musl": {
|
| 593 |
+
"version": "4.16.4",
|
| 594 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz",
|
| 595 |
+
"integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==",
|
| 596 |
+
"cpu": [
|
| 597 |
+
"x64"
|
| 598 |
+
],
|
| 599 |
+
"dev": true,
|
| 600 |
+
"optional": true,
|
| 601 |
+
"os": [
|
| 602 |
+
"linux"
|
| 603 |
+
]
|
| 604 |
+
},
|
| 605 |
+
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
| 606 |
+
"version": "4.16.4",
|
| 607 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz",
|
| 608 |
+
"integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==",
|
| 609 |
+
"cpu": [
|
| 610 |
+
"arm64"
|
| 611 |
+
],
|
| 612 |
+
"dev": true,
|
| 613 |
+
"optional": true,
|
| 614 |
+
"os": [
|
| 615 |
+
"win32"
|
| 616 |
+
]
|
| 617 |
+
},
|
| 618 |
+
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
| 619 |
+
"version": "4.16.4",
|
| 620 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz",
|
| 621 |
+
"integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==",
|
| 622 |
+
"cpu": [
|
| 623 |
+
"ia32"
|
| 624 |
+
],
|
| 625 |
+
"dev": true,
|
| 626 |
+
"optional": true,
|
| 627 |
+
"os": [
|
| 628 |
+
"win32"
|
| 629 |
+
]
|
| 630 |
+
},
|
| 631 |
+
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
| 632 |
+
"version": "4.16.4",
|
| 633 |
+
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz",
|
| 634 |
+
"integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==",
|
| 635 |
+
"cpu": [
|
| 636 |
+
"x64"
|
| 637 |
+
],
|
| 638 |
+
"dev": true,
|
| 639 |
+
"optional": true,
|
| 640 |
+
"os": [
|
| 641 |
+
"win32"
|
| 642 |
+
]
|
| 643 |
+
},
|
| 644 |
+
"node_modules/@types/estree": {
|
| 645 |
+
"version": "1.0.5",
|
| 646 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
| 647 |
+
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
| 648 |
+
"dev": true
|
| 649 |
+
},
|
| 650 |
+
"node_modules/@ungap/structured-clone": {
|
| 651 |
+
"version": "1.2.0",
|
| 652 |
+
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
| 653 |
+
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
|
| 654 |
+
},
|
| 655 |
+
"node_modules/@wearemothership/dicom-character-set": {
|
| 656 |
+
"version": "1.0.4-opt.1",
|
| 657 |
+
"resolved": "https://registry.npmjs.org/@wearemothership/dicom-character-set/-/dicom-character-set-1.0.4-opt.1.tgz",
|
| 658 |
+
"integrity": "sha512-stqhnpawYHY2UZKj4RHTF71ab3q3z8S1SO9ToQKjsHQwowUdFVo6YFea93psFux3yqNbRlQjwoCdPjHcD0YQzw==",
|
| 659 |
+
"engines": {
|
| 660 |
+
"node": ">=10"
|
| 661 |
+
}
|
| 662 |
+
},
|
| 663 |
+
"node_modules/array-equal": {
|
| 664 |
+
"version": "1.0.2",
|
| 665 |
+
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.2.tgz",
|
| 666 |
+
"integrity": "sha512-gUHx76KtnhEgB3HOuFYiCm3FIdEs6ocM2asHvNTkfu/Y09qQVrrVVaOKENmS2KkSaGoxgXNqC+ZVtR/n0MOkSA==",
|
| 667 |
+
"funding": {
|
| 668 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 669 |
+
}
|
| 670 |
+
},
|
| 671 |
+
"node_modules/commander": {
|
| 672 |
+
"version": "2.20.3",
|
| 673 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
| 674 |
+
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
| 675 |
+
},
|
| 676 |
+
"node_modules/cssfilter": {
|
| 677 |
+
"version": "0.0.10",
|
| 678 |
+
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
|
| 679 |
+
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
|
| 680 |
+
},
|
| 681 |
+
"node_modules/daikon": {
|
| 682 |
+
"version": "1.2.46",
|
| 683 |
+
"resolved": "https://registry.npmjs.org/daikon/-/daikon-1.2.46.tgz",
|
| 684 |
+
"integrity": "sha512-S8dTTlsWYTH3LQztjTW9KnNvxDeL2mr2cau0auLdYMJe4TrocYP1PmidHizO3rXUs+gXpBWI1PQ2qvB4b21QFw==",
|
| 685 |
+
"dependencies": {
|
| 686 |
+
"@wearemothership/dicom-character-set": "^1.0.4-opt.1",
|
| 687 |
+
"fflate": "*",
|
| 688 |
+
"jpeg-lossless-decoder-js": "2.0.7",
|
| 689 |
+
"pako": "^2.1",
|
| 690 |
+
"xss": "1.0.14"
|
| 691 |
+
}
|
| 692 |
+
},
|
| 693 |
+
"node_modules/esbuild": {
|
| 694 |
+
"version": "0.20.2",
|
| 695 |
+
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
| 696 |
+
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
| 697 |
+
"dev": true,
|
| 698 |
+
"hasInstallScript": true,
|
| 699 |
+
"bin": {
|
| 700 |
+
"esbuild": "bin/esbuild"
|
| 701 |
+
},
|
| 702 |
+
"engines": {
|
| 703 |
+
"node": ">=12"
|
| 704 |
+
},
|
| 705 |
+
"optionalDependencies": {
|
| 706 |
+
"@esbuild/aix-ppc64": "0.20.2",
|
| 707 |
+
"@esbuild/android-arm": "0.20.2",
|
| 708 |
+
"@esbuild/android-arm64": "0.20.2",
|
| 709 |
+
"@esbuild/android-x64": "0.20.2",
|
| 710 |
+
"@esbuild/darwin-arm64": "0.20.2",
|
| 711 |
+
"@esbuild/darwin-x64": "0.20.2",
|
| 712 |
+
"@esbuild/freebsd-arm64": "0.20.2",
|
| 713 |
+
"@esbuild/freebsd-x64": "0.20.2",
|
| 714 |
+
"@esbuild/linux-arm": "0.20.2",
|
| 715 |
+
"@esbuild/linux-arm64": "0.20.2",
|
| 716 |
+
"@esbuild/linux-ia32": "0.20.2",
|
| 717 |
+
"@esbuild/linux-loong64": "0.20.2",
|
| 718 |
+
"@esbuild/linux-mips64el": "0.20.2",
|
| 719 |
+
"@esbuild/linux-ppc64": "0.20.2",
|
| 720 |
+
"@esbuild/linux-riscv64": "0.20.2",
|
| 721 |
+
"@esbuild/linux-s390x": "0.20.2",
|
| 722 |
+
"@esbuild/linux-x64": "0.20.2",
|
| 723 |
+
"@esbuild/netbsd-x64": "0.20.2",
|
| 724 |
+
"@esbuild/openbsd-x64": "0.20.2",
|
| 725 |
+
"@esbuild/sunos-x64": "0.20.2",
|
| 726 |
+
"@esbuild/win32-arm64": "0.20.2",
|
| 727 |
+
"@esbuild/win32-ia32": "0.20.2",
|
| 728 |
+
"@esbuild/win32-x64": "0.20.2"
|
| 729 |
+
}
|
| 730 |
+
},
|
| 731 |
+
"node_modules/fflate": {
|
| 732 |
+
"version": "0.8.2",
|
| 733 |
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
| 734 |
+
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
|
| 735 |
+
},
|
| 736 |
+
"node_modules/fsevents": {
|
| 737 |
+
"version": "2.3.3",
|
| 738 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 739 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 740 |
+
"dev": true,
|
| 741 |
+
"hasInstallScript": true,
|
| 742 |
+
"optional": true,
|
| 743 |
+
"os": [
|
| 744 |
+
"darwin"
|
| 745 |
+
],
|
| 746 |
+
"engines": {
|
| 747 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 748 |
+
}
|
| 749 |
+
},
|
| 750 |
+
"node_modules/gl-matrix": {
|
| 751 |
+
"version": "3.4.3",
|
| 752 |
+
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
|
| 753 |
+
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
|
| 754 |
+
},
|
| 755 |
+
"node_modules/jpeg-lossless-decoder-js": {
|
| 756 |
+
"version": "2.0.7",
|
| 757 |
+
"resolved": "https://registry.npmjs.org/jpeg-lossless-decoder-js/-/jpeg-lossless-decoder-js-2.0.7.tgz",
|
| 758 |
+
"integrity": "sha512-tbZlhFkKmx+JaqVMkq47SKWGuXLkIaV8fTbnhO39dYEnQrSShLGuLCGb0n6ntXjtmk6oAWGiIriWOLwj9od0yQ=="
|
| 759 |
+
},
|
| 760 |
+
"node_modules/nanoid": {
|
| 761 |
+
"version": "3.3.7",
|
| 762 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
| 763 |
+
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
| 764 |
+
"dev": true,
|
| 765 |
+
"funding": [
|
| 766 |
+
{
|
| 767 |
+
"type": "github",
|
| 768 |
+
"url": "https://github.com/sponsors/ai"
|
| 769 |
+
}
|
| 770 |
+
],
|
| 771 |
+
"bin": {
|
| 772 |
+
"nanoid": "bin/nanoid.cjs"
|
| 773 |
+
},
|
| 774 |
+
"engines": {
|
| 775 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 776 |
+
}
|
| 777 |
+
},
|
| 778 |
+
"node_modules/nifti-reader-js": {
|
| 779 |
+
"version": "0.6.8",
|
| 780 |
+
"resolved": "https://registry.npmjs.org/nifti-reader-js/-/nifti-reader-js-0.6.8.tgz",
|
| 781 |
+
"integrity": "sha512-yIKNVzYFiUcSHazoR+sd6Ka7sUmZTabaVqJRFxbdlAKR1hnPBuNP71g3AyApo37nJ3k41c632QPij5q7gF1YPQ==",
|
| 782 |
+
"dependencies": {
|
| 783 |
+
"fflate": "*"
|
| 784 |
+
}
|
| 785 |
+
},
|
| 786 |
+
"node_modules/pako": {
|
| 787 |
+
"version": "2.1.0",
|
| 788 |
+
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
| 789 |
+
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
| 790 |
+
},
|
| 791 |
+
"node_modules/picocolors": {
|
| 792 |
+
"version": "1.0.0",
|
| 793 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
| 794 |
+
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
| 795 |
+
"dev": true
|
| 796 |
+
},
|
| 797 |
+
"node_modules/postcss": {
|
| 798 |
+
"version": "8.4.38",
|
| 799 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
| 800 |
+
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
| 801 |
+
"dev": true,
|
| 802 |
+
"funding": [
|
| 803 |
+
{
|
| 804 |
+
"type": "opencollective",
|
| 805 |
+
"url": "https://opencollective.com/postcss/"
|
| 806 |
+
},
|
| 807 |
+
{
|
| 808 |
+
"type": "tidelift",
|
| 809 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 810 |
+
},
|
| 811 |
+
{
|
| 812 |
+
"type": "github",
|
| 813 |
+
"url": "https://github.com/sponsors/ai"
|
| 814 |
+
}
|
| 815 |
+
],
|
| 816 |
+
"dependencies": {
|
| 817 |
+
"nanoid": "^3.3.7",
|
| 818 |
+
"picocolors": "^1.0.0",
|
| 819 |
+
"source-map-js": "^1.2.0"
|
| 820 |
+
},
|
| 821 |
+
"engines": {
|
| 822 |
+
"node": "^10 || ^12 || >=14"
|
| 823 |
+
}
|
| 824 |
+
},
|
| 825 |
+
"node_modules/rollup": {
|
| 826 |
+
"version": "4.16.4",
|
| 827 |
+
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz",
|
| 828 |
+
"integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==",
|
| 829 |
+
"dev": true,
|
| 830 |
+
"dependencies": {
|
| 831 |
+
"@types/estree": "1.0.5"
|
| 832 |
+
},
|
| 833 |
+
"bin": {
|
| 834 |
+
"rollup": "dist/bin/rollup"
|
| 835 |
+
},
|
| 836 |
+
"engines": {
|
| 837 |
+
"node": ">=18.0.0",
|
| 838 |
+
"npm": ">=8.0.0"
|
| 839 |
+
},
|
| 840 |
+
"optionalDependencies": {
|
| 841 |
+
"@rollup/rollup-android-arm-eabi": "4.16.4",
|
| 842 |
+
"@rollup/rollup-android-arm64": "4.16.4",
|
| 843 |
+
"@rollup/rollup-darwin-arm64": "4.16.4",
|
| 844 |
+
"@rollup/rollup-darwin-x64": "4.16.4",
|
| 845 |
+
"@rollup/rollup-linux-arm-gnueabihf": "4.16.4",
|
| 846 |
+
"@rollup/rollup-linux-arm-musleabihf": "4.16.4",
|
| 847 |
+
"@rollup/rollup-linux-arm64-gnu": "4.16.4",
|
| 848 |
+
"@rollup/rollup-linux-arm64-musl": "4.16.4",
|
| 849 |
+
"@rollup/rollup-linux-powerpc64le-gnu": "4.16.4",
|
| 850 |
+
"@rollup/rollup-linux-riscv64-gnu": "4.16.4",
|
| 851 |
+
"@rollup/rollup-linux-s390x-gnu": "4.16.4",
|
| 852 |
+
"@rollup/rollup-linux-x64-gnu": "4.16.4",
|
| 853 |
+
"@rollup/rollup-linux-x64-musl": "4.16.4",
|
| 854 |
+
"@rollup/rollup-win32-arm64-msvc": "4.16.4",
|
| 855 |
+
"@rollup/rollup-win32-ia32-msvc": "4.16.4",
|
| 856 |
+
"@rollup/rollup-win32-x64-msvc": "4.16.4",
|
| 857 |
+
"fsevents": "~2.3.2"
|
| 858 |
+
}
|
| 859 |
+
},
|
| 860 |
+
"node_modules/rxjs": {
|
| 861 |
+
"version": "7.8.1",
|
| 862 |
+
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
| 863 |
+
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
| 864 |
+
"dependencies": {
|
| 865 |
+
"tslib": "^2.1.0"
|
| 866 |
+
}
|
| 867 |
+
},
|
| 868 |
+
"node_modules/source-map-js": {
|
| 869 |
+
"version": "1.2.0",
|
| 870 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
| 871 |
+
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
| 872 |
+
"dev": true,
|
| 873 |
+
"engines": {
|
| 874 |
+
"node": ">=0.10.0"
|
| 875 |
+
}
|
| 876 |
+
},
|
| 877 |
+
"node_modules/tslib": {
|
| 878 |
+
"version": "2.6.2",
|
| 879 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
| 880 |
+
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
| 881 |
+
},
|
| 882 |
+
"node_modules/vite": {
|
| 883 |
+
"version": "5.2.10",
|
| 884 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz",
|
| 885 |
+
"integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==",
|
| 886 |
+
"dev": true,
|
| 887 |
+
"dependencies": {
|
| 888 |
+
"esbuild": "^0.20.1",
|
| 889 |
+
"postcss": "^8.4.38",
|
| 890 |
+
"rollup": "^4.13.0"
|
| 891 |
+
},
|
| 892 |
+
"bin": {
|
| 893 |
+
"vite": "bin/vite.js"
|
| 894 |
+
},
|
| 895 |
+
"engines": {
|
| 896 |
+
"node": "^18.0.0 || >=20.0.0"
|
| 897 |
+
},
|
| 898 |
+
"funding": {
|
| 899 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 900 |
+
},
|
| 901 |
+
"optionalDependencies": {
|
| 902 |
+
"fsevents": "~2.3.3"
|
| 903 |
+
},
|
| 904 |
+
"peerDependencies": {
|
| 905 |
+
"@types/node": "^18.0.0 || >=20.0.0",
|
| 906 |
+
"less": "*",
|
| 907 |
+
"lightningcss": "^1.21.0",
|
| 908 |
+
"sass": "*",
|
| 909 |
+
"stylus": "*",
|
| 910 |
+
"sugarss": "*",
|
| 911 |
+
"terser": "^5.4.0"
|
| 912 |
+
},
|
| 913 |
+
"peerDependenciesMeta": {
|
| 914 |
+
"@types/node": {
|
| 915 |
+
"optional": true
|
| 916 |
+
},
|
| 917 |
+
"less": {
|
| 918 |
+
"optional": true
|
| 919 |
+
},
|
| 920 |
+
"lightningcss": {
|
| 921 |
+
"optional": true
|
| 922 |
+
},
|
| 923 |
+
"sass": {
|
| 924 |
+
"optional": true
|
| 925 |
+
},
|
| 926 |
+
"stylus": {
|
| 927 |
+
"optional": true
|
| 928 |
+
},
|
| 929 |
+
"sugarss": {
|
| 930 |
+
"optional": true
|
| 931 |
+
},
|
| 932 |
+
"terser": {
|
| 933 |
+
"optional": true
|
| 934 |
+
}
|
| 935 |
+
}
|
| 936 |
+
},
|
| 937 |
+
"node_modules/xss": {
|
| 938 |
+
"version": "1.0.14",
|
| 939 |
+
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
|
| 940 |
+
"integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
|
| 941 |
+
"dependencies": {
|
| 942 |
+
"commander": "^2.20.3",
|
| 943 |
+
"cssfilter": "0.0.10"
|
| 944 |
+
},
|
| 945 |
+
"bin": {
|
| 946 |
+
"xss": "bin/xss"
|
| 947 |
+
},
|
| 948 |
+
"engines": {
|
| 949 |
+
"node": ">= 0.10.0"
|
| 950 |
+
}
|
| 951 |
+
}
|
| 952 |
+
}
|
| 953 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "niivue-tinygrad",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.1.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"preview": "vite preview"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"@niivue/niivue": "^0.48.1"
|
| 13 |
+
},
|
| 14 |
+
"devDependencies": {
|
| 15 |
+
"vite": "^5.2.0"
|
| 16 |
+
}
|
| 17 |
+
}
|
public/colormap_mindgrab.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"R": [0, 255],
|
| 3 |
+
"G": [0, 0],
|
| 4 |
+
"B": [0, 0],
|
| 5 |
+
"labels": ["background", "brain"]
|
| 6 |
+
}
|
public/favicon.ico
ADDED
|
|
public/net_mindgrab.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ff5dc4075c10687a2023236248c1d3ba3d5adfa4a4a0b2150bcdd1e21b5d0554
|
| 3 |
+
size 587188
|
public/net_mindgrab_tta_axial.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:36045f90556ed4969339654f7c6256c5af61ba8fa78e77dc6a67e064ca961f54
|
| 3 |
+
size 849596
|
public/net_mindgrab_tta_coronal.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:36045f90556ed4969339654f7c6256c5af61ba8fa78e77dc6a67e064ca961f54
|
| 3 |
+
size 849596
|
public/net_mindgrab_tta_sagittal.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:36045f90556ed4969339654f7c6256c5af61ba8fa78e77dc6a67e064ca961f54
|
| 3 |
+
size 849596
|
public/niivue.css
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
html {
|
| 2 |
+
height: auto;
|
| 3 |
+
min-height: 100%;
|
| 4 |
+
margin: 0;
|
| 5 |
+
}
|
| 6 |
+
body {
|
| 7 |
+
display: flex;
|
| 8 |
+
flex-direction: column;
|
| 9 |
+
margin: 0;
|
| 10 |
+
min-height: 100%;
|
| 11 |
+
width: 100%;
|
| 12 |
+
position: absolute;
|
| 13 |
+
font-family: system-ui, Arial, Helvetica, sans-serif;
|
| 14 |
+
user-select: none; /* Standard syntax */
|
| 15 |
+
color: white;
|
| 16 |
+
background: #303030;
|
| 17 |
+
}
|
| 18 |
+
header {
|
| 19 |
+
margin: 10px;
|
| 20 |
+
}
|
| 21 |
+
main {
|
| 22 |
+
flex: 1;
|
| 23 |
+
background: #000000;
|
| 24 |
+
position: relative;
|
| 25 |
+
}
|
| 26 |
+
footer {
|
| 27 |
+
margin: 10px;
|
| 28 |
+
}
|
| 29 |
+
canvas {
|
| 30 |
+
position: absolute;
|
| 31 |
+
cursor: crosshair;
|
| 32 |
+
}
|
| 33 |
+
canvas:focus {
|
| 34 |
+
outline: 0px;
|
| 35 |
+
}
|
| 36 |
+
div {
|
| 37 |
+
display: table-row;
|
| 38 |
+
background-color: blue;
|
| 39 |
+
}
|
| 40 |
+
.dropdown {
|
| 41 |
+
float: left;
|
| 42 |
+
overflow: hidden;
|
| 43 |
+
}
|
| 44 |
+
.dropdown .dropbtn {
|
| 45 |
+
font-size: 16px;
|
| 46 |
+
border: none;
|
| 47 |
+
outline: none;
|
| 48 |
+
color: white;
|
| 49 |
+
padding: 12px 12px;
|
| 50 |
+
background-color: #303030;
|
| 51 |
+
font-family: inherit;
|
| 52 |
+
margin: 0;
|
| 53 |
+
}
|
| 54 |
+
.dropdown:hover .dropbtn {
|
| 55 |
+
background-color: #9a9;
|
| 56 |
+
}
|
| 57 |
+
.dropdown-content {
|
| 58 |
+
display: none;
|
| 59 |
+
position: absolute;
|
| 60 |
+
background-color: #303030;
|
| 61 |
+
min-width: 160px;
|
| 62 |
+
border-radius: 5px;
|
| 63 |
+
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
| 64 |
+
z-index: 1;
|
| 65 |
+
}
|
| 66 |
+
.dropdown-content a {
|
| 67 |
+
float: none;
|
| 68 |
+
color: white;
|
| 69 |
+
padding: 12px 16px;
|
| 70 |
+
text-decoration: none;
|
| 71 |
+
display: block;
|
| 72 |
+
text-align: left;
|
| 73 |
+
line-height: 6px;
|
| 74 |
+
}
|
| 75 |
+
.dropdown-content a:hover {
|
| 76 |
+
background-color: #aba;
|
| 77 |
+
}
|
| 78 |
+
.dropdown:hover .dropdown-content {
|
| 79 |
+
display: block;
|
| 80 |
+
}
|
| 81 |
+
.dropdown-item-checked::before {
|
| 82 |
+
position: absolute;
|
| 83 |
+
left: 0.2rem;
|
| 84 |
+
content: "\2022"; /* or '✓' */
|
| 85 |
+
font-weight: 600;
|
| 86 |
+
}
|
| 87 |
+
.divider {
|
| 88 |
+
border-top: 1px solid grey;
|
| 89 |
+
}
|
| 90 |
+
.vertical-divider {
|
| 91 |
+
border-left: 1px solid grey;
|
| 92 |
+
height: 40px;
|
| 93 |
+
}
|
| 94 |
+
.help-text {
|
| 95 |
+
margin: auto;
|
| 96 |
+
max-width: 150px;
|
| 97 |
+
padding: 0 10px;
|
| 98 |
+
}
|
| 99 |
+
.slidecontainer {
|
| 100 |
+
padding: 10px 10px;
|
| 101 |
+
white-space: normal;
|
| 102 |
+
word-break: break-word;
|
| 103 |
+
display: flex;
|
| 104 |
+
align-items: center;
|
| 105 |
+
flex: 0 0 auto;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
div.footer { width: 100%; display: block; background: #303030;}
|
| 109 |
+
table.footer { width: 100%;height: 100%; table-layout: fixed;}
|
public/t1_crop.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9f5708dd1980fe0017ad70f63d895bae66bd9f5487eb1c38d129591116113613
|
| 3 |
+
size 3068922
|
style.css
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
body {
|
| 2 |
-
padding: 2rem;
|
| 3 |
-
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
|
| 4 |
-
}
|
| 5 |
-
|
| 6 |
-
h1 {
|
| 7 |
-
font-size: 16px;
|
| 8 |
-
margin-top: 0;
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
p {
|
| 12 |
-
color: rgb(107, 114, 128);
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
.card {
|
| 19 |
-
max-width: 620px;
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
.card p:last-child {
|
| 27 |
-
margin-bottom: 0;
|
| 28 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tensor-utils.js
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as tf from '@tensorflow/tfjs'
|
| 2 |
+
import { BWLabeler } from './bwlabels.js'
|
| 3 |
+
|
| 4 |
+
export async function addZeroPaddingTo3dTensor(tensor3d, rowPadArr = [1, 1], colPadArr = [1, 1], depthPadArr = [1, 1]) {
|
| 5 |
+
if (tensor3d.rank !== 3) {
|
| 6 |
+
throw new Error('Tensor must be 3D')
|
| 7 |
+
}
|
| 8 |
+
return tensor3d.pad([rowPadArr, colPadArr, depthPadArr])
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export async function applyMriThreshold(tensor, percentage) {
|
| 12 |
+
// Perform asynchronous operations outside of tf.tidy
|
| 13 |
+
const maxTensor = tensor.max()
|
| 14 |
+
const thresholdTensor = maxTensor.mul(percentage)
|
| 15 |
+
const threshold = await thresholdTensor.data() // Extracts the threshold value
|
| 16 |
+
|
| 17 |
+
// Dispose tensors not needed anymore
|
| 18 |
+
maxTensor.dispose()
|
| 19 |
+
thresholdTensor.dispose()
|
| 20 |
+
|
| 21 |
+
// Use tf.tidy for synchronous operations
|
| 22 |
+
return tf.tidy(() => {
|
| 23 |
+
const dataForProcessing = tensor.clone()
|
| 24 |
+
|
| 25 |
+
// Thresholding (assuming background has very low values compared to the head)
|
| 26 |
+
const mask = dataForProcessing.greater(threshold[0])
|
| 27 |
+
// -- const denoisedMriData = dataForProcessing.mul(mask)
|
| 28 |
+
|
| 29 |
+
// No need to manually dispose dataForProcessing and mask, as tf.tidy() will dispose them auto.
|
| 30 |
+
return mask
|
| 31 |
+
})
|
| 32 |
+
|
| 33 |
+
// -- return denoisedMriData
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
export async function binarizeVolumeDataTensor(volumeDataTensor) {
|
| 37 |
+
const alpha = 0
|
| 38 |
+
// element-wise: (x > 0 ? 1 : alpha * x ); e.g. Tenosr [0, 0.9, 0.8, -3] => Tensor [0, 1, 1, 0]
|
| 39 |
+
return volumeDataTensor.step(alpha)
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
async function calculateQuantiles(tensor, lowerQuantile = 0.01, upperQuantile = 0.99) {
|
| 43 |
+
// Flatten the tensor
|
| 44 |
+
const flatTensor = tensor.flatten()
|
| 45 |
+
|
| 46 |
+
// Convert the flattened tensor to an array to sort it
|
| 47 |
+
const flatArray = await flatTensor.array()
|
| 48 |
+
flatArray.sort((a, b) => a - b) // Sort the array in ascending order
|
| 49 |
+
|
| 50 |
+
// Convert the sorted array back to a tensor
|
| 51 |
+
const sortedTensor = tf.tensor1d(flatArray)
|
| 52 |
+
|
| 53 |
+
// Calculate the indices for the quantiles
|
| 54 |
+
const numElements = sortedTensor.shape[0]
|
| 55 |
+
const lowIndex = Math.floor(numElements * lowerQuantile)
|
| 56 |
+
const highIndex = Math.ceil(numElements * upperQuantile) - 1 // Subtract 1 because indices are 0-based
|
| 57 |
+
|
| 58 |
+
// Slice the sorted tensor to get qmin and qmax
|
| 59 |
+
const qmin = sortedTensor.slice(lowIndex, 1) // Get the value at the low index
|
| 60 |
+
const qmax = sortedTensor.slice(highIndex, 1) // Get the value at the high index
|
| 61 |
+
|
| 62 |
+
// Get the actual values from the tensors
|
| 63 |
+
const qminValue = (await qmin.array())[0]
|
| 64 |
+
const qmaxValue = (await qmax.array())[0]
|
| 65 |
+
|
| 66 |
+
// Clean up tensors to free memory
|
| 67 |
+
flatTensor.dispose()
|
| 68 |
+
sortedTensor.dispose()
|
| 69 |
+
qmin.dispose()
|
| 70 |
+
qmax.dispose()
|
| 71 |
+
|
| 72 |
+
return { qmin: qminValue, qmax: qmaxValue }
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
export async function convByOutputChannelAndInputSlicing(input, filter, biases, stride, pad, dilationRate, sliceSize) {
|
| 76 |
+
const inChannels = input.shape[4]
|
| 77 |
+
const outChannels = filter.shape[4]
|
| 78 |
+
|
| 79 |
+
// Create an empty array to hold the output channels
|
| 80 |
+
let outputChannels = null
|
| 81 |
+
|
| 82 |
+
// Slice the input tensor and process one output channel at a time
|
| 83 |
+
for (let channel = 0; channel < outChannels; channel++) {
|
| 84 |
+
const numSlices = Math.ceil(inChannels / sliceSize)
|
| 85 |
+
const biasesSlice = biases.slice([channel], [1])
|
| 86 |
+
let outputChannel = null
|
| 87 |
+
|
| 88 |
+
for (let i = 0; i < numSlices; i++) {
|
| 89 |
+
const startChannel = i * sliceSize
|
| 90 |
+
const endChannel = Math.min((i + 1) * sliceSize, inChannels)
|
| 91 |
+
|
| 92 |
+
// Only proceed if there are channels to process
|
| 93 |
+
if (startChannel < inChannels) {
|
| 94 |
+
const resultSlice = tf.tidy(() => {
|
| 95 |
+
const inputSlice = input.slice([0, 0, 0, 0, startChannel], [-1, -1, -1, -1, endChannel - startChannel])
|
| 96 |
+
const filterSlice = filter.slice([0, 0, 0, startChannel, channel], [-1, -1, -1, endChannel - startChannel, 1])
|
| 97 |
+
// Perform the convolution for the current slice and output channel
|
| 98 |
+
return tf.conv3d(inputSlice, filterSlice, stride, pad, 'NDHWC', dilationRate)
|
| 99 |
+
})
|
| 100 |
+
|
| 101 |
+
if (outputChannel === null) {
|
| 102 |
+
outputChannel = resultSlice
|
| 103 |
+
} else {
|
| 104 |
+
const updatedOutputChannel = outputChannel.add(resultSlice)
|
| 105 |
+
outputChannel.dispose()
|
| 106 |
+
resultSlice.dispose()
|
| 107 |
+
outputChannel = updatedOutputChannel
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Add the biases to the accumulated convolutions for this channel
|
| 113 |
+
const biasedOutputChannel = outputChannel.add(biasesSlice)
|
| 114 |
+
outputChannel.dispose()
|
| 115 |
+
biasesSlice.dispose()
|
| 116 |
+
|
| 117 |
+
// Accumulate the channel to the output array
|
| 118 |
+
if (outputChannels == null) {
|
| 119 |
+
outputChannels = biasedOutputChannel
|
| 120 |
+
} else {
|
| 121 |
+
const updatedOutputChannels = await tf.concat([outputChannels, biasedOutputChannel], 4)
|
| 122 |
+
biasedOutputChannel.dispose()
|
| 123 |
+
outputChannels.dispose()
|
| 124 |
+
outputChannels = updatedOutputChannels
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
return outputChannels
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
export async function draw3dObjBoundingVolume(unstackOutVolumeTensor, opts, modelEntry, callbackImg) {
|
| 132 |
+
const allOutputSlices3DCC = []
|
| 133 |
+
|
| 134 |
+
// dataSync() using to flatten array. Takes around 1.5 s
|
| 135 |
+
for (let sliceTensorIdx = 0; sliceTensorIdx < unstackOutVolumeTensor.length; sliceTensorIdx++) {
|
| 136 |
+
allOutputSlices3DCC[sliceTensorIdx] = Array.from(unstackOutVolumeTensor[sliceTensorIdx].dataSync())
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// Use this conversion to download output slices as nii file. Takes around 30 ms
|
| 140 |
+
// does not use `push` to avoid stack overflows. In future: consider .set() with typed arrays
|
| 141 |
+
const allOutputSlices3DCC1DimArray = new Array(allOutputSlices3DCC[0].length * allOutputSlices3DCC.length)
|
| 142 |
+
let index = 0
|
| 143 |
+
for (let sliceIdx = 0; sliceIdx < allOutputSlices3DCC.length; sliceIdx++) {
|
| 144 |
+
for (let i = 0; i < allOutputSlices3DCC[sliceIdx].length; i++) {
|
| 145 |
+
allOutputSlices3DCC1DimArray[index++] = allOutputSlices3DCC[sliceIdx][i]
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
console.log('Done with allOutputSlices3DCC1DimArray ')
|
| 149 |
+
const brainMaskTensor1d = await binarizeVolumeDataTensor(tf.tensor1d(allOutputSlices3DCC1DimArray))
|
| 150 |
+
const brainOut = Array.from(brainMaskTensor1d.dataSync())
|
| 151 |
+
callbackImg(brainOut, opts, modelEntry)
|
| 152 |
+
}
|
| 153 |
+
// return first and last non-zero voxel in row (dim = 0), column (1) or slice (2) dimension
|
| 154 |
+
async function firstLastNonZero(tensor3D, dim = 0) {
|
| 155 |
+
let mxs = []
|
| 156 |
+
if (dim === 0) {
|
| 157 |
+
mxs = await tensor3D.max(2).max(1).arraySync()
|
| 158 |
+
} else if (dim === 1) {
|
| 159 |
+
mxs = await tensor3D.max(2).max(0).arraySync()
|
| 160 |
+
} else {
|
| 161 |
+
mxs = await tensor3D.max(1).max(0).arraySync()
|
| 162 |
+
}
|
| 163 |
+
let mn = mxs.length
|
| 164 |
+
let mx = 0
|
| 165 |
+
for (let i = 0; i < mxs.length; i++) {
|
| 166 |
+
if (mxs[i] > 0) {
|
| 167 |
+
mn = i
|
| 168 |
+
break
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
for (let i = mxs.length - 1; i >= 0; i--) {
|
| 172 |
+
if (mxs[i] > 0) {
|
| 173 |
+
mx = i
|
| 174 |
+
break
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
return [mn, mx]
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
export async function firstLastNonZero3D(tensor3D) {
|
| 181 |
+
const [row_min, row_max] = await firstLastNonZero(tensor3D, 0)
|
| 182 |
+
const [col_min, col_max] = await firstLastNonZero(tensor3D, 1)
|
| 183 |
+
const [depth_min, depth_max] = await firstLastNonZero(tensor3D, 2)
|
| 184 |
+
console.log('row min and max :', row_min, row_max)
|
| 185 |
+
console.log('col min and max :', col_min, col_max)
|
| 186 |
+
console.log('depth min and max :', depth_min, depth_max)
|
| 187 |
+
return [row_min, row_max, col_min, col_max, depth_min, depth_max]
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/*
|
| 191 |
+
//simpler function, but x4 slower
|
| 192 |
+
export async function firstLastNonZero3D(tensor3D) {
|
| 193 |
+
const coords = await tf.whereAsync(tensor3D)
|
| 194 |
+
const row_min = coords.min(0).arraySync()[0]
|
| 195 |
+
const row_max = coords.max(0).arraySync()[0]
|
| 196 |
+
const col_min = coords.min(0).arraySync()[1]
|
| 197 |
+
const col_max = coords.max(0).arraySync()[1]
|
| 198 |
+
const depth_min = coords.min(0).arraySync()[2]
|
| 199 |
+
const depth_max = coords.max(0).arraySync()[2]
|
| 200 |
+
coords.dispose()
|
| 201 |
+
return [row_min, row_max, col_min, col_max, depth_min, depth_max]
|
| 202 |
+
}
|
| 203 |
+
*/
|
| 204 |
+
|
| 205 |
+
export async function generateBrainMask(
|
| 206 |
+
unstackOutVolumeTensor,
|
| 207 |
+
num_of_slices,
|
| 208 |
+
slice_height,
|
| 209 |
+
slice_width,
|
| 210 |
+
modelEntry,
|
| 211 |
+
opts,
|
| 212 |
+
callbackUI,
|
| 213 |
+
callbackImg,
|
| 214 |
+
isFinalImage = true
|
| 215 |
+
) {
|
| 216 |
+
if (unstackOutVolumeTensor[0].dtype !== 'int32') {
|
| 217 |
+
callbackUI('', -1, 'generateBrainMask assumes int32')
|
| 218 |
+
}
|
| 219 |
+
if (modelEntry.preModelPostProcess) {
|
| 220 |
+
callbackUI('', -1, 'generateBrainMask assumes BWLabeler instead of preModelPostProcess')
|
| 221 |
+
}
|
| 222 |
+
const numSlices = unstackOutVolumeTensor.length
|
| 223 |
+
const numPixels2D = unstackOutVolumeTensor[0].size
|
| 224 |
+
const numVox3D = numSlices * numPixels2D
|
| 225 |
+
// preallocate to reduce heap usage
|
| 226 |
+
const brainOut = new Int32Array(numVox3D)
|
| 227 |
+
let offset = 0
|
| 228 |
+
for (let i = 0; i < numSlices; i++) {
|
| 229 |
+
brainOut.set(unstackOutVolumeTensor[i].dataSync(), offset)
|
| 230 |
+
offset += numPixels2D
|
| 231 |
+
}
|
| 232 |
+
for (let i = 0; i < numVox3D; i++) {
|
| 233 |
+
brainOut[i] = brainOut[i] !== 0 ? 1 : 0
|
| 234 |
+
}
|
| 235 |
+
if (isFinalImage || opts.showPhase1Output) {
|
| 236 |
+
// all done
|
| 237 |
+
callbackImg(brainOut, opts, modelEntry)
|
| 238 |
+
callbackUI('Segmentation finished', 0)
|
| 239 |
+
}
|
| 240 |
+
return tf.tensor(brainOut, [num_of_slices, slice_height, slice_width])
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
export async function generateOutputSlicesV2(
|
| 244 |
+
img,
|
| 245 |
+
OutVolumeTensorShape,
|
| 246 |
+
OutVolumeTensorType,
|
| 247 |
+
num_of_slices,
|
| 248 |
+
numSegClasses,
|
| 249 |
+
slice_height,
|
| 250 |
+
slice_width,
|
| 251 |
+
modelEntry,
|
| 252 |
+
opts,
|
| 253 |
+
niftiImage
|
| 254 |
+
) {
|
| 255 |
+
// Convert all slices into 1 Dim array
|
| 256 |
+
if (opts.isPostProcessEnable) {
|
| 257 |
+
const BWInstance = new BWLabeler()
|
| 258 |
+
const dim = new Uint32Array(OutVolumeTensorShape)
|
| 259 |
+
const conn = 26 // Example connectivity
|
| 260 |
+
const binarize = true
|
| 261 |
+
const onlyLargestClusterPerClass = true
|
| 262 |
+
const [_labelCount, labeledImage] = BWInstance.bwlabel(img, dim, conn, binarize, onlyLargestClusterPerClass)
|
| 263 |
+
for (let i = 0; i < img.length; i++) {
|
| 264 |
+
img[i] *= labeledImage[i]
|
| 265 |
+
}
|
| 266 |
+
} // if isPostProcessEnable
|
| 267 |
+
const typedArrayConstructor = {
|
| 268 |
+
float32: Float32Array,
|
| 269 |
+
int32: Int32Array
|
| 270 |
+
// Add other cases as needed for different dtypes
|
| 271 |
+
}[OutVolumeTensorType]
|
| 272 |
+
// Create a new TypedArray from img with the same type as outLabelVolume
|
| 273 |
+
const allOutputSlices3DCC1DimArray = new Uint8Array(img)
|
| 274 |
+
switch (modelEntry.type) {
|
| 275 |
+
case 'Brain_Masking': {
|
| 276 |
+
const brainMask = new Uint8Array(allOutputSlices3DCC1DimArray.length)
|
| 277 |
+
for (let i = 0; i < allOutputSlices3DCC1DimArray.length; i++) {
|
| 278 |
+
brainMask[i] = allOutputSlices3DCC1DimArray[i] !== 0 ? 1 : 0
|
| 279 |
+
}
|
| 280 |
+
return brainMask
|
| 281 |
+
}
|
| 282 |
+
case 'Brain_Extraction': {
|
| 283 |
+
const maskedData = new Uint8Array(allOutputSlices3DCC1DimArray.length)
|
| 284 |
+
for (let i = 0; i < allOutputSlices3DCC1DimArray.length; i++) {
|
| 285 |
+
// Create the mask - 1 where the value is non-zero, 0 where it is zero.
|
| 286 |
+
const maskValue = allOutputSlices3DCC1DimArray[i] !== 0 ? 1 : 0
|
| 287 |
+
// Apply the mask to the data - multiply by the mask value.
|
| 288 |
+
maskedData[i] = niftiImage[i] * maskValue
|
| 289 |
+
}
|
| 290 |
+
return maskedData
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
return img
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
export async function getAllSlicesDataAsTF3D(num_of_slices, niftiHeader, niftiImage) {
|
| 297 |
+
// Get nifti dimensions
|
| 298 |
+
const cols = niftiHeader.dims[1] // Slice width
|
| 299 |
+
const rows = niftiHeader.dims[2] // Slice height
|
| 300 |
+
let typedData
|
| 301 |
+
if (niftiHeader.datatypeCode === 2) {
|
| 302 |
+
// enum from nvimage/utils DT_UINT8 = 2
|
| 303 |
+
typedData = new Uint8Array(niftiImage)
|
| 304 |
+
} else if (niftiHeader.datatypeCode === 4) {
|
| 305 |
+
// DT_INT16 = 4
|
| 306 |
+
typedData = new Int16Array(niftiImage)
|
| 307 |
+
} else if (niftiHeader.datatypeCode === 8) {
|
| 308 |
+
// DT_INT32 = 8
|
| 309 |
+
typedData = new Int32Array(niftiImage)
|
| 310 |
+
} else if (niftiHeader.datatypeCode === 16) {
|
| 311 |
+
// DT_FLOAT32 = 16
|
| 312 |
+
typedData = new Float32Array(niftiImage)
|
| 313 |
+
} else if (niftiHeader.datatypeCode === 64) {
|
| 314 |
+
// DT_FLOAT64 = 64
|
| 315 |
+
typedData = new Float64Array(niftiImage)
|
| 316 |
+
} else if (niftiHeader.datatypeCode === 256) {
|
| 317 |
+
// DT_INT8 = 256
|
| 318 |
+
typedData = new Int8Array(niftiImage)
|
| 319 |
+
} else if (niftiHeader.datatypeCode === 512) {
|
| 320 |
+
// DT_UINT16 = 512
|
| 321 |
+
typedData = new Uint16Array(niftiImage)
|
| 322 |
+
} else if (niftiHeader.datatypeCode === 768) {
|
| 323 |
+
// DT_UINT32 = 768
|
| 324 |
+
typedData = new Uint32Array(niftiImage)
|
| 325 |
+
} else {
|
| 326 |
+
return
|
| 327 |
+
}
|
| 328 |
+
const allSlices_2D = []
|
| 329 |
+
let offset3D = 0
|
| 330 |
+
// Draw pixels
|
| 331 |
+
for (let slice = 0; slice < num_of_slices; slice++) {
|
| 332 |
+
const slice = new Array(rows * cols)
|
| 333 |
+
let offset2D = 0
|
| 334 |
+
for (let row = 0; row < rows; row++) {
|
| 335 |
+
for (let col = 0; col < cols; col++) {
|
| 336 |
+
const value = typedData[offset3D++]
|
| 337 |
+
// Create 1Dim Array of pixel value, this 1 dim represents one channel
|
| 338 |
+
slice[offset2D++] = value & 0xff
|
| 339 |
+
}
|
| 340 |
+
}
|
| 341 |
+
allSlices_2D.push(tf.tensor(slice, [rows, cols])) // slice_height, slice_width
|
| 342 |
+
}
|
| 343 |
+
const allSlices_3D = tf.stack(allSlices_2D)
|
| 344 |
+
tf.dispose(allSlices_2D)
|
| 345 |
+
return allSlices_3D
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
export async function getModelNumLayers(modelObj) {
|
| 349 |
+
return modelObj.layers.length
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
export async function getModelNumParameters(modelObj) {
|
| 353 |
+
let numParameters = 0
|
| 354 |
+
for (let layerIdx = 0; layerIdx < modelObj.layers.length; layerIdx++) {
|
| 355 |
+
numParameters += modelObj.layers[layerIdx].countParams()
|
| 356 |
+
}
|
| 357 |
+
return numParameters
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
export async function isModelChnlLast(modelObj) {
|
| 361 |
+
for (let layerIdx = 0; layerIdx < modelObj.layers.length; layerIdx++) {
|
| 362 |
+
if (modelObj.layersByDepth[layerIdx][0].dataFormat) {
|
| 363 |
+
return modelObj.layersByDepth[layerIdx][0].dataFormat === 'channelsLast'
|
| 364 |
+
}
|
| 365 |
+
}
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
export async function load_model(modelUrl) {
|
| 369 |
+
return await tf.loadLayersModel(modelUrl)
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
export async function minMaxNormalizeVolumeData(volumeData) {
|
| 373 |
+
// Normalize the data to the range 0 - 1 using min-max scaling
|
| 374 |
+
const volumeData_Max = volumeData.max()
|
| 375 |
+
const volumeData_Min = volumeData.min()
|
| 376 |
+
const normalizedSlices_3d = await volumeData.sub(volumeData_Min).div(volumeData_Max.sub(volumeData_Min))
|
| 377 |
+
return normalizedSlices_3d
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
function processTensorInChunks(inputTensor, filterWeights, chunkSize) {
|
| 381 |
+
// Assuming inputTensor's shape: [batch, depth, height, width, inChannels]
|
| 382 |
+
// and filterWeights's shape: [filterDepth, filterHeight, filterWidth, inChannels, outChannels]
|
| 383 |
+
const stride = 1
|
| 384 |
+
const pad = 0
|
| 385 |
+
const dilationRate = 1
|
| 386 |
+
const inChannels = inputTensor.shape[4]
|
| 387 |
+
const numSlices = Math.ceil(inChannels / chunkSize)
|
| 388 |
+
|
| 389 |
+
let accumulatedResult = null
|
| 390 |
+
for (let i = 0; i < numSlices; i++) {
|
| 391 |
+
const startChannel = i * chunkSize
|
| 392 |
+
const endChannel = Math.min((i + 1) * chunkSize, inChannels)
|
| 393 |
+
const channels = endChannel - startChannel
|
| 394 |
+
|
| 395 |
+
const inputSlice = tf.tidy(() => {
|
| 396 |
+
// Slice the input tensor to get the current chunk
|
| 397 |
+
return inputTensor.slice([0, 0, 0, 0, startChannel], [-1, -1, -1, -1, channels])
|
| 398 |
+
})
|
| 399 |
+
const filterSlice = tf.tidy(() => {
|
| 400 |
+
// Slice the filter weights to match the input tensor's current chunk
|
| 401 |
+
return filterWeights.slice([0, 0, 0, startChannel, 0], [-1, -1, -1, channels, -1])
|
| 402 |
+
})
|
| 403 |
+
|
| 404 |
+
const resultSlice = tf.conv3d(inputSlice, filterSlice, stride, pad, 'NDHWC', dilationRate)
|
| 405 |
+
// Clean up the slices to free memory
|
| 406 |
+
inputSlice.dispose()
|
| 407 |
+
filterSlice.dispose()
|
| 408 |
+
|
| 409 |
+
// Squeeze the result slice to remove dimensions of size 1
|
| 410 |
+
const squeezedResultSlice = tf.squeeze(resultSlice)
|
| 411 |
+
resultSlice.dispose() // Dispose of the original resultSlice after squeezing
|
| 412 |
+
|
| 413 |
+
if (accumulatedResult === null) {
|
| 414 |
+
accumulatedResult = squeezedResultSlice
|
| 415 |
+
} else {
|
| 416 |
+
// Accumulate the result by adding the new result slice to it
|
| 417 |
+
const newAccumulatedResult = accumulatedResult.add(squeezedResultSlice)
|
| 418 |
+
|
| 419 |
+
// Dispose of the previous accumulatedResult and squeezedResultSlice
|
| 420 |
+
accumulatedResult.dispose()
|
| 421 |
+
// Dispose of squeezedResultSlice only if it wasn't assigned to accumulatedResult
|
| 422 |
+
if (accumulatedResult !== squeezedResultSlice) {
|
| 423 |
+
squeezedResultSlice.dispose()
|
| 424 |
+
}
|
| 425 |
+
// Update accumulatedResult with the new result
|
| 426 |
+
accumulatedResult = newAccumulatedResult
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
tf.tidy(() => {
|
| 430 |
+
tf.matMul(tf.zeros([1, 1]), tf.zeros([1, 1]))
|
| 431 |
+
})
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
return accumulatedResult
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
export async function quantileNormalizeVolumeData(tensor, lowerQuantile = 0.05, upperQuantile = 0.95) {
|
| 438 |
+
// Call calculateQuantiles and wait for the result
|
| 439 |
+
const { qmin, qmax } = await calculateQuantiles(tensor, lowerQuantile, upperQuantile)
|
| 440 |
+
|
| 441 |
+
// Convert qmin and qmax back to scalars
|
| 442 |
+
const qminScalar = tf.scalar(qmin)
|
| 443 |
+
const qmaxScalar = tf.scalar(qmax)
|
| 444 |
+
|
| 445 |
+
// Perform the operation: (tensor - qmin) / (qmax - qmin)
|
| 446 |
+
const resultTensor = tensor.sub(qminScalar).div(qmaxScalar.sub(qminScalar))
|
| 447 |
+
|
| 448 |
+
// Dispose of the created scalars to free memory
|
| 449 |
+
qminScalar.dispose()
|
| 450 |
+
qmaxScalar.dispose()
|
| 451 |
+
|
| 452 |
+
// Return the resulting tensor
|
| 453 |
+
return resultTensor
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
export async function removeZeroPaddingFrom3dTensor(tensor3d, rowPad = 1, colPad = 1, depthPad = 1) {
|
| 457 |
+
if (tensor3d.rank !== 3) {
|
| 458 |
+
throw new Error('Tensor must be 3D')
|
| 459 |
+
}
|
| 460 |
+
const [h, w, d] = tensor3d.shape
|
| 461 |
+
return tensor3d.slice([rowPad, colPad, depthPad], [h - 2 * rowPad, w - 2 * colPad, d - 2 * depthPad])
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
export async function resizeWithZeroPadding(croppedTensor3d, newDepth, newHeight, newWidth, refVoxel, boundVolSizeArr) {
|
| 465 |
+
const row_pad_befor = refVoxel[0]
|
| 466 |
+
const col_pad_befor = refVoxel[1]
|
| 467 |
+
const depth_pad_befor = refVoxel[2]
|
| 468 |
+
// last and lower volume voxel
|
| 469 |
+
const row_max = row_pad_befor + boundVolSizeArr[0] - 1 // size [2, 2, 2] means 2 voxels total in each dim
|
| 470 |
+
const col_max = col_pad_befor + boundVolSizeArr[1] - 1
|
| 471 |
+
const depth_max = depth_pad_befor + boundVolSizeArr[2] - 1
|
| 472 |
+
|
| 473 |
+
const row_pad_after = newHeight - row_max - 1 > 0 ? newHeight - row_max - 1 : 0
|
| 474 |
+
const col_pad_after = newWidth - col_max - 1 > 0 ? newWidth - col_max - 1 : 0
|
| 475 |
+
const depth_pad_after = newDepth - depth_max - 1 > 0 ? newDepth - depth_max - 1 : 0
|
| 476 |
+
|
| 477 |
+
return croppedTensor3d.pad([
|
| 478 |
+
[row_pad_befor, row_pad_after],
|
| 479 |
+
[col_pad_befor, col_pad_after],
|
| 480 |
+
[depth_pad_befor, depth_pad_after]
|
| 481 |
+
])
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
export class SequentialConvLayer {
|
| 485 |
+
constructor(model, chunkSize, isChannelLast, callbackUI, isWebWorker = true) {
|
| 486 |
+
this.model = model
|
| 487 |
+
this.outChannels = model.outputLayers[0].kernel.shape[4]
|
| 488 |
+
this.chunkSize = chunkSize
|
| 489 |
+
this.isChannelLast = isChannelLast
|
| 490 |
+
this.callbackUI = callbackUI
|
| 491 |
+
this.isWebWorker = isWebWorker
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
/**
|
| 495 |
+
* Apply sequential convolution layer
|
| 496 |
+
* @since 3.0.0
|
| 497 |
+
* @member SequentialConvLayer
|
| 498 |
+
* @param {tf.Tensor} inputTensor e.g. [ 1, 256, 256, 256, 5 ]
|
| 499 |
+
* @return {outC}
|
| 500 |
+
*/
|
| 501 |
+
|
| 502 |
+
async apply(inputTensor) {
|
| 503 |
+
const oldDeleteTextureThreshold = tf.ENV.get('WEBGL_DELETE_TEXTURE_THRESHOLD')
|
| 504 |
+
tf.ENV.set('WEBGL_DELETE_TEXTURE_THRESHOLD', 0)
|
| 505 |
+
|
| 506 |
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
| 507 |
+
const self = this
|
| 508 |
+
// Important to avoid "undefined" class var members inside the timer.
|
| 509 |
+
// "this" has another meaning inside the timer.
|
| 510 |
+
|
| 511 |
+
// document.getElementById("progressBarChild").parentElement.style.visibility = "visible"
|
| 512 |
+
const startTime = performance.now()
|
| 513 |
+
|
| 514 |
+
const convLayer = self.model.layers[self.model.layers.length - 1]
|
| 515 |
+
const weights = convLayer.getWeights()[0] //
|
| 516 |
+
const biases = convLayer.getWeights()[1]
|
| 517 |
+
const outputShape = self.isChannelLast ? inputTensor.shape.slice(1, -1) : inputTensor.shape.slice(2)
|
| 518 |
+
// -- e.g. outputShape : [256,256,256] or cropped Dim
|
| 519 |
+
// -- if inputTensor [ 1, D, H, W, 50 ], channelLast true -> outputShape : outputShape [D, H, W]
|
| 520 |
+
// -- if inputTensor [ 1, 50, D, H, W ], channelLast false -> outputShape : outputShape [D, H, W]
|
| 521 |
+
|
| 522 |
+
let outB = tf.mul(tf.ones(outputShape), -10000)
|
| 523 |
+
// -- e.g. outB.shape [256,256,256]
|
| 524 |
+
let outC = tf.zeros(outputShape)
|
| 525 |
+
// -- e.g. outC.shape [256,256,256]
|
| 526 |
+
let chIdx = 0
|
| 527 |
+
|
| 528 |
+
// console.log("---------------------------------------------------------")
|
| 529 |
+
console.log(' channel loop')
|
| 530 |
+
|
| 531 |
+
while (true) {
|
| 532 |
+
tf.engine().startScope() // Start TensorFlow.js scope
|
| 533 |
+
/* console.log('=======================')
|
| 534 |
+
const memoryInfo0 = await tf.memory()
|
| 535 |
+
console.log(`| Number of Tensors: ${memoryInfo0.numTensors}`)
|
| 536 |
+
console.log(`| Number of Data Buffers: ${memoryInfo0.numDataBuffers}`) */
|
| 537 |
+
|
| 538 |
+
const result = await tf.tidy(() => {
|
| 539 |
+
const filterWeights = weights.slice([0, 0, 0, 0, chIdx], [-1, -1, -1, -1, 1])
|
| 540 |
+
// -- e.g. filterWeights.shape [ 1, 1, 1, 5, 1 ]
|
| 541 |
+
const filterBiases = biases.slice([chIdx], [1])
|
| 542 |
+
// -- e.g. filterBiases.shape [1] -> Tensor [-0.7850812]
|
| 543 |
+
const outA = processTensorInChunks(inputTensor, filterWeights, Math.min(self.chunkSize, self.outChannels)).add(
|
| 544 |
+
filterBiases
|
| 545 |
+
)
|
| 546 |
+
const greater = tf.greater(outA, outB)
|
| 547 |
+
const newoutB = tf.where(greater, outA, outB)
|
| 548 |
+
const newoutC = tf.where(greater, tf.fill(outC.shape, chIdx), outC)
|
| 549 |
+
// Dispose the old tensors before reassigning
|
| 550 |
+
tf.dispose([outB, outC, filterWeights, filterBiases, outA, greater])
|
| 551 |
+
// Dummy operation to trigger cleanup
|
| 552 |
+
tf.tidy(() => tf.matMul(tf.ones([1, 1]), tf.ones([1, 1])))
|
| 553 |
+
return [newoutC, newoutB]
|
| 554 |
+
})
|
| 555 |
+
console.log('=======================')
|
| 556 |
+
self.callbackUI(`Iteration ${chIdx}`, chIdx / self.outChannels)
|
| 557 |
+
if (!self.isWebWorker) {
|
| 558 |
+
// allow user interface to refresh
|
| 559 |
+
await new Promise((resolve) => setTimeout(resolve, 17))
|
| 560 |
+
}
|
| 561 |
+
const memoryInfo = await tf.memory()
|
| 562 |
+
console.log(`Number of Tensors: ${memoryInfo.numTensors}`)
|
| 563 |
+
console.log(`Number of Data Buffers: ${memoryInfo.numDataBuffers}`)
|
| 564 |
+
console.log(`Megabytes In Use: ${(memoryInfo.numBytes / 1048576).toFixed(3)} MB`)
|
| 565 |
+
if (memoryInfo.unreliable) {
|
| 566 |
+
console.log(`Unreliable: ${memoryInfo.unreliable}`)
|
| 567 |
+
}
|
| 568 |
+
// Dispose of previous values before assigning new tensors to outC and outB
|
| 569 |
+
if (typeof outC !== 'undefined') {
|
| 570 |
+
outC.dispose()
|
| 571 |
+
}
|
| 572 |
+
if (typeof outB !== 'undefined') {
|
| 573 |
+
outB.dispose()
|
| 574 |
+
}
|
| 575 |
+
// Assign the new values to outC and outB
|
| 576 |
+
outC = tf.keep(result[0])
|
| 577 |
+
outB = tf.keep(result[1])
|
| 578 |
+
// // Assign the new values to outC and outB
|
| 579 |
+
// outC = result[0]
|
| 580 |
+
// outB = result[1]
|
| 581 |
+
tf.engine().endScope()
|
| 582 |
+
|
| 583 |
+
if (chIdx === self.outChannels - 1) {
|
| 584 |
+
// document.getElementById("progressBarChild").style.width = 0 + "%"
|
| 585 |
+
tf.dispose(outB)
|
| 586 |
+
const endTime = performance.now()
|
| 587 |
+
const executionTime = endTime - startTime
|
| 588 |
+
console.log(`Execution time for output layer: ${executionTime} milliseconds`)
|
| 589 |
+
tf.ENV.set('WEBGL_DELETE_TEXTURE_THRESHOLD', oldDeleteTextureThreshold)
|
| 590 |
+
return outC
|
| 591 |
+
} else {
|
| 592 |
+
chIdx++
|
| 593 |
+
|
| 594 |
+
// the seemingly strange sequence of operations
|
| 595 |
+
// below prevents tfjs from uncontrolably
|
| 596 |
+
// grabbing buffers, even when all tensors have
|
| 597 |
+
// already been disposed
|
| 598 |
+
|
| 599 |
+
const outCShape = outC.shape
|
| 600 |
+
const outCdata = outC.dataSync()
|
| 601 |
+
const outBShape = outC.shape
|
| 602 |
+
const outBdata = outB.dataSync()
|
| 603 |
+
outC.dispose()
|
| 604 |
+
outB.dispose()
|
| 605 |
+
// tf.disposeVariables()
|
| 606 |
+
outC = tf.tensor(outCdata, outCShape)
|
| 607 |
+
outB = tf.tensor(outBdata, outBShape)
|
| 608 |
+
|
| 609 |
+
// document.getElementById("progressBarChild").style.width = (chIdx + 1) * 100 / self.outChannels + "%"
|
| 610 |
+
}
|
| 611 |
+
}
|
| 612 |
+
}
|
| 613 |
+
} // <<<< End of class
|
vite.config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
|
| 3 |
+
export default defineConfig({
|
| 4 |
+
root: '.',
|
| 5 |
+
base: '/niivue-tinygrad/',
|
| 6 |
+
server: {
|
| 7 |
+
open: 'index.html',
|
| 8 |
+
},
|
| 9 |
+
})
|