diff --git a/conf/testing.py b/conf/testing.py index 5f95b5caa314ac6ff315677136094135c983ff5b..f2df5040d83ede67bd06bb1c114e2e02bc0a0772 100644 --- a/conf/testing.py +++ b/conf/testing.py @@ -42,6 +42,16 @@ tifffile = { 'z': 7, } +filename = 'mono_zstack_mask.tif' +monozstackmask = { + 'filename': filename, + 'path': root / filename, + 'w': 256, + 'h': 256, + 'c': 1, + 'z': 85 +} + ilastik = { 'pixel_classifier': 'demo_px.ilp', 'object_classifier': 'demo_obj.ilp', diff --git a/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py b/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py index 7b61d580dd9aecc7c7feaff3e48de8359d29a3c8..d528f86bc7a8709b2f33e8703e905ffa04fb761e 100644 --- a/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py +++ b/extensions/chaeo/examples/transfer_labels_to_ilastik_object_classifier.py @@ -1,12 +1,10 @@ -import csv from pathlib import Path import h5py -import ilastik.applets.objectClassification +import json import numpy as np import pandas as pd -import tifffile +import uuid -from extensions.ilastik.models import IlastikObjectClassifierModel from model_server.accessors import generate_file_accessor def get_dataset_info(h5): @@ -14,11 +12,17 @@ def get_dataset_info(h5): info = {} for gk in ['Raw Data', 'Segmentation Image']: info[gk] = {} - for dk in ['location', 'filePath', 'shape']: + 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']] return info def transfer_labels_to_ilastik_ilp(ilp, df_stack_meta): @@ -58,42 +62,55 @@ def transfer_labels_to_ilastik_ilp(ilp, df_stack_meta): def generate_ilastik_object_classifier(template_ilp, where_training: str): - # validate input data + # validate z-stack input data where = Path(where_training) zstacks = { - 'raw': { + 'Raw Data': { 'path': where / 'zstack_train_raw.tif', }, - 'seg': { + 'Segmentation Image': { 'path': where / 'zstack_train_mask.tif', } } for k, v in zstacks.items(): - # assert v['path'].exists(), 'Could not find input z-stack: ' + v['path'] - # ff = tifffile.imread(v['path']) - # v['nz'] = ff.shape[0] - # v['hw'] = ff.shape[1:2] - # v['dtype'] = ff.dtype v['acc'] = generate_file_accessor(v['path']) - assert zstacks['raw']['acc'].is_binary() - assert zstacks['raw'] + assert zstacks['Segmentation Image']['acc'].is_mask() + + assert len(set([v['acc'].hw for k, v in zstacks.items()])) == 1 # same height and width + assert len(set([v['acc'].nz for k, v in zstacks.items()])) == 1 # same z-depth + + # now load CSV + csv_path = where / 'train_stack.csv' + assert csv_path.exists() + df_meta = pd.read_csv(csv_path) + assert np.all( + df_meta['zi'].sort_values().to_numpy() == np.arange(0, zstacks['Raw Data']['acc'].nz) + ) with h5py.File(template_ilp, 'r+') as h5: - pass + info = get_dataset_info(h5) + + def set_ds(grp, ds, val): + ds = h5[f'Input Data/infos/lane0000/{grp}/{ds}'] + ds[()] = val + return ds[()] + + for hg in ['Raw Data', 'Segmentation Image']: + assert info[hg]['location'] == b'FileSystem' + assert info[hg]['axes'] == ['t', 'y', 'x'] + set_ds(hg, 'filePath', zstacks[hg]['path'].__str__()) + set_ds(hg, 'nickname', zstacks[hg]['path'].stem) + shape_zyx = [zstacks[hg]['acc'].shape_dict[ax] for ax in ['Z', 'Y', 'X']] + set_ds(hg, 'shape', np.array(shape_zyx)) + new_info = get_dataset_info(h5) if __name__ == '__main__': ilp = 'c:/Users/rhodes/model-server/ilastik/test_autolabel_obj - Copy.ilp' - # ilp = 'c:/Users/rhodes/model-server/ilastik/test_template_obj.ilp' - - df = pd.read_csv( - 'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0009/output/labeled_patches-20231014-0002/train_stack.csv' - ) - # transfer_labels_to_ilastik_ilp(ilp, df) generate_ilastik_object_classifier( - ilp, - 'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0009/output/labeled_patches-20231014-0002' + 'c:/Users/rhodes/model-server/ilastik/test_template_obj.ilp', + 'c:/Users/rhodes/projects/proj0011-plankton-seg/exp0009/output/labeled_patches-20231014-0004' ) \ No newline at end of file diff --git a/extensions/chaeo/workflows.py b/extensions/chaeo/workflows.py index 4d7e0ccd176a04d7d79083c8041d937410914172..d949323dd880d6afddc3c1d8dafc95047c0a6a5f 100644 --- a/extensions/chaeo/workflows.py +++ b/extensions/chaeo/workflows.py @@ -250,6 +250,7 @@ def transfer_ecotaxa_labels_to_patch_stacks( 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 diff --git a/model_server/accessors.py b/model_server/accessors.py index bd6cc11e6557ae95f5104cd0567881036c8ef1d8..8ef4059eb4d3d6a7a8373035e39bb32e5ffbb6b9 100644 --- a/model_server/accessors.py +++ b/model_server/accessors.py @@ -34,7 +34,11 @@ class GenericImageDataAccessor(ABC): return True if self.shape_dict['Z'] > 1 else False def is_mask(self): - return self._data.dtype == 'bool' + if self._data.dtype == 'bool': + return True + elif self._data.dtype == 'uint8': + return np.all(np.unique(self._data) == [0, 255]) + return False def get_one_channel_data (self, channel: int): c = int(channel) @@ -101,12 +105,18 @@ class TifSingleSeriesFileAccessor(GenericImageFileAccessor): raise DataShapeError(f'Expect only one series in {fpath}') se = tf.series[0] - sd = {ch: se.shape[se.axes.index(ch)] for ch in se.axes} - idx = {k: sd[k] for k in ['Y', 'X', 'C', 'Z']} + order = ['Y', 'X', 'C', 'Z'] + axs = [a for a in se.axes if a in order] + da = se.asarray() + + if 'C' not in axs: + axs.append('C') + da = np.expand_dims(da, len(da.shape)) + yxcz = np.moveaxis( - se.asarray(), - [se.axes.index(ch) for ch in idx], + da, + [axs.index(k) for k in order], [0, 1, 2, 3] ) diff --git a/tests/test_accessors.py b/tests/test_accessors.py index 4e1e7817d724dfd78216541c9960278cc632ecd6..f7a4341dc04d42aff05ed6f01f0113578590684a 100644 --- a/tests/test_accessors.py +++ b/tests/test_accessors.py @@ -2,7 +2,7 @@ import unittest import numpy as np -from conf.testing import czifile, output_path, monopngfile, rgbpngfile, tifffile +from conf.testing import czifile, output_path, monopngfile, rgbpngfile, tifffile, monozstackmask from model_server.accessors import CziImageFileAccessor, DataShapeError, generate_file_accessor, InMemoryDataAccessor, PngFileAccessor, write_accessor_data_to_file, TifSingleSeriesFileAccessor class TestCziImageFileAccess(unittest.TestCase): @@ -106,4 +106,8 @@ class TestCziImageFileAccess(unittest.TestCase): self.assertEqual(acc.nz, 1) def test_read_mono_png(self): - return self.test_read_png(pngfile=monopngfile) \ No newline at end of file + return self.test_read_png(pngfile=monopngfile) + + def test_read_zstack_mono_mask(self): + acc = generate_file_accessor(monozstackmask['path']) + self.assertTrue(acc.is_mask()) \ No newline at end of file