Skip to content

nd2

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

Modules:

  • index

    Index ND2 files and print the results as a table.

  • jobs

    JOBS support.

  • structures

    Dataclasses and other structures used for metadata.

  • tiff

    Functions for converting .nd2 to .tiff files.

Classes:

  • BinaryLayer

    Wrapper for data from a single binary layer in an nd2.ND2File.

  • BinaryLayers

    Sequence of Binary Layers found in an ND2 file.

  • ND2File

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

Functions:

  • imread

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

  • is_legacy

    Return True if path is a legacy ND2 file.

  • is_supported_file

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

  • nd2_to_tiff

    Export an ND2 file to an (OME)-TIFF file.

  • rescue_nd2

    Iterator that yields all discovered frames in a file handle.

BinaryLayer dataclass

BinaryLayer(
    data: list[ndarray | None],
    name: str,
    file_tag: str,
    comp_name: str | None,
    comp_order: int | None,
    color: int | None,
    color_mode: int | None,
    state: int | None,
    layer_id: int | None,
    coordinate_shape: tuple[int, ...],
)

Wrapper for data from a single binary layer in an nd2.ND2File.

A "layer" is a set of binary data that can be associated with a specific component in an ND2 file, such as a single channel.

This object behaves like a list[numpy.ndarray] | None. It will have a length matching the number of frames in the file, with None for any frames that lack binary data.

Attributes:

  • data (list[ndarray] | None) –

    The data for each frame. If a frame has no binary data, the value will be None. Data will have the same length as the number of sequences in the file.

  • name (str) –

    The name of the binary layer.

  • comp_name (str) –

    The name of the associated component, if Any.

  • comp_order (int) –

    The order of the associated component, if Any.

  • color (int) –

    The color of the binary layer.

  • color_mode (int) –

    The color mode of the binary layer. I believe this is related to how colors are chosen in NIS-Elements software. Where "0" is direct color (i.e. use, the color value), "8" is color by 3D ... and I'm not sure about the rest :)

  • state (int) –

    The state of the binary layer. (meaning still unclear)

  • file_tag (str) –

    The key for the binary layer in the CustomData metadata, e.g. RleZipBinarySequence_1_v1

  • layer_id (int) –

    The ID of the binary layer.

  • coordinate_shape (tuple[int, ...]) –

    The shape of the coordinates for the associated nd2 file. This is used to reshape the data into a 3D array in asarray.

Methods:

  • asarray

    Stack all the frames into a single array.

frame_shape property

frame_shape: tuple[int, ...]

Shape (Y, X) of each mask in data.

asarray

asarray() -> ndarray | None

Stack all the frames into a single array.

If there are no frames, returns None.

Source code in src/nd2/_binary.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def asarray(self) -> np.ndarray | None:
    """Stack all the frames into a single array.

    If there are no frames, returns None.
    """
    frame_shape = self.frame_shape
    if frame_shape == (0, 0):
        return None

    # TODO: this is a bit of a hack (takes up memory), but it works for now
    # could do something with dask
    d = [
        i if i is not None else np.zeros(frame_shape, dtype="uint16")
        for i in self.data
    ]
    return cast(
        "np.ndarray", np.stack(d).reshape(self.coordinate_shape + frame_shape)
    )

BinaryLayers

BinaryLayers(data: list[BinaryLayer])

Sequence of Binary Layers found in an ND2 file.

This is the output type of ND2File.binary_data.

This object is a sequence of BinaryLayer objects, one for each binary layer in the file. Each layer has a name attribute, and a data attribute that is list of numpy arrays - one for each frame in the experiment - or None if the layer was not present in that frame.

The wrapper can be cast to a numpy array (with BinaryLayers.asarray() or np.asarray(BinaryLayers)) to stack all the layers into a single array. The output array will have shape (n_layers, *coord_shape, *frame_shape).

Methods:

  • asarray

    Stack all the layers/frames into a single array.

Source code in src/nd2/_binary.py
134
135
def __init__(self, data: list[BinaryLayer]) -> None:
    self._data = data

asarray

asarray() -> ndarray

Stack all the layers/frames into a single array.

The output array will have shape (n_layers, coord_shape, frame_shape).

Source code in src/nd2/_binary.py
159
160
161
162
163
164
165
166
167
168
169
def asarray(self) -> np.ndarray:
    """Stack all the layers/frames into a single array.

    The output array will have shape (n_layers, *coord_shape, *frame_shape).
    """
    out = []
    for bin_layer in self._data:
        d = bin_layer.asarray()
        if d is not None:
            out.append(d)
    return np.stack(out)

