From c78eca27abb64c39a90b9ba02cb5393b012817dd Mon Sep 17 00:00:00 2001
From: Christopher Rhodes <christopher.rhodes@embl.de>
Date: Wed, 28 Feb 2024 14:00:53 +0100
Subject: [PATCH] Moved all of chaeo extension to trec-adaptive-feedback
 project

---
 model_server/extensions/chaeo/__init__.py     |   0
 .../chaeo/batch_jobs/20231026_Porto.py        |  82 --------
 .../extensions/chaeo/batch_jobs/__init__.py   |   0
 .../extensions/chaeo/conf/__init__.py         |   0
 model_server/extensions/chaeo/ecotaxa.py      |  45 -----
 .../extensions/chaeo/examples/__init__.py     |   0
 .../chaeo/examples/batch_obj_cla.py           |  45 -----
 .../chaeo/examples/label_patches.py           |  17 --
 ...fer_labels_to_ilastik_object_classifier.py |  97 ----------
 model_server/extensions/chaeo/h5util.py       |  34 ----
 model_server/extensions/chaeo/models.py       | 112 -----------
 .../20230805_kristineberg_PA.py               |  78 --------
 .../20230807_kristineberg_spiked.py           |  52 -----
 .../old_batch_jobs/20231008_Bilbao_PA.py      |  91 ---------
 .../old_batch_jobs/20231023_Porto_4ch.py      |  78 --------
 .../20231023_Porto_EtOHfixed.py               |  55 ------
 .../old_batch_jobs/20231023_Porto_Live.py     |  56 ------
 .../chaeo/old_batch_jobs/__init__.py          |   0
 .../chaeo/old_batch_jobs/coloring_book.py     |  52 -----
 .../int_test_20231028_Porto_PA.py             |  81 --------
 .../old_batch_jobs/proj0004-exp0038-fixed.py  |  43 -----
 model_server/extensions/chaeo/router.py       |  42 -----
 .../extensions/chaeo/tests/__init__.py        |   0
 .../chaeo/tests/test_roiset_workflow.py       |  67 -------
 model_server/extensions/chaeo/workflows.py    | 177 ------------------
 25 files changed, 1304 deletions(-)
 delete mode 100644 model_server/extensions/chaeo/__init__.py
 delete mode 100644 model_server/extensions/chaeo/batch_jobs/20231026_Porto.py
 delete mode 100644 model_server/extensions/chaeo/batch_jobs/__init__.py
 delete mode 100644 model_server/extensions/chaeo/conf/__init__.py
 delete mode 100644 model_server/extensions/chaeo/ecotaxa.py
 delete mode 100644 model_server/extensions/chaeo/examples/__init__.py
 delete mode 100644 model_server/extensions/chaeo/examples/batch_obj_cla.py
 delete mode 100644 model_server/extensions/chaeo/examples/label_patches.py
 delete mode 100644 model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py
 delete mode 100644 model_server/extensions/chaeo/h5util.py
 delete mode 100644 model_server/extensions/chaeo/models.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/20230805_kristineberg_PA.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/20230807_kristineberg_spiked.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/20231008_Bilbao_PA.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_4ch.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_EtOHfixed.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_Live.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/__init__.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/coloring_book.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/int_test_20231028_Porto_PA.py
 delete mode 100644 model_server/extensions/chaeo/old_batch_jobs/proj0004-exp0038-fixed.py
 delete mode 100644 model_server/extensions/chaeo/router.py
 delete mode 100644 model_server/extensions/chaeo/tests/__init__.py
 delete mode 100644 model_server/extensions/chaeo/tests/test_roiset_workflow.py
 delete mode 100644 model_server/extensions/chaeo/workflows.py

