From 491dd3d60fba375bfc8fbe4e42912f3ea417a5e3 Mon Sep 17 00:00:00 2001
From: s184058 <s184058@student.dtu.dk>
Date: Thu, 31 Aug 2023 15:56:50 +0200
Subject: [PATCH] First version of tiff stack loader

---
 qim3d/io/load.py | 64 ++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 59 insertions(+), 5 deletions(-)

diff --git a/qim3d/io/load.py b/qim3d/io/load.py
index 8f886167..c00988e2 100644
--- a/qim3d/io/load.py
+++ b/qim3d/io/load.py
@@ -5,6 +5,7 @@ import sys
 import difflib
 import tifffile
 import h5py
+import numpy as np
 from qim3d.io.logger import log
 from qim3d.utils.internal_tools import sizeof
 
@@ -22,6 +23,7 @@ class DataLoader:
     Methods:
         load_tiff(path): Load a TIFF file from the specified path.
         load_h5(path): Load an HDF5 file from the specified path.
+        load_tiff_stack(path): Load a stack of TIFF files from the specified path.
         load(path): Load a file or directory based on the given path.
 
     Raises:
@@ -42,11 +44,13 @@ class DataLoader:
             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 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)   
         """
         # Virtual stack is False by default
         self.virtual_stack = kwargs.get("virtual_stack", False)
         self.dataset_name = kwargs.get("dataset_name", None)
         self.return_metadata = kwargs.get("return_metadata", False)
+        self.contains = kwargs.get("contains", None)
 
     def load_tiff(self, path):
         """Load a TIFF file from the specified path.
@@ -118,7 +122,7 @@ class DataLoader:
             else:
                 if self.dataset_name:  # Dataset name is provided
                     similar_names = difflib.get_close_matches(
-                        self.dataset_name, list(datasets.keys())
+                        self.dataset_name, datasets
                     )  # Find closest matching name if any
                     if similar_names:
                         suggestion = similar_names[0]  # Get the closest match
@@ -141,6 +145,8 @@ class DataLoader:
         if not self.virtual_stack:
             vol = vol[()]  # Load dataset into memory
             f.close()
+        else:
+            log.info("Using virtual stack")
 
         log.info("Loaded the following dataset: %s", name)
         log.info("Loaded shape: %s", vol.shape)
@@ -150,7 +156,54 @@ class DataLoader:
             return vol, metadata
         else:
             return vol
+        
+    def load_tiff_stack(self, path):
+        """Load a stack of TIFF files from the specified path.
+
+        Args:
+            path (str): The path to the stack of TIFF files.
+
+        Returns:
+            numpy.ndarray: The loaded volume as a NumPy array.
+
+        Raises:
+            ValueError: If the 'contains' argument is not specified.
+            ValueError: If the 'contains' argument matches multiple TIFF stacks in the directory
+
+        """
+        if not self.contains:
+            raise ValueError(
+                "Please specify a part of the name that is common for the TIFF file stack with the argument 'contains'"
+            )
+
+        tiff_stack = [file for file in os.listdir(path) if (file.endswith('.tif') or file.endswith('.tiff')) and self.contains in file]
+        tiff_stack.sort()  # Ensure proper ordering
 
+        # Check that only one TIFF stack in the directory contains the provided string in its name
+        tiff_stack_only_letters = []
+        for filename in tiff_stack:
+            name = os.path.splitext(filename)[0] # Remove file extension
+            tiff_stack_only_letters.append(''.join(filter(str.isalpha, name))) # Remove everything else than letters from the name
+
+        # Get unique elements from tiff_stack_only_letters
+        unique_names = list(set(tiff_stack_only_letters))
+        if len(unique_names)>1:
+            raise ValueError(f"The provided part of the filename for the TIFF stack matches multiple TIFF stacks: {unique_names}.\nPlease provide a string that is unique for the TIFF stack that is intended to be loaded")
+    
+
+        vol = tifffile.imread([os.path.join(path, file) for file in tiff_stack])
+
+        if not self.virtual_stack:
+            vol = np.copy(vol) # Copy to memory
+        else:
+            log.info("Using virtual stack")
+
+        log.info("Found %s file(s)", len(tiff_stack))
+        log.info("Loaded shape: %s", vol.shape)
+        log.info("Using %s of memory", sizeof(sys.getsizeof(vol)))
+
+        return vol
+    
     def load(self, path):
         """
         Load a file or directory based on the given path.
@@ -181,7 +234,7 @@ class DataLoader:
 
         # Load a directory
         elif os.path.isdir(path):
-            raise NotImplementedError("Loading from directory is not implemented yet")
+            return self.load_tiff_stack(path)
 
         # Fails
         else:
@@ -205,7 +258,7 @@ class DataLoader:
         return keys
 
 
-def load(path, virtual_stack=False, dataset_name=None, return_metadata=False, **kwargs):
+def load(path, virtual_stack=False, dataset_name=None, return_metadata=False, contains=None, **kwargs):
     """
     Load data from the specified file or directory.
 
@@ -216,7 +269,7 @@ def load(path, virtual_stack=False, dataset_name=None, return_metadata=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 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)
         **kwargs: Additional keyword arguments to be passed
         to the DataLoader constructor.
 
@@ -235,7 +288,8 @@ def load(path, virtual_stack=False, dataset_name=None, return_metadata=False, **
         virtual_stack=virtual_stack,
         dataset_name=dataset_name,
         return_metadata=return_metadata,
+        contains=contains,
         **kwargs,
     )
 
-    return loader.load(path)
+    return loader.load(path)
\ No newline at end of file
-- 
GitLab