Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions monai/metrics/confusion_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ class range, ignored regions are inferred from spatial locations where all label
ValueError: when `y_pred` and `y` have different shapes.
"""

# The true positive/negative counts below test `y_pred + y`; for bool tensors that sum is a logical OR,
# which never equals 2, so every true positive would be lost. Count on float values instead.
if y_pred.dtype == torch.bool:
y_pred = y_pred.float()
if y.dtype == torch.bool:
y = y.float()

original_y = y
if not include_background:
y_pred, y = ignore_background(y_pred=y_pred, y=y)
Expand Down
7 changes: 7 additions & 0 deletions monai/metrics/f_beta_score.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ def aggregate(


def get_f_beta_score(y_pred: torch.Tensor, y: torch.Tensor, include_background: bool = True) -> torch.Tensor:
# The true positive/negative counts below test `y_pred + y`; for bool tensors that sum is a logical OR,
# which never equals 2, so every true positive would be lost. Count on float values instead.
if y_pred.dtype == torch.bool:
y_pred = y_pred.float()
if y.dtype == torch.bool:
y = y.float()

if not include_background:
y_pred, y = ignore_background(y_pred=y_pred, y=y)

Expand Down
12 changes: 12 additions & 0 deletions tests/metrics/test_compute_confusion_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,18 @@ def test_precision(self, input_data, expected_value):
assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
np.testing.assert_equal(result.device, input_data["y_pred"].device)

def test_bool_inputs_match_float_inputs(self):
"""Bool masks (e.g. ``pred > 0.5``) must give the same confusion matrix as their float equivalents."""
generator = torch.Generator().manual_seed(0)
y_pred = torch.rand(2, 3, 8, 8, generator=generator) > 0.5
y = torch.rand(2, 3, 8, 8, generator=generator) > 0.5

for include_background in (True, False):
expected = get_confusion_matrix(y_pred.float(), y.float(), include_background=include_background)
result = get_confusion_matrix(y_pred, y, include_background=include_background)
assert_allclose(result, expected, type_test=False)
self.assertTrue(bool((result[..., 0] > 0).all())) # true positives are counted


if __name__ == "__main__":
unittest.main()
17 changes: 17 additions & 0 deletions tests/metrics/test_compute_f_beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from parameterized import parameterized

from monai.metrics import FBetaScore
from monai.metrics.f_beta_score import get_f_beta_score
from tests.test_utils import assert_allclose

_device = "cuda:0" if torch.cuda.is_available() else "cpu"
Expand Down Expand Up @@ -75,6 +76,22 @@ def test_prediction_and_result_have_different_shape(self):
with self.assertRaises(ValueError):
metric(y_pred=torch.Tensor([[1, 1, 1], [1, 1, 1]]), y=torch.Tensor([1, 1, 1]))

def test_bool_inputs_match_float_inputs(self):
"""Bool masks (e.g. ``pred > 0.5``) must give the same counts and score as their float equivalents."""
generator = torch.Generator().manual_seed(0)
y_pred = torch.rand(2, 3, 8, 8, generator=generator) > 0.5
y = torch.rand(2, 3, 8, 8, generator=generator) > 0.5

for include_background in (True, False):
expected = get_f_beta_score(y_pred.float(), y.float(), include_background=include_background)
result = get_f_beta_score(y_pred, y, include_background=include_background)
assert_allclose(result, expected, type_test=False)
self.assertTrue(bool((result[..., 0] > 0).all())) # true positives are counted

metric = FBetaScore()
metric(y_pred=y, y=y)
assert_allclose(metric.aggregate()[0], torch.Tensor([1.0]), atol=1e-7, rtol=1e-7)


if __name__ == "__main__":
unittest.main()
Loading