Skip to content

Commit

Permalink
[DLMED] change to utility
Browse files Browse the repository at this point in the history
Signed-off-by: Nic Ma <nma@nvidia.com>
  • Loading branch information
Nic-Ma committed Aug 3, 2021
1 parent 0c20766 commit d752ea4
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 132 deletions.
25 changes: 14 additions & 11 deletions docs/source/transforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,6 @@ Intensity
:members:
:special-members: __call__

`IntensityStats`
""""""""""""""""
.. autoclass:: IntensityStats
:members:
:special-members: __call__


IO
^^
Expand Down Expand Up @@ -668,6 +662,13 @@ Utility
:members:
:special-members: __call__

`IntensityStats`
""""""""""""""""
.. autoclass:: IntensityStats
:members:
:special-members: __call__


Dictionary Transforms
---------------------

Expand Down Expand Up @@ -917,11 +918,6 @@ Intensity (Dict)
:members:
:special-members: __call__

`IntensityStatsd`
"""""""""""""""""
.. autoclass:: IntensityStatsd
:members:
:special-members: __call__

IO (Dict)
^^^^^^^^^
Expand Down Expand Up @@ -1277,6 +1273,13 @@ Utility (Dict)
:members:
:special-members: __call__

`IntensityStatsd`
"""""""""""""""""
.. autoclass:: IntensityStatsd
:members:
:special-members: __call__


Transform Adaptors
------------------
.. automodule:: monai.transforms.adaptors
Expand Down
8 changes: 4 additions & 4 deletions monai/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@
GaussianSharpen,
GaussianSmooth,
GibbsNoise,
IntensityStats,
KSpaceSpikeNoise,
MaskIntensity,
NormalizeIntensity,
Expand Down Expand Up @@ -121,9 +120,6 @@
GibbsNoised,
GibbsNoiseD,
GibbsNoiseDict,
IntensityStatsd,
IntensityStatsD,
IntensityStatsDict,
KSpaceSpikeNoised,
KSpaceSpikeNoiseD,
KSpaceSpikeNoiseDict,
Expand Down Expand Up @@ -332,6 +328,7 @@
EnsureType,
FgBgToIndices,
Identity,
IntensityStats,
LabelToMask,
Lambda,
MapLabelValue,
Expand Down Expand Up @@ -394,6 +391,9 @@
Identityd,
IdentityD,
IdentityDict,
IntensityStatsd,
IntensityStatsD,
IntensityStatsDict,
LabelToMaskd,
LabelToMaskD,
LabelToMaskDict,
Expand Down
56 changes: 0 additions & 56 deletions monai/transforms/intensity/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
"KSpaceSpikeNoise",
"RandKSpaceSpikeNoise",
"RandCoarseDropout",
"IntensityStats",
]


Expand Down Expand Up @@ -1627,58 +1626,3 @@ def __call__(self, img: np.ndarray):
img[h] = self.fill_value

return img


class IntensityStats(Transform):
"""
Compute statistics for the intensity values of input image and store into the meta data dictionary.
For example: if `ops=[lambda x: np.mean(x), "max"]` and `key_prefix="orig"`, may generate below stats:
`{"orig_custom_0": 1.5, "orig_max": 3.0}`.
Args:
ops: expected operations to compute statistics for the intensity.
if a string, will map to the predefined operations, supported: ["mean", "median", "max", "min", "std"]
mapping to `np.nanmean`, `np.nanmedian`, `np.nanmax`, `np.nanmin`, `np.nanstd`.
if a callable function, will execute the function on input image.
key_prefix: the prefix to combine with `ops` name to generate the key to store the results in the
meta data dictionary. if some `ops` are callable functions, will use "{key_prefix}_custom_{index}"
as the key, where index counts from 0.
channel_wise: whether to compute statistics for every channel of input image separately.
if True, return a list of values for every operation, default to False.
"""

