napari-npifile

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.

  • Daniel Haase

PyPI - Version Source Code at Codeberg napari hub

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

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

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 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

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:

pip install napari

See the napari installation instructions 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:
    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):

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):

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:

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.

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.

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. If None (the default), the default level of the respective compressor is used.

Examples

Standard ZIP compression:

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

Using an explicit compression method:

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:

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

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, i.e. axis: int or Sequence[int] and value: float or Sequence[float]
camera_properties dict Optional subfields as in napari's Camera (e.g., {"center": (0.0, 10.0, 20.0)})
grid_properties dict Optional subfields as in napari's GridCanvas (enabled: bool, shape: tuple[int, int], stride: int, spacing: float)
scale_bar_properties dict Optional subfields as in napari's ScaleBarOverlay (e.g., visible: bool, unit: str, gridded: bool)
text_overlay_properties dict Optional subfields as in napari's TextOverlay (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 (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 or HDF5, while OME-TIFF 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.

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

License

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

Version:

  • 0.2.0

Last updated:

  • 2026-03-17

First released:

  • 2026-03-14

License:

  • MIT

Supported data:

  • Information not submitted

Plugin type:

Open extension:

Save extension:

Python versions supported:

Operating system:

  • Information not submitted

Requirements:

  • numpy>=1.13.3
Website by the napari team, original design by CZI. Go to napari main website.