Skip to content

Commit

Permalink
Pipeline component and implementation for merging BinaryMaskCollections
Browse files Browse the repository at this point in the history
This does not handle the case where the pixel/physical ticks do not line up.  This is for cases where we derive the data from the exact same set of images, and inherit their pixel/physical ticks from the same source.

Test plan: Added one positive test case, and tested the cases where the merge should fail.
  • Loading branch information
Tony Tung committed Dec 16, 2019
1 parent 715b762 commit 65ac815
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 2 deletions.
18 changes: 16 additions & 2 deletions docs/source/api/morphology/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Morphology Transformations
==========================

starfish provides a variety of methods to perform transformations on morphological data. These include :py:class:`~starfish.morphology.Binarize`, which transform image data into morphological data and
:py:class:`~starfish.morphology.Filter`, which performs filtering operations on morphological data.

:py:class:`~starfish.morphology.Filter`, which performs filtering operations on morphological data, and
:py:class:`~starfish.morphology.Merge`, which combines different sets of morphological data.

.. _binarize:

Expand Down Expand Up @@ -34,3 +34,17 @@ Filtering operations can be imported using ``starfish.morphology.Filter``, which
.. automodule:: starfish.morphology.Filter
:members:

.. _merge:

Merge
-----

Filtering operations can be imported using ``starfish.morphology.Merge``, which registers all classes that subclass :py:class:`~starfish.morphology.Merge.MergeAlgorithm`:

