Metadata-Version: 2.4
Name: napari-npifile
Version: 0.2.0
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: homepage, https://pypi.org/project/napari-npifile/
Project-URL: documentation, https://codeberg.org/dhaase-de/napari-npifile/src/branch/main/README.md
Project-URL: repository, https://codeberg.org/dhaase-de/napari-npifile
Project-URL: changelog, https://codeberg.org/dhaase-de/napari-npifile/src/branch/main/README.md
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`

[![PyPI - Version](https://img.shields.io/pypi/v/napari-npifile?style=flat-square)](https://pypi.org/project/napari-npifile/)
[![Source Code at Codeberg](https://img.shields.io/badge/codeberg.org-%20?style=flat-square&logo=codeberg&label=source%20code&color=blue&link=https%3A%2F%2Fcodeberg.org%2Fdhaase-de%2Fnapari-npifile)](https://codeberg.org/dhaase-de/napari-npifile)
[![napari hub](https://img.shields.io/endpoint?url=https://api.napari-hub.org/shields/napari-npifile)](https://napari-hub.org/plugins/napari-npifile.html)

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), "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"})
```

#### Incremental writing

When using `NpiWriter_v1`, the `.npi` archive is created immediately during
initialization. From that point on, the file is always a valid `.npi` archive.

Each call to `write_layer` or `write_viewer_settings` simply appends new
entries to the ZIP archive.

### Compression (optional)

`.npi` files are standard ZIP archives and can therefore optionally use ZIP compression.

Compression can be configured when creating an archive. The same two parameters
`compression` and `compresslevel` are available in all writer entry points and have the
identical meaning and function as defined in the underlying
[zipfile Python standard library](https://docs.python.org/3/library/zipfile.html).

Below follows a short description. For more details about `compression` and 
`compresslevel`, please see
https://docs.python.org/3/library/zipfile.html#zipfile-objects.

#### Parameter `compression`

Controls the
[ZIP compression method as supported by zipfile](https://docs.python.org/3/library/zipfile.html#zipfile-objects).

Accepted values:
* `None`: no compression (`ZIP_STORED`)
* `False`: no compression (`ZIP_STORED`)
* `True`: standard ZIP compression (`ZIP_DEFLATED`)
* `int`: a `zipfile.ZIP_*` numeric constant (e.g., `zipfile.ZIP_DEFLATED` or
  `zipfile.ZIP_LZMA`)
* `str`: case-insensitive *name* of a `zipfile.ZIP_*` numeric constant, without the 
  leading `"ZIP_"` (e.g., `"deflated"`, `"lzma"`, or `"bzip2"`)

#### Parameter `compresslevel`

Optional compression level (`int` or `None`) used by some compression methods.

The allowed range depends on the chosen ZIP method and follows the
[limits defined by `zipfile`](https://docs.python.org/3/library/zipfile.html#zipfile-objects).
If `None` (the default), the default level of the 
respective compressor is 
used.

#### Examples

Standard ZIP compression:
```python
napari_npifile.write_npi(
    out_path="compressed.npi",
    data=data,
    compression=True,
)
```

Using an explicit compression method:

```python
import zipfile

napari_npifile.write_npi(
  out_path="compressed.npi",
  data=data,
  compression="lzma",
)

# both are identical

napari_npifile.write_npi(
  out_path="compressed.npi",
  data=data,
  compression=zipfile.ZIP_LZMA,
)
```

Specifying a compression level:
```python
napari_npifile.write_npi(
    out_path="compressed.npi",
    data=data,
    compression="DEFLATED",
    compresslevel=9,
)
```

#### Notes

* The default behavior is no compression (`ZIP_STORED`).
* Compression can reduce disk usage but may increase write and read times.
* Since `.npi` stores raw `.npy` arrays, compression effectiveness depends on the 
  structure of the data.

## 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`) can be specified, 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.
* **Generic compression**: `.npi` allows for standard ZIP compression. 
  Formats designed specifically for numerical arrays may achieve better 
  compression or faster access depending on the dataset.
* **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`.
* When using `NpiWriter_v1` to write an `.npi` file, the method `write_viewer_settings` 
  can only be called once, because the underlying
  [`zipfile` library does not allow to modfify a file in an existing ZIP file](https://stackoverflow.com/a/25739108).  

## Todo / Ideas

* Add support to write `.npi` files directly from napari.
* Add CLI to convert existing `.npy` files to `.npi` files (and back).

## Changelog

* `v0.2.0` (2026-03-17)
  * added support for optional ZIP compression
* `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.
