Skip to content

Commit

Permalink
Replace --interactive with --output in overlay and annotations commands
Browse files Browse the repository at this point in the history
  • Loading branch information
sumanthratna committed Jan 9, 2021
1 parent 7f24dd5 commit 74472fd
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 190 deletions.
9 changes: 3 additions & 6 deletions viewmask/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,7 @@ def export(self, mode):
return [flip(coordinate_pair) for coordinate_pair in self.data]
elif mode == 'opencv':
from numpy import asarray as to_numpy_array, int32 as npint32

out = [[], ] * len(self.data)
for index, contour in enumerate(self.data):
out[index] = to_numpy_array(contour, dtype=npint32)
return out
return [to_numpy_array(contour, dtype=npint32) for contour in self.data]

def fit_spline(self):
# TODO: docstring
Expand All @@ -122,7 +118,8 @@ def as_image(self):
y_max = np.amax([y for contour in contours for _, y in contour])
shape = (y_max, x_max, 3)
rendered_annotations = np.zeros(shape, dtype=np.uint8)
rendered_annotations = drawContours(rendered_annotations, contours, -1, [0, 255, 0])
rendered_annotations = drawContours(
rendered_annotations, contours, -1, [0, 255, 0])
for contour in contours:
rendered_annotations = fillPoly(
rendered_annotations,
Expand Down
323 changes: 139 additions & 184 deletions viewmask/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,42 +16,20 @@
except ImportError:
napari = None

INTERACTIVE_OPTION_HELP = 'If passed, the annotations will be rendered as ' + \
'`napari` objects rather than rendered together and displayed as an image.'

NAPARI_NOT_INSTALLED_WARNING = "The package `napari` is not installed. " + \
"We recommend installing `napari`, which will enable viewing outputs " + \
"in an interactive image viewer."


def validate_interactive_napari(ctx, param, value):
# assert isinstance(value, bool)
if value and napari is None:
if value is None and napari is None:
raise click.BadParameter(
'The `interactive` flag cannot be passed '
'without `napari` in the environment. Please install the `napari` '
'package if you would like to use the `napari` interactive '
'viewer.')


def validate_interactive_xml(ctx, param, value):
# TODO: don't rely on filepath extension, actually check file contents
_, annotations_ext = splitext(ctx.params['annotations'])
annotations_are_xml = annotations_ext == '.xml'
if value and not annotations_are_xml:
raise click.BadParameter(
"The `interactive` flag is only supported with XML annotations.")


class ComposedCallback:
def __init__(self, *callbacks):
self.callbacks = callbacks

def __call__(self, ctx, param, value):
output = None
for callback in self.callbacks:
output = callback(ctx, param, value)
return output
return value


@click.group()
Expand All @@ -68,91 +46,79 @@ def cli():
type=click.Path(exists=True, dir_okay=False)
)
@click.option(
'-i',
'--interactive',
default=False,
show_default=True,
type=bool,
is_flag=True,
help=INTERACTIVE_OPTION_HELP,
callback=ComposedCallback(
validate_interactive_napari, validate_interactive_xml),
'-o',
'--output',
default=None,
type=click.File(mode='wb'),
callback=validate_interactive_napari,
)
def view_annotations(annotations, interactive):
if napari is None:
# TODO: add output CLI option and write to file
return
def view_annotations(annotations, output):
from viewmask import Annotations
_, annotations_ext = splitext(annotations)

with napari.gui_qt():
viewer = napari.Viewer()
if interactive:
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
annotations = Annotations.from_tcga(tree)
annotations = regions.fit_spline()
regions = annotations.export('napari')
line_color = get_stroke_color(tree)
if output is None: # interactive viewer
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
annotations_data = Annotations.from_tcga(tree)
annotations_data = annotations_data.fit_spline()
regions = annotations_data.export('napari')
line_color = get_stroke_color(tree)

with napari.gui_qt():
viewer = napari.Viewer()
viewer.add_shapes(
regions,
shape_type='path',
edge_color=f"#{line_color}"
edge_color=f"#{line_color}",
)
viewer.add_points(
centers_of_contours(regions),
name='centers'
)
else:
if annotations_ext == '.npy':
rendered_annotations = np.load(annotations)
elif annotations_ext == '.png':
from dask.array import squeeze
rendered_annotations = squeeze(file_to_dask_array(annotations))
elif annotations_ext == '.xml':
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
annotations = Annotations.from_tcga(tree)
annotations = annotations.fit_spline()
rendered_annotations = annotations.as_image()
else:
# TODO: raise ValueError
pass
viewer.add_image(
rendered_annotations,
name='annotations',
blending='additive',
multiscale=False,
)
from cv2 import inRange as select_color, countNonZero
mask = select_color( # select pure blue
rendered_annotations if isinstance(rendered_annotations,
np.ndarray) \
else rendered_annotations.compute(),
np.array([0, 0, 255]),
np.array([0, 0, 255])
)
if countNonZero(mask) == 0:
# TODO: it'd be nice if we could check this before computing
# the blue-based nuclei mask. the current flow control seems
# unintuitive
mask = rendered_annotations
centers = centers_of_contours(mask_to_contours(mask))
viewer.add_image(
centers_to_image(centers, shape=rendered_annotations.shape),
name='centers',
rgb=True,
blending='additive',
multiscale=False,
)
else:
_, annotations_ext = splitext(annotations)
if annotations_ext == '.npy':
rendered_annotations = np.load(annotations)
elif annotations_ext == '.png':
from dask.array import squeeze
rendered_annotations = squeeze(file_to_dask_array(annotations))
elif annotations_ext == '.xml':
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
annotations_data = Annotations.from_tcga(tree)
annotations_data = annotations_data.fit_spline()
rendered_annotations = annotations_data.as_image()
else:
# TODO: raise ValueError
pass

