From 1a762d7ed853eef1698ad2951e586cd226650bfc Mon Sep 17 00:00:00 2001
From: Arin Wongprommoon <arin.wongprommoon@ed.ac.uk>
Date: Tue, 17 Jan 2023 11:38:20 +0000
Subject: [PATCH] fix!(aliby): incorporate ref_z in creating dummy image object

WHY IS THIS CHANGE NEEDED?:
- using dev_testimage.py in skeletons: when dummy tiler is created and
  tiler.initialise_traps() is invoked, segment_traps() (still) raises 'No valid
  'tiling regions found'

HOW DOES THE CHANGE SOLVE THE PROBLEM?:
- in tiler.initialise_traps(), initial_image was defined based on
  self.ref_channel and self.ref_z; however, if ref_z was not 0, then
  initial_image would be a zero array.
- this was because ImageDummy.pad_array() put the input image at the 0
  position and pads with zeros regardless of the value of ref_z.
- so i wanted the output of ImageDummy.get_data_lazy() to be defined in
  such a way that the (ref_z)th slice in the z-direction is the input
  image.
- therefore, i added a new argument to Image.Dummy.pad_array() so
  that ref_z can be used to achieve the above.

WHAT SIDE EFFECTS DOES THIS CHANGE HAVE?:
- tech debt: no (easy) way to deal with ref_channel because it's a
  string.  workaround is to keep the ref_channel the first channel
  in the dummy tiler, and assume that throughout.

EVIDENCE THAT COMMIT WORKS:
- modified test_image.py works
- skeletons: dummy tiler can be defined and dummy_tiler._run_tp(0) runs
  without errors
---
 src/aliby/io/image.py              | 27 ++++++++++++++++++++++-----
 src/aliby/tile/tiler.py            |  3 ++-
 tests/aliby/pipeline/test_image.py | 12 ++++++++----
 3 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/src/aliby/io/image.py b/src/aliby/io/image.py
index 0bd41880..da563f3e 100644
--- a/src/aliby/io/image.py
+++ b/src/aliby/io/image.py
@@ -138,7 +138,12 @@ class ImageDummy(BaseLocalImage):
 
     # Goal: make Tiler happy.
     @staticmethod
-    def pad_array(image_array: da.Array, dim: int, n_empty_slices: int):
+    def pad_array(
+        image_array: da.Array,
+        dim: int,
+        n_empty_slices: int,
+        image_position: int = 0,
+    ):
         """Extends a dimension in a dask array and pads with zeros
 
         Extends a dimension in a dask array that has existing content, then pads
@@ -153,19 +158,29 @@ class ImageDummy(BaseLocalImage):
         n_empty_slices : int
             Number of empty slices to extend the dask array by, in the specified
             dimension/axis.
+        image_position : int
+            Position within the new dimension to place the input arary, default 0
+            (the beginning).
 
         Examples
         --------
         ```
-        extended_array = pad_array(my_da_array, dim = 2, n_empty_slices = 4)
+        extended_array = pad_array(
+            my_da_array, dim = 2, n_empty_slices = 4, image_position = 1)
         ```
         Extends a dask array called `my_da_array` in the 3rd dimension
-        (dimensions start from 0) by 4 slices, filled with zeros.
+        (dimensions start from 0) by 4 slices, filled with zeros.  And puts the
+        original content in slice 1 of the 3rd dimension
         """
         # Concats zero arrays with same dimensions as image_array, and puts
         # image_array as first element in list of arrays to be concatenated
+        zeros_array = da.zeros_like(image_array)
         return da.concatenate(
-            [image_array, *([da.zeros_like(image_array)] * n_empty_slices)],
+            [
+                *([zeros_array] * image_position),
+                image_array,
+                *([zeros_array] * (n_empty_slices - image_position)),
+            ],
             axis=dim,
         )
 
@@ -192,7 +207,9 @@ class ImageDummy(BaseLocalImage):
             img, dim=0, n_empty_slices=199
         )  # 200 timepoints total
         img = self.pad_array(img, dim=1, n_empty_slices=2)  # 3 channels
-        img = self.pad_array(img, dim=2, n_empty_slices=4)  # 5 z-stacks
+        img = self.pad_array(
+            img, dim=2, n_empty_slices=4, image_position=self.ref_z
+        )  # 5 z-stacks
         return img
 
     def name(self):
diff --git a/src/aliby/tile/tiler.py b/src/aliby/tile/tiler.py
index 9de61621..93ae8cc4 100644
--- a/src/aliby/tile/tiler.py
+++ b/src/aliby/tile/tiler.py
@@ -258,6 +258,7 @@ class Tiler(StepABC):
         """
         imgdmy_obj = ImageDummy(parameters)
         dummy_image = imgdmy_obj.get_data_lazy()
+        # FIXME: TECHNICAL DEBT: hard-coding dimension orders
         dummy_omero_metadata = {
             "size_x": dummy_image.shape[3],
             "size_y": dummy_image.shape[4],
@@ -265,8 +266,8 @@ class Tiler(StepABC):
             "size_c": dummy_image.shape[1],
             "size_t": dummy_image.shape[0],
             "channels": [
-                *(["nil"] * (dummy_image.shape[1] - 1)),
                 parameters["ref_channel"],
+                *(["nil"] * (dummy_image.shape[1] - 1)),
             ],
             "name": " ",
         }
diff --git a/tests/aliby/pipeline/test_image.py b/tests/aliby/pipeline/test_image.py
index d8343a5d..3a211400 100644
--- a/tests/aliby/pipeline/test_image.py
+++ b/tests/aliby/pipeline/test_image.py
@@ -17,23 +17,27 @@ sample_da = da.reshape(
 @pytest.mark.parametrize("sample_da", [sample_da])
 @pytest.mark.parametrize("dim", [2])
 @pytest.mark.parametrize("n_empty_slices", [4])
-def test_pad_array(sample_da, dim, n_empty_slices):
+@pytest.mark.parametrize("image_position", [1])
+def test_pad_array(sample_da, dim, n_empty_slices, image_position):
     """Test ImageDummy.pad_array() method"""
     # create object
     imgdmy = ImageDummy(tiler_parameters)
     # pads array
     padded_da = imgdmy.pad_array(
-        sample_da, dim=dim, n_empty_slices=n_empty_slices
+        sample_da,
+        dim=dim,
+        n_empty_slices=n_empty_slices,
+        image_position=image_position,
     )
 
     # select which dimension to index the multidimensional array
-    indices = {dim: 0}
+    indices = {dim: image_position}
     ix = [
         indices.get(dim, slice(None))
         for dim in range(padded_da.compute().ndim)
     ]
 
-    # Checks that original image array is there and is at the first index
+    # Checks that original image array is there and is at the correct index
     assert np.array_equal(padded_da.compute()[ix], sample_da.compute()[0])
     # Checks that the additional axis is extended correctly
     assert padded_da.compute().shape[dim] == n_empty_slices + 1
-- 
GitLab