napari-nninteractive 2.3.2__tar.gz → 2.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {napari_nninteractive-2.3.2/src/napari_nninteractive.egg-info → napari_nninteractive-2.4.2}/PKG-INFO +63 -29
  2. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/README.md +59 -22
  3. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/pyproject.toml +18 -8
  4. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/layers/scribble_layer.py +12 -1
  5. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/widget_controls.py +13 -11
  6. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/widget_gui.py +142 -9
  7. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/widget_main.py +219 -45
  8. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2/src/napari_nninteractive.egg-info}/PKG-INFO +63 -29
  9. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive.egg-info/requires.txt +3 -6
  10. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/LICENSE +0 -0
  11. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/MANIFEST.in +0 -0
  12. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/setup.cfg +0 -0
  13. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/__init__.py +0 -0
  14. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/_version_check.py +0 -0
  15. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/controls/__init__.py +0 -0
  16. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/controls/bbox_controls.py +0 -0
  17. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/controls/lasso_controls.py +0 -0
  18. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/controls/point_controls.py +0 -0
  19. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/controls/scribble_controls.py +0 -0
  20. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/layers/__init__.py +0 -0
  21. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/layers/abstract_layer.py +0 -0
  22. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/layers/bbox_layer.py +0 -0
  23. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/layers/lasso_layer.py +0 -0
  24. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/layers/point_layer.py +0 -0
  25. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/napari.yaml +0 -0
  26. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/utils/__init__.py +0 -0
  27. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/utils/affine.py +0 -0
  28. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive/utils/utils.py +0 -0
  29. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive.egg-info/SOURCES.txt +0 -0
  30. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive.egg-info/dependency_links.txt +0 -0
  31. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive.egg-info/entry_points.txt +0 -0
  32. {napari_nninteractive-2.3.2 → napari_nninteractive-2.4.2}/src/napari_nninteractive.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: napari-nninteractive
3
- Version: 2.3.2
3
+ Version: 2.4.2
4
4
  Summary: nnInteractive plugin for Napari
5
5
  Author: Lars Krämer, Fabian Isensee, Maximilian Rokuss
6
6
  Author-email: lars.kraemer@dkfz-heidelberg.de, f.isensee@dkfz-heidelberg.de, maximilian.rokuss@dkfz-heidelberg.de
@@ -225,14 +225,13 @@ Classifier: Topic :: Scientific/Engineering :: Image Processing
225
225
  Requires-Python: >=3.10
226
226
  Description-Content-Type: text/markdown
227
227
  License-File: LICENSE
228
- Requires-Dist: torch
229
228
  Requires-Dist: numpy
230
229
  Requires-Dist: qtpy
231
230
  Requires-Dist: napari-nifti
232
- Requires-Dist: huggingface_hub
233
- Requires-Dist: hf_transfer
234
- Requires-Dist: nnInteractive>=2.4.0
235
231
  Requires-Dist: napari_toolkit
232
+ Requires-Dist: nninteractive-client>=2.5.0
233
+ Provides-Extra: local
234
+ Requires-Dist: nnInteractive>=2.5.0; extra == "local"
236
235
  Provides-Extra: testing
237
236
  Requires-Dist: tox; extra == "testing"
238
237
  Requires-Dist: pytest; extra == "testing"
@@ -240,8 +239,6 @@ Requires-Dist: pytest-cov; extra == "testing"
240
239
  Requires-Dist: pytest-qt; extra == "testing"
241
240
  Requires-Dist: napari; extra == "testing"
242
241
  Requires-Dist: pyqt5; extra == "testing"
243
- Provides-Extra: remote
244
- Requires-Dist: nnInteractive[client]>=2.4.0; extra == "remote"
245
242
  Dynamic: license-file
246
243
 
247
244
  <img src="https://github.com/MIC-DKFZ/napari-nninteractive/raw/main/imgs/nnInteractive_header.png" width="1200">
@@ -286,57 +283,68 @@ Extensive benchmarking demonstrates that nnInteractive far surpasses existing me
286
283
 
287
284
  ## Installation
288
285
 
289
- ### Prerequisites
286
+ `napari-nninteractive` installs as a **lightweight, torch-free remote client by default** — it can drive a remote `nninteractive-server` straight away, on any machine. **Local (in-process) inference is an opt-in extra** (`[local]`) that adds the full nnInteractive backend (PyTorch + nnU-Net) and needs an Nvidia GPU.
290
287
 
291
- For **local** inference you need a Linux or Windows computer with an Nvidia GPU. 10GB of VRAM is recommended. Small objects should work with \<6GB.
288
+ | Mode | Install | PyTorch? | Hardware |
289
+ |------|---------|----------|----------|
290
+ | **Remote client** (default) | `pip install napari-nninteractive` | **No** (lightweight, torch-free) | anything — laptop, Mac, no GPU |
291
+ | **Local inference** | `pip install "napari-nninteractive[local]"` (+ PyTorch) | **Yes** (large download) | Linux/Windows + Nvidia GPU — 10 GB VRAM recommended, \<6 GB works for small objects |
292
292
 
