Skip to content
This repository was archived by the owner on Dec 1, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,34 @@ from aicsimageio import AICSImage, imread
im = imread("/path/to/your/file_or_buffer.ome.tiff")

# For AICSImage object that
im = AICSImage("/path/to/your/file_or_buffer.ome.tiff")
with AICSImage("/path/to/your/file_or_buffer.ome.tiff") as im:
# use im object

# To specify a known dimension order
im = AICSImage("/path/to/your/file_or_buffer.ome.tiff", known_dims="SYX")
with AICSImage("/path/to/your/file_or_buffer.ome.tiff", known_dims="SYX") as im:
# use im object

# if you instantiate an AICSImage:
im = AICSImage("/path/to/your/file_or_buffer.ome.tiff")
# you should close it when done:
im.close()

# Image data is stored in `data` attribute
im.data # returns the image data numpy array

# Image dimension sizes can be obtained via properties:
im.size_z # returns the size of the Z dimension. X,Y,Z,C,T, and S supported.

# Image dimensions can also be obtained as a tuple in two ways:
im.size("ZYX") # returns a tuple containing the Z, Y, and X sizes only
im.get_image_data(out_orientation="ZYX").shape # returns same as above

# Image metadata is stored in `metadata` attribute
im.metadata # returns whichever metadata parser best suites the file format
im.metadata # returns whichever metadata parser best suits the file format

# Subsets or transposes of the image data can be requested:
im.get_image_data(out_orientation="ZYX") # returns a 3d data block containing only the ZYX dimensions

