Skip to content

nd2

nd2: A Python library for reading and writing ND2 files.

ND2File

Main objecting for opening and extracting data from an nd2 file.

with nd2.ND2File("path/to/file.nd2") as nd2_file:
    ...

The key metadata outputs are:

Some files may also have:

Tip

For a simple way to read nd2 file data into an array, see nd2.imread.

Parameters:

  • path (Path | str) –

    Filename of an nd2 file.

  • validate_frames (bool, default: False ) –

    Whether to verify (and attempt to fix) frames whose positions have been shifted relative to the predicted offset (i.e. in a corrupted file). This comes at a slight performance penalty at file open, but may "rescue" some corrupt files. by default False.

  • search_window (int, default: 100 ) –

    When validate_frames is true, this is the search window (in KB) that will be used to try to find the actual chunk position. by default 100 KB

attributes: Attributes cached property

Core image attributes.

Example Output

Attributes(
    bitsPerComponentInMemory=16,
    bitsPerComponentSignificant=16,
    componentCount=2,
    heightPx=32,
    pixelDataType="unsigned",
    sequenceCount=60,
    widthBytes=128,
    widthPx=32,
    compressionLevel=None,
    compressionType=None,
    tileHeightPx=None,
    tileWidthPx=None,
    channelCount=2,
)

Returns:

binary_data: BinaryLayers | None cached property

Return binary layers embedded in the file.

The returned BinaryLayers object is an immutable sequence of BinaryLayer objects, one for each binary layer in the file (there will usually be a binary layer associated with each channel in the dataset).

Each BinaryLayer object in the sequence has a name attribute, and a data attribute which is list of numpy arrays (or None if there was no binary mask for that frame). The length of the list will be the same as the number of sequence frames in this file (i.e. self.attributes.sequenceCount). BinaryLayers can be indexed directly with an integer corresponding to the frame index.

Both the BinaryLayers and individual BinaryLayer objects can be cast to a numpy array with np.asarray(), or by using the .asarray() method

Returns:

  • BinaryLayers | None

    The binary layers embedded in the file, or None if there are no binary layers.

Examples:

>>> f = ND2File("path/to/file.nd2")
>>> f.binary_data
<BinaryLayers with 4 layers>
>>> first_layer = f.binary_data[0]  # the first binary layer
>>> first_layer
BinaryLayer(name='attached Widefield green (green color)',
comp_name='Widefield Green', comp_order=2, color=65280, color_mode=0,
state=524288, file_tag='RleZipBinarySequence_1_v1', layer_id=2)
>>> first_layer.data  # list of arrays
# you can also index in to the BinaryLayers object itself
>>> first_layer[0]  # get binary data for first frame (or None if missing)
>>> np.asarray(first_layer)  # cast to array matching shape of full sequence
>>> np.asarray(f.binary_data).shape  # cast all layers to array
(4, 3, 4, 5, 32, 32)

closed: bool property

Return True if the file is closed.

components_per_channel: int property

Number of components per channel (e.g. 3 for rgb).

custom_data: dict[str, Any] cached property

Dict of various unstructured custom metadata.

dtype: np.dtype cached property

Image data type.

experiment: list[ExpLoop] cached property

Loop information for each axis of an nD acquisition.

Example Output
[
    TimeLoop(
        count=3,
        nestingLevel=0,
        parameters=TimeLoopParams(
            startMs=0.0,
            periodMs=1.0,
            durationMs=0.0,
            periodDiff=PeriodDiff(
                avg=3674.199951171875,
                max=3701.219970703125,
                min=3647.179931640625,
            ),
        ),
        type="TimeLoop",
    ),
    ZStackLoop(
        count=5,
        nestingLevel=1,
        parameters=ZStackLoopParams(
            homeIndex=2,
            stepUm=1.0,
            bottomToTop=True,
            deviceName="Ti2 ZDrive",
        ),
        type="ZStackLoop",
    ),
]

Returns:

is_legacy: bool property

Whether file is a legacy nd2 (JPEG2000) file.

is_rgb: bool property

Whether the image is rgb (i.e. it has 3 or 4 components per channel).

loop_indices: tuple[dict[str, int], ...] cached property

Return a tuple of dicts of loop indices for each frame.

Examples:

>>> with nd2.ND2File("path/to/file.nd2") as f:
...     f.loop_indices
(
    {'Z': 0, 'T': 0, 'C': 0},
    {'Z': 0, 'T': 0, 'C': 1},
    {'Z': 0, 'T': 0, 'C': 2},
    ...
)

metadata: Metadata cached property

Various metadata (will be dict only if legacy format).

Example output
Metadata(
    contents=Contents(channelCount=2, frameCount=15),
    channels=[
        Channel(
            channel=ChannelMeta(
                name="Widefield Green",
                index=0,
                color=Color(r=91, g=255, b=0, a=1.0),
                emissionLambdaNm=535.0,
                excitationLambdaNm=None,
            ),
            loops=LoopIndices(
                NETimeLoop=None, TimeLoop=0, XYPosLoop=None, ZStackLoop=1
            ),
            microscope=Microscope(
                objectiveMagnification=10.0,
                objectiveName="Plan Fluor 10x Ph1 DLL",
                objectiveNumericalAperture=0.3,
                zoomMagnification=1.0,
                immersionRefractiveIndex=1.0,
                projectiveMagnification=None,
                pinholeDiameterUm=None,
                modalityFlags=["fluorescence"],
            ),
            volume=Volume(
                axesCalibrated=[True, True, True],
                axesCalibration=[0.652452890023035, 0.652452890023035, 1.0],
                axesInterpretation=["distance", "distance", "distance"],
                bitsPerComponentInMemory=16,
                bitsPerComponentSignificant=16,
                cameraTransformationMatrix=[
                    -0.9998932296054086,
                    -0.014612644841559427,
                    0.014612644841559427,
                    -0.9998932296054086,
                ],
                componentCount=1,
                componentDataType="unsigned",
                voxelCount=[32, 32, 5],
                componentMaxima=[0.0],
                componentMinima=[0.0],
                pixelToStageTransformationMatrix=None,
            ),
        ),
        Channel(
            channel=ChannelMeta(
                name="Widefield Red",
                index=1,
                color=Color(r=255, g=85, b=0, a=1.0),
                emissionLambdaNm=620.0,
                excitationLambdaNm=None,
            ),
            loops=LoopIndices(
                NETimeLoop=None, TimeLoop=0, XYPosLoop=None, ZStackLoop=1
            ),
            microscope=Microscope(
                objectiveMagnification=10.0,
                objectiveName="Plan Fluor 10x Ph1 DLL",
                objectiveNumericalAperture=0.3,
                zoomMagnification=1.0,
                immersionRefractiveIndex=1.0,
                projectiveMagnification=None,
                pinholeDiameterUm=None,
                modalityFlags=["fluorescence"],
            ),
            volume=Volume(
                axesCalibrated=[True, True, True],
                axesCalibration=[0.652452890023035, 0.652452890023035, 1.0],
                axesInterpretation=["distance", "distance", "distance"],
                bitsPerComponentInMemory=16,
                bitsPerComponentSignificant=16,
                cameraTransformationMatrix=[
                    -0.9998932296054086,
                    -0.014612644841559427,
                    0.014612644841559427,
                    -0.9998932296054086,
                ],
                componentCount=1,
                componentDataType="unsigned",
                voxelCount=[32, 32, 5],
                componentMaxima=[0.0],
                componentMinima=[0.0],
                pixelToStageTransformationMatrix=None,
            ),
        ),
    ],
)

Returns:

nbytes: int property

Total bytes of image data.

ndim: int cached property

Number of dimensions (i.e. len(self.shape)).

path: str property

Path of the image.

rois: dict[int, ROI] cached property

Return dict of {id: ROI} for all ROIs found in the metadata.

Returns:

  • dict[int, ROI]

    The dict of ROIs is keyed by the ROI ID.

shape: tuple[int, ...] cached property

Size of each axis.

Examples:

>>> ndfile.shape
(3, 5, 2, 512, 512)

size: int property

Total number of voxels in the volume (the product of the shape).

sizes: Mapping[str, int] cached property

Names and sizes for each axis.

This is an ordered dict, with the same order as the corresponding shape

Examples:

>>> ndfile.sizes
{'T': 3, 'Z': 5, 'C': 2, 'Y': 512, 'X': 512}
>>> ndfile.shape
(3, 5, 2, 512, 512)

text_info: TextInfo cached property

Miscellaneous text info.

Example Output
{
    'description': 'Metadata:\r\nDimensions: T(3) x XY(4) x λ(2) x Z(5)...'
    'capturing': 'Flash4.0, SN:101412\r\nSample 1:\r\n  Exposure: 100 ms...'
    'date': '9/28/2021  9:41:27 AM',
    'optics': 'Plan Fluor 10x Ph1 DLL'
}

Returns:

  • TextInfo | dict

    If the file is a legacy nd2 file, a dict is returned. Otherwise, a TextInfo object is returned.

version: tuple[int, ...] cached property

Return the file format version as a tuple of ints.

Likely values are:

  • (1, 0) = a legacy nd2 file (JPEG2000)
  • (2, 0), (2, 1) = non-JPEG2000 nd2 with xml metadata
  • (3, 0) = new format nd2 file with lite variant metadata
  • (-1, -1) =

Returns:

  • tuple[int, ...]

    The file format version as a tuple of ints.

Raises:

  • ValueError

    If the file is not a valid nd2 file.

asarray(position: int | None = None) -> np.ndarray

Read image into a numpy.ndarray.

For a simple way to read a file into a numpy array, see nd2.imread.

Parameters:

  • position (int, default: None ) –

    A specific XY position to extract, by default (None) reads all.

Returns:

Raises:

  • ValueError

    if position is a string and is not a valid position name

  • IndexError

    if position is provided and is out of range

close() -> None

Close file.

Note

Files are best opened using a context manager:

with nd2.ND2File("path/to/file.nd2") as nd2_file:
    ...

This will automatically close the file when the context exits.

events(*, orient: Literal['records', 'list', 'dict'] = 'records', null_value: Any = float('nan')) -> ListOfDicts | DictOfLists | DictOfDicts

Return tabular data recorded for each frame and/or event of the experiment.

This method returns tabular data in the format specified by the orient argument: - 'records' : list of dict - [{column -> value}, ...] (default) - 'dict' : dict of dict - {column -> {index -> value}, ...} - 'list' : dict of list - {column -> [value, ...]}

All return types are passable to pd.DataFrame(). It matches the tabular data reported in the Image Properties > Recorded Data tab of the NIS Viewer.

There will be a column for each tag in the CustomDataV2_0 section of ND2File.custom_data, as well columns for any events recorded in the data. Not all cells will be populated, and empty cells will be filled with null_value (default float('nan')).

Legacy ND2 files are not supported.

