Skip to content
Snippets Groups Projects
Commit a8fb9158 authored by Christopher Randolph Rhodes's avatar Christopher Randolph Rhodes
Browse files

load method returns data array, init assigns it

parent 18c0884c
No related branches found
No related tags found
No related merge requests found
...@@ -24,6 +24,10 @@ class GenericImageDataAccessor(ABC): ...@@ -24,6 +24,10 @@ class GenericImageDataAccessor(ABC):
""" """
pass pass
@property
def loaded(self):
return self._data is not None
@property @property
def chroma(self): def chroma(self):
return self.shape_dict['C'] return self.shape_dict['C']
...@@ -38,6 +42,7 @@ class GenericImageDataAccessor(ABC): ...@@ -38,6 +42,7 @@ class GenericImageDataAccessor(ABC):
def is_3d(self): def is_3d(self):
return True if self.shape_dict['Z'] > 1 else False return True if self.shape_dict['Z'] > 1 else False
# TODO: no direct calls to self._data outside load/unload methods
def is_mask(self): def is_mask(self):
return is_mask(self._data) return is_mask(self._data)
...@@ -198,7 +203,7 @@ class InMemoryDataAccessor(GenericImageDataAccessor): ...@@ -198,7 +203,7 @@ class InMemoryDataAccessor(GenericImageDataAccessor):
self._data = self.conform_data(data) self._data = self.conform_data(data)
class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded from a file class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded from a file
def __init__(self, fpath: Path): def __init__(self, fpath: Path, lazy=False):
""" """
Interface for image data that originates in an image file Interface for image data that originates in an image file
:param fpath: absolute path to image file :param fpath: absolute path to image file
...@@ -208,6 +213,15 @@ class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded ...@@ -208,6 +213,15 @@ class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded
raise FileAccessorError(f'Could not find file at {fpath}') raise FileAccessorError(f'Could not find file at {fpath}')
self.fpath = fpath self.fpath = fpath
if not lazy:
self._data = self.load()
else:
self._data = None
@abstractmethod
def load(self):
pass
@staticmethod @staticmethod
def read(fp: Path): def read(fp: Path):
return generate_file_accessor(fp) return generate_file_accessor(fp)
...@@ -219,12 +233,12 @@ class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded ...@@ -219,12 +233,12 @@ class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded
return d return d
class TifSingleSeriesFileAccessor(GenericImageFileAccessor): class TifSingleSeriesFileAccessor(GenericImageFileAccessor):
def __init__(self, fpath: Path): def load(self):
super().__init__(fpath) fpath = self.fpath
try: try:
tf = tifffile.TiffFile(fpath) tf = tifffile.TiffFile(fpath)
self.tf = tf self.tf = tf # TODO: close file connection
except Exception: except Exception:
raise FileAccessorError(f'Unable to access data in {fpath}') raise FileAccessorError(f'Unable to access data in {fpath}')
...@@ -251,14 +265,15 @@ class TifSingleSeriesFileAccessor(GenericImageFileAccessor): ...@@ -251,14 +265,15 @@ class TifSingleSeriesFileAccessor(GenericImageFileAccessor):
[0, 1, 2, 3] [0, 1, 2, 3]
) )
self._data = self.conform_data(yxcz.reshape(yxcz.shape[0:4])) return self.conform_data(yxcz.reshape(yxcz.shape[0:4]))
# TODO: remove
def __del__(self): def __del__(self):
self.tf.close() self.tf.close()
class PngFileAccessor(GenericImageFileAccessor): class PngFileAccessor(GenericImageFileAccessor):
def __init__(self, fpath: Path): def load(self):
super().__init__(fpath) fpath = self.fpath
try: try:
arr = imread(fpath) arr = imread(fpath)
...@@ -266,19 +281,19 @@ class PngFileAccessor(GenericImageFileAccessor): ...@@ -266,19 +281,19 @@ class PngFileAccessor(GenericImageFileAccessor):
FileAccessorError(f'Unable to access data in {fpath}') FileAccessorError(f'Unable to access data in {fpath}')
if len(arr.shape) == 3: # rgb if len(arr.shape) == 3: # rgb
self._data = np.expand_dims(arr, 3) return np.expand_dims(arr, 3)
else: # mono else: # mono
self._data = np.expand_dims(arr, (2, 3)) return np.expand_dims(arr, (2, 3))
class CziImageFileAccessor(GenericImageFileAccessor): class CziImageFileAccessor(GenericImageFileAccessor):
""" """
Image that is stored in a Zeiss .CZI file; may be multi-channel, and/or a z-stack, Image that is stored in a Zeiss .CZI file; may be multi-channel, and/or a z-stack, but not a time series
but not a time series or multiposition acquisition. or multiposition acquisition. Read the whole file as a single accessor, no interaction with subblocks.
""" """
def __init__(self, fpath: Path): def load(self):
super().__init__(fpath) fpath = self.fpath
try: try:
# TODO: persist metadata then remove file connection
cf = czifile.CziFile(fpath) cf = czifile.CziFile(fpath)
self.czifile = cf self.czifile = cf
except Exception: except Exception:
...@@ -302,7 +317,7 @@ class CziImageFileAccessor(GenericImageFileAccessor): ...@@ -302,7 +317,7 @@ class CziImageFileAccessor(GenericImageFileAccessor):
[cf.axes.index(ch) for ch in idx], [cf.axes.index(ch) for ch in idx],
[0, 1, 2, 3] [0, 1, 2, 3]
) )
self._data = self.conform_data(yxcz.reshape(yxcz.shape[0:4])) return self.conform_data(yxcz.reshape(yxcz.shape[0:4]))
def __del__(self): def __del__(self):
self.czifile.close() self.czifile.close()
...@@ -363,21 +378,21 @@ def write_accessor_data_to_file(fpath: Path, acc: GenericImageDataAccessor, mkdi ...@@ -363,21 +378,21 @@ def write_accessor_data_to_file(fpath: Path, acc: GenericImageDataAccessor, mkdi
return True return True
def generate_file_accessor(fpath): def generate_file_accessor(fpath, **kwargs):
""" """
Given an image file path, return an image accessor, assuming the file is a supported format and represents 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 multichannel, single plane or z-stack. a single position array, which may be single or multichannel, single plane or z-stack.
""" """
if str(fpath).upper().endswith('.TIF') or str(fpath).upper().endswith('.TIFF'): if str(fpath).upper().endswith('.TIF') or str(fpath).upper().endswith('.TIFF'):
return TifSingleSeriesFileAccessor(fpath) return TifSingleSeriesFileAccessor(fpath, **kwargs)
elif str(fpath).upper().endswith('.CZI'): elif str(fpath).upper().endswith('.CZI'):
return CziImageFileAccessor(fpath) return CziImageFileAccessor(fpath, **kwargs)
elif str(fpath).upper().endswith('.PNG'): elif str(fpath).upper().endswith('.PNG'):
return PngFileAccessor(fpath) return PngFileAccessor(fpath, **kwargs)
else: else:
raise FileAccessorError(f'Could not match a file accessor with {fpath}') raise FileAccessorError(f'Could not match a file accessor with {fpath}')
# TODO: implement lazy loading at patch stack level
class PatchStack(InMemoryDataAccessor): class PatchStack(InMemoryDataAccessor):
axes = 'PYXCZ' axes = 'PYXCZ'
......
...@@ -163,10 +163,11 @@ class _Session(object): ...@@ -163,10 +163,11 @@ class _Session(object):
if accessor_id is None: if accessor_id is None:
idx = len(self.accessors) idx = len(self.accessors)
accessor_id = f'acc_{idx:06d}' accessor_id = f'acc_{idx:06d}'
self.accessors[accessor_id] = {'loaded': True, 'object': acc, **acc.info} self.accessors[accessor_id] = {'loaded': acc.loaded, 'object': acc, **acc.info}
self.log_info(f'Added accessor {accessor_id}') self.log_info(f'Added accessor {accessor_id}')
return accessor_id return accessor_id
# TODO: divergent cases between lazy file-backed accessor (with its own loaded state) and in-memory ones
def del_accessor(self, accessor_id: str) -> str: def del_accessor(self, accessor_id: str) -> str:
""" """
Remove accessor object but retain its info dictionary Remove accessor object but retain its info dictionary
......
...@@ -228,6 +228,9 @@ class TestCziImageFileAccess(unittest.TestCase): ...@@ -228,6 +228,9 @@ class TestCziImageFileAccess(unittest.TestCase):
) )
) )
def test_lazy_load(self):
cf = generate_file_accessor(data['czifile']['path'])
self.assertEqual(1, 0)
class TestPatchStackAccessor(unittest.TestCase): class TestPatchStackAccessor(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment