Skip to content

Commit

Permalink
Filters for mask collections (#1659)
Browse files Browse the repository at this point in the history
This PR introduces filters to take a binary mask collection to produce another binary mask collection.

A map filter is added that can take arbitrary methods in numpy/scipy/skimage and run them against the binary masks.

Depends on #1655
Test plan: Added a simple test that does binary dilation on binary masks.
  • Loading branch information
Tony Tung authored Nov 22, 2019
1 parent 2186b2f commit 5799686
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 0 deletions.
12 changes: 12 additions & 0 deletions starfish/core/morphology/Filter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Algorithms in this module filter a BinaryMaskCollection, producing another
BinaryMaskCollection."""
from ._base import FilterAlgorithm
from .map import Map

# autodoc's automodule directive only captures the modules explicitly listed in __all__.
all_filters = {
filter_name: filter_cls
for filter_name, filter_cls in locals().items()
if isinstance(filter_cls, type) and issubclass(filter_cls, FilterAlgorithm)
}
__all__ = list(all_filters.keys())
17 changes: 17 additions & 0 deletions starfish/core/morphology/Filter/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from abc import abstractmethod

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


class FilterAlgorithm(metaclass=AlgorithmBase):

@abstractmethod
def run(
self,
binary_mask_collection: BinaryMaskCollection,
*args,
**kwargs
) -> BinaryMaskCollection:
"""Performs a filter on the binary mask collection provided."""
raise NotImplementedError()
82 changes: 82 additions & 0 deletions starfish/core/morphology/Filter/map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from typing import Callable, Optional

from starfish.core.morphology.binary_mask import BinaryMaskCollection
from starfish.core.types import FunctionSource
from ._base import FilterAlgorithm


class Map(FilterAlgorithm):
"""
Map from input to output by applying a specified function to the input. The output must have
the same shape as the input.
Parameters
----------
func : str
Name of a function in the module specified by the ``module`` parameter to apply across the
dimension(s) specified by dims. The function is resolved by ``getattr(<module>, func)``,
except in the cases of predefined aliases. See :py:class:`FunctionSource` for more
information about aliases.
module : FunctionSource
Python module that serves as the source of the function. It must be listed as one of the
members of :py:class:`FunctionSource`.
Currently, the supported FunctionSources are:
- ``np``: the top-level package of numpy
- ``scipy``: the top-level package of scipy
Examples
--------
Applying a binary opening function.
>>> from starfish.core.morphology.object.binary_mask.test import factories
>>> from starfish.morphology import Filter
>>> from skimage.morphology import disk
>>> binary_mask_collection = factories.binary_mask_collection_2d()
>>> opener = Filter.Map("morphology.binary_opening", disk(4))
>>> opened = opener.run(binary_mask_collection)
See Also
--------
starfish.core.types.Axes
"""

def __init__(
self,
func: str,
*func_args,
module: FunctionSource = FunctionSource.np,
**func_kwargs,
) -> None:
self._func: Callable = module._resolve_method(func)
self._func_args = func_args
self._func_kwargs = func_kwargs

def run(
self,
binary_mask_collection: BinaryMaskCollection,
n_processes: Optional[int] = None,
*args,
**kwargs
) -> BinaryMaskCollection:
"""Map from input to output by applying a specified function to the input.
Parameters
----------
binary_mask_collection : BinaryMaskCollection
BinaryMaskCollection to be filtered.
n_processes : Optional[int]
The number of processes to use for apply. If None, uses the output of os.cpu_count()
(default = None).
Returns
-------
BinaryMaskCollection
Return the results of filter as a new BinaryMaskCollection.
"""

# Apply the reducing function
return binary_mask_collection._apply(
self._func,
*self._func_args,
**self._func_kwargs)
Empty file.
33 changes: 33 additions & 0 deletions starfish/core/morphology/Filter/test/test_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import numpy as np

from starfish.core.morphology.binary_mask.test.factories import binary_mask_collection_2d
from starfish.core.types import Axes, FunctionSource
from ..map import Map


def test_apply():
input_mask_collection = binary_mask_collection_2d()
filt = Map("morphology.binary_dilation", module=FunctionSource.skimage)
output_mask_collection = filt.run(input_mask_collection)

assert input_mask_collection._pixel_ticks == output_mask_collection._pixel_ticks
assert input_mask_collection._physical_ticks == output_mask_collection._physical_ticks
assert input_mask_collection._log == output_mask_collection._log
assert len(input_mask_collection) == len(output_mask_collection)

region_0, region_1 = output_mask_collection.masks()

assert region_0.name == '0'
assert region_1.name == '1'

temp = np.ones((2, 6), dtype=np.bool)
assert np.array_equal(region_0, temp)
temp = np.ones((3, 4), dtype=np.bool)
temp[0, 0] = 0
assert np.array_equal(region_1, temp)

assert np.array_equal(region_0[Axes.Y.value], [0, 1])
assert np.array_equal(region_0[Axes.X.value], [0, 1, 2, 3, 4, 5])

assert np.array_equal(region_1[Axes.Y.value], [2, 3, 4])
assert np.array_equal(region_1[Axes.X.value], [2, 3, 4, 5])
4 changes: 4 additions & 0 deletions starfish/core/morphology/binary_mask/binary_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ def max_shape(self) -> Mapping[Axes, int]:
for ix, (axis, _) in enumerate(zip(*_get_axes_names(len(self._pixel_ticks))))
}

@property
def log(self) -> Log:
return self._log

@classmethod
def from_label_image(cls, label_image: LabelImage) -> "BinaryMaskCollection":
"""Creates binary masks from a label image. Masks are cropped to the smallest size that
Expand Down
1 change: 1 addition & 0 deletions starfish/core/types/_functionsource.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ def _resolve_method(self, method: str) -> Callable:
np = ("numpy", {'max': 'amax'})
"""Function source for the numpy libraries"""
scipy = ("scipy",)
skimage = ("skimage",)
1 change: 1 addition & 0 deletions starfish/morphology.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from starfish.core.morphology import Filter # noqa: F401

0 comments on commit 5799686

Please sign in to comment.