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

Tests of zmask results

parent e730dc2d
No related branches found
No related tags found
No related merge requests found
import unittest import unittest
import numpy as np
from conf.testing import output_path from conf.testing import output_path
from extensions.chaeo.conf.testing import multichannel_zstack, pixel_classifier, pipeline_params from extensions.chaeo.conf.testing import multichannel_zstack, pixel_classifier, pipeline_params
...@@ -11,34 +13,42 @@ class TestZStackDerivedDataProducts(unittest.TestCase): ...@@ -11,34 +13,42 @@ class TestZStackDerivedDataProducts(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
# need test data incl obj map # need test data incl obj map
self.zstack = generate_file_accessor(multichannel_zstack['path']) self.stack = generate_file_accessor(multichannel_zstack['path'])
pxmodel = IlastikPixelClassifierModel( pxmodel = IlastikPixelClassifierModel(
{'project_file': pixel_classifier['path']}, {'project_file': pixel_classifier['path']},
) )
mip = InMemoryDataAccessor(self.zstack.get_one_channel_data(channel=0).data.max(axis=-1, keepdims=True)) mip = InMemoryDataAccessor(self.stack.get_one_channel_data(channel=0).data.max(axis=-1, keepdims=True))
self.pxmap, result = pxmodel.infer(mip) self.pxmap, result = pxmodel.infer(mip)
write_accessor_data_to_file(output_path / 'pxmap.tif', self.pxmap) write_accessor_data_to_file(output_path / 'pxmap.tif', self.pxmap)
self.obmap = InMemoryDataAccessor((self.pxmap.data > pipeline_params['threshold']).astype('uint8')) self.obmap = InMemoryDataAccessor(self.pxmap.data > pipeline_params['threshold'])
write_accessor_data_to_file(output_path / 'obmap.tif', self.obmap) write_accessor_data_to_file(output_path / 'obmap.tif', self.obmap)
def test_zmask_makes_correct_boxes(self): def test_zmask_makes_correct_boxes(self):
zmask, meta = build_stack_mask( zmask, meta = build_stack_mask(
'test_zmask_with boxes', 'test_zmask_with boxes',
self.stack, self.obmap.get_one_channel_data(0),
self.obmap, self.stack.get_one_channel_data(0),
mask_type='boxes', mask_type='boxes',
) )
zmask_acc = InMemoryDataAccessor(zmask) zmask_acc = InMemoryDataAccessor(zmask)
self.assertTrue(zmask_acc.is_object_map()) self.assertTrue(zmask_acc.is_mask())
# assert dimensionality of zmask # assert dimensionality of zmask
self.assertEqual(zmask.shape_dict['Z'] > 1) self.assertGreater(zmask_acc.shape_dict['Z'], 1)
self.assertEqual(zmask.shape_dict['C'] == 1) self.assertEqual(zmask_acc.shape_dict['C'], 1)
write_accessor_data_to_file(output_path / 'zmask.tif', zmask_acc)
# mask values are not just all True or all False
self.assertTrue(np.any(zmask))
self.assertFalse(np.all(zmask))
# assert non-trivial meta info in boxes # assert non-trivial meta info in boxes
pass self.assertGreater(len(meta), 1)
sh = meta[1]['mask'].shape
ar = meta[1]['info'].area
self.assertGreaterEqual(sh[0] * sh[1], ar)
def test_zmask_makes_correct_contours(self): def test_zmask_makes_correct_contours(self):
pass pass
\ No newline at end of file
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from skimage.measure import find_contours, regionprops_table from skimage.measure import find_contours, label, regionprops_table
from model_server.accessors import GenericImageDataAccessor
# build a single boolean 3d mask (objects v. bboxes) and return bounding boxes # build a single boolean 3d mask (objects v. bboxes) and return bounding boxes
def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expand_box_by=(0, 0)): # TODO: specify boxes data type def build_stack_mask(desc, obmap: GenericImageDataAccessor, stack: GenericImageDataAccessor, filters=None, mask_type='contour', expand_box_by=(0, 0)): # TODO: specify boxes data type
""" """
filters: dict of (min, max) tuples filters: dict of (min, max) tuples
...@@ -12,17 +14,17 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa ...@@ -12,17 +14,17 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa
""" """
# validate inputs # validate inputs
assert len(stack.shape) == 3, stack.shape # assert len(stack.shape) == 3, stack.shape
assert mask_type in ('contour', 'box'), mask_type # TODO: replace with call to validator assert stack.chroma == 1
assert stack.shape_dict['Z'] > 1
assert mask_type in ('contours', 'boxes'), mask_type # TODO: replace with call to validator
for k in filters.keys(): assert obmap.is_mask()
assert k in ('area', 'solidity') lamap = label(obmap.data[:, :, 0, 0])
vmin, vmax = filters[k]
assert vmin >= 0
# build object query # build object query
query_str = 'label > 0' # always true query_str = 'label > 0' # always true
if filters: if filters is not None:
for k in filters.keys(): for k in filters.keys():
assert k in ('area', 'solidity') assert k in ('area', 'solidity')
vmin, vmax = filters[k] vmin, vmax = filters[k]
...@@ -30,11 +32,11 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa ...@@ -30,11 +32,11 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa
query_str = query_str + f' & {k} > {vmin} & {k} < {vmax}' query_str = query_str + f' & {k} > {vmin} & {k} < {vmax}'
# build dataframe of objects, assign z index to each object # build dataframe of objects, assign z index to each object
argmax = stack.argmax(axis=0) argmax = stack.data.argmax(axis=3, keepdims=True)[:, :, 0, 0]
df = ( df = (
pd.DataFrame( pd.DataFrame(
regionprops_table( regionprops_table(
obmap, lamap,
intensity_image=argmax, intensity_image=argmax,
properties=('label', 'area', 'intensity_mean', 'solidity', 'bbox') properties=('label', 'area', 'intensity_mean', 'solidity', 'bbox')
) )
...@@ -52,12 +54,12 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa ...@@ -52,12 +54,12 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa
df['zi'] = df['intensity_mean'].round().astype('int') df['zi'] = df['intensity_mean'].round().astype('int')
# make an object map where label is replaced by focus position in stack and background is -1 # make an object map where label is replaced by focus position in stack and background is -1
lut = np.zeros(obmap.max() + 1) - 1 lut = np.zeros(lamap.max() + 1) - 1
lut[df.label] = df.zi lut[df.label] = df.zi
# convert bounding boxes to slices # convert bounding boxes to slices
ebxy, ebz = expand_box_by ebxy, ebz = expand_box_by
nz, h, w = stack.shape h, w, c, nz = stack.shape
boxes = [] boxes = []
for ob in df.itertuples(name='LabeledObject'): for ob in df.itertuples(name='LabeledObject'):
...@@ -76,10 +78,11 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa ...@@ -76,10 +78,11 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa
'x1': ob.x1 - x0, 'x1': ob.x1 - x0,
} }
sl = np.s_[z0: z1 + 1, y0: y1, x0: x1] # sl = np.s_[z0: z1 + 1, y0: y1, x0: x1]
sl = np.s_[y0: y1, x0: x1, 0, z0: z1 + 1]
# compute contours # compute contours
obmask = (obmap == ob.label) obmask = (lamap == ob.label)
contour = find_contours(obmask) contour = find_contours(obmask)
mask = obmask[ob.y0: ob.y1, ob.x0: ob.x1] mask = obmask[ob.y0: ob.y1, ob.x0: ob.x1]
...@@ -93,15 +96,15 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa ...@@ -93,15 +96,15 @@ def build_stack_mask(desc, obmap, stack, filters=None, mask_type='contour', expa
# build mask z-stack # build mask z-stack
zi_st = np.zeros(stack.shape, dtype='bool') zi_st = np.zeros(stack.shape, dtype='bool')
if mask_type == 'contour': if mask_type == 'contours':
zi_map = (lut[obmap] + 1.0).astype('int') zi_map = (lut[lamap] + 1.0).astype('int')
idxs = np.array([zi_map]) - 1 idxs = np.array([zi_map]) - 1
np.put_along_axis(zi_st, idxs, 1, axis=0) np.put_along_axis(zi_st, idxs, 1, axis=3)
# change background level from to 0 in final frame # change background level from to 0 in final frame
zi_st[-1, :, :][obmap == 0] = 0 zi_st[:, :, :, -1][lamap == 0] = 0
elif mask_type == 'box': elif mask_type == 'boxes':
for bb in boxes: for bb in boxes:
sl = bb['slice'] sl = bb['slice']
zi_st[sl] = 1 zi_st[sl] = 1
......
...@@ -32,6 +32,9 @@ class GenericImageDataAccessor(ABC): ...@@ -32,6 +32,9 @@ 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
def is_mask(self):
return self._data.dtype == 'bool'
def get_one_channel_data (self, channel: int): def get_one_channel_data (self, channel: int):
c = int(channel) c = int(channel)
return InMemoryDataAccessor(self.data[:, :, c:(c+1), :]) return InMemoryDataAccessor(self.data[:, :, c:(c+1), :])
...@@ -133,7 +136,10 @@ def write_accessor_data_to_file(fpath: Path, accessor: GenericImageDataAccessor) ...@@ -133,7 +136,10 @@ def write_accessor_data_to_file(fpath: Path, accessor: GenericImageDataAccessor)
[3, 2, 0, 1], [3, 2, 0, 1],
[0, 1, 2, 3] [0, 1, 2, 3]
) )
tifffile.imwrite(fpath, zcyx, imagej=True) if accessor.is_mask():
tifffile.imwrite(fpath, zcyx.astype('uint8'), imagej=True)
else:
tifffile.imwrite(fpath, zcyx, imagej=True)
except: except:
raise FileWriteError(f'Unable to write data to file') raise FileWriteError(f'Unable to write data to file')
return True return True
......
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