You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
7.4 KiB
202 lines
7.4 KiB
import numpy as np
|
|
import tifffile as tif
|
|
import skimage.io as io
|
|
from typing import List, Optional, Sequence, Type, Union
|
|
|
|
from monai.utils.enums import PostFix
|
|
from monai.utils.module import optional_import
|
|
from monai.utils.misc import ensure_tuple, ensure_tuple_rep
|
|
from monai.data.utils import is_supported_format
|
|
from monai.data.image_reader import ImageReader, NumpyReader
|
|
from monai.transforms import LoadImage, LoadImaged # type: ignore
|
|
from monai.config.type_definitions import DtypeLike, PathLike, KeysCollection
|
|
|
|
|
|
# Default value for metadata postfix
|
|
DEFAULT_POST_FIX = PostFix.meta()
|
|
|
|
# Try to import ITK library; if not available, has_itk will be False
|
|
itk, has_itk = optional_import("itk", allow_namespace_pkg=True)
|
|
|
|
|
|
__all__ = [
|
|
"CustomLoadImage", # Basic image loader
|
|
"CustomLoadImaged", # Dictionary-based image loader
|
|
"CustomLoadImageD", # Dictionary-based image loader
|
|
"CustomLoadImageDict", # Dictionary-based image loader
|
|
]
|
|
|
|
|
|
class CustomLoadImage(LoadImage):
|
|
"""
|
|
Class for loading one or multiple images from a given path.
|
|
|
|
If a reader is not specified, the appropriate file reading method is automatically chosen
|
|
based on the file extension. Priority:
|
|
- Reader passed by the user at runtime.
|
|
- Reader specified in the constructor.
|
|
- Registered readers (from last to first).
|
|
- Standard readers for different formats (e.g., NibabelReader for nii, PILReader for png/jpg, etc.).
|
|
|
|
[Note] Here, the original ITKReader is replaced by the universal reader UniversalImageReader.
|
|
"""
|
|
def __init__(
|
|
self,
|
|
reader: Optional[Union[ImageReader, Type[ImageReader], str]] = None,
|
|
image_only: bool = False,
|
|
dtype: DtypeLike = np.float32,
|
|
ensure_channel_first: bool = False,
|
|
*args,
|
|
**kwargs,
|
|
) -> None:
|
|
super().__init__(
|
|
reader=reader,
|
|
image_only=image_only,
|
|
dtype=dtype,
|
|
ensure_channel_first=ensure_channel_first,
|
|
*args, **kwargs
|
|
)
|
|
# Clear the list of registered readers
|
|
self.readers = []
|
|
# Register the universal reader that handles TIFF, PNG, JPG, BMP, etc.
|
|
self.register(UniversalImageReader(*args, **kwargs))
|
|
|
|
|
|
class CustomLoadImaged(LoadImaged):
|
|
"""
|
|
Dictionary-based image loader.
|
|
|
|
Wraps image loading with CustomLoadImage and allows processing of data represented as a dictionary,
|
|
where keys point to file paths.
|
|
"""
|
|
def __init__(
|
|
self,
|
|
keys: KeysCollection,
|
|
reader: Optional[Union[Type[ImageReader], str]] = None,
|
|
dtype: DtypeLike = np.float32,
|
|
meta_keys: Optional[KeysCollection] = None,
|
|
meta_key_postfix: str = DEFAULT_POST_FIX,
|
|
overwriting: bool = False,
|
|
image_only: bool = False,
|
|
ensure_channel_first: bool = False,
|
|
simple_keys: bool = False,
|
|
allow_missing_keys: bool = False,
|
|
*args,
|
|
**kwargs,
|
|
) -> None:
|
|
super().__init__(
|
|
keys=keys,
|
|
reader=reader,
|
|
dtype=dtype,
|
|
meta_keys=meta_keys,
|
|
meta_key_postfix=meta_key_postfix,
|
|
overwriting=overwriting,
|
|
image_only=image_only,
|
|
ensure_channel_first=ensure_channel_first,
|
|
simple_keys=simple_keys,
|
|
allow_missing_keys=allow_missing_keys,
|
|
*args,
|
|
**kwargs,
|
|
)
|
|
# Assign the custom image loader
|
|
self._loader = CustomLoadImage(
|
|
reader=reader,
|
|
image_only=image_only,
|
|
dtype=dtype,
|
|
ensure_channel_first=ensure_channel_first,
|
|
*args, **kwargs
|
|
)
|
|
# Ensure that meta_key_postfix is a string
|
|
if not isinstance(meta_key_postfix, str):
|
|
raise TypeError(
|
|
f"meta_key_postfix must be a string, but got {type(meta_key_postfix).__name__}."
|
|
)
|
|
# If meta_keys are not provided, create a tuple of None for each key
|
|
self.meta_keys = (
|
|
ensure_tuple_rep(None, len(self.keys))
|
|
if meta_keys is None
|
|
else ensure_tuple(meta_keys)
|
|
)
|
|
# Check that the number of meta_keys matches the number of keys
|
|
if len(self.keys) != len(self.meta_keys):
|
|
raise ValueError("meta_keys must have the same length as keys.")
|
|
# Assign each key its corresponding metadata postfix
|
|
self.meta_key_postfix = ensure_tuple_rep(meta_key_postfix, len(self.keys))
|
|
self.overwriting = overwriting
|
|
|
|
|
|
class UniversalImageReader(NumpyReader):
|
|
"""
|
|
Universal image reader for TIFF, PNG, JPG, BMP, etc.
|
|
|
|
Uses:
|
|
- tifffile for reading TIFF files.
|
|
- ITK (if available) for reading other formats.
|
|
- skimage.io for reading if the previous methods fail.
|
|
|
|
The image is loaded with its original number of channels (layers) without forced modifications
|
|
(e.g., repeating or cropping channels).
|
|
"""
|
|
def __init__(
|
|
self, channel_dim: Optional[int] = None, **kwargs,
|
|
):
|
|
super().__init__(channel_dim=channel_dim, **kwargs)
|
|
self.kwargs = kwargs
|
|
self.channel_dim = channel_dim
|
|
|
|
def verify_suffix(self, filename: Union[Sequence[PathLike], PathLike]) -> bool:
|
|
"""
|
|
Check if the file format is supported for reading.
|
|
|
|
Supported extensions: tif, tiff, png, jpg, bmp, jpeg.
|
|
"""
|
|
suffixes: Sequence[str] = ["tif", "tiff", "png", "jpg", "bmp", "jpeg"]
|
|
return has_itk or is_supported_format(filename, suffixes)
|
|
|
|
def read(self, data: Union[Sequence[PathLike], PathLike], **kwargs):
|
|
"""
|
|
Read image(s) from the given path.
|
|
|
|
Arguments:
|
|
data: A file path or a sequence of file paths.
|
|
kwargs: Additional parameters for reading.
|
|
|
|
Returns:
|
|
A single image or a list of images depending on the number of paths provided.
|
|
"""
|
|
images: List[np.ndarray] = [] # List to store the loaded images
|
|
|
|
# Convert data to a tuple to support multiple files
|
|
filenames: Sequence[PathLike] = ensure_tuple(data)
|
|
# Merge parameters provided in the constructor and the read() method
|
|
kwargs_ = self.kwargs.copy()
|
|
kwargs_.update(kwargs)
|
|
|
|
for name in filenames:
|
|
# Convert file name to string
|
|
name = f"{name}"
|
|
# If the file has a .tif or .tiff extension (case-insensitive), use tifffile for reading
|
|
if name.lower().endswith((".tif", ".tiff")):
|
|
img_array = tif.imread(name)
|
|
else:
|
|
# Attempt to read the image using ITK (if available)
|
|
try:
|
|
img_itk = itk.imread(name, **kwargs_)
|
|
img_array = itk.array_view_from_image(img_itk, keep_axes=False)
|
|
except Exception:
|
|
# If ITK fails, use skimage.io for reading
|
|
img_array = io.imread(name)
|
|
|
|
# Check the number of dimensions (axes) of the loaded image
|
|
if img_array.ndim == 2:
|
|
# If the image is 2D (height, width), add a new axis at the end to represent the channel
|
|
img_array = np.expand_dims(img_array, axis=-1)
|
|
|
|
images.append(img_array)
|
|
|
|
# Return a single image if only one file was provided, otherwise return a list of images
|
|
return images if len(filenames) > 1 else images[0]
|
|
|
|
|
|
|
|
CustomLoadImageD = CustomLoadImageDict = CustomLoadImaged |