From 22d6190c15677751178be2fb4461d2a9413bad4b Mon Sep 17 00:00:00 2001
From: Christopher Rhodes <christopher.rhodes@embl.de>
Date: Mon, 6 Nov 2023 17:43:37 +0100
Subject: [PATCH] A few workarounds with zero-length patch sets

---
 extensions/chaeo/accessors.py                 | 28 +++++++---
 .../chaeo/batch_jobs/20231028_Porto_PA.py     | 56 +++++++++++++++++++
 extensions/chaeo/products.py                  |  2 +
 extensions/chaeo/workflows.py                 | 25 +++++----
 4 files changed, 91 insertions(+), 20 deletions(-)
 create mode 100644 extensions/chaeo/batch_jobs/20231028_Porto_PA.py

diff --git a/extensions/chaeo/accessors.py b/extensions/chaeo/accessors.py
index f606470e..82f726ba 100644
--- a/extensions/chaeo/accessors.py
+++ b/extensions/chaeo/accessors.py
@@ -16,15 +16,25 @@ class MonoPatchStack(InMemoryDataAccessor):
             assert data.ndim == 3
             self._data = np.expand_dims(data, 2)
         elif isinstance(data, list): # list of YX patches
-            nda = np.array(data).squeeze()
-            assert nda.ndim == 3
-            self._data = np.expand_dims(
-                np.moveaxis(
-                    nda,
-                    [1, 2, 0],
-                    [0, 1, 2]),
-                2
-            )
+            if len(data) == 0:
+                self._data = np.ndarray([0, 0, 0, 0], dtype='uin9')
+            elif len(data) == 1:
+                self._data = np.expand_dims(
+                    np.array(
+                        data[0].squeeze()
+                    ),
+                    (2, 3)
+                )
+            else:
+                nda = np.array(data).squeeze()
+                assert nda.ndim == 3
+                self._data = np.expand_dims(
+                    np.moveaxis(
+                        nda,
+                        [1, 2, 0],
+                        [0, 1, 2]),
+                    2
+                )
         else:
             raise InvalidDataForPatchStackError(f'Cannot create accessor from {type(data)}')
 
diff --git a/extensions/chaeo/batch_jobs/20231028_Porto_PA.py b/extensions/chaeo/batch_jobs/20231028_Porto_PA.py
new file mode 100644
index 00000000..a4afbcdd
--- /dev/null
+++ b/extensions/chaeo/batch_jobs/20231028_Porto_PA.py
@@ -0,0 +1,56 @@
+from pathlib import Path
+
+from model_server.util import autonumber_new_directory, get_matching_files, loop_workflow
+from extensions.chaeo.ecotaxa import write_ecotaxa_tsv
+from extensions.chaeo.workflows import export_patches_from_multichannel_zstack
+from extensions.ilastik.models import IlastikPixelClassifierModel
+
+
+if __name__ == '__main__':
+    sample_id = '20231028-porto-PA'
+    root = Path('c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/')
+    where_czi = (root / 'TREC_STOP_26_Porto/Selection').__str__()
+    where_output = autonumber_new_directory(
+        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0023/output',
+        'batch-output'
+    )
+
+    px_ilp = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0017/pxAF405_dim8bit.ilp').__str__()
+
+    params = {
+        'pxmap_threshold': 0.25,
+        'pxmap_foreground_channel': 0,
+        'segmentation_channel': 0,
+        'zmask_zindex': None,
+        'patches_channel': 2,
+        'zmask_type': 'boxes',
+        'zmask_filters': {'area': (1e3, 1e8)},
+        'zmask_expand_box_by': (128, 3),
+        'export_pixel_probabilities': True,
+        'export_2d_patches_for_training': True,
+        'draw_bounding_box_on_2d_patch': True,
+        'export_2d_patches_for_annotation': True,
+        'export_3d_patches': False,
+        'export_annotated_zstack': True,
+        'export_patch_masks': True,
+        'zmask_clip': 0.01,
+        'rgb_overlay_channels': (1, None, None),
+        'rgb_overlay_weights': (0.2, 1.0, 1.0),
+        'draw_label_on_zstack': True,
+    }
+
+    input_files = get_matching_files(where_czi, 'czi', coord_filter={})
+
+    loop_workflow(
+        input_files,
+        where_output,
+        export_patches_from_multichannel_zstack,
+        [IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)})],
+        params,
+        catch_and_continue=False,
+    )
+
+    csv_path = (Path(where_output) / 'workflow_data.csv').__str__()
+    write_ecotaxa_tsv(csv_path, where_output, sample_id=sample_id, scope_id='EMBL-MS-Zeiss-LSM900')
+
+    print('Finished')
\ No newline at end of file
diff --git a/extensions/chaeo/products.py b/extensions/chaeo/products.py
index a0291874..0f1ac976 100644
--- a/extensions/chaeo/products.py
+++ b/extensions/chaeo/products.py
@@ -120,6 +120,7 @@ def get_patches_from_zmask_meta(
         **kwargs
 ) -> MonoPatchStack:
     patches = []