from cv2 import inRange as select_color, countNonZero
pure_blue = np.asarray([0, 0, 255])
mask = select_color( # select pure blue
rendered_annotations,
pure_blue,
pure_blue,
)
if countNonZero(mask) == 0: # if all black
# TODO: it'd be nice if we could check this before computing
# the blue-based nuclei mask. the current flow control seems
# unintuitive
mask = rendered_annotations

centers = centers_of_contours(annotations_data.export('opencv'))
# TODO: white pixels over-write red pixels so centers can't be seen
rendered_annotations += centers_to_image(
centers, shape=rendered_annotations.shape)

from PIL.Image import fromarray as array_to_pil_image
array_to_pil_image(rendered_annotations).save(output)


@cli.command(name='image')
Expand All @@ -170,102 +136,91 @@ def view_image(image):
@click.argument('image', type=click.Path(exists=True, dir_okay=False))
@click.argument('annotations', type=click.Path(exists=True, dir_okay=False))
@click.option(
'-i',
'--interactive',
default=False,
show_default=True,
type=bool,
is_flag=True,
help=INTERACTIVE_OPTION_HELP,
callback=ComposedCallback(
validate_interactive_napari, validate_interactive_xml),
'-o',
'--output',
default=None,
type=click.File(mode='wb'),
callback=validate_interactive_napari,
)
def view_overlay(image, annotations, interactive):
if napari is None:
# TODO: add output CLI option and write to file
return
annotations_ext = splitext(annotations)[1]

da_img = file_to_dask_array(image)

with napari.gui_qt():
viewer = napari.Viewer()

viewer.add_image(
da_img,
name='image',
blending='additive',
multiscale=False,
)
def view_annotations(image, annotations, output):
from dask.array import squeeze
from viewmask import Annotations

if interactive:
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
from viewmask import Annotations
annotations = Annotations.from_tcga(tree)
annotations = annotations.fit_spline()
regions = annotations.export('napari')
line_color = get_stroke_color(tree)
da_img = squeeze(file_to_dask_array(image))

if output is None: # interactive viewer
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
annotations_data = Annotations.from_tcga(tree)
annotations_data = annotations_data.fit_spline()
regions = annotations_data.export('napari')
line_color = get_stroke_color(tree)

with napari.gui_qt():
viewer = napari.Viewer()
viewer.add_image(
da_img,
name='image',
blending='additive',
multiscale=False,
)
viewer.add_shapes(
regions,
shape_type='path',
edge_color=f"#{line_color}"
edge_color=f"#{line_color}",
)
viewer.add_points(
centers_of_contours(regions),
name='centers'
)
else:
if annotations_ext == '.npy':
rendered_annotations = np.load(annotations)
elif annotations_ext == '.png':
from dask.array import squeeze
rendered_annotations = squeeze(file_to_dask_array(annotations))
elif annotations_ext == '.xml':
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
from viewmask import Annotations
annotations = Annotations.from_tcga(tree)
annotations = annotations.fit_spline()
rendered_annotations = annotations.as_image()
else:
# TODO: raise ValueError
pass
viewer.add_image(
rendered_annotations,
name='annotations',
blending='additive',
multiscale=False,
)
from cv2 import inRange as select_color, countNonZero
mask = select_color( # select pure blue
rendered_annotations if isinstance(rendered_annotations,
np.ndarray) \
else rendered_annotations.compute(),
np.array([0, 0, 255]),
np.array([0, 0, 255])
)
if countNonZero(mask) == 0:
# TODO: it'd be nice if we could check this before computing
# the blue-based nuclei mask. the current flow control seems
# unintuitive
mask = rendered_annotations
centers = centers_of_contours(mask_to_contours(mask))
viewer.add_image(
centers_to_image(centers, shape=rendered_annotations.shape),
name='centers',
rgb=True,
blending='additive',
multiscale=False,
)
else:
rendered_annotations = da_img

_, annotations_ext = splitext(annotations)
if annotations_ext == '.npy':
rendered_annotations += np.load(annotations)
elif annotations_ext == '.png':
rendered_annotations += squeeze(file_to_dask_array(annotations))
elif annotations_ext == '.xml':
try:
tree = ET.parse(annotations)
except ET.ParseError:
# TODO: don't just do nothing, some XMLs might actually be
# unparseable
pass
annotations_data = Annotations.from_tcga(tree)
annotations_data = annotations_data.fit_spline()
rendered_annotations += annotations_data.as_image()
else:
# TODO: raise ValueError
pass

from cv2 import inRange as select_color
from dask.array import asarray as as_da_array, from_delayed as delayed_to_da, count_nonzero
from dask import delayed
pure_blue = as_da_array((0, 0, 255))
mask = delayed_to_da(delayed(select_color, pure=True)( # select pure blue
rendered_annotations,
pure_blue,
pure_blue,
), rendered_annotations.shape[:2], dtype=int)
if count_nonzero(mask) == 0: # if no blue
# TODO: it'd be nice if we could check this before computing
# the blue-based nuclei mask. the current flow control seems
# unintuitive
mask = rendered_annotations

centers = centers_of_contours(annotations_data.export('opencv'))
# TODO: displays bright green heatmap instead of mask
rendered_annotations += centers_to_image(
centers, shape=rendered_annotations.shape)

from PIL.Image import fromarray as array_to_pil_image
array_to_pil_image(rendered_annotations.compute()).save(output)


if __name__ == '__main__':
Expand Down

0 comments on commit 74472fd

Please sign in to comment.