├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ ├── release.yml
│ └── sphinx.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── README.md
├── biome.json
├── build.js
├── build_assets
├── $itksnap.json
└── $slicer3d.json
├── docs
├── Makefile
├── make.bat
└── source
│ ├── api.rst
│ ├── architecture.rst
│ ├── conf.py
│ ├── contributing.rst
│ ├── enums.rst
│ ├── index.rst
│ ├── install.rst
│ ├── options.rst
│ └── traits.rst
├── environment.yml
├── examples
├── add_from_frontend.ipynb
├── additive_voxels.ipynb
├── afni.ipynb
├── alphathreshold.ipynb
├── atlas.ipynb
├── atlas.sparse.ipynb
├── basic_multiplanar.ipynb
├── colormaps.ipynb
├── colormaps.mesh.ipynb
├── complex.ipynb
├── denoise.ipynb
├── document.load.ipynb
├── draw.ui.ipynb
├── draw2.ipynb
├── example_4d.ipynb
├── example_sideview.ipynb
├── mask.ipynb
├── mesh.4d.ipynb
├── mesh.matcap.ipynb
├── mesh_and_volume.ipynb
├── mesh_layers.ipynb
├── modulatescalar.ipynb
├── mosaic.ipynb
├── mosaics1.ipynb
├── mosaics2.ipynb
├── on_event.ipynb
├── prototypes
│ ├── meshes_(GIfTI, FreeSurfer, MZ3, OBJ, STL, legacy VTK).ipynb
│ ├── torso_regions.ipynb
│ └── trajectory.ipynb
├── saving.ipynb
├── segment.ipynb
├── sync.bidirectional.ipynb
├── test_images.ipynb
├── tracts.ipynb
├── vox.tradeoffs.ipynb
├── widgets.ipynb
├── worldspace.ipynb
└── worldspace2.ipynb
├── js
├── lib.ts
├── mesh.ts
├── types.ts
├── types
│ ├── niivue-fix.d.ts
│ └── niivue-min.d.ts
├── volume.ts
└── widget.ts
├── original_gallery.md
├── package.json
├── pyproject.toml
├── scripts
└── generate_config_options.py
├── src
└── ipyniivue
│ ├── __init__.py
│ ├── config_options.py
│ ├── constants.py
│ ├── download_dataset.py
│ ├── serializers.py
│ ├── traits.py
│ ├── utils.py
│ └── widget.py
├── tests
└── test_ipyniivue.py
└── tsconfig.json
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "monthly"
8 |
9 | # Maintain dependencies for npm
10 | - package-ecosystem: "npm"
11 | directory: "/"
12 | schedule:
13 | interval: "monthly"
14 |
15 | # Maintain dependencies for Composer
16 | - package-ecosystem: "pip"
17 | directory: "/"
18 | schedule:
19 | interval: "monthly"
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | Lint:
13 | runs-on: macos-14
14 | steps:
15 | - uses: actions/checkout@v5
16 | - uses: actions/setup-python@v6
17 | with:
18 | python-version: "3.12"
19 | # Run Ruff
20 | - name: Run Ruff
21 | run: |
22 | pip install uv
23 | uv pip install --system ruff
24 | ruff check
25 | # Run Codespell
26 | - name: Run Codespell
27 | run: |
28 | pip install codespell
29 | codespell codespell src/**/*.py
30 |
31 | Test:
32 | runs-on: ubuntu-latest
33 | strategy:
34 | matrix:
35 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
36 | os:
37 | - ubuntu-latest
38 | - macos-latest # arm64 (Apple Silicon)
39 | - macos-13 # latest Intel release
40 | - windows-latest
41 |
42 | steps:
43 | - uses: actions/checkout@v5
44 | # requires npm because JS needs to be built
45 | - uses: actions/setup-node@v5
46 | with:
47 | node-version: "18.x"
48 | - uses: actions/setup-python@v6
49 | with:
50 | python-version: ${{ matrix.python-version }}
51 | - run: |
52 | pip install pytest-notebook
53 | pip install pre-commit
54 | pre-commit run --all
55 | pre-commit install
56 | # pipx install hatch
57 | # hatch run test --cover --nb-test-files
58 | - name: Install IPyNiiVue & Deps
59 | run: pip install -e .
60 | - name: Test import
61 | run: |
62 | python -c "import ipyniivue"
63 | - name: Test IPyNiiVue
64 | run: |
65 | pip install pytest-cov
66 | pip install --upgrade pip ipython ipykernel
67 | ipython kernel install --name "python3" --user
68 | mkdir -p ./reports
69 | pytest --cov --cov-report=json:./reports/coverage.json --cov-report=xml:./reports/coverage.xml
70 | - name: Upload ipyniivue test coverage report
71 | uses: actions/upload-artifact@v4
72 | with:
73 | name: coverage_ipyniivue-${{ matrix.os }}-py${{ matrix.python-version }}
74 | path: ./reports/coverage.json
75 | - name: Generate Markdown summary of code coverage
76 | uses: irongut/CodeCoverageSummary@v1.3.0
77 | with:
78 | filename: ./reports/coverage.xml
79 | format: markdown
80 | output: both
81 | thresholds: "10 10"
82 | - name: Add test results to job summary
83 | run: |
84 | cat ./code-coverage-results.md >> $GITHUB_STEP_SUMMARY
85 | - name: Test example notebooks
86 | run: |
87 | pip install nbmake
88 | rm -rf ./reports
89 | mkdir -p ./reports
90 | pytest --nbmake examples/*ipynb --cov=src --cov-report=json:./reports/coverage.json --cov-report=xml:./reports/coverage.xml
91 | - name: Upload ipyniivue example notbooks coverage report
92 | uses: actions/upload-artifact@v4
93 | with:
94 | name: coverage_ipyniivue_notebooks-${{ matrix.os }}-py${{ matrix.python-version }}
95 | path: ./reports/coverage.json
96 | - name: Generate Markdown summary of code coverage
97 | uses: irongut/CodeCoverageSummary@v1.3.0
98 | with:
99 | filename: ./reports/coverage.xml
100 | format: markdown
101 | output: both
102 | thresholds: "10 10"
103 | - name: Add test results to job summary
104 | run: |
105 | cat ./code-coverage-results.md >> $GITHUB_STEP_SUMMARY
106 | # coverage run -m pytest --nb-test-files
107 | # nbt-test-files check for changes in the output of the notebook.
108 | # This does not work for our purpose since we wipe the output
109 | # before committing changes to facilitate integration with git.
110 | # - name: Upload coverage to codecov (Only do this for the ubuntu-latest job)
111 | # if: matrix.os == 'ubuntu-latest'
112 | # uses: codecov/codecov-action@v5
113 | # with:
114 | # token: ${{secrets.CODECOV_TOKEN}}
115 | #
116 | LintJavaScript:
117 | name: JavaScript / Lint
118 | runs-on: macos-14
119 | steps:
120 | - uses: actions/checkout@v5
121 | - uses: biomejs/setup-biome@v2
122 | with:
123 | version: 1.9.4
124 | - run: biome ci .
125 |
126 | TypecheckJavaScript:
127 | name: JavaScript / Typecheck
128 | runs-on: macos-14
129 | steps:
130 | - uses: actions/checkout@v5
131 | - uses: actions/setup-node@v5
132 | with:
133 | node-version: "22.x"
134 | - run: |
135 | npm install
136 | npm run typecheck
137 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 |
10 | Release:
11 | runs-on: ubuntu-latest
12 | environment:
13 | name: pypi
14 | url: https://pypi.org/p/ipyniivue
15 | permissions:
16 | # IMPORTANT: this permission is mandatory for trusted publishing
17 | id-token: write
18 | # For generating GitHub Releases + Release notes
19 | # ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
20 | contents: write
21 | steps:
22 | - uses: actions/checkout@v5
23 | with:
24 | fetch-depth: 0
25 |
26 | # requires Node to build JS
27 | - uses: actions/setup-node@v5
28 | with:
29 | node-version: "18.x"
30 |
31 | - uses: actions/setup-python@v6
32 | with:
33 | python-version: "3.x"
34 |
35 | - run: |
36 | pipx install hatch
37 | hatch build
38 |
39 | - name: Publish distribution 📦 to PyPI
40 | uses: pypa/gh-action-pypi-publish@release/v1
41 |
42 | - run: npx changelogithub@0.12
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
--------------------------------------------------------------------------------
/.github/workflows/sphinx.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Check out repository
13 | uses: actions/checkout@v5
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: Set up Python
18 | uses: actions/setup-python@v6
19 | with:
20 | python-version: '3.12'
21 |
22 | - name: Install Hatch
23 | run: |
24 | python -m pip install --upgrade pip
25 | pip install hatch
26 |
27 | - name: Set up Hatch environment and install dependencies
28 | run: |
29 | hatch env create
30 |
31 | - name: Build documentation with Hatch
32 | run: |
33 | hatch run docs
34 |
35 | - name: Deploy to GitHub Pages
36 | uses: peaceiris/actions-gh-pages@v4
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: docs/build/html
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .venv
3 | dist
4 | images
5 | docs/build
6 | package-lock.json
7 |
8 | # Python
9 | __pycache__
10 | .ipynb_checkpoints
11 |
12 | src/ipyniivue/static
13 |
14 | launch.json
15 |
16 | # OS Specific
17 | .DS_Store
18 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/srstevenson/nb-clean
3 | rev: 4.0.1
4 | hooks:
5 | - id: nb-clean
6 | args:
7 | - --remove-empty-cells
8 | - --
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2025, Niivue
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ipyniivue
2 |
3 | [](https://badge.fury.io/py/ipyniivue)
4 | [](https://opensource.org/license/bsd-2-clause)
5 | [](https://mybinder.org/v2/gh/niivue/ipyniivue/main?urlpath=lab%2Ftree%2Fexamples)
6 |
7 | **A Jupyter Widget for [Niivue](https://github.com/niivue/niivue) based on [anywidget](https://github.com/manzt/anywidget).**
8 |
9 | ---
10 |
11 |
12 | ## Installation
13 |
14 | Install ipyniivue using `pip`:
15 |
16 | ```sh
17 | pip install ipyniivue
18 | ```
19 |
20 | ---
21 |
22 | ## Usage
23 |
24 | In a Jupyter environment:
25 |
26 | ```python
27 | from ipyniivue import NiiVue
28 |
29 | nv = NiiVue()
30 | nv.load_volumes([{"path": "images/mni152.nii.gz"}])
31 | nv
32 | ```
33 |
34 | This will render an interactive Niivue widget within your notebook.
35 |
36 | **See the [basic demo](./examples/basic_multiplanar.ipynb) to learn more.**
37 |
38 | ---
39 |
40 | ## Documentation
41 |
42 | See the [Documentation](https://niivue.github.io/ipyniivue) for usage.
43 |
44 |
45 | ---
46 |
47 | ## Development
48 |
49 | ipyniivue uses the recommended [`hatchling`](https://packaging.python.org/en/latest/flow/#using-hatch) build system, which is convenient to use via the [`hatch` CLI](https://hatch.pypa.io/latest/). We recommend installing `hatch` globally (e.g., via `pipx`) and running the various commands defined within `pyproject.toml`. `hatch` will take care of creating and synchronizing a virtual environment with all dependencies defined in `pyproject.toml`.
50 |
51 | ### Command Cheat Sheet
52 |
53 | Run these commands from the root of the project:
54 |
55 | | Command | Description |
56 | |------------------------|----------------------------------------------------------------------|
57 | | `hatch run format` | Format the project with `ruff format .` and apply linting with `ruff --fix .` |
58 | | `hatch run lint` | Lint the project with `ruff check .` |
59 | | `hatch run test` | Run unit tests with `pytest` |
60 | | `hatch run docs` | Build docs with `Sphinx` |
61 |
62 | Alternatively, you can manually create a virtual environment and manage installation and dependencies with `pip`:
63 |
64 | ```sh
65 | python3 -m venv .venv && source .venv/bin/activate
66 | pip install -e ".[dev]"
67 | ```
68 |
69 | ### Making Changes to the JavaScript Code
70 |
71 | This is an [anywidget](https://github.com/manzt/anywidget) project, meaning the codebase is a hybrid of Python and JavaScript. The JavaScript code resides under the `js/` directory and uses [esbuild](https://esbuild.github.io/) for bundling. Whenever you make changes to the JavaScript code, you need to rebuild the files under `src/ipyniivue/static`.
72 |
73 | You have two options:
74 |
75 | 1. **Build Once**: Build the JavaScript code one time:
76 |
77 | ```sh
78 | npm run build
79 | ```
80 |
81 | 2. **Start Development Server**: Start a development server that automatically rebuilds the code as you make changes:
82 |
83 | ```sh
84 | npm run dev
85 | ```
86 |
87 | We recommend this approach for a smoother development experience.
88 |
89 | **Working with Jupyter**
90 |
91 | Once the development server is running, you can start JupyterLab or Visual Studio Code to develop the widget. When you're finished, stop the development server with `Ctrl+C`.
92 |
93 | > **Note:** To have `anywidget` automatically apply changes as you work, set the environment variable `ANYWIDGET_HMR=1`. You can set this directly in a notebook cell:
94 | >
95 | > ```python
96 | > %env ANYWIDGET_HMR=1
97 | > ```
98 | > or in the shell:
99 | > ```sh
100 | > export ANYWIDGET_HMR=1
101 | > ```
102 |
103 | ---
104 |
105 | ## Release Process
106 |
107 | Releases are automated using GitHub Actions via the [`release.yml`](.github/workflows/release.yml) workflow.
108 |
109 | ### Steps to Create a New Release
110 |
111 | 1. **Commit Changes**: Ensure all your changes are committed.
112 |
113 | 2. **Create a Tag**: Create a new tag matching the pattern `v*`:
114 |
115 | ```sh
116 | git tag -a vX.X.X -m "vX.X.X"
117 | git push --follow-tags
118 | ```
119 |
120 | 3. **Workflow Actions**: When triggered, the workflow will:
121 |
122 | - Publish the package to PyPI with the tag version.
123 | - Generate a changelog based on conventional commits.
124 | - Create a GitHub Release with the changelog.
125 |
126 | ### Changelog Generation
127 |
128 | - We generate a changelog for GitHub releases with [`antfu/changelogithub`](https://github.com/antfu/changelogithub).
129 | - Each changelog entry is grouped and rendered based on conventional commits.
130 | - It's recommended to follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification.
131 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "organizeImports": {
4 | "enabled": true
5 | },
6 | "linter": {
7 | "enabled": true,
8 | "rules": {
9 | "recommended": true
10 | }
11 | },
12 | "vcs": {
13 | "enabled": true,
14 | "clientKind": "git",
15 | "useIgnoreFile": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | import path from "node:path";
3 | import { fileURLToPath } from "node:url";
4 | import * as niivue from "@niivue/niivue";
5 | import esbuild from "esbuild";
6 |
7 | global.window = {};
8 |
9 | const __filename = fileURLToPath(import.meta.url);
10 | const __dirname = path.dirname(__filename);
11 |
12 | const generateColormapsPlugin = {
13 | name: "generate-colormaps",
14 | setup(build) {
15 | build.onEnd((result) => {
16 | if (result?.errors?.length === 0) {
17 | const nv = new niivue.Niivue();
18 | const colormapNames = nv.colormaps();
19 |
20 | const outputDir = path.join(
21 | __dirname,
22 | "src",
23 | "ipyniivue",
24 | "static",
25 | "colormaps",
26 | );
27 |
28 | if (fs.existsSync(outputDir)) {
29 | fs.rmSync(outputDir, { recursive: true, force: true });
30 | }
31 |
32 | fs.mkdirSync(outputDir, { recursive: true });
33 |
34 | for (const name of colormapNames) {
35 | const data = nv.colormapFromKey(name);
36 | const jsonContent = JSON.stringify(data);
37 | const filePath = path.join(outputDir, `${name}.json`);
38 |
39 | fs.writeFileSync(filePath, jsonContent, "utf8");
40 | }
41 |
42 | // colormaps for drawing
43 | fs.copyFileSync(
44 | path.join(__dirname, "build_assets", "$itksnap.json"),
45 | path.join(outputDir, "$itksnap.json"),
46 | );
47 | fs.copyFileSync(
48 | path.join(__dirname, "build_assets", "$slicer3d.json"),
49 | path.join(outputDir, "$slicer3d.json"),
50 | );
51 |
52 | console.log("Successfully generated colormaps.");
53 | } else {
54 | console.error("Build failed, colormaps not generated.");
55 | }
56 | });
57 | },
58 | };
59 |
60 | const generateShaderNamesPlugin = {
61 | name: "generate-shader-names",
62 | setup(build) {
63 | build.onEnd((result) => {
64 | if (result?.errors?.length === 0) {
65 | const nv = new niivue.Niivue();
66 | const shaderNames = nv.meshShaders.map((shader) => shader.Name);
67 | const shaderNamesTxtContent = shaderNames.join("\n");
68 |
69 | const outputPath = path.join(
70 | __dirname,
71 | "src",
72 | "ipyniivue",
73 | "static",
74 | "meshShaderNames.txt",
75 | );
76 |
77 | fs.writeFileSync(outputPath, shaderNamesTxtContent, "utf8");
78 | console.log("Successfully generated meshShaderNames.txt.");
79 | } else {
80 | console.error("Build failed, meshShaderNames.txt not generated.");
81 | }
82 | });
83 | },
84 | };
85 |
86 | async function build(isWatchMode) {
87 | const buildOptions = {
88 | entryPoints: ["js/widget.ts"],
89 | bundle: true,
90 | outfile: "src/ipyniivue/static/widget.js",
91 | plugins: [generateColormapsPlugin, generateShaderNamesPlugin],
92 | format: "esm",
93 | minify: true,
94 | };
95 |
96 | if (isWatchMode) {
97 | const ctx = await esbuild.context(buildOptions);
98 | await ctx.watch();
99 | console.log("Watching for changes...");
100 | } else {
101 | await esbuild.build(buildOptions).catch(() => process.exit(1));
102 | }
103 | }
104 |
105 | const isWatchMode = process.argv.includes("--watch");
106 |
107 | build(isWatchMode).catch((e) => {
108 | console.error(e);
109 | process.exit(1);
110 | });
111 |
--------------------------------------------------------------------------------
/build_assets/$itksnap.json:
--------------------------------------------------------------------------------
1 | {
2 | "R": [
3 | 0, 255, 0, 0, 255, 0, 255, 255, 0, 205, 210, 102, 0, 0, 46, 255, 106, 221,
4 | 233, 165, 255, 147, 218, 75, 255, 60, 255, 255, 218, 0, 188, 255, 255, 222,
5 | 127, 139, 124, 255, 70, 0, 238, 238, 240, 245, 184, 32, 255, 25, 112, 34,
6 | 248, 245, 255, 144, 173, 65, 255, 250, 128, 50, 244, 255, 123, 255, 173,
7 | 255, 127, 255, 143, 220, 253, 255, 0, 0, 128, 255, 250, 148, 178, 255, 135,
8 | 100, 240, 250, 255, 107, 135, 0, 139, 245, 186, 255, 255, 0, 210, 255, 47,
9 | 72, 175, 128, 176, 255, 139, 240, 255, 216, 119, 219, 72, 255, 199, 154,
10 | 189, 240, 230, 0, 85, 64, 153, 205, 250, 95, 0, 255, 224, 176, 138, 30, 240,
11 | 152, 160
12 | ],
13 | "G": [
14 | 0, 0, 255, 0, 255, 255, 0, 239, 0, 133, 180, 205, 0, 139, 139, 228, 90, 160,
15 | 150, 42, 250, 112, 112, 0, 182, 179, 235, 228, 165, 128, 143, 105, 218, 184,
16 | 255, 69, 252, 255, 130, 100, 130, 232, 255, 222, 134, 178, 20, 25, 128, 139,
17 | 248, 255, 160, 238, 255, 105, 99, 240, 0, 205, 164, 255, 104, 165, 216, 192,
18 | 255, 140, 188, 20, 245, 250, 206, 255, 0, 250, 128, 0, 34, 127, 206, 149,
19 | 230, 235, 245, 142, 206, 0, 0, 245, 85, 228, 222, 191, 105, 248, 79, 61,
20 | 238, 128, 224, 240, 0, 255, 215, 191, 136, 112, 209, 0, 21, 205, 183, 248,
21 | 230, 250, 107, 224, 50, 92, 250, 158, 128, 69, 255, 196, 43, 144, 128, 251,
22 | 82
23 | ],
24 | "B": [
25 | 0, 0, 0, 255, 0, 255, 255, 213, 205, 63, 140, 170, 128, 139, 87, 225, 205,
26 | 221, 122, 42, 250, 219, 214, 130, 193, 113, 205, 196, 32, 128, 143, 180,
27 | 185, 135, 0, 19, 0, 224, 180, 0, 238, 170, 240, 179, 11, 170, 147, 112, 144,
28 | 34, 255, 250, 122, 144, 47, 225, 71, 230, 0, 50, 96, 240, 238, 0, 230, 203,
29 | 212, 0, 143, 60, 230, 240, 209, 127, 128, 205, 114, 211, 34, 80, 235, 237,
30 | 140, 215, 238, 35, 250, 139, 139, 220, 211, 181, 173, 255, 30, 220, 79, 139,
31 | 238, 0, 230, 245, 0, 255, 0, 216, 153, 147, 204, 255, 133, 50, 107, 255,
32 | 250, 154, 47, 208, 204, 92, 210, 160, 0, 0, 255, 222, 226, 255, 128, 152, 45
33 | ],
34 | "A": [
35 | 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
36 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
37 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
38 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
39 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
40 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
41 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
42 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
43 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
44 | ],
45 | "I": [
46 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
47 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
48 | 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
49 | 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
50 | 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
51 | 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
52 | 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
53 | 128, 129, 130
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/source/api.rst:
--------------------------------------------------------------------------------
1 | API Documentation
2 | =================
3 |
4 | .. autoclass:: ipyniivue.NiiVue
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
9 | .. autoclass:: ipyniivue.Volume
10 | :members:
11 | :undoc-members:
12 | :show-inheritance:
13 |
14 | .. autoclass:: ipyniivue.Mesh
15 | :members:
16 | :undoc-members:
17 | :show-inheritance:
18 |
19 | .. autoclass:: ipyniivue.MeshLayer
20 | :members:
21 | :undoc-members:
22 | :show-inheritance:
--------------------------------------------------------------------------------
/docs/source/architecture.rst:
--------------------------------------------------------------------------------
1 | Architecture
2 | ============
3 |
4 | IPyNiiVue is a widget that bridges WebGL-powered JavaScript visualization (Niivue) with Python in notebooks. This document contains an overview of how JavaScript interacts with Python in this library.
5 |
6 | System Overview
7 | ---------------
8 |
9 | - **Python Side**: Users interact with the ``NiiVue`` class to load and manipulate neuroimaging data
10 | - **JavaScript Side**: Handles WebGL rendering and user interactions in the browser
11 | - **Communication Layer**: Traitlets synchronize state between Python and JavaScript via WebSocket (JupyterLab) or HTTP (Marimo)
12 |
13 | Architecture Layers
14 | -------------------
15 |
16 | 1. Python Backend (Kernel)
17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
18 |
19 | Runs in the notebook kernel process and contains:
20 |
21 | - ``NiiVue``, ``Volume``, ``Mesh``, and ``MeshLayer`` classes
22 | - State management through traitlets
23 | - Data processing and computational logic
24 | - Chunked data handling for large data from the frontend (to overcome Tornado's 10MB limit)
25 |
26 | 2. Widget Bridge Layer
27 | ~~~~~~~~~~~~~~~~~~~~~~
28 |
29 | Provided by anywidget framework:
30 |
31 | - Maintains synchronized widget models between kernel and browser
32 | - Handles state synchronization via traitlets
33 | - Manages WebSocket/HTTP communication
34 | - Serializes/deserializes data between Python and JavaScript
35 |
36 | 3. JavaScript Frontend (Browser)
37 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38 |
39 | Runs in the browser and includes:
40 |
41 | - WebGL rendering via Niivue.js library
42 | - UI event handling (mouse, keyboard interactions)
43 | - Visual output in notebook cells
44 |
45 | Data Flow
46 | ---------
47 |
48 | .. mermaid::
49 |
50 | flowchart LR
51 | subgraph "Kernel"
52 | A[Python Backend
NiiVue class]
53 | end
54 |
55 | subgraph "Communication Layer"
56 | B[Widget Bridge
Traitlets sync
WebSocket/HTTP]
57 | end
58 |
59 | subgraph "Browser"
60 | C[JavaScript Frontend
WebGL rendering
User interactions]
61 | D[Notebook Output Cell
Visual display]
62 | end
63 |
64 | A <--> B
65 | B <--> C
66 | C <--> D
67 |
68 | Communication Patterns
69 | ~~~~~~~~~~~~~~~~~~~~~~
70 |
71 | 1. **State Updates**: Property changes (opacity, colormap, etc.) sync automatically via traitlets
72 | 2. **Large Data Transfer**: Volume/mesh data transmitted in chunks to handle size limitations
73 | 3. **Efficient Updates**: Only array differences (indices + values) sent for existing data
74 | 4. **Event Handling**: User interactions in JS trigger Python callbacks via custom messages
75 |
76 | Some Implementation Details
77 | ---------------------------
78 |
79 | **JavaScript Bundle**:
80 |
81 | - Source: ``js/`` directory
82 | - Build: esbuild bundles to ``src/ipyniivue/static/widget.js``
83 | - Main components: ``widget.ts``, ``volume.ts``, ``mesh.ts``, ``lib.ts``
84 |
85 | **Python Components**:
86 |
87 | - ``widget.py``: Core NiiVue widget and data models
88 | - ``serializers.py``: Data conversion between Python and JavaScript
89 | - ``config_options.py``: Configuration management
90 | - ``traits.py``: Custom trait types for specialized data
91 |
92 | **Build System**:
93 |
94 | - ``build.js``: Generates colormaps and shader names during build
95 | - ``pyproject.toml``: Python packaging configuration
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | """Configuration file for the Sphinx documentation builder."""
2 |
3 | import os
4 | import sys
5 |
6 | sys.path.insert(0, os.path.abspath("../../src"))
7 |
8 | # -- Project information -----------------------------------------------------
9 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
10 |
11 | project = "IPyNiiVue"
12 | author = "Jan-Hendrik Müller, Trevor Manz, Bradley Alford, Anthony Androulakis, "
13 | author += "Taylor Hanayik, Christian O'Reilly"
14 | project_copyright = "2025, " + author
15 | release = "2.1.0"
16 |
17 | # -- General configuration ---------------------------------------------------
18 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
19 |
20 | extensions = [
21 | "sphinx.ext.autodoc",
22 | "sphinx.ext.viewcode",
23 | "sphinxcontrib.mermaid",
24 | "myst_parser",
25 | ]
26 |
27 | templates_path = ["_templates"]
28 | exclude_patterns = []
29 |
30 | # Autodoc options
31 | autodoc_default_options = {
32 | "members": True,
33 | "undoc-members": False,
34 | "inherited-members": False,
35 | "special-members": "__init__",
36 | "show-inheritance": True,
37 | }
38 | # autodoc_member_order = "bysource"
39 | autoclass_content = "class"
40 |
41 | # -- Options for HTML output -------------------------------------------------
42 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
43 |
44 | html_theme = "furo"
45 | # html_static_path = ["_static"]
46 |
--------------------------------------------------------------------------------
/docs/source/contributing.rst:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | We are glad you are here! Contributions to this package are always welcome.
5 | Read on to learn more about the contribution process and package design.
6 |
7 | ipyniivue uses `the recommended `__ hatchling build-system, which is convenient to use via the `hatch CLI `__. We recommend installing hatch globally (e.g., via pipx) and running the various commands defined within pyproject.toml. hatch will take care of creating and synchronizing a virtual environment with all dependencies defined in pyproject.toml.
8 |
9 | Install Pre-Commit hooks
10 | ^^^^^^^^^^^^^^^^^^^^^^^^
11 | We use `pre-commit `__ to run code checks and clear
12 | notebook outputs before committing changes. To install the pre-commit hooks,
13 | run the following command:
14 |
15 | .. code-block:: console
16 |
17 | $ pre-commit install
18 |
19 | As a result, the hooks will automatically check and fix some issues before
20 | pushing with git. If changes are made after committing with git, you will need
21 | to commit again afterwards. Alternatively, you can just run the following
22 | command beforehand:
23 |
24 | .. code-block:: console
25 |
26 | $ nb-clean clean --remove-empty-cells
27 |
28 | Command Cheatsheet
29 | ^^^^^^^^^^^^^^^^^^
30 | All commands are run from the root of the project, from a terminal:
31 |
32 | +--------------------+-----------------------------------+
33 | | Command | Action |
34 | +====================+===================================+
35 | | $ hatch run format | Format project with ruff format . |
36 | | | and apply linting with ruff --fix |
37 | +--------------------+-----------------------------------+
38 | | $ hatch run lint | Lint project with ruff check . |
39 | +--------------------+-----------------------------------+
40 | | $ hatch run test | Run unit tests with pytest |
41 | +--------------------+-----------------------------------+
42 | | $ hatch run docs | Build docs with Sphinx |
43 | +--------------------+-----------------------------------+
44 |
45 | Alternatively, you can develop ipyniivue by manually creating a virtual environment and managing installation and dependencies with pip.
46 |
47 | .. code-block:: console
48 |
49 | python3 -m venv .venv && source .venv/bin/activate
50 | pip install -e ".[dev]"
51 |
52 | Making Changes to the JavaScript Code
53 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54 |
55 | This is an `anywidget `__ project, which means the code base is hybrid Python and JavaScript. The JavaScript part is developed under js/ and uses `esbuild `__ to bundle the code. Any time you make changes to the JavaScript code, you need to rebuild the files under src/ipyniivue/static. This can be done in two ways:
56 |
57 | .. code-block:: console
58 |
59 | $ npm run build
60 |
61 | which will build the JavaScript code once, or you can start a development server:
62 |
63 | .. code-block:: console
64 |
65 | $ npm run dev
66 |
67 | which will start a development server that will automatically rebuild the code as you make changes. We recommend the latter approach, as it is more convenient.
68 |
69 | Once you have the development server running, you can start the JupyterLab or VS Code to develop the widget. When finished, you can stop the development server with Ctrl+C.
70 |
71 | NOTE: In order to have anywidget automatically apply changes as you work, make sure to export ANYWIDGET_HMR=1 environment variable. This can be set directly in a notebook with %env ANYWIDGET_HMR=1 in a cell.
--------------------------------------------------------------------------------
/docs/source/enums.rst:
--------------------------------------------------------------------------------
1 | Enums
2 | =====
3 |
4 | .. currentmodule:: ipyniivue
5 |
6 | .. autoclass:: ColormapType
7 | :members:
8 | :undoc-members:
9 | :show-inheritance:
10 |
11 | .. autoclass:: DragMode
12 | :members:
13 | :undoc-members:
14 | :show-inheritance:
15 |
16 | .. autoclass:: MultiplanarType
17 | :members:
18 | :undoc-members:
19 | :show-inheritance:
20 |
21 | .. autoclass:: PenType
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
26 | .. autoclass:: ShowRender
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | .. autoclass:: SliceType
32 | :members:
33 | :undoc-members:
34 | :show-inheritance:
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | IPyNiiVue Documentation
2 | =======================
3 |
4 | A Jupyter Widget for Niivue based on anywidget.
5 |
6 | .. toctree::
7 | :maxdepth: 4
8 | :caption: Contents:
9 |
10 | install
11 | api
12 | options
13 | traits
14 | enums
15 | architecture
16 | contributing
--------------------------------------------------------------------------------
/docs/source/install.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 | To first install IPyNiiVue and its dependencies, simply run:
5 |
6 | .. code-block:: console
7 |
8 | $ pip install ipyniivue
9 |
10 | Usage
11 | ^^^^^^
12 |
13 | In a Jupyter environment:
14 |
15 | .. code-block:: python
16 |
17 | from ipyniivue import NiiVue
18 |
19 | nv = NiiVue()
20 | nv.load_volumes([{"path": "images/mni152.nii.gz"}])
21 | nv
22 |
23 | See the `basic demo `__ to learn more.
--------------------------------------------------------------------------------
/docs/source/options.rst:
--------------------------------------------------------------------------------
1 | Options
2 | =======
3 |
4 | .. autoclass:: ipyniivue.config_options.ConfigOptions
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
--------------------------------------------------------------------------------
/docs/source/traits.rst:
--------------------------------------------------------------------------------
1 | Traits
2 | ======
3 |
4 | .. currentmodule:: ipyniivue
5 |
6 | .. autoclass:: ColorMap
7 | :members:
8 | :undoc-members:
9 | :show-inheritance:
10 |
11 | .. autoclass:: LUT
12 | :members:
13 | :undoc-members:
14 | :show-inheritance:
15 |
16 | .. autoclass:: Graph
17 | :members:
18 | :undoc-members:
19 | :show-inheritance:
--------------------------------------------------------------------------------
/environment.yml:
--------------------------------------------------------------------------------
1 | name: ipyniivue
2 | channels:
3 | - conda-forge
4 | dependencies:
5 | - pip
6 | - pip:
7 | - ipyniivue==2.4.1
8 | - requests
9 | - anywidget
10 | - nibabel
11 | - ipylab
--------------------------------------------------------------------------------
/examples/add_from_frontend.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "3c537ca6",
6 | "metadata": {},
7 | "source": [
8 | "# Drag-and-Drop\n",
9 | "This example showcases adding volumes/meshes via drag-and-drop. "
10 | ]
11 | },
12 | {
13 | "cell_type": "markdown",
14 | "id": "4bf34aa7",
15 | "metadata": {},
16 | "source": [
17 | "This first code cell creates a new nv object without any loaded volumes or meshes."
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "id": "37a1e7ad-ea43-4e2a-95e9-2eef3dc74a97",
24 | "metadata": {},
25 | "outputs": [],
26 | "source": [
27 | "from ipyniivue import NiiVue\n",
28 | "\n",
29 | "nv = NiiVue()\n",
30 | "nv"
31 | ]
32 | },
33 | {
34 | "cell_type": "markdown",
35 | "id": "abbcb465",
36 | "metadata": {},
37 | "source": [
38 | "Now, try dragging and dropping a volume file, for example [mni152.nii.gz](https://niivue.com/demos/images/mni152.nii.gz). Then, run the next code cell to check that volumes have been loaded into the python backend."
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "id": "78169ab8-781c-4488-8259-47a6be43e625",
45 | "metadata": {},
46 | "outputs": [],
47 | "source": [
48 | "nv.volumes"
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "id": "0844a7c1",
54 | "metadata": {},
55 | "source": [
56 | "You can also modify this volume's features, such as its opacity."
57 | ]
58 | },
59 | {
60 | "cell_type": "code",
61 | "execution_count": null,
62 | "id": "06bd69d3-3a1a-40ad-9362-7cc21f264ebf",
63 | "metadata": {},
64 | "outputs": [],
65 | "source": [
66 | "if len(nv.volumes) > 0:\n",
67 | " nv.volumes[0].opacity = 0.5"
68 | ]
69 | }
70 | ],
71 | "metadata": {},
72 | "nbformat": 4,
73 | "nbformat_minor": 5
74 | }
75 |
--------------------------------------------------------------------------------
/examples/additive_voxels.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from pathlib import Path\n",
10 | "\n",
11 | "import ipyniivue\n",
12 | "from ipyniivue import NiiVue, download_dataset\n",
13 | "\n",
14 | "# GitHub API URL for the base folder\n",
15 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
16 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "metadata": {},
23 | "outputs": [],
24 | "source": [
25 | "# Download data for example\n",
26 | "download_dataset(\n",
27 | " BASE_API_URL,\n",
28 | " DATA_FOLDER,\n",
29 | " files=[\n",
30 | " \"mni152.nii.gz\",\n",
31 | " \"narps-4965_9U7M-hypo1_unthresh.nii.gz\",\n",
32 | " \"narps-4735_50GV-hypo1_unthresh.nii.gz\",\n",
33 | " ],\n",
34 | ")"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": null,
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "# based on https://niivue.github.io/niivue/features/additive.voxels.html\n",
44 | "\n",
45 | "volumes = [\n",
46 | " {\"path\": DATA_FOLDER / \"mni152.nii.gz\"},\n",
47 | " {\n",
48 | " \"path\": DATA_FOLDER / \"narps-4965_9U7M-hypo1_unthresh.nii.gz\",\n",
49 | " \"colormap\": \"red\",\n",
50 | " \"cal_min\": 2,\n",
51 | " \"cal_max\": 4,\n",
52 | " },\n",
53 | " {\n",
54 | " \"path\": DATA_FOLDER / \"narps-4735_50GV-hypo1_unthresh.nii.gz\",\n",
55 | " \"colormap\": \"green\",\n",
56 | " \"cal_min\": 2,\n",
57 | " \"cal_max\": 4,\n",
58 | " },\n",
59 | "]\n",
60 | "nv = NiiVue(\n",
61 | " back_color=(1, 1, 1, 1),\n",
62 | " show_3D_crosshair=True,\n",
63 | " is_colorbar=True,\n",
64 | ")\n",
65 | "nv.load_volumes(volumes)\n",
66 | "nv"
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": null,
72 | "metadata": {},
73 | "outputs": [],
74 | "source": [
75 | "import ipywidgets\n",
76 | "\n",
77 | "nv.volumes[0].colorbar_visible = False\n",
78 | "sred = ipywidgets.FloatSlider(min=0.1, max=0.4, step=0.01, value=0.2)\n",
79 | "ipywidgets.link((sred, \"value\"), (nv.volumes[1], \"cal_min\"))\n",
80 | "sgreen = ipywidgets.FloatSlider(min=0.1, max=0.4, step=0.01, value=0.2)\n",
81 | "ipywidgets.link((sgreen, \"value\"), (nv.volumes[2], \"cal_min\"))\n",
82 | "ipywidgets.HBox([sred, sgreen])"
83 | ]
84 | }
85 | ],
86 | "metadata": {},
87 | "nbformat": 4,
88 | "nbformat_minor": 4
89 | }
90 |
--------------------------------------------------------------------------------
/examples/afni.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "8ac54f8d-2d2c-4e57-a8a1-a1a1463b6451",
6 | "metadata": {},
7 | "source": [
8 | "# Import necessary modules"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "e57b7f74-2402-4580-a3c9-bd1dbb3b8cdc",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import asyncio\n",
19 | "import pathlib\n",
20 | "\n",
21 | "import ipywidgets as widgets\n",
22 | "from IPython.display import display\n",
23 | "\n",
24 | "import ipyniivue\n",
25 | "from ipyniivue import NiiVue, ShowRender, SliceType, download_dataset"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "id": "b000ac63-b5b8-4258-b1be-e83868675c25",
31 | "metadata": {},
32 | "source": [
33 | "# Download required data"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": null,
39 | "id": "8f176e83",
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
44 | "\n",
45 | "download_dataset(\n",
46 | " api_url=\"https://niivue.com/demos/images/\",\n",
47 | " dest_folder=DATA_FOLDER,\n",
48 | " files=[\n",
49 | " \"example4d+orig.HEAD\",\n",
50 | " \"example4d+orig.BRIK.gz\",\n",
51 | " ],\n",
52 | ")"
53 | ]
54 | },
55 | {
56 | "cell_type": "markdown",
57 | "id": "a3678fb5-65b1-4528-9a74-d7f27c62c8df",
58 | "metadata": {},
59 | "source": [
60 | "# Create the NiiVue widget"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "id": "a873dc99-8c09-4518-a053-c6a4720aff8d",
67 | "metadata": {},
68 | "outputs": [],
69 | "source": [
70 | "nv = NiiVue()\n",
71 | "\n",
72 | "nv.set_radiological_convention(False)\n",
73 | "nv.set_slice_type(SliceType.MULTIPLANAR)\n",
74 | "nv.opts.multiplanar_show_render = ShowRender.ALWAYS\n",
75 | "\n",
76 | "# Configure graph values\n",
77 | "nv.graph.auto_size_multiplanar = True\n",
78 | "nv.graph.normalize_values = False\n",
79 | "nv.graph.opacity = 1.0\n",
80 | "\n",
81 | "# Load 4D volume with paired HEAD and BRIK files\n",
82 | "nv.load_volumes(\n",
83 | " [\n",
84 | " {\n",
85 | " \"path\": DATA_FOLDER / \"example4d+orig.HEAD\",\n",
86 | " \"paired_img_path\": DATA_FOLDER / \"example4d+orig.BRIK.gz\",\n",
87 | " \"colormap\": \"gray\",\n",
88 | " \"opacity\": 1.0,\n",
89 | " \"visible\": True,\n",
90 | " },\n",
91 | " ]\n",
92 | ")"
93 | ]
94 | },
95 | {
96 | "cell_type": "markdown",
97 | "id": "dd0e9aee-0bdb-4eba-829c-e6e5acd68835",
98 | "metadata": {},
99 | "source": [
100 | "# Create other buttons/checkboxes"
101 | ]
102 | },
103 | {
104 | "cell_type": "code",
105 | "execution_count": null,
106 | "id": "3fd88e96-ca86-4b36-92eb-1e1f2d80f8ed",
107 | "metadata": {},
108 | "outputs": [],
109 | "source": [
110 | "display_frame = widgets.Label(value=\"Volume: 0\")\n",
111 | "\n",
112 | "normalize_checkbox = widgets.Checkbox(\n",
113 | " value=False,\n",
114 | " description=\"Normalize Graph\",\n",
115 | ")\n",
116 | "\n",
117 | "prev_button = widgets.Button(description=\"Back\")\n",
118 | "next_button = widgets.Button(description=\"Forward\")"
119 | ]
120 | },
121 | {
122 | "cell_type": "markdown",
123 | "id": "111a97d2-714e-47ae-81b8-80c44f6726a9",
124 | "metadata": {},
125 | "source": [
126 | "# Implement the callbacks"
127 | ]
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": null,
132 | "id": "167128f8-a16a-43bd-803f-da10f08c0d4d",
133 | "metadata": {},
134 | "outputs": [],
135 | "source": [
136 | "def on_normalize_change(change):\n",
137 | " \"\"\"Normalize graph.\"\"\"\n",
138 | " nv.graph.normalize_values = change[\"new\"]\n",
139 | "\n",
140 | "\n",
141 | "normalize_checkbox.observe(on_normalize_change, names=\"value\")\n",
142 | "\n",
143 | "\n",
144 | "def on_prev_button_clicked(b):\n",
145 | " \"\"\"Decrement the frame index.\"\"\"\n",
146 | " if nv.volumes:\n",
147 | " current_frame = nv.volumes[0].frame_4d\n",
148 | " new_frame = max(current_frame - 1, 0)\n",
149 | " nv.volumes[0].frame_4d = new_frame\n",
150 | " display_frame.value = f\"Volume: {new_frame}\"\n",
151 | "\n",
152 | "\n",
153 | "def on_next_button_clicked(b):\n",
154 | " \"\"\"Increment the frame index.\"\"\"\n",
155 | " if nv.volumes:\n",
156 | " current_frame = nv.volumes[0].frame_4d\n",
157 | " n_frames = nv.volumes[0].n_frame_4d\n",
158 | " new_frame = min(current_frame + 1, n_frames - 1)\n",
159 | " nv.volumes[0].frame_4d = new_frame\n",
160 | " display_frame.value = f\"Volume: {new_frame}\"\n",
161 | "\n",
162 | "\n",
163 | "prev_button.on_click(on_prev_button_clicked)\n",
164 | "next_button.on_click(on_next_button_clicked)"
165 | ]
166 | },
167 | {
168 | "cell_type": "markdown",
169 | "id": "b310d908-6f11-4d66-83fa-117af002799e",
170 | "metadata": {},
171 | "source": [
172 | "# Create animate button"
173 | ]
174 | },
175 | {
176 | "cell_type": "code",
177 | "execution_count": null,
178 | "id": "24726cb0-0d09-4e98-a16c-630d3b746cd3",
179 | "metadata": {},
180 | "outputs": [],
181 | "source": [
182 | "animate_button = widgets.Button(description=\"Animate\")\n",
183 | "\n",
184 | "animation_running = False\n",
185 | "animation_task = None\n",
186 | "\n",
187 | "\n",
188 | "async def animate_frames():\n",
189 | " \"\"\"Animation loop.\"\"\"\n",
190 | " global animation_running\n",
191 | " if not nv.volumes:\n",
192 | " return\n",
193 | " n_frames = nv.volumes[0].n_frame_4d\n",
194 | " try:\n",
195 | " while animation_running:\n",
196 | " current_frame = nv.volumes[0].frame_4d\n",
197 | " current_frame = (current_frame + 1) % n_frames\n",
198 | " nv.volumes[0].frame_4d = current_frame\n",
199 | " display_frame.value = f\"Volume: {current_frame}\"\n",
200 | " await asyncio.sleep(0.1)\n",
201 | " except asyncio.CancelledError:\n",
202 | " pass\n",
203 | "\n",
204 | "\n",
205 | "def on_animate_button_clicked(b):\n",
206 | " \"\"\"Define 'Animate' button click handler.\"\"\"\n",
207 | " global animation_running, animation_task\n",
208 | " if not animation_running:\n",
209 | " # Start animation\n",
210 | " animation_running = True\n",
211 | " animate_button.description = \"Stop\"\n",
212 | " # Schedule the animation coroutine and store the future\n",
213 | " animation_task = asyncio.ensure_future(animate_frames())\n",
214 | " else:\n",
215 | " # Stop animation\n",
216 | " animation_running = False\n",
217 | " animate_button.description = \"Animate\"\n",
218 | " # Cancel the running task if it's active\n",
219 | " if animation_task is not None:\n",
220 | " animation_task.cancel()\n",
221 | " animation_task = None\n",
222 | "\n",
223 | "\n",
224 | "animate_button.on_click(on_animate_button_clicked)"
225 | ]
226 | },
227 | {
228 | "cell_type": "markdown",
229 | "id": "5f6f5a15-6b36-4312-b735-10e185900c19",
230 | "metadata": {},
231 | "source": [
232 | "# Reset frame index on image loaded"
233 | ]
234 | },
235 | {
236 | "cell_type": "code",
237 | "execution_count": null,
238 | "id": "71ba69a0-6fe0-48a7-a03b-29822e24d676",
239 | "metadata": {},
240 | "outputs": [],
241 | "source": [
242 | "@nv.on_image_loaded\n",
243 | "def update_number_of_frames(volume):\n",
244 | " \"\"\"Reset to first frame.\"\"\"\n",
245 | " nv.volumes[0].frame_4d = 0\n",
246 | " display_frame.value = \"Volume: 0\""
247 | ]
248 | },
249 | {
250 | "cell_type": "markdown",
251 | "id": "906aee0a-cb57-46b6-9808-a8ac5443a408",
252 | "metadata": {},
253 | "source": [
254 | "# Display all"
255 | ]
256 | },
257 | {
258 | "cell_type": "code",
259 | "execution_count": null,
260 | "id": "44999f74-851a-40c7-aee1-84a65aeb69b0",
261 | "metadata": {},
262 | "outputs": [],
263 | "source": [
264 | "controls = widgets.HBox(\n",
265 | " [\n",
266 | " normalize_checkbox,\n",
267 | " prev_button,\n",
268 | " next_button,\n",
269 | " animate_button,\n",
270 | " ]\n",
271 | ")\n",
272 | "\n",
273 | "display(widgets.VBox([controls, display_frame, nv]))"
274 | ]
275 | }
276 | ],
277 | "metadata": {},
278 | "nbformat": 4,
279 | "nbformat_minor": 5
280 | }
281 |
--------------------------------------------------------------------------------
/examples/atlas.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "496e0b0c-4465-49ac-b45b-90840e8584cc",
6 | "metadata": {},
7 | "source": [
8 | "# Import Necessary Modules"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "db0d1707-8667-4fb5-bd05-7f9c72e114bd",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import json\n",
19 | "import pathlib\n",
20 | "\n",
21 | "import ipywidgets as widgets\n",
22 | "from IPython.display import display\n",
23 | "\n",
24 | "import ipyniivue\n",
25 | "from ipyniivue import NiiVue, ShowRender, download_dataset"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "id": "6f7c902e-74ba-43ef-8abc-4e71e635452b",
31 | "metadata": {},
32 | "source": [
33 | "# Download Required Data"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": null,
39 | "id": "4e7b6d15-fadc-4775-b7b5-4768a2e6f184",
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "# Define the data folder where the images will be stored\n",
44 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
45 | "\n",
46 | "# Download the required data files\n",
47 | "download_dataset(\n",
48 | " \"https://niivue.com/demos/images/\",\n",
49 | " dest_folder=DATA_FOLDER,\n",
50 | " files=[\n",
51 | " \"mni152.nii.gz\",\n",
52 | " \"aal.nii.gz\",\n",
53 | " \"aal.json\",\n",
54 | " ],\n",
55 | ")"
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "id": "426ab05d-8568-48ad-b50c-559af3327e75",
61 | "metadata": {},
62 | "source": [
63 | "# Create NiiVue Instance"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "id": "a262366c-aa16-4dde-927b-04dc5133e885",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "nv = NiiVue(\n",
74 | " show_3d_crosshair=True,\n",
75 | " back_color=(0.5, 0.5, 0.5, 1),\n",
76 | ")\n",
77 | "\n",
78 | "nv.set_interpolation(True)\n",
79 | "nv.opts.crosshair_gap = 12\n",
80 | "nv.opts.multiplanar_show_render = ShowRender.ALWAYS\n",
81 | "nv.opts.drag_mode = \"PAN\"\n",
82 | "nv.opts.yoke_3d_to_2d_zoom = True\n",
83 | "\n",
84 | "nv.load_volumes(\n",
85 | " [\n",
86 | " {\"path\": DATA_FOLDER / \"mni152.nii.gz\"},\n",
87 | " {\"path\": DATA_FOLDER / \"aal.nii.gz\"},\n",
88 | " ]\n",
89 | ")"
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "id": "ef9c6c82-c66c-405a-8af5-431f9cf0d14b",
95 | "metadata": {},
96 | "source": [
97 | "# Load colormap label"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": null,
103 | "id": "c974ffd9-b6ad-4ff5-bfde-634c3ec0a61e",
104 | "metadata": {},
105 | "outputs": [],
106 | "source": [
107 | "with open(DATA_FOLDER / \"aal.json\") as f:\n",
108 | " cmap = json.load(f)\n",
109 | "\n",
110 | "nv.volumes[1].set_colormap_label(cmap)\n",
111 | "\n",
112 | "clut = nv.volumes[1].colormap_label.lut.copy()\n",
113 | "\n",
114 | "# Make all regions translucent by setting alpha values to 96\n",
115 | "for i in range(3, len(clut), 4):\n",
116 | " clut[i] = 96\n",
117 | "\n",
118 | "# Update the colormap label with the modified lut\n",
119 | "nv.volumes[1].colormap_label.lut = clut"
120 | ]
121 | },
122 | {
123 | "cell_type": "markdown",
124 | "id": "9e56f814-6c98-4450-aa36-9f4c6b6930f6",
125 | "metadata": {},
126 | "source": [
127 | "# Add other widgets"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "id": "64ccf253-e201-412d-99e9-f5566cbb7e94",
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "interp_checkbox = widgets.Checkbox(\n",
138 | " value=True,\n",
139 | " description=\"Jagged\",\n",
140 | ")\n",
141 | "\n",
142 | "outline_slider = widgets.IntSlider(\n",
143 | " min=0,\n",
144 | " max=255,\n",
145 | " value=1,\n",
146 | " description=\"Outline\",\n",
147 | ")\n",
148 | "\n",
149 | "alpha_slider = widgets.IntSlider(\n",
150 | " min=1,\n",
151 | " max=255,\n",
152 | " value=150,\n",
153 | " description=\"Opacity\",\n",
154 | ")\n",
155 | "\n",
156 | "pad_slider = widgets.IntSlider(\n",
157 | " min=0,\n",
158 | " max=10,\n",
159 | " value=5,\n",
160 | " description=\"Padding\",\n",
161 | ")\n",
162 | "\n",
163 | "gap_slider = widgets.IntSlider(\n",
164 | " min=0,\n",
165 | " max=36,\n",
166 | " value=12,\n",
167 | " description=\"Crosshair Gap\",\n",
168 | ")"
169 | ]
170 | },
171 | {
172 | "cell_type": "markdown",
173 | "id": "f05fd288-5f27-49f5-ba92-8b8cd26aa954",
174 | "metadata": {},
175 | "source": [
176 | "# Setup observer functions"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "id": "2e86879a-71c5-4048-8d9f-6bca0ed9d9fd",
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "def on_outline_change(change):\n",
187 | " \"\"\"Handle changes in the outline slider.\"\"\"\n",
188 | " nv.set_atlas_outline(change[\"new\"] / 255)\n",
189 | "\n",
190 | "\n",
191 | "def on_alpha_change(change):\n",
192 | " \"\"\"Handle changes in the opacity slider.\"\"\"\n",
193 | " nv.volumes[1].opacity = change[\"new\"] / 255\n",
194 | "\n",
195 | "\n",
196 | "def on_pad_change(change):\n",
197 | " \"\"\"Handle changes in the padding slider.\"\"\"\n",
198 | " nv.opts.multiplanar_pad_pixels = change[\"new\"]\n",
199 | "\n",
200 | "\n",
201 | "def on_gap_change(change):\n",
202 | " \"\"\"Handle changes in the crosshair gap slider.\"\"\"\n",
203 | " nv.opts.crosshair_gap = change[\"new\"]\n",
204 | "\n",
205 | "\n",
206 | "def on_interp_change(change):\n",
207 | " \"\"\"Handle changes in the interpolation checkbox.\"\"\"\n",
208 | " nv.set_interpolation(change[\"new\"])"
209 | ]
210 | },
211 | {
212 | "cell_type": "markdown",
213 | "id": "2e8ec870-d0c2-4701-8eef-bf34afc9c891",
214 | "metadata": {},
215 | "source": [
216 | "# Observe changes"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "id": "a2d4118d-2987-4bbd-af6b-f82f2a9e623c",
223 | "metadata": {},
224 | "outputs": [],
225 | "source": [
226 | "# Observe changes in widget values and call the respective functions\n",
227 | "outline_slider.observe(on_outline_change, names=\"value\")\n",
228 | "alpha_slider.observe(on_alpha_change, names=\"value\")\n",
229 | "pad_slider.observe(on_pad_change, names=\"value\")\n",
230 | "gap_slider.observe(on_gap_change, names=\"value\")\n",
231 | "interp_checkbox.observe(on_interp_change, names=\"value\")\n",
232 | "\n",
233 | "# Initialize the NiiVue instance with the current widget values\n",
234 | "on_alpha_change({\"new\": alpha_slider.value})\n",
235 | "on_outline_change({\"new\": outline_slider.value})\n",
236 | "nv.opts.multiplanar_pad_pixels = pad_slider.value\n",
237 | "nv.opts.crosshair_gap = gap_slider.value\n",
238 | "nv.set_interpolation(interp_checkbox.value)"
239 | ]
240 | },
241 | {
242 | "cell_type": "markdown",
243 | "id": "2d1d8a71-3c2b-4449-828c-e74e9653938f",
244 | "metadata": {},
245 | "source": [
246 | "# Setup hover and click updates"
247 | ]
248 | },
249 | {
250 | "cell_type": "code",
251 | "execution_count": null,
252 | "id": "44980c10-573d-493a-9a4d-c002942cf563",
253 | "metadata": {},
254 | "outputs": [],
255 | "source": [
256 | "output = widgets.HTML(\"Hover:
Clicked:\")\n",
257 | "\n",
258 | "active_idx = -1\n",
259 | "\n",
260 | "\n",
261 | "@nv.on_hover_idx_change\n",
262 | "def on_hover_idx_change(data):\n",
263 | " \"\"\"Handle hover updates.\"\"\"\n",
264 | " global active_idx\n",
265 | " idx_values = data[\"idxValues\"]\n",
266 | " idx = idx_values[1][\"idx\"]\n",
267 | " if idx is not None and idx != active_idx:\n",
268 | " nv.opts.atlas_active_index = idx\n",
269 | " label = cmap[\"labels\"][idx] if idx < len(cmap[\"labels\"]) else \"\"\n",
270 | " clicked_line = output.value.split(\"Clicked:\")[1]\n",
271 | " output.value = f\"Hover: {label}
Clicked:{clicked_line}\"\n",
272 | "\n",
273 | "\n",
274 | "@nv.on_location_change\n",
275 | "def handle_location_change(location):\n",
276 | " \"\"\"Handle mouse clicks.\"\"\"\n",
277 | " hover_line = output.value.split(\"Hover:\")[1].split(\"
\")[0]\n",
278 | " output.value = f\"Hover:{hover_line}
Clicked: {location['string']}\""
279 | ]
280 | },
281 | {
282 | "cell_type": "markdown",
283 | "id": "20ac0527-b46a-4203-af39-512902327917",
284 | "metadata": {},
285 | "source": [
286 | "# Display all"
287 | ]
288 | },
289 | {
290 | "cell_type": "code",
291 | "execution_count": null,
292 | "id": "cd2e77fd-7b6e-4026-a814-0fe155151e67",
293 | "metadata": {},
294 | "outputs": [],
295 | "source": [
296 | "controls = widgets.VBox(\n",
297 | " [\n",
298 | " interp_checkbox,\n",
299 | " outline_slider,\n",
300 | " alpha_slider,\n",
301 | " pad_slider,\n",
302 | " gap_slider,\n",
303 | " output,\n",
304 | " ]\n",
305 | ")\n",
306 | "\n",
307 | "display(\n",
308 | " widgets.VBox(\n",
309 | " [\n",
310 | " controls,\n",
311 | " nv,\n",
312 | " ]\n",
313 | " )\n",
314 | ")"
315 | ]
316 | }
317 | ],
318 | "metadata": {},
319 | "nbformat": 4,
320 | "nbformat_minor": 5
321 | }
322 |
--------------------------------------------------------------------------------
/examples/atlas.sparse.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "39b55e89-0de0-449c-b0f5-0d0ecb54e6f4",
6 | "metadata": {},
7 | "source": [
8 | "# Import Necessary Modules"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "ec364cef-1bd3-498d-b571-281dfada6249",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import pathlib\n",
19 | "\n",
20 | "import ipywidgets as widgets\n",
21 | "from IPython.display import display\n",
22 | "\n",
23 | "import ipyniivue\n",
24 | "from ipyniivue import DragMode, NiiVue, ShowRender, download_dataset"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "fbd334de-9cf8-4b78-bfd3-bec98d52d50f",
30 | "metadata": {},
31 | "source": [
32 | "# Download Required Data"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "ef853053-a88e-423b-9e8c-096399af731f",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
43 | "\n",
44 | "download_dataset(\n",
45 | " \"https://niivue.com/demos/images/\",\n",
46 | " dest_folder=DATA_FOLDER,\n",
47 | " files=[\n",
48 | " \"inia19-t1-brain.nii.gz\",\n",
49 | " \"inia19-NeuroMaps.nii.gz\",\n",
50 | " ],\n",
51 | ")"
52 | ]
53 | },
54 | {
55 | "cell_type": "markdown",
56 | "id": "bdc45f6e-9fff-44b2-bb22-dac86c37326d",
57 | "metadata": {},
58 | "source": [
59 | "# Setup NiiVue Instance"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": null,
65 | "id": "bd327698-3349-4f6a-ac25-1b12b3ee18d4",
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "nv = NiiVue(\n",
70 | " back_color=(0.5, 0.5, 0.5, 1),\n",
71 | " show_3d_crosshair=True,\n",
72 | " drag_mode=DragMode.PAN,\n",
73 | " yoke_3d_to_2d_zoom=True,\n",
74 | " multiplanar_show_render=ShowRender.ALWAYS,\n",
75 | ")\n",
76 | "\n",
77 | "nv.load_volumes(\n",
78 | " [\n",
79 | " {\"path\": DATA_FOLDER / \"inia19-t1-brain.nii.gz\"},\n",
80 | " {\"path\": DATA_FOLDER / \"inia19-NeuroMaps.nii.gz\", \"opacity\": 0.5},\n",
81 | " ]\n",
82 | ")\n",
83 | "\n",
84 | "nv.volumes[1].set_colormap_label_from_url(\n",
85 | " \"https://niivue.com/demos/images/inia19-NeuroMaps.json\"\n",
86 | ")\n",
87 | "\n",
88 | "nv.volumes[1].opacity = 0.188\n",
89 | "nv.set_atlas_outline(1.0)"
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "id": "b6a15481-c027-4128-8cc7-b13b0e0df1c8",
95 | "metadata": {},
96 | "source": [
97 | "# Create interactive widgets"
98 | ]
99 | },
100 | {
101 | "cell_type": "code",
102 | "execution_count": null,
103 | "id": "909c02d8-5392-4f52-928e-407620fc2eea",
104 | "metadata": {},
105 | "outputs": [],
106 | "source": [
107 | "# Slider for opacity\n",
108 | "opacity_slider = widgets.IntSlider(\n",
109 | " min=1,\n",
110 | " max=255,\n",
111 | " value=48,\n",
112 | " description=\"Opacity\",\n",
113 | " continuous_update=True,\n",
114 | ")\n",
115 | "\n",
116 | "# Slider for outline\n",
117 | "outline_slider = widgets.IntSlider(\n",
118 | " min=0,\n",
119 | " max=255,\n",
120 | " value=255,\n",
121 | " description=\"Outline\",\n",
122 | " continuous_update=True,\n",
123 | ")\n",
124 | "\n",
125 | "location_label = widgets.HTML(\" \")"
126 | ]
127 | },
128 | {
129 | "cell_type": "markdown",
130 | "id": "c22dcfc8-4ac1-443f-9c4b-dcb9073d329a",
131 | "metadata": {},
132 | "source": [
133 | "# Define callbacks"
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": null,
139 | "id": "36e58df0-4a17-4f6c-bcc7-f211fa48e428",
140 | "metadata": {},
141 | "outputs": [],
142 | "source": [
143 | "def on_opacity_change(change):\n",
144 | " \"\"\"Update the opacity of the atlas volume.\"\"\"\n",
145 | " nv.volumes[1].opacity = change[\"new\"] / 255\n",
146 | "\n",
147 | "\n",
148 | "def on_outline_change(change):\n",
149 | " \"\"\"Update the outline thickness of the atlas.\"\"\"\n",
150 | " nv.set_atlas_outline(change[\"new\"] / 255)\n",
151 | "\n",
152 | "\n",
153 | "def handle_location_change(location):\n",
154 | " \"\"\"Update the location label with current coordinates.\"\"\"\n",
155 | " location_label.value = location[\"string\"]"
156 | ]
157 | },
158 | {
159 | "cell_type": "markdown",
160 | "id": "2e03552e-e106-41fd-9a01-cf953757f800",
161 | "metadata": {},
162 | "source": [
163 | "# Setup observers"
164 | ]
165 | },
166 | {
167 | "cell_type": "code",
168 | "execution_count": null,
169 | "id": "350e4e11-9202-4e99-9afa-e8883d2b3bd5",
170 | "metadata": {},
171 | "outputs": [],
172 | "source": [
173 | "opacity_slider.observe(on_opacity_change, names=\"value\")\n",
174 | "outline_slider.observe(on_outline_change, names=\"value\")\n",
175 | "\n",
176 | "nv.on_location_change(handle_location_change)\n",
177 | "\n",
178 | "on_opacity_change({\"new\": opacity_slider.value})\n",
179 | "on_outline_change({\"new\": outline_slider.value})"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "id": "84c861a3-040a-4597-9a4d-40974c8501e5",
185 | "metadata": {},
186 | "source": [
187 | "# Display all"
188 | ]
189 | },
190 | {
191 | "cell_type": "code",
192 | "execution_count": null,
193 | "id": "94acebe5-edd8-4b92-875b-391a8f11bd57",
194 | "metadata": {},
195 | "outputs": [],
196 | "source": [
197 | "controls = widgets.HBox([opacity_slider, outline_slider])\n",
198 | "\n",
199 | "display(\n",
200 | " widgets.VBox(\n",
201 | " [\n",
202 | " controls,\n",
203 | " nv,\n",
204 | " location_label,\n",
205 | " ]\n",
206 | " )\n",
207 | ")"
208 | ]
209 | }
210 | ],
211 | "metadata": {},
212 | "nbformat": 4,
213 | "nbformat_minor": 5
214 | }
215 |
--------------------------------------------------------------------------------
/examples/basic_multiplanar.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from pathlib import Path\n",
10 | "\n",
11 | "import ipyniivue\n",
12 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
13 | "\n",
14 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
15 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
16 | ]
17 | },
18 | {
19 | "cell_type": "code",
20 | "execution_count": null,
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "# Download data for example\n",
25 | "download_dataset(\n",
26 | " BASE_API_URL,\n",
27 | " DATA_FOLDER,\n",
28 | " files=[\n",
29 | " \"mni152.nii.gz\",\n",
30 | " \"hippo.nii.gz\",\n",
31 | " ],\n",
32 | ")"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "# based on https://niivue.github.io/niivue/features/basic.multiplanar.html\n",
42 | "\n",
43 | "volumes = [\n",
44 | " {\n",
45 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
46 | " \"colormap\": \"gray\",\n",
47 | " \"visible\": True,\n",
48 | " \"opacity\": 1.0,\n",
49 | " },\n",
50 | " {\n",
51 | " \"path\": DATA_FOLDER / \"hippo.nii.gz\",\n",
52 | " \"colormap\": \"red\",\n",
53 | " \"visible\": True,\n",
54 | " \"opacity\": 1,\n",
55 | " },\n",
56 | "]\n",
57 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
58 | "nv.load_volumes(volumes)\n",
59 | "nv"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": null,
65 | "metadata": {},
66 | "outputs": [],
67 | "source": [
68 | "nv.volumes[0].opacity = 0.3\n",
69 | "nv.volumes[1].colormap = \"blue\""
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": null,
75 | "metadata": {},
76 | "outputs": [],
77 | "source": [
78 | "nv.opts.show_3d_crosshair = True\n",
79 | "nv.opts.crosshair_color = [0, 1, 1, 1]\n",
80 | "nv.opts.back_color = [1, 1, 1, 1]\n",
81 | "nv.opts.clip_plane_color = [0, 1, 1, 1]\n",
82 | "nv.opts.is_colorbar = True\n",
83 | "nv.opts.view_mode_hot_key = \"KeyN\""
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": null,
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "nv"
93 | ]
94 | }
95 | ],
96 | "metadata": {},
97 | "nbformat": 4,
98 | "nbformat_minor": 4
99 | }
100 |
--------------------------------------------------------------------------------
/examples/colormaps.mesh.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "3f68cd5d-4213-44e2-b1db-fae4bebb7a5d",
6 | "metadata": {},
7 | "source": [
8 | "Prepare images"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "c36ea7c1-438a-462e-b796-92c586424af8",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "from pathlib import Path\n",
19 | "\n",
20 | "import ipyniivue\n",
21 | "\n",
22 | "# GitHub API URL for the base folder\n",
23 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
24 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
25 | ]
26 | },
27 | {
28 | "cell_type": "code",
29 | "execution_count": null,
30 | "id": "e1149f33-ef1e-4249-821f-e41d0644319e",
31 | "metadata": {},
32 | "outputs": [],
33 | "source": [
34 | "# Download data for example\n",
35 | "ipyniivue.download_dataset(\n",
36 | " BASE_API_URL,\n",
37 | " DATA_FOLDER,\n",
38 | " files=[\n",
39 | " \"BrainMesh_ICBM152.lh.curv\",\n",
40 | " \"BrainMesh_ICBM152.lh.motor.mz3\",\n",
41 | " \"BrainMesh_ICBM152.lh.mz3\",\n",
42 | " ],\n",
43 | ")"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "id": "1d522f34-35e0-452c-808d-2d45a9ee9c37",
49 | "metadata": {},
50 | "source": [
51 | "Create instance of niivue"
52 | ]
53 | },
54 | {
55 | "cell_type": "code",
56 | "execution_count": null,
57 | "id": "b4444b81-5bfe-46cc-af63-f6044d04fb80",
58 | "metadata": {},
59 | "outputs": [],
60 | "source": [
61 | "from ipyniivue import NiiVue\n",
62 | "\n",
63 | "nv = NiiVue(back_color=(0.3, 0.3, 0.3, 1))\n",
64 | "\n",
65 | "meshLHLayersList1 = [\n",
66 | " {\n",
67 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.curv\",\n",
68 | " \"colormap\": \"gray\",\n",
69 | " \"cal_min\": 0.3,\n",
70 | " \"cal_max\": 0.5,\n",
71 | " \"opacity\": 0.7,\n",
72 | " },\n",
73 | " {\n",
74 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.motor.mz3\",\n",
75 | " \"cal_min\": 1.64,\n",
76 | " \"cal_max\": 5,\n",
77 | " \"colormap\": \"warm\",\n",
78 | " \"colormap_negative\": \"winter\",\n",
79 | " \"use_negative_cmap\": True,\n",
80 | " \"opacity\": 0.7,\n",
81 | " },\n",
82 | "]\n",
83 | "\n",
84 | "nv.load_meshes(\n",
85 | " [\n",
86 | " {\n",
87 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.mz3\",\n",
88 | " \"layers\": meshLHLayersList1,\n",
89 | " }\n",
90 | " ]\n",
91 | ")\n",
92 | "\n",
93 | "nv.opts.is_colorbar = True\n",
94 | "\n",
95 | "nv.meshes[0].layers[0].colorbar_visible = False"
96 | ]
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "id": "55820127-9917-4e54-aa48-bf1e169ee4c6",
101 | "metadata": {},
102 | "source": [
103 | "Create interactive widgets"
104 | ]
105 | },
106 | {
107 | "cell_type": "code",
108 | "execution_count": null,
109 | "id": "19ea4d11-94cc-4d7a-ba4d-21e407c18939",
110 | "metadata": {},
111 | "outputs": [],
112 | "source": [
113 | "import ipywidgets as widgets\n",
114 | "from IPython.display import display\n",
115 | "\n",
116 | "# Invert Checkbox\n",
117 | "invert_check = widgets.Checkbox(\n",
118 | " value=False,\n",
119 | " description=\"Invert\",\n",
120 | ")\n",
121 | "\n",
122 | "\n",
123 | "def on_invert_change(change):\n",
124 | " \"\"\"Set mesh layer 1 colormap invert.\"\"\"\n",
125 | " nv.set_mesh_layer_property(nv.meshes[0].id, 1, \"colormap_invert\", change[\"new\"])\n",
126 | "\n",
127 | "\n",
128 | "invert_check.observe(on_invert_change, names=\"value\")\n",
129 | "\n",
130 | "# Curvature Slider\n",
131 | "curve_slider = widgets.IntSlider(\n",
132 | " value=7,\n",
133 | " min=0,\n",
134 | " max=10,\n",
135 | " step=1,\n",
136 | " description=\"Curvature\",\n",
137 | ")\n",
138 | "\n",
139 | "\n",
140 | "def on_curve_slider_change(change):\n",
141 | " \"\"\"Set mesh layer 0 opacity.\"\"\"\n",
142 | " nv.set_mesh_layer_property(nv.meshes[0].id, 0, \"opacity\", change[\"new\"] * 0.1)\n",
143 | "\n",
144 | "\n",
145 | "curve_slider.observe(on_curve_slider_change, names=\"value\")\n",
146 | "\n",
147 | "# Opacity Slider\n",
148 | "opacity_slider = widgets.IntSlider(\n",
149 | " value=7,\n",
150 | " min=0,\n",
151 | " max=10,\n",
152 | " step=1,\n",
153 | " description=\"Opacity\",\n",
154 | ")\n",
155 | "\n",
156 | "\n",
157 | "def on_opacity_slider_change(change):\n",
158 | " \"\"\"Set mesh layer 1 opacity.\"\"\"\n",
159 | " nv.set_mesh_layer_property(nv.meshes[0].id, 1, \"opacity\", change[\"new\"] * 0.1)\n",
160 | "\n",
161 | "\n",
162 | "opacity_slider.observe(on_opacity_slider_change, names=\"value\")\n",
163 | "\n",
164 | "# Save Bitmap Button\n",
165 | "save_bmp_button = widgets.Button(\n",
166 | " description=\"Save Bitmap\",\n",
167 | ")\n",
168 | "\n",
169 | "\n",
170 | "def on_save_bmp_clicked(b):\n",
171 | " \"\"\"Save scene.\"\"\"\n",
172 | " nv.save_scene(\"ScreenShot.png\")\n",
173 | " with out:\n",
174 | " from IPython.display import clear_output\n",
175 | "\n",
176 | " clear_output(wait=True)\n",
177 | " print('Scene saved as \"ScreenShot.png\"')\n",
178 | "\n",
179 | "\n",
180 | "save_bmp_button.on_click(on_save_bmp_clicked)\n",
181 | "\n",
182 | "# Output widget for messages\n",
183 | "out = widgets.Output()"
184 | ]
185 | },
186 | {
187 | "cell_type": "markdown",
188 | "id": "25290c1d-4992-45fb-a601-12ae69278856",
189 | "metadata": {},
190 | "source": [
191 | "Create the colormap header buttons"
192 | ]
193 | },
194 | {
195 | "cell_type": "code",
196 | "execution_count": null,
197 | "id": "dba89d89-9870-4b5b-943e-b87718b537e1",
198 | "metadata": {},
199 | "outputs": [],
200 | "source": [
201 | "import math\n",
202 | "\n",
203 | "# Get available colormaps\n",
204 | "colormaps = nv.colormaps()\n",
205 | "colormap_buttons = []\n",
206 | "\n",
207 | "\n",
208 | "def create_colormap_button(name):\n",
209 | " \"\"\"Create colormap.\"\"\"\n",
210 | " btn = widgets.Button(description=name)\n",
211 | "\n",
212 | " def on_click(b):\n",
213 | " \"\"\"Set mesh layer colormap.\"\"\"\n",
214 | " nv.set_mesh_layer_property(nv.meshes[0].id, 1, \"colormap\", name)\n",
215 | "\n",
216 | " btn.on_click(on_click)\n",
217 | " return btn\n",
218 | "\n",
219 | "\n",
220 | "colormap_buttons = [create_colormap_button(name) for name in colormaps]\n",
221 | "\n",
222 | "# Organize colormap buttons in a grid\n",
223 | "num_cols = 10\n",
224 | "num_rows = math.ceil(len(colormap_buttons) / num_cols)\n",
225 | "colormap_grid = []\n",
226 | "\n",
227 | "for i in range(num_rows):\n",
228 | " row_buttons = colormap_buttons[i * num_cols : (i + 1) * num_cols]\n",
229 | " colormap_grid.append(widgets.HBox(row_buttons))\n",
230 | "\n",
231 | "colormap_buttons_widget = widgets.VBox(colormap_grid)"
232 | ]
233 | },
234 | {
235 | "cell_type": "markdown",
236 | "id": "4e53f48a-867b-4195-881b-c800e17ffc90",
237 | "metadata": {},
238 | "source": [
239 | "Display"
240 | ]
241 | },
242 | {
243 | "cell_type": "code",
244 | "execution_count": null,
245 | "id": "0dd22c2c-0c0c-4b03-9b56-9af6020b9b60",
246 | "metadata": {},
247 | "outputs": [],
248 | "source": [
249 | "# Display header controls\n",
250 | "header_controls = widgets.HBox(\n",
251 | " [\n",
252 | " invert_check,\n",
253 | " curve_slider,\n",
254 | " opacity_slider,\n",
255 | " save_bmp_button,\n",
256 | " ]\n",
257 | ")\n",
258 | "\n",
259 | "display(header_controls)\n",
260 | "display(nv)\n",
261 | "\n",
262 | "# Display footer with colormap buttons\n",
263 | "display(colormap_buttons_widget)\n",
264 | "\n",
265 | "# Print output\n",
266 | "display(out)"
267 | ]
268 | }
269 | ],
270 | "metadata": {},
271 | "nbformat": 4,
272 | "nbformat_minor": 5
273 | }
274 |
--------------------------------------------------------------------------------
/examples/complex.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "ed7fbf9d-3501-42a4-b921-5b52d8f03ed1",
6 | "metadata": {},
7 | "source": [
8 | "# Import necessary modules"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "d3a59e62-a3df-4e47-b6d5-53fd79a9d959",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import pathlib\n",
19 | "\n",
20 | "import ipywidgets as widgets\n",
21 | "from IPython.display import display\n",
22 | "\n",
23 | "import ipyniivue\n",
24 | "from ipyniivue import NiiVue, download_dataset"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "8e9962f5-1c8f-4b23-bbec-7799dedcc36d",
30 | "metadata": {},
31 | "source": [
32 | "# Download required data"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "51551acf-1252-4bd9-837a-e5322f4458d5",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
43 | "\n",
44 | "download_dataset(\n",
45 | " \"https://niivue.com/demos/images/\",\n",
46 | " dest_folder=DATA_FOLDER,\n",
47 | " files=[\n",
48 | " \"complex.nii.gz\",\n",
49 | " ],\n",
50 | ")"
51 | ]
52 | },
53 | {
54 | "cell_type": "markdown",
55 | "id": "e41d20b2-cb78-4593-ab04-27f62d866762",
56 | "metadata": {},
57 | "source": [
58 | "# Setup NiiVue instance"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": null,
64 | "id": "1fcb1f7a-fdbf-4fdc-b59d-ce18e53a8f04",
65 | "metadata": {},
66 | "outputs": [],
67 | "source": [
68 | "nv = NiiVue()\n",
69 | "\n",
70 | "intensity_output = widgets.HTML(\" \")\n",
71 | "\n",
72 | "\n",
73 | "@nv.on_location_change\n",
74 | "def handle_intensity_change(location):\n",
75 | " \"\"\"Handle location change.\"\"\"\n",
76 | " intensity_output.value = location[\"string\"]\n",
77 | "\n",
78 | "\n",
79 | "nv.load_volumes(\n",
80 | " [\n",
81 | " {\"path\": DATA_FOLDER / \"complex.nii.gz\"},\n",
82 | " ]\n",
83 | ")"
84 | ]
85 | },
86 | {
87 | "cell_type": "markdown",
88 | "id": "9fb4ad15-2f7c-41cd-9444-dc9792ff621a",
89 | "metadata": {},
90 | "source": [
91 | "# Create other widget"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": null,
97 | "id": "a238bbe0-efaa-402b-8242-00a40b4494fa",
98 | "metadata": {},
99 | "outputs": [],
100 | "source": [
101 | "slice_type_dropdown = widgets.Dropdown(\n",
102 | " options=[\n",
103 | " (\"Axial\", 0),\n",
104 | " (\"Coronal\", 1),\n",
105 | " (\"Sagittal\", 2),\n",
106 | " (\"Render\", 4),\n",
107 | " (\"A+C+S+R\", 3),\n",
108 | " ],\n",
109 | " value=3,\n",
110 | " description=\"Slice Type:\",\n",
111 | ")\n",
112 | "\n",
113 | "\n",
114 | "def on_slice_type_change(change):\n",
115 | " \"\"\"Set slice type.\"\"\"\n",
116 | " nv.set_slice_type(change[\"new\"])\n",
117 | "\n",
118 | "\n",
119 | "slice_type_dropdown.observe(on_slice_type_change, names=\"value\")"
120 | ]
121 | },
122 | {
123 | "cell_type": "markdown",
124 | "id": "a0c433b8-9e9c-4c8c-881b-2785b5c2da64",
125 | "metadata": {},
126 | "source": [
127 | "# Display all"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "id": "1c5c0e07-9729-41b2-ab33-9d04163a392d",
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "display(widgets.VBox([slice_type_dropdown, nv, intensity_output]))"
138 | ]
139 | }
140 | ],
141 | "metadata": {},
142 | "nbformat": 4,
143 | "nbformat_minor": 5
144 | }
145 |
--------------------------------------------------------------------------------
/examples/document.load.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "65d657c7-c5a9-4a9c-b1c4-4d8e6e5290dd",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "import ipywidgets as widgets\n",
11 | "from IPython.display import display\n",
12 | "\n",
13 | "from ipyniivue import NiiVue"
14 | ]
15 | },
16 | {
17 | "cell_type": "code",
18 | "execution_count": null,
19 | "id": "10b9f3e0-66e8-4b8d-a273-e35ce5dae539",
20 | "metadata": {},
21 | "outputs": [],
22 | "source": [
23 | "nv = NiiVue()\n",
24 | "display(nv)"
25 | ]
26 | },
27 | {
28 | "cell_type": "code",
29 | "execution_count": null,
30 | "id": "d48877dc-3d23-45c6-b6f6-9cc2573fc691",
31 | "metadata": {},
32 | "outputs": [],
33 | "source": [
34 | "# Document URLs to load\n",
35 | "document_urls = [\n",
36 | " \"https://niivue.com/demos/images/niivue.basic.nvd\",\n",
37 | " \"https://niivue.com/demos/images/niivue.drawing.nvd\",\n",
38 | " \"https://niivue.com/demos/images/niivue.mesh.nvd\",\n",
39 | "]\n",
40 | "\n",
41 | "\n",
42 | "# Load the first document by default\n",
43 | "@nv.on_canvas_attached\n",
44 | "def load_first_document():\n",
45 | " \"\"\"Load first document.\"\"\"\n",
46 | " nv.load_document(document_urls[0])"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "id": "e6ec2eb7-865d-4dd5-a649-03ee7fe806d7",
53 | "metadata": {},
54 | "outputs": [],
55 | "source": [
56 | "# Create preview buttons for documents\n",
57 | "def create_document_button(url, index):\n",
58 | " \"\"\"Create a button for loading a document.\"\"\"\n",
59 | " name = url.split(\"/\")[-1]\n",
60 | " button = widgets.Button(\n",
61 | " description=f\"Load {name}\", layout=widgets.Layout(width=\"auto\", margin=\"2px\")\n",
62 | " )\n",
63 | "\n",
64 | " def on_click(b):\n",
65 | " nv.load_document(url)\n",
66 | " status_output.value = f\"Loaded: {name}\"\n",
67 | "\n",
68 | " button.on_click(on_click)\n",
69 | " return button\n",
70 | "\n",
71 | "\n",
72 | "# Create buttons for each document\n",
73 | "document_buttons = [\n",
74 | " create_document_button(url, i) for i, url in enumerate(document_urls)\n",
75 | "]\n",
76 | "\n",
77 | "# Status display\n",
78 | "status_output = widgets.HTML(value=\"Loaded: niivue.basic.nvd\")"
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": null,
84 | "id": "41e8f292-0719-4c47-a2d6-f28be679efdc",
85 | "metadata": {},
86 | "outputs": [],
87 | "source": [
88 | "# Track hippocampus state\n",
89 | "hippo_state = {\"is_showing\": False}\n",
90 | "hippo_url = \"https://niivue.com/demos/images/hippo.nii.gz\"\n",
91 | "\n",
92 | "\n",
93 | "def toggle_hippocampus(b):\n",
94 | " \"\"\"Toggle the hippocampus overlay.\"\"\"\n",
95 | " if hippo_state[\"is_showing\"]:\n",
96 | " nv.volumes = nv.volumes[:-1]\n",
97 | " b.description = \"Add Hippocampus\"\n",
98 | " hippo_state[\"is_showing\"] = False\n",
99 | " status_output.value = \"Removed hippocampus overlay\"\n",
100 | " else:\n",
101 | " # Add hippocampus\n",
102 | " nv.add_volume({\"url\": hippo_url, \"colormap\": \"bluegrn\", \"opacity\": 0.7})\n",
103 | " b.description = \"Remove Hippocampus\"\n",
104 | " hippo_state[\"is_showing\"] = True\n",
105 | " status_output.value = \"Added hippocampus overlay\"\n",
106 | "\n",
107 | "\n",
108 | "# Create toggle button\n",
109 | "hippo_button = widgets.Button(description=\"Add Hippocampus\", button_style=\"info\")\n",
110 | "hippo_button.on_click(toggle_hippocampus)"
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "id": "2740219d-9ba6-4d72-adbb-a6acb87cb932",
117 | "metadata": {},
118 | "outputs": [],
119 | "source": [
120 | "def save_document(b):\n",
121 | " \"\"\"Save the current scene as a document.\"\"\"\n",
122 | " nv.save_document(\"my_document.nvd\")\n",
123 | " status_output.value = \"Document saved as 'my_document.nvd'\"\n",
124 | "\n",
125 | "\n",
126 | "# Create save button\n",
127 | "save_button = widgets.Button(description=\"Save Document\", button_style=\"success\")\n",
128 | "save_button.on_click(save_document)"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "id": "0e85641e-6b10-4bd7-ac1b-9db365257810",
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "# Create organized layout\n",
139 | "file_controls = widgets.VBox(\n",
140 | " [\n",
141 | " widgets.HTML(\"File Operations
\"),\n",
142 | " widgets.HBox([save_button]),\n",
143 | " widgets.HTML(\"Load Documents
\"),\n",
144 | " widgets.VBox(document_buttons),\n",
145 | " widgets.HTML(\"Volume Controls
\"),\n",
146 | " widgets.HBox([hippo_button]),\n",
147 | " widgets.HTML(\"Status
\"),\n",
148 | " status_output,\n",
149 | " ]\n",
150 | ")\n",
151 | "\n",
152 | "# Display controls\n",
153 | "display(file_controls)\n",
154 | "display(nv)"
155 | ]
156 | }
157 | ],
158 | "metadata": {},
159 | "nbformat": 4,
160 | "nbformat_minor": 5
161 | }
162 |
--------------------------------------------------------------------------------
/examples/example_4d.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "094d7d78-087b-4eeb-963a-186d1653b582",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "import ipywidgets as widgets\n",
11 | "\n",
12 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
13 | "from ipyniivue.download_dataset import DATA_FOLDER\n",
14 | "\n",
15 | "# GitHub API URL for the base folder\n",
16 | "BASE_API_URL = \"https://niivue.com/demos/images/\""
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "bca75d90",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "# Download data for example\n",
27 | "download_dataset(\n",
28 | " BASE_API_URL,\n",
29 | " DATA_FOLDER,\n",
30 | " files=[\n",
31 | " \"mpld_asl.nii.gz\",\n",
32 | " ],\n",
33 | ")"
34 | ]
35 | },
36 | {
37 | "cell_type": "markdown",
38 | "id": "bf39aa6b",
39 | "metadata": {},
40 | "source": [
41 | "Importing nibabel. Since nibabel isn't currently in the ipyniivue requirements, you might need to install it."
42 | ]
43 | },
44 | {
45 | "cell_type": "code",
46 | "execution_count": null,
47 | "id": "7332152b-1073-4112-a231-a04d402ae2d0",
48 | "metadata": {},
49 | "outputs": [],
50 | "source": [
51 | "try:\n",
52 | " import nibabel as nib\n",
53 | "except ModuleNotFoundError:\n",
54 | " !pip install nibabel\n",
55 | " import nibabel as nib"
56 | ]
57 | },
58 | {
59 | "cell_type": "code",
60 | "execution_count": null,
61 | "id": "66143d2b-af3a-4fcf-9d0c-b5c7ff19931d",
62 | "metadata": {},
63 | "outputs": [],
64 | "source": [
65 | "example = DATA_FOLDER / \"mpld_asl.nii.gz\"\n",
66 | "volumes = [\n",
67 | " {\n",
68 | " \"path\": example,\n",
69 | " \"colormap\": \"gray\",\n",
70 | " \"visible\": True,\n",
71 | " \"opacity\": 1.0,\n",
72 | " },\n",
73 | "]\n",
74 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
75 | "nv.load_volumes(volumes)\n",
76 | "\n",
77 | "nvols = nib.load(example).shape[-1]\n",
78 | "\n",
79 | "slider = widgets.IntSlider(min=0, max=nvols - 1, description=\"Volume\")\n",
80 | "\n",
81 | "\n",
82 | "def update_frame(*args):\n",
83 | " \"\"\"Select the frame corresponding to the slider value.\"\"\"\n",
84 | " nv.volumes[0].frame_4d = slider.value\n",
85 | "\n",
86 | "\n",
87 | "slider.observe(update_frame, \"value\")\n",
88 | "\n",
89 | "display(slider)\n",
90 | "display(nv)"
91 | ]
92 | }
93 | ],
94 | "metadata": {},
95 | "nbformat": 4,
96 | "nbformat_minor": 5
97 | }
98 |
--------------------------------------------------------------------------------
/examples/example_sideview.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "52de1125",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "# This is not part of IPyNiiVue dependencies so it has to be installed independently\n",
11 | "!pip install ipylab"
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "b1cbb154-3c30-4d61-afea-8f7e42ee65b9",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "from pathlib import Path\n",
22 | "\n",
23 | "from ipylab import JupyterFrontEnd, SplitPanel\n",
24 | "\n",
25 | "import ipyniivue\n",
26 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
27 | "\n",
28 | "# GitHub API URL for the base folder\n",
29 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
30 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": null,
36 | "id": "6291cd2c-0674-4ba3-a912-75ff6ca7fe78",
37 | "metadata": {},
38 | "outputs": [],
39 | "source": [
40 | "# Download data for example\n",
41 | "download_dataset(\n",
42 | " BASE_API_URL,\n",
43 | " DATA_FOLDER,\n",
44 | " files=[\n",
45 | " \"mni152.nii.gz\",\n",
46 | " \"hippo.nii.gz\",\n",
47 | " ],\n",
48 | ")"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": null,
54 | "id": "74af1303-22e9-4933-9a13-de80b2109067",
55 | "metadata": {},
56 | "outputs": [],
57 | "source": [
58 | "# this example needs to have ipylab installed and only runs in JupyterLab.\n",
59 | "volumes = [\n",
60 | " {\n",
61 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
62 | " \"colormap\": \"gray\",\n",
63 | " \"visible\": True,\n",
64 | " \"opacity\": 1.0,\n",
65 | " },\n",
66 | " {\n",
67 | " \"path\": DATA_FOLDER / \"hippo.nii.gz\",\n",
68 | " \"colormap\": \"red\",\n",
69 | " \"visible\": True,\n",
70 | " \"opacity\": 1,\n",
71 | " },\n",
72 | "]\n",
73 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
74 | "nv.load_volumes(volumes)\n",
75 | "\n",
76 | "\n",
77 | "app = JupyterFrontEnd()\n",
78 | "split_panel = SplitPanel()\n",
79 | "\n",
80 | "# add the widgets to the split panel\n",
81 | "split_panel.children = [nv]\n",
82 | "\n",
83 | "split_panel.title.label = \"NiiVue SplitPanel\"\n",
84 | "split_panel.title.icon_class = \"jp-PythonIcon\"\n",
85 | "split_panel.title.closable = True\n",
86 | "app.shell.add(split_panel, \"main\", {\"mode\": \"split-right\"})\n",
87 | "print(\"Split panel created!\")"
88 | ]
89 | },
90 | {
91 | "cell_type": "code",
92 | "execution_count": null,
93 | "id": "97c74b53-7f9f-45b4-9608-b594d564b8cd",
94 | "metadata": {},
95 | "outputs": [],
96 | "source": [
97 | "nv.volumes[0].opacity = 0.5\n",
98 | "nv.volumes[1].colormap = \"green\""
99 | ]
100 | }
101 | ],
102 | "metadata": {},
103 | "nbformat": 4,
104 | "nbformat_minor": 5
105 | }
106 |
--------------------------------------------------------------------------------
/examples/mask.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "a7560ca5-f0bc-494d-abb0-477fb3944821",
6 | "metadata": {},
7 | "source": [
8 | "Prepare images"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "e1a27d89-e050-47b9-b959-1fb51e39098e",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "from pathlib import Path\n",
19 | "\n",
20 | "import ipyniivue\n",
21 | "\n",
22 | "# GitHub API URL for the base folder\n",
23 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
24 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
25 | ]
26 | },
27 | {
28 | "cell_type": "code",
29 | "execution_count": null,
30 | "id": "c30f3c8d-28b5-4d0a-9883-a0d22f4f51e5",
31 | "metadata": {},
32 | "outputs": [],
33 | "source": [
34 | "# Download data for example\n",
35 | "ipyniivue.download_dataset(\n",
36 | " BASE_API_URL,\n",
37 | " DATA_FOLDER,\n",
38 | " files=[\n",
39 | " \"fslmean.nii.gz\",\n",
40 | " \"fslt.nii.gz\",\n",
41 | " ],\n",
42 | ")"
43 | ]
44 | },
45 | {
46 | "cell_type": "markdown",
47 | "id": "f91d30c7-3cf5-48e7-97eb-f76ba165eadf",
48 | "metadata": {},
49 | "source": [
50 | "Imports"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": null,
56 | "id": "67a84d95-df0a-4f24-ad0b-27fc4a724cc3",
57 | "metadata": {},
58 | "outputs": [],
59 | "source": [
60 | "# Import necessary libraries\n",
61 | "import ipywidgets as widgets\n",
62 | "from IPython.display import display\n",
63 | "\n",
64 | "from ipyniivue import NiiVue, SliceType"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "id": "4348595a-6674-48e5-8ef0-04e73332e87c",
70 | "metadata": {},
71 | "source": [
72 | "Create niivue instance"
73 | ]
74 | },
75 | {
76 | "cell_type": "code",
77 | "execution_count": null,
78 | "id": "1af1bf1b-9033-4fed-800c-856180de5fa4",
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "# Create a NiiVue instance with specific options\n",
83 | "nv = NiiVue(show_3d_crosshair=True)\n",
84 | "\n",
85 | "# Load the volumes\n",
86 | "volumes = [\n",
87 | " {\n",
88 | " \"path\": DATA_FOLDER / \"fslmean.nii.gz\",\n",
89 | " \"colormap\": \"gray\",\n",
90 | " \"opacity\": 1.0,\n",
91 | " \"visible\": True,\n",
92 | " },\n",
93 | " {\n",
94 | " \"path\": DATA_FOLDER / \"fslt.nii.gz\",\n",
95 | " \"colormap\": \"redyell\",\n",
96 | " \"cal_min\": 0.05,\n",
97 | " \"cal_max\": 5.05,\n",
98 | " \"opacity\": 0.9,\n",
99 | " \"visible\": True,\n",
100 | " },\n",
101 | "]\n",
102 | "\n",
103 | "nv.load_volumes(volumes)\n",
104 | "\n",
105 | "# Set the slice type to render (3D view)\n",
106 | "nv.set_slice_type(SliceType.RENDER)\n",
107 | "\n",
108 | "# Set the clip plane\n",
109 | "nv.set_clip_plane(0.15, 270, 0)\n",
110 | "\n",
111 | "# Set the render azimuth and elevation\n",
112 | "nv.set_render_azimuth_elevation(45, 45)"
113 | ]
114 | },
115 | {
116 | "cell_type": "markdown",
117 | "id": "3257ee16-f7aa-48c4-a191-95616a44a9db",
118 | "metadata": {},
119 | "source": [
120 | "Create interactive checkbox"
121 | ]
122 | },
123 | {
124 | "cell_type": "code",
125 | "execution_count": null,
126 | "id": "9946de46-fff6-4b24-8de8-739f1bac8d32",
127 | "metadata": {},
128 | "outputs": [],
129 | "source": [
130 | "# Create a checkbox to toggle background masks overlays\n",
131 | "background_masks_checkbox = widgets.Checkbox(\n",
132 | " value=False,\n",
133 | " description=\"Background masks overlay\",\n",
134 | " disabled=False,\n",
135 | ")\n",
136 | "\n",
137 | "\n",
138 | "# Function to handle checkbox changes\n",
139 | "def on_background_masks_change(change):\n",
140 | " \"\"\"Set background mask overlay.\"\"\"\n",
141 | " nv.background_masks_overlays = change.new\n",
142 | "\n",
143 | "\n",
144 | "# Observe changes to the checkbox\n",
145 | "background_masks_checkbox.observe(on_background_masks_change, names=\"value\")"
146 | ]
147 | },
148 | {
149 | "cell_type": "markdown",
150 | "id": "930e164e-7f58-400e-8102-94502dce8076",
151 | "metadata": {},
152 | "source": [
153 | "Display all"
154 | ]
155 | },
156 | {
157 | "cell_type": "code",
158 | "execution_count": null,
159 | "id": "79df762c-d512-4a00-a33a-a5fd6ca22840",
160 | "metadata": {},
161 | "outputs": [],
162 | "source": [
163 | "display(background_masks_checkbox)\n",
164 | "display(nv)"
165 | ]
166 | }
167 | ],
168 | "metadata": {},
169 | "nbformat": 4,
170 | "nbformat_minor": 5
171 | }
172 |
--------------------------------------------------------------------------------
/examples/mesh.4d.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "479d3a4f-9821-494c-8784-d1cbf85e91cf",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from pathlib import Path\n",
11 | "\n",
12 | "import ipyniivue\n",
13 | "\n",
14 | "# GitHub API URL for the base folder\n",
15 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
16 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "119c091d-8a98-4b0b-97c6-d5b724d67f80",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "# Download data for example\n",
27 | "ipyniivue.download_dataset(\n",
28 | " BASE_API_URL,\n",
29 | " DATA_FOLDER,\n",
30 | " files=[\n",
31 | " \"Human.colin.Cerebral.R.VERY_INFLATED.71723.surf.gii\",\n",
32 | " \"Human.colin.R.FUNCTIONAL.71723.func.gii\",\n",
33 | " ],\n",
34 | ")"
35 | ]
36 | },
37 | {
38 | "cell_type": "code",
39 | "execution_count": null,
40 | "id": "45ba4675-32cd-4a89-ab5e-33eec2597d26",
41 | "metadata": {},
42 | "outputs": [],
43 | "source": [
44 | "import ipywidgets as widgets\n",
45 | "from IPython.display import display\n",
46 | "\n",
47 | "from ipyniivue import NiiVue, SliceType\n",
48 | "\n",
49 | "nv = NiiVue(\n",
50 | " show_3d_crosshair=True,\n",
51 | " back_color=(0.9, 0.9, 1, 1),\n",
52 | ")\n",
53 | "nv.set_slice_type(SliceType.RENDER)\n",
54 | "\n",
55 | "# Load meshes\n",
56 | "mesh_layers = [\n",
57 | " {\n",
58 | " \"path\": DATA_FOLDER / \"Human.colin.R.FUNCTIONAL.71723.func.gii\",\n",
59 | " \"colormap\": \"rocket\",\n",
60 | " \"opacity\": 0.7,\n",
61 | " },\n",
62 | "]\n",
63 | "\n",
64 | "meshes = [\n",
65 | " {\n",
66 | " \"path\": DATA_FOLDER / \"Human.colin.Cerebral.R.VERY_INFLATED.71723.surf.gii\",\n",
67 | " \"rgba255\": [255, 255, 255, 255],\n",
68 | " \"layers\": mesh_layers,\n",
69 | " },\n",
70 | "]\n",
71 | "nv.load_meshes(meshes)\n",
72 | "\n",
73 | "# Set the clip plane\n",
74 | "nv.set_clip_plane(-0.1, 270, 0)"
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "id": "44cffb51-66b6-4668-be46-78bd45ff9bfe",
81 | "metadata": {},
82 | "outputs": [],
83 | "source": [
84 | "# Timepoint Slider\n",
85 | "slider_timepoint = widgets.IntSlider(min=0, max=1, value=0, description=\"Timepoint\")\n",
86 | "\n",
87 | "\n",
88 | "def on_timepoint_change(change):\n",
89 | " \"\"\"Set mesh layer frame 4D.\"\"\"\n",
90 | " nv.set_mesh_layer_property(nv.meshes[0].id, 0, \"frame_4d\", change[\"new\"])\n",
91 | "\n",
92 | "\n",
93 | "slider_timepoint.observe(on_timepoint_change, names=\"value\")\n",
94 | "\n",
95 | "# Opacity Slider\n",
96 | "slider_opacity = widgets.IntSlider(min=1, max=10, value=7, description=\"Opacity\")\n",
97 | "\n",
98 | "\n",
99 | "def on_opacity_change(change):\n",
100 | " \"\"\"Set mesh layer opacity.\"\"\"\n",
101 | " nv.set_mesh_layer_property(nv.meshes[0].id, 0, \"opacity\", change[\"new\"] * 0.1)\n",
102 | "\n",
103 | "\n",
104 | "slider_opacity.observe(on_opacity_change, names=\"value\")"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": null,
110 | "id": "475cf9fe-f1a6-4d87-b8a7-4d2e5995289f",
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "shader_options = nv.mesh_shader_names()\n",
115 | "\n",
116 | "shader_dropdown = widgets.Dropdown(\n",
117 | " options=shader_options,\n",
118 | " value=\"Phong\", # Default shader\n",
119 | " description=\"Shader:\",\n",
120 | ")\n",
121 | "\n",
122 | "\n",
123 | "def on_shader_change(change):\n",
124 | " \"\"\"Set mesh layer shader.\"\"\"\n",
125 | " nv.set_mesh_shader(nv.meshes[0].id, change[\"new\"])\n",
126 | "\n",
127 | "\n",
128 | "shader_dropdown.observe(on_shader_change, names=\"value\")"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "id": "f3f74d15-aeaa-42fa-bf86-bf1a8c4758f2",
135 | "metadata": {},
136 | "outputs": [],
137 | "source": [
138 | "controls = widgets.HBox([slider_timepoint, slider_opacity, shader_dropdown])"
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": null,
144 | "id": "c89e658b-fd33-46f6-b74f-6ec47b25b514",
145 | "metadata": {},
146 | "outputs": [],
147 | "source": [
148 | "display(controls)\n",
149 | "display(nv)"
150 | ]
151 | }
152 | ],
153 | "metadata": {},
154 | "nbformat": 4,
155 | "nbformat_minor": 5
156 | }
157 |
--------------------------------------------------------------------------------
/examples/mesh.matcap.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "ef04a141",
6 | "metadata": {},
7 | "source": [
8 | "# Import modules"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "d1950524-21ee-4281-8909-4b23a6734143",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import pathlib\n",
19 | "\n",
20 | "import ipywidgets as widgets\n",
21 | "\n",
22 | "import ipyniivue\n",
23 | "from ipyniivue import NiiVue, SliceType, download_dataset"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "id": "51a48f6d",
29 | "metadata": {},
30 | "source": [
31 | "# Download required data"
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": null,
37 | "id": "e8f5d640",
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
42 | "\n",
43 | "# Meshes\n",
44 | "download_dataset(\n",
45 | " \"https://niivue.com/demos/images/\",\n",
46 | " dest_folder=DATA_FOLDER,\n",
47 | " files=[\n",
48 | " \"BrainMesh_ICBM152.lh.mz3\",\n",
49 | " \"BrainMesh_ICBM152.lh.motor.mz3\",\n",
50 | " ],\n",
51 | ")\n",
52 | "\n",
53 | "# Matcaps\n",
54 | "download_dataset(\n",
55 | " api_url=\"https://niivue.com/demos/matcaps\",\n",
56 | " dest_folder=DATA_FOLDER / \"matcaps\",\n",
57 | " files=[\n",
58 | " \"Shiny.jpg\",\n",
59 | " \"Cortex.jpg\",\n",
60 | " \"Cream.jpg\",\n",
61 | " \"Fuzzy.jpg\",\n",
62 | " \"Peach.jpg\",\n",
63 | " \"Plastic.jpg\",\n",
64 | " \"Gold.jpg\",\n",
65 | " ],\n",
66 | ")"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "id": "7087b6f4",
72 | "metadata": {},
73 | "source": [
74 | "# Create niivue instance"
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "id": "1269f9f8-0f3f-441d-ac79-8e92e3021281",
81 | "metadata": {},
82 | "outputs": [],
83 | "source": [
84 | "nv = NiiVue(\n",
85 | " show_3d_crosshair=True,\n",
86 | " back_color=(1, 1, 1, 1),\n",
87 | ")\n",
88 | "\n",
89 | "nv.set_slice_type(SliceType.RENDER)\n",
90 | "nv.opts.is_colorbar = True"
91 | ]
92 | },
93 | {
94 | "cell_type": "markdown",
95 | "id": "3af1bc26",
96 | "metadata": {},
97 | "source": [
98 | "# Load meshes and mesh layers"
99 | ]
100 | },
101 | {
102 | "cell_type": "code",
103 | "execution_count": null,
104 | "id": "1b38ade7-7484-4625-a02e-be4855cd8db4",
105 | "metadata": {},
106 | "outputs": [],
107 | "source": [
108 | "mesh_layer = {\n",
109 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.motor.mz3\",\n",
110 | " \"cal_min\": 2,\n",
111 | " \"cal_max\": 5,\n",
112 | " \"use_negative_cmap\": True,\n",
113 | " \"opacity\": 0.7,\n",
114 | "}\n",
115 | "\n",
116 | "nv.load_meshes(\n",
117 | " [\n",
118 | " {\n",
119 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.mz3\",\n",
120 | " \"layers\": [mesh_layer],\n",
121 | " },\n",
122 | " ]\n",
123 | ")\n",
124 | "\n",
125 | "nv.set_mesh_shader(nv.meshes[0].id, \"Matcap\")\n",
126 | "nv.set_clip_plane(-0.1, 270, 0)"
127 | ]
128 | },
129 | {
130 | "cell_type": "markdown",
131 | "id": "9b17f707",
132 | "metadata": {},
133 | "source": [
134 | "# Add extra widgets and setup observers"
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": null,
140 | "id": "e8a67c91-15a4-4b7b-8192-b26cba349375",
141 | "metadata": {},
142 | "outputs": [],
143 | "source": [
144 | "threshold_slider = widgets.IntSlider(\n",
145 | " value=20,\n",
146 | " min=1,\n",
147 | " max=49,\n",
148 | " description=\"Threshold\",\n",
149 | ")\n",
150 | "\n",
151 | "matcap_options = [\"Shiny\", \"Cortex\", \"Cream\", \"Fuzzy\", \"Peach\", \"Plastic\", \"Gold\"]\n",
152 | "matcap_dropdown = widgets.Dropdown(\n",
153 | " options=matcap_options,\n",
154 | " value=\"Shiny\",\n",
155 | " description=\"MatCap\",\n",
156 | ")\n",
157 | "\n",
158 | "\n",
159 | "def on_threshold_change(change):\n",
160 | " \"\"\"Set mesh layer property cal_min.\"\"\"\n",
161 | " nv.set_mesh_layer_property(\n",
162 | " mesh_id=nv.meshes[0].id,\n",
163 | " layer_index=0,\n",
164 | " attribute=\"cal_min\",\n",
165 | " value=change[\"new\"] * 0.1,\n",
166 | " )\n",
167 | "\n",
168 | "\n",
169 | "threshold_slider.observe(on_threshold_change, names=\"value\")\n",
170 | "\n",
171 | "\n",
172 | "def on_matcap_change(change):\n",
173 | " \"\"\"Load matcap texture.\"\"\"\n",
174 | " nv.set_mesh_shader(nv.meshes[0].id, \"Matcap\")\n",
175 | " matcap_name = change[\"new\"]\n",
176 | " matcap_path = DATA_FOLDER / \"matcaps\" / f\"{matcap_name}.jpg\"\n",
177 | " with open(matcap_path, \"rb\") as f:\n",
178 | " matcap_data = f.read()\n",
179 | " nv.load_mat_cap_texture(matcap_data)\n",
180 | "\n",
181 | "\n",
182 | "matcap_dropdown.observe(on_matcap_change, names=\"value\")"
183 | ]
184 | },
185 | {
186 | "cell_type": "markdown",
187 | "id": "a4c45439",
188 | "metadata": {},
189 | "source": [
190 | "# Display all"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "id": "1254b5a8",
197 | "metadata": {},
198 | "outputs": [],
199 | "source": [
200 | "widgets.VBox(\n",
201 | " [\n",
202 | " widgets.HBox([threshold_slider, matcap_dropdown]),\n",
203 | " nv,\n",
204 | " ]\n",
205 | ")"
206 | ]
207 | }
208 | ],
209 | "metadata": {},
210 | "nbformat": 4,
211 | "nbformat_minor": 5
212 | }
213 |
--------------------------------------------------------------------------------
/examples/mesh_and_volume.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "593d466e-6bf6-47df-ba63-23b627f41da0",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from pathlib import Path\n",
11 | "\n",
12 | "import ipyniivue\n",
13 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
14 | "\n",
15 | "# GitHub API URL for the base folder\n",
16 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
17 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "id": "a09b7f2a-d0f7-4337-bfcc-ce5152fa9e79",
24 | "metadata": {},
25 | "outputs": [],
26 | "source": [
27 | "# Download data for example\n",
28 | "download_dataset(\n",
29 | " BASE_API_URL,\n",
30 | " DATA_FOLDER,\n",
31 | " files=[\n",
32 | " \"mni152.nii.gz\",\n",
33 | " \"BrainMesh_ICBM152.lh.mz3\",\n",
34 | " ],\n",
35 | ")"
36 | ]
37 | },
38 | {
39 | "cell_type": "code",
40 | "execution_count": null,
41 | "id": "71c2f1ec-fdbf-4aae-8d09-77fd5988e11c",
42 | "metadata": {},
43 | "outputs": [],
44 | "source": [
45 | "# based on https://github.com/niivue/ipyniivue/blob/main/original_gallery.md#tractography-tck-trk-trx-vtk\n",
46 | "\n",
47 | "volumes = [\n",
48 | " {\n",
49 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
50 | " \"colormap\": \"gray\",\n",
51 | " \"visible\": True,\n",
52 | " \"opacity\": 1.0,\n",
53 | " },\n",
54 | "]\n",
55 | "\n",
56 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
57 | "nv.load_volumes(volumes)\n",
58 | "\n",
59 | "nv.load_meshes(\n",
60 | " [\n",
61 | " {\n",
62 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.mz3\",\n",
63 | " \"rgba255\": [255, 255, 255, 255],\n",
64 | " }\n",
65 | " ]\n",
66 | ")\n",
67 | "\n",
68 | "\n",
69 | "nv"
70 | ]
71 | }
72 | ],
73 | "metadata": {},
74 | "nbformat": 4,
75 | "nbformat_minor": 5
76 | }
77 |
--------------------------------------------------------------------------------
/examples/mesh_layers.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "69a4c0b3",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from pathlib import Path\n",
11 | "\n",
12 | "import ipyniivue\n",
13 | "from ipyniivue import NiiVue, download_dataset\n",
14 | "\n",
15 | "# GitHub API URL for the base folder\n",
16 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
17 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "id": "eaca0a33-87c7-437f-b9b5-a0c967a44faf",
24 | "metadata": {},
25 | "outputs": [],
26 | "source": [
27 | "# Download data for example\n",
28 | "download_dataset(\n",
29 | " BASE_API_URL,\n",
30 | " DATA_FOLDER,\n",
31 | " files=[\n",
32 | " \"BrainMesh_ICBM152.lh.motor.mz3\",\n",
33 | " \"BrainMesh_ICBM152.lh.mz3\",\n",
34 | " \"CIT168.mz3\",\n",
35 | " ],\n",
36 | ")"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "id": "36d80248-e8a6-4f27-92c5-98dabf2aec81",
43 | "metadata": {},
44 | "outputs": [],
45 | "source": [
46 | "nv = NiiVue(\n",
47 | " show_3d_crosshair=True, back_color=(1, 1, 1, 1), mesh_xray=0.3, is_colorbar=True\n",
48 | ")\n",
49 | "\n",
50 | "mesh_layer = {\n",
51 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.motor.mz3\",\n",
52 | " \"cal_min\": 0.5,\n",
53 | " \"cal_max\": 5.5,\n",
54 | " \"use_negative_cmap\": True,\n",
55 | " \"opacity\": 0.7,\n",
56 | "}\n",
57 | "\n",
58 | "nv.load_meshes(\n",
59 | " [\n",
60 | " {\n",
61 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.mz3\",\n",
62 | " \"rgba255\": [255, 255, 255, 255],\n",
63 | " \"layers\": [mesh_layer],\n",
64 | " },\n",
65 | " {\"path\": DATA_FOLDER / \"CIT168.mz3\", \"rgba255\": [0, 0, 255, 255]},\n",
66 | " ]\n",
67 | ")\n",
68 | "\n",
69 | "nv"
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": null,
75 | "id": "956fe2ef-3180-44cc-a81e-05efc783ee05",
76 | "metadata": {},
77 | "outputs": [],
78 | "source": [
79 | "nv.meshes[0].rgba255 = [0, 200, 200, 200]"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "id": "7ae05143-ddfd-4ac5-824b-4c579730f016",
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "nv.meshes[0].layers[0].opacity = 0.2"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "id": "21b63939-9a36-48eb-83d6-76941fb6d7e1",
96 | "metadata": {},
97 | "outputs": [],
98 | "source": [
99 | "nv.set_mesh_layer_property(nv.meshes[0].id, 0, \"opacity\", 0.8)"
100 | ]
101 | }
102 | ],
103 | "metadata": {},
104 | "nbformat": 4,
105 | "nbformat_minor": 5
106 | }
107 |
--------------------------------------------------------------------------------
/examples/mosaics1.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "7d5def93-aba0-4da7-881f-94f5119e858d",
6 | "metadata": {},
7 | "source": [
8 | "# Voxel Mosaics 1\n",
9 | "\n",
10 | "Choose axial (A), coronal (C) or sagittal (S) slices. Modify with cross slices (X), renderings (R), and horizontal overlap (H).\n",
11 | "\n",
12 | "See https://niivue.com/demos/features/mosaics.html for mirror."
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "id": "09a54079-77fc-4986-9c30-d73b149b9d9e",
19 | "metadata": {},
20 | "outputs": [],
21 | "source": [
22 | "import ipywidgets as widgets\n",
23 | "from IPython.display import display\n",
24 | "\n",
25 | "from ipyniivue import NiiVue"
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "id": "a6112177-7f66-4ca2-a14e-99af9cd202f0",
31 | "metadata": {},
32 | "source": [
33 | "## Create NiiVue Instance and Load Data"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": null,
39 | "id": "bb0bfa17-379b-4c74-886b-2552dd8754cb",
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "nv = NiiVue(\n",
44 | " is_colorbar=True,\n",
45 | " is_alpha_clip_dark=True,\n",
46 | " center_mosaic=True,\n",
47 | " back_color=(1, 1, 1, 1),\n",
48 | ")\n",
49 | "\n",
50 | "nv.load_volumes(\n",
51 | " [\n",
52 | " {\n",
53 | " \"url\": \"https://niivue.com/demos/images/mni152.nii.gz\",\n",
54 | " \"colorbar_visible\": False,\n",
55 | " },\n",
56 | " {\n",
57 | " \"url\": \"https://niivue.com/demos/images/spmMotor.nii.gz\",\n",
58 | " \"cal_min\": 4.2,\n",
59 | " \"cal_max\": 8,\n",
60 | " \"colormap\": \"warm\",\n",
61 | " \"colormap_negative\": \"winter\",\n",
62 | " },\n",
63 | " ]\n",
64 | ")\n",
65 | "\n",
66 | "initial_mosaic = \"A -20 50 60 70 C -10 -20 -50 S R X 0 R X -0\"\n",
67 | "nv.set_slice_mosaic_string(initial_mosaic)\n",
68 | "\n",
69 | "\n",
70 | "@nv.on_canvas_attached\n",
71 | "def setup_illumination():\n",
72 | " \"\"\"Set volume render illumination after canvas attached.\"\"\"\n",
73 | " nv.set_volume_render_illumination(0.3)"
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "id": "0c8b9382-ae77-47be-a452-ba02e59a5d71",
79 | "metadata": {},
80 | "source": [
81 | "## Create Interactive Controls"
82 | ]
83 | },
84 | {
85 | "cell_type": "code",
86 | "execution_count": null,
87 | "id": "0a73edb3-ead1-4eac-a03c-f80de5b939be",
88 | "metadata": {},
89 | "outputs": [],
90 | "source": [
91 | "# mosaic string input\n",
92 | "mosaic_text = widgets.Text(\n",
93 | " value=initial_mosaic,\n",
94 | " placeholder=\"Enter mosaic string\",\n",
95 | " description=\"Mosaic String:\",\n",
96 | " style={\"description_width\": \"initial\"},\n",
97 | " layout=widgets.Layout(width=\"600px\"),\n",
98 | ")\n",
99 | "\n",
100 | "# help button\n",
101 | "help_button = widgets.Button(\n",
102 | " description=\"Help\",\n",
103 | " button_style=\"info\",\n",
104 | " tooltip=\"Click for information about mosaic strings\",\n",
105 | " layout=widgets.Layout(width=\"80px\"),\n",
106 | ")\n",
107 | "\n",
108 | "# outline slider\n",
109 | "outline_slider = widgets.IntSlider(\n",
110 | " min=0,\n",
111 | " max=8,\n",
112 | " value=0,\n",
113 | " description=\"Outline:\",\n",
114 | " continuous_update=True,\n",
115 | ")\n",
116 | "\n",
117 | "# alpha mode dropdown\n",
118 | "alpha_dropdown = widgets.Dropdown(\n",
119 | " options=[\n",
120 | " (\"Restrict colorbar to range\", 0),\n",
121 | " (\"Colorbar from 0, transparent subthreshold\", 1),\n",
122 | " (\"Colorbar from 0, translucent subthreshold\", 2),\n",
123 | " ],\n",
124 | " value=0,\n",
125 | " description=\"Alpha Mode:\",\n",
126 | " style={\"description_width\": \"initial\"},\n",
127 | " layout=widgets.Layout(width=\"400px\"),\n",
128 | ")\n",
129 | "\n",
130 | "# glossy rendering dropdown\n",
131 | "glossy_dropdown = widgets.Dropdown(\n",
132 | " options=[\n",
133 | " (\"Slices\", -1),\n",
134 | " (\"Matte\", 0),\n",
135 | " (\"Low\", 0.3),\n",
136 | " (\"Medium\", 0.6),\n",
137 | " (\"High\", 1.0),\n",
138 | " ],\n",
139 | " value=0.3,\n",
140 | " description=\"Glossy:\",\n",
141 | " style={\"description_width\": \"initial\"},\n",
142 | ")\n",
143 | "\n",
144 | "# dark mode checkbox\n",
145 | "dark_checkbox = widgets.Checkbox(\n",
146 | " value=False,\n",
147 | " description=\"Dark Mode\",\n",
148 | ")\n",
149 | "\n",
150 | "# save button\n",
151 | "save_button = widgets.Button(\n",
152 | " description=\"Save Bitmap\",\n",
153 | " button_style=\"success\",\n",
154 | " tooltip=\"Save current view as PNG\",\n",
155 | ")\n",
156 | "\n",
157 | "# output for messages\n",
158 | "output = widgets.Output()"
159 | ]
160 | },
161 | {
162 | "cell_type": "markdown",
163 | "id": "b69f5c09-bd2b-49c0-93a1-a4e82aface43",
164 | "metadata": {},
165 | "source": [
166 | "## Setup Event Handlers"
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": null,
172 | "id": "f02cfe96-fe8e-4483-bfc9-8b4bf0422e7f",
173 | "metadata": {},
174 | "outputs": [],
175 | "source": [
176 | "def on_mosaic_change(change):\n",
177 | " \"\"\"Handle mosaic string changes.\"\"\"\n",
178 | " nv.set_slice_mosaic_string(change[\"new\"])\n",
179 | "\n",
180 | "\n",
181 | "def on_help_click(b):\n",
182 | " \"\"\"Display help information.\"\"\"\n",
183 | " with output:\n",
184 | " output.clear_output()\n",
185 | " print(\"Mosaic String Help:\")\n",
186 | " print(\"===================\")\n",
187 | " print(\"Choose axial (A), coronal (C) or sagittal (S) slices.\")\n",
188 | " print(\"Modify with:\")\n",
189 | " print(\" - X: cross slices\")\n",
190 | " print(\" - R: renderings\")\n",
191 | " print(\" - H: horizontal overlap (e.g., H 0.3)\")\n",
192 | " print(\"\")\n",
193 | " print(\"Example: 'A -20 50 60 70 C -10 -20 -50 S R X 0 R X -0'\")\n",
194 | " print(\"This creates axial slices at positions -20, 50, 60, 70,\")\n",
195 | " print(\"coronal slices at -10, -20, -50, a sagittal slice,\")\n",
196 | " print(\"and renderings with cross sections.\")\n",
197 | "\n",
198 | "\n",
199 | "def on_outline_change(change):\n",
200 | " \"\"\"Handle outline width changes.\"\"\"\n",
201 | " nv.overlay_outline_width = 0.25 * change[\"new\"]\n",
202 | "\n",
203 | "\n",
204 | "def on_alpha_change(change):\n",
205 | " \"\"\"Handle alpha mode changes.\"\"\"\n",
206 | " nv.volumes[1].colormap_type = change[\"new\"]\n",
207 | "\n",
208 | "\n",
209 | "def on_glossy_change(change):\n",
210 | " \"\"\"Handle glossy rendering changes.\"\"\"\n",
211 | " value = float(change[\"new\"])\n",
212 | " nv.set_volume_render_illumination(value)\n",
213 | "\n",
214 | "\n",
215 | "def on_dark_change(change):\n",
216 | " \"\"\"Handle dark mode toggle.\"\"\"\n",
217 | " if change[\"new\"]:\n",
218 | " nv.opts.back_color = (0, 0, 0, 1)\n",
219 | " else:\n",
220 | " nv.opts.back_color = (1, 1, 1, 1)\n",
221 | "\n",
222 | "\n",
223 | "def on_save_click(b):\n",
224 | " \"\"\"Save the current scene.\"\"\"\n",
225 | " with output:\n",
226 | " output.clear_output()\n",
227 | " nv.save_scene(\"mosaic_screenshot.png\")\n",
228 | " print(\"Scene saved as 'mosaic_screenshot.png'\")\n",
229 | "\n",
230 | "\n",
231 | "# attach event handlers\n",
232 | "mosaic_text.observe(on_mosaic_change, names=\"value\")\n",
233 | "help_button.on_click(on_help_click)\n",
234 | "outline_slider.observe(on_outline_change, names=\"value\")\n",
235 | "alpha_dropdown.observe(on_alpha_change, names=\"value\")\n",
236 | "glossy_dropdown.observe(on_glossy_change, names=\"value\")\n",
237 | "dark_checkbox.observe(on_dark_change, names=\"value\")\n",
238 | "save_button.on_click(on_save_click)\n",
239 | "\n",
240 | "# initialize values\n",
241 | "on_outline_change({\"new\": outline_slider.value})\n",
242 | "on_alpha_change({\"new\": alpha_dropdown.value})"
243 | ]
244 | },
245 | {
246 | "cell_type": "markdown",
247 | "id": "7e61aaaf-427f-49a2-a484-40404d17a7af",
248 | "metadata": {},
249 | "source": [
250 | "## Display All"
251 | ]
252 | },
253 | {
254 | "cell_type": "code",
255 | "execution_count": null,
256 | "id": "18c02aeb-ffbf-478c-8690-c3046aa4602a",
257 | "metadata": {},
258 | "outputs": [],
259 | "source": [
260 | "# organize controls\n",
261 | "mosaic_row = widgets.HBox([mosaic_text, help_button])\n",
262 | "controls_row1 = widgets.HBox([outline_slider, alpha_dropdown])\n",
263 | "controls_row2 = widgets.HBox([glossy_dropdown, dark_checkbox, save_button])\n",
264 | "\n",
265 | "# create main layout\n",
266 | "controls = widgets.VBox([mosaic_row, controls_row1, controls_row2, output])\n",
267 | "\n",
268 | "# display everything\n",
269 | "display(widgets.VBox([controls, nv]))"
270 | ]
271 | }
272 | ],
273 | "metadata": {},
274 | "nbformat": 4,
275 | "nbformat_minor": 5
276 | }
277 |
--------------------------------------------------------------------------------
/examples/on_event.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "93bdb4d8-14a4-42ba-b80f-554f823c9c6a",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from ipyniivue import NiiVue, download_dataset\n",
11 | "from ipyniivue.download_dataset import DATA_FOLDER\n",
12 | "\n",
13 | "# GitHub API URL for the base folder\n",
14 | "BASE_API_URL = \"https://niivue.com/demos/images/\""
15 | ]
16 | },
17 | {
18 | "cell_type": "code",
19 | "execution_count": null,
20 | "id": "3138317c",
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "# Download data for example\n",
25 | "download_dataset(\n",
26 | " BASE_API_URL,\n",
27 | " DATA_FOLDER,\n",
28 | " files=[\n",
29 | " \"mni152.nii.gz\",\n",
30 | " \"hippo.nii.gz\",\n",
31 | " ],\n",
32 | ")"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "7dea48af-cb5f-4a1c-afe4-46ae3c83cd1d",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "from IPython.display import display\n",
43 | "from ipywidgets import Output\n",
44 | "\n",
45 | "from ipyniivue import SliceType\n",
46 | "\n",
47 | "out = Output()\n",
48 | "display(out)\n",
49 | "\n",
50 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
51 | "\n",
52 | "\n",
53 | "@nv.on_image_loaded\n",
54 | "def on_image_loaded(volume):\n",
55 | " \"\"\"\n",
56 | " Event handler called when an image is loaded.\n",
57 | "\n",
58 | " Parameters\n",
59 | " ----------\n",
60 | " volume : ipyniivue.Volume\n",
61 | " The loaded image volume.\n",
62 | " \"\"\"\n",
63 | " with out:\n",
64 | " print(\"Image loaded:\", volume)"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "id": "d70957d1-9fa5-4810-82fc-c3be4b48d17b",
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "# Load volumes\n",
75 | "nv.load_volumes(\n",
76 | " [\n",
77 | " {\n",
78 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
79 | " \"colormap\": \"gray\",\n",
80 | " \"visible\": True,\n",
81 | " \"opacity\": 1.0,\n",
82 | " },\n",
83 | " {\n",
84 | " \"path\": DATA_FOLDER / \"hippo.nii.gz\",\n",
85 | " \"colormap\": \"red\",\n",
86 | " \"visible\": True,\n",
87 | " \"opacity\": 1,\n",
88 | " },\n",
89 | " ]\n",
90 | ")\n",
91 | "\n",
92 | "# Display the widget\n",
93 | "nv"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "id": "a8f78736-184d-43a2-b93f-fc8fe5d899e7",
100 | "metadata": {},
101 | "outputs": [],
102 | "source": [
103 | "nv.add_volume(\n",
104 | " {\n",
105 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
106 | " \"colormap\": \"gray\",\n",
107 | " \"visible\": True,\n",
108 | " \"opacity\": 1.0,\n",
109 | " }\n",
110 | ")"
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "id": "afad59f1-1cf1-4e95-8df3-8a0e1f155916",
117 | "metadata": {},
118 | "outputs": [],
119 | "source": [
120 | "out"
121 | ]
122 | }
123 | ],
124 | "metadata": {},
125 | "nbformat": 4,
126 | "nbformat_minor": 5
127 | }
128 |
--------------------------------------------------------------------------------
/examples/prototypes/meshes_(GIfTI, FreeSurfer, MZ3, OBJ, STL, legacy VTK).ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from pathlib import Path\n",
10 | "\n",
11 | "import ipyniivue\n",
12 | "from ipyniivue import NiiVue, download_dataset\n",
13 | "\n",
14 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
15 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
16 | ]
17 | },
18 | {
19 | "cell_type": "code",
20 | "execution_count": null,
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "# Download data for example\n",
25 | "download_dataset(\n",
26 | " BASE_API_URL,\n",
27 | " DATA_FOLDER,\n",
28 | " files=[\n",
29 | " \"BrainMesh_ICBM152.lh.mz3\",\n",
30 | " \"CIT168.mz3\",\n",
31 | " ],\n",
32 | ")"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "# based on https://github.com/niivue/ipyniivue/blob/main/original_gallery.md#meshes-gifti-freesurfer-mz3-obj-stl-legacy-vtk\n",
42 | "nv = NiiVue(\n",
43 | " show_3d_crosshair=True, back_color=(1, 1, 1, 1), mesh_xray=0.3, is_colorbar=True\n",
44 | ")\n",
45 | "\n",
46 | "nv.load_meshes(\n",
47 | " [\n",
48 | " {\n",
49 | " \"path\": DATA_FOLDER / \"BrainMesh_ICBM152.lh.mz3\",\n",
50 | " \"rgba255\": [222, 164, 164, 255],\n",
51 | " },\n",
52 | " {\"path\": DATA_FOLDER / \"CIT168.mz3\", \"rgba255\": [0, 0, 255, 255]},\n",
53 | " ]\n",
54 | ")\n",
55 | "\n",
56 | "nv\n",
57 | "\n",
58 | "# nv1.setMeshShader(nv1.meshes[0].id, \"Outline\"); does not seem to exist yet."
59 | ]
60 | }
61 | ],
62 | "metadata": {},
63 | "nbformat": 4,
64 | "nbformat_minor": 4
65 | }
66 |
--------------------------------------------------------------------------------
/examples/prototypes/torso_regions.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "1794ac1d-e7e7-405b-9d83-8a61082431a8",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from pathlib import Path\n",
11 | "\n",
12 | "import ipyniivue\n",
13 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
14 | "\n",
15 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
16 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "id": "6e7d22d8-9ed3-4ad3-8f21-cb2f33292ae4",
23 | "metadata": {},
24 | "outputs": [],
25 | "source": [
26 | "# Download data for example\n",
27 | "download_dataset(\n",
28 | " BASE_API_URL,\n",
29 | " DATA_FOLDER,\n",
30 | " files=[\n",
31 | " \"torso.nii.gz\",\n",
32 | " ],\n",
33 | ")"
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": null,
39 | "id": "71c2f1ec-fdbf-4aae-8d09-77fd5988e11c",
40 | "metadata": {},
41 | "outputs": [],
42 | "source": [
43 | "# based on https://github.com/niivue/ipyniivue/blob/main/original_gallery.md#torso-regions\n",
44 | "\n",
45 | "volumes = [\n",
46 | " {\n",
47 | " \"path\": DATA_FOLDER / \"torso.nii.gz\",\n",
48 | " \"colormap\": (0, 0, 0, 0),\n",
49 | " \"visible\": True,\n",
50 | " \"opacity\": 1.0,\n",
51 | " },\n",
52 | "]\n",
53 | "\n",
54 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
55 | "nv.load_volumes(volumes)\n",
56 | "\n",
57 | "nv"
58 | ]
59 | },
60 | {
61 | "cell_type": "code",
62 | "execution_count": null,
63 | "id": "593d466e-6bf6-47df-ba63-23b627f41da0",
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "cmap = {\n",
68 | " \"R\": [0, 0, 185, 185, 252, 0, 103, 216, 127, 127, 0, 222],\n",
69 | " \"G\": [0, 20, 102, 102, 0, 255, 76, 132, 0, 127, 255, 154],\n",
70 | " \"B\": [0, 152, 83, 83, 0, 0, 71, 105, 127, 0, 255, 132],\n",
71 | " \"A\": [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],\n",
72 | " \"labels\": [\n",
73 | " \"background\",\n",
74 | " \"1spleen\",\n",
75 | " \"2kidneyR\",\n",
76 | " \"3kidneyL\",\n",
77 | " \"4gallbladder\",\n",
78 | " \"5esophagus\",\n",
79 | " \"6Liver\",\n",
80 | " \"7stomach\",\n",
81 | " \"8aorta\",\n",
82 | " \"9inferiorvenacava\",\n",
83 | " \"10pancreas\",\n",
84 | " \"11bladder\",\n",
85 | " ],\n",
86 | "}\n",
87 | "nv.setDrawColormap(cmap);"
88 | ]
89 | }
90 | ],
91 | "metadata": {},
92 | "nbformat": 4,
93 | "nbformat_minor": 5
94 | }
95 |
--------------------------------------------------------------------------------
/examples/prototypes/trajectory.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "from pathlib import Path\n",
10 | "\n",
11 | "import ipyniivue\n",
12 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
13 | "\n",
14 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
15 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
16 | ]
17 | },
18 | {
19 | "cell_type": "code",
20 | "execution_count": null,
21 | "metadata": {},
22 | "outputs": [],
23 | "source": [
24 | "# Download data for example\n",
25 | "download_dataset(\n",
26 | " BASE_API_URL,\n",
27 | " DATA_FOLDER,\n",
28 | " files=[\"mni152.nii.gz\", \"dpsv.trx\"],\n",
29 | ")"
30 | ]
31 | },
32 | {
33 | "cell_type": "code",
34 | "execution_count": null,
35 | "metadata": {},
36 | "outputs": [],
37 | "source": [
38 | "# based on https://github.com/niivue/ipyniivue/blob/main/original_gallery.md#tractography-tck-trk-trx-vtk\n",
39 | "\n",
40 | "volumes = [\n",
41 | " {\n",
42 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
43 | " \"colormap\": \"gray\",\n",
44 | " \"visible\": True,\n",
45 | " \"opacity\": 1.0,\n",
46 | " },\n",
47 | "]\n",
48 | "\n",
49 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
50 | "# nv.load_volumes(volumes)\n",
51 | "\n",
52 | "\n",
53 | "nv.load_meshes(\n",
54 | " [\n",
55 | " {\n",
56 | " \"path\": DATA_FOLDER / \"dpsv.trx\",\n",
57 | " \"rgba255\": [0, 142, 0, 255],\n",
58 | " }\n",
59 | " ]\n",
60 | ")\n",
61 | "\n",
62 | "\n",
63 | "nv"
64 | ]
65 | }
66 | ],
67 | "metadata": {},
68 | "nbformat": 4,
69 | "nbformat_minor": 4
70 | }
71 |
--------------------------------------------------------------------------------
/examples/saving.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "fc4fd25c-ba20-49b0-aa4d-ce02266553fc",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from pathlib import Path\n",
11 | "\n",
12 | "import ipyniivue\n",
13 | "from ipyniivue import NiiVue, SliceType, download_dataset\n",
14 | "\n",
15 | "# GitHub API URL for the base folder\n",
16 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
17 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
18 | ]
19 | },
20 | {
21 | "cell_type": "code",
22 | "execution_count": null,
23 | "id": "29230e89-1d68-4d8d-bbb8-2f688c690590",
24 | "metadata": {},
25 | "outputs": [],
26 | "source": [
27 | "# Download data for example\n",
28 | "download_dataset(\n",
29 | " BASE_API_URL,\n",
30 | " DATA_FOLDER,\n",
31 | " files=[\n",
32 | " \"mni152.nii.gz\",\n",
33 | " \"hippo.nii.gz\",\n",
34 | " ],\n",
35 | ")"
36 | ]
37 | },
38 | {
39 | "cell_type": "code",
40 | "execution_count": null,
41 | "id": "4aa5999b-45e6-42a6-a124-6953572a881b",
42 | "metadata": {},
43 | "outputs": [],
44 | "source": [
45 | "# based on https://niivue.github.io/niivue/features/basic.multiplanar.html\n",
46 | "\n",
47 | "volumes = [\n",
48 | " {\n",
49 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
50 | " \"colormap\": \"gray\",\n",
51 | " \"visible\": True,\n",
52 | " \"opacity\": 1.0,\n",
53 | " },\n",
54 | " {\n",
55 | " \"path\": DATA_FOLDER / \"hippo.nii.gz\",\n",
56 | " \"colormap\": \"red\",\n",
57 | " \"visible\": True,\n",
58 | " \"opacity\": 1,\n",
59 | " },\n",
60 | "]\n",
61 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
62 | "nv.load_volumes(volumes)\n",
63 | "nv"
64 | ]
65 | },
66 | {
67 | "cell_type": "code",
68 | "execution_count": null,
69 | "id": "d5bd3f6b-145a-4a31-9328-6acbde412ce0",
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "nv.save_document(\"test.nvd\")"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "id": "7cff3d5a-b2f7-465a-9b1a-b5e456ad9192",
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "nv.save_html(\"test.html\")"
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": null,
89 | "id": "5d9ef2a7-d293-4de9-8e43-c8ca01f1f45f",
90 | "metadata": {},
91 | "outputs": [],
92 | "source": [
93 | "nv.save_image()"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "id": "6859daba-d0fd-40a0-98e3-1ba134e1e3e4",
100 | "metadata": {},
101 | "outputs": [],
102 | "source": [
103 | "nv.save_scene(\"test.png\")"
104 | ]
105 | }
106 | ],
107 | "metadata": {},
108 | "nbformat": 4,
109 | "nbformat_minor": 5
110 | }
111 |
--------------------------------------------------------------------------------
/examples/segment.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "0c478d8f-80c7-477d-bc80-c57196bb6469",
6 | "metadata": {},
7 | "source": [
8 | "# Import Necessary Modules"
9 | ]
10 | },
11 | {
12 | "cell_type": "code",
13 | "execution_count": null,
14 | "id": "548a4032-863a-4e4b-82db-098fd4ffda94",
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "import json\n",
19 | "import pathlib\n",
20 | "\n",
21 | "import ipywidgets as widgets\n",
22 | "\n",
23 | "import ipyniivue\n",
24 | "from ipyniivue import NiiVue, ShowRender, SliceType, download_dataset"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "3e06336d-1b09-4318-8d65-7524e1c08ccd",
30 | "metadata": {},
31 | "source": [
32 | "# Download Required Data"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "ec54c0c9-677d-49a0-af2f-9133477b2771",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
43 | "\n",
44 | "download_dataset(\n",
45 | " \"https://niivue.com/demos/images/\",\n",
46 | " dest_folder=DATA_FOLDER,\n",
47 | " files=[\n",
48 | " \"mni152.nii.gz\",\n",
49 | " \"mni152_pveseg.nii.gz\",\n",
50 | " ],\n",
51 | ")"
52 | ]
53 | },
54 | {
55 | "cell_type": "markdown",
56 | "id": "e1c9bf8a-e57b-4b09-b000-ef63ae59fb24",
57 | "metadata": {},
58 | "source": [
59 | "# Setup NiiVue instance"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": null,
65 | "id": "ad3925f0-ff97-415a-bff5-919b93464c13",
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "nv = NiiVue(\n",
70 | " back_color=(0.3, 0.3, 0.3, 1),\n",
71 | " show_3d_crosshair=True,\n",
72 | ")\n",
73 | "nv.set_radiological_convention(False)\n",
74 | "\n",
75 | "nv.load_volumes(\n",
76 | " [\n",
77 | " {\"path\": DATA_FOLDER / \"mni152.nii.gz\"},\n",
78 | " {\"path\": DATA_FOLDER / \"mni152_pveseg.nii.gz\", \"opacity\": 0.5},\n",
79 | " ]\n",
80 | ")\n",
81 | "\n",
82 | "nv.opts.multiplanar_show_render = ShowRender.ALWAYS\n",
83 | "nv.set_slice_type(SliceType.MULTIPLANAR)\n",
84 | "nv.graph.auto_size_multiplanar = True"
85 | ]
86 | },
87 | {
88 | "cell_type": "markdown",
89 | "id": "3d276b99-ef28-4892-8fa9-ee0e07333cbf",
90 | "metadata": {},
91 | "source": [
92 | "# Add widgets"
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": null,
98 | "id": "59a5aa81-7297-465d-9fb1-b39596d14245",
99 | "metadata": {},
100 | "outputs": [],
101 | "source": [
102 | "background_checkbox = widgets.Checkbox(\n",
103 | " value=True,\n",
104 | " description=\"Background\",\n",
105 | ")\n",
106 | "\n",
107 | "mask_checkbox = widgets.Checkbox(\n",
108 | " value=False,\n",
109 | " description=\"Mask\",\n",
110 | ")\n",
111 | "\n",
112 | "smooth_checkbox = widgets.Checkbox(\n",
113 | " value=True,\n",
114 | " description=\"Smooth\",\n",
115 | ")\n",
116 | "\n",
117 | "opacity_slider = widgets.IntSlider(\n",
118 | " value=127,\n",
119 | " min=1,\n",
120 | " max=255,\n",
121 | " description=\"Opacity\",\n",
122 | ")"
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "id": "75802028-0a3c-4fb5-aaaf-4329a7c72b89",
128 | "metadata": {},
129 | "source": [
130 | "# Define Event Handlers"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "id": "e57d8338-b6c4-4afc-a2dc-b771d503a1ac",
137 | "metadata": {},
138 | "outputs": [],
139 | "source": [
140 | "def on_opacity_change(change):\n",
141 | " \"\"\"Adjust the opacity of the second volume.\"\"\"\n",
142 | " nv.set_opacity(1, change[\"new\"] / 255)\n",
143 | "\n",
144 | "\n",
145 | "def on_background_checkbox_change(change):\n",
146 | " \"\"\"Show or hide the background volume.\"\"\"\n",
147 | " nv.volumes[0].opacity = 1.0 if change[\"new\"] else 0.0\n",
148 | "\n",
149 | "\n",
150 | "def on_mask_checkbox_change(change):\n",
151 | " \"\"\"Set is_alpha_clip_dark.\"\"\"\n",
152 | " nv.opts.is_alpha_clip_dark = change[\"new\"]\n",
153 | "\n",
154 | "\n",
155 | "def on_smooth_checkbox_change(change):\n",
156 | " \"\"\"Set interpolation.\"\"\"\n",
157 | " nv.set_interpolation(not change[\"new\"])\n",
158 | "\n",
159 | "\n",
160 | "opacity_slider.observe(on_opacity_change, names=\"value\")\n",
161 | "background_checkbox.observe(on_background_checkbox_change, names=\"value\")\n",
162 | "mask_checkbox.observe(on_mask_checkbox_change, names=\"value\")\n",
163 | "smooth_checkbox.observe(on_smooth_checkbox_change, names=\"value\")"
164 | ]
165 | },
166 | {
167 | "cell_type": "markdown",
168 | "id": "c542be5f-7e79-45c5-8dd4-04aed3e41905",
169 | "metadata": {},
170 | "source": [
171 | "# Set Initial Values"
172 | ]
173 | },
174 | {
175 | "cell_type": "code",
176 | "execution_count": null,
177 | "id": "4755b591-db0f-426a-b613-9a04558e8a08",
178 | "metadata": {},
179 | "outputs": [],
180 | "source": [
181 | "on_opacity_change({\"new\": opacity_slider.value})\n",
182 | "on_background_checkbox_change({\"new\": background_checkbox.value})\n",
183 | "on_mask_checkbox_change({\"new\": mask_checkbox.value})\n",
184 | "on_smooth_checkbox_change({\"new\": smooth_checkbox.value})"
185 | ]
186 | },
187 | {
188 | "cell_type": "markdown",
189 | "id": "7e882d8f-57e0-489f-8bf3-51749a9eb6f8",
190 | "metadata": {},
191 | "source": [
192 | "# Handle location changes"
193 | ]
194 | },
195 | {
196 | "cell_type": "code",
197 | "execution_count": null,
198 | "id": "3580f73c-92e7-4ea7-a654-95e5aa4270d7",
199 | "metadata": {},
200 | "outputs": [],
201 | "source": [
202 | "location_label = widgets.HTML(\" \")\n",
203 | "\n",
204 | "\n",
205 | "def handle_location_change(data):\n",
206 | " \"\"\"Update the location label when the crosshair moves.\"\"\"\n",
207 | " location_label.value = \" \" + data[\"string\"]\n",
208 | "\n",
209 | "\n",
210 | "nv.on_location_change(handle_location_change)"
211 | ]
212 | },
213 | {
214 | "cell_type": "markdown",
215 | "id": "5fd0e5bc-55d1-4247-913d-c6546396b6a8",
216 | "metadata": {},
217 | "source": [
218 | "# Handle Custom Colormap Input"
219 | ]
220 | },
221 | {
222 | "cell_type": "code",
223 | "execution_count": null,
224 | "id": "1a3d4517-a7be-43ef-9026-2166fbe0c853",
225 | "metadata": {},
226 | "outputs": [],
227 | "source": [
228 | "colormap_textarea = widgets.Textarea(\n",
229 | " value=\"\"\"{\n",
230 | " \"R\": [0, 0, 120, 175],\n",
231 | " \"G\": [0, 90, 60, 185],\n",
232 | " \"B\": [0, 120, 60, 175],\n",
233 | " \"labels\": [\"air\", \"CSF\", \"gray\", \"white\"]\n",
234 | "}\"\"\",\n",
235 | " description=\"Custom Colormap (JSON)\",\n",
236 | " layout=widgets.Layout(width=\"100%\", height=\"150px\"),\n",
237 | ")\n",
238 | "\n",
239 | "apply_button = widgets.Button(\n",
240 | " description=\"Apply Colormap\",\n",
241 | ")\n",
242 | "\n",
243 | "\n",
244 | "def on_apply_button_click(b):\n",
245 | " \"\"\"Apply custom colormap json.\"\"\"\n",
246 | " json_str = colormap_textarea.value\n",
247 | " try:\n",
248 | " cmap = json.loads(json_str)\n",
249 | " nv.volumes[1].set_colormap_label(cmap)\n",
250 | " except json.JSONDecodeError as e:\n",
251 | " print(\"Invalid JSON format:\", e)\n",
252 | "\n",
253 | "\n",
254 | "apply_button.on_click(on_apply_button_click)\n",
255 | "\n",
256 | "on_apply_button_click(None)"
257 | ]
258 | },
259 | {
260 | "cell_type": "markdown",
261 | "id": "5f66cfe6-aa40-499a-9f68-fd06a2375b1c",
262 | "metadata": {},
263 | "source": [
264 | "# Display all"
265 | ]
266 | },
267 | {
268 | "cell_type": "code",
269 | "execution_count": null,
270 | "id": "cb4ec15a-733b-4af5-a00b-242e2184711f",
271 | "metadata": {},
272 | "outputs": [],
273 | "source": [
274 | "controls = widgets.HBox(\n",
275 | " [\n",
276 | " background_checkbox,\n",
277 | " mask_checkbox,\n",
278 | " smooth_checkbox,\n",
279 | " opacity_slider,\n",
280 | " ]\n",
281 | ")\n",
282 | "\n",
283 | "script_controls = widgets.VBox(\n",
284 | " [\n",
285 | " colormap_textarea,\n",
286 | " apply_button,\n",
287 | " ]\n",
288 | ")\n",
289 | "\n",
290 | "display_widgets = widgets.VBox(\n",
291 | " [\n",
292 | " controls,\n",
293 | " nv,\n",
294 | " location_label,\n",
295 | " script_controls,\n",
296 | " ]\n",
297 | ")\n",
298 | "\n",
299 | "display(display_widgets)"
300 | ]
301 | }
302 | ],
303 | "metadata": {},
304 | "nbformat": 4,
305 | "nbformat_minor": 5
306 | }
307 |
--------------------------------------------------------------------------------
/examples/sync.bidirectional.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "bdfd3b58-76fb-4f37-bc38-71a014830bd1",
6 | "metadata": {},
7 | "source": [
8 | "# Bidirectional Sync\n",
9 | "\n",
10 | "This demo mirrors https://niivue.com/demos/features/sync.bidirectional.html."
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "id": "f6480178-93e9-4e36-b701-d3c99ea45387",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "import ipywidgets as widgets\n",
21 | "from IPython.display import display\n",
22 | "\n",
23 | "from ipyniivue import MultiplanarType, NiiVue"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "id": "2fa54822-6433-433e-ab59-7bd8816b30d9",
29 | "metadata": {},
30 | "source": [
31 | "## Create Three NiiVue Instances"
32 | ]
33 | },
34 | {
35 | "cell_type": "code",
36 | "execution_count": null,
37 | "id": "c9c037aa-d318-46e9-b8ab-138ef4c11ba9",
38 | "metadata": {},
39 | "outputs": [],
40 | "source": [
41 | "nv1 = NiiVue(\n",
42 | " height=400,\n",
43 | " multiplanar_force_render=True,\n",
44 | " back_color=(0.0, 0.0, 0.0, 1.0),\n",
45 | ")\n",
46 | "\n",
47 | "nv2 = NiiVue(\n",
48 | " height=400,\n",
49 | " multiplanar_force_render=True,\n",
50 | " back_color=(0.0, 0.0, 0.0, 1.0),\n",
51 | ")\n",
52 | "\n",
53 | "nv3 = NiiVue(\n",
54 | " height=400,\n",
55 | " multiplanar_force_render=True,\n",
56 | " back_color=(0.0, 0.0, 0.0, 1.0),\n",
57 | ")"
58 | ]
59 | },
60 | {
61 | "cell_type": "markdown",
62 | "id": "1da1475a-de66-4783-8feb-1183373410dc",
63 | "metadata": {},
64 | "source": [
65 | "## Load Volumes"
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": null,
71 | "id": "f0a140d9-6ca6-445e-9ecb-d10d913075fc",
72 | "metadata": {},
73 | "outputs": [],
74 | "source": [
75 | "nv1.load_volumes([{\"url\": \"https://niivue.com/demos/images/pcasl.nii.gz\"}])\n",
76 | "nv2.load_volumes([{\"url\": \"https://niivue.com/demos/images/aal.nii.gz\"}])\n",
77 | "nv3.load_volumes([{\"url\": \"https://niivue.com/demos/images/mni152.nii.gz\"}])"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "id": "a3ece015-96f3-41ae-b451-8af7d7fa6b61",
83 | "metadata": {},
84 | "source": [
85 | "## Create Interactive Controls"
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": null,
91 | "id": "f91cf421-cd58-4f5a-975a-1d83daf0b9b2",
92 | "metadata": {},
93 | "outputs": [],
94 | "source": [
95 | "layout_dropdown = widgets.Dropdown(\n",
96 | " options=[\n",
97 | " (\"Auto\", MultiplanarType.AUTO),\n",
98 | " (\"Column\", MultiplanarType.COLUMN),\n",
99 | " (\"Grid\", MultiplanarType.GRID),\n",
100 | " (\"Row\", MultiplanarType.ROW),\n",
101 | " ],\n",
102 | " value=MultiplanarType.AUTO,\n",
103 | " description=\"Layout:\",\n",
104 | ")\n",
105 | "\n",
106 | "sync_dropdown = widgets.Dropdown(\n",
107 | " options=[\n",
108 | " (\"Sync Disabled\", 0),\n",
109 | " (\"Sync 2D\", 1),\n",
110 | " (\"Sync 3D\", 2),\n",
111 | " (\"Sync 2D and 3D\", 3),\n",
112 | " ],\n",
113 | " value=3,\n",
114 | " description=\"Broadcast:\",\n",
115 | ")\n",
116 | "\n",
117 | "status1 = widgets.HTML(value=\" \")\n",
118 | "status2 = widgets.HTML(value=\" \")\n",
119 | "status3 = widgets.HTML(value=\" \")"
120 | ]
121 | },
122 | {
123 | "cell_type": "markdown",
124 | "id": "82b36505-e723-4e53-b0f2-83920d244639",
125 | "metadata": {},
126 | "source": [
127 | "## Setup Event Handlers"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "id": "da739796-9153-4955-a4d7-8b875e7ae555",
134 | "metadata": {},
135 | "outputs": [],
136 | "source": [
137 | "def on_layout_change(change):\n",
138 | " \"\"\"Handle layout dropdown changes.\"\"\"\n",
139 | " new_layout = change[\"new\"]\n",
140 | " nv1.opts.multiplanar_layout = new_layout\n",
141 | " nv2.opts.multiplanar_layout = new_layout\n",
142 | " nv3.opts.multiplanar_layout = new_layout\n",
143 | "\n",
144 | "\n",
145 | "def on_sync_change(change):\n",
146 | " \"\"\"Handle sync mode dropdown changes.\"\"\"\n",
147 | " v = change[\"new\"]\n",
148 | " is_2d = False\n",
149 | " is_3d = False\n",
150 | "\n",
151 | " if v % 2: # If odd (1 or 3)\n",
152 | " is_2d = True\n",
153 | " if v > 1: # If 2 or 3\n",
154 | " is_3d = True\n",
155 | "\n",
156 | " nv1.broadcast_to([nv2, nv3], {\"2d\": is_2d, \"3d\": is_3d})\n",
157 | " nv2.broadcast_to([nv1, nv3], {\"2d\": is_2d, \"3d\": is_3d})\n",
158 | " nv3.broadcast_to([nv1, nv2], {\"2d\": is_2d, \"3d\": is_3d})\n",
159 | "\n",
160 | "\n",
161 | "@nv1.on_location_change\n",
162 | "def handle_location1(data):\n",
163 | " \"\"\"Handle location string.\"\"\"\n",
164 | " status1.value = f\" {data['string']}\"\n",
165 | "\n",
166 | "\n",
167 | "@nv2.on_location_change\n",
168 | "def handle_location2(data):\n",
169 | " \"\"\"Handle location string.\"\"\"\n",
170 | " status2.value = f\" {data['string']}\"\n",
171 | "\n",
172 | "\n",
173 | "@nv3.on_location_change\n",
174 | "def handle_location3(data):\n",
175 | " \"\"\"Handle location string.\"\"\"\n",
176 | " status3.value = f\" {data['string']}\"\n",
177 | "\n",
178 | "\n",
179 | "layout_dropdown.observe(on_layout_change, names=\"value\")\n",
180 | "sync_dropdown.observe(on_sync_change, names=\"value\")\n",
181 | "\n",
182 | "on_sync_change({\"new\": sync_dropdown.value})"
183 | ]
184 | },
185 | {
186 | "cell_type": "markdown",
187 | "id": "08b6e470-ba7e-48be-9687-41edcbd5e6fd",
188 | "metadata": {},
189 | "source": [
190 | "## Display All"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "id": "8462e01e-6265-4580-b59b-58c63138b432",
197 | "metadata": {},
198 | "outputs": [],
199 | "source": [
200 | "controls = widgets.HBox([layout_dropdown, sync_dropdown])\n",
201 | "\n",
202 | "viewers = widgets.GridspecLayout(1, 3, height=\"400px\")\n",
203 | "viewers[0, 0] = nv1\n",
204 | "viewers[0, 1] = nv2\n",
205 | "viewers[0, 2] = nv3\n",
206 | "\n",
207 | "status_row = widgets.HBox([status1, status2, status3])\n",
208 | "\n",
209 | "display(widgets.VBox([controls, viewers, status_row]))"
210 | ]
211 | }
212 | ],
213 | "metadata": {},
214 | "nbformat": 4,
215 | "nbformat_minor": 5
216 | }
217 |
--------------------------------------------------------------------------------
/examples/tracts.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Import necessary modules"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import pathlib\n",
17 | "\n",
18 | "import ipywidgets as widgets\n",
19 | "\n",
20 | "import ipyniivue\n",
21 | "from ipyniivue import NiiVue, SliceType, download_dataset"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "# Download required data"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "metadata": {},
35 | "outputs": [],
36 | "source": [
37 | "DATA_FOLDER = pathlib.Path(ipyniivue.__file__).parent / \"images\"\n",
38 | "\n",
39 | "download_dataset(\n",
40 | " \"https://niivue.com/demos/images/\",\n",
41 | " dest_folder=DATA_FOLDER,\n",
42 | " files=[\n",
43 | " \"mni152.nii.gz\",\n",
44 | " \"dpsv.trx\",\n",
45 | " ],\n",
46 | ")"
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {},
52 | "source": [
53 | "# Create NiiVue instance"
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "execution_count": null,
59 | "metadata": {},
60 | "outputs": [],
61 | "source": [
62 | "nv = NiiVue(show_3d_crosshair=True, back_color=(0.8, 0.8, 1, 1))\n",
63 | "\n",
64 | "nv.opts.is_colorbar = True\n",
65 | "nv.set_slice_type(SliceType.RENDER)\n",
66 | "nv.set_clip_plane(-0.1, 270, 0);"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "# Load the volume and mesh data"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "nv.load_volumes(\n",
83 | " [\n",
84 | " {\"path\": DATA_FOLDER / \"mni152.nii.gz\"},\n",
85 | " ]\n",
86 | ")\n",
87 | "\n",
88 | "nv.load_meshes(\n",
89 | " [\n",
90 | " {\"path\": DATA_FOLDER / \"dpsv.trx\", \"rgba255\": [0, 142, 0, 255]},\n",
91 | " ]\n",
92 | ")\n",
93 | "\n",
94 | "nv.meshes[0].colormap = \"blue\"\n",
95 | "nv.meshes[0].rgba255 = (0, 255, 255, 255)"
96 | ]
97 | },
98 | {
99 | "cell_type": "markdown",
100 | "metadata": {},
101 | "source": [
102 | "# Create sliders and dropdowns"
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "execution_count": null,
108 | "metadata": {},
109 | "outputs": [],
110 | "source": [
111 | "radius_slider = widgets.IntSlider(\n",
112 | " value=0,\n",
113 | " min=0,\n",
114 | " max=20,\n",
115 | " description=\"Radius\",\n",
116 | ")\n",
117 | "\n",
118 | "length_slider = widgets.IntSlider(\n",
119 | " value=3,\n",
120 | " min=1,\n",
121 | " max=80,\n",
122 | " description=\"Length\",\n",
123 | ")\n",
124 | "\n",
125 | "dither_slider = widgets.IntSlider(\n",
126 | " value=1,\n",
127 | " min=0,\n",
128 | " max=10,\n",
129 | " description=\"Dither\",\n",
130 | ")\n",
131 | "\n",
132 | "fiber_coloration_options = [\n",
133 | " (\"Global direction\", \"Global\"),\n",
134 | " (\"Local direction\", \"Local\"),\n",
135 | " (\"Fixed\", \"Fixed\"),\n",
136 | " (\"First Per Vertex Type (if available)\", \"DPV0\"),\n",
137 | " (\"First Per Streamline Type (if available)\", \"DPS0\"),\n",
138 | "]\n",
139 | "fiber_color_dropdown = widgets.Dropdown(\n",
140 | " options=fiber_coloration_options,\n",
141 | " value=\"Global\",\n",
142 | " description=\"Fiber Coloration:\",\n",
143 | ")\n",
144 | "\n",
145 | "fiber_reduction_options = [(\"100%\", 1), (\"50%\", 2), (\"25%\", 4), (\"10%\", 10)]\n",
146 | "fiber_reduction_dropdown = widgets.Dropdown(\n",
147 | " options=fiber_reduction_options,\n",
148 | " value=1,\n",
149 | " description=\"Fiber Reduction\",\n",
150 | ")"
151 | ]
152 | },
153 | {
154 | "cell_type": "markdown",
155 | "metadata": {},
156 | "source": [
157 | "# Set up callbacks"
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "def on_radius_change(change):\n",
167 | " \"\"\"Set fiber radius.\"\"\"\n",
168 | " nv.set_mesh_property(nv.meshes[0].id, \"fiber_radius\", change[\"new\"] * 0.1)\n",
169 | "\n",
170 | "\n",
171 | "radius_slider.observe(on_radius_change, names=\"value\")\n",
172 | "\n",
173 | "\n",
174 | "def on_length_change(change):\n",
175 | " \"\"\"Set fiber length.\"\"\"\n",
176 | " nv.set_mesh_property(nv.meshes[0].id, \"fiber_length\", change[\"new\"])\n",
177 | "\n",
178 | "\n",
179 | "length_slider.observe(on_length_change, names=\"value\")\n",
180 | "\n",
181 | "\n",
182 | "def on_dither_change(change):\n",
183 | " \"\"\"Set fiber dither.\"\"\"\n",
184 | " nv.set_mesh_property(nv.meshes[0].id, \"fiber_dither\", change[\"new\"] * 0.1)\n",
185 | "\n",
186 | "\n",
187 | "dither_slider.observe(on_dither_change, names=\"value\")\n",
188 | "\n",
189 | "\n",
190 | "def on_fiber_color_change(change):\n",
191 | " \"\"\"Set fiber color.\"\"\"\n",
192 | " nv.set_mesh_property(nv.meshes[0].id, \"fiber_color\", change[\"new\"])\n",
193 | "\n",
194 | "\n",
195 | "fiber_color_dropdown.observe(on_fiber_color_change, names=\"value\")\n",
196 | "\n",
197 | "\n",
198 | "def on_fiber_reduction_change(change):\n",
199 | " \"\"\"Set fiber decimation stride.\"\"\"\n",
200 | " nv.set_mesh_property(nv.meshes[0].id, \"fiber_decimation_stride\", change[\"new\"])\n",
201 | "\n",
202 | "\n",
203 | "fiber_reduction_dropdown.observe(on_fiber_reduction_change, names=\"value\")"
204 | ]
205 | },
206 | {
207 | "cell_type": "markdown",
208 | "metadata": {},
209 | "source": [
210 | "# Display all"
211 | ]
212 | },
213 | {
214 | "cell_type": "code",
215 | "execution_count": null,
216 | "metadata": {},
217 | "outputs": [],
218 | "source": [
219 | "controls = widgets.VBox(\n",
220 | " [\n",
221 | " radius_slider,\n",
222 | " length_slider,\n",
223 | " dither_slider,\n",
224 | " fiber_color_dropdown,\n",
225 | " fiber_reduction_dropdown,\n",
226 | " ]\n",
227 | ")\n",
228 | "\n",
229 | "widgets.VBox(\n",
230 | " [\n",
231 | " controls,\n",
232 | " nv,\n",
233 | " ]\n",
234 | ")"
235 | ]
236 | }
237 | ],
238 | "metadata": {},
239 | "nbformat": 4,
240 | "nbformat_minor": 4
241 | }
242 |
--------------------------------------------------------------------------------
/examples/widgets.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "id": "f26da1e3-877f-4893-8baa-8b9460601205",
7 | "metadata": {},
8 | "outputs": [],
9 | "source": [
10 | "from pathlib import Path\n",
11 | "\n",
12 | "import ipywidgets\n",
13 | "\n",
14 | "import ipyniivue\n",
15 | "from ipyniivue import NiiVue, SliceType, WidgetObserver, download_dataset\n",
16 | "\n",
17 | "# GitHub API URL for the base folder\n",
18 | "BASE_API_URL = \"https://niivue.com/demos/images/\"\n",
19 | "DATA_FOLDER = Path(ipyniivue.__file__).parent / \"images\""
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "id": "37b8beb5-32d3-4d0b-8294-47889185f60d",
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "# Download data for example\n",
30 | "download_dataset(\n",
31 | " BASE_API_URL,\n",
32 | " DATA_FOLDER,\n",
33 | " files=[\n",
34 | " \"mni152.nii.gz\",\n",
35 | " \"hippo.nii.gz\",\n",
36 | " ],\n",
37 | ")"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": null,
43 | "id": "cda3a5f6-5bed-4190-82a7-057e17fa6635",
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "volumes = [\n",
48 | " {\n",
49 | " \"path\": DATA_FOLDER / \"mni152.nii.gz\",\n",
50 | " \"colormap\": \"gray\",\n",
51 | " \"visible\": True,\n",
52 | " \"opacity\": 1.0,\n",
53 | " },\n",
54 | " {\n",
55 | " \"path\": DATA_FOLDER / \"hippo.nii.gz\",\n",
56 | " \"colormap\": \"red\",\n",
57 | " \"visible\": True,\n",
58 | " \"opacity\": 1.0,\n",
59 | " },\n",
60 | "]\n",
61 | "\n",
62 | "nv = NiiVue(slice_type=SliceType.MULTIPLANAR)\n",
63 | "nv.load_volumes(volumes)\n",
64 | "\n",
65 | "widgetArray = []\n",
66 | "\n",
67 | "widget_slice_type = {\n",
68 | " \"widget\": ipywidgets.RadioButtons(\n",
69 | " options=[\n",
70 | " (\"Axial\", 0),\n",
71 | " (\"Coronal\", 1),\n",
72 | " (\"Sagittal\", 2),\n",
73 | " (\"Multiplanar\", 3),\n",
74 | " (\"Render\", 4),\n",
75 | " ],\n",
76 | " value=3,\n",
77 | " description=\"Slice Type:\",\n",
78 | " ),\n",
79 | " \"obj\": nv,\n",
80 | " \"attribute\": \"slice_type\",\n",
81 | "}\n",
82 | "widgetArray.append(widget_slice_type)\n",
83 | "\n",
84 | "widget_scan_opacity = {\n",
85 | " \"widget\": ipywidgets.FloatSlider(\n",
86 | " value=1.0,\n",
87 | " min=0.0,\n",
88 | " max=1.0,\n",
89 | " step=0.1,\n",
90 | " description=\"Scan Opacity:\",\n",
91 | " orientation=\"horizontal\",\n",
92 | " ),\n",
93 | " \"obj\": nv.volumes[0],\n",
94 | " \"attribute\": \"opacity\",\n",
95 | "}\n",
96 | "widgetArray.append(widget_scan_opacity)\n",
97 | "\n",
98 | "widget_hippo_opacity = {\n",
99 | " \"widget\": ipywidgets.FloatSlider(\n",
100 | " value=1.0,\n",
101 | " min=0.0,\n",
102 | " max=1.0,\n",
103 | " step=0.1,\n",
104 | " description=\"Hippocampus Opacity:\",\n",
105 | " orientation=\"horizontal\",\n",
106 | " ),\n",
107 | " \"obj\": nv.volumes[1],\n",
108 | " \"attribute\": \"opacity\",\n",
109 | "}\n",
110 | "widgetArray.append(widget_hippo_opacity)\n",
111 | "\n",
112 | "widget_scan_colormap = {\n",
113 | " \"widget\": ipywidgets.Select(\n",
114 | " options=[\"Gray\", \"Red\", \"Blue\", \"Green\"],\n",
115 | " value=\"Gray\",\n",
116 | " description=\"Scan Colormap:\",\n",
117 | " ),\n",
118 | " \"obj\": nv.volumes[0],\n",
119 | " \"attribute\": \"colormap\",\n",
120 | "}\n",
121 | "widgetArray.append(widget_scan_colormap)\n",
122 | "\n",
123 | "widget_hippo_colormap = {\n",
124 | " \"widget\": ipywidgets.Select(\n",
125 | " options=[\"Red\", \"Blue\", \"Green\", \"Gray\"],\n",
126 | " value=\"Red\",\n",
127 | " description=\"Hippocampus Colormap:\",\n",
128 | " ),\n",
129 | " \"obj\": nv.volumes[1],\n",
130 | " \"attribute\": \"colormap\",\n",
131 | "}\n",
132 | "widgetArray.append(widget_hippo_colormap)\n",
133 | "\n",
134 | "for widget in widgetArray:\n",
135 | " WidgetObserver(**widget)"
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": null,
141 | "id": "cce13351-2f24-49fb-b8f6-4bac07f7b2dc",
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "nv"
146 | ]
147 | },
148 | {
149 | "cell_type": "code",
150 | "execution_count": null,
151 | "id": "66b82692-c7b0-471d-8134-f399cf9ac399",
152 | "metadata": {},
153 | "outputs": [],
154 | "source": [
155 | "for widget in widgetArray:\n",
156 | " display(widget[\"widget\"])"
157 | ]
158 | }
159 | ],
160 | "metadata": {},
161 | "nbformat": 4,
162 | "nbformat_minor": 5
163 | }
164 |
--------------------------------------------------------------------------------
/examples/worldspace.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "3e92e45e-bd2f-47ac-8470-c4ce892f58a1",
6 | "metadata": {},
7 | "source": [
8 | "# World Space Viewer\n",
9 | "This example demonstrates world space navigation with various orientation options\n",
10 | "\n",
11 | "See https://niivue.com/demos/features/worldspace.html for mirror."
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "37ba744a-9570-4a52-b8f0-8add586fd9e8",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "import ipywidgets as widgets\n",
22 | "from IPython.display import display\n",
23 | "\n",
24 | "from ipyniivue import DragMode, NiiVue"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "5f665aa5-188f-4ddc-b34b-654b7de94c34",
30 | "metadata": {},
31 | "source": [
32 | "## Create NiiVue Instance"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "7f25381f-9a83-4a02-9eec-b4ac90985c53",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "nv = NiiVue(\n",
43 | " drag_and_drop_enabled=True,\n",
44 | " back_color=(1, 1, 1, 1),\n",
45 | " show_3d_crosshair=True,\n",
46 | ")\n",
47 | "\n",
48 | "nv.set_slice_mm(True)\n",
49 | "nv.set_radiological_convention(False)\n",
50 | "nv.set_slice_type(nv.opts.slice_type.MULTIPLANAR)\n",
51 | "\n",
52 | "nv.load_volumes(\n",
53 | " [\n",
54 | " {\n",
55 | " \"url\": \"https://niivue.com/demos/images/FLAIR.nii.gz\",\n",
56 | " \"colormap\": \"gray\",\n",
57 | " \"opacity\": 1.0,\n",
58 | " \"visible\": True,\n",
59 | " }\n",
60 | " ]\n",
61 | ")"
62 | ]
63 | },
64 | {
65 | "cell_type": "markdown",
66 | "id": "5d97d24f-fb7c-43c0-9db7-8cdd0061b2ff",
67 | "metadata": {},
68 | "source": [
69 | "## Create Interactive Controls"
70 | ]
71 | },
72 | {
73 | "cell_type": "code",
74 | "execution_count": null,
75 | "id": "f213e39a-3042-430a-a8eb-69731c531d90",
76 | "metadata": {},
77 | "outputs": [],
78 | "source": [
79 | "# LR checkbox (Radiological convention)\n",
80 | "lr_checkbox = widgets.Checkbox(\n",
81 | " value=False,\n",
82 | " description=\"LR\",\n",
83 | " tooltip=\"Toggle between radiological and neurological convention\",\n",
84 | ")\n",
85 | "\n",
86 | "# nose checkbox (Sagittal nose direction)\n",
87 | "nose_checkbox = widgets.Checkbox(\n",
88 | " value=False, description=\"Nose\", tooltip=\"Toggle sagittal nose direction\"\n",
89 | ")\n",
90 | "\n",
91 | "# world checkbox (World space vs voxel space)\n",
92 | "world_checkbox = widgets.Checkbox(\n",
93 | " value=True,\n",
94 | " description=\"World\",\n",
95 | " tooltip=\"Toggle between world space (mm) and voxel space\",\n",
96 | ")\n",
97 | "\n",
98 | "# drag mode dropdown\n",
99 | "drag_dropdown = widgets.Dropdown(\n",
100 | " options=[\n",
101 | " (\"contrast\", DragMode.CONTRAST),\n",
102 | " (\"measurement\", DragMode.MEASUREMENT),\n",
103 | " (\"pan/zoom\", DragMode.PAN),\n",
104 | " (\"none\", DragMode.NONE),\n",
105 | " ],\n",
106 | " value=DragMode.CONTRAST,\n",
107 | " description=\"Drag mode:\",\n",
108 | ")"
109 | ]
110 | },
111 | {
112 | "cell_type": "markdown",
113 | "id": "433395cc-6a36-4309-bd9a-3412efb8ab15",
114 | "metadata": {},
115 | "source": [
116 | "## Setup Event Handlers"
117 | ]
118 | },
119 | {
120 | "cell_type": "code",
121 | "execution_count": null,
122 | "id": "03ec3bf3-3c47-48b1-9f73-87b87900e596",
123 | "metadata": {},
124 | "outputs": [],
125 | "source": [
126 | "def on_lr_change(change):\n",
127 | " \"\"\"Handle radiological convention checkbox changes.\"\"\"\n",
128 | " nv.set_radiological_convention(change[\"new\"])\n",
129 | "\n",
130 | "\n",
131 | "def on_nose_change(change):\n",
132 | " \"\"\"Handle nose direction checkbox changes.\"\"\"\n",
133 | " nv.opts.sagittal_nose_left = change[\"new\"]\n",
134 | "\n",
135 | "\n",
136 | "def on_world_change(change):\n",
137 | " \"\"\"Handle world space checkbox changes.\"\"\"\n",
138 | " nv.set_slice_mm(change[\"new\"])\n",
139 | "\n",
140 | "\n",
141 | "def on_drag_mode_change(change):\n",
142 | " \"\"\"Handle drag mode dropdown changes.\"\"\"\n",
143 | " nv.opts.drag_mode = change[\"new\"]\n",
144 | "\n",
145 | "\n",
146 | "lr_checkbox.observe(on_lr_change, names=\"value\")\n",
147 | "nose_checkbox.observe(on_nose_change, names=\"value\")\n",
148 | "world_checkbox.observe(on_world_change, names=\"value\")\n",
149 | "drag_dropdown.observe(on_drag_mode_change, names=\"value\")"
150 | ]
151 | },
152 | {
153 | "cell_type": "markdown",
154 | "id": "c285bb25-7ce4-48d0-a42f-1829647e280d",
155 | "metadata": {},
156 | "source": [
157 | "## Display All"
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "id": "2247f2d2-4935-4a1a-8cfe-8e501c1dc470",
164 | "metadata": {},
165 | "outputs": [],
166 | "source": [
167 | "controls = widgets.HBox([lr_checkbox, nose_checkbox, world_checkbox, drag_dropdown])\n",
168 | "\n",
169 | "display(widgets.VBox([controls, nv]))"
170 | ]
171 | }
172 | ],
173 | "metadata": {},
174 | "nbformat": 4,
175 | "nbformat_minor": 5
176 | }
177 |
--------------------------------------------------------------------------------
/examples/worldspace2.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "f473438a-2002-4526-a6dc-87ce28cc5d41",
6 | "metadata": {},
7 | "source": [
8 | "## Advanced World Space Demo\n",
9 | "advanced world space features including mesh clipping, orientation options, and multiplanar viewing with meshes\n",
10 | "\n",
11 | "See https://niivue.com/demos/features/worldspace2.html for mirror."
12 | ]
13 | },
14 | {
15 | "cell_type": "code",
16 | "execution_count": null,
17 | "id": "a7a68189-b242-4d32-93a2-616d0c1bd624",
18 | "metadata": {},
19 | "outputs": [],
20 | "source": [
21 | "import ipywidgets as widgets\n",
22 | "from IPython.display import display\n",
23 | "\n",
24 | "from ipyniivue import DragMode, NiiVue, ShowRender, SliceType"
25 | ]
26 | },
27 | {
28 | "cell_type": "markdown",
29 | "id": "d926ba4b-b02b-48fd-8cd7-5985a0d736c9",
30 | "metadata": {},
31 | "source": [
32 | "## Create NiiVue Instance"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "id": "7bf9089d-1508-4476-9ca7-44731ec302b6",
39 | "metadata": {},
40 | "outputs": [],
41 | "source": [
42 | "nv = NiiVue(\n",
43 | " height=400,\n",
44 | " drag_and_drop_enabled=True,\n",
45 | " back_color=(0.3, 0.2, 0.4, 1.0),\n",
46 | " show_3d_crosshair=True,\n",
47 | ")\n",
48 | "\n",
49 | "nv.set_slice_mm(True)\n",
50 | "nv.set_clip_plane(-0.1, 270, 0)\n",
51 | "nv.set_render_azimuth_elevation(120, 10)\n",
52 | "nv.set_high_resolution_capable(True)\n",
53 | "\n",
54 | "nv.load_volumes(\n",
55 | " [\n",
56 | " {\n",
57 | " \"url\": \"https://niivue.com/demos/images/mni152.nii.gz\",\n",
58 | " \"colormap\": \"gray\",\n",
59 | " \"opacity\": 1.0,\n",
60 | " }\n",
61 | " ]\n",
62 | ")\n",
63 | "\n",
64 | "nv.load_meshes(\n",
65 | " [\n",
66 | " {\n",
67 | " \"url\": \"https://niivue.com/demos/images/BrainMesh_ICBM152.lh.mz3\",\n",
68 | " \"rgba255\": [200, 162, 255, 255],\n",
69 | " },\n",
70 | " {\n",
71 | " \"url\": \"https://niivue.com/demos/images/dpsv.trx\",\n",
72 | " \"rgba255\": [255, 255, 255, 255],\n",
73 | " },\n",
74 | " ]\n",
75 | ")\n",
76 | "\n",
77 | "nv.opts.slice_type = SliceType.MULTIPLANAR"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "id": "34b70342-5120-4159-a877-58d651c58990",
83 | "metadata": {},
84 | "source": [
85 | "## Create Interactive Controls"
86 | ]
87 | },
88 | {
89 | "cell_type": "code",
90 | "execution_count": null,
91 | "id": "5f35d49b-af66-40b8-a64b-264c9c0a8d8a",
92 | "metadata": {},
93 | "outputs": [],
94 | "source": [
95 | "# mesh clipping slider\n",
96 | "mesh_clipping_slider = widgets.IntSlider(\n",
97 | " value=11,\n",
98 | " min=0,\n",
99 | " max=11,\n",
100 | " description=\"Mesh clipping:\",\n",
101 | " continuous_update=True,\n",
102 | " style={\"description_width\": \"initial\"},\n",
103 | ")\n",
104 | "\n",
105 | "# checkboxes\n",
106 | "radiological_checkbox = widgets.Checkbox(value=False, description=\"Radiological\")\n",
107 | "\n",
108 | "corner_text_checkbox = widgets.Checkbox(value=False, description=\"Corner text\")\n",
109 | "\n",
110 | "world_space_checkbox = widgets.Checkbox(value=True, description=\"World space\")\n",
111 | "\n",
112 | "colorbar_checkbox = widgets.Checkbox(value=False, description=\"Colorbar\")\n",
113 | "\n",
114 | "cube_checkbox = widgets.Checkbox(value=False, description=\"Cube\")\n",
115 | "\n",
116 | "pad_checkbox = widgets.Checkbox(value=False, description=\"Pad\")\n",
117 | "\n",
118 | "high_dpi_checkbox = widgets.Checkbox(value=True, description=\"HighDPI\")\n",
119 | "\n",
120 | "force_render_checkbox = widgets.Checkbox(value=False, description=\"Force Render\")\n",
121 | "\n",
122 | "# drag mode dropdown\n",
123 | "drag_mode_dropdown = widgets.Dropdown(\n",
124 | " options=[\n",
125 | " (\"contrast\", DragMode.CONTRAST),\n",
126 | " (\"measurement\", DragMode.MEASUREMENT),\n",
127 | " (\"pan/zoom\", DragMode.PAN),\n",
128 | " (\"none\", DragMode.NONE),\n",
129 | " ],\n",
130 | " value=DragMode.CONTRAST,\n",
131 | " description=\"Drag mode:\",\n",
132 | " style={\"description_width\": \"initial\"},\n",
133 | ")"
134 | ]
135 | },
136 | {
137 | "cell_type": "markdown",
138 | "id": "08353015-8486-4de6-a5bf-391a412b30e9",
139 | "metadata": {},
140 | "source": [
141 | "## Setup Event Handlers"
142 | ]
143 | },
144 | {
145 | "cell_type": "code",
146 | "execution_count": null,
147 | "id": "ce29c6ad-abfe-4f63-9e62-d95016db5f56",
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "def on_mesh_clipping_change(change):\n",
152 | " \"\"\"Handle mesh clipping slider changes.\"\"\"\n",
153 | " dx = float(change[\"new\"])\n",
154 | " if dx > 10:\n",
155 | " dx = float(\"inf\")\n",
156 | " nv.set_mesh_thickness_on_2d(dx)\n",
157 | "\n",
158 | "\n",
159 | "def on_radiological_change(change):\n",
160 | " \"\"\"Handle radiological convention checkbox changes.\"\"\"\n",
161 | " nv.set_radiological_convention(change[\"new\"])\n",
162 | "\n",
163 | "\n",
164 | "def on_corner_text_change(change):\n",
165 | " \"\"\"Handle corner text checkbox changes.\"\"\"\n",
166 | " nv.set_corner_orientation_text(change[\"new\"])\n",
167 | "\n",
168 | "\n",
169 | "def on_world_space_change(change):\n",
170 | " \"\"\"Handle world space checkbox changes.\"\"\"\n",
171 | " nv.set_slice_mm(change[\"new\"])\n",
172 | "\n",
173 | "\n",
174 | "def on_colorbar_change(change):\n",
175 | " \"\"\"Handle colorbar checkbox changes.\"\"\"\n",
176 | " nv.opts.is_colorbar = change[\"new\"]\n",
177 | "\n",
178 | "\n",
179 | "def on_cube_change(change):\n",
180 | " \"\"\"Handle orientation cube checkbox changes.\"\"\"\n",
181 | " nv.opts.is_orient_cube = change[\"new\"]\n",
182 | "\n",
183 | "\n",
184 | "def on_pad_change(change):\n",
185 | " \"\"\"Handle multiplanar padding checkbox changes.\"\"\"\n",
186 | " pad = 5 if change[\"new\"] else 0\n",
187 | " nv.set_multiplanar_pad_pixels(pad)\n",
188 | "\n",
189 | "\n",
190 | "def on_high_dpi_change(change):\n",
191 | " \"\"\"Handle high DPI checkbox changes.\"\"\"\n",
192 | " nv.set_high_resolution_capable(change[\"new\"])\n",
193 | "\n",
194 | "\n",
195 | "def on_force_render_change(change):\n",
196 | " \"\"\"Handle force render checkbox changes.\"\"\"\n",
197 | " if change[\"new\"]:\n",
198 | " nv.opts.multiplanar_show_render = ShowRender.ALWAYS\n",
199 | " else:\n",
200 | " nv.opts.multiplanar_show_render = ShowRender.AUTO\n",
201 | "\n",
202 | "\n",
203 | "def on_drag_mode_change(change):\n",
204 | " \"\"\"Handle drag mode dropdown changes.\"\"\"\n",
205 | " nv.opts.drag_mode = change[\"new\"]"
206 | ]
207 | },
208 | {
209 | "cell_type": "markdown",
210 | "id": "c2f6e749-6e86-406b-9ede-89c1dee1f962",
211 | "metadata": {},
212 | "source": [
213 | "## Attach event handlers"
214 | ]
215 | },
216 | {
217 | "cell_type": "code",
218 | "execution_count": null,
219 | "id": "334235db-b8b3-44d5-8316-c07cb4e2477b",
220 | "metadata": {},
221 | "outputs": [],
222 | "source": [
223 | "mesh_clipping_slider.observe(on_mesh_clipping_change, names=\"value\")\n",
224 | "radiological_checkbox.observe(on_radiological_change, names=\"value\")\n",
225 | "corner_text_checkbox.observe(on_corner_text_change, names=\"value\")\n",
226 | "world_space_checkbox.observe(on_world_space_change, names=\"value\")\n",
227 | "colorbar_checkbox.observe(on_colorbar_change, names=\"value\")\n",
228 | "cube_checkbox.observe(on_cube_change, names=\"value\")\n",
229 | "pad_checkbox.observe(on_pad_change, names=\"value\")\n",
230 | "high_dpi_checkbox.observe(on_high_dpi_change, names=\"value\")\n",
231 | "force_render_checkbox.observe(on_force_render_change, names=\"value\")\n",
232 | "drag_mode_dropdown.observe(on_drag_mode_change, names=\"value\")"
233 | ]
234 | },
235 | {
236 | "cell_type": "markdown",
237 | "id": "8d0264d4-a1d8-4108-b0e2-2199b0abe3c4",
238 | "metadata": {},
239 | "source": [
240 | "## Initialize mesh clipping"
241 | ]
242 | },
243 | {
244 | "cell_type": "code",
245 | "execution_count": null,
246 | "id": "1b6ae48d-4771-446a-bb75-3f075f6a13f3",
247 | "metadata": {},
248 | "outputs": [],
249 | "source": [
250 | "on_mesh_clipping_change({\"new\": 11}) # sets to infinity cause >10"
251 | ]
252 | },
253 | {
254 | "cell_type": "markdown",
255 | "id": "6642b8c8-6747-41e5-9aee-3cd4c29e7b5a",
256 | "metadata": {},
257 | "source": [
258 | "## Display All"
259 | ]
260 | },
261 | {
262 | "cell_type": "code",
263 | "execution_count": null,
264 | "id": "780a17c6-0c47-491e-b4f6-bf9a7c7e657b",
265 | "metadata": {},
266 | "outputs": [],
267 | "source": [
268 | "controls_row1 = widgets.HBox([mesh_clipping_slider, drag_mode_dropdown])\n",
269 | "\n",
270 | "controls_row2 = widgets.HBox(\n",
271 | " [\n",
272 | " radiological_checkbox,\n",
273 | " corner_text_checkbox,\n",
274 | " world_space_checkbox,\n",
275 | " colorbar_checkbox,\n",
276 | " ]\n",
277 | ")\n",
278 | "\n",
279 | "controls_row3 = widgets.HBox(\n",
280 | " [cube_checkbox, pad_checkbox, high_dpi_checkbox, force_render_checkbox]\n",
281 | ")\n",
282 | "\n",
283 | "controls = widgets.VBox([controls_row1, controls_row2, controls_row3])\n",
284 | "\n",
285 | "display(widgets.VBox([controls, nv]))"
286 | ]
287 | }
288 | ],
289 | "metadata": {},
290 | "nbformat": 4,
291 | "nbformat_minor": 5
292 | }
293 |
--------------------------------------------------------------------------------
/js/lib.ts:
--------------------------------------------------------------------------------
1 | import type { NVConfigOptions } from "@niivue/niivue";
2 | import type { AnyModel, Scene, TypedBufferPayload } from "./types.ts";
3 |
4 | function delay(ms: number) {
5 | return new Promise((resolve) => setTimeout(resolve, ms));
6 | }
7 |
8 | function dataViewToBase64(dataView: DataView) {
9 | const uint8Array = new Uint8Array(dataView.buffer);
10 | let binaryString = "";
11 | const len = uint8Array.byteLength;
12 | for (let i = 0; i < len; i++) {
13 | binaryString += String.fromCharCode(uint8Array[i]);
14 | }
15 | return btoa(binaryString);
16 | }
17 |
18 | export function deserializeOptions(
19 | options: Partial>,
20 | ): NVConfigOptions {
21 | const result: Partial = {};
22 | const specialValues: Record = {
23 | Infinity: Number.POSITIVE_INFINITY,
24 | "-Infinity": Number.NEGATIVE_INFINITY,
25 | NaN: Number.NaN,
26 | "-0": -0,
27 | };
28 |
29 | for (const [key, value] of Object.entries(options) as [
30 | keyof NVConfigOptions,
31 | unknown,
32 | ][]) {
33 | if (typeof value === "string" && value in specialValues) {
34 | // biome-ignore lint/suspicious/noExplicitAny: NVConfigOptions
35 | (result as any)[key] = specialValues[value];
36 | } else {
37 | // biome-ignore lint/suspicious/noExplicitAny: NVConfigOptions
38 | (result as any)[key] = value;
39 | }
40 | }
41 |
42 | return result as NVConfigOptions;
43 | }
44 |
45 | export function handleBufferMsg(
46 | // biome-ignore lint/suspicious/noExplicitAny: targetObject can be any
47 | targetObject: any,
48 | payload: TypedBufferPayload,
49 | buffers: DataView[],
50 | callback: (data: TypedBufferPayload) => void,
51 | ): boolean {
52 | const { type, data } = payload;
53 |
54 | switch (type) {
55 | case "buffer_change": {
56 | const attrName = data.attr;
57 | const dataType = data.type;
58 | const buffer = buffers[0].buffer;
59 | const TypedArrayConstructor = getTypedArrayConstructor(dataType);
60 | const typedArray = new TypedArrayConstructor(buffer);
61 |
62 | targetObject[attrName] = typedArray;
63 |
64 | callback(payload);
65 |
66 | return true;
67 | }
68 | case "buffer_update": {
69 | const attrName = data.attr;
70 | const dataType = data.type;
71 | const indicesType = data.indices_type;
72 | const [indicesBuffer, valuesBuffer] = [
73 | buffers[0].buffer,
74 | buffers[1].buffer,
75 | ];
76 |
77 | const IndicesArrayConstructor = getTypedArrayConstructor(indicesType);
78 | const ValuesArrayConstructor = getTypedArrayConstructor(dataType);
79 |
80 | const indicesArray = new IndicesArrayConstructor(indicesBuffer);
81 | const valuesArray = new ValuesArrayConstructor(valuesBuffer);
82 |
83 | const existingArray = targetObject[attrName] as TypedArray;
84 |
85 | if (!existingArray || existingArray.length === 0) {
86 | console.error(
87 | `Existing array ${attrName} is empty or not initialized.`,
88 | );
89 | return true;
90 | }
91 |
92 | applyDifferencesToTypedArray(existingArray, indicesArray, valuesArray);
93 |
94 | callback(payload);
95 |
96 | return true;
97 | }
98 | default:
99 | return false;
100 | }
101 | }
102 |
103 | export type TypedArray =
104 | | Float32Array
105 | | Uint32Array
106 | | Uint8Array
107 | | Int16Array
108 | | Int32Array
109 | | Float64Array
110 | | Uint16Array;
111 |
112 | type TypedArrayConstructor = new (
113 | buffer: ArrayBufferLike,
114 | ) => T;
115 |
116 | const typeMapping: { [key: string]: TypedArrayConstructor } = {
117 | float32: Float32Array,
118 | uint32: Uint32Array,
119 | uint8: Uint8Array,
120 | int16: Int16Array,
121 | int32: Int32Array,
122 | float64: Float64Array,
123 | uint16: Uint16Array,
124 | };
125 |
126 | export function getArrayType(typedArray: TypedArray): string {
127 | for (const typeStr in typeMapping) {
128 | const c = typeMapping[typeStr];
129 | if (typedArray instanceof c) {
130 | return typeStr;
131 | }
132 | }
133 | console.log("getArrayType unsupportedarraytype err:", typedArray);
134 | throw new Error("Unsupported array type");
135 | }
136 |
137 | export function getTypedArrayConstructor(
138 | typeStr: string,
139 | ): TypedArrayConstructor {
140 | const c = typeMapping[typeStr];
141 | if (c) {
142 | return c;
143 | }
144 | throw new Error(`Unsupported data type: ${typeStr}`);
145 | }
146 |
147 | export function deserializeBufferToTypedArray(
148 | buffer: ArrayBuffer,
149 | typeStr: string,
150 | ): TypedArray {
151 | const TypedArrayConstructor = getTypedArrayConstructor(typeStr);
152 | return new TypedArrayConstructor(buffer);
153 | }
154 |
155 | export function applyDifferencesToTypedArray(
156 | array: TypedArray,
157 | indices: TypedArray,
158 | values: TypedArray,
159 | ): void {
160 | for (let i = 0; i < indices.length; i++) {
161 | const idx = indices[i];
162 | array[idx] = values[i];
163 | }
164 | }
165 |
166 | export async function forceSendState(
167 | model: AnyModel,
168 | state: Record,
169 | ) {
170 | const isMarimo = typeof model.send_sync_message === "undefined";
171 |
172 | if (isMarimo) {
173 | model.onChange(state);
174 | } else {
175 | const msgId = model.send_sync_message(state);
176 | if (typeof model.rememberLastUpdateFor !== "undefined") {
177 | model.rememberLastUpdateFor(msgId);
178 | }
179 | }
180 | }
181 |
182 | export async function sendChunkedData(
183 | model: AnyModel,
184 | dataProperty: string,
185 | arrayBuffer: ArrayBuffer,
186 | dataType: string,
187 | _chunkSize = 5 * 1024 * 1024,
188 | wait = 0,
189 | ) {
190 | const isMarimo = typeof model.send_sync_message === "undefined";
191 |
192 | const chunkSize = isMarimo
193 | ? Math.min(_chunkSize, 2 * 1024 * 1024)
194 | : _chunkSize;
195 |
196 | const totalSize = arrayBuffer.byteLength;
197 | const totalChunks = Math.ceil(totalSize / chunkSize);
198 | let offset = 0;
199 | let chunkIndex = 0;
200 |
201 | while (offset < totalSize) {
202 | if (!isMarimo && !model._comm_live) {
203 | break;
204 | }
205 |
206 | const chunkEnd = Math.min(offset + chunkSize, totalSize);
207 | const chunk = arrayBuffer.slice(offset, chunkEnd);
208 | const chunkView = new DataView(chunk);
209 |
210 | const attributeName: string = `chunk_${dataProperty}_${chunkIndex}`;
211 |
212 | const data = {
213 | chunk_index: chunkIndex,
214 | total_chunks: totalChunks,
215 | data_type: dataType,
216 | chunk: isMarimo ? dataViewToBase64(chunkView) : chunkView,
217 | };
218 | const message: Record = {};
219 | message[attributeName] = data;
220 |
221 | console.log("lib.sendChunkedData:", message);
222 |
223 | if (isMarimo) {
224 | model.onChange(message);
225 | } else {
226 | const msgId = model.send_sync_message(message);
227 | if (typeof model.rememberLastUpdateFor !== "undefined") {
228 | model.rememberLastUpdateFor(msgId);
229 | }
230 | }
231 |
232 | if (wait > 0) {
233 | await delay(wait);
234 | }
235 |
236 | offset = chunkEnd;
237 | chunkIndex += 1;
238 | }
239 | }
240 |
241 | export function gather_models(
242 | model: AnyModel,
243 | ids: Array,
244 | ): Promise> {
245 | // biome-ignore lint/suspicious/noExplicitAny: we know the type of the models
246 | const models: Array> = [];
247 | const widget_manager = model.widget_manager;
248 | for (const id of ids) {
249 | const model_id = id.slice("IPY_MODEL_".length);
250 | models.push(widget_manager.get_model(model_id));
251 | }
252 | return Promise.all(models);
253 | }
254 |
255 | /**
256 | * A class to keep track of disposers for callbacks for updating the scene.
257 | */
258 | export class Disposer {
259 | #disposers = new Map void>();
260 |
261 | register(
262 | obj: { id: string } | { id: string | undefined },
263 | disposer: () => void,
264 | ): void {
265 | const id = obj.id || "";
266 | this.#disposers.set(id, disposer);
267 | }
268 |
269 | has(id: string): boolean {
270 | return this.#disposers.has(id);
271 | }
272 |
273 | dispose(id: string): void {
274 | const dispose = this.#disposers.get(id);
275 | if (dispose) {
276 | dispose();
277 | this.#disposers.delete(id);
278 | }
279 | }
280 |
281 | disposeAll(): void {
282 | for (const dispose of this.#disposers.values()) {
283 | dispose();
284 | }
285 | this.#disposers.clear();
286 | }
287 | }
288 |
289 | function numberArraysEqual(a: number[], b: number[]): boolean {
290 | if (a.length !== b.length) return false;
291 | return a.every((val, idx) => val === b[idx]);
292 | }
293 |
294 | export function sceneDiff(
295 | oldScene: Scene | null,
296 | newScene: Scene,
297 | ): Partial {
298 | if (!oldScene) return newScene;
299 |
300 | const diff: Partial = {};
301 |
302 | if (oldScene.renderAzimuth !== newScene.renderAzimuth) {
303 | diff.renderAzimuth = newScene.renderAzimuth;
304 | }
305 | if (oldScene.renderElevation !== newScene.renderElevation) {
306 | diff.renderElevation = newScene.renderElevation;
307 | }
308 | if (oldScene.volScaleMultiplier !== newScene.volScaleMultiplier) {
309 | diff.volScaleMultiplier = newScene.volScaleMultiplier;
310 | }
311 | if (oldScene.gamma !== newScene.gamma) {
312 | diff.gamma = newScene.gamma;
313 | }
314 |
315 | if (
316 | oldScene.crosshairPos &&
317 | newScene.crosshairPos &&
318 | !numberArraysEqual(oldScene.crosshairPos, newScene.crosshairPos)
319 | ) {
320 | diff.crosshairPos = newScene.crosshairPos;
321 | }
322 | if (
323 | oldScene.clipPlane &&
324 | newScene.clipPlane &&
325 | !numberArraysEqual(oldScene.clipPlane, newScene.clipPlane)
326 | ) {
327 | diff.clipPlane = newScene.clipPlane;
328 | }
329 | if (
330 | oldScene.clipPlaneDepthAziElev &&
331 | newScene.clipPlaneDepthAziElev &&
332 | !numberArraysEqual(
333 | oldScene.clipPlaneDepthAziElev,
334 | newScene.clipPlaneDepthAziElev,
335 | )
336 | ) {
337 | diff.clipPlaneDepthAziElev = newScene.clipPlaneDepthAziElev;
338 | }
339 | if (
340 | oldScene.pan2Dxyzmm &&
341 | newScene.pan2Dxyzmm &&
342 | !numberArraysEqual(oldScene.pan2Dxyzmm, newScene.pan2Dxyzmm)
343 | ) {
344 | diff.pan2Dxyzmm = newScene.pan2Dxyzmm;
345 | }
346 |
347 | return diff;
348 | }
349 |
--------------------------------------------------------------------------------
/js/types.ts:
--------------------------------------------------------------------------------
1 | import type { AnyModel as BaseAnyModel } from "@anywidget/types";
2 | import type { NVConfigOptions } from "@niivue/niivue";
3 | import type { NIFTI1 } from "nifti-reader-js";
4 |
5 | export interface AnyModel extends BaseAnyModel {
6 | // biome-ignore lint/suspicious/noExplicitAny: callbacks are Record
7 | send_sync_message(state: object, callbacks?: any): string;
8 | rememberLastUpdateFor(msgId: string): void;
9 | _comm_live: boolean;
10 | // marimo support, since marimo uses POST requests instead of websocket
11 | onChange: (value: Partial) => void;
12 | }
13 |
14 | // just part of the NiivueObject3D in niivue
15 | export type NiivueObject3D = {
16 | id: number;
17 | extents_min: number[];
18 | extents_max: number[];
19 | scale: number[];
20 | furthest_vertex_from_origin?: number;
21 | field_of_view_de_oblique_mm?: number[];
22 | };
23 |
24 | interface FileInput {
25 | name: string;
26 | data: DataView;
27 | }
28 |
29 | type ColorMap = {
30 | R: number[];
31 | G: number[];
32 | B: number[];
33 | A: number[];
34 | I: number[];
35 | min?: number;
36 | max?: number;
37 | labels?: string[];
38 | };
39 |
40 | type LUT = {
41 | lut: Uint8ClampedArray;
42 | min?: number;
43 | max?: number;
44 | labels?: string[];
45 | };
46 |
47 | type Graph = {
48 | LTWH: number[];
49 | opacity: number;
50 | vols: number[];
51 | autoSizeMultiplanar: boolean;
52 | normalizeValues: boolean;
53 | isRangeCalMinMax: boolean;
54 |
55 | plotLTWH?: number[];
56 | backColor?: number[];
57 | lineColor?: number[];
58 | textColor?: number[];
59 | lineThickness?: number;
60 | gridLineThickness?: number;
61 | lineAlpha?: number;
62 | lines?: number[][];
63 | selectedColumn?: number;
64 | lineRGB?: number[][];
65 | };
66 |
67 | export type Scene = {
68 | renderAzimuth?: number;
69 | renderElevation?: number;
70 | volScaleMultiplier?: number;
71 | crosshairPos?: number[];
72 | clipPlane?: number[];
73 | clipPlaneDepthAziElev?: number[];
74 | pan2Dxyzmm?: number[];
75 | gamma?: number;
76 | };
77 |
78 | export type VolumeModel = AnyModel<{
79 | path: FileInput;
80 | url: string;
81 | data: DataView;
82 |
83 | paired_img_path: FileInput;
84 | paired_img_url: string;
85 | paired_img_data: DataView;
86 |
87 | id: string;
88 | name: string;
89 | colormap: string;
90 | opacity: number;
91 | visible: boolean;
92 | colorbar_visible: boolean;
93 | cal_min: number;
94 | cal_max: number;
95 | cal_min_neg: number;
96 | cal_max_neg: number;
97 | frame_4d: number;
98 | colormap_negative: string;
99 | colormap_label: LUT;
100 | colormap_type: number;
101 |
102 | colormap_invert: boolean;
103 | n_frame_4d: number | null;
104 | modulation_image: number | null;
105 | modulate_alpha: number;
106 |
107 | hdr: Partial; // only updated via frontend...but this might change in the future..
108 | img: DataView;
109 | dims: number[];
110 | extents_min_ortho: number[];
111 | extents_max_ortho: number[];
112 | frac2mm: number[];
113 | frac2mm_ortho: number[];
114 | dims_ras: number[];
115 | mat_ras: number[];
116 | }>;
117 |
118 | export type MeshModel = AnyModel<{
119 | path: FileInput;
120 | url: string;
121 | data: DataView;
122 |
123 | id: string;
124 | name: string;
125 | rgba255: Array;
126 | opacity: number;
127 | layers: Array;
128 | visible: boolean;
129 |
130 | colormap_invert: boolean;
131 | colorbar_visible: boolean;
132 | mesh_shader_index: number;
133 | fiber_radius: number;
134 | fiber_length: number;
135 | fiber_dither: number;
136 | fiber_color: string;
137 | fiber_decimation_stride: number;
138 | colormap: string;
139 |
140 | pts: DataView;
141 | tris: DataView;
142 | extents_min: number[];
143 | extents_max: number[];
144 | }>;
145 |
146 | export type MeshLayerModel = AnyModel<{
147 | path: FileInput;
148 | url: string;
149 | data: DataView;
150 |
151 | id: string;
152 | name: string;
153 | opacity: number;
154 | colormap: string;
155 | colormap_negative: string;
156 | use_negative_cmap: boolean;
157 | cal_min: number;
158 | cal_max: number;
159 | outline_border: number;
160 |
161 | colormap_invert: boolean;
162 | frame_4d: number;
163 | colorbar_visible: boolean;
164 | }>;
165 |
166 | export type Model = AnyModel<{
167 | this_model_id: string;
168 |
169 | height: number;
170 | volumes: Array;
171 | meshes: Array;
172 | opts: Partial>;
173 |
174 | _canvas_attached: boolean;
175 |
176 | background_masks_overlays: number;
177 | draw_lut: LUT;
178 | draw_opacity: number;
179 | draw_fill_overwrites: boolean;
180 | graph: Graph;
181 | scene: Scene;
182 | overlay_outline_width: number;
183 | overlay_alpha_shader: number;
184 |
185 | _volume_object_3d_data: NiivueObject3D; // only updated via frontend (1-way comm)
186 |
187 | draw_bitmap: DataView | null;
188 | }>;
189 |
190 | // Custom message datas
191 | type SaveDocumentData = [fileName: string, compress: boolean];
192 |
193 | type SaveHTMLData = [fileName: string, canvasId: string];
194 |
195 | type SaveImageData = [
196 | filename: string,
197 | isSaveDrawing: boolean,
198 | volumeByIndex: number,
199 | ];
200 |
201 | type SaveSceneData = [fileName: string];
202 |
203 | type AddColormapData = [name: string, cmap: ColorMap];
204 |
205 | type SetGammaData = [gamma: number];
206 |
207 | type SetVolumeRenderIlluminationData = [gradientAmount: number];
208 |
209 | type LoadPngAsTextureData = [pngUrl: string, textureNum: number];
210 |
211 | type SetInterpolationData = [isNearest: boolean];
212 |
213 | type SetDrawingEnabledData = [drawingEnabled: boolean];
214 |
215 | type DrawOtsuData = [levels: number];
216 |
217 | type MoveCrosshairInVoxData = [x: number, y: number, z: number];
218 |
219 | type RemoveHazeData = [level: number, volIndex: number];
220 |
221 | type LoadDrawingFromUrlData = [url: string, isBinarize: boolean];
222 |
223 | type LoadDocumentFromUrlData = [url: string];
224 |
225 | type SaveToDiskData = [fileName: string];
226 |
227 | export type CustomMessagePayload =
228 | | { type: "save_document"; data: SaveDocumentData }
229 | | { type: "save_html"; data: SaveHTMLData }
230 | | { type: "save_image"; data: SaveImageData }
231 | | { type: "save_scene"; data: SaveSceneData }
232 | | { type: "add_colormap"; data: AddColormapData }
233 | | { type: "set_gamma"; data: SetGammaData }
234 | | { type: "resize_listener"; data: [] }
235 | | { type: "draw_scene"; data: [] }
236 | | { type: "update_gl_volume"; data: [] }
237 | | {
238 | type: "set_volume_render_illumination";
239 | data: SetVolumeRenderIlluminationData;
240 | }
241 | | { type: "load_png_as_texture"; data: LoadPngAsTextureData }
242 | | { type: "set_interpolation"; data: SetInterpolationData }
243 | | { type: "set_drawing_enabled"; data: SetDrawingEnabledData }
244 | | { type: "draw_otsu"; data: DrawOtsuData }
245 | | { type: "draw_grow_cut"; data: [] }
246 | | { type: "move_crosshair_in_vox"; data: MoveCrosshairInVoxData }
247 | | { type: "remove_haze"; data: RemoveHazeData }
248 | | { type: "draw_undo"; data: [] }
249 | | { type: "close_drawing"; data: [] }
250 | | { type: "load_drawing_from_url"; data: LoadDrawingFromUrlData }
251 | | { type: "load_document_from_url"; data: LoadDocumentFromUrlData };
252 |
253 | export type VolumeCustomMessage = {
254 | type: "save_to_disk";
255 | data: SaveToDiskData;
256 | };
257 |
258 | export type TypedBufferPayload =
259 | | {
260 | type: "buffer_change";
261 | data: {
262 | attr: string;
263 | type: string;
264 | };
265 | }
266 | | {
267 | type: "buffer_update";
268 | data: {
269 | attr: string;
270 | type: string;
271 | indices_type: string;
272 | };
273 | };
274 |
--------------------------------------------------------------------------------
/js/types/niivue-fix.d.ts:
--------------------------------------------------------------------------------
1 | import { Niivue } from "@niivue/niivue";
2 |
3 | declare module "@niivue/niivue" {
4 | interface Niivue {
5 | setMeshShader(meshId: string, shader: string): void;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/js/types/niivue-min.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@niivue/niivue/min";
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module",
3 | "scripts": {
4 | "dev": "node build.js --watch",
5 | "build": "node build.js",
6 | "lint": "biome ci .",
7 | "fix": "biome check --fix .",
8 | "typecheck": "tsc"
9 | },
10 | "dependencies": {
11 | "@anywidget/types": "^0.2.0",
12 | "@niivue/niivue": "^0.62.1"
13 | },
14 | "devDependencies": {
15 | "@biomejs/biome": "^1.9.4",
16 | "esbuild": "^0.25.2",
17 | "typescript": "^5.8.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling", "hatch-vcs"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "ipyniivue"
7 | license = { text = "BSD-2-Clause" }
8 | dynamic = ["version"]
9 | description = "A Jupyter Widget for Niivue based on anywidget."
10 | dependencies = ["anywidget", "requests", "numpy>=2.0.2"]
11 | readme = "README.md"
12 | requires-python = ">=3.9"
13 | classifiers = [
14 | "Framework :: Jupyter",
15 | "Framework :: Jupyter :: JupyterLab",
16 | "Framework :: Jupyter :: JupyterLab :: 4",
17 | "Framework :: Jupyter :: JupyterLab :: Extensions",
18 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
19 | "License :: OSI Approved :: BSD License",
20 | "Programming Language :: Python",
21 | "Programming Language :: Python :: 3",
22 | ]
23 |
24 | [project.urls]
25 | homepage = "https://github.com/niivue/ipyniivue"
26 |
27 | [project.optional-dependencies]
28 | dev = ["watchfiles", "jupyterlab", "ruff", "pytest", "sphinx", "furo", "sphinxcontrib-mermaid", "myst-parser"]
29 |
30 | [pytest]
31 | nb_test_files = true
32 |
33 | [tool.hatch.envs.default]
34 | features = ["dev"]
35 | uv = true
36 |
37 | # https://github.com/ofek/hatch-vcs
38 | [tool.hatch.version]
39 | source = "vcs"
40 |
41 | [tool.hatch.build]
42 | only-packages = true
43 | artifacts = ["src/ipyniivue/static/*"]
44 |
45 | [tool.hatch.build.hooks.jupyter-builder]
46 | build-function = "hatch_jupyter_builder.npm_builder"
47 | dependencies = ["hatch-jupyter-builder>=0.5.0"]
48 |
49 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs]
50 | npm = "npm"
51 | build_cmd = "build"
52 |
53 | [tool.hatch.envs.default.scripts]
54 | lint = ["ruff check . {args:.}", "ruff format . --check --diff {args:.}"]
55 | format = ["ruff format . {args:.}", "ruff check . --fix {args:.}"]
56 | test = ["pytest {args:.}"]
57 | docs = "cd docs && make clean && make html"
58 |
59 | [tool.ruff.lint]
60 | pydocstyle = { convention = "numpy" }
61 | select = [
62 | "E", # style errors
63 | "W", # style warnings
64 | "F", # flakes
65 | "D", # pydocstyle
66 | "D417", # Missing argument descriptions in Docstrings
67 | "I", # isort
68 | "UP", # pyupgrade
69 | "C4", # flake8-comprehensions
70 | "B", # flake8-bugbear
71 | "A001", # flake8-builtins
72 | "RUF", # ruff-specific rules
73 | "TCH", # flake8-type-checking
74 | "TID", # flake8-tidy-imports
75 | ]
76 |
77 | [tool.ruff.lint.per-file-ignores]
78 | "tests/*.py" = ["D", "S"]
79 | "scripts/*.py" = ["D", "S"]
80 |
--------------------------------------------------------------------------------
/scripts/generate_config_options.py:
--------------------------------------------------------------------------------
1 | import enum
2 | import math
3 | import pathlib
4 | import typing
5 |
6 | from ipyniivue.constants import (
7 | _SNAKE_TO_CAMEL_OVERRIDES,
8 | DragMode,
9 | MultiplanarType,
10 | PenType,
11 | ShowRender,
12 | SliceType,
13 | )
14 |
15 | RENAME_OVERRIDES = {v: k for k, v in _SNAKE_TO_CAMEL_OVERRIDES.items()}
16 |
17 |
18 | def camel_to_snake(name: str):
19 | return "".join(["_" + c.lower() if c.isupper() else c for c in name]).lstrip("_")
20 |
21 |
22 | def type_hint(value: typing.Any):
23 | if isinstance(value, bool):
24 | return "t.Bool"
25 | elif isinstance(value, int):
26 | return "t.Int"
27 | elif isinstance(value, float):
28 | return "t.Float"
29 | elif isinstance(value, str):
30 | return "t.Unicode"
31 | elif isinstance(value, tuple):
32 | return "t.Tuple"
33 | elif isinstance(value, list):
34 | return "t.List"
35 | elif value is None:
36 | return "t.Any"
37 | elif isinstance(value, enum.Enum):
38 | enum_class_name = type(value).__name__
39 | return f"t.UseEnum({enum_class_name}"
40 | else:
41 | return "t.Any"
42 |
43 |
44 | def get_default_value(value: typing.Any):
45 | if value == float("inf"):
46 | return 'float("inf")'
47 | if isinstance(value, float) and math.isnan(value):
48 | return 'float("nan")'
49 | if isinstance(value, enum.Enum):
50 | enum_class_name = type(value).__name__
51 | return f"{enum_class_name}.{value.name}"
52 | if isinstance(value, str):
53 | return f'"{value}"'
54 | return repr(value)
55 |
56 |
57 | def generate_config_options(options: dict[str, typing.Any]):
58 | lines = [
59 | "# This file is automatically generated by scripts/generate_options_mixin.py",
60 | "# Do not edit this file directly",
61 | "",
62 | '"""Defines a class for NiiVue configuration options."""',
63 | "",
64 | "import traitlets as t",
65 | "",
66 | "from ipyniivue.constants import (",
67 | " DragMode,",
68 | " MultiplanarType,",
69 | " PenType,",
70 | " ShowRender,",
71 | " SliceType,",
72 | ")",
73 | "",
74 | '__all__ = ["CAMEL_TO_SNAKE", "SNAKE_TO_CAMEL", "ConfigOptions"]',
75 | "",
76 | "",
77 | "class ConfigOptions(t.HasTraits):",
78 | ' """Configuration options for NiiVue."""',
79 | "",
80 | " _parent = None",
81 | "",
82 | ]
83 |
84 | for option, value in options.items():
85 | # Convert camelCase to snake_case
86 | snake_name = RENAME_OVERRIDES.get(option, camel_to_snake(option))
87 | hint = type_hint(value)
88 | default_value = get_default_value(value)
89 | if "UseEnum" in hint:
90 | # For Enums, default_value is passed as a keyword argument
91 | lines.append(
92 | f" {snake_name} = {hint}, "
93 | f"default_value={default_value}).tag(sync=False)"
94 | )
95 | else:
96 | lines.append(f" {snake_name} = {hint}({default_value}).tag(sync=False)")
97 | lines.append("")
98 |
99 | # Add __init__ method
100 | lines.append(" def __init__(self, parent=None, **kwargs):")
101 | lines.append(" super().__init__(**kwargs)")
102 | lines.append(" self._parent = parent")
103 | lines.append("")
104 |
105 | # Add observe method
106 | option_names = [
107 | RENAME_OVERRIDES.get(option, camel_to_snake(option))
108 | for option in options.keys()
109 | ]
110 |
111 | lines.append(" _OBSERVED_TRAITS = (")
112 | for name in option_names:
113 | lines.append(f' "{name}",')
114 | lines.append(" )")
115 | lines.append("")
116 |
117 | lines.append(" @t.observe(*_OBSERVED_TRAITS)")
118 | lines.append(" def _propagate_parent_change(self, change):")
119 | lines.append(
120 | " if self._parent and callable("
121 | 'getattr(self._parent, "_notify_opts_changed", None)):'
122 | )
123 | lines.append(" self._parent._notify_opts_changed()")
124 | lines.append("")
125 |
126 | option_names = options.keys()
127 | snake_case_names = [
128 | RENAME_OVERRIDES.get(option, camel_to_snake(option)) for option in option_names
129 | ]
130 | mappings = dict(zip(option_names, snake_case_names))
131 | lines.append("CAMEL_TO_SNAKE = {")
132 | for orig_name, snake_name in mappings.items():
133 | lines.append(f' "{orig_name}": "{snake_name}",')
134 | lines.append("}")
135 | lines.append("")
136 |
137 | lines.append("SNAKE_TO_CAMEL = {v: k for k, v in CAMEL_TO_SNAKE.items()}")
138 |
139 | return "\n".join(lines)
140 |
141 |
142 | if __name__ == "__main__":
143 | # Copied from niivue (should be able to automatically generate this)
144 | DEFAULT_OPTIONS = {
145 | "textHeight": -1.0,
146 | "fontSizeScaling": 0.4,
147 | "fontMinPx": 13,
148 | "colorbarHeight": 0.05,
149 | "colorbarWidth": -1.0,
150 | "showColorbarBorder": True,
151 | "crosshairWidth": 1.0,
152 | "crosshairWidthUnit": "voxels",
153 | "crosshairGap": 0.0,
154 | "rulerWidth": 4.0,
155 | "show3Dcrosshair": False,
156 | "backColor": (0.0, 0.0, 0.0, 1.0),
157 | "crosshairColor": (1.0, 0.0, 0.0, 1.0),
158 | "fontColor": (0.5, 0.5, 0.5, 1.0),
159 | "selectionBoxColor": (1.0, 1.0, 1.0, 0.5),
160 | "clipPlaneColor": (0.7, 0.0, 0.7, 0.5),
161 | "paqdUniforms": (0.3, 0.5, 0.5, 1.0),
162 | "clipThick": 2.0,
163 | "clipVolumeLow": (0.0, 0.0, 0.0),
164 | "clipVolumeHigh": (1.0, 1.0, 1.0),
165 | "rulerColor": (1.0, 0.0, 0.0, 0.8),
166 | "colorbarMargin": 0.05,
167 | "trustCalMinMax": True,
168 | "clipPlaneHotKey": "KeyC",
169 | "viewModeHotKey": "KeyV",
170 | "doubleTouchTimeout": 500,
171 | "longTouchTimeout": 1000,
172 | "keyDebounceTime": 50,
173 | "isNearestInterpolation": False,
174 | "isResizeCanvas": True,
175 | "atlasOutline": 0.0,
176 | "atlasActiveIndex": 0,
177 | "isRuler": False,
178 | "isColorbar": False,
179 | "isOrientCube": False,
180 | "tileMargin": 0.0,
181 | "multiplanarPadPixels": 0,
182 | "multiplanarForceRender": False,
183 | "multiplanarEqualSize": False,
184 | "multiplanarShowRender": ShowRender.AUTO,
185 | "isRadiologicalConvention": False,
186 | "meshThicknessOn2D": float("inf"),
187 | "dragMode": DragMode.CONTRAST,
188 | "dragModePrimary": DragMode.CROSSHAIR,
189 | "mouseEventConfig": None,
190 | "touchEventConfig": None,
191 | "yoke3Dto2DZoom": False,
192 | "isDepthPickMesh": False,
193 | "isCornerOrientationText": False,
194 | "isOrientationTextVisible": True,
195 | "showAllOrientationMarkers": False,
196 | "heroImageFraction": 0.0,
197 | "heroSliceType": SliceType.RENDER,
198 | "sagittalNoseLeft": False,
199 | "isSliceMM": False,
200 | "isV1SliceShader": False,
201 | "forceDevicePixelRatio": 0.0,
202 | "logLevel": "info",
203 | "loadingText": "loading ...",
204 | "isForceMouseClickToVoxelCenters": False,
205 | "dragAndDropEnabled": True,
206 | "drawingEnabled": False,
207 | "penValue": 1.0,
208 | "penType": PenType.PEN,
209 | "floodFillNeighbors": 6,
210 | "isFilledPen": False,
211 | "thumbnail": "",
212 | "maxDrawUndoBitmaps": 8,
213 | "sliceType": SliceType.MULTIPLANAR,
214 | "meshXRay": 0.0,
215 | "isAntiAlias": None,
216 | "limitFrames4D": float("nan"),
217 | "isAdditiveBlend": False,
218 | "showLegend": True,
219 | "legendBackgroundColor": (0.3, 0.3, 0.3, 0.5),
220 | "legendTextColor": (1.0, 1.0, 1.0, 1.0),
221 | "multiplanarLayout": MultiplanarType.AUTO,
222 | "renderOverlayBlend": 1.0,
223 | "sliceMosaicString": "",
224 | "centerMosaic": False,
225 | "penSize": 1.0,
226 | "interactive": True,
227 | "clickToSegment": False,
228 | "clickToSegmentRadius": 3.0,
229 | "clickToSegmentBright": True,
230 | "clickToSegmentAutoIntensity": False,
231 | "clickToSegmentIntensityMax": float("nan"),
232 | "clickToSegmentIntensityMin": float("nan"),
233 | "clickToSegmentPercent": 0.0,
234 | "clickToSegmentMaxDistanceMM": float("inf"),
235 | "clickToSegmentIs2D": False,
236 | "selectionBoxLineThickness": 4.0,
237 | "selectionBoxIsOutline": False,
238 | "scrollRequiresFocus": False,
239 | "showMeasureUnits": True,
240 | "measureTextJustify": "center",
241 | "measureTextColor": (1.0, 0.0, 0.0, 1.0),
242 | "measureLineColor": (1.0, 0.0, 0.0, 1.0),
243 | "measureTextHeight": 0.06,
244 | "isAlphaClipDark": False,
245 | "gradientOrder": 1,
246 | "gradientOpacity": 0.0,
247 | "renderSilhouette": 0.0,
248 | "gradientAmount": 0.0,
249 | "invertScrollDirection": False,
250 | "is2DSliceShader": False,
251 | }
252 | code = generate_config_options(DEFAULT_OPTIONS)
253 | loc = pathlib.Path(__file__).parent / "../src/ipyniivue/config_options.py"
254 | loc.write_text(code)
255 |
--------------------------------------------------------------------------------
/src/ipyniivue/__init__.py:
--------------------------------------------------------------------------------
1 | """A Jupyter widget for Niivue based on anywidget."""
2 |
3 | import importlib.metadata
4 |
5 | from .constants import ( # noqa: F401
6 | ColormapType,
7 | DragMode,
8 | MultiplanarType,
9 | PenType,
10 | ShowRender,
11 | SliceType,
12 | )
13 | from .download_dataset import download_dataset # noqa: F401
14 | from .traits import ( # noqa: F401
15 | LUT,
16 | ColorMap,
17 | Graph,
18 | )
19 | from .widget import ( # noqa: F401
20 | Mesh,
21 | MeshLayer,
22 | NiiVue,
23 | Volume,
24 | WidgetObserver,
25 | )
26 |
27 | __version__ = importlib.metadata.version("ipyniivue")
28 |
--------------------------------------------------------------------------------
/src/ipyniivue/constants.py:
--------------------------------------------------------------------------------
1 | """
2 | Provides classes for reusable, readable versions of important data.
3 |
4 | This module uses enum to enumerate important constants.
5 | """
6 |
7 | import enum
8 |
9 | __all__ = [
10 | "ColormapType",
11 | "DragMode",
12 | "MultiplanarType",
13 | "PenType",
14 | "ShowRender",
15 | "SliceType",
16 | ]
17 |
18 |
19 | class SliceType(enum.Enum):
20 | """
21 | An enumeration of slice types for NiiVue instances.
22 |
23 | Members
24 | -------
25 | AXIAL : int
26 | Axial slice type (value 0).
27 | CORONAL : int
28 | Coronal slice type (value 1).
29 | SAGITTAL : int
30 | Sagittal slice type (value 2).
31 | MULTIPLANAR : int
32 | Multiplanar view type (value 3).
33 | RENDER : int
34 | Render view type (value 4).
35 | """
36 |
37 | AXIAL = 0
38 | CORONAL = 1
39 | SAGITTAL = 2
40 | MULTIPLANAR = 3
41 | RENDER = 4
42 |
43 |
44 | class PenType(enum.Enum):
45 | """
46 | An enumeration of pen types for drawing tools.
47 |
48 | Members
49 | -------
50 | PEN : int
51 | Standard pen (freehand drawing) (value 0).
52 | RECTANGLE : int
53 | Rectangle drawing mode (value 1).
54 | ELLIPSE : int
55 | Ellipse drawing mode (value 2).
56 | """
57 |
58 | PEN = 0
59 | RECTANGLE = 1
60 | ELLIPSE = 2
61 |
62 |
63 | class ShowRender(enum.Enum):
64 | """
65 | An enumeration for specifying when to show rendering in NiiVue instances.
66 |
67 | Members
68 | -------
69 | NEVER : int
70 | Never show rendering (value 0).
71 | ALWAYS : int
72 | Always show rendering (value 1).
73 | AUTO : int
74 | Automatically determine whether to show rendering (value 2).
75 | """
76 |
77 | NEVER = 0
78 | ALWAYS = 1
79 | AUTO = 2
80 |
81 |
82 | class MultiplanarType(enum.Enum):
83 | """
84 | An enumeration of multiplanar types for NiiVue instances.
85 |
86 | Members
87 | -------
88 | AUTO : int
89 | Automatic multiplanar layout (value 0).
90 | COLUMN : int
91 | Vertical column multiplanar layout (value 1).
92 | GRID : int
93 | Grid multiplanar layout (value 2).
94 | ROW : int
95 | Horizontal row multiplanar layout (value 3).
96 | """
97 |
98 | AUTO = 0
99 | COLUMN = 1
100 | GRID = 2
101 | ROW = 3
102 |
103 |
104 | class DragMode(enum.Enum):
105 | """
106 | An enumeration of drag modes for NiiVue instances.
107 |
108 | Members
109 | -------
110 | NONE : int
111 | No drag mode active (value 0).
112 | CONTRAST : int
113 | Contrast adjustment mode (value 1).
114 | MEASUREMENT : int
115 | Measurement mode for taking measurements (value 2).
116 | PAN : int
117 | Pan mode for moving around the image (value 3).
118 | SLICER_3D : int
119 | 3D slicer interaction mode (value 4).
120 | CALLBACK_ONLY : int
121 | Callback only mode (value 5).
122 | ROI_SELECTION : int
123 | ROI (Region of Interest) selection mode (value 6).
124 | ANGLE : int
125 | Angle measurement mode (value 7).
126 | CROSSHAIR : int
127 | Crosshair interaction mode (value 8).
128 | WINDOWING : int
129 | Windowing adjustment mode (value 9).
130 | """
131 |
132 | NONE = 0
133 | CONTRAST = 1
134 | MEASUREMENT = 2
135 | PAN = 3
136 | SLICER_3D = 4
137 | CALLBACK_ONLY = 5
138 | ROI_SELECTION = 6
139 | ANGLE = 7
140 | CROSSHAIR = 8
141 | WINDOWING = 9
142 |
143 |
144 | class ColormapType(enum.Enum):
145 | """
146 | An enumeration of colormap types.
147 |
148 | Members
149 | -------
150 | MIN_TO_MAX : int
151 | Colormap spans from minimum to maximum values (value 0).
152 | ZERO_TO_MAX_TRANSPARENT_BELOW_MIN : int
153 | Colormap spans from zero to maximum, transparent below minimum (value 1).
154 | ZERO_TO_MAX_TRANSLUCENT_BELOW_MIN : int
155 | Colormap spans from zero to maximum, translucent below minimum (value 2).
156 | """
157 |
158 | MIN_TO_MAX = 0
159 | ZERO_TO_MAX_TRANSPARENT_BELOW_MIN = 1
160 | ZERO_TO_MAX_TRANSLUCENT_BELOW_MIN = 2
161 |
162 |
163 | _SNAKE_TO_CAMEL_OVERRIDES = {
164 | "show_3d_crosshair": "show3Dcrosshair",
165 | "mesh_thickness_on_2d": "meshThicknessOn2D",
166 | "yoke_3d_to_2d_zoom": "yoke3Dto2DZoom",
167 | "is_slice_mm": "isSliceMM",
168 | "limit_frames_4d": "limitFrames4D",
169 | "click_to_segment_is_2d": "clickToSegmentIs2D",
170 | "is_v1_slice_shader": "isV1SliceShader",
171 | "mesh_xray": "meshXRay",
172 | "click_to_segment_max_distance_mm": "clickToSegmentMaxDistanceMM",
173 | "is_2d_slice_shader": "is2DSliceShader",
174 | }
175 |
--------------------------------------------------------------------------------
/src/ipyniivue/download_dataset.py:
--------------------------------------------------------------------------------
1 | """
2 | Downloads the data needed to run provided examples.
3 |
4 | This module is designed to download the nii files needed to run examples and is
5 | set to do so if another url is not provided. It can, however, download other
6 | data, like fonts, from the NiiVue repo or elsewhere.
7 | """
8 |
9 | from pathlib import Path
10 |
11 | import requests
12 |
13 | import ipyniivue
14 |
15 | BASE_API_URL = (
16 | "https://api.github.com/repos/niivue/niivue/contents/packages/niivue/demos/images"
17 | )
18 | DATA_FOLDER = Path(ipyniivue.__file__).parent / "images"
19 |
20 |
21 | def download_dataset(api_url=None, dest_folder=None, force_download=False, files=None):
22 | """
23 | Download the datasets used for demos and testing.
24 |
25 | Parameters
26 | ----------
27 | api_url : str, optional
28 | The API URL to fetch the dataset from. Defaults to the base URL.
29 | dest_folder : Path, optional
30 | The destination folder to save the downloaded files. Defaults to the
31 | images directory in ipyniivue.
32 | force_download : bool, optional
33 | If True, download files even if they already exist locally. Defaults to
34 | False.
35 | files : list of str, optional
36 | A list of file paths (relative to the base API URL) to download. If
37 | None, all files will be downloaded.
38 |
39 | Raises
40 | ------
41 | FileNotFoundError
42 | If a specified file is not found in the dataset.
43 | Exception
44 | If a file cannot be downloaded due to HTTP errors.
45 | """
46 | if api_url is None:
47 | api_url = BASE_API_URL
48 | if dest_folder is None:
49 | dest_folder = DATA_FOLDER
50 |
51 | dest_folder.mkdir(parents=True, exist_ok=True)
52 |
53 | if files:
54 | for file_path in files:
55 | local_file_path = dest_folder / file_path
56 | if local_file_path.exists() and not force_download:
57 | print(f"{file_path} already exists.")
58 | continue
59 |
60 | file_api_url = f"{api_url}/{file_path}"
61 | response = requests.get(file_api_url)
62 | if response.status_code != 200:
63 | raise FileNotFoundError(
64 | f"File {file_path} not found (HTTP {response.status_code})."
65 | )
66 |
67 | content_type = response.headers.get("Content-Type")
68 |
69 | if (
70 | content_type
71 | and "application/json" in content_type.lower()
72 | and not file_path.endswith(".json")
73 | ):
74 | file_info = response.json()
75 | download_url = file_info.get("download_url")
76 | if not download_url:
77 | raise Exception(f"No download URL for {file_path}.")
78 | else:
79 | download_url = file_api_url
80 |
81 | print(f"Downloading {file_path}...")
82 | file_response = requests.get(download_url)
83 | file_response.raise_for_status()
84 | local_file_path.parent.mkdir(parents=True, exist_ok=True)
85 | local_file_path.write_bytes(file_response.content)
86 |
87 | else:
88 | response = requests.get(api_url)
89 | response.raise_for_status()
90 | file_list = response.json()
91 |
92 | for item in file_list:
93 | if item["type"] != "file":
94 | continue
95 |
96 | local_file_path = dest_folder / item["name"]
97 | if local_file_path.exists() and not force_download:
98 | print(f"{item['name']} already exists.")
99 | continue
100 |
101 | print(f"Downloading {item['name']}...")
102 | download_url = item["download_url"]
103 | file_response = requests.get(download_url)
104 | file_response.raise_for_status()
105 | local_file_path.write_bytes(file_response.content)
106 |
107 | print(f"Dataset downloaded successfully to {dest_folder}.")
108 |
--------------------------------------------------------------------------------
/tests/test_ipyniivue.py:
--------------------------------------------------------------------------------
1 | def test_it_loads():
2 | import ipyniivue
3 |
4 | assert ipyniivue.__version__ is not None
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "nodenext",
5 | "moduleResolution": "nodenext",
6 | "strict": true,
7 | "noEmit": true,
8 | "verbatimModuleSyntax": true,
9 | "skipLibCheck": true,
10 | "allowImportingTsExtensions": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------