+
     for mi in zmask_meta:
 
         sl = mi['slice']
@@ -197,6 +198,7 @@ def get_patches_from_zmask_meta(
             patch = pad(patch, pad_to)
 
         patches.append(patch)
+
     if not make_3d and pc == 1:
         return MonoPatchStack(patches)
     else:
diff --git a/extensions/chaeo/workflows.py b/extensions/chaeo/workflows.py
index 57072bb8..119ee1ff 100644
--- a/extensions/chaeo/workflows.py
+++ b/extensions/chaeo/workflows.py
@@ -122,7 +122,7 @@ def export_patches_from_multichannel_zstack(
         )
         ti.click('export_pixel_probability')
 
-    if export_3d_patches:
+    if export_3d_patches and len(zmask_meta) > 0:
         files = export_patches_from_zstack(
             Path(output_folder_path) / '3d_patches',
             stack.get_one_channel_data(patches_channel),
@@ -134,7 +134,7 @@ def export_patches_from_multichannel_zstack(
         )
         ti.click('export_3d_patches')
 
-    if export_2d_patches_for_annotation:
+    if export_2d_patches_for_annotation and len(zmask_meta) > 0:
         files = export_multichannel_patches_from_zstack(
             Path(output_folder_path) / '2d_patches_annotation',
             stack,
@@ -159,7 +159,7 @@ def export_patches_from_multichannel_zstack(
         # prepopulate patch UUID
         df['patch_id'] = df.apply(lambda _: uuid4(), axis=1)
 
-    if export_2d_patches_for_training:
+    if export_2d_patches_for_training and len(zmask_meta) > 0:
         files = export_multichannel_patches_from_zstack(
             Path(output_folder_path) / '2d_patches_training',
             stack.get_one_channel_data(patches_channel),
@@ -171,7 +171,7 @@ def export_patches_from_multichannel_zstack(
         )
         ti.click('export_2d_patches')
 
-    if export_patch_masks:
+    if export_patch_masks and len(zmask_meta) > 0:
         files = export_patch_masks_from_zstack(
             Path(output_folder_path) / 'patch_masks',
             stack.get_one_channel_data(patches_channel),
@@ -195,13 +195,16 @@ def export_patches_from_multichannel_zstack(
 
     # generate multichannel projection from label centroids
     dff = df[df['keeper']]
-    interm['projected'] = project_stack_from_focal_points(
-        dff['centroid-0'].to_numpy(),
-        dff['centroid-1'].to_numpy(),
-        dff['zi'].to_numpy(),
-        stack,
-        degree=4,
-    )
+    if len(zmask_meta) > 0:
+        interm['projected'] = project_stack_from_focal_points(
+            dff['centroid-0'].to_numpy(),
+            dff['centroid-1'].to_numpy(),
+            dff['zi'].to_numpy(),
+            stack,
+            degree=4,
+        )
+    else: # else just return MIP
+        interm['projected'] = stack.data.max(axis=-1)
 
     return {
         'pixel_model_id': pixel_classifier.model_id,
-- 
GitLab