Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
42a45e0
Merge pull request #19 from Project-MONAI/master
Nic-Ma Feb 1, 2021
cd16a13
Merge pull request #32 from Project-MONAI/master
Nic-Ma Feb 24, 2021
1449a0c
Merge pull request #117 from Project-MONAI/dev
Nic-Ma Jun 1, 2021
301b8e5
[DLMED] add metric base class
Nic-Ma Jun 1, 2021
c6ad418
Merge branch 'dev' into 561-provide-metrics-base
Nic-Ma Jun 1, 2021
4f63d2f
[DLMED] update meandice and auc
Nic-Ma Jun 2, 2021
9571f53
[DLMED] extract reduce API
Nic-Ma Jun 2, 2021
c31d1ae
[DLMED] update regression metrics
Nic-Ma Jun 2, 2021
5871957
[DLMED] update all the other metrics and enhance unit tests
Nic-Ma Jun 2, 2021
bb4b5c7
Merge branch 'dev' into 561-provide-metrics-base
Nic-Ma Jun 2, 2021
566a3cb
[DLMED] add doc-strings and update unit tests
Nic-Ma Jun 2, 2021
e808466
Merge branch 'dev' into 561-provide-metrics-base
Nic-Ma Jun 2, 2021
0b35531
[MONAI] python code formatting
monai-bot Jun 2, 2021
29b87e0
[DLMED] fix flake8 issue
Nic-Ma Jun 2, 2021
9f9139c
[DLMED] fix pytype issue
Nic-Ma Jun 2, 2021
f07413f
[DLMED] fix all the mypy issues
Nic-Ma Jun 2, 2021
88f56d4
Merge branch 'dev' into 561-provide-metrics-base
Nic-Ma Jun 2, 2021
1372133
[DLMED] fix integration test
Nic-Ma Jun 2, 2021
1ed7a4f
[MONAI] python code formatting
monai-bot Jun 2, 2021
0d5d278
[DLMED] fix flake8 issue
Nic-Ma Jun 2, 2021
1a173fb
[DLMED] fix pytype issue
Nic-Ma Jun 2, 2021
af858e4
[DLMED] add more sanity check
Nic-Ma Jun 2, 2021
e4d5f7b
[DLMED] update according to comments
Nic-Ma Jun 2, 2021
0888faa
Merge branch 'dev' into 561-provide-metrics-base
yiheng-wang-nv Jun 3, 2021
74a271a
[DLMED] update according to Yiheng's comments
Nic-Ma Jun 3, 2021
0d2f32c
[DLMED] update according to Wenqi's comments
Nic-Ma Jun 3, 2021
ed9acde
Merge branch 'dev' into 561-provide-metrics-base
Nic-Ma Jun 3, 2021
07ec911
[DLMED] change to "_compute()" and "aggregate()"
Nic-Ma Jun 3, 2021
1bb00b0
[DLMED] add compute_list()
Nic-Ma Jun 3, 2021
1e10a51
[DLMED] fix flake8 issue
Nic-Ma Jun 3, 2021
51cc9f1
[DLMED] fix flake8 issue
Nic-Ma Jun 3, 2021
b1ec923
Merge branch 'dev' into 561-provide-metrics-base
Nic-Ma Jun 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/source/metrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Metrics
------
.. autofunction:: compute_froc_score

`Metric`
--------
.. autoclass:: Metric
:members:

`Mean Dice`
-----------
.. autofunction:: compute_meandice
Expand All @@ -21,6 +26,9 @@ Metrics
--------------------------
.. autofunction:: compute_roc_auc

.. autoclass:: ROCAUCMetric
:members:

`Confusion matrix`
------------------
.. autofunction:: get_confusion_matrix
Expand Down
2 changes: 1 addition & 1 deletion monai/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
print_gpu_info,
print_system_info,
)
from .type_definitions import DtypeLike, IndexSelection, KeysCollection, NdarrayTensor
from .type_definitions import DtypeLike, IndexSelection, KeysCollection, NdarrayTensor, TensorOrList
12 changes: 10 additions & 2 deletions monai/config/type_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Collection, Hashable, Iterable, TypeVar, Union
from typing import Collection, Hashable, Iterable, Sequence, TypeVar, Union

import numpy as np
import torch

__all__ = ["KeysCollection", "IndexSelection", "DtypeLike", "NdarrayTensor"]
__all__ = ["KeysCollection", "IndexSelection", "DtypeLike", "NdarrayTensor", "TensorOrList"]

"""Commonly used concepts
This module provides naming and type specifications for commonly used concepts
Expand Down Expand Up @@ -55,6 +55,7 @@
container must be iterable.
"""