293
293
  > [!TIP]
294
- > **On a Mac, or without a capable Nvidia GPU?** Use [**Remote inference**](#remote-inference-server--client) instead. nnInteractive relies heavily on 3D convolutions, which are prohibitively slow on Apple Silicon (MPS) and CPU hardware running the model on a remote GPU and driving it from napari is the recommended, and often only practical, way to use nnInteractive on these machines. The napari client itself has no GPU requirements and runs anywhere; only the GPU server needs the hardware above.
294
+ > **On a Mac, or without a capable Nvidia GPU?** The default install is already exactly what you want — stop after Step 2. nnInteractive relies heavily on 3D convolutions, which are prohibitively slow on Apple Silicon (MPS) and CPU hardware, so running the model on a remote GPU and driving it from napari is the recommended, and often only practical, way to use it on these machines. See [Remote inference (server / client)](#remote-inference-server--client) for how to set up and connect to the server.
295
295
 
296
- ##### 1. Create a virtual environment:
296
+ ### Step 1 Create a virtual environment
297
297
 
298
- nnInteractive supports Python 3.10+ and works with Conda, pip, or any other virtual environment. Heres an example using Conda:
298
+ nnInteractive supports Python 3.10+ and works with Conda, pip, or any other virtual environment. Here's an example using Conda:
299
299
 
300
- ```
300
+ ```bash
301
301
  conda create -n nnInteractive python=3.12
302
302
  conda activate nnInteractive
303
303
  ```
304
304
 
305
- ##### 2. Install the correct PyTorch for your system
305
+ Install napari if you don't already have it:
306
306
 
307
- Go to the [PyTorch homepage](https://pytorch.org/get-started/locally/) and pick the right configuration.
308
- Note that since recently PyTorch needs to be installed via pip. This is fine to do within your conda environment.
307
+ ```bash
308
+ pip install napari[all]
309
+ ```
309
310
 
310
- For Ubuntu with a Nvidia GPU, pick 'stable', 'Linux', 'Pip', 'Python', 'CUDA12.6' (if all drivers are up to date, otherwise use and older version):
311
+ ### Step 2 Install the plugin (remote client, torch-free)
311
312
 
312
- ```
313
- pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126
313
+ ```bash
314
+ pip install napari-nninteractive
314
315
  ```
315
316
 
316
- ##### 3. Install this repository + dependencies via
317
+ This is the **entire** install if you only drive a remote `nninteractive-server`: no PyTorch, no GPU required. The plugin starts in Remote mode — see [Remote inference (server / client)](#remote-inference-server--client) to connect.
317
318
 
318
- Install napari if necessary
319
+ ### Step 3 — (Optional) Enable local inference
320
+
321
+ Only needed if **this** machine runs the model itself. Requires a Linux or Windows computer with an Nvidia GPU.
322
+
323
+ **1. Install the correct PyTorch for your system.** Go to the [PyTorch homepage](https://pytorch.org/get-started/locally/) and pick the right configuration. PyTorch must be installed via pip nowadays, which is fine inside a conda environment. For Ubuntu with an Nvidia GPU, pick 'stable', 'Linux', 'Pip', 'Python', CUDA version does not matter (ensure your driver is up to date!). Then execute the command that is displayed, for example:
319
324
 
320
325
  ```bash
321
- pip install napari[all]
326
+ pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126
322
327
  ```
323
328
 
324
- Install the plugin via pip:
329
+ **2. Add the `[local]` extra** (pulls the full nnInteractive backend + nnU-Net):
325
330
 
326
331
  ```bash
327
- pip install napari-nninteractive
332
+ pip install "napari-nninteractive[local]"
328
333
  ```
329
334
 
330
- Or clone and install this repository:
335
+ Quote the brackets so your shell does not treat them as a glob (required in zsh / macOS). Model weights are downloaded automatically on first use; this can take a couple of minutes depending on your connection.
336
+
337
+ You can do this at **any** time: if you started with the default client-only install and the **Local** switch in the plugin is greyed out, just run the command above (after installing PyTorch) and restart napari to unlock local inference.
338
+
339
+ ### Install from source (optional)
331
340
 
332
341
  ```bash
333
342
  git clone https://github.com/MIC-DKFZ/napari-nninteractive
334
343
  cd napari-nninteractive
335
- pip install -e .
344
+ pip install -e . # remote client (torch-free) — the default
345
+ pip install -e ".[local]" # local inference — also install PyTorch first (see Step 3)
336
346
  ```
337
347
 
338
- **Note:** Model weights are automatically downloaded on first use. This can take up to a couple of minutes depending on your internet connection
339
-
340
348
  ## Getting Started
341
349
 
342
350
  Use one of these three options to start napari and activate the plugin.
@@ -401,6 +409,32 @@ Share the printed API key with your users via whatever channel you use for share
401
409
 
402
410
  The model checkpoint is fixed by the server at startup, so the local checkpoint selector is hidden in Remote mode.
403
411
 
412
+ > [!NOTE]
413
+ > The **default install is the torch-free client**, so unless you added the `[local]` extra
414
+ > the plugin starts directly in Remote mode and the **Local** switch is greyed out. Hover the
415
+ > switch (or read the one-line notice printed on start-up) for the reason and the exact
416
+ > command. To unlock local inference, install PyTorch and the extra
417
+ > (`pip install "napari-nninteractive[local]"`; see Step 3 of [Installation](#installation) above),
418
+ > then restart napari.
419
+
420
+ ### …or run the server in Docker
421
+
422
+ If you'd rather not install the backend on the GPU box, the server is also published as a Docker
423
+ image with the model **baked in** (a GPU host with the
424
+ [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)
425
+ is required):
426
+
427
+ ```bash
428
+ docker run --gpus all -p 1527:1527 \
429
+ -e NN_INTERACTIVE_API_KEY="$(openssl rand -hex 32)" \
430
+ ghcr.io/mic-dkfz/nninteractive-server:latest
431
+ ```
432
+
433
+ A `lite` tag is also available if you'd rather mount your own checkpoint folder at `/model` (or
434
+ fetch a model by id at startup). See the backend
435
+ [DOCKER.md](https://github.com/MIC-DKFZ/nnInteractive/blob/master/nnInteractive/inference/server/DOCKER.md)
436
+ for both flavours and all configuration options.
437
+
404
438
  ### Things to be aware of
405
439
 
406
440
  - **Lost connection or idle timeout.** Server-side sessions are reaped after 10 minutes of inactivity (configurable on the server), and a server that goes away (restart, crash, network drop) ends the session too. When this happens the plugin resets the **Connect** button and asks you to reconnect. **Your current segmentation is preserved** on the client: after you reconnect and click **Initialize** again, the image is re-uploaded and the segmentation is restored so you can keep refining where you left off. Only the in-progress prompt markers (the individual points/boxes/scribbles) need to be redone.
@@ -421,7 +455,7 @@ Link: [![arXiv](https://img.shields.io/badge/arXiv-2503.08373-b31b1b.svg)](https
421
455
 
422
456
  # License
423
457
 
424
- Note that while this repository is available under Apache-2.0 license (see [LICENSE](./LICENSE)), the [model checkpoint](https://huggingface.co/nnInteractive/nnInteractive) is `Creative Commons Attribution Non Commercial Share Alike 4.0`!
458
+ Note that while this repository is available under Apache-2.0 license (see [LICENSE](./LICENSE)), the [model checkpoint](https://huggingface.co/MIC-DKFZ/nnInteractive) is `Creative Commons Attribution Non Commercial Share Alike 4.0`!
425
459
 
426
460
  ______________________________________________________________________
427
461
 
@@ -40,57 +40,68 @@ Extensive benchmarking demonstrates that nnInteractive far surpasses existing me
40
40
 
41
41
  ## Installation
42
42
 
43
- ### Prerequisites
43
+ `napari-nninteractive` installs as a **lightweight, torch-free remote client by default** — it can drive a remote `nninteractive-server` straight away, on any machine. **Local (in-process) inference is an opt-in extra** (`[local]`) that adds the full nnInteractive backend (PyTorch + nnU-Net) and needs an Nvidia GPU.
44
44
 
45
- For **local** inference you need a Linux or Windows computer with an Nvidia GPU. 10GB of VRAM is recommended. Small objects should work with \<6GB.
45
+ | Mode | Install | PyTorch? | Hardware |
46
+ |------|---------|----------|----------|
47
+ | **Remote client** (default) | `pip install napari-nninteractive` | **No** (lightweight, torch-free) | anything — laptop, Mac, no GPU |
48
+ | **Local inference** | `pip install "napari-nninteractive[local]"` (+ PyTorch) | **Yes** (large download) | Linux/Windows + Nvidia GPU — 10 GB VRAM recommended, \<6 GB works for small objects |
46
49
 
47
50
  > [!TIP]
48
- > **On a Mac, or without a capable Nvidia GPU?** Use [**Remote inference**](#remote-inference-server--client) instead. nnInteractive relies heavily on 3D convolutions, which are prohibitively slow on Apple Silicon (MPS) and CPU hardware running the model on a remote GPU and driving it from napari is the recommended, and often only practical, way to use nnInteractive on these machines. The napari client itself has no GPU requirements and runs anywhere; only the GPU server needs the hardware above.
51
+ > **On a Mac, or without a capable Nvidia GPU?** The default install is already exactly what you want — stop after Step 2. nnInteractive relies heavily on 3D convolutions, which are prohibitively slow on Apple Silicon (MPS) and CPU hardware, so running the model on a remote GPU and driving it from napari is the recommended, and often only practical, way to use it on these machines. See [Remote inference (server / client)](#remote-inference-server--client) for how to set up and connect to the server.
49
52
 
50
- ##### 1. Create a virtual environment:
53
+ ### Step 1 Create a virtual environment
51
54
 
52
- nnInteractive supports Python 3.10+ and works with Conda, pip, or any other virtual environment. Heres an example using Conda:
55
+ nnInteractive supports Python 3.10+ and works with Conda, pip, or any other virtual environment. Here's an example using Conda:
53
56
 
54
- ```
57
+ ```bash
55
58
  conda create -n nnInteractive python=3.12
56
59
  conda activate nnInteractive
57
60
  ```
58
61
 
59
- ##### 2. Install the correct PyTorch for your system
62
+ Install napari if you don't already have it:
60
63
 
61
- Go to the [PyTorch homepage](https://pytorch.org/get-started/locally/) and pick the right configuration.
62
- Note that since recently PyTorch needs to be installed via pip. This is fine to do within your conda environment.
64
+ ```bash
65
+ pip install napari[all]
66
+ ```
63
67
 
64
- For Ubuntu with a Nvidia GPU, pick 'stable', 'Linux', 'Pip', 'Python', 'CUDA12.6' (if all drivers are up to date, otherwise use and older version):
68
+ ### Step 2 Install the plugin (remote client, torch-free)
65
69
 
66
- ```
67
- pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126
70
+ ```bash
71
+ pip install napari-nninteractive
68
72
  ```
69
73
 
70
- ##### 3. Install this repository + dependencies via
74
+ This is the **entire** install if you only drive a remote `nninteractive-server`: no PyTorch, no GPU required. The plugin starts in Remote mode — see [Remote inference (server / client)](#remote-inference-server--client) to connect.
71
75
 
72
- Install napari if necessary
76
+ ### Step 3 — (Optional) Enable local inference
77
+
78
+ Only needed if **this** machine runs the model itself. Requires a Linux or Windows computer with an Nvidia GPU.
79
+
80
+ **1. Install the correct PyTorch for your system.** Go to the [PyTorch homepage](https://pytorch.org/get-started/locally/) and pick the right configuration. PyTorch must be installed via pip nowadays, which is fine inside a conda environment. For Ubuntu with an Nvidia GPU, pick 'stable', 'Linux', 'Pip', 'Python', CUDA version does not matter (ensure your driver is up to date!). Then execute the command that is displayed, for example:
73
81
 
74
82
  ```bash
75
- pip install napari[all]
83
+ pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126
76
84
  ```
77
85
 
78
- Install the plugin via pip:
86
+ **2. Add the `[local]` extra** (pulls the full nnInteractive backend + nnU-Net):
79
87
 
80
88
  ```bash
81
- pip install napari-nninteractive
89
+ pip install "napari-nninteractive[local]"
82
90
  ```
83
91
 
84
- Or clone and install this repository:
92
+ Quote the brackets so your shell does not treat them as a glob (required in zsh / macOS). Model weights are downloaded automatically on first use; this can take a couple of minutes depending on your connection.
93
+
94
+ You can do this at **any** time: if you started with the default client-only install and the **Local** switch in the plugin is greyed out, just run the command above (after installing PyTorch) and restart napari to unlock local inference.
95
+
96
+ ### Install from source (optional)
85
97
 
86
98
  ```bash
87
99
  git clone https://github.com/MIC-DKFZ/napari-nninteractive
88
100
  cd napari-nninteractive
89
- pip install -e .
101
+ pip install -e . # remote client (torch-free) — the default
102
+ pip install -e ".[local]" # local inference — also install PyTorch first (see Step 3)
90
103
  ```
91
104
 
92
- **Note:** Model weights are automatically downloaded on first use. This can take up to a couple of minutes depending on your internet connection
93
-
94
105
  ## Getting Started
95
106
 
96
107
  Use one of these three options to start napari and activate the plugin.
@@ -155,6 +166,32 @@ Share the printed API key with your users via whatever channel you use for share
155
166
 
156
167
  The model checkpoint is fixed by the server at startup, so the local checkpoint selector is hidden in Remote mode.
157
168
 
169
+ > [!NOTE]
170
+ > The **default install is the torch-free client**, so unless you added the `[local]` extra
171
+ > the plugin starts directly in Remote mode and the **Local** switch is greyed out. Hover the
172
+ > switch (or read the one-line notice printed on start-up) for the reason and the exact
173
+ > command. To unlock local inference, install PyTorch and the extra
174
+ > (`pip install "napari-nninteractive[local]"`; see Step 3 of [Installation](#installation) above),
175
+ > then restart napari.
176
+
177
+ ### …or run the server in Docker
178
+
179
+ If you'd rather not install the backend on the GPU box, the server is also published as a Docker
180
+ image with the model **baked in** (a GPU host with the
181
+ [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)
182
+ is required):
183
+
184
+ ```bash
185
+ docker run --gpus all -p 1527:1527 \
186
+ -e NN_INTERACTIVE_API_KEY="$(openssl rand -hex 32)" \
187
+ ghcr.io/mic-dkfz/nninteractive-server:latest
188
+ ```
189
+
190
+ A `lite` tag is also available if you'd rather mount your own checkpoint folder at `/model` (or
191
+ fetch a model by id at startup). See the backend
192
+ [DOCKER.md](https://github.com/MIC-DKFZ/nnInteractive/blob/master/nnInteractive/inference/server/DOCKER.md)
193
+ for both flavours and all configuration options.
194
+
158
195
  ### Things to be aware of
159
196
 
160
197
  - **Lost connection or idle timeout.** Server-side sessions are reaped after 10 minutes of inactivity (configurable on the server), and a server that goes away (restart, crash, network drop) ends the session too. When this happens the plugin resets the **Connect** button and asks you to reconnect. **Your current segmentation is preserved** on the client: after you reconnect and click **Initialize** again, the image is re-uploaded and the segmentation is restored so you can keep refining where you left off. Only the in-progress prompt markers (the individual points/boxes/scribbles) need to be redone.
@@ -175,7 +212,7 @@ Link: [![arXiv](https://img.shields.io/badge/arXiv-2503.08373-b31b1b.svg)](https
175
212
 
176
213
  # License
177
214
 
178
- Note that while this repository is available under Apache-2.0 license (see [LICENSE](./LICENSE)), the [model checkpoint](https://huggingface.co/nnInteractive/nnInteractive) is `Creative Commons Attribution Non Commercial Share Alike 4.0`!
215
+ Note that while this repository is available under Apache-2.0 license (see [LICENSE](./LICENSE)), the [model checkpoint](https://huggingface.co/MIC-DKFZ/nnInteractive) is `Creative Commons Attribution Non Commercial Share Alike 4.0`!
179
216
 
180
217
  ______________________________________________________________________
181
218
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "napari-nninteractive"
3
- version = "2.3.2"
3
+ version = "2.4.2"
4
4
  description = "nnInteractive plugin for Napari"
5
5
  readme = "README.md"
6
6
  license = {file = "LICENSE"}
@@ -26,17 +26,30 @@ classifiers = [
26
26
  ]
27
27
  requires-python = ">=3.10"
28
28
  dependencies = [
29
- "torch",
29
+ # The default install is the lightweight, torch-free REMOTE client: it can drive
30
+ # a remote nninteractive-server out of the box and pulls no PyTorch. For local
31
+ # inference install the [local] extra (see below / the README), which adds the
32
+ # full nnInteractive backend (torch + nnU-Net). pip extras can only ADD
33
+ # dependencies, never subtract, so local is the opt-in extra rather than
34
+ # something a remote-only user has to strip out of a torch-heavy base.
30
35
  "numpy",
31
36
  "qtpy",
32
37
  "napari-nifti",
33
- "huggingface_hub",
34
- "hf_transfer",
35
- "nnInteractive>=2.4.0",
36
38
  "napari_toolkit",
39
+ # Torch-free remote client + shared wire protocol (pulls numpy/httpx/blosc2).
40
+ # Provides nnInteractive.inference.remote; the full nnInteractive package (added
41
+ # by [local]) depends on this same client, so the two coexist cleanly.
42
+ "nninteractive-client>=2.5.0",
37
43
  ]
38
44
 
39
45
  [project.optional-dependencies]
46
+ # Local (in-process) inference. Adds the full nnInteractive backend, which pulls
47
+ # PyTorch + nnU-Net and provides model discovery/download. On a GPU machine install
48
+ # the matching CUDA build of torch first (see the README), then:
49
+ # pip install "napari-nninteractive[local]"
50
+ local = [
51
+ "nnInteractive>=2.5.0",
52
+ ]
40
53
  testing = [
41
54
  "tox",
42
55
  "pytest", # https://docs.pytest.org/en/latest/contents.html
@@ -45,9 +58,6 @@ testing = [
45
58
  "napari",
46
59
  "pyqt5",
47
60
  ]
48
- remote = [
49
- "nnInteractive[client]>=2.4.0",
50
- ]
51
61
 
52
62
  [project.entry-points."napari.manifest"]
53
63
  napari-nninteractive = "napari_nninteractive:napari.yaml"
@@ -48,7 +48,18 @@ class ScribbleLayer(BaseLayerClass, Labels):
48
48
  """
49
49
  Finalizes the current scribble interaction, updating the label index and marking the layer as free.
50
50
  """
51
- self.data[self.data == 1] = self.prompt_index + 2
51
+ # Recolor the just-drawn stroke (value 1 -> committed prompt value) on the
52
+ # painted slice only. The stroke never spans more than the last painted slice
53
+ # (on_draw undoes any pending stroke before a new one, and _commit_staged_history
54
+ # records that slice), so this avoids scanning/writing the whole D×H×W volume.
55
+ if self._last_slice_id is not None:
56
+ idx = [slice(None)] * 3
57
+ idx[self._last_dim_not_displayed] = self._last_slice_id
58
+ slice_view = self.data[tuple(idx)] # integer index on one axis -> a view
59
+ slice_view[slice_view == 1] = self.prompt_index + 2 # in-place, writes back to self.data
60
+ else:
61
+ # Defensive fallback: run is normally called only after a commit recorded a slice.
62
+ self.data[self.data == 1] = self.prompt_index + 2
52
63
  self._is_free = True
53
64
  self.refresh()
54
65
 
@@ -4,7 +4,6 @@ from pathlib import Path
4
4
  from typing import Any, Optional
5
5
 
6
6
  import numpy as np
7
- from huggingface_hub import snapshot_download
8
7
  from napari._qt.layer_controls.qt_layer_controls_container import layer_to_controls
9
8
  from napari.layers import Labels
10
9
  from napari.layers.base._base_constants import ActionType
@@ -233,22 +232,25 @@ class LayerControls(BaseGUI):
233
232
  self.checkpoint_path = None
234
233
  print(f"Using remote model at: {self.server_url_edit.text().strip()}")
235
234
  else:
236
- model_name = self.model_selection.currentText()
237
235
  model_name_local = self.model_selection_local.text()
238
236
  if model_name_local != "" and Path(model_name_local).exists():
239
237
  # Use Local Checkpoint
240
238
  model_name = Path(model_name_local).name
241
239
  self.checkpoint_path = model_name_local
242
240
  else:
243
- # Download Checkpoint
244
- repo_id = "nnInteractive/nnInteractive"
245
- force_download = False
246
- download_path = snapshot_download(
247
- repo_id=repo_id,
248
- allow_patterns=[f"{model_name}/*"],
249
- force_download=force_download,
250
- )
251
- self.checkpoint_path = Path(download_path).joinpath(model_name)
241
+ # Resolve the selected official model through the nnInteractive backend.
242
+ # It downloads the model on first use, reuses it afterwards, and works
243
+ # offline once a model has been downloaded.
244
+ from nnInteractive.model_management import ensure_model_available
245
+
246
+ idx = self.model_selection.currentIndex()
247
+ if idx < 0 or idx >= len(self._model_ids):
248
+ raise ValueError(
249
+ "No model selected. Pick a model from the dropdown or set a local "
250
+ "checkpoint path."
251
+ )
252
+ model_name = self._model_ids[idx]
253
+ self.checkpoint_path = ensure_model_available(model_name)
252
254
  print(f"Using Model {model_name} at : {self.checkpoint_path}")
253
255
 
254
256
  # --- DATA HANDLING --- #
@@ -1,3 +1,5 @@
1
+ import importlib.util
2
+ import warnings
1
3
  from typing import Optional
2
4
 
3
5
  from napari.layers import Image, Labels
@@ -24,6 +26,7 @@ from qtpy.QtWidgets import (
24
26
  QHBoxLayout,
25
27
  QLabel,
26
28
  QLineEdit,
29
+ QMessageBox,
27
30
  QShortcut,
28
31
  QSizePolicy,
29
32
  QVBoxLayout,
@@ -32,6 +35,47 @@ from qtpy.QtWidgets import (
32
35
 
33
36
  from napari_nninteractive._version_check import VersionChecker, _is_outdated
34
37
 
38
+ # Shared wording for the Local tooltip, the start-up notice, and the dialog shown
39
+ # when the user clicks the (greyed) Local switch. Kept as a reason + a how-to so the
40
+ # dialog can present them as headline + details without repeating itself; the tooltip
41
+ # and console print use the two joined. The lightweight ``nninteractive-client``
42
+ # distribution is remote-only and torch-free; local inference lives in the full
43
+ # ``nnInteractive`` package.
44
+ _REMOTE_ONLY_REASON = (
45
+ "Local inference is unavailable: this is a remote-only (client-only) install "
46
+ "without the local nnInteractive backend, so only a remote nninteractive-server "
47
+ "can be used."
48
+ )
49
+ _ENABLE_LOCAL_STEPS = (
50
+ "To enable local inference (needs an Nvidia GPU):\n"
51
+ " 1. Install PyTorch yourself — it is NOT installed automatically. Pick the build "
52
+ "matching your GPU/CUDA from https://pytorch.org/get-started/locally/\n"
53
+ ' 2. pip install "napari-nninteractive[local]"\n'
54
+ "then restart napari. See the README for details."
55
+ )
56
+ _REMOTE_ONLY_HINT = f"{_REMOTE_ONLY_REASON}\n{_ENABLE_LOCAL_STEPS}"
57
+
58
+
59
+ def _local_inference_available() -> bool:
60
+ """Return True if local inference is installed.
61
+
62
+ The lightweight ``nninteractive-client`` ships only the remote client; the
63
+ local engine and its torch / nnU-Net stack live in the full ``nnInteractive``
64
+ package. We probe for the local inference module *without* importing torch:
65
+ ``find_spec`` only locates the module. In a client-only environment the full
66
+ package's last-resort meta-path finder raises ``ModuleNotFoundError`` here
67
+ (see ``nnInteractive.inference.remote._full_required``), which we treat as
68
+ "not available".
69
+ """
70
+ try:
71
+ return (
72
+ importlib.util.find_spec("nnInteractive.inference.inference_session") is not None
73
+ )
74
+ except ModuleNotFoundError:
75
+ return False
76
+ except Exception: # noqa: BLE001 - never block the GUI on a capability probe
77
+ return False
78
+
35
79
 
36
80
  class BaseGUI(QWidget):
37
81
  """
@@ -49,9 +93,18 @@ class BaseGUI(QWidget):
49
93
  self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
50
94
  self._viewer = viewer
51
95
  self.session_cfg = None
52
- self._remote_mode = False
96
+ # Whether local inference is installed. A remote-only
97
+ # 'nninteractive-client' install cannot run it, so we start in Remote
98
+ # mode and disable the Local controls (see _init_model_selection).
99
+ self._local_available = _local_inference_available()
100
+ self._remote_mode = not self._local_available
53
101
  self._settings = QSettings("MIC-DKFZ", "napari-nninteractive")
54
102
 
103
+ # Be transparent on start-up: tell remote-only users why local inference
104
+ # is off and how to enable it, so a missing Local button is never a mystery.
105
+ if not self._local_available:
106
+ print(f"[napari-nninteractive] {_REMOTE_ONLY_HINT}")
107
+
55
108
  _main_layout = QVBoxLayout()
56
109
  self.setLayout(_main_layout)
57
110
 
@@ -114,9 +167,14 @@ class BaseGUI(QWidget):
114
167
 
115
168
  self.version_status_label.setVisible(True)
116
169
  if outdated:
170
+ # Name only the package(s) actually behind PyPI, and build the upgrade
171
+ # command from those alone. A package whose installed version is newer
172
+ # than the latest release (e.g. a dev/unreleased build) is never listed,
173
+ # so we don't wrongly tell the user to "update" something already ahead.
174
+ names = ", ".join(outdated)
117
175
  self.version_status_label.setText(
118
- "Update available. Please run:\n"
119
- "pip install -U nnInteractive napari-nninteractive"
176
+ f"Update available for {names}. Please run:\n"
177
+ f"pip install -U {' '.join(outdated)}"
120
178
  )
121
179
  self.version_status_label.setStyleSheet("color: #e8830c; font-weight: bold;") # orange
122
180
  else:
@@ -193,16 +251,29 @@ class BaseGUI(QWidget):
193
251
  """Initializes the model selection as a combo box."""
194
252
  _group_box, _layout = setup_vgroupbox(text="Model Selection:")
195
253
 
196
- # Local | Remote mode switch
254
+ # Local | Remote mode switch. Start on Remote when local inference is not
255
+ # installed, since Local cannot be used in that case.
197
256
  self.mode_switch = setup_hswitch(
198
257
  _layout,
199
258
  options=["Local", "Remote"],
200
259
  function=self.on_mode_switched,
201
- default=0,
260
+ default=0 if self._local_available else 1,
202
261
  fixed_color="rgb(0,100, 167)",
203
262
  tooltips="Run inference locally or on a remote nninteractive-server",
204
263
  )
205
264
 
265
+ # Remote-only install: local inference is not installed. Keep the Local
266
+ # button enabled — a *disabled* button swallows clicks and can show no
267
+ # feedback — but selecting it pops an explanatory dialog and snaps the
268
+ # switch back to Remote (handled in on_mode_switched). The tooltip explains
269
+ # why local is unavailable and how to enable it.
270
+ if not self._local_available:
271
+ self.mode_switch.buttons[0].setToolTip(_REMOTE_ONLY_HINT)
272
+ self.mode_switch.buttons[1].setToolTip(
273
+ "Run inference on a remote nninteractive-server"
274
+ )
275
+ self._grey_local_switch_button()
276
+
206
277
  # --- Local container --- #
207
278
  self.local_container = QWidget()
208
279
  _local_layout = QVBoxLayout()
@@ -210,11 +281,43 @@ class BaseGUI(QWidget):
210
281
  self.local_container.setLayout(_local_layout)
211
282
  _layout.addWidget(self.local_container)
212
283
 
213
- model_options = ["nnInteractive_v1.0"]
284
+ # Populate the model dropdown from the nnInteractive backend manifest — the
285
+ # authoritative list of selectable official models, fetched from Hugging Face
286
+ # (remote-first) with an offline cache fallback. The dropdown shows display
287
+ # names; self._model_ids keeps the matching manifest ids by position. If the
288
+ # list can't be loaded at all, the dropdown is left empty and the user can
289
+ # still point to a local checkpoint below or switch to Remote mode.
290
+ self._model_ids: list[str] = []
291
+ model_display_names: list[str] = []
292
+ default_index = 0
293
+ # The model dropdown only drives local inference (in Remote mode the server
294
+ # picks the model), and model discovery lives in the full package. A
295
+ # remote-only install therefore skips loading the list entirely rather than
296
+ # triggering the full-package-required error.
297
+ if self._local_available:
298
+ try:
299
+ from nnInteractive.model_management import get_default_model_id, list_models
300
+
301
+ models = list_models()
302
+ self._model_ids = [m["id"] for m in models]
303
+ model_display_names = [m.get("display_name", m["id"]) for m in models]
304
+ default_id = get_default_model_id()
305
+ if default_id in self._model_ids:
306
+ default_index = self._model_ids.index(default_id)
307
+ except Exception as exc: # noqa: BLE001 - never block the GUI on model discovery
308
+ warnings.warn(
309
+ f"Could not load the nnInteractive model list: {exc}", stacklevel=2
310
+ )
214
311
 
215
312
  self.model_selection = setup_combobox(
216
- _local_layout, options=model_options, function=self.on_model_selected
313
+ _local_layout, options=model_display_names, function=self.on_model_selected
217
314
  )
315
+ if model_display_names:
316
+ # Select the manifest default without firing on_model_selected during init
317
+ # (the handler touches subclass state that isn't built yet).
318
+ self.model_selection.blockSignals(True)
319
+ self.model_selection.setCurrentIndex(default_index)
320
+ self.model_selection.blockSignals(False)
218
321
 
219
322
  _boxlayout = QHBoxLayout()
220
323
  _local_layout.addLayout(_boxlayout)
@@ -300,8 +403,13 @@ class BaseGUI(QWidget):
300
403
  self.remote_status_label.setWordWrap(True)
301
404
  _remote_layout.addWidget(self.remote_status_label)
302
405
 
303
- # Default: Local visible, Remote hidden
304
- self.remote_container.setVisible(False)
406
+ # Show the container for the active mode. A remote-only install starts in
407
+ # Remote mode; its Local controls are hidden and disabled (the menus that
408
+ # belong to the now-greyed Local button).
409
+ self.local_container.setVisible(not self._remote_mode)
410
+ self.remote_container.setVisible(self._remote_mode)
411
+ if not self._local_available:
412
+ self.local_container.setEnabled(False)
305
413
 
306
414
  # Restore last-used values (blocking signals so we don't trigger
307
415
  # on_model_selected / on_remote_settings_changed before the rest of
@@ -349,6 +457,31 @@ class BaseGUI(QWidget):
349
457
  _group_box.setLayout(_layout)
350
458
  return _group_box
351
459
 
460
+ def _grey_local_switch_button(self) -> None:
461
+ """Lightly grey the Local switch button so it reads as unavailable while
462
+ staying clickable (clicking it explains how to enable local inference).
463
+
464
+ The switch resets each button's stylesheet whenever it toggles, so this is
465
+ re-applied after switch changes (see on_mode_switched).
466
+ """
467
+ self.mode_switch.buttons[0].setStyleSheet("color: gray;")
468
+
469
+ def _show_local_unavailable_dialog(self) -> None:
470
+ """Explain why Local is unavailable and how to enable local inference.
471
+
472
+ Shown when the user clicks the greyed (but still clickable) Local switch.
473
+ Deliberately offers no one-click install: the correct PyTorch build depends
474
+ on the user's GPU/CUDA and must be installed manually first (see the steps).
475
+ """
476
+ box = QMessageBox(self)
477
+ box.setIcon(QMessageBox.Information)
478
+ box.setWindowTitle("Local inference not installed")
479
+ box.setText(_REMOTE_ONLY_REASON)
480
+ box.setInformativeText(_ENABLE_LOCAL_STEPS)
481
+ box.addButton(QMessageBox.Close)
482
+ box.setDefaultButton(QMessageBox.Close)
483
+ box.exec()
484
+
352
485
  def _init_image_selection(self) -> QGroupBox:
353
486
  """Initializes the image selection combo box in a group box."""
354
487
  _group_box, _layout = setup_vgroupbox(text="Image Selection:")