diff --git a/model_server/base/accessors.py b/model_server/base/accessors.py index c6a787bc4c12918b64d06e95c97c50cf4ad2fc53..fec80c819ad808a1f8ca2afa09b6b0fd65a8b4fe 100644 --- a/model_server/base/accessors.py +++ b/model_server/base/accessors.py @@ -185,6 +185,14 @@ class CziImageFileAccessor(GenericImageFileAccessor): def write_accessor_data_to_file(fpath: Path, accessor: GenericImageDataAccessor, mkdir=True) -> bool: + """ + Export an image accessor to file. + :param fpath: complete path including filename and extension + :param accessor: image accessor to be written + :param mkdir: create any needed subdirectories in fpath if True + :return: True + """ + if mkdir: fpath.parent.mkdir(parents=True, exist_ok=True) try: @@ -207,6 +215,10 @@ def write_accessor_data_to_file(fpath: Path, accessor: GenericImageDataAccessor, def generate_file_accessor(fpath): + """ + Given an image file path, return an image accessor, assuming the file is a supported format and represents + a single position array, which may be single or multi-channel, single plane or z-stack. + """ if str(fpath).upper().endswith('.TIF') or str(fpath).upper().endswith('.TIFF'): return TifSingleSeriesFileAccessor(fpath) elif str(fpath).upper().endswith('.CZI'): @@ -216,27 +228,6 @@ def generate_file_accessor(fpath): else: raise FileAccessorError(f'Could not match a file accessor with {fpath}') -class Error(Exception): - pass - -class FileAccessorError(Error): - pass - -class FileNotFoundError(Error): - pass - -class DataShapeError(Error): - pass - -class FileWriteError(Error): - pass - -class InvalidAxisKey(Error): - pass - -class InvalidDataShape(Error): - pass - class PatchStack(InMemoryDataAccessor): @@ -293,6 +284,7 @@ class PatchStack(InMemoryDataAccessor): [0, 3, 4, 1, 2], [0, 1, 2, 3, 4] ) + @property def shape(self): return self.data.shape @@ -300,3 +292,41 @@ class PatchStack(InMemoryDataAccessor): @property def shape_dict(self): return dict(zip(('P', 'Y', 'X', 'C', 'Z'), self.data.shape)) + + +def make_patch_stack_from_file(fpath): # interpret z-dimension as patch position + if not Path(fpath).exists(): + raise FileNotFoundError(f'Could not find {fpath}') + + pyxc = np.moveaxis( + generate_file_accessor(fpath).data, # yxcz + [0, 1, 2, 3], + [1, 2, 3, 0] + ) + pyxcz = np.expand_dims(pyxc, axis=3) + return PatchStack(pyxcz) + + +class Error(Exception): + pass + +class FileAccessorError(Error): + pass + +class FileNotFoundError(Error): + pass + +class DataShapeError(Error): + pass + +class FileWriteError(Error): + pass + +class InvalidAxisKey(Error): + pass + +class InvalidDataShape(Error): + pass + + + diff --git a/model_server/extensions/chaeo/accessors.py b/model_server/extensions/chaeo/accessors.py index 2307d80ccb0b90bd70e2ca03c6a3b14800069e65..7c9f74e57caf5413e515e89eff49622967cd4c2c 100644 --- a/model_server/extensions/chaeo/accessors.py +++ b/model_server/extensions/chaeo/accessors.py @@ -1,25 +1,11 @@ -from pathlib import Path - import numpy as np from skimage.io import imsave from tifffile import imwrite from base.process import make_rgb -from model_server.base.accessors import generate_file_accessor, InMemoryDataAccessor - - -class MonoPatchStackFromFile(MonoPatchStack): - def __init__(self, fpath): - if not Path(fpath).exists(): - raise FileNotFoundError(f'Could not find {fpath}') - self.file_acc = generate_file_accessor(fpath) - super().__init__(self.file_acc.data[:, :, 0, :]) - - @property - def fpath(self): - return self.file_acc.fpath +# TODO: move this as method in base.accessors def write_patch_to_file(where, fname, yxcz): ext = fname.split('.')[-1].upper() where.mkdir(parents=True, exist_ok=True) @@ -52,7 +38,4 @@ class Error(Exception): class InvalidDataForPatchStackError(Error): pass -class FileNotFoundError(Error): - pass - diff --git a/model_server/extensions/chaeo/models.py b/model_server/extensions/chaeo/models.py index 1a969db316e2e4a469f4c879770ae83c37d03e68..58235d81ed8d56845d220cd6203b7ed22ec6624c 100644 --- a/model_server/extensions/chaeo/models.py +++ b/model_server/extensions/chaeo/models.py @@ -5,16 +5,15 @@ import h5py import numpy as np import skimage -from model_server.extensions.chaeo.accessors import MonoPatchStackFromFile - +from model_server.extensions.chaeo.accessors import PatchStack def generate_ilastik_object_classifier( template_ilp: Path, target_ilp: Path, - raw_stack: MonoPatchStackFromFile, - mask_stack: MonoPatchStackFromFile, - label_stack: MonoPatchStackFromFile, + raw_stack: PatchStack, + mask_stack: PatchStack, + label_stack: PatchStack, label_names: list, lane: int = 0, allow_multiple_objects=True, diff --git a/model_server/extensions/chaeo/tests/test_accessors.py b/model_server/extensions/chaeo/tests/test_accessors.py index a92c058c6d9c490ba47f9b5d0d250c7acd8a8030..c51f31ca49af2694c0cec8306920ff557d463b75 100644 --- a/model_server/extensions/chaeo/tests/test_accessors.py +++ b/model_server/extensions/chaeo/tests/test_accessors.py @@ -3,8 +3,7 @@ import unittest import numpy as np from model_server.conf.testing import monozstackmask -from model_server.extensions.chaeo.accessors import MonoPatchStackFromFile -from base.accessors import PatchStack +from base.accessors import PatchStack, make_patch_stack_from_file, FileNotFoundError class TestMultipositionCziImageFileAccess(unittest.TestCase): @@ -36,16 +35,15 @@ class TestMultipositionCziImageFileAccess(unittest.TestCase): w = monozstackmask['w'] c = monozstackmask['c'] n = monozstackmask['z'] - acc = MonoPatchStackFromFile(monozstackmask['path']) + + acc = make_patch_stack_from_file(monozstackmask['path']) self.assertEqual(acc.hw, (h, w)) self.assertEqual(acc.count, n) - self.assertEqual(acc.make_tczyx().shape, (n, c, 1, h, w)) - self.assertEqual(acc.fpath, monozstackmask['path']) + self.assertEqual(acc.pyxcz.shape, (n, h, w, c, 1)) def test_raises_filenotfound(self): - from model_server.extensions.chaeo.accessors import FileNotFoundError with self.assertRaises(FileNotFoundError): - acc = MonoPatchStackFromFile('c:/fake/file/name.tif') + acc = make_patch_stack_from_file('c:/fake/file/name.tif') def test_make_3d_patch_stack_from_nonuniform_list(self): w = 256