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

Image now enforces XYCZ axes order

parent 648455f3
No related branches found
No related tags found
No related merge requests found
from abc import ABC
from abc import ABC, abstractmethod
import os
from pathlib import Path
from typing import Dict
......@@ -10,29 +10,13 @@ import tifffile
class GenericImageDataAccessor(ABC):
valid_keys = ['X', 'Y', 'C', 'Z']
def __init__(self, data, shape_dict, **kwargs):
@abstractmethod
def __init__(self):
"""
Abstract base class that exposes an interfaces for image data, irrespective of whether it is instantiated
from file I/O or other means.
:param kwargs: variable-length keyword arguments
"""
if shape_dict['S'] > 1 or shape_dict['T'] > 1:
raise FileShapeError(f'Cannot handle image with multiple positions or time points: {shape_dict}')
if any([k not in self.valid_keys for k in shape_dict.keys()]):
raise InvalidAxisKey(f'Invalid keys: {shape_dict.keys}')
self._shape_dict = shape_dict
self._data = data
self.data = np.moveaxis(
cf.asarray(),
[cf.axes.index(ch) for ch in self.shape_dict],
[0, 1, 2, 3]
).squeeze()
pass
@property
def chroma(self):
......@@ -41,44 +25,27 @@ class GenericImageDataAccessor(ABC):
def is_3d(self):
return True if self.shape_dict['Z'] > 1 else False
@property
def data(self):
return np.moveaxis(
cf.asarray(),
[cf.axes.index(ch) for ch in self.shape_dict],
[0, 1, 2, 3]
).squeeze()
def get_one_channel_data (self, channel: int):
return self.data[:, :, channel, :]
# @data.setter
# def data(self, data_arg):
# if len(data_arg.shape) > 4:
# raise InvalidDataShape(f'Invalid data shape: {data_arg.shape}')
# self._data = data_arg
@property
def data(self): # XYCZ enforced
return self._data
@property
def shape_dict(self):
return self._shape_dict
# @shape_dict.setter
# def shape_dict(self, dict_arg: Dict[str, int]):
# if any([k not in self.valid_keys for k in dict_arg.keys()]):
# raise InvalidAxisKey(f'Invalid keys: {dict_arg}')
# self._shape_dict = dict_arg
def get_one_channel(self):
ci = self.shape_dict['C']
return self.data[]
return dict(zip(('X', 'Y', 'C', 'Z'), self.data.shape))
class GenericImageFileAccessor(GenericImageDataAccessor): # image data is loaded from a file
def __init__(self, fpath: Path, **kwargs):
def __init__(self, fpath: Path):
"""
Interface for image data that originates in an image file
:param fpath: absolute path to image file
:param kwargs: variable-length keyword arguments
"""
super().__init__(**kwargs)
if not os.path.exists(fpath):
raise FileAccessorError(f'Could not find file at {fpath}')
self.fpath = fpath
class CziImageFileAccessor(GenericImageFileAccessor):
"""
......@@ -87,28 +54,28 @@ class CziImageFileAccessor(GenericImageFileAccessor):
"""
def __init__(self, fpath: Path):
super().__init__(fpath)
self.fpath = fpath
try:
cf = czifile.CziFile(fpath)
self.czifile = cf
except:
raise FileAccessorError(f'Unable to access data in {fpath}')
self.shape_dict = {ch: cf.shape[cf.axes.index(ch)] for ch in cf.axes}
self.data = cf.asarray()
except Exception:
raise FileAccessorError(f'Unable to access CZI data in {fpath}')
# sd = {ch: cf.shape[cf.axes.index(ch)] for ch in cf.axes}
# if sd['S'] > 1 or sd['T'] > 1:
# raise FileShapeError(f'Cannot handle image with multiple positions or time points: {sd}')
#
# self.shape_dict = {k: sd[k] for k in ['X', 'Y', 'C', 'Z']}
# self.data = np.moveaxis(
# cf.asarray(),
# [cf.axes.index(ch) for ch in self.shape_dict],
# [0, 1, 2, 3]
# ).squeeze()
sd = {ch: cf.shape[cf.axes.index(ch)] for ch in cf.axes}
if sd['S'] > 1 or sd['T'] > 1:
raise FileShapeError(f'Cannot handle image with multiple positions or time points: {sd}')
idx = {k: sd[k] for k in ['X', 'Y', 'C', 'Z']}
xycz = np.moveaxis(
cf.asarray(),
[cf.axes.index(ch) for ch in idx],
[0, 1, 2, 3]
)
try:
self._data = xycz.reshape(xycz.shape[0:4])
except Exception:
raise FileShapeError(f'Cannot handle image with dimensions other than X, Y, C, and Z')
def __del__(self):
self.czifile.close()
......
......@@ -12,11 +12,15 @@ class TestCziImageFileAccess(unittest.TestCase):
self.assertEqual(cf.shape_dict['X'], czifile['w'])
self.assertEqual(cf.chroma, czifile['c'])
self.assertFalse(cf.is_3d())
self.assertEqual(len(cf.data.shape), 4)
def test_write_single_channel_tif(self):
ch = 4
ch = 2
cf = CziImageFileAccessor(czifile['path'])
of = WriteableTiffFileAccessor(
output_path / f'{cf.fpath.stem}_ch{ch}.tif'
)
of.write(cf.data[])
\ No newline at end of file
mono = cf.get_one_channel_data(2)
of.write(mono)
self.assertEqual(cf.data.shape[0:2], mono.data.shape[0:2])
self.assertEqual(cf.data.shape[3], mono.data.shape[2])
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