```

## Notes
Expand Down
2 changes: 1 addition & 1 deletion aicsimageio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

# Do not edit this string manually, always use bumpversion
# Details in CONTRIBUTING.md
__version__ = "3.0.3"
__version__ = "3.0.6"
160 changes: 145 additions & 15 deletions aicsimageio/aics_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import numpy as np

from . import constants, transforms, types
from .exceptions import UnsupportedFileFormatError
from .readers import (CziReader, DefaultReader, NdArrayReader, OmeTiffReader,
TiffReader)
from .exceptions import InvalidDimensionOrderingError, UnsupportedFileFormatError
from .readers import CziReader, DefaultReader, NdArrayReader, OmeTiffReader, TiffReader
from .readers.reader import Reader

log = logging.getLogger(__name__)
Expand All @@ -34,18 +33,29 @@ class AICSImage:

zstack_t10 = data[0, 10, 0, :, :, :] # access the S=0, T=10, C=0 "ZYX" cube


File Examples
-------------

with AICSImage("filename.ome.tif") as img:
# do work with img
data = img.data
metadata = img.metadata
# img will be closed automatically at end of scope

OmeTif
img = AICSImage("filename.ome.tif")
img.close()
CZI (Zeiss)
img = AICSImage("filename.czi") or AICSImage("filename.czi", max_workers=8)
img.close()
Tiff
img = AICSImage("filename.tif")
img.close()
Png/Gif/...
img = AICSImage("filename.png")
img.close()
img = AICSImage("filename.gif")
img.close()

Bytestream Examples
-------------------
Expand All @@ -70,9 +80,15 @@ class AICSImage:
img.reader.dims = 'TCX'
data = img.data # get a 6D ndarray back in "STCZYX" order
"""

SUPPORTED_READERS = [CziReader, OmeTiffReader, TiffReader, DefaultReader]

def __init__(self, data: typing.Union[types.FileLike, np.ndarray], known_dims: Optional[str] = None, **kwargs):
def __init__(
self,
data: typing.Union[types.FileLike, np.ndarray],
known_dims: Optional[str] = None,
**kwargs,
):
"""
Constructor for AICSImage class intended for providing a unified interface for dealing with
microscopy images. To extend support to a new reader simply add a new reader child class of
Expand Down Expand Up @@ -109,7 +125,13 @@ def determine_reader(data: types.ImageLike) -> Type[Reader]:
"""
# The order of the readers in this list is important.
# Example: if TiffReader was placed before OmeTiffReader, we would never use the OmeTiffReader.
for reader_class in [NdArrayReader, CziReader, OmeTiffReader, TiffReader, DefaultReader]:
for reader_class in [
NdArrayReader,
CziReader,
OmeTiffReader,
TiffReader,
DefaultReader,
]:
if reader_class.is_this_type(data):
return reader_class

Expand All @@ -126,11 +148,84 @@ def data(self):
reader_data = self._reader.data

# Read and reshape and handle delayed known dims reshape
self._data = transforms.reshape_data(data=reader_data,
given_dims=self._known_dims or self.reader.dims,
return_dims=self.dims)
self._data = transforms.reshape_data(
data=reader_data,
given_dims=self._known_dims or self.reader.dims,
return_dims=self.dims,
)
return self._data

def size(self, dims: str = "STCZYX"):
"""
Parameters
----------
dims: A string containing a list of dimensions being requested. The default is to return the 6 standard dims

Returns
-------
Returns a tuple with the requested dimensions filled in
"""
dims = dims.upper()
if not (all(d in "STCZYX" for d in dims)):
raise InvalidDimensionOrderingError(f"Invalid dimensions requested: {dims}")
if not (all(d in self.dims for d in dims)):
raise InvalidDimensionOrderingError(f"Invalid dimensions requested: {dims}")
return tuple([self.data.shape[self.dims.index(dim)] for dim in dims])

@property
def size_x(self):
"""
Returns
-------
Returns the x size
"""
return self.size("X")[0]

@property
def size_y(self):
"""
Returns
-------
Returns the y size
"""
return self.size("Y")[0]

@property
def size_z(self):
"""
Returns
-------
Returns the z size
"""
return self.size("Z")[0]

@property
def size_c(self):
"""
Returns
-------
Returns the c size
"""
return self.size("C")[0]

@property
def size_t(self):
"""
Returns
-------
Returns the t size
"""
return self.size("T")[0]

@property
def size_s(self):
"""
Returns
-------
Returns the s size
"""
return self.size("S")[0]

@property
def metadata(self):
"""
Expand Down Expand Up @@ -158,7 +253,9 @@ def reader(self) -> Reader:
"""
return self._reader

def get_image_data(self, out_orientation: str = None, copy: bool = False, **kwargs) -> np.ndarray:
def get_image_data(
self, out_orientation: str = None, copy: bool = False, **kwargs
) -> np.ndarray:
"""

Parameters
Expand All @@ -180,12 +277,45 @@ def get_image_data(self, out_orientation: str = None, copy: bool = False, **kwar
out_orientation = self.dims if out_orientation is None else out_orientation
if out_orientation == self.dims:
return self.data
return transforms.reshape_data(data=self.data, given_dims=self.dims,
return_dims=out_orientation, copy=copy, **kwargs)
return transforms.reshape_data(
data=self.data,
given_dims=self.dims,
return_dims=out_orientation,
copy=copy,
**kwargs,
)

def get_channel_names(self, scene: int = 0):
"""

Parameters
----------
scene: the index of the scene for which to return channel names

Returns
-------
list of strings representing the channel names
"""
try:
names = self._reader.get_channel_names(scene)
except AttributeError:
names = [str(i) for i in range(self.size_c)]
return names

def close(self):
self.reader.close()

def __repr__(self) -> str:
return f'<AICSImage [{type(self.reader).__name__}]>'
return f"<AICSImage [{type(self.reader).__name__}]>"

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.close()


def imread(data: types.ImageLike, **kwargs):
return AICSImage(data, **kwargs).data
def imread(data: types.ImageLike, **kwargs) -> np.ndarray:
with AICSImage(data, **kwargs) as image:
imagedata = image.data
return imagedata
19 changes: 18 additions & 1 deletion aicsimageio/readers/czi_reader.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import io
import logging
import warnings
from typing import Optional
from typing import Optional, Tuple
from xml.etree import ElementTree

import numpy as np
Expand Down Expand Up @@ -213,3 +213,20 @@ def _is_multiscene(self):
return False
img_shape = self.czi.filtered_subblock_directory[0].shape
return img_shape[index] != 1

def get_channel_names(self, scene: int = 0):
chelem = self.metadata.findall("./Metadata/Information/Image/Dimensions/Channels/Channel")
return [ch.get("Name") for ch in chelem]

# TODO refactor this utility function into a metadata wrapper class
def _getmetadataxmltext(self, findpath, default=None):
ref = self.metadata.find(findpath)
if ref is None:
return default
return ref.text

def get_physical_pixel_size(self, scene: int = 0) -> Tuple[float]:
px = float(self._getmetadataxmltext("./Metadata/Scaling/Items/Distance[@Id='X']/Value", "1.0"))
py = float(self._getmetadataxmltext("./Metadata/Scaling/Items/Distance[@Id='Y']/Value", "1.0"))
pz = float(self._getmetadataxmltext("./Metadata/Scaling/Items/Distance[@Id='Z']/Value", "1.0"))
return (px, py, pz)
8 changes: 8 additions & 0 deletions aicsimageio/readers/ome_tiff_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import numpy as np
import tifffile
from typing import Tuple

from .. import types
from ..vendor import omexml
Expand Down Expand Up @@ -102,3 +103,10 @@ def dtype(self):

def is_ome(self):
return OmeTiffReader._is_this_type(self._bytes)

def get_channel_names(self, scene: int = 0):
return self.metadata.image(scene).Pixels.get_channel_names()

def get_physical_pixel_size(self, scene: int = 0) -> Tuple[float]:
p = self.metadata.image(scene).Pixels
return (p.get_PhysicalSizeX(), p.get_PhysicalSizeY(), p.get_PhysicalSizeZ())
9 changes: 9 additions & 0 deletions aicsimageio/tests/readers/test_czi_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ def test_dtype(resources_dir, test_input, expected):
assert czi.dtype() == expected


@pytest.mark.parametrize("test_input, expected", [
(TWO_DIM_CZI, (1.0833333333333333e-06, 1.0833333333333333e-06, 1.0)),
(SIX_DIM_CZI, (1.0833333333333333e-06, 1.0833333333333333e-06, 1e-06))
])
def test_pixel_size(resources_dir, test_input, expected):
czi = CziReader(resources_dir / test_input)
assert czi.get_physical_pixel_size() == expected


def test_shape(resources_dir):
czi = CziReader(resources_dir / SIX_DIM_CZI)
data = czi.data
Expand Down
6 changes: 6 additions & 0 deletions aicsimageio/tests/readers/test_ome_tiff_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,15 @@ def test_loadSampleOmeTif(self):
"CYX", # Inferred from metadata not shape
"ZCYX",
]
physical_pixel_sizes = [
(1.0833333333333333, 1.0833333333333333, 1.0),
(1.0, 1.0, 1.0),
(1.0833333333333333, 1.0833333333333333, 1.0),
]
for i, x in enumerate(names):
with OmeTiffReader(os.path.join(self.dir_path, "..", "resources", x)) as reader:
assert reader.is_ome()
data = reader.data
self.assertEqual(data.shape, dims[i])
self.assertEqual(reader.dims, dim_orders[i])
self.assertEqual(reader.get_physical_pixel_size(), physical_pixel_sizes[i])
Loading