def __init__(self, ops: Sequence[Union[str, Callable]], key_prefix: str, channel_wise: bool = False) -> None:
self.supported_ops = {
"mean": lambda x: np.nanmean(x),
"median": lambda x: np.nanmedian(x),
"max": lambda x: np.nanmax(x),
"min": lambda x: np.nanmin(x),
"std": lambda x: np.nanstd(x),
}
self.ops = ensure_tuple(ops)
for o in self.ops:
if isinstance(o, str) and o not in self.supported_ops:
raise ValueError(f"unsupported operation: {o}.")
self.key_prefix = key_prefix
self.channel_wise = channel_wise

def __call__(self, img: np.ndarray, meta_data: Optional[Dict] = None) -> Tuple[np.ndarray, Dict]:
if meta_data is None:
meta_data = {}

def _compute(op: Callable, img: np.ndarray):
if self.channel_wise:
return [op(c) for c in img]
else:
return op(img)

custom_index = 0
for o in self.ops:
if isinstance(o, str):
meta_data[self.key_prefix + "_" + o] = _compute(self.supported_ops[o], img)
elif callable(o):
meta_data[self.key_prefix + "_custom_" + str(custom_index)] = _compute(o, img)
custom_index += 1

return img, meta_data
61 changes: 0 additions & 61 deletions monai/transforms/intensity/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
GaussianSharpen,
GaussianSmooth,
GibbsNoise,
IntensityStats,
KSpaceSpikeNoise,
MaskIntensity,
NormalizeIntensity,
Expand Down Expand Up @@ -72,7 +71,6 @@
"RandKSpaceSpikeNoised",
"RandHistogramShiftd",
"RandCoarseDropoutd",
"IntensityStatsd",
"RandGaussianNoiseD",
"RandGaussianNoiseDict",
"ShiftIntensityD",
Expand Down Expand Up @@ -123,8 +121,6 @@
"RandRicianNoiseDict",
"RandCoarseDropoutD",
"RandCoarseDropoutDict",
"IntensityStatsD",
"IntensityStatsDict",
]


Expand Down Expand Up @@ -1468,62 +1464,6 @@ def __call__(self, data):
return d