Parameters:

  • orient (('records', 'dict', 'list'), default: 'records' ) –

    The format of the returned data. See pandas.DataFrame - 'records' : list of dict -[{column -> value}, ...](default) - 'dict' : dict of dict -{column -> {index -> value}, ...}- 'list' : dict of list -{column -> [value, ...]}`

  • null_value (Any, default: float('nan') ) –

    The value to use for missing data.

Returns:

  • ListOfDicts | DictOfLists | DictOfDicts

    Tabular data in the format specified by orient.

frame_metadata(seq_index: int | tuple) -> FrameMetadata | dict

Metadata for specific frame.

👀 See also: metadata

This includes the global metadata from the metadata function. (will be dict if legacy format).

Example output
FrameMetadata(
    contents=Contents(channelCount=2, frameCount=15),
    channels=[
        FrameChannel(
            channel=ChannelMeta(
                name="Widefield Green",
                index=0,
                color=Color(r=91, g=255, b=0, a=1.0),
                emissionLambdaNm=535.0,
                excitationLambdaNm=None,
            ),
            loops=LoopIndices(
                NETimeLoop=None, TimeLoop=0, XYPosLoop=None, ZStackLoop=1
            ),
            microscope=Microscope(
                objectiveMagnification=10.0,
                objectiveName="Plan Fluor 10x Ph1 DLL",
                objectiveNumericalAperture=0.3,
                zoomMagnification=1.0,
                immersionRefractiveIndex=1.0,
                projectiveMagnification=None,
                pinholeDiameterUm=None,
                modalityFlags=["fluorescence"],
            ),
            volume=Volume(
                axesCalibrated=[True, True, True],
                axesCalibration=[0.652452890023035, 0.652452890023035, 1.0],
                axesInterpretation=["distance", "distance", "distance"],
                bitsPerComponentInMemory=16,
                bitsPerComponentSignificant=16,
                cameraTransformationMatrix=[
                    -0.9998932296054086,
                    -0.014612644841559427,
                    0.014612644841559427,
                    -0.9998932296054086,
                ],
                componentCount=1,
                componentDataType="unsigned",
                voxelCount=[32, 32, 5],
                componentMaxima=[0.0],
                componentMinima=[0.0],
                pixelToStageTransformationMatrix=None,
            ),
            position=Position(
                stagePositionUm=StagePosition(
                    x=26950.2, y=-1801.6000000000001, z=494.3
                ),
                pfsOffset=None,
                name=None,
            ),
            time=TimeStamp(
                absoluteJulianDayNumber=2459486.0682717753,
                relativeTimeMs=580.3582921028137,
            ),
        ),
        FrameChannel(
            channel=ChannelMeta(
                name="Widefield Red",
                index=1,
                color=Color(r=255, g=85, b=0, a=1.0),
                emissionLambdaNm=620.0,
                excitationLambdaNm=None,
            ),
            loops=LoopIndices(
                NETimeLoop=None, TimeLoop=0, XYPosLoop=None, ZStackLoop=1
            ),
            microscope=Microscope(
                objectiveMagnification=10.0,
                objectiveName="Plan Fluor 10x Ph1 DLL",
                objectiveNumericalAperture=0.3,
                zoomMagnification=1.0,
                immersionRefractiveIndex=1.0,
                projectiveMagnification=None,
                pinholeDiameterUm=None,
                modalityFlags=["fluorescence"],
            ),
            volume=Volume(
                axesCalibrated=[True, True, True],
                axesCalibration=[0.652452890023035, 0.652452890023035, 1.0],
                axesInterpretation=["distance", "distance", "distance"],
                bitsPerComponentInMemory=16,
                bitsPerComponentSignificant=16,
                cameraTransformationMatrix=[
                    -0.9998932296054086,
                    -0.014612644841559427,
                    0.014612644841559427,
                    -0.9998932296054086,
                ],
                componentCount=1,
                componentDataType="unsigned",
                voxelCount=[32, 32, 5],
                componentMaxima=[0.0],
                componentMinima=[0.0],
                pixelToStageTransformationMatrix=None,
            ),
            position=Position(
                stagePositionUm=StagePosition(
                    x=26950.2, y=-1801.6000000000001, z=494.3
                ),
                pfsOffset=None,
                name=None,
            ),
            time=TimeStamp(
                absoluteJulianDayNumber=2459486.0682717753,
                relativeTimeMs=580.3582921028137,
            ),
        ),
    ],
)

Parameters:

  • seq_index (Union[int, tuple]) –

    frame index

Returns:

is_supported_file(path: StrOrPath) -> bool staticmethod

Return True if the file is supported by this reader.

ome_metadata(*, include_unstructured: bool = True, tiff_file_name: str | None = None) -> OME

Return ome_types.OME metadata object for this file.

See the ome_types.OME documentation for details on this object.

Parameters:

  • include_unstructured (bool, default: True ) –

    Whether to include all available metadata in the OME file. If True, (the default), the unstructured_metadata method is used to fetch all retrievable metadata, and the output is added to OME.structured_annotations, where each key is the chunk key, and the value is a JSON-serialized dict of the metadata. If False, only metadata which can be directly added to the OME data model are included.

  • tiff_file_name (str | None, default: None ) –

    If provided, ome_types.model.TiffData block entries are added for each ome_types.model.Plane in the OME object, with the TiffData.uuid.file_name set to this value. (Useful for exporting to tiff.)

Examples:

import nd2

with nd2.ND2File("path/to/file.nd2") as f:
    ome = f.ome_metadata()
    xml = ome.to_xml()

open() -> None

Open file for reading.

Note

Files are best opened using a context manager:

with nd2.ND2File("path/to/file.nd2") as nd2_file:
    ...

This will automatically close the file when the context exits.

read_frame(frame_index: SupportsInt) -> np.ndarray

Read a single frame from the file, indexed by frame number.

to_dask(wrapper: bool = True, copy: bool = True) -> dask.array.core.Array

Create dask array (delayed reader) representing image.

This generally works well, but it remains to be seen whether performance is optimized, or if we're duplicating safety mechanisms. You may try various combinations of wrapper and copy, setting both to False will very likely cause segmentation faults in many cases. But setting one of them to False, may slightly improve read speed in certain cases.

Parameters:

  • wrapper (bool, default: True ) –

    If True (the default), the returned object will be a thin subclass of a dask.array.Array (a ResourceBackedDaskArray) that manages the opening and closing of this file when getting chunks via compute(). If wrapper is False, then a pure dask.array.core.Array will be returned. However, when that array is computed, it will incur a file open/close on every chunk that is read (in the _dask_block method). As such wrapper will generally be much faster, however, it may fail (i.e. result in segmentation faults) with certain dask schedulers.

  • copy (bool, default: True ) –

    If True (the default), the dask chunk-reading function will return an array copy. This can avoid segfaults in certain cases, though it may also add overhead.

Returns:

  • dask_array ( Array ) –

    A dask array representing the image data.

to_xarray(delayed: bool = True, squeeze: bool = True, position: int | None = None, copy: bool = True) -> xr.DataArray

Return a labeled xarray.DataArray representing image.

Xarrays are a powerful way to label and manipulate n-dimensional data with axis-associated coordinates.

array.dims will be populated according to image metadata, and coordinates will be populated based on pixel spacings. Additional metadata is available in array.attrs['metadata'].

Parameters:

  • delayed (bool, default: True ) –

    Whether the DataArray should be backed by dask array or numpy array, by default True (dask).

  • squeeze (bool, default: True ) –

    Whether to squeeze singleton dimensions, by default True

  • position (int, default: None ) –

    A specific XY position to extract, by default (None) reads all.

  • copy (bool, default: True ) –

    Only applies when delayed==True. See to_dask for details.

Returns:

  • DataArray

    xarray with all axes labeled.

unstructured_metadata(*, strip_prefix: bool = True, include: set[str] | None = None, exclude: set[str] | None = None) -> dict[str, Any]

Exposes, and attempts to decode, each metadata chunk in the file.

This is provided as a experimental fallback in the event that ND2File.experiment does not contain all of the information you need. No attempt is made to parse or validate the metadata, and the format of various sections, may change in future versions of nd2. Consumption of this metadata should use appropriate exception handling!

The 'ImageMetadataLV' chunk is the most likely to contain useful information, but if you're generally looking for "hidden" metadata, it may be helpful to look at the full output.

Parameters:

  • strip_prefix (bool, default: True ) –

    Whether to strip the type information from the front of the keys in the dict. For example, if True: uiModeFQ becomes ModeFQ and bUsePFS becomes UsePFS, etc... by default True

  • include (set[str] | None, default: None ) –

    If provided, only include the specified keys in the output. by default, all metadata sections found in the file are included.

  • exclude (set[str] | None, default: None ) –

    If provided, exclude the specified keys from the output. by default None

Returns:

  • dict[str, Any]

    A dict of the unstructured metadata, with keys that are the type of the metadata chunk (things like 'CustomData|RoiMetadata_v1' or 'ImageMetadataLV'), and values that are associated metadata chunk.

voxel_size(channel: int = 0) -> _util.VoxelSize

XYZ voxel size in microns.

Parameters:

  • channel (int, default: 0 ) –

    Channel for which to retrieve voxel info, by default 0. (Not yet implemented.)

Returns:

  • VoxelSize

    Named tuple with attrs x, y, and z.

write_tiff(dest: str | PathLike, *, include_unstructured_metadata: bool = True, progress: bool = False, on_frame: Callable[[int, int, dict[str, int]], None] | None | None = None, modify_ome: Callable[[ome_types.OME], None] | None = None) -> None

Export to an (OME)-TIFF file.

To include OME-XML metadata, use extension .ome.tif or .ome.tiff.

Parameters:

  • dest (str | PathLike) –

    The destination TIFF file.

  • include_unstructured_metadata ( bool, default: True ) –

    Whether to include unstructured metadata in the OME-XML. This includes all of the metadata that we can find in the ND2 file in the StructuredAnnotations section of the OME-XML (as mapping of metadata chunk name to JSON-encoded string). By default True.

  • progress (bool, default: False ) –

    Whether to display progress bar. If True and tqdm is installed, it will be used. Otherwise, a simple text counter will be printed to the console. By default False.

  • on_frame (Callable[[int, int, dict[str, int]], None] | None, default: None ) –

    A function to call after each frame is written. The function should accept three arguments: the current frame number, the total number of frames, and a dictionary of the current frame's indices (e.g. {"T": 0, "Z": 1}) (Useful for integrating custom progress bars or logging.)

  • modify_ome (Callable[[OME], None], default: None ) –

    A function to modify the OME metadata before writing it to the file. Accepts an ome_types.OME object and should modify it in place. (reminder: OME-XML is only written if the file extension is .ome.tif or .ome.tiff)

imread(file: Path | str, *, dask: bool = False, xarray: bool = False, validate_frames: bool = False) -> np.ndarray | xr.DataArray | dask.array.core.Array

Open file, return requested array type, and close file.

Parameters:

  • file (Path | str) –

    Filepath (str) or Path object to ND2 file.

  • dask (bool, default: False ) –

    If True, returns a (delayed) dask.array.Array. This will avoid reading any data from disk until specifically requested by using .compute() or casting to a numpy array with np.asarray(). By default False.

  • xarray (bool, default: False ) –

    If True, returns an xarray.DataArray, array.dims will be populated according to image metadata, and coordinates will be populated based on pixel spacings. Additional metadata is available in array.attrs['metadata']. If dask is also True, will return an xarray backed by a delayed dask array. By default False.

  • validate_frames (bool, default: False ) –

    Whether to verify (and attempt to fix) frames whose positions have been shifted relative to the predicted offset (i.e. in a corrupted file). This comes at a slight performance penalty at file open, but may "rescue" some corrupt files. by default False.

Returns:

is_legacy(path: StrOrPath) -> bool

Return True if path is a legacy ND2 file.

Parameters:

Returns:

  • bool

    Whether the file is a legacy ND2 file.

is_supported_file(path: FileOrBinaryIO, open_: Callable[[StrOrPath], BinaryIO] = _open_binary) -> bool

Return True if path can be opened as an nd2 file.

Parameters:

Returns:

  • bool

    Whether the can be opened.

rescue_nd2(handle: BinaryIO | str, frame_shape: tuple[int, ...] = (), dtype: DTypeLike = 'uint16', max_iters: int | None = None, verbose: bool = True, chunk_start: bytes = _default_chunk_start) -> Iterator[np.ndarray]

Iterator that yields all discovered frames in a file handle.

In nd2 files, each "frame" contains XY and all channel info (both true channels as well as RGB components). Frames are laid out as (Y, X, C), and the frame_shape should match the expected frame size. If frame_shape is not provided, a guess will be made about the vector shape of each frame, but it may be incorrect.

Parameters:

  • handle (BinaryIO | str) –

    Filepath string, or binary file handle (For example handle = open('some.nd2', 'rb'))

  • frame_shape (Tuple[int, ...], default: () ) –

    expected shape of each frame, by default a 1 dimensional array will be yielded for each frame, which can be reshaped later if desired. NOTE: nd2 frames are generally ordered as (height, width, true_channels, rgbcomponents). So unlike numpy, which would use (channels, Y, X), you should use (Y, X, channels)

  • dtype (dtype, default: 'uint16' ) –

    Data type, by default np.uint16

  • max_iters (Optional[int], default: None ) –

    A maximum number of frames to yield, by default will yield until the end of the file is reached

  • verbose (bool, default: True ) –

    whether to print info

  • chunk_start (bytes, default: _default_chunk_start ) –

    The bytes that start each chunk, by default 0x0ABECEDA.to_bytes(4, "little")

Yields:

  • ndarray

    each discovered frame in the file

Examples:

>>> with open('some_bad.nd2', 'rb') as fh:
>>>     frames = rescue_nd2(fh, (512, 512, 4), 'uint16')
>>>     ary = np.stack(frames)

You will likely want to reshape ary after that.