.. code-block:: python
from starfish.morphology import Merge
.. automodule:: starfish.morphology.Merge
:members:
10 changes: 10 additions & 0 deletions starfish/core/morphology/Merge/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Algorithms in this module merge multiple BinaryMaskCollections together."""
from ._base import MergeAlgorithm
from .simple import SimpleMerge

# autodoc's automodule directive only captures the modules explicitly listed in __all__.
__all__ = list(set(
implementation_name
for implementation_name, implementation_cls in locals().items()
if isinstance(implementation_cls, type) and issubclass(implementation_cls, MergeAlgorithm)
))
18 changes: 18 additions & 0 deletions starfish/core/morphology/Merge/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from abc import abstractmethod
from typing import Sequence

from starfish.core.morphology.binary_mask import BinaryMaskCollection
from starfish.core.pipeline.algorithmbase import AlgorithmBase


class MergeAlgorithm(metaclass=AlgorithmBase):

@abstractmethod
def run(
self,
binary_mask_collections: Sequence[BinaryMaskCollection],
*args,
**kwargs
) -> BinaryMaskCollection:
"""Merge multiple binary mask collections together."""
raise NotImplementedError()
48 changes: 48 additions & 0 deletions starfish/core/morphology/Merge/simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Mapping, Optional, Sequence

import numpy as np

from starfish.core.morphology.binary_mask import BinaryMaskCollection
from starfish.core.morphology.util import _ticks_equal
from starfish.core.types import ArrayLike, Axes, Coordinates, Number
from ._base import MergeAlgorithm


class SimpleMerge(MergeAlgorithm):
def run(
self,
binary_mask_collections: Sequence[BinaryMaskCollection],
*args,
**kwargs
) -> BinaryMaskCollection:
"""Merge multiple binary mask collections together. This implementation requires that all
the binary mask collections have the same pixel and physical ticks."""
pixel_ticks: Optional[Mapping[Axes, ArrayLike[int]]] = None
physical_ticks: Optional[Mapping[Coordinates, ArrayLike[Number]]] = None

# validate that they have the same pixel/physical ticks.
for binary_mask_collection in binary_mask_collections:
pixel_ticks = pixel_ticks or binary_mask_collection._pixel_ticks
physical_ticks = physical_ticks or binary_mask_collection._physical_ticks

if not _ticks_equal(pixel_ticks, binary_mask_collection._pixel_ticks):
raise ValueError("not all masks have the same pixel ticks")
if not _ticks_equal(physical_ticks, binary_mask_collection._physical_ticks):
raise ValueError("not all masks have the same physical ticks")

# gather up all the uncropped masks.
all_uncropped_masks = [
np.asarray(binary_mask_collection.uncropped_mask(ix))
for binary_mask_collection in binary_mask_collections
for ix in range(len(binary_mask_collection))
]

assert pixel_ticks is not None
assert physical_ticks is not None

return BinaryMaskCollection.from_binary_arrays_and_ticks(
all_uncropped_masks,
pixel_ticks,
physical_ticks,
None,
)
Empty file.
82 changes: 82 additions & 0 deletions starfish/core/morphology/Merge/test/test_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import numpy as np
import pytest

from starfish.core.morphology.binary_mask import BinaryMaskCollection
from starfish.core.morphology.binary_mask.test.factories import (
binary_arrays_2d,
binary_mask_collection_2d,
binary_mask_collection_3d,
)
from starfish.core.morphology.util import _ticks_equal
from starfish.core.types import Axes, Coordinates
from ..simple import SimpleMerge


def test_success():
mask_collection_0 = binary_mask_collection_2d()
binary_arrays, physical_ticks = binary_arrays_2d()
binary_arrays_negated = [
np.bitwise_not(binary_array)
for binary_array in binary_arrays
]
mask_collection_1 = BinaryMaskCollection.from_binary_arrays_and_ticks(
binary_arrays_negated, None, physical_ticks, None)

merged = SimpleMerge().run([mask_collection_0, mask_collection_1])

assert _ticks_equal(merged._pixel_ticks, mask_collection_0._pixel_ticks)
assert _ticks_equal(merged._physical_ticks, mask_collection_0._physical_ticks)
assert len(mask_collection_0) + len(mask_collection_1) == len(merged)

# go through all the original uncroppped masks, and verify that they are somewhere in the merged
# set.
for mask_collection in (mask_collection_0, mask_collection_1):
for ix in range(len(mask_collection)):
uncropped_original_mask = mask_collection.uncropped_mask(ix)
for jx in range(len(merged)):
uncropped_copy_mask = merged.uncropped_mask(jx)

if uncropped_original_mask.equals(uncropped_copy_mask):
# found the copy, break
break
else:
pytest.fail("could not find mask in merged set.")

def test_pixel_tick_mismatch():
mask_collection_0 = binary_mask_collection_2d()
mask_collection_0._pixel_ticks[Axes.X.value] = np.asarray(
mask_collection_0._pixel_ticks[Axes.X.value]) + 1
binary_arrays, physical_ticks = binary_arrays_2d()
binary_arrays_negated = [
np.bitwise_not(binary_array)
for binary_array in binary_arrays
]
mask_collection_1 = BinaryMaskCollection.from_binary_arrays_and_ticks(
binary_arrays_negated, None, physical_ticks, None)

with pytest.raises(ValueError):
SimpleMerge().run([mask_collection_0, mask_collection_1])


def test_physical_tick_mismatch():
mask_collection_0 = binary_mask_collection_2d()
mask_collection_0._physical_ticks[Coordinates.X] = np.asarray(
mask_collection_0._physical_ticks[Coordinates.X]) + 1
binary_arrays, physical_ticks = binary_arrays_2d()
binary_arrays_negated = [
np.bitwise_not(binary_array)
for binary_array in binary_arrays
]
mask_collection_1 = BinaryMaskCollection.from_binary_arrays_and_ticks(
binary_arrays_negated, None, physical_ticks, None)

with pytest.raises(ValueError):
SimpleMerge().run([mask_collection_0, mask_collection_1])


def test_shape_mismatch():
mask_collection_0 = binary_mask_collection_2d()
mask_collection_1 = binary_mask_collection_3d()

with pytest.raises(ValueError):
SimpleMerge().run([mask_collection_0, mask_collection_1])
1 change: 1 addition & 0 deletions starfish/morphology.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from starfish.core.morphology import ( # noqa: F401
Binarize,
Filter,
Merge,
)
from starfish.core.morphology.binary_mask import BinaryMaskCollection # noqa: F401
from starfish.core.morphology.label_image import LabelImage # noqa: F401

0 comments on commit 65ac815

Please sign in to comment.