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 Filter.Reduce (general dimension reduction for ImageStack) #1342

Merged
merged 14 commits into from
May 30, 2019
123 changes: 123 additions & 0 deletions starfish/core/image/_filter/reduce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import warnings
from copy import deepcopy
from typing import Callable, Iterable, Optional, Union

import numpy as np

from starfish.core.imagestack.imagestack import ImageStack
from starfish.core.types import Axes
from starfish.core.util import click
from ._base import FilterAlgorithmBase


class Reduce(FilterAlgorithmBase):
"""
kevinyamauchi marked this conversation as resolved.
Show resolved Hide resolved
Reduces the dimensions of the ImageStack by applying a function
along one or more axes.

Parameters
----------
dims : Axes
one or more Axes to project over
func : Union[str, Callable]
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not accept just Callable?

Copy link
Collaborator

Choose a reason for hiding this comment

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

i like it. this is the more 'pythonic' way in scientific computing -- will be easier for numpy/pandas users to reason about i think.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@dganguli You like what? The original or my suggestion?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the absence of an opposition, I think func should be a Callable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it's nice to have the string option. I think a lot of users will find it annoying to need to import numpy.amax() and this component to perform a maximum intensity projection.

Copy link
Member

Choose a reason for hiding this comment

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

I support the string option, just for ecosystem continuity -- users are used to passing function names that are resolved by getattr, as this formalism is used a lot in scipy.spatial.distance among other packages.

function to apply across the dimension(s) specified by dims.
If a functino is provided, it follow the form specified by
kevinyamauchi marked this conversation as resolved.
Show resolved Hide resolved
DataArray.reduce():
http://xarray.pydata.org/en/stable/generated/xarray.DataArray.reduce.html
The following strings are valid:
max: maximum intensity projection (applies numpy.amax)
mean: take the mean across the dim(s) (applies numpy.mean)
sum: sum across the dim(s) (applies numpy.sum)

See Also
--------
starfish.types.Axes

"""

def __init__(
self, dims: Iterable[Union[Axes, str]], func: Union[str, Callable] = 'max'
) -> None:

self.dims = dims

# If the user provided a string, convert to callable
if isinstance(func, str):
if func == 'max':
func = np.amax
elif func == 'mean':
func = np.mean
elif func == 'sum':
func = np.sum
else:
raise ValueError('func should be max, mean, or sum')
elif callable(func):
warnings.warn('User-specific functions are not logged')
kevinyamauchi marked this conversation as resolved.
Show resolved Hide resolved
self.func = func

_DEFAULT_TESTING_PARAMETERS = {"dims": 'r', "func": 'max'}

def run(
self,
stack: ImageStack,
in_place: bool = False,
kevinyamauchi marked this conversation as resolved.
Show resolved Hide resolved
verbose: bool = False,
n_processes: Optional[int] = None,
*args,
) -> ImageStack:
"""Performs the dimension reduction with the specifed function

Parameters
----------
stack : ImageStack
Stack to be filtered.
in_place : bool
if True, process ImageStack in-place, otherwise return a new stack
verbose : bool
if True, report on filtering progress (default = False)
n_processes : Optional[int]
Number of parallel processes to devote to calculating the filter

Returns
-------
ImageStack :
If in-place is False, return the results of filter as a new stack. Otherwise return the
original stack.

"""
if not in_place:
stack = deepcopy(stack)

# Apply the reducing function
reduced = stack._data.reduce(self.func, dim=[dim.value for dim in self.dims])

# Add the reduced dims back and align with the original stack
reduced = reduced.expand_dims(tuple(dim.value for dim in self.dims))
reduced = reduced.transpose(*stack.xarray.dims)
kevinyamauchi marked this conversation as resolved.
Show resolved Hide resolved

# Construct the stack
stack = stack.from_numpy(reduced.values)

return stack

@staticmethod
@click.command("Reduce")
@click.option(
"--dims",
type=click.Choice(
[Axes.ROUND.value, Axes.CH.value, Axes.ZPLANE.value, Axes.X.value, Axes.Y.value]
),
multiple=True,
help="The dimensions the Imagestack should max project over."
"For multiple dimensions add multiple --dims. Ex."
"--dims r --dims c")
@click.option(
"--func",
type=click.Choice(["max", "mean", "sum"]),
multiple=False,
help="The function to apply across dims"
"Valid function names: max, mean, sum."
)
@click.pass_context
def _cli(ctx, dims, func):
ctx.obj["component"]._cli_run(ctx, Reduce(dims, func))
kevinyamauchi marked this conversation as resolved.
Show resolved Hide resolved