diff --git a/qim3d/io/load.py b/qim3d/io/load.py index 880f7b7a5fe25af0a846a5b6effa3a0a352490df..4d67274de5f4ea26099b39a846ecf369d5abd33b 100644 --- a/qim3d/io/load.py +++ b/qim3d/io/load.py @@ -11,6 +11,7 @@ import qim3d from qim3d.io.logger import log from qim3d.utils.internal_tools import sizeof, stringify_path from qim3d.utils.system import Memory +from PIL import Image, UnidentifiedImageError class DataLoader: @@ -45,7 +46,7 @@ class DataLoader: stack when loading files. Default is False. dataset_name (str, optional): Specifies the name of the dataset to be loaded in case multiple dataset exist within the same file. Default is None (only for HDF5 files) - return_metadata (bool, optional): Specifies whether to return metadata or not. Default is False (only for HDF5 and TXRM/TXM/XRM files) + return_metadata (bool, optional): Specifies whether to return metadata or not. Default is False (only for HDF5, TXRM/TXM/XRM and NIfTI files) contains (str, optional): Specifies a part of the name that is common for the TIFF file stack to be loaded (only for TIFF stacks). Default is None. """ @@ -61,7 +62,8 @@ class DataLoader: path (str): The path to the TIFF file. Returns: - numpy.ndarray: The loaded volume as a NumPy array. + numpy.ndarray or numpy.memmap: The loaded volume. + If 'self.virtual_stack' is True, returns a numpy.memmap object. """ if self.virtual_stack: @@ -80,7 +82,8 @@ class DataLoader: path (str): The path to the HDF5 file. Returns: - numpy.ndarray or tuple: The loaded volume as a NumPy array. + numpy.ndarray, h5py._hl.dataset.Dataset or tuple: The loaded volume. + If 'self.virtual_stack' is True, returns a h5py._hl.dataset.Dataset object If 'self.return_metadata' is True, returns a tuple (volume, metadata). Raises: @@ -161,7 +164,8 @@ class DataLoader: path (str): The path to the stack of TIFF files. Returns: - numpy.ndarray: The loaded volume as a NumPy array. + numpy.ndarray or numpy.memmap: The loaded volume. + If 'self.virtual_stack' is True, returns a numpy.memmap object. Raises: ValueError: If the 'contains' argument is not specified. @@ -214,7 +218,7 @@ class DataLoader: path (str): The path to the HDF5 file. Returns: - numpy.ndarray or tuple: The loaded volume as a NumPy array. + numpy.ndarray or tuple: The loaded volume. If 'self.return_metadata' is True, returns a tuple (volume, metadata). Raises: @@ -252,7 +256,8 @@ class DataLoader: path (str): The path to the NIfTI file. Returns: - numpy.ndarray or tuple: The loaded volume as a NumPy array. + numpy.ndarray, nibabel.arrayproxy.ArrayProxy or tuple: The loaded volume. + If 'self.virtual_stack' is True, returns a nibabel.arrayproxy.ArrayProxy object If 'self.return_metadata' is True, returns a tuple (volume, metadata). """ @@ -272,6 +277,17 @@ class DataLoader: return vol, metadata else: return vol + + def load_pil(self, path): + """Load a PIL image from the specified path + + Args: + path (str): The path to the image supported by PIL. + + Returns: + numpy.ndarray: The loaded image/volume. + """ + return np.array(Image.open(path)) def load(self, path): """ @@ -281,8 +297,9 @@ class DataLoader: path (str or os.PathLike): The path to the file or directory. Returns: - numpy.ndarray: The loaded volume as a NumPy array. - + numpy.ndarray, numpy.memmap, h5py._hl.dataset.Dataset, nibabel.arrayproxy.ArrayProxy or tuple: The loaded volume. + If 'self.virtual_stack' is True, returns numpy.memmap, h5py._hl.dataset.Dataset or nibabel.arrayproxy.ArrayProxy depending on file format + If 'self.return_metadata' is True and file format is either HDF5, NIfTI or TXRM/TXM/XRM, returns a tuple (volume, metadata). Raises: ValueError: If the format is not supported ValueError: If the file or directory does not exist. @@ -292,6 +309,7 @@ class DataLoader: data = loader.load("image.tif") """ + # Stringify path in case it is not already a string path = stringify_path(path) # Load a file @@ -306,7 +324,10 @@ class DataLoader: elif path.endswith((".nii",".nii.gz")): return self.load_nifti(path) else: - raise ValueError("Unsupported file format") + try: + return self.load_pil(path) + except UnidentifiedImageError: + raise ValueError("Unsupported file format") # Load a directory elif os.path.isdir(path): @@ -357,8 +378,9 @@ def load( to the DataLoader constructor. Returns: - numpy.ndarray: The loaded volume as a NumPy array. - If 'return_metadata' is True and file format is either HDF5 or TXRM/TXM/XRM, returns a tuple (volume, metadata). + numpy.ndarray, numpy.memmap, h5py._hl.dataset.Dataset, nibabel.arrayproxy.ArrayProxy or tuple: The loaded volume. + If 'virtual_stack' is True, returns numpy.memmap, h5py._hl.dataset.Dataset or nibabel.arrayproxy.ArrayProxy depending on file format + If 'return_metadata' is True and file format is either HDF5, NIfTI or TXRM/TXM/XRM, returns a tuple (volume, metadata). Example: data = qim3d.io.load("image.tif", virtual_stack=True) @@ -374,16 +396,27 @@ def load( data = loader.load(path) - if not virtual_stack: + + def log_memory_info(data): mem = Memory() log.info( "Volume using %s of memory\n", - sizeof(data[0].nbytes if return_metadata else data.nbytes), + sizeof(data[0].nbytes if isinstance(data, tuple) else data.nbytes), ) mem.report() + if return_metadata and not isinstance(data,tuple): + log.warning('The file format does not contain metadata') + + if not virtual_stack: + log_memory_info(data) else: - log.info("Using virtual stack") + # Only log if file type is not a np.ndarray, i.e., it is some kind of memmap object + if not isinstance( data[0] if isinstance(data,tuple) else data, np.ndarray ): + log.info("Using virtual stack") + else: + log.warning('Virtual stack is not supported for this file format') + log_memory_info(data) return data diff --git a/qim3d/utils/system.py b/qim3d/utils/system.py index 383060df8c8fad875df4c4984325b7fb0f92ed6a..57ac08c2a3c1e1d6d886e28dec42d878843f0baf 100644 --- a/qim3d/utils/system.py +++ b/qim3d/utils/system.py @@ -19,7 +19,7 @@ class Memory: self.total = mem.total self.free = mem.available - self.free_pct = (mem.free / mem.total) * 100 + self.free_pct = (mem.available / mem.total) * 100 self.used = mem.total - mem.available self.used_pct = mem.percent @@ -28,7 +28,7 @@ class Memory: "System memory:\n • Total.: %s\n • Used..: %s (%s%%)\n • Free..: %s (%s%%)", sizeof(self.total), sizeof(self.used), - round(self.used_pct, 2), + round(self.used_pct, 1), sizeof(self.free), - round(self.free_pct, 2), + round(self.free_pct, 1), )