import typing
from abc import ABC, abstractmethod
from io import BytesIO
from itertools import product
from pathlib import Path
import numpy as np
from tifffile import imwrite
from PartSegImage.image import Image, minimal_dtype
[docs]
class BaseImageWriter(ABC):
@classmethod
@abstractmethod
def save(cls, image: Image, save_path: typing.Union[str, BytesIO, Path], compression="ADOBE_DEFLATE"):
pass
@classmethod
@abstractmethod
def save_mask(cls, image: Image, save_path: typing.Union[str, Path], compression="ADOBE_DEFLATE"):
pass
[docs]
class ImageWriter(BaseImageWriter):
"""class for saving TIFF images"""
@classmethod
def prepare_metadata(cls, image: Image, channels: int):
spacing = image.get_um_spacing()
shift = image.get_um_shift()
plane_li = [
{
"TheT": t,
"TheZ": z,
"TheC": c,
"PositionZ": shift[0] if len(shift) == 3 else 0,
"PositionY": shift[-2],
"PositionX": shift[-1],
"PositionZUnit": "µm",
"PositionYUnit": "µm",
"PositionXUnit": "µm",
}
for t, z, c in product(range(image.times), range(image.layers), range(channels))
]
metadata = {
"Pixels": {
"PhysicalSizeZ": spacing[0] if len(spacing) == 3 else 1,
"PhysicalSizeY": spacing[-2],
"PhysicalSizeX": spacing[-1],
"PhysicalSizeZUnit": "µm",
"PhysicalSizeYUnit": "µm",
"PhysicalSizeXUnit": "µm",
},
"Plane": plane_li,
"Creator": "PartSeg",
}
if image.name:
metadata["Name"] = image.name
return metadata
[docs]
@classmethod
def save(cls, image: Image, save_path: typing.Union[str, BytesIO, Path], compression="ADOBE_DEFLATE"):
"""
Save image as tiff to path or buffer
:param image: image for save
:param save_path: save location
"""
data = image.get_image_for_save()
metadata = cls.prepare_metadata(image, image.channels)
metadata["Channel"] = {
"Name": image.channel_names,
"axes": "TZYXC",
}
cls._save(data, save_path, metadata, compression)
[docs]
@classmethod
def save_mask(cls, image: Image, save_path: typing.Union[str, Path], compression="ADOBE_DEFLATE"):
"""
Save mask connected to image as tiff to path or buffer
:param image: mast is obtain with :py:meth:`.Image.get_mask_for_save`
:param save_path: save location
"""
mask = image.get_mask_for_save()
if mask is None:
return
mask_max = np.max(mask)
mask = mask.astype(minimal_dtype(mask_max))
metadata = cls.prepare_metadata(image, 1)
metadata["Channel"] = {
"Name": "Mask",
"axes": "TZYX",
}
cls._save(mask, save_path, metadata, compression)
@staticmethod
def _save(data: np.ndarray, save_path, metadata=None, compression="ADOBE_DEFLATE"):
# TODO change to ome TIFF
imwrite(
save_path,
data,
ome=True,
software="PartSeg",
metadata=metadata,
compression=compression,
) # , compress=6,
[docs]
class IMAGEJImageWriter(BaseImageWriter):
"""class for saving TIFF images"""
[docs]
@classmethod
def save(cls, image: Image, save_path: typing.Union[str, BytesIO, Path], compression=""):
"""
Save image as tiff to path or buffer
:param image: image for save
:param save_path: save location
"""
data = image.get_image_for_save()
spacing = image.get_um_spacing()
metadata: typing.Dict[str, typing.Any] = {"mode": "color", "unit": "\\u00B5m"}
if len(spacing) == 3:
metadata["spacing"] = spacing[0]
if image.channel_names is not None:
metadata["Labels"] = image.channel_names * image.layers
coloring = image.get_imagej_colors()
if coloring is not None:
metadata["LUTs"] = coloring
ranges_li = image.get_ranges()
metadata["Ranges"] = np.array(ranges_li).reshape(len(ranges_li) * 2)
resolution = [1 / x for x in spacing[-2:]]
cls._save(data, save_path, resolution, metadata)
[docs]
@classmethod
def save_mask(cls, image: Image, save_path: typing.Union[str, Path], compression=""):
"""
Save mask connected to image as tiff to path or buffer
:param image: mast is obtain with :py:meth:`.Image.get_mask_for_save`
:param save_path: save location
"""
mask = image.get_mask_for_save()
if mask is None:
return
mask_max = np.max(mask)
mask = mask.astype(minimal_dtype(mask_max))
metadata: typing.Dict[str, typing.Union[str, float]] = {"mode": "color", "unit": "\\u00B5m"}
spacing = image.get_um_spacing()
if len(spacing) == 3:
metadata["spacing"] = spacing[0]
resolution = [1 / x for x in spacing[-2:]]
cls._save(mask, save_path, resolution, metadata)
@staticmethod
def _save(data: np.ndarray, save_path, resolution=None, metadata=None):
# TODO change to ome TIFF
if data.dtype in [np.uint8, np.uint16, np.float32]:
imwrite(
save_path,
data,
imagej=True,
software="PartSeg",
metadata=metadata,
resolution=resolution,
) # , compress=6,
else:
raise ValueError(f"Data type {data.dtype} not supported by imagej tiff")
# imagej=True, software="PartSeg")