Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add --allow_overlaps arg to cli #120

Merged
merged 11 commits into from
Jul 6, 2022
20 changes: 12 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ To export images via bioformats2raw we use the ```--bf``` flag::
$ omero zarr --output /home/user/zarr_files export 1 --bf
Image exported to /home/user/zarr_files/2chZT.lsm

Masks
^^^^^
Masks and Polygons
^^^^^^^^^^^^^^^^^^

To export Masks for an Image or Plate::
To export Masks or Polygons for an Image or Plate, use the `masks` or `polygons` command::

# Saved under 1.zarr/labels/0 - 1.zarr/ must already exist
$ omero zarr masks Image:1
Expand All @@ -78,12 +78,16 @@ To export Masks for an Image or Plate::
# e.g. Export to 1.zarr/labels/A
$ omero zarr masks Image:1 --label-name=A

The default behaviour is to export all masks on the Image to a single 5D
"labeled" zarr array, with a different value for each mask Shape.
An exception will be thrown if any of the masks overlap.
# Allow overlapping masks or polygons (overlap will be maximum value of the dtype)
$ omero zarr polygons Image:1 --overlaps=dtype_max

To handle overlapping masks, split masks into non-overlapping zarr groups
using a "label-map" which is a csv file of that specifies the name of
The default behaviour is to export all masks or polygons on the Image to a single 5D
"labeled" zarr array, with a different value for each Shape.
An exception will be thrown if any of the masks overlap, unless the `--overlaps`
option is used as above.

An alternative to handle overlapping masks is to split masks into non-overlapping zarr
groups using a "label-map" which is a csv file that specifies the name of
the zarr group for each ROI on the Image. Columns are ID, NAME, ROI_ID.

For example, to create a group from the `textValue` of each Shape,
Expand Down
17 changes: 16 additions & 1 deletion src/omero_zarr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
from zarr.hierarchy import open_group
from zarr.storage import FSStore

from .masks import MASK_DTYPE_SIZE, image_shapes_to_zarr, plate_shapes_to_zarr
from .masks import (
MASK_DTYPE_SIZE,
MaskSaver,
image_shapes_to_zarr,
plate_shapes_to_zarr,
)
from .raw_pixels import (
add_omero_metadata,
add_toplevel_metadata,
Expand Down Expand Up @@ -272,6 +277,16 @@ def _configure(self, parser: Parser) -> None:
subcommand.add_argument(
"--output", type=str, default="", help="The output directory"
)
for subcommand in (polygons, masks):
subcommand.add_argument(
"--overlaps",
type=str,
default=MaskSaver.OVERLAPS[0],
choices=MaskSaver.OVERLAPS,
help="To allow overlapping shapes, use 'dtype_max':"
" All overlapping regions will be set to the"
" max value for the dtype",
)

@gateway_required
def masks(self, args: argparse.Namespace) -> None:
Expand Down
57 changes: 41 additions & 16 deletions src/omero_zarr/masks.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ def plate_shapes_to_zarr(

dtype = MASK_DTYPE_SIZE[int(args.label_bits)]
saver = MaskSaver(
plate, None, dtype, args.label_path, args.style, args.source_image
plate,
None,
dtype,
args.label_path,
args.style,
args.source_image,
args.overlaps,
)

count = 0
Expand Down Expand Up @@ -154,7 +160,13 @@ def image_shapes_to_zarr(

if masks:
saver = MaskSaver(
None, image, dtype, args.label_path, args.style, args.source_image
None,
image,
dtype,
args.label_path,
args.style,
args.source_image,
args.overlaps,
)

if args.style == "split":
Expand All @@ -178,6 +190,8 @@ class MaskSaver:
masks to zarr groups/arrays.
"""

OVERLAPS = ["error", "dtype_max"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a very small aside, unless you want this to be externally extensible, a tuple property would be safer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 2a0c9ce


def __init__(
self,
plate: Optional[omero.gateway.PlateWrapper],
Expand All @@ -186,13 +200,15 @@ def __init__(
path: str = "labels",
style: str = "labeled",
source: str = "..",
overlaps: str = "error",
) -> None:
self.dtype = dtype
self.path = path
self.style = style
self.source_image = source
self.plate = plate
self.plate_path = Optional[str]
self.overlaps = overlaps
if image:
self.image = image
self.size_t = image.getSizeT()
Expand Down Expand Up @@ -302,7 +318,6 @@ def save(self, masks: List[omero.model.Shape], name: str) -> None:
masks,
mask_shape,
ignored_dimensions,
check_overlaps=True,
)

axes = marshal_axes(self.image)
Expand Down Expand Up @@ -433,7 +448,7 @@ def masks_to_labels(
masks: List[omero.model.Mask],
mask_shape: Tuple[int, ...],
ignored_dimensions: Set[str] = None,
check_overlaps: bool = True,
check_overlaps: bool = None,
) -> Tuple[np.ndarray, Dict[int, str], Dict[int, Dict]]:
"""
:param masks [MaskI]: Iterable container of OMERO masks
Expand Down Expand Up @@ -464,6 +479,11 @@ def masks_to_labels(
sorted_roi_ids = list(set(roi_ids))
sorted_roi_ids.sort()

if check_overlaps is None:
# If overlaps isn't 'dtype_max', an exception is thrown
# if any overlaps exist
check_overlaps = self.overlaps != "dtype_max"

# label values are 1...n
max_value = len(sorted_roi_ids)
# find most suitable dtype...
Expand Down Expand Up @@ -512,22 +532,27 @@ def masks_to_labels(
for i_z in self._get_indices(
ignored_dimensions, "Z", z, size_z
):
if check_overlaps and np.any(
np.logical_and(
labels[
i_t, i_c, i_z, y : (y + h), x : (x + w)
].astype(np.bool),
binim_yx,
)
):
raise Exception(
f"Mask {shape.roi.id.val} overlaps "
"with existing labels"
)
overlap = np.logical_and(
labels[i_t, i_c, i_z, y : (y + h), x : (x + w)].astype(
np.bool
),
binim_yx,
)
# ADD to the array, so zeros in our binarray don't
# wipe out previous shapes
labels[i_t, i_c, i_z, y : (y + h), x : (x + w)] += (
binim_yx * shape_value
)

if np.any(overlap):
if check_overlaps:
raise Exception(
f"Shape {shape.roi.id.val} overlaps "
"with existing labels"
)
else:
# set overlapping region to max(dtype)
labels[i_t, i_c, i_z, y : (y + h), x : (x + w)][
overlap
] = np.iinfo(labels_dtype).max
return labels, fillColors, properties