class IntensityStatsd(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.IntensityStats`.
Compute statistics for the intensity values of input image and store into the meta data dictionary.
For example: if `ops=[lambda x: np.mean(x), "max"]` and `key_prefix="orig"`, may generate below stats:
`{"orig_custom_0": 1.5, "orig_max": 3.0}`.
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
ops: expected operations to compute statistics for the intensity.
if a string, will map to the predefined operations, supported: ["mean", "median", "max", "min", "std"]
mapping to `np.nanmean`, `np.nanmedian`, `np.nanmax`, `np.nanmin`, `np.nanstd`.
if a callable function, will execute the function on input image.
key_prefix: the prefix to combine with `ops` name to generate the key to store the results in the
meta data dictionary. if some `ops` are callable functions, will use "{key_prefix}_custom_{index}"
as the key, where index counts from 0.
channel_wise: whether to compute statistics for every channel of input image separately.
if True, return a list of values for every operation, default to False.
meta_keys: explicitly indicate the key of the corresponding meta data dictionary.
used to store the computed statistics to the meta dict.
for example, for data with key `image`, the metadata by default is in `image_meta_dict`.
the meta data is a dictionary object which contains: filename, original_shape, etc.
it can be a sequence of string, map to the `keys`.
if None, will try to construct meta_keys by `key_{meta_key_postfix}`.
meta_key_postfix: if meta_keys is None, use `key_{postfix}` to to fetch the meta data according
to the key data, default is `meta_dict`, the meta data is a dictionary object.
used to store the computed statistics to the meta dict.
allow_missing_keys: don't raise exception if key is missing.
"""

def __init__(
self,
keys: KeysCollection,
ops: Sequence[Union[str, Callable]],
key_prefix: str,
channel_wise: bool = False,
meta_keys: Optional[KeysCollection] = None,
meta_key_postfix: str = "meta_dict",
allow_missing_keys: bool = False,
) -> None:
super().__init__(keys, allow_missing_keys)
self.stats = IntensityStats(ops=ops, key_prefix=key_prefix, channel_wise=channel_wise)
self.meta_keys = ensure_tuple_rep(None, len(self.keys)) if meta_keys is None else ensure_tuple(meta_keys)
if len(self.keys) != len(self.meta_keys):
raise ValueError("meta_keys should have the same length as keys.")
self.meta_key_postfix = ensure_tuple_rep(meta_key_postfix, len(self.keys))

def __call__(self, data) -> Dict[Hashable, np.ndarray]:
d = dict(data)
for key, meta_key, meta_key_postfix in self.key_iterator(d, self.meta_keys, self.meta_key_postfix):
meta_key = meta_key or f"{key}_{meta_key_postfix}"
d[key], d[meta_key] = self.stats(d[key], d.get(meta_key))
return d


RandGaussianNoiseD = RandGaussianNoiseDict = RandGaussianNoised
RandRicianNoiseD = RandRicianNoiseDict = RandRicianNoised
Expand Down Expand Up @@ -1551,4 +1491,3 @@ def __call__(self, data) -> Dict[Hashable, np.ndarray]:
KSpaceSpikeNoiseD = KSpaceSpikeNoiseDict = KSpaceSpikeNoised
RandKSpaceSpikeNoiseD = RandKSpaceSpikeNoiseDict = RandKSpaceSpikeNoised
RandCoarseDropoutD = RandCoarseDropoutDict = RandCoarseDropoutd
IntensityStatsD = IntensityStatsDict = IntensityStatsd
56 changes: 56 additions & 0 deletions monai/transforms/utility/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"AddExtremePointsChannel",
"TorchVision",
"MapLabelValue",
"IntensityStats",
]


Expand Down Expand Up @@ -938,3 +939,58 @@ def __call__(self, img: np.ndarray):
np.place(out_flat, img_flat == o, t)

return out_flat.reshape(img.shape)


class IntensityStats(Transform):
"""
Compute statistics for the intensity values of input image and store into the meta data dictionary.
For example: if `ops=[lambda x: np.mean(x), "max"]` and `key_prefix="orig"`, may generate below stats:
`{"orig_custom_0": 1.5, "orig_max": 3.0}`.
Args:
ops: expected operations to compute statistics for the intensity.
if a string, will map to the predefined operations, supported: ["mean", "median", "max", "min", "std"]
mapping to `np.nanmean`, `np.nanmedian`, `np.nanmax`, `np.nanmin`, `np.nanstd`.
if a callable function, will execute the function on input image.
key_prefix: the prefix to combine with `ops` name to generate the key to store the results in the
meta data dictionary. if some `ops` are callable functions, will use "{key_prefix}_custom_{index}"
as the key, where index counts from 0.
channel_wise: whether to compute statistics for every channel of input image separately.
if True, return a list of values for every operation, default to False.
"""

def __init__(self, ops: Sequence[Union[str, Callable]], key_prefix: str, channel_wise: bool = False) -> None:
self.supported_ops = {
"mean": lambda x: np.nanmean(x),
"median": lambda x: np.nanmedian(x),
"max": lambda x: np.nanmax(x),
"min": lambda x: np.nanmin(x),
"std": lambda x: np.nanstd(x),
}
self.ops = ensure_tuple(ops)
for o in self.ops:
if isinstance(o, str) and o not in self.supported_ops:
raise ValueError(f"unsupported operation: {o}.")
self.key_prefix = key_prefix
self.channel_wise = channel_wise

def __call__(self, img: np.ndarray, meta_data: Optional[Dict] = None) -> Tuple[np.ndarray, Dict]:
if meta_data is None:
meta_data = {}

def _compute(op: Callable, img: np.ndarray):
if self.channel_wise:
return [op(c) for c in img]
else:
return op(img)

custom_index = 0
for o in self.ops:
if isinstance(o, str):
meta_data[self.key_prefix + "_" + o] = _compute(self.supported_ops[o], img)
elif callable(o):
meta_data[self.key_prefix + "_custom_" + str(custom_index)] = _compute(o, img)
custom_index += 1

return img, meta_data
Loading

0 comments on commit d752ea4

Please sign in to comment.