limnd2 package¶
A Python library for reading and writing .nd2 files produced by Nikon NIS-Elements Software.
Built upon tlambert03/nd2 with a compatible drop-in interface, adding write capabilities and extended metadata support.
Warning
This library is still in active development.
Current version: 0.3.0.
Until 1.0, behavior and API can change, and some changes may be released without a version number bump.
GitHub Issues and Pull Requests are currently disabled.
If you have a problem or question, contact: techsupp@lim.cz.
Installation¶
Prerequisites¶
Base limnd2 requires:
- python>=3.10
- numpy
- ome_types
Optional extras enable specific workflows:
limnd2[results]- load analysis tables from.h5files (h5py,pandas)limnd2[commonff]- shared image format deps (Pillow,tifffile,zarr)limnd2[ome-zarr]- OME-Zarr export and tools (dask,ome-zarr,zarr,fsspec,s3fs)limnd2[legacy]- read legacy JPEG2000 ND2 (imagecodecs)limnd2[all]- main runtime extras (does not includeome-zarryet)
Install examples from our package index:
pip install --index-url https://pypi.laboratory-imaging.com/simple limnd2
pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[results]"
pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[commonff,legacy]"
pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[ome-zarr]"
pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[all]"
uv pip install --index-url https://pypi.laboratory-imaging.com/simple limnd2
uv pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[results]"
uv pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[commonff,legacy]"
uv pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[ome-zarr]"
uv pip install --index-url https://pypi.laboratory-imaging.com/simple "limnd2[all]"
Quick install check:
Manual Installation¶
This project uses pyproject.toml for dependency management and can be installed with either pip or uv.
Usage¶
OME-Zarr export¶
Install the extra:
Python API:
import limnd2
with limnd2.Nd2Reader("file.nd2") as reader:
reader.to_ome_zarr(
"file.ome.zarr",
include_binaries=True,
use_dask=True,
overwrite=True,
)
CLI:
limnd2-ome-zarr-export file.nd2
limnd2-ome-zarr-export file.nd2 --output-folder .\exports
limnd2-ome-zarr-export file.nd2 --s3-prefix s3://my-bucket/ome-zarr
GUI:
Reading .nd2 files¶
An example Python file showcasing how to read an .nd2 file with this library is found at GitHub repo for this page: example_reader.py.
You can read the following from .nd2 files:
- Summary image information - Quick overview of file dimensions, calibration, and acquisition details
- Image attributes - Structured data about width, height, component count, pixel types, and sequence count
- Image data - Raw image frames as NumPy arrays
- Metadata - Comprehensive channel information including wavelengths, microscope settings, and objectives
- Experiment data - Loop definitions (time, Z-stack, multipoint) organizing image sequences
- Text information - Descriptive text metadata and microscope settings stored as strings
- Other data - Binary layers, ROIs, and additional custom data
Note
Metadata may not be present if the image is a simple RGB or Mono image. Experiment data may not be present if the file contains just one frame.
Opening .nd2 file¶
An .nd2 file can be opened using Nd2Reader class like this:
However it is recommended to open .nd2 files (especially when writing) using with statement to automatically close the file.
| example_reader.py | |
|---|---|
Summary image information¶
Quick access to information about the file can be gained with the generalImageInfo helper function:
| example_reader.py | |
|---|---|
See example output
Summary information
filename: file.nd2
path: C:\Users\user\Desktop\nd2_files
bit_depth: 32bit float
loops: XY(25), Z(5)
dimension: 1024 x 1024 (2 comps 32bit float) x 125 frames: XY(25), Z(5)
file_size: 6732537856
frame_res: 1024 x 1024
volume_size: 40MB
sizes: 6GB on disk, 8MB frame, 40MB volume
calibration: 0.432 µm/px
mtime: 06/07/21 14:17:56
app_created: NIS-Elements AR 5.20.00 (Build 1423)
Image attributes¶
Image attributes dataclass mostly contains information about dimensions of an image like width and height, number of components and number of frames in nd2 file.
For all properties and methods of this dataclass see attributes.py.
To get image attributes use imageAttributes attribute of Nd2Reader instance created in previous step.
| example_reader.py | |
|---|---|
Then you can use following properties to get information about the file:
| example_reader.py | |
|---|---|
See example output
Image data¶
This library uses NumPy arrays to store image data found in the .nd2 file, if you want to access image data itself, you can do so by using .image() method with index of the image you want to get like this:
| example_reader.py | |
|---|---|
See example output
If you want to get all images in the .nd2 file, use a for loop with frameCount property from image attributes.
| example_reader.py | |
|---|---|
Metadata¶
Metadata in .nd2 file contain a lot of additional data about the image, especially about planes, this information includes:
- plane name
- modality
- filter path
- sample settings
- fluorescent probe
- much more, see metadata.py for full information about
.nd2metadata
To get metadata, use pictureMetadata attribute like this:
| example_reader.py | |
|---|---|
To iterate over planes in the image, use the channels property from metadata, then use .sampleSettings() to get sample settings for each plane.
With channel and settings stored in separate variables, you can then access selected attributes like this:
See example output
Channel name: DETECTOR A
Modality: Camera AUX
Emission wavelength: 520.0
Excitation wavelength: 488.0
Camera name Nikon A1 LFOV
Microscope name Ti2 Microscope
Objective magnification 40.0
Channel name: DETECTOR B
Modality: Camera AUX
Emission wavelength: 650.0
Excitation wavelength: 488.0
Camera name Nikon A1 LFOV
Microscope name Ti2 Microscope
Objective magnification 40.0
Experiment data¶
Experiments in .nd2 files define how image sequences are organized and looped. The most common types of loops include:
- Time Loop (
timeloop): A sequence of images captured over time. - Z-Stack (
zstack): Frames stacked along the z-axis, representing different focal planes. - Multi-Point (
multipoint): Images captured at multiple specified locations (points) with known coordinates.
An image can have no experiment, a single experiment, or a combination of multiple experiments.
To obtain data structure with information about used experiments, use experiment property.
| example_reader.py | |
|---|---|
Then to see what kind of experiment .nd2 file contains, iterate over this structure (when present):
| example_reader.py | |
|---|---|
See example output
Now if we want to access attributes and methods for specific loop type, we can use .findLevel() method with ExperimentLoopType type as parameter, in this example we search for Z-Stack experiment = use value ExperimentLoopType.eEtZStackLoop.
Then we can access data for this experiment through parameters of this experiment, in this example we use attributes and properties of ExperimentZStackLoop.
See example output
For all attributes of all experiments type look into experiment.py
Text information¶
More information about components can be in imageTextInfo dataclass, though this information is stored as a string:
| example_reader.py | |
|---|---|
See example output
More information
imageId:
type:
group:
sampleId:
author:
description: Metadata:
Dimensions: XY(25) x Z(5)
Camera Name: Nikon A1 LFOV
Numerical Aperture: 1.15
Refractive Index: 1.333
Number of Picture Planes: 2
Plane #1:
Name: DETECTOR A
Component Count: 1
Modality: AUX
Microscope Settings: Microscope: Ti2 Microscope
External Phase, position: 0
Polarizer, position: Out
DIC Prism, position: In
Bertrand Lens, position: Out
Nikon Ti2, FilterChanger(Turret-Lo): 1 (Empty)
Nikon Ti2, FilterChanger(Turret-Up): 1 (Empty)
Nikon Ti2, Shutter(FL-Lo): Closed
Nikon Ti2, Shutter(FL-Up): Closed
LightPath: L100
Analyzer Slider: Extracted
Condenser: 3 (OPEN)
PFS, state: Off
PFS, offset: 5700
PFS, mirror: Inserted
PFS, Dish Type: Glass
Zoom: 1.00x
Eyepiece Ports:
Port 1: Off (Camera)
Port 2: On (Eye)
LAPP Upper Ports:
Port 1: Off (1)
Port 2: On (2)
LAPP Lower Ports:
Port 1: Off (H-TIRF Direct XY-F)
Port 2: Off (2)
Port 3: On (3)
H-TIRF X: 0.0
H-TIRF Y: 0.0
H-TIRF Focus: 0.0
H-TIRF X: 0.0
H-TIRF Y: 0.0
H-TIRF Focus: 0.0
E-TIRF1 Angle: 0.0
E-TIRF1 Direction: 0.0
NIDAQ, FilterChanger(FilterWheel): 1 (Empty)
NIDAQ, Shutter(LUN-F): Closed
NIDAQ, Shutter(LUN4): Closed
NIDAQ, Shutter(AUX1): Closed
NIDAQ, Shutter(EPI): Closed
NIDAQ, MultiLaser(LUN-F):
Line:1; ExW:405; Power: 34.8; On
NIDAQ, MultiLaser(LUN4):
Line:1; ExW:405; Power: 30.0; Off
Line:2; ExW:488; Power: 30.0; Off
Line:3; ExW:561; Power: 30.0; On
Line:4; ExW:640; Power: 30.0; Off
Plane #2:
Name: DETECTOR B
Component Count: 1
Modality: AUX
Microscope Settings: Microscope: Ti2 Microscope
External Phase, position: 0
Polarizer, position: Out
DIC Prism, position: In
Bertrand Lens, position: Out
Nikon Ti2, FilterChanger(Turret-Lo): 1 (Empty)
Nikon Ti2, FilterChanger(Turret-Up): 1 (Empty)
Nikon Ti2, Shutter(FL-Lo): Closed
Nikon Ti2, Shutter(FL-Up): Closed
LightPath: L100
Analyzer Slider: Extracted
Condenser: 3 (OPEN)
PFS, state: Off
PFS, offset: 5700
PFS, mirror: Inserted
PFS, Dish Type: Glass
Zoom: 1.00x
Eyepiece Ports:
Port 1: Off (Camera)
Port 2: On (Eye)
LAPP Upper Ports:
Port 1: Off (1)
Port 2: On (2)
LAPP Lower Ports:
Port 1: Off (H-TIRF Direct XY-F)
Port 2: Off (2)
Port 3: On (3)
H-TIRF X: 0.0
H-TIRF Y: 0.0
H-TIRF Focus: 0.0
H-TIRF X: 0.0
H-TIRF Y: 0.0
H-TIRF Focus: 0.0
E-TIRF1 Angle: 0.0
E-TIRF1 Direction: 0.0
NIDAQ, FilterChanger(FilterWheel): 1 (Empty)
NIDAQ, Shutter(LUN-F): Closed
NIDAQ, Shutter(LUN4): Closed
NIDAQ, Shutter(AUX1): Closed
NIDAQ, Shutter(EPI): Closed
NIDAQ, MultiLaser(LUN-F):
Line:1; ExW:405; Power: 34.8; On
NIDAQ, MultiLaser(LUN4):
Line:1; ExW:405; Power: 30.0; Off
Line:2; ExW:488; Power: 30.0; Off
Line:3; ExW:561; Power: 30.0; On
Line:4; ExW:640; Power: 30.0; Off
Z Stack Loop: 5
- Step: 4 µm
- Device: Ti2 ZDrive
capturing: Nikon A1 LFOV
sampling:
location:
date: 9/22/2068 12:28:28 AM
conclusion:
info1:
info2:
optics: Apo LWD 40x WI λS DIC N2
Other data and metadata¶
Attributes, experiments, metadata, and image data are the most important parts of an .nd2 file, which is why they were the focus of this guide. The limnd2 module can also access information about binary layers, ROIs, and other data stored in the file. However, at this time, we do not provide a guide on how to read these additional components.
If this causes any issues or you need further clarification, contact techsupp@lim.cz.
Writing to .nd2 file¶
This package also allows you to write into and create .nd2 files using Nd2Writer class, an example of how to do this, you can look into example_writer.py, which will also be described below.
In the example below we will create new .nd2 file with preset width, height, bits per component, component count and sequence count. Instead of using actual image data we will use NumPy to generate arrays filled with random noise, which we will store in the result file.
Here are the settings that will be used to generate image attributes and NumPy arrays with image data.
| example_writer.py | |
|---|---|
We will also add 2 experiments in the file to showcase how Experiment creation works. We have 10 frames as defined above, we will split them into 5 timeloop indices and 2 Z-stack indices, we will also define step between frames on each axis:
| example_writer.py | |
|---|---|
Opening / creating .nd2 file for writing¶
With constants defined, we can open .nd2 file for reading using Nd2Writer class and with clause for automatic file closure.
| example_writer.py | |
|---|---|
Info
Nd2Writer can only be created with new, non existing .nd2 files.
Tip
As explained below in writing image data section, image data can only be written after
image attributes are set, but if you want to write image data into .nd2 file without knowing how many frames there is
(for example with continuous writing),
you can pass ImageAttributes instance when creating .nd2 using custom chunker argument as shown below.
Setting ImageAttributes this way will not store them in .nd2 file and you still have to store them at some point,
however you can do so after you know how many frames there is.
attributes = limnd2.attributes.ImageAttributes.create(
width = WIDTH,
height = HEIGHT,
component_count = COMPONENT_COUNT,
bits = BITS,
sequence_count = ... # will be set later
)
with limnd2.Nd2Writer("outfile.nd2", chunker_kwargs={"with_image_attributes": attributes}) as nd2:
# you can now set image data without setting attributes
Creating and writing image attributes¶
Image attributes can be created using ImageAttributes.create() method, we can simply assign those to imageAttributes property of Nd2Writer.
| example_writer.py | |
|---|---|
Creating and writing image data¶
Danger
Image data can only be written after image attributes are set either by setting imageAttributes as shown here or by using with_image_attributes as shown in Tip box here.
After writing image attributes, we can create random noise data and store them in the .nd2 file, for this we will use create_random_noise() function (see example_writer.py for function definition) and send the result array to setImage() method.
Important
You must manually keep track of index or the image you are storing, also as we with with random noise, the order in which images are inserted does not matter, however the images must be inserted in correct order with respect to used experiments.
This is especially important if you are converting multidimensional image sequence to .nd2 file.
| example_writer.py | |
|---|---|
Creating and writing experiments¶
Experiments can be created with ExperimentFactory from experiment_factory module. In this example, we will set count and step for timeloop and zstack experiments and then create the experiment data structure with the createExperiment() method.
| example_writer.py | |
|---|---|
Creating and writing metadata¶
Metadata are created in similar way using MetadataFactory from metadata_factory module.
On the constructor we provide microscope settings for all planes, then we use addPlane() method to add planes to the metadata with their settings, finally we create metadata with createMetadata() method and assign it to [pictureMetadata] property of [Nd2Writer].
Saving file¶
As we used with context manager, file is automatically saved and closed, if you did not use context manager, you need to manually call .finalize() method from Nd2Writer instance.
With this done, you can now run the Python script and open the file in NIS Elements.
Full API reference¶
Here are the most important files in this library and an overview of what they contain:
- nd2.py - contains classes for opening ND2 files for reading and writing
- attributes.py - contains data structures about image attributes (width, height, component count, sequence count, ...)
- experiment.py - contains data structures about experiment loops (timeloop, z-stack, multipoint, ...)
- experiment_factory.py - contains helpers for creating experiment data structure
- metadata.py - contains data structures about image attributes (width, height, component count, sequence count, ...)
Compatibility layer:
- nd2file.py - serves as a wrapper around limnd2 library to provide same interface to nd2 library by Talley Lambert
Feedback¶
For questions, bug reports, or feature requests, contact techsupp@lim.cz.