ND2File

ND2File(
    path: FileOrBinaryIO,
    *,
    validate_frames: bool = False,
    search_window: int = 100,
)

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

Methods:

Attributes:

Source code in src/nd2/_nd2file.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def __init__(
    self,
    path: FileOrBinaryIO,
    *,
    validate_frames: bool = False,
    search_window: int = 100,
) -> None:
    self._error_radius: int | None = (
        search_window * 1000 if validate_frames else None
    )
    self._rdr = ND2Reader.create(path, self._error_radius)
    self._path = self._rdr._path
    self._lock = threading.RLock()

attributes cached property

attributes: Attributes

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

binary_data: BinaryLayers | None

Return binary layers embedded in the file.

new in version 0.5.1

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 property

closed: bool

Return True if the file is closed.

components_per_channel property

components_per_channel: int

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

custom_data cached property

custom_data: dict[str, Any]

Dict of various unstructured custom metadata.

dtype cached property

dtype: dtype

Image data type.

experiment cached property

experiment: list[ExpLoop]

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 property

is_legacy: bool

Whether file is a legacy nd2 (JPEG2000) file.

is_rgb property

is_rgb: bool

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

loop_indices cached property

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

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

new in version 0.8.0

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

metadata: Metadata

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 property

nbytes: int

Total bytes of image data.

ndim cached property

ndim: int

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

path property

path: str

Path of the image.

rois cached property

rois: dict[int, ROI]

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

new in version 0.4.6

Returns:

  • dict[int, ROI]

    The dict of ROIs is keyed by the ROI ID.

shape cached property

shape: tuple[int, ...]

Size of each axis.

Examples:

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

size property

size: int

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

sizes cached property

sizes: Mapping[str, int]

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

text_info: TextInfo

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

version: tuple[int, ...]

Return the file format version as a tuple of ints.

new in version 0.6.1

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

asarray(position: int | None = None) -> 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