DtypeLike = Union[
np.dtype,
type,
Expand All @@ -67,3 +68,10 @@
# Generic type which can represent either a numpy.ndarray or a torch.Tensor
# Unlike Union can create a dependence between parameter(s) / return(s)
NdarrayTensor = TypeVar("NdarrayTensor", np.ndarray, torch.Tensor)


TensorOrList = Union[torch.Tensor, Sequence[torch.Tensor]]
"""TensorOrList

The TensorOrList type is used for defining `batch-first Tensor` or `list of channel-first Tensor`.
"""
16 changes: 8 additions & 8 deletions monai/handlers/confusion_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Callable, Union
from typing import Callable, Union

import torch

from monai.handlers.iteration_metric import IterationMetric
from monai.metrics import ConfusionMatrixMetric, compute_confusion_matrix_metric
from monai.metrics.utils import MetricReduction, do_metric_reduction
from monai.metrics import ConfusionMatrixMetric
from monai.metrics.utils import MetricReduction


class ConfusionMatrix(IterationMetric):
Expand All @@ -30,6 +30,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -47,6 +48,9 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: TP/TN/FP/FN of every image.
default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

See also:
:py:meth:`monai.metrics.confusion_matrix`
Expand All @@ -55,7 +59,7 @@ def __init__(
include_background=include_background,
metric_name=metric_name,
compute_sample=False,
reduction=MetricReduction.NONE,
reduction=reduction,
)
self.metric_name = metric_name
super().__init__(
Expand All @@ -64,7 +68,3 @@ def __init__(
device=device,
save_details=save_details,
)

def _reduce(self, scores) -> Any:
confusion_matrix, _ = do_metric_reduction(scores, MetricReduction.MEAN)
return compute_confusion_matrix_metric(self.metric_name, confusion_matrix)
7 changes: 5 additions & 2 deletions monai/handlers/hausdorff_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -48,15 +49,17 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: hausdorff distance
of every image. default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

"""
super().__init__(output_transform, device=device)
metric_fn = HausdorffDistanceMetric(
include_background=include_background,
distance_metric=distance_metric,
percentile=percentile,
directed=directed,
reduction=MetricReduction.NONE,
reduction=reduction,
)
super().__init__(
metric_fn=metric_fn,
Expand Down
27 changes: 8 additions & 19 deletions monai/handlers/iteration_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
import torch

from monai.handlers.utils import evenly_divisible_all_gather
from monai.metrics import do_metric_reduction
from monai.utils import MetricReduction, exact_version, optional_import
from monai.metrics import Metric
from monai.utils import exact_version, optional_import

idist, _ = optional_import("ignite", "0.4.4", exact_version, "distributed")
Metric, _ = optional_import("ignite.metrics", "0.4.4", exact_version, "Metric")
IgniteMetric, _ = optional_import("ignite.metrics", "0.4.4", exact_version, "Metric")
reinit__is_reduced, _ = optional_import("ignite.metrics.metric", "0.4.4", exact_version, "reinit__is_reduced")
if TYPE_CHECKING:
from ignite.engine import Engine
else:
Engine, _ = optional_import("ignite.engine", "0.4.4", exact_version, "Engine")


class IterationMetric(Metric): # type: ignore[valid-type, misc] # due to optional_import
class IterationMetric(IgniteMetric): # type: ignore[valid-type, misc] # due to optional_import
"""
Class for metrics that should be computed on every iteration and compute final results when epoch completed.
Similar to the `EpochMetric` in ignite:
Expand All @@ -46,7 +46,7 @@ class IterationMetric(Metric): # type: ignore[valid-type, misc] # due to option

def __init__(
self,
metric_fn: Callable,
metric_fn: Metric,
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
Expand Down Expand Up @@ -78,19 +78,7 @@ def update(self, output: Sequence[torch.Tensor]) -> None:

y_pred, y = output

def _compute(y_pred, y):
if isinstance(y_pred, torch.Tensor):
y_pred = y_pred.detach()
if isinstance(y, torch.Tensor):
y = y.detach()
score = self.metric_fn(y_pred, y)
return score[0] if isinstance(score, (tuple, list)) else score

if isinstance(y_pred, (list, tuple)) or isinstance(y, (list, tuple)):
# if y_pred or y is a list of channel-first data, add batch dim and compute metric, then concat the scores
score = torch.cat([_compute(p_.unsqueeze(0), y_.unsqueeze(0)) for p_, y_ in zip(y_pred, y)], dim=0)
else:
score = _compute(y_pred, y)
score = self.metric_fn(y_pred, y)
self._scores.append(score.to(self._device))

def compute(self) -> Any:
Expand All @@ -117,6 +105,7 @@ def compute(self) -> Any:
if idist.get_rank() == 0:
# run compute_fn on zero rank only
result = self._reduce(_scores)
result = result[0] if isinstance(result, (list, tuple)) else result

if ws > 1:
# broadcast result to all processes
Expand All @@ -125,7 +114,7 @@ def compute(self) -> Any:
return result.item() if isinstance(result, torch.Tensor) else result

def _reduce(self, scores) -> Any:
Comment thread
Nic-Ma marked this conversation as resolved.
return do_metric_reduction(scores, MetricReduction.MEAN)[0]
return self.metric_fn.aggregate(scores)

def attach(self, engine: Engine, name: str) -> None:
"""
Expand Down
9 changes: 5 additions & 4 deletions monai/handlers/mean_dice.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -39,14 +40,14 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: mean dice of every image.
default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

See also:
:py:meth:`monai.metrics.meandice.compute_meandice`
"""
metric_fn = DiceMetric(
include_background=include_background,
reduction=MetricReduction.NONE,
)
metric_fn = DiceMetric(include_background=include_background, reduction=reduction)
super().__init__(
metric_fn=metric_fn,
output_transform=output_transform,
Expand Down
33 changes: 20 additions & 13 deletions monai/handlers/regression_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -36,13 +37,14 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: mean squared error of every image.
default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

See also:
:py:class:`monai.metrics.MSEMetric`
"""
metric_fn = MSEMetric(
reduction=MetricReduction.NONE,
)
metric_fn = MSEMetric(reduction=reduction)
super().__init__(
metric_fn=metric_fn,
output_transform=output_transform,
Expand All @@ -61,6 +63,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -69,13 +72,14 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: mean absolute error of every image.
default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

See also:
:py:class:`monai.metrics.MAEMetric`
"""
metric_fn = MAEMetric(
reduction=MetricReduction.NONE,
)
metric_fn = MAEMetric(reduction=reduction)
super().__init__(
metric_fn=metric_fn,
output_transform=output_transform,
Expand All @@ -94,6 +98,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -102,13 +107,14 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: root mean squared error of every image.
default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

See also:
:py:class:`monai.metrics.RMSEMetric`
"""
metric_fn = RMSEMetric(
reduction=MetricReduction.NONE,
)
metric_fn = RMSEMetric(reduction=reduction)
super().__init__(
metric_fn=metric_fn,
output_transform=output_transform,
Expand All @@ -128,6 +134,7 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
save_details: bool = True,
reduction: Union[MetricReduction, str] = MetricReduction.MEAN,
) -> None:
"""

Expand All @@ -138,14 +145,14 @@ def __init__(
device: device specification in case of distributed computation usage.
save_details: whether to save metric computation details per image, for example: PSNR of every image.
default to True, will save to `engine.state.metric_details` dict with the metric name as key.
reduction: {``"none"``, ``"mean"``, ``"sum"``, ``"mean_batch"``, ``"sum_batch"``,
``"mean_channel"``, ``"sum_channel"``}
Define the mode to reduce computation result. Defaults to ``"mean"``.

See also:
:py:class:`monai.metrics.PSNRMetric`
"""
metric_fn = PSNRMetric(
max_val=max_val,
reduction=MetricReduction.NONE,
)
metric_fn = PSNRMetric(max_val=max_val, reduction=reduction)
super().__init__(
metric_fn=metric_fn,
output_transform=output_transform,
Expand Down
19 changes: 9 additions & 10 deletions monai/handlers/roc_auc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Callable, Union
from typing import Any, Callable, Tuple, Union

import torch

from monai.handlers.utils import evenly_divisible_all_gather
from monai.metrics import compute_roc_auc
from monai.metrics import ROCAUCMetric
from monai.utils import Average, exact_version, optional_import

idist, _ = optional_import("ignite", "0.4.4", exact_version, "distributed")
EpochMetric, _ = optional_import("ignite.metrics", "0.4.4", exact_version, "EpochMetric")
reinit__is_reduced, _ = optional_import("ignite.metrics.metric", "0.4.4", exact_version, "reinit__is_reduced")


class ROCAUC(EpochMetric): # type: ignore[valid-type, misc] # due to optional_import
Expand Down Expand Up @@ -56,21 +57,19 @@ def __init__(
output_transform: Callable = lambda x: x,
device: Union[str, torch.device] = "cpu",
) -> None:
def _compute_fn(pred, label):
return compute_roc_auc(
y_pred=pred,
y=label,
average=Average(average),
)

self.metric = ROCAUCMetric(average=Average(average))
self._is_reduced: bool = False
super().__init__(
compute_fn=_compute_fn,
compute_fn=lambda p, y: self.metric.aggregate(data=(p, y)),
output_transform=output_transform,
check_compute_fn=False,
device=device,
)

@reinit__is_reduced
def update(self, output: Tuple[torch.Tensor, torch.Tensor]) -> None:
super().update(output=self.metric(output[0], output[1]))

def compute(self) -> Any:
_prediction_tensor = torch.cat(self._predictions, dim=0)
_target_tensor = torch.cat(self._targets, dim=0)
Expand Down
Loading