Metadata-Version: 2.4
Name: napari-npifile
Version: 0.1.1
Summary: Read and write .npi files, a lightweight format based on NumPy .npy arrays with additional napari viewer and layer metadata such as axis names and colormaps.
Author-email: Daniel Haase <git@dhaase.de>
License-Expression: MIT
Project-URL: Repository, https://codeberg.org/dhaase-de/napari-npifile
Project-URL: Source Code, https://codeberg.org/dhaase-de/napari-npifile
Classifier: Framework :: napari
Classifier: Topic :: File Formats
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Operating System :: MacOS
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: numpy>=1.13.3
Dynamic: license-file

# Napari Plugin `napari-npifile`

Allows reading and writing of `.npi` files.

`.npi` files are essentially NumPy arrays packaged together with JSON metadata that
stores **napari viewer and layer configuration**, such as axis names or colormaps.

The goal is to keep the simplicity of `.npy` files while preserving useful visualization
settings when opening arrays in napari.

## Quick Example

```python
import napari_npifile
import numpy as np

data = np.random.random(size=(10, 20, 30, 40))

# save it without napari metadata
np.save("quick_example.npy", data)

# save it with napari metadata
napari_npifile.write_npi(
    "quick_example.npi",
    data=data,
    layer_attributes={
        "name": "foo",
        "colormap": "magma",
        "contrast_limits": (-0.25, 1.25),
    },
    viewer_settings={
        "axis_labels": ["T", "Z", "Y", "X"],
        "scale_bar_properties": {"visible": True, "unit": "px", "font_size": 40.0},
    },
)

# and drag and drop it into napari
#    |
#    |
#    v
```

![quick_example.gif](https://codeberg.org/dhaase-de/napari-npifile/raw/branch/main/quick_example.gif)

## Motivation

When working on image analysis tasks in Python it is common to save temporary NumPy
arrays such as:

* image stacks
* neural network tensors
* intermediate processing results
* ...

to `.npy` files, which can easily be opened in [napari](https://napari.org/) via
drag-and-drop.

However, `.npy` files contain no visualization metadata. Each time a file is opened,
display settings (colormap, contrast limits, etc.) must be configured manually.

`.npi` files solve this by storing the arrays together with napari viewer metadata.

## Installation

```console
pip install --upgrade napari-npifile
```

**Note**: napari is **not** included as a dependency, to allow writing `.npi` files
even if napari is not installed (e.g., on headless servers).

To view `.npi` files in napari, you must install napari separately:

```console
pip install napari
```

See the
[napari installation instructions](https://napari.org/stable/tutorials/fundamentals/installation.html)
for details.

## Usage

### Reading `.npi` files in napari

Once `napari-npifile` is installed, `.npi` files can be opened in napari like any other
supported format:

1. via drag-and-drop,
2. via the `File -> Open` menu,
3. via the napari console using `viewer.open("myfile.npi", plugin="napari-npifile")`, or
4. programmatically using:
    ```python
    import napari

    viewer = napari.Viewer()
    viewer.open("myfile.npi", plugin="napari-npifile")

    napari.run()
    ```

When loaded, each layer in the `.npi` file becomes a separate napari layer. Layer
settings (colormap, contrast limits, gamma, name, etc.) and viewer settings (axis
labels, camera settings, scale bar settings, etc.) are applied automatically.

### Writing `.npi` files from Python (no napari required)

`napari-npifile` provides a main writer class `NpiWriter_v1` for full control, as well
as the convenience wrappers `write_npi`, `write_multilayer_npi` into a `.npi` file.

#### When to use what:

The convenience wrapper functions (`write_npi`, `write_multilayer_npi`) are useful for
cases where all the data to be stored is available upfront. Use `write_npi` for writing
a single layer and `write_multilayer_npi` for multiple layers.

The class `NpiWriter_v1` allows to **incrementally** write layers into a single `.npi`
file over time, making it ideal for loops.

#### Convenience wrapper `write_npi` (single-layer):

```python
import napari_npifile
import numpy as np

data = np.random.random((10, 20, 30))

napari_npifile.write_npi(
    "single.npi",
    data=data,
    layer_attributes={"name": "layer0", "colormap": "magma"},
    viewer_settings={"axis_labels": ["Z", "Y", "X"]},
)
```

#### Convenience wrapper `write_multilayer_npi` (multi-layer):

```python
import napari_npifile
import numpy as np

data_seq = [
    np.random.random((10, 20, 30)),
    np.random.random((10, 20, 30)),
]
layer_attributes_seq = [
    {"name": "first", "colormap": "gray"},
    {"name": "second", "colormap": "green"},
]

napari_npifile.write_multilayer_npi(
    "multi.npi",
    data_seq=data_seq,
    layer_attributes_seq=layer_attributes_seq,
    viewer_settings={"axis_labels": ["Z", "Y", "X"]},
)
```

#### Full general example using `NpiWriter_v1`:

```python
import napari_npifile
import numpy as np

# prepare writer
writer = napari_npifile.NpiWriter_v1(
    out_path="out.npi",
    exist_ok=True,
)

# viewer settings are saved once and are used for all layers
writer.write_viewer_settings(
    {
        "axis_labels": ["T", "K", "Z", "Y", "X"],
        "grid_properties": {"enabled": True, "shape": (1, -1), "stride": 1, "spacing": 0.1},
        "scale_bar_properties": {"visible": True, "unit": "px", "gridded": True},
    }
)

# layers are written one at a time, each with individual attributes
data1 = np.random.random(size=(3, 10, 20, 30, 40)) 
writer.write_layer(data=data1, attributes={"colormap": "cyan", "name": "foo"})

data2 = np.random.random(size=(1, 10, 20, 30, 40))
writer.write_layer(data=data2, attributes={"colormap": "green", "name": "bar"})
```

## File Format

### Overview

`.npi` files are ZIP archives containing raw NumPy array data (stored
as `.npy` files) together with napari viewer and layer metadata stored as JSON files.

### Versions

Every `.npi` file contains a root `metadata.json` with a mandatory `"npi_format"`
field. This format versioning ensures backward compatibility: the reader uses it to
select the appropriate parser for each format.

In addition, the class `NpiWriter_v1` will remain indefinitely to ensure backward
compatibility. If a new `.npi` format version is introduced, a new writer class will be
added alongside it, leaving existing code using `NpiWriter_v1` fully functional.

Version history:

| Format Version | `napari-npifile` version | Comment                                                                                                                                               |
|----------------|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
| `v1`           | `0.1.0+`                 | Initial version, current format. Supports single- and multi-layer `.npi` files with full napari image layer metadata and a subset of viewer settings. |

### Current Version Details (`v1`)

#### Archive Layout

```text
example.npi                  # this is just a ZIP archive
├── metadata.json            # contains {"npi_format": "v1"}
├── viewer_settings.json     # dictionary with global viewer settings - see below
└── layers
    ├── 00000000             # each layer is stored under a sequential numeric key
    │   ├── data.npy         # NumPy array as saved via np.save
    │   └── attributes.json  # layer attributes (as in napari.layers.Image)
    └── 00000001
        ├── data.npy
        └── attributes.json
```

All metadata files are JSON files which contain a dictionary.

#### Metadata File `metadata.json`

Only contains `{"npi_format": "v1"}`.
It is used by the reader to select the correct parser for the file.

#### Metadata File `viewer_settings.json`

Stores global napari viewer settings.
All fields are **optional**; if missing, the reader does not change the current napari
configuration.

| Field                     | Type        | Notes                                                                                                                                                                                                              |
|---------------------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `clear_existing_layers`   | `bool`      | If `True`, removes all existing layers before adding new ones                                                                                                                                                      |
| `hide_existing_layers`    | `bool`      | If `True`, hides all existing layers before adding new ones                                                                                                                                                        |
| `title`                   | `str`       | Napari window title                                                                                                                                                                                                |
| `ndisplay`                | `int`       | `2` or `3` (for 2D or 3D mode)                                                                                                                                                                                     |
| `axis_labels`             | `list[str]` | One label per axis (e.g., `["T", "Z", "Y", "X"]`)                                                                                                                                                                  |
| `dims_set_point`          | `dict`      | Optional subfields as in [napari's set_point](https://napari.org/stable/api/napari.components.Dims.html#napari.components.Dims.set_point), i.e. `axis: int or Sequence[int]` and `value: float or Sequence[float]` |
| `camera_properties`       | `dict`      | Optional subfields as in [napari's Camera](https://napari.org/stable/api/napari.components.Camera.html) (e.g., `{"center": (0.0, 10.0, 20.0)}`)                                                                    |
| `grid_properties`         | `dict`      | Optional subfields as in [napari's GridCanvas](https://github.com/napari/napari/blob/main/src/napari/components/grid.py) (`enabled: bool`, `shape: tuple[int, int]`, `stride: int`, `spacing: float`)              |
| `scale_bar_properties`    | `dict`      | Optional subfields as in [napari's ScaleBarOverlay](https://github.com/napari/napari/blob/main/src/napari/components/overlays/scale_bar.py) (e.g., `visible: bool`, `unit: str`, `gridded: bool`)                  |
| `text_overlay_properties` | `dict`      | Optional subfields as in [napari's TextOverlay](https://github.com/napari/napari/blob/main/src/napari/components/overlays/text.py) (e.g., `visible: bool`, `text: str`, `gridded: bool`, `font_size: float`)       |

#### Metadata File(s) `layers/*/attributes.json`

Layer-specific settings for each layer. All fields are **optional**; if missing, the
reader uses napari defaults.

[Any keyword argument accepted by napari’s Image layer](https://napari.org/stable/api/napari.layers.Image.html#napari-layers-image)
(except `data`), e.g.:

* `name: str`
* `colormap: str` (any napari colormap)
* `contrast_limits: tuple[float, float]`
* `gamma: float`
* `blending: str ("translucent", "additive", "opaque", ...)`
* ...

## Design Principles and Limitations

Key principles for the `.npi` format and `napari-npifile` are:

* **Simplicity and accessibility**: `.npi` files are standard ZIP archives containing
  NumPy arrays and JSON metadata. Thus, the files can easily be inspected, read, and
  created even without this library.
* **Minimal dependencies**: Only NumPy and napari (for viewing) are required.
* **Tight napari integration**: `.npi` stores viewer and layer settings exclusively for
  napari, and thus allows fine-grained control over how arrays are displayed.
* **Backward compatibility**: Every `.npi` includes a format version, ensuring future
  readers can correctly handle older files.
* **Headless-friendly writing**: `.npi` files can be created without napari installed,
  which is useful for server-side or automated workflows.

These design choices naturally impose some limitations:

* **No access optimization**: `.npi` uses plain `np.save`/`np.load` from ZIP
  archives and thus can't compete with optimized formats for large arrays.
* **No compression**: `.npi` stores raw arrays; large datasets may consume significant
  disk space.
* **Viewer-centric metadata**: Metadata is tailored specifically to napari; other
  viewers or tools require extra work to interpret it.

In short, `.npi` prioritizes simplicity and tight napari integration over maximal
performance. Users needing advanced storage features or highly
efficient random access may prefer formats such as [Zarr](https://zarr.dev/) or
[HDF5](https://en.wikipedia.org/wiki/Hierarchical_Data_Format), while
[OME-TIFF](https://ome-model.readthedocs.io/en/stable/) provides more generic
metadata handling.

## Known Issues

* Setting the viewer slice position (`viewer.dims.set_point`) and camera properties does
  not work when there are no pre-existing layers or when `clear_existing_layers` is
  `True`.

## Todo / Ideas

* Add support to write `.npi` files directly from napari.
* Add CLI to convert existing `.npy` files to `.npi` files (and back).
* Add option to specify ZIP compression method and level to optionally reduce file size.

## Changelog

* `v0.1.1` (2026-03-14)
  * fixed missing project description and formatting errors in the readme
* `v0.1.0` (2026-03-14)
  * initial release with standalone read/write and napari reader support

## Related Discussions / Projects

* [napari GitHub issue: Changing viewer dimension when adding a new layer #6127](https://github.com/napari/napari/issues/6127)
* [image.sc forum: Saving volumetric data with voxel size, colormap, annotations](https://forum.image.sc/t/saving-volumetric-data-with-voxel-size-colormap-annotations/85537/24)

## License

`napari-npifile` is released under the MIT License. See `LICENSE.txt` for full details.