Source code in src/nd2/_nd2file.py
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
def asarray(self, 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, optional
        A specific XY position to extract, by default (None) reads all.

    Returns
    -------
    array : np.ndarray

    Raises
    ------
    ValueError
        if `position` is a string and is not a valid position name
    IndexError
        if `position` is provided and is out of range
    """
    final_shape = list(self.shape)
    if position is None:
        seqs: Sequence[int] = range(self._frame_count)
    else:
        if isinstance(position, str):
            try:
                position = self._position_names().index(position)
            except ValueError as e:
                raise ValueError(
                    f"{position!r} is not a valid position name"
                ) from e
        try:
            pidx = list(self.sizes).index(AXIS.POSITION)
        except ValueError as exc:
            if position > 0:  # pragma: no cover
                raise IndexError(
                    f"Position {position} is out of range. "
                    f"Only 1 position available"
                ) from exc
            seqs = range(self._frame_count)
        else:
            if position >= self.sizes[AXIS.POSITION]:
                raise IndexError(  # pragma: no cover
                    f"Position {position} is out of range. "
                    f"Only {self.sizes[AXIS.POSITION]} positions available"
                )

            ranges: list[range | tuple] = [range(x) for x in self._coord_shape]
            ranges[pidx] = (position,)
            coords = list(zip(*product(*ranges)))
            seqs = self._seq_index_from_coords(coords)  # type: ignore
            final_shape[pidx] = 1

    arr: np.ndarray = np.stack([self.read_frame(i) for i in seqs])
    return arr.reshape(final_shape)

close

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.

Source code in src/nd2/_nd2file.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def close(self) -> None:
    """Close file.

    !!! note

        Files are best opened using a context manager:

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

        This will automatically close the file when the context exits.
    """
    if not self.closed:
        self._rdr.close()

events

events(
    *,
    orient: Literal["records"] = ...,
    null_value: Any = ...,
) -> ListOfDicts
events(
    *, orient: Literal["list"], null_value: Any = ...
) -> DictOfLists
events(
    *, orient: Literal["dict"], null_value: Any = ...
) -> DictOfDicts
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.

new in version 0.6.1

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.

Source code in src/nd2/_nd2file.py
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
def events(
    self,
    *,
    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.

    !!! Tip "new in version 0.6.1"

    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`.
    """
    if orient not in ("records", "dict", "list"):  # pragma: no cover
        raise ValueError("orient must be one of 'records', 'dict', or 'list'")

    return self._rdr.events(orient=orient, null_value=null_value)

frame_metadata

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:

Source code in src/nd2/_nd2file.py
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
def frame_metadata(self, seq_index: int | tuple) -> FrameMetadata | dict:
    """Metadata for specific frame.

    :eyes: **See also:** [metadata][nd2.ND2File.metadata]

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

    ??? example "Example output"

        ```python
        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
    -------
    FrameMetadata | dict
        dict if legacy format, else FrameMetadata
    """
    idx = cast(
        "int",
        (
            self._seq_index_from_coords(seq_index)
            if isinstance(seq_index, tuple)
            else seq_index
        ),
    )
    return self._rdr.frame_metadata(idx)

is_supported_file staticmethod

is_supported_file(path: StrOrPath) -> bool

Return True if the file is supported by this reader.

Source code in src/nd2/_nd2file.py
112
113
114
115
@staticmethod
def is_supported_file(path: StrOrPath) -> bool:
    """Return `True` if the file is supported by this reader."""
    return is_supported_file(path)

jobs

jobs() -> JobsDict | None

Return JOBS metadata if the file was acquired using JOBS, else None.

new in version 0.11.0

JOBS is a software feature in NIS Elements for automated acquisition workflows. Files acquired with JOBS contain metadata about the job definition, including task definitions and wellplate configurations.

The metadata is returned as a dictionary, and there are a lot of possible types of JOBS definitions. You refer to nd2.jobs.types to see the typical structure of these dictionaries (inferred from real-world jobs files). But nd2.jobs.types should only ever be used for type hinting, and not imported at runtime.

Returns:

  • dict | None

    A dictionary with JOBS metadata, or None if the file was not acquired using JOBS. The dictionary contains:

    • "JobRunGUID": str - Unique identifier for the job run
    • "ProgramDesc": dict - Job description including JobDefType
    • "Job": dict | None - Full job definition (None if encrypted)
    • "ProtectedJob": dict | None - Encryption info (if encrypted)

Examples:

>>> with nd2.ND2File("path/to/jobs_file.nd2") as f:
...     if jobs := f.jobs():
...         print(jobs["JobRunGUID"])
...         if jobs["Job"]:
...             print(list(jobs["Job"]["Tasks"].keys()))
Source code in src/nd2/_nd2file.py
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
def jobs(self) -> JobsDict | None:
    """Return JOBS metadata if the file was acquired using JOBS, else None.

    !!! Tip "new in version 0.11.0"

    JOBS is a software feature in NIS Elements for automated acquisition workflows.
    Files acquired with JOBS contain metadata about the job definition,
    including task definitions and wellplate configurations.

    The metadata is returned as a dictionary, and there are a *lot* of possible
    types of JOBS definitions. You refer to `nd2.jobs.types` to see the typical
    structure of these dictionaries (inferred from real-world jobs files).
    But `nd2.jobs.types` should only ever be used for type hinting, and not imported
    at runtime.

    Returns
    -------
    dict | None
        A dictionary with JOBS metadata, or None if the file was not
        acquired using JOBS. The dictionary contains:

        - `"JobRunGUID"`: str - Unique identifier for the job run
        - `"ProgramDesc"`: dict - Job description including JobDefType
        - `"Job"`: dict | None - Full job definition (None if encrypted)
        - `"ProtectedJob"`: dict | None - Encryption info (if encrypted)

    Examples
    --------
    >>> with nd2.ND2File("path/to/jobs_file.nd2") as f:
    ...     if jobs := f.jobs():
    ...         print(jobs["JobRunGUID"])
    ...         if jobs["Job"]:
    ...             print(list(jobs["Job"]["Tasks"].keys()))
    """
    return self._rdr.jobs()

ome_metadata

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

Return ome_types.OME metadata object for this file.

new in version 0.7.0

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()
Source code in src/nd2/_nd2file.py
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
def ome_metadata(
    self, *, include_unstructured: bool = True, tiff_file_name: str | None = None
) -> OME:
    """Return `ome_types.OME` metadata object for this file.

    !!! Tip "new in version 0.7.0"

    See the [`ome_types.OME`][] documentation for details on this object.

    Parameters
    ----------
    include_unstructured : bool
        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
        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
    --------
    ```python
    import nd2

    with nd2.ND2File("path/to/file.nd2") as f:
        ome = f.ome_metadata()
        xml = ome.to_xml()
    ```
    """
    from ._ome import nd2_ome_metadata

    return nd2_ome_metadata(
        self,
        include_unstructured=include_unstructured,
        tiff_file_name=tiff_file_name,
    )

open

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.

Source code in src/nd2/_nd2file.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def open(self) -> None:
    """Open file for reading.

    !!! note

        Files are best opened using a context manager:

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

        This will automatically close the file when the context exits.
    """
    if self.closed:
        self._rdr.open()

read_frame

read_frame(frame_index: SupportsInt) -> ndarray

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

new in version 0.8.0

Source code in src/nd2/_nd2file.py
1249
1250
1251
1252
1253
1254
1255
1256
def read_frame(self, frame_index: SupportsInt) -> np.ndarray:
    """Read a single frame from the file, indexed by frame number.

    !!! Tip "new in version 0.8.0"
    """
    frame = self._rdr.read_frame(int(frame_index))
    frame.shape = self._raw_frame_shape
    return frame.transpose((2, 0, 1, 3)).squeeze()

to_dask

to_dask(wrapper: bool = True, copy: bool = True) -> 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.

Source code in src/nd2/_nd2file.py
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
def to_dask(self, 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
        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
        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: dask.array.Array
        A dask array representing the image data.
    """
    from dask.array.core import map_blocks

    chunks = [(1,) * x for x in self._coord_shape]
    chunks += [(x,) for x in self._frame_shape]
    dask_arr = map_blocks(
        self._dask_block,
        copy=copy,
        chunks=chunks,
        dtype=self.dtype,
    )
    if wrapper:
        from resource_backed_dask_array import ResourceBackedDaskArray

        # this subtype allows the dask array to re-open the underlying
        # nd2 file on compute.
        return ResourceBackedDaskArray.from_array(dask_arr, self)
    return dask_arr

to_xarray

to_xarray(
    delayed: bool = True,
    squeeze: bool = True,
    position: int | None = None,
    copy: bool = True,
) -> 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.

Source code in src/nd2/_nd2file.py
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
def to_xarray(
    self,
    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
        Whether the DataArray should be backed by dask array or numpy array,
        by default True (dask).
    squeeze : bool
        Whether to squeeze singleton dimensions, by default True
    position : int, optional
        A specific XY position to extract, by default (None) reads all.
    copy : bool
        Only applies when `delayed==True`.  See `to_dask` for details.

    Returns
    -------
    xr.DataArray
        xarray with all axes labeled.
    """
    import xarray as xr

    data = self.to_dask(copy=copy) if delayed else self.asarray(position)
    dims = list(self.sizes)
    coords = self._expand_coords(squeeze)
    if not squeeze:
        for missing_dim in set(coords).difference(dims):
            dims.insert(0, missing_dim)
        missing_axes = len(dims) - data.ndim
        if missing_axes > 0:
            data = data[(np.newaxis,) * missing_axes]

    if position is not None and not delayed and AXIS.POSITION in coords:
        # if it's delayed, we do this using isel below instead.
        coords[AXIS.POSITION] = [coords[AXIS.POSITION][position]]

    x = xr.DataArray(
        data,
        dims=dims,
        coords=coords,
        attrs={
            "metadata": {
                "metadata": self.metadata,
                "experiment": self.experiment,
                "attributes": self.attributes,
                "text_info": self.text_info,
            }
        },
    )
    if delayed and position is not None and AXIS.POSITION in coords:
        x = x.isel({AXIS.POSITION: [position]})
    return x.squeeze() if squeeze else x

unstructured_metadata

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.

new in version 0.4.3

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.

Source code in src/nd2/_nd2file.py
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
def unstructured_metadata(
    self,
    *,
    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.

    !!! Tip "new in version 0.4.3"

    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, optional
        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, optional
        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, optional
        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.
    """
    return self._rdr.unstructured_metadata(strip_prefix, include, exclude)

voxel_size

voxel_size(channel: int = 0) -> 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.

Source code in src/nd2/_nd2file.py
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
def voxel_size(self, channel: int = 0) -> _util.VoxelSize:
    """XYZ voxel size in microns.

    Parameters
    ----------
    channel : int
        Channel for which to retrieve voxel info, by default 0.
        (Not yet implemented.)

    Returns
    -------
    VoxelSize
        Named tuple with attrs `x`, `y`, and `z`.
    """
    return _util.VoxelSize(*self._rdr.voxel_size())

write_ome_zarr

write_ome_zarr(
    dest: str | PathLike,
    *,
    chunk_shape: tuple[int, ...]
    | Literal["auto"]
    | None = "auto",
    shard_shape: tuple[int, ...] | None = None,
    backend: ZarrBackend = "auto",
    progress: bool = False,
    position: int | None = None,
    force_series: bool = False,
    include_all_metadata: bool = True,
    include_labels: bool = True,
    version: Literal["0.5"] = "0.5",
    overwrite: bool = False,
) -> Path

Export to an OME-Zarr store.

new in version 0.11.0

Requires extras

In order to use write_ome_zarr you must install nd2 with an appropriate array-writing backend. zarr-python is the reference implementation, but tensorstore is faster.

  • pip install "nd2[ome-zarr-tensorstore]" (to use tensorstore backend)
  • pip install "nd2[ome-zarr]" (to use zarr-python backend)

Creates a Zarr v3 store with OME-NGFF 0.5 compliant metadata. Uses yaozarrs for metadata generation and either zarr-python or tensorstore for array writing.

Parameters:

  • dest

    (str | PathLike) –

    Destination path for the Zarr store. Will be created as a directory.

  • chunk_shape

    (tuple[int, ...] | 'auto' | None, default: 'auto' ) –

    Shape of chunks for the output array. If "auto" (default), determines optimal chunking based on data size. If None, uses a single chunk.

  • shard_shape

    (tuple[int, ...] | None, default: None ) –

    Shape of shards for sharded storage. If provided, enables Zarr v3 sharding where each shard contains multiple chunks. Useful for cloud storage to reduce number of objects.

  • backend

    ('zarr' | 'tensorstore' | 'auto', default: 'auto' ) –

    Backend library to use for writing arrays. - "tensorstore": Uses Google's tensorstore library - "zarr": Uses zarr-python - "auto": Tries to use tensorstore if installed, otherwise falls back to zarr-python. Raises ImportError if neither is available.

  • progress

    (bool, default: False ) –

    Whether to display a progress bar during writing.

  • position

    (int | None, default: None ) –

    If the ND2 file contains multiple positions (XY stage positions), export only this position index. If None, exports all positions as separate groups within the store.

  • force_series

    (bool, default: False ) –

    If True, use bioformats2raw layout even for single position files. This creates a store with OME/ directory and series metadata, with the image in a "0/" subdirectory. Default is False.

  • include_all_metadata

    (bool, default: True ) –

    If True (default), all unstructured metadata chunks found in the ND2 will be included in the OME-Zarr metadata as JSON-encoded strings. If False, only a minimal set of metadata will be included (those necessary for the OME-NGFF specification, along with the OME-XML metadata if present). Use True for maximum metadata retention, or False for a smaller store.

  • include_labels

    (bool, default: True ) –

    If True (default), export binary masks as OME-Zarr labels. Binary masks from the ND2 file will be written to a "labels" subdirectory within the image group. Each binary layer becomes a separate label with its own name. Has no effect if the file contains no binary data.

  • version

    ('0.5', default: '0.5' ) –

    OME-NGFF specification version to use. Currently only "0.5" is supported. This parameter is reserved for future use.

  • overwrite

    (bool, default: False ) –

    If True, overwrite the destination if it already exists.

Returns:

  • Path

    Path to the created Zarr store.

Raises:

  • ImportError

    If yaozarrs or the required backend library is not installed.

  • ValueError

    If the file contains unsupported data structures or invalid version.

Examples:

Basic export:

>>> import nd2
>>> with nd2.ND2File("experiment.nd2") as f:
...     f.write_ome_zarr("experiment.zarr")

Export with specific chunking:

>>> with nd2.ND2File("experiment.nd2") as f:
...     f.write_ome_zarr(
...         "experiment.zarr",
...         chunk_shape=(1, 1, 64, 256, 256),
...     )

Export using tensorstore backend:

>>> with nd2.ND2File("experiment.nd2") as f:
...     f.write_ome_zarr("experiment.zarr", backend="tensorstore")
Source code in src/nd2/_nd2file.py
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
def write_ome_zarr(
    self,
    dest: str | PathLike,
    *,
    chunk_shape: tuple[int, ...] | Literal["auto"] | None = "auto",
    shard_shape: tuple[int, ...] | None = None,
    backend: ZarrBackend = "auto",
    progress: bool = False,
    position: int | None = None,
    force_series: bool = False,
    include_all_metadata: bool = True,
    include_labels: bool = True,
    version: Literal["0.5"] = "0.5",
    overwrite: bool = False,
) -> Path:
    """Export to an OME-Zarr store.

    !!! Tip "new in version 0.11.0"

    !!!important "Requires extras"
        In order to use `write_ome_zarr` you must install `nd2` with an appropriate
        array-writing backend.  `zarr-python` is the reference implementation,
        but `tensorstore` is faster.

        - `pip install "nd2[ome-zarr-tensorstore]"` (to use tensorstore backend)
        - `pip install "nd2[ome-zarr]"` (to use zarr-python backend)

    Creates a Zarr v3 store with OME-NGFF 0.5 compliant metadata.
    Uses yaozarrs for metadata generation and either `zarr-python` or
    `tensorstore` for array writing.

    Parameters
    ----------
    dest : str | PathLike
        Destination path for the Zarr store. Will be created as a directory.
    chunk_shape : tuple[int, ...] | "auto" | None
        Shape of chunks for the output array. If "auto" (default), determines
        optimal chunking based on data size. If None, uses a single chunk.
    shard_shape : tuple[int, ...] | None
        Shape of shards for sharded storage. If provided, enables Zarr v3
        sharding where each shard contains multiple chunks. Useful for
        cloud storage to reduce number of objects.
    backend : "zarr" | "tensorstore" | "auto"
        Backend library to use for writing arrays.
        - "tensorstore": Uses Google's tensorstore library
        - "zarr": Uses zarr-python
        - "auto": Tries to use tensorstore if installed, otherwise falls back
        to zarr-python. Raises ImportError if neither is available.
    progress : bool
        Whether to display a progress bar during writing.
    position : int | None
        If the ND2 file contains multiple positions (XY stage positions),
        export only this position index. If None, exports all positions
        as separate groups within the store.
    force_series : bool
        If True, use bioformats2raw layout even for single position files.
        This creates a store with OME/ directory and series metadata,
        with the image in a "0/" subdirectory. Default is False.
    include_all_metadata : bool
        If True (default), *all* unstructured metadata chunks found in the ND2
        will be included in the OME-Zarr metadata as JSON-encoded strings.
        If False, only a minimal set of metadata will be included (those necessary
        for the OME-NGFF specification, along with the OME-XML metadata if present).
        Use `True` for maximum metadata retention, or `False` for a smaller store.
    include_labels : bool
        If True (default), export binary masks as OME-Zarr labels.
        Binary masks from the ND2 file will be written to a "labels"
        subdirectory within the image group. Each binary layer becomes
        a separate label with its own name. Has no effect if the file
        contains no binary data.
    version : "0.5"
        OME-NGFF specification version to use. Currently only "0.5" is
        supported. This parameter is reserved for future use.
    overwrite : bool
        If True, overwrite the destination if it already exists.

    Returns
    -------
    Path
        Path to the created Zarr store.

    Raises
    ------
    ImportError
        If yaozarrs or the required backend library is not installed.
    ValueError
        If the file contains unsupported data structures or invalid version.

    Examples
    --------
    Basic export:

    >>> import nd2
    >>> with nd2.ND2File("experiment.nd2") as f:
    ...     f.write_ome_zarr("experiment.zarr")

    Export with specific chunking:

    >>> with nd2.ND2File("experiment.nd2") as f:
    ...     f.write_ome_zarr(
    ...         "experiment.zarr",
    ...         chunk_shape=(1, 1, 64, 256, 256),
    ...     )

    Export using tensorstore backend:

    >>> with nd2.ND2File("experiment.nd2") as f:
    ...     f.write_ome_zarr("experiment.zarr", backend="tensorstore")
    """
    from ._ome_zarr import nd2_to_ome_zarr

    return nd2_to_ome_zarr(
        self,
        dest,
        chunk_shape=chunk_shape,
        shard_shape=shard_shape,
        backend=backend,
        progress=progress,
        position=position,
        force_series=force_series,
        include_all_metadata=include_all_metadata,
        include_labels=include_labels,
        version=version,
        overwrite=overwrite,
    )

write_tiff

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], None] | None = None,
) -> None

Export to an (OME)-TIFF file.

new in version 0.10.0

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)

Source code in src/nd2/_nd2file.py
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
def write_tiff(
    self,
    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.

    !!! Tip "new in version 0.10.0"

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

    Parameters
    ----------
    dest : str  | PathLike
        The destination TIFF file.
    include_unstructured_metadata :  bool
        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
        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
        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_types.OME], 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`)
    """
    from .tiff import nd2_to_tiff

    return nd2_to_tiff(
        self,
        dest,
        include_unstructured_metadata=include_unstructured_metadata,
        progress=progress,
        on_frame=on_frame,
        modify_ome=modify_ome,
    )

imread

imread(
    file: Path | str,
    *,
    dask: Literal[False] = ...,
    xarray: Literal[False] = ...,
    validate_frames: bool = ...,
) -> ndarray
imread(
    file: Path | str,
    *,
    dask: bool = ...,
    xarray: Literal[True],
    validate_frames: bool = ...,
) -> DataArray
imread(
    file: Path | str,
    *,
    dask: Literal[True],
    xarray: Literal[False] = ...,
    validate_frames: bool = ...,
) -> Array
imread(
    file: Path | str,
    *,
    dask: bool = False,
    xarray: bool = False,
    validate_frames: bool = False,
) -> ndarray | DataArray | 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:

Source code in src/nd2/_nd2file.py
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
def 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
        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
        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
        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
    -------
    Union[np.ndarray, dask.array.Array, xarray.DataArray]
        Array subclass, depending on arguments used.
    """
    with ND2File(file, validate_frames=validate_frames) as nd2:
        if xarray:
            return nd2.to_xarray(delayed=dask)
        elif dask:
            return nd2.to_dask()
        else:
            return nd2.asarray()

is_legacy

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.

Source code in src/nd2/_util.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def is_legacy(path: StrOrPath) -> bool:
    """Return `True` if `path` is a legacy ND2 file.

    Parameters
    ----------
    path : Union[str, bytes, PathLike]
        A path to query

    Returns
    -------
    bool
        Whether the file is a legacy ND2 file.
    """
    with open(path, "rb") as fh:
        return fh.read(4) == OLD_HEADER_MAGIC

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

Source code in src/nd2/_util.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def is_supported_file(
    path: FileOrBinaryIO,
    open_: Callable[[StrOrPath], BinaryIO] = _open_binary,
) -> bool:
    """Return `True` if `path` can be opened as an nd2 file.

    Parameters
    ----------
    path : Union[str, bytes, PathLike]
        A path to query
    open_ : Callable[[StrOrBytesPath, str], BinaryIO]
        Filesystem opener, by default `builtins.open`

    Returns
    -------
    bool
        Whether the can be opened.
    """
    if hasattr(path, "read"):
        path = cast("BinaryIO", path)
        path.seek(0)
        magic = path.read(4)
    else:
        with open_(path) as fh:
            magic = fh.read(4)
    return magic in (NEW_HEADER_MAGIC, OLD_HEADER_MAGIC)

nd2_to_tiff

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

Export an ND2 file to an (OME)-TIFF file.

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

https://docs.openmicroscopy.org/ome-model/6.3.1/ome-tiff/specification.html

Parameters:

  • source

    (str | PathLike | ND2File) –

    The ND2 file path or an open ND2File object.

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

Source code in src/nd2/tiff.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def nd2_to_tiff(
    source: str | PathLike | ND2File,
    dest: str | PathLike,
    *,
    include_unstructured_metadata: bool = True,
    progress: bool = False,
    on_frame: Callable[[int, int, dict[str, int]], None] | None = None,
    modify_ome: Callable[[ome_types.OME], None] | None = None,
) -> None:
    """Export an ND2 file to an (OME)-TIFF file.

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

    <https://docs.openmicroscopy.org/ome-model/6.3.1/ome-tiff/specification.html>

    Parameters
    ----------
    source : str | PathLike | ND2File
        The ND2 file path or an open ND2File object.
    dest : str  | PathLike
        The destination TIFF file.
    include_unstructured_metadata :  bool
        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
        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
        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_types.OME], 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`)
    """
    dest_path = Path(dest).expanduser().resolve()
    output_ome = ".ome." in dest_path.name

    # normalize source to an open ND2File, and remember if we opened it
    close_when_done = False
    if isinstance(source, (str, PathLike)):
        from ._nd2file import ND2File

        nd2f = ND2File(source)
        close_when_done = True
    else:
        nd2f = source
        if close_when_done := nd2f.closed:
            nd2f.open()

    try:
        # map of axis_name -> size
        sizes = dict(nd2f.sizes)

        # pop the number of positions from the sizes.
        # The OME data model does best with 5D data, so we'll write multi-5D series
        n_positions = sizes.pop(AXIS.POSITION, 1)

        # join axis names as a string, and get shape of the data without positions
        axes, shape = zip(*sizes.items())
        # U (Unknown) -> Q : other (OME)
        metadata = {"axes": "".join(axes).upper().replace(AXIS.UNKNOWN, "Q")}

        # Create OME-XML
        ome_xml: bytes | None = None
        if output_ome:
            if nd2f.is_legacy:
                warnings.warn(
                    "Cannot write OME metadata for legacy nd2 files."
                    "Please use a different file extension to avoid confusion",
                    stacklevel=2,
                )
            else:
                # get the OME metadata object from the ND2File
                ome = nd2_ome_metadata(
                    nd2f,
                    include_unstructured=include_unstructured_metadata,
                    tiff_file_name=dest_path.name,
                )
                if modify_ome:
                    # allow user to modify the OME metadata if they want
                    modify_ome(ome)
                ome_xml = ome.to_xml(exclude_unset=True).encode("utf-8")

        # total number of frames we will write
        tot = nd2f._frame_count
        # create a progress bar if requested
        pbar = _pbar(total=tot, desc=f"Exporting {nd2f.path}") if progress else None

        # `p_groups` will be a map of {position index -> [(frame_number, f_index) ...]}
        # where frame_number is passed to read_frame
        # and f_index is a map of axis name to index (e.g. {"T": 0, "Z": 1})
        # positions are grouped together so we can write them to the tiff file in order
        p_groups: defaultdict[int, list[tuple[int, dict[str, int]]]] = defaultdict(list)
        for f_num, f_index in enumerate(nd2f.loop_indices):
            p_groups[f_index.get(AXIS.POSITION, 0)].append((f_num, f_index))

        # create a function to iterate over all frames, updating pbar if requested
        def position_iter(p: int) -> Iterator[np.ndarray]:
            """Iterator over frames for a given position."""
            for f_num, f_index in p_groups[p]:
                # call on_frame callback if provided
                if on_frame is not None:
                    on_frame(f_num, tot, f_index)

                # yield the frame and update the progress bar
                yield nd2f.read_frame(f_num)
                if pbar is not None:
                    pbar.set_description(repr(f_index))
                    pbar.update()

        # if we have ome_xml, we tell tifffile not to worry about it (ome=False)
        tf_ome = False if ome_xml else None
        # Write the tiff file
        pixelsize = nd2f.voxel_size().x
        photometric = tf.PHOTOMETRIC.RGB if nd2f.is_rgb else tf.PHOTOMETRIC.MINISBLACK
        with tf.TiffWriter(dest_path, bigtiff=True, ome=tf_ome) as tif:
            for p in range(n_positions):
                tif.write(
                    iter(position_iter(p)),
                    shape=shape,
                    dtype=nd2f.dtype,
                    resolution=(1 / pixelsize, 1 / pixelsize),
                    resolutionunit=tf.RESUNIT.MICROMETER,
                    photometric=photometric,
                    metadata=metadata,
                    description=ome_xml,
                )

        if pbar is not None:
            pbar.close()

    finally:
        # close the nd2 file if we opened it
        if close_when_done:
            nd2f.close()

rescue_nd2

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

Source code in src/nd2/_parse/_chunk_decode.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
def 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, ...], optional
        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 : np.dtype, optional
        Data type, by default np.uint16
    max_iters : Optional[int], optional
        A maximum number of frames to yield, by default will yield until the
        end of the file is reached
    verbose : bool
        whether to print info
    chunk_start : bytes, optional
        The bytes that start each chunk, by default 0x0ABECEDA.to_bytes(4, "little")

    Yields
    ------
    np.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.
    """
    dtype = np.dtype(dtype)
    with ensure_handle(handle) as _fh:
        mm = mmap.mmap(_fh.fileno(), 0, access=mmap.ACCESS_READ)

        offset = 0
        iters = 0
        while True:
            # search for the next part of the file starting with CHUNK_START
            offset = mm.find(chunk_start, offset)
            if offset < 0:
                if verbose:
                    print("End of file.")
                return

            # location at the end of the chunk header
            end_hdr = offset + CHUNK_HEADER.size

            # find the next "!"
            # In nd2 files, each data chunk starts with the
            # string "ImageDataSeq|N" ... where N is the frame index
            next_bang = mm.find(b"!", end_hdr)
            if next_bang > 0 and (0 < next_bang - end_hdr < 128):
                # if we find the "!"... make sure we have an ImageDataSeq
                chunk_name = mm[end_hdr:next_bang]
                if chunk_name.startswith(b"ImageDataSeq|"):
                    if verbose:
                        print(f"Found image {iters} at offset {offset}")
                    # Now, read the actual data
                    _, shift, length = CHUNK_HEADER.unpack(mm[offset:end_hdr])
                    # convert to numpy array and yield
                    # (can't remember why the extra 8 bytes)
                    try:
                        shape = frame_shape or ((length - 8) // dtype.itemsize,)
                        yield np.ndarray(
                            shape=shape,
                            dtype=dtype,
                            buffer=mm,
                            offset=end_hdr + shift + 8,
                        )
                    except TypeError as e:  # pragma: no cover
                        # buffer is likely too small
                        if verbose:
                            print(f"Error at offset {offset}: {e}")
                    iters += 1
            elif verbose:
                print(f"Found chunk at offset {offset} with no image data")

            offset += 1
            if max_iters and iters >= max_iters:
                return