diff --git a/model_server/extensions/chaeo/__init__.py b/model_server/extensions/chaeo/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/model_server/extensions/chaeo/batch_jobs/20231026_Porto.py b/model_server/extensions/chaeo/batch_jobs/20231026_Porto.py
deleted file mode 100644
index 52ec5788..00000000
--- a/model_server/extensions/chaeo/batch_jobs/20231026_Porto.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from pathlib import Path
-
-from model_server.base.roiset import RoiSetMetaParams, RoiSetExportParams
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.ecotaxa import write_ecotaxa_tsv
-from model_server.extensions.chaeo.workflows import export_zstack_roiset
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-
-if __name__ == '__main__':
-    sample_id = '20231026-porto'
-    root = Path('y:/TREC_STOP_26_Porto/MobileLab/LSM900')
-    where_czi = (root / '231026_automic/20231026-152512_lowzoom_data/LowZoom').__str__()
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0022/output',
-        'batch-output'
-    )
-
-    # px_ilp = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0017/pxAF405_dim8bit.ilp').__str__()
-    px_ilp = Path(
-        'y:/TREC_Heidelberg/LSM900/Tina/20240109_automic/ilastik/pxAF405-8bit-embedded.ilp'
-    ).__str__()
-
-    params = {
-        'segmentation_channel': 0,
-        'patches_channel': 2,
-    }
-
-    roi_params = RoiSetMetaParams(**{
-        'mask_type': 'boxes',
-        'filters': {
-            'area': {'min': 1e3, 'max': 1e8}
-        },
-        'expand_box_by': [128, 2]
-    })
-
-    export_params = RoiSetExportParams(**{
-        'pixel_probabilities': True,
-        'patches_3d': {},
-        'annotated_patches_2d': {
-            'draw_bounding_box': True,
-            'rgb_overlay_channels': [1, None, None],
-            'rgb_overlay_weights': [0.2, 1.0, 1.0],
-            'pad_to': 512,
-        },
-        'patches_2d': {
-            'draw_bounding_box': False,
-        },
-        'annotated_zstacks': {},
-        'object_classes': True,
-        'dataframe': True,
-    })
-
-    input_files = get_matching_files(where_czi, 'czi', coord_filter={})
-
-    models = {
-        'pixel_classifier': {
-            'model': IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)}),
-            'params': {
-                'px_class': 0,
-                'px_prob_threshold': 0.25,
-            }
-        },
-    }
-
-    loop_workflow(
-        input_files,
-        where_output,
-        export_zstack_roiset,
-        models,
-        # params,
-        segmentation_channel=params['segmentation_channel'],
-        patches_channel=params['patches_channel'],
-        export_params=export_params,
-        roi_params=roi_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/model_server/extensions/chaeo/batch_jobs/__init__.py b/model_server/extensions/chaeo/batch_jobs/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/model_server/extensions/chaeo/conf/__init__.py b/model_server/extensions/chaeo/conf/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/model_server/extensions/chaeo/ecotaxa.py b/model_server/extensions/chaeo/ecotaxa.py
deleted file mode 100644
index 255552f3..00000000
--- a/model_server/extensions/chaeo/ecotaxa.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from pathlib import Path
-
-import pandas as pd
-
-def write_ecotaxa_tsv(patches_csv_path: str, where: str, sample_id: str, scope_id: str):
-    # import patch output table
-    df_patches = pd.read_csv(patches_csv_path)
-    df_patches['img_file_name'] = df_patches['patch_filename'].apply(lambda x: '2d_patches_annotation/' + x)
-
-    # make second column index level to comply w/ EcoTaxa schema
-    df_patches.columns = pd.MultiIndex.from_frame(
-        pd.DataFrame([
-            df_patches.columns,
-            df_patches.dtypes.apply(
-                lambda x: '[f]' if x in ['float64'] else '[t]'
-            )
-        ]).T,
-        names=['variable', 'data type']
-    )
-
-    # add new columns for ecotaxa
-    df_patches.loc[:, ('acq_instrument', '[t]')] = 'Other microscope'
-    df_patches.loc[:, ('acq_instrument_microscope', '[t]')] = scope_id
-    df_patches.loc[:, ('sample_id', '[t]')] = sample_id
-
-    df_patches.loc[:, ('acq_id', '[t]')] = df_patches.loc[:, ('input_file', '[t]')]
-    df_patches.loc[:, ('object_id', '[t]')] = df_patches.loc[:, ('patch_id', '[t]')]
-    df_patches.loc[:, ('process_id', '[t]')] = df_patches.loc[:, ('patch_id', '[t]')]
-    df_patches.loc[:, ('acq_pixel_scale_in_micrometers', '[f]')] = df_patches.loc[:, ('pixel_scale_in_micrometers', '[f]')]
-
-    cols_to_transfer = [
-        'img_file_name',
-        'object_id',
-        'acq_id',
-        'acq_instrument',
-        'acq_instrument_microscope',
-        'sample_id',
-        'process_id'
-    ]
-    df_export = df_patches.loc[:, pd.IndexSlice[cols_to_transfer, :]]
-    df_export.to_csv(Path(where) / 'ecotaxa.tsv', sep='\t', index=False)
-
-def write_ecotaxa_tsv_chunked_subdirectories(top_dir: str, csv_filename: str, sample_id: str, scope_id: str):
-    for sd in Path(top_dir).iterdir():
-        write_ecotaxa_tsv((sd / csv_filename).__str__(), sd.__str__(), sample_id, scope_id)
diff --git a/model_server/extensions/chaeo/examples/__init__.py b/model_server/extensions/chaeo/examples/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/model_server/extensions/chaeo/examples/batch_obj_cla.py b/model_server/extensions/chaeo/examples/batch_obj_cla.py
deleted file mode 100644
index 752c50c5..00000000
--- a/model_server/extensions/chaeo/examples/batch_obj_cla.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from pathlib import Path
-
-from model_server.conf.testing import output_path
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from extensions.ilastik.models import PatchStackObjectClassifier
-from model_server.extensions.chaeo.workflows import export_zstack_roiset
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-
-
-if __name__ == '__main__':
-    where_czi = 'c:/Users/rhodes/projects/proj0004-marine-photoactivation/data/exp0038/AutoMic/20230906-163415/Selection'
-    where_output = autonumber_new_directory(
-        output_path,
-        'zmask_to_automic',
-    )
-
-    px_ilp = Path.home() / 'model_server' / 'testing' / 'zmask' / 'AF405-bodies_boundaries.ilp'
-    ob_ilp = Path.home() / 'model_server' / 'testing' / 'zmask' / 'auto_obj_after.ilp'
-
-    params = {
-        'pxmap_threshold': 0.25,
-        'pxmap_foreground_channel': 0,
-        'segmentation_channel': 0,
-        'patches_channel': 4,
-        'zmask_filters': {'area': (1e3, 1e8)},
-    }
-
-    input_files = get_matching_files(where_czi, 'czi', coord_filter={'P': (0, 10)}, )
-
-    models = [
-        IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)}),
-        PatchStackObjectClassifier(params={'project_file': Path(ob_ilp)})
-    ]
-
-    loop_workflow(
-        input_files,
-        where_output,
-        export_zstack_roiset,
-        models,
-        params,
-        catch_and_continue=False,
-    )
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/examples/label_patches.py b/model_server/extensions/chaeo/examples/label_patches.py
deleted file mode 100644
index 54b242ee..00000000
--- a/model_server/extensions/chaeo/examples/label_patches.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory
-from model_server.extensions.chaeo.workflows import transfer_ecotaxa_labels_to_patch_stacks
-
-if __name__ == '__main__':
-    root = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0009/output')
-    transfer_ecotaxa_labels_to_patch_stacks(
-        where_masks=(root / 'batch-output-20231012-0016/patch_masks').__str__(),
-        where_patches=(root / 'batch-output-20231012-0016/2d_patches_training').__str__(),
-        object_csv=(root / 'batch-output-20231011-0003/df_objects.csv').__str__(),
-        ecotaxa_tsv='c:/Users/rhodes/projects/proj0011-plankton-seg/exp0013/ecotaxa_export_10468_20231012_0930.tsv',
-        where_output=autonumber_new_directory(root, 'labeled_patches'),
-    )
-    print('Finished')
-
-
diff --git a/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py b/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py
deleted file mode 100644
index 04e0c20e..00000000
--- a/model_server/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from pathlib import Path
-import numpy as np
-import pandas as pd
-import skimage
-
-from model_server.base.accessors import make_patch_stack_from_file
-from model_server.extensions.chaeo.models import generate_ilastik_object_classifier
-from extensions.ilastik.models import PatchStackObjectClassifier
-from model_server.base.accessors import GenericImageDataAccessor, write_accessor_data_to_file
-
-
-def compare_object_maps(truth: GenericImageDataAccessor, inferred: GenericImageDataAccessor) -> pd.DataFrame:
-    """
-    Compare two object maps to assess classification results
-    :param truth: t-stack of truth objects
-    :param inferred: t-stack of inferred objects, presumably with same segmentation boundaries as truth
-    :return: DataFrame comparing results for each frame in truth and inferred stacks
-    """
-    assert truth.shape == inferred.shape
-    assert np.all((truth.data == 0) == (inferred.data == 0))
-    assert inferred.chroma == 1
-
-    labels = []
-    for zi in range(0, inferred.nz):
-        inf_img = inferred.data[:, :, :, zi]
-
-        unique = np.unique(inf_img)
-        assert unique[0] == 0
-
-        dd = {'zi': zi, 'truth_label': np.unique(truth.data[:, :, :, zi])[1], 'multiples': False}
-
-        if len(unique) == 1:  # no object in frame
-            dd['inferred_label'] = unique[0]
-        elif len(unique) > 2:  # multiple objects in frame, so mask out all but largest
-            print(f'Found multiple nonzero unique values in label {zi}: {unique}')
-            ob_id = skimage.measure.label(inf_img)
-            pr = skimage.measure.regionprops_table(ob_id, properties=['label', 'area'])
-            mask = inf_img == pr['label'][pr['area'].argmax()]
-            dd['inferred_label'] = np.unique(mask * inf_img)[-1]  # occasionally no object in frame
-            dd['multiples'] = True
-        else:  # exactly one unique object class in frame
-            dd['inferred_label'] = unique[1]
-        labels.append(dd)
-    return pd.DataFrame(labels)
-
-
-def infer_and_compare(classifier: PatchStackObjectClassifier, prefix, raw, mask, labels):
-    result_acc, _ = classifier.infer(raw, mask)
-    write_accessor_data_to_file(root / f'zstack_train_result.tif', result_acc)
-
-    # write comparison tables
-    df_comp = compare_object_maps(labels, result_acc)
-    df_comp.to_csv(root / f'compare_{prefix}_result.csv', index=False)
-    print(f'Generated ilastik project {classifier_file.name}')
-    print('Truth and inferred labels match?')
-    print(pd.value_counts(df_comp['truth_label'] == df_comp['inferred_label']))
-
-
-if __name__ == '__main__':
-    root = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0009/output/labeled_patches-20231030-0007')
-    template_ilp = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0014/template_obj.ilp')
-
-    df_labels = pd.read_csv(root / 'labels_key.csv')
-    label_names = list(df_labels.sort_values('annotation_class_id').annotation_class.unique())
-    label_names[0] = 'none'
-    assert len(label_names) >= 2
-
-    # auto-populate an object classifier
-    classifier_file = generate_ilastik_object_classifier(
-        template_ilp,
-        root / 'new_auto_obj.ilp',
-        make_patch_stack_from_file(root / 'zstack_train_raw.tif'),
-        make_patch_stack_from_file(root / 'zstack_train_mask.tif'),
-        make_patch_stack_from_file(root / 'zstack_train_label.tif'),
-        label_names,
-        allow_multiple_objects=False
-    )
-    classifier = PatchStackObjectClassifier({'project_file': classifier_file})
-
-    # verify self-consistency of training set
-    infer_and_compare(
-        classifier,
-        'train',
-        make_patch_stack_from_file(root / 'zstack_train_raw.tif'),
-        make_patch_stack_from_file(root / 'zstack_train_mask.tif'),
-        make_patch_stack_from_file(root / 'zstack_train_label.tif')
-    )
-
-    # run test set
-    infer_and_compare(
-        classifier,
-        'test',
-        make_patch_stack_from_file(root / 'zstack_test_raw.tif'),
-        make_patch_stack_from_file(root / 'zstack_test_mask.tif'),
-        make_patch_stack_from_file(root / 'zstack_test_label.tif'),
-    )
-
diff --git a/model_server/extensions/chaeo/h5util.py b/model_server/extensions/chaeo/h5util.py
deleted file mode 100644
index 0b508132..00000000
--- a/model_server/extensions/chaeo/h5util.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import json
-import uuid
-
-import h5py
-
-def get_dataset_info(h5: h5py.File, lane : int = 0):
-    """
-    Report out specific datasets in ilastik project file HDF5
-    :param h5: handle to ilastik project file, as h5py.File object
-    :param lane: ilastik lane identifier
-    :return: (dict) selected data values from project file
-    """
-    lns = f'{lane:04d}'
-    lane = f'Input Data/infos/lane{lns}'
-    info = {}
-    for gk in ['Raw Data', 'Segmentation Image']:
-        info[gk] = {}
-        for dk in ['location', 'filePath', 'shape', 'nickname']:
-            try:
-                info[gk][dk] = h5[f'{lane}/{gk}/{dk}'][()]
-            except Exception as e:
-                print(e)
-        try:
-            info[gk]['id'] = uuid.UUID(h5[f'{lane}/{gk}/datasetId'][()].decode())
-        except ValueError as e:
-            info[gk]['id'] = '<invalid UUID>'
-        info[gk]['axistags'] = json.loads(h5[f'{lane}/{gk}/axistags'][()].decode())
-        info[gk]['axes'] = [ax['key'] for ax in info[gk]['axistags']['axes']]
-
-    obj_cl_group = h5[f'ObjectClassification/LabelInputs/{lns}']
-    info['misc'] = {
-        'number_of_label_inputs': len(obj_cl_group.items())
-    }
-    return info
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/models.py b/model_server/extensions/chaeo/models.py
deleted file mode 100644
index 865537e6..00000000
--- a/model_server/extensions/chaeo/models.py
+++ /dev/null
@@ -1,112 +0,0 @@
-from pathlib import Path
-import shutil
-
-import h5py
-import numpy as np
-import skimage
-
-from model_server.base.accessors import PatchStack
-
-
-def generate_ilastik_object_classifier(
-        template_ilp: Path,
-        target_ilp: Path,
-        raw_stack: PatchStack,
-        mask_stack: PatchStack,
-        label_stack: PatchStack,
-        label_names: list,
-        lane: int = 0,
-        allow_multiple_objects=True,
-) -> Path:
-    """
-    Starting with a template project file, transfer input data and labels to a new project file.
-    :param template_ilp: path to existing ilastik object classifier to use as a template
-    :param target_ilp: path to new classifier
-    :param raw_stack: stack of patches containing raw data
-    :param mask_stack: stack of patches containing object masks
-    :param label_stack: stack of patches containing object labels
-    :param label_names: list of label names
-    :param lane: ilastik lane identifier
-    :param allow_multiple_objects: skip check for multiple objects in each mask image
-    :return: path to generated object classifier
-    """
-    assert mask_stack.shape == raw_stack.shape
-    assert label_stack.shape == raw_stack.shape
-
-    new_ilp = shutil.copy(template_ilp, target_ilp)
-
-    accessors = {
-        'Raw Data': raw_stack,
-        'Segmentation Image': mask_stack,
-    }
-
-    # get labels from label image
-    labels = []
-    for ii in range(0, label_stack.count):
-        unique = np.unique(label_stack.iat(ii))
-        assert len(unique) <= 2, 'Label image contains more than one non-zero value'
-        assert unique[0] == 0, 'Label image does not contain unlabeled background'
-        assert unique[-1] < len(label_names) + 1, f'Label ID {unique[-1]} exceeds number of label names: {len(label_names)}'
-        labels.append(unique[-1])
-
-        if not allow_multiple_objects:
-            ob_id = skimage.measure.label(mask_stack.iat(ii))
-            num_obj = len(np.unique(ob_id)) - 1
-            if num_obj > 1:
-                raise MoreThanOneObjectError(f'Found {num_obj} connected objects in mask {ii}')
-
-    # write to new project file
-    with h5py.File(new_ilp, 'r+') as h5:
-
-        for gk in ['Raw Data', 'Segmentation Image']:
-            group = f'Input Data/infos/lane{lane:04d}/{gk}'
-
-            # set path to input image files
-            del h5[f'{group}/filePath']
-            h5[f'{group}/filePath'] = accessors[gk].fpath.name
-            assert not Path(h5[f'{group}/filePath'][()].decode()).is_absolute()
-            assert h5[f'{group}/filePath'][()] == accessors[gk].fpath.name.encode()
-            assert h5[f'{group}/location'][()] == 'FileSystem'.encode()
-
-            # set input nickname
-            del h5[f'{group}/nickname']
-            h5[f'{group}/nickname'] = accessors[gk].fpath.stem
-
-            # set input shape
-            del h5[f'{group}/shape']
-            shape_zyx = [accessors[gk].shape_dict[ax] for ax in ['Z', 'Y', 'X']]
-            h5[f'{group}/shape'] = np.array(shape_zyx)
-
-        # change key of label names
-        if (k := 'ObjectClassification/LabelNames') in h5.keys():
-            del h5[k]
-        ln = np.array(label_names)
-        h5.create_dataset(k, data=ln.astype('O'))
-
-        if (k := 'ObjectClassification/MaxNumObj') in h5.keys():
-            del h5[k]
-        h5[k] = len(label_names) - 1
-
-        del h5['currentApplet']
-        h5['currentApplet'] = 1
-
-        # change object labels
-        if (k := f'ObjectClassification/LabelInputs/{lane:04d}') in h5.keys():
-            del h5[k]
-        lag = h5.create_group(k)
-        for zi, la in enumerate(labels):
-            lag[f'{zi}'] = np.array([0., float(la)])
-
-        # delete existing classification weights
-        if (k := f'ObjectExtraction/RegionFeatures/{lane:04d}') in h5.keys():
-            del h5[k]
-        if (k := 'ObjectClassification/ClassifierForests') in h5.keys():
-            del h5[k]
-
-    return Path(new_ilp)
-
-class Error(Exception):
-    pass
-
-class MoreThanOneObjectError(Error):
-    pass
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/20230805_kristineberg_PA.py b/model_server/extensions/chaeo/old_batch_jobs/20230805_kristineberg_PA.py
deleted file mode 100644
index 9e4e8016..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/20230805_kristineberg_PA.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-from model_server.base.accessors import CziImageFileAccessor, write_accessor_data_to_file, InMemoryDataAccessor
-from model_server.base.process import rescale
-
-
-def export_single_channel_tif_from_multichannel_czi(input_file_path, output_folder_path, channel, **kwargs):
-    in_acc = CziImageFileAccessor(input_file_path)
-    data = in_acc.get_one_channel_data(channel).data
-    if 'rescale_zmask_clip' in kwargs:
-        data = rescale(data, clip=kwargs['rescale_zmask_clip'])
-    outf = Path(output_folder_path) / (Path(input_file_path).stem + '.tif')
-    write_accessor_data_to_file(
-        outf,
-        InMemoryDataAccessor(data),
-    )
-    print(f'Wrote file: {outf}')
-
-if __name__ == '__main__':
-    where_czi = 'c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/TREC_STOP_15_Kristineberg/230805_automic_AI_PA/20230805-122525_AI_PA_successfulrun_recognitiononPLL405cilindionas/Selection'
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0016/output',
-        'batch-output'
-    )
-
-    px_ilp = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0016/pxAF405_dim8bit.ilp')
-
-    params = {
-        'pxmap_threshold': 0.25,
-        'pxmap_foreground_channel': 0,
-        'segmentation_channel': 0,
-        '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': (3, None, None),
-        'rgb_overlay_weights': (0.5, 1.0, 1.0)
-    }
-
-    input_files = get_matching_files(where_czi, 'czi', coord_filter={})
-
-
-    tif_export_params = {
-        'channel': 0,
-        'rescale_zmask_clip': 0.01,
-    }
-
-    # loop_workflow(
-    #     input_files,
-    #     where_output,
-    #     export_single_channel_tif_from_multichannel_czi,
-    #     tif_export_params,
-    #     catch_and_continue=False,
-    #     export_batch_csvs=False,
-    #     write_intermediate_products=False,
-    # )
-
-    loop_workflow(
-        input_files,
-        where_output,
-        export_patches_from_multichannel_zstack,
-        [IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)})],
-        params,
-        catch_and_continue=False,
-    )
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/20230807_kristineberg_spiked.py b/model_server/extensions/chaeo/old_batch_jobs/20230807_kristineberg_spiked.py
deleted file mode 100644
index cfdd0531..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/20230807_kristineberg_spiked.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-# TODO: support list-comp of single image sequence in multiple locations
-
-# TODO: split multi-pos CZI into sequence of accessors, append to list
-
-if __name__ == '__main__':
-    root = Path('c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/')
-    where_czi = (root / 'TREC_STOP_24_Bilbao/231008_automic/20231008-162336/Selection').__str__()
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0017',
-        '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,
-        'patches_channel': 2,
-        'zmask_clip': 0.01,
-        '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,
-        'rgb_overlay_channels': (1, None, None),
-        'rgb_overlay_weights': (0.5, 1.0, 1.0)
-    }
-
-    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,
-    )
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/20231008_Bilbao_PA.py b/model_server/extensions/chaeo/old_batch_jobs/20231008_Bilbao_PA.py
deleted file mode 100644
index e0929874..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/20231008_Bilbao_PA.py
+++ /dev/null
@@ -1,91 +0,0 @@
-from pathlib import Path
-
-import pandas as pd
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-def write_ecotaxa_tsv(patches_csv_path, where):
-    # import patch output table
-    df_patches = pd.read_csv(patches_csv_path)
-    df_patches['img_file_name'] = df_patches['patch_filename'].apply(lambda x: '2d_patches_annotation/' + x)
-
-    # make second column index level to comply w/ EcoTaxa schema
-    df_patches.columns = pd.MultiIndex.from_frame(
-        pd.DataFrame([
-            df_patches.columns,
-            df_patches.dtypes.apply(
-                lambda x: '[f]' if x in ['float64'] else '[t]'
-            )
-        ]).T,
-        names=['variable', 'data type']
-    )
-
-    # add new columns for ecotaxa
-    df_patches.loc[:, ('acq_instrument', '[t]')] = 'Other microscope'
-    df_patches.loc[:, ('acq_instrument_microscope', '[t]')] = 'EMBL-MS-Zeiss-LSM900'
-    df_patches.loc[:, ('sample_id', '[t]')] = '20231008-bilbao'
-
-    df_patches.loc[:, ('acq_id', '[t]')] = df_patches.loc[:, ('input_file', '[t]')]
-    df_patches.loc[:, ('object_id', '[t]')] = df_patches.loc[:, ('patch_id', '[t]')]
-    df_patches.loc[:, ('process_id', '[t]')] = df_patches.loc[:, ('patch_id', '[t]')]
-
-    cols_to_transfer = [
-        'img_file_name',
-        'object_id',
-        'acq_id',
-        'acq_instrument',
-        'acq_instrument_microscope',
-        'sample_id',
-        'process_id'
-    ]
-    df_export = df_patches.loc[:, pd.IndexSlice[cols_to_transfer, :]]
-    df_export.to_csv(Path(where) / 'ecotaxa.tsv', sep='\t', index=False)
-
-if __name__ == '__main__':
-    root = Path('c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/')
-    where_czi = (root / 'TREC_STOP_24_Bilbao/231008_automic/20231008-162336/Selection').__str__()
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0017/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': 3,
-        '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)
-    }
-
-    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)
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_4ch.py b/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_4ch.py
deleted file mode 100644
index a421c791..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_4ch.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from pathlib import Path
-
-import czifile
-
-from model_server.base.accessors import write_accessor_data_to_file
-from model_server.base.czi_util import get_accessor_from_multiposition_czi
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.ecotaxa import write_ecotaxa_tsv_chunked_subdirectories
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-
-if __name__ == '__main__':
-    sample_id = '20231023-porto-4ch'
-    root = Path('y:/TREC_STOP_26_Porto/MobileLab/LSM900/231023')
-    czi_filepath = root / 'PK2_LM_BioO5D_231023_am_bottle_8_5_1_2_7.czi'
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0017-ehcfm-analysis/exp0001/output',
-        'batch-output'
-    )
-    cf = czifile.CziFile(czi_filepath.__str__())
-
-    # write single-position TIF color stacks
-    where_proc = Path(where_output) / 'proc'
-
-    # for pos in range(0, cf.shape[cf.axes.index('S')]):
-    for pos in [0, 50, 100, 150, 200]:
-        fname = f'{czi_filepath.stem}_p{pos:04d}.tif'
-        write_accessor_data_to_file(
-            where_proc / fname,
-            get_accessor_from_multiposition_czi(cf, pos)
-        )
-        print(f'Wrote file {fname}')
-
-    px_ilp = Path('c:/Users/rhodes/projects/proj0017-ehcfm-analysis/exp0001/ilastik/px_TL_boundaries.ilp').__str__()
-
-    params = {
-        'pxmap_threshold': 0.4,
-        'pxmap_foreground_channel': 0,
-        'segmentation_channel': 2,
-        'zmask_zindex': None,
-        'patches_channel': 2,
-        'zmask_type': 'boxes',
-        'zmask_filters': {'area': (2e2, 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,
-        'pxmap_use_all_channels': False,
-    }
-
-    input_files = get_matching_files(where_proc, 'tif', 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,
-        chunk_size=50,
-    )
-
-    write_ecotaxa_tsv_chunked_subdirectories(
-        where_output,
-        'workflow_data.csv',
-        sample_id=sample_id,
-        scope_id='EMBL-MS-Zeiss-LSM900',
-    )
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_EtOHfixed.py b/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_EtOHfixed.py
deleted file mode 100644
index abc8eeec..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_EtOHfixed.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.ecotaxa import write_ecotaxa_tsv
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-
-if __name__ == '__main__':
-    root = Path('c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/')
-    where_czi = (root / 'TREC_STOP_26_Porto/231023_automic/20231023-175838_EtOHfixed/LowZoom').__str__()
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0020/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='20231023-porto', scope_id='EMBL-MS-Zeiss-LSM900')
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_Live.py b/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_Live.py
deleted file mode 100644
index e458f7a3..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/20231023_Porto_Live.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.ecotaxa import write_ecotaxa_tsv
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-
-if __name__ == '__main__':
-    sample_id = '20231023-porto-live'
-    root = Path('c:/Users/rhodes/projects/proj0012-trec-handoff/owncloud-sync/TREC-HD/Images/')
-    where_czi = (root / 'TREC_STOP_26_Porto/231023_automic/20231023-182725_Live/LowZoom').__str__()
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0021/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/model_server/extensions/chaeo/old_batch_jobs/__init__.py b/model_server/extensions/chaeo/old_batch_jobs/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/model_server/extensions/chaeo/old_batch_jobs/coloring_book.py b/model_server/extensions/chaeo/old_batch_jobs/coloring_book.py
deleted file mode 100644
index e65dd1d2..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/coloring_book.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from pathlib import Path
-
-import numpy as np
-import pandas as pd
-from skimage.filters import gaussian
-from skimage.measure import label, regionprops_table
-
-import tifffile
-
-from model_server.base.accessors import PatchStack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-from model_server.base.accessors import write_accessor_data_to_file, InMemoryDataAccessor
-
-if __name__ == '__main__':
-    root = Path('c:/Users/rhodes/projects/proj0011-plankton-seg/exp0024')
-    px_ilp = root / 'px02.ilp'
-    sigma = 1.0
-    thresh = 0.15
-    min_area = 400
-
-    tf = tifffile.imread(root / '20231008-162336-z04-TL.tif')
-    instack = PatchStack(np.moveaxis(tf, 0, -1))
-    px_mod = IlastikPixelClassifierModel(params={'project_file': px_ilp})
-    pxmaps = []
-
-    def pipeline(yx_img):
-        pxmap, _ = px_mod.infer(InMemoryDataAccessor(yx_img))
-        smoothed = gaussian(pxmap.get_one_channel_data(0).data, sigma=sigma)
-        mask = smoothed > thresh
-        proc_mask = mask
-        lamap = label(proc_mask).astype('uint16')
-        df = (
-            pd.DataFrame(
-                regionprops_table(
-                    lamap[:, :, 0, 0],
-                    properties=('label', 'area')
-                )
-            )
-        )
-        la_keep = df.loc[df['area'] > min_area, 'label'].unique()
-        fil_mask = np.isin(lamap, la_keep)
-        inv = np.invert(fil_mask)
-        return InMemoryDataAccessor(inv)
-
-    results = []
-    for i in range(0, instack.count):
-        res = pipeline(instack.iat(i))
-        results.append(res)
-        outfile = root / 'output' / f'res_{i:04d}.tif'
-        outfile.parent.mkdir(parents=True, exist_ok=True)
-        write_accessor_data_to_file(outfile, res)
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/int_test_20231028_Porto_PA.py b/model_server/extensions/chaeo/old_batch_jobs/int_test_20231028_Porto_PA.py
deleted file mode 100644
index 4248d80e..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/int_test_20231028_Porto_PA.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.ecotaxa import write_ecotaxa_tsv_chunked_subdirectories
-from extensions.chaeo import ZMaskExportParams
-from model_server.extensions.chaeo.workflows import export_zstack_roiset
-from model_server.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/231028_automic/20231028-134649_successfulrun/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__()
-
-    export_params = {
-        'pixel_probabilities': True,
-        # 'patches_3d': {},
-        'patches_2d_for_training': {
-            # 'draw_bounding_box': False,
-        },
-        'patches_2d_for_annotation': {
-            'draw_bounding_box': True,
-            'rgb_overlay_channels': (1, None, None),
-            'rgb_overlay_weights': (0.2, 1.0, 1.0),
-        },
-        'annotated_z_stack': {
-            'draw_label': True
-        }
-    }
-    ZMaskExportParams(**export_params)
-
-    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),
-        'zmask_clip': 0.01,
-        # '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,
-
-        # 'rgb_overlay_channels': (1, None, None),
-        # 'rgb_overlay_weights': (0.2, 1.0, 1.0),
-        # 'draw_label_on_zstack': True,
-        'exports': ZMaskExportParams(**export_params),
-    }
-
-    input_files = get_matching_files(where_czi, 'czi', coord_filter={})
-
-    loop_workflow(
-        input_files,
-        where_output,
-        export_zstack_roiset,
-        [IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)})],
-        params,
-        catch_and_continue=False,
-        chunk_size=25,
-    )
-
-    write_ecotaxa_tsv_chunked_subdirectories(
-        where_output,
-        'workflow_data.csv',
-        sample_id=sample_id,
-        scope_id='EMBL-MS-Zeiss-LSM900'
-    )
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/old_batch_jobs/proj0004-exp0038-fixed.py b/model_server/extensions/chaeo/old_batch_jobs/proj0004-exp0038-fixed.py
deleted file mode 100644
index efa8a8e7..00000000
--- a/model_server/extensions/chaeo/old_batch_jobs/proj0004-exp0038-fixed.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from pathlib import Path
-
-from model_server.base.util import autonumber_new_directory, get_matching_files, loop_workflow
-from model_server.extensions.chaeo.workflows import export_patches_from_multichannel_zstack
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-if __name__ == '__main__':
-    where_czi = 'z:/rhodes/projects/proj0004-marine-photoactivation/data/exp0038/AutoMic/20230906-163415/Selection'
-    where_output = autonumber_new_directory(
-        'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0009/output',
-        'batch-output'
-    )
-
-    px_ilp = Path.home() / 'model_server' / 'ilastik' / 'AF405-bodies_boundaries.ilp'
-
-    params = {
-        'pxmap_threshold': 0.25,
-        'pxmap_foreground_channel': 0,
-        'segmentation_channel': 0,
-        'patches_channel': 4,
-        'zmask_type': 'boxes',
-        'zmask_filters': {'area': (1e3, 1e8)},
-        'zmask_expand_box_by': (128, 3),
-        'export_pixel_probabilities': False,
-        'export_2d_patches_for_training': True,
-        'export_2d_patches_for_annotation': True,
-        'export_3d_patches': False,
-        'export_annotated_zstack': False,
-        'export_patch_masks': True,
-    }
-
-    input_files = get_matching_files(where_czi, 'czi', coord_filter={'P': (0, 10)}, )
-
-    loop_workflow(
-        input_files,
-        where_output,
-        export_patches_from_multichannel_zstack,
-        [IlastikPixelClassifierModel(params={'project_file': Path(px_ilp)})],
-        params,
-        catch_and_continue=False,
-    )
-
-    print('Finished')
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/router.py b/model_server/extensions/chaeo/router.py
deleted file mode 100644
index 87d75074..00000000
--- a/model_server/extensions/chaeo/router.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from fastapi import APIRouter
-
-from model_server.extensions.chaeo.workflows import export_zstack_roiset
-from model_server.base.session import Session
-from model_server.base.validators import validate_workflow_inputs
-
-router = APIRouter(
-    prefix='/chaeo',
-    tags=['chaeo'],
-)
-
-session = Session()
-
-@router.put('/classify_zstack/infer')
-def infer_px_then_ob_maps(
-        px_model_id: str,
-        ob_model_id: str,
-        input_filename: str,
-        pxmap_threshold: float,
-        pxmap_foreground_channel: int,
-        segmentation_channel: int,
-        patches_channel: int,
-        zmask_filters: dict = {'area': (1e3, 1e8)},
-) -> dict:
-    inpath = session.paths['inbound_images'] / input_filename
-    validate_workflow_inputs([px_model_id, ob_model_id], [inpath])
-
-    record = export_zstack_roiset(
-        inpath,
-        session.paths['outbound_images'],
-        [
-            session.models[px_model_id]['object'],
-            session.models[px_model_id]['object']
-        ],
-        pxmap_threshold=pxmap_threshold,
-        pxmap_foreground_channel=pxmap_foreground_channel,
-        segmentation_channel=segmentation_channel,
-        patches_channel=patches_channel,
-        zmask_filters=zmask_filters,
-    )
-
-    return record
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/tests/__init__.py b/model_server/extensions/chaeo/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/model_server/extensions/chaeo/tests/test_roiset_workflow.py b/model_server/extensions/chaeo/tests/test_roiset_workflow.py
deleted file mode 100644
index a52fd11f..00000000
--- a/model_server/extensions/chaeo/tests/test_roiset_workflow.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import unittest
-
-from model_server.base.models import DummyInstanceSegmentationModel
-from model_server.base.roiset import RoiSetMetaParams, RoiSetExportParams
-from model_server.conf.testing import output_path, roiset_test_data
-from model_server.extensions.chaeo.workflows import export_zstack_roiset
-from model_server.extensions.ilastik.models import IlastikPixelClassifierModel
-
-from tests.test_roiset import BaseTestRoiSetMonoProducts
-
-class TestRoiSetWorkflow(BaseTestRoiSetMonoProducts, unittest.TestCase):
-
-    def test_object_map_workflow(self):
-        pp = roiset_test_data['pipeline_params']
-
-        models = {
-            'pixel_classifier': {
-                'model': IlastikPixelClassifierModel(params={'project_file': roiset_test_data['pixel_classifier']}),
-                'params': {
-                    'px_class': 0,
-                    'px_prob_threshold': 0.6,
-                }
-            },
-            'object_classifier': {
-                'name': 'dummy',
-                'model': DummyInstanceSegmentationModel(),
-            }
-        }
-
-        roi_params = RoiSetMetaParams(**{
-            'mask_type': 'boxes',
-            'filters': {
-                'area': {'min': 1e3, 'max': 1e8}
-            },
-            'expand_box_by': [128, 2]
-        })
-
-        export_params = RoiSetExportParams(**{
-            'pixel_probabilities': True,
-            'patches_3d': {},
-            'annotated_patches_2d': {
-                'draw_bounding_box': True,
-                'rgb_overlay_channels': [3, None, None],
-                'rgb_overlay_weights': [0.2, 1.0, 1.0],
-                'pad_to': 512,
-            },
-            'patches_2d': {
-                'draw_bounding_box': False,
-                'draw_mask': False,
-            },
-            'patch_masks': {
-                'pad_to': 256,
-            },
-            'annotated_zstacks': {},
-            'object_classes': True,
-            'dataframe': True,
-        })
-
-        export_zstack_roiset(
-            roiset_test_data['multichannel_zstack']['path'],
-            output_path / 'roiset' / 'workflow',
-            models,
-            segmentation_channel=pp['segmentation_channel'],
-            patches_channel=pp['patches_channel'],
-            export_params=export_params,
-            roi_params=roi_params,
-        )
\ No newline at end of file
diff --git a/model_server/extensions/chaeo/workflows.py b/model_server/extensions/chaeo/workflows.py
deleted file mode 100644
index b6f362e1..00000000
--- a/model_server/extensions/chaeo/workflows.py
+++ /dev/null
@@ -1,177 +0,0 @@
-from pathlib import Path
-from typing import Dict, List
-
-import numpy as np
-import pandas as pd
-
-from skimage.measure import label
-from skimage.morphology import dilation
-from sklearn.model_selection import train_test_split
-
-from base.roiset import RoiSetMetaParams, RoiSetExportParams
-from base.process import mask_largest_object
-from base.roiset import _get_label_ids, RoiSet
-
-from model_server.base.accessors import generate_file_accessor, InMemoryDataAccessor, write_accessor_data_to_file
-from model_server.base.models import Model, InstanceSegmentationModel, SemanticSegmentationModel
-from model_server.base.workflows import Timer
-
-
-def export_zstack_roiset(
-        input_file_path: str,
-        output_folder_path: str,
-        models: List[Model],
-        segmentation_channel: int,
-        patches_channel: int,
-        zmask_zindex: int = None,  # None for MIP,
-        roi_params: RoiSetMetaParams = RoiSetMetaParams(),
-        export_params: RoiSetExportParams = RoiSetExportParams(),
-) -> Dict:
-    assert isinstance(models['pixel_classifier']['model'], SemanticSegmentationModel)
-
-    ti = Timer()
-    stack = generate_file_accessor(Path(input_file_path))
-    fstem = Path(input_file_path).stem
-    ti.click('file_input')
-
-    # MIP if no zmask z-index is given, then classify pixels
-    if isinstance(zmask_zindex, int):
-        assert 0 < zmask_zindex < stack.nz
-        zmask_data = stack.get_one_channel_data(channel=segmentation_channel).data[:, :, :, zmask_zindex]
-    else:
-        zmask_data = stack.get_one_channel_data(channel=segmentation_channel).data.max(axis=-1, keepdims=True)
-    mip = InMemoryDataAccessor(zmask_data)
-
-    mip_mask = models['pixel_classifier']['model'].label_pixel_class(mip, **models['pixel_classifier']['params'])
-    ti.click('classify_pixels')
-
-    # make zmask
-    rois = RoiSet(stack, _get_label_ids(mip_mask), params=roi_params)
-    ti.click('generate_zmasks')
-
-    # optionally classify if an object classifier is passed
-    if 'object_classifier' in models.keys():
-        assert isinstance(models['object_classifier']['model'], InstanceSegmentationModel)
-        rois.classify_by(
-            models['object_classifier']['name'],
-            patches_channel,
-            models['object_classifier']['model']
-        )
-        ti.click('classify_objects')
-
-    rois.run_exports(Path(output_folder_path), patches_channel, fstem, export_params)
-    ti.click('export_roi_products')
-
-    return {
-        'timer_results': ti.events,
-        'dataframe': rois.get_df(),
-        'interm': {},
-        'output_path': output_folder_path,
-    }
-
-
-
-def transfer_ecotaxa_labels_to_patch_stacks(
-    where_masks: str,
-    where_patches: str,
-    object_csv: str,
-    ecotaxa_tsv: str,
-    where_output: str,
-    patch_size: tuple = (256, 256),
-    tr_split=0.6,
-    dilate_label_mask: bool = True, # to mitigate connected components error in ilastik
-    allow_multiple_objects: bool = False,
-) -> Dict:
-    assert tr_split > 0.5 # reduce chance that low-probability objects are omitted from training
-
-    # read patch metadata
-    df_obj = pd.read_csv(
-        object_csv,
-    )
-    df_ecotaxa = pd.read_csv(
-        ecotaxa_tsv,
-        sep='\t',
-        header=[0],
-        dtype={
-            ('object_annotation_date', '[t]'): str,
-            ('object_annotation_time', '[t]'): str,
-            ('object_annotation_category_id', '[t]'): str,
-        }
-    )
-    df_merge = pd.merge(df_obj, df_ecotaxa, left_on='patch_id', right_on='object_id')
-
-    # assign each unique lowest-level annotation to a class index
-    se_unique = pd.Series(
-        df_merge.object_annotation_hierarchy.unique()
-    )
-    df_split = (
-        se_unique.str.rsplit(
-            pat='>', n=1, expand=True
-        )
-    )
-    df_labels = pd.DataFrame({
-        'annotation_class_id': df_split.index + 1,
-        'hierarchy': se_unique,
-        'annotation_class': df_split.loc[:, 1].str.lower()
-    })
-
-    # join patch filenames and annotation classes
-    df_pf = pd.merge(
-        df_merge[['patch_filename', 'object_annotation_hierarchy']],
-        df_labels,
-        left_on='object_annotation_hierarchy',
-        right_on='hierarchy',
-    )
-    df_pl = df_pf[df_pf['object_annotation_hierarchy'].notnull()]
-
-    # export annotation classes and their summary stats
-    df_tr, df_te = train_test_split(df_pl, train_size=tr_split)
-    # df_labels['counts'] = df_pl['annotation_class_id'].value_counts()
-    df_labels = pd.merge(
-        df_labels,
-        pd.DataFrame(
-            [df_pl.annotation_class_id.value_counts(), df_tr.annotation_class_id.value_counts(), df_te.annotation_class_id.value_counts()],
-            index=['total', 'to_train', 'to_test']
-        ).T,
-        left_on='annotation_class_id',
-        right_index=True,
-        how='outer'
-    )
-    df_labels.loc[df_labels.to_train.isna(), 'to_train'] = 0
-    df_labels.loc[df_labels.to_test.isna(), 'to_test'] = 0
-    for col in ['total', 'to_train', 'to_test']:
-        df_labels.loc[df_labels[col].isna(), col] = 0
-    df_labels.to_csv(Path(where_output) / 'labels_key.csv', index=False)
-
-    # export patches as z-stacks
-    for (dfk, dfv) in {'train': df_tr, 'test': df_te}.items():
-        zstack_keys = ['mask', 'label', 'raw']
-        zstacks = {f'{dfk}_{zsk}': np.zeros((*patch_size, 1, len(dfv)), dtype='uint8') for zsk in zstack_keys}
-        stack_meta = []
-        for fi, pl in enumerate(dfv.itertuples(name='PatchFile')):
-            fn = pl._asdict()['patch_filename']
-            ac = pl._asdict()['annotation_class']
-            aci = pl._asdict()['annotation_class_id']
-
-            stack_meta.append({'zi': fi, 'patch_filename': fn, 'annotation_class': ac, 'annotation_class_id': aci})
-            acc_bm = generate_file_accessor(Path(where_masks) / fn)
-            assert acc_bm.is_mask()
-            assert acc_bm.hw == patch_size, f'Unexpected patch size {patch_size}'
-            assert acc_bm.chroma == 1
-            assert acc_bm.nz == 1
-            mask = acc_bm.data[:, :, 0, 0]
-            if dilate_label_mask:
-                mask = dilation(mask)
-            if not allow_multiple_objects:
-                ob_id = label(acc_bm.data[:, :, 0, 0])
-                mask = mask_largest_object(ob_id)
-            zstacks[dfk + '_mask'][:, :, 0, fi] = mask
-            zstacks[dfk + '_label'][:, :, 0, fi] = (mask == 255) * aci
-
-            acc_pa = generate_file_accessor(Path(where_patches) / fn)
-            zstacks[dfk + '_raw'][:, :, :, fi] = acc_pa.data[:, :, :, 0]
-
-        for k in zstacks.keys():
-            write_accessor_data_to_file(Path(where_output) / f'zstack_{k}.tif', InMemoryDataAccessor(zstacks[k]))
-
-        pd.DataFrame(stack_meta).to_csv(Path(where_output) / f'{dfk}_stack.csv', index=False)
\ No newline at end of file
-- 
GitLab