From be2e6010d511799efc2a7f101ea6220bc714d6b1 Mon Sep 17 00:00:00 2001 From: Raphael Malikian Date: Fri, 19 Jun 2026 01:04:04 -0700 Subject: [PATCH 1/3] fix: add missing stacklevel=2 to warnings.warn() in metrics/ Add stacklevel=2 to 12 warnings.warn() calls across 6 files in monai/metrics/ that were missing it: - cumulative_average.py: non-finite input warning - average_precision.py: all-same-value and invalid-value warnings - utils.py: ground truth/prediction all-zero, binarized tensor, and Voronoi CPU warnings - active_learning_metrics.py: spatial map and reduction warnings - confusion_matrix.py: compute_sample warning - rocauc.py: all-same-value and invalid-value warnings Without stacklevel=2, these warnings point to library internals instead of the user's calling code, making them unhelpful for debugging. This follows the same pattern as PR #8930 for losses/. Fixes #8931 Signed-off-by: Raphael Malikian --- monai/metrics/active_learning_metrics.py | 8 ++++++-- monai/metrics/average_precision.py | 8 ++++++-- monai/metrics/confusion_matrix.py | 4 +++- monai/metrics/cumulative_average.py | 4 +++- monai/metrics/rocauc.py | 8 ++++++-- monai/metrics/utils.py | 12 +++++++++--- 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/monai/metrics/active_learning_metrics.py b/monai/metrics/active_learning_metrics.py index 5c51d262ed..b872b555c2 100644 --- a/monai/metrics/active_learning_metrics.py +++ b/monai/metrics/active_learning_metrics.py @@ -137,7 +137,9 @@ def compute_variance( n_len = len(y_pred.shape) if n_len < 4 and spatial_map: - warnings.warn("Spatial map requires a 2D/3D image with N-repeats and C-channels") + warnings.warn("Spatial map requires a 2D/3D image with N-repeats and C-channels", + stacklevel=2, + ) return None # Create new shape list @@ -190,7 +192,9 @@ def label_quality_score( n_len = len(y_pred.shape) if n_len < 4 and scalar_reduction == "none": - warnings.warn("Reduction set to None, Spatial map return requires a 2D/3D image of B-Batchsize and C-channels") + warnings.warn("Reduction set to None, Spatial map return requires a 2D/3D image of B-Batchsize and C-channels", + stacklevel=2, + ) return None abs_diff_map = torch.abs(y_pred - y) diff --git a/monai/metrics/average_precision.py b/monai/metrics/average_precision.py index 7dd277bde6..ff12a06c39 100644 --- a/monai/metrics/average_precision.py +++ b/monai/metrics/average_precision.py @@ -88,10 +88,14 @@ def _calculate(y_pred: torch.Tensor, y: torch.Tensor) -> float: raise AssertionError("y and y_pred must be 1 dimension data with same length.") y_unique = y.unique() if len(y_unique) == 1: - warnings.warn(f"y values can not be all {y_unique.item()}, skip AP computation and return `Nan`.") + warnings.warn(f"y values can not be all {y_unique.item()}, skip AP computation and return `Nan`.", + stacklevel=2, + ) return float("nan") if not y_unique.equal(torch.tensor([0, 1], dtype=y.dtype, device=y.device)): - warnings.warn(f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AP computation and return `Nan`.") + warnings.warn(f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AP computation and return `Nan`.", + stacklevel=2, + ) return float("nan") n = len(y) diff --git a/monai/metrics/confusion_matrix.py b/monai/metrics/confusion_matrix.py index 26ec823081..2d625710e6 100644 --- a/monai/metrics/confusion_matrix.py +++ b/monai/metrics/confusion_matrix.py @@ -93,7 +93,9 @@ def _compute_tensor(self, y_pred: torch.Tensor, y: torch.Tensor) -> torch.Tensor raise ValueError("y_pred should have at least two dimensions.") if dims == 2 or (dims == 3 and y_pred.shape[-1] == 1): if self.compute_sample: - warnings.warn("As for classification task, compute_sample should be False.") + warnings.warn("As for classification task, compute_sample should be False.", + stacklevel=2, + ) self.compute_sample = False return get_confusion_matrix(y_pred=y_pred, y=y, include_background=self.include_background) diff --git a/monai/metrics/cumulative_average.py b/monai/metrics/cumulative_average.py index dccf7b094b..d415da7afd 100644 --- a/monai/metrics/cumulative_average.py +++ b/monai/metrics/cumulative_average.py @@ -154,7 +154,9 @@ def append(self, val: Any, count: Any | None = 1) -> None: # account for possible non-finite numbers in val and replace them with 0s nfin = torch.isfinite(val) if not torch.all(nfin): - warnings.warn(f"non-finite inputs received: val: {val}, count: {count}") + warnings.warn(f"non-finite inputs received: val: {val}, count: {count}", + stacklevel=2, + ) count = torch.where(nfin, count, torch.zeros_like(count)) val = torch.where(nfin, val, torch.zeros_like(val)) diff --git a/monai/metrics/rocauc.py b/monai/metrics/rocauc.py index 72f0b3730c..c8515b3ee5 100644 --- a/monai/metrics/rocauc.py +++ b/monai/metrics/rocauc.py @@ -77,10 +77,14 @@ def _calculate(y_pred: torch.Tensor, y: torch.Tensor) -> float: raise AssertionError("y and y_pred must be 1 dimension data with same length.") y_unique = y.unique() if len(y_unique) == 1: - warnings.warn(f"y values can not be all {y_unique.item()}, skip AUC computation and return `Nan`.") + warnings.warn(f"y values can not be all {y_unique.item()}, skip AUC computation and return `Nan`.", + stacklevel=2, + ) return float("nan") if not y_unique.equal(torch.tensor([0, 1], dtype=y.dtype, device=y.device)): - warnings.warn(f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AUC computation and return `Nan`.") + warnings.warn(f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AUC computation and return `Nan`.", + stacklevel=2, + ) return float("nan") n = len(y) diff --git a/monai/metrics/utils.py b/monai/metrics/utils.py index 3070764e06..f20629a54f 100644 --- a/monai/metrics/utils.py +++ b/monai/metrics/utils.py @@ -341,12 +341,14 @@ def get_edge_surface_distance( if not edges_gt.any(): warnings.warn( f"the ground truth of class {class_index if class_index != -1 else 'Unknown'} is all 0," - " this may result in nan/inf distance." + " this may result in nan/inf distance.", + stacklevel=2, ) if not edges_pred.any(): warnings.warn( f"the prediction of class {class_index if class_index != -1 else 'Unknown'} is all 0," - " this may result in nan/inf distance." + " this may result in nan/inf distance.", + stacklevel=2, ) distances: tuple[torch.Tensor, torch.Tensor] | tuple[torch.Tensor] if symmetric: @@ -375,7 +377,9 @@ def is_binary_tensor(input: torch.Tensor, name: str) -> None: if not isinstance(input, torch.Tensor): raise ValueError(f"{name} must be of type PyTorch Tensor.") if not torch.all(input.byte() == input) or input.max() > 1 or input.min() < 0: - warnings.warn(f"{name} should be a binarized tensor.") + warnings.warn(f"{name} should be a binarized tensor.", + stacklevel=2, + ) def remap_instance_id(pred: torch.Tensor, by_size: bool = False) -> torch.Tensor: @@ -511,6 +515,8 @@ def compute_voronoi_regions_fast(labels: np.ndarray | torch.Tensor) -> torch.Ten warnings.warn( "Voronoi computation is running on CPU. " "To accelerate, move the input tensor to GPU and ensure 'cupy' with 'cupyx.scipy.ndimage' is installed." +, + stacklevel=2, ) x = labels.cpu().numpy() else: From a4a54c9f02c0ead36c6046432739555be6ce44f4 Mon Sep 17 00:00:00 2001 From: Raphael Malikian Date: Fri, 19 Jun 2026 02:04:01 -0700 Subject: [PATCH 2/3] fix: remove stray comma on separate line in metrics/utils.py Address CodeRabbit review: fix formatting of warnings.warn() call where comma was on a separate line before stacklevel parameter. Signed-off-by: Raphael Malikian --- monai/metrics/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monai/metrics/utils.py b/monai/metrics/utils.py index f20629a54f..0d635f2ff5 100644 --- a/monai/metrics/utils.py +++ b/monai/metrics/utils.py @@ -514,8 +514,7 @@ def compute_voronoi_regions_fast(labels: np.ndarray | torch.Tensor) -> torch.Ten if isinstance(labels, torch.Tensor): warnings.warn( "Voronoi computation is running on CPU. " - "To accelerate, move the input tensor to GPU and ensure 'cupy' with 'cupyx.scipy.ndimage' is installed." -, + "To accelerate, move the input tensor to GPU and ensure 'cupy' with 'cupyx.scipy.ndimage' is installed.", stacklevel=2, ) x = labels.cpu().numpy() From 726e9f033b3097790bfc3410ef71d5b79ac6d524 Mon Sep 17 00:00:00 2001 From: rtmalikian Date: Fri, 19 Jun 2026 09:45:18 -0700 Subject: [PATCH 3/3] fix: run black formatting on metrics/ files Signed-off-by: rtmalikian --- monai/metrics/active_learning_metrics.py | 7 +++---- monai/metrics/average_precision.py | 8 +++----- monai/metrics/confusion_matrix.py | 4 +--- monai/metrics/cumulative_average.py | 4 +--- monai/metrics/rocauc.py | 8 ++++---- monai/metrics/utils.py | 4 +--- 6 files changed, 13 insertions(+), 22 deletions(-) diff --git a/monai/metrics/active_learning_metrics.py b/monai/metrics/active_learning_metrics.py index b872b555c2..a31d2fc150 100644 --- a/monai/metrics/active_learning_metrics.py +++ b/monai/metrics/active_learning_metrics.py @@ -137,9 +137,7 @@ def compute_variance( n_len = len(y_pred.shape) if n_len < 4 and spatial_map: - warnings.warn("Spatial map requires a 2D/3D image with N-repeats and C-channels", - stacklevel=2, - ) + warnings.warn("Spatial map requires a 2D/3D image with N-repeats and C-channels", stacklevel=2) return None # Create new shape list @@ -192,7 +190,8 @@ def label_quality_score( n_len = len(y_pred.shape) if n_len < 4 and scalar_reduction == "none": - warnings.warn("Reduction set to None, Spatial map return requires a 2D/3D image of B-Batchsize and C-channels", + warnings.warn( + "Reduction set to None, Spatial map return requires a 2D/3D image of B-Batchsize and C-channels", stacklevel=2, ) return None diff --git a/monai/metrics/average_precision.py b/monai/metrics/average_precision.py index ff12a06c39..3bb7b0bcb4 100644 --- a/monai/metrics/average_precision.py +++ b/monai/metrics/average_precision.py @@ -88,13 +88,11 @@ def _calculate(y_pred: torch.Tensor, y: torch.Tensor) -> float: raise AssertionError("y and y_pred must be 1 dimension data with same length.") y_unique = y.unique() if len(y_unique) == 1: - warnings.warn(f"y values can not be all {y_unique.item()}, skip AP computation and return `Nan`.", - stacklevel=2, - ) + warnings.warn(f"y values can not be all {y_unique.item()}, skip AP computation and return `Nan`.", stacklevel=2) return float("nan") if not y_unique.equal(torch.tensor([0, 1], dtype=y.dtype, device=y.device)): - warnings.warn(f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AP computation and return `Nan`.", - stacklevel=2, + warnings.warn( + f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AP computation and return `Nan`.", stacklevel=2 ) return float("nan") diff --git a/monai/metrics/confusion_matrix.py b/monai/metrics/confusion_matrix.py index 2d625710e6..3152b023c0 100644 --- a/monai/metrics/confusion_matrix.py +++ b/monai/metrics/confusion_matrix.py @@ -93,9 +93,7 @@ def _compute_tensor(self, y_pred: torch.Tensor, y: torch.Tensor) -> torch.Tensor raise ValueError("y_pred should have at least two dimensions.") if dims == 2 or (dims == 3 and y_pred.shape[-1] == 1): if self.compute_sample: - warnings.warn("As for classification task, compute_sample should be False.", - stacklevel=2, - ) + warnings.warn("As for classification task, compute_sample should be False.", stacklevel=2) self.compute_sample = False return get_confusion_matrix(y_pred=y_pred, y=y, include_background=self.include_background) diff --git a/monai/metrics/cumulative_average.py b/monai/metrics/cumulative_average.py index d415da7afd..8fbc470058 100644 --- a/monai/metrics/cumulative_average.py +++ b/monai/metrics/cumulative_average.py @@ -154,9 +154,7 @@ def append(self, val: Any, count: Any | None = 1) -> None: # account for possible non-finite numbers in val and replace them with 0s nfin = torch.isfinite(val) if not torch.all(nfin): - warnings.warn(f"non-finite inputs received: val: {val}, count: {count}", - stacklevel=2, - ) + warnings.warn(f"non-finite inputs received: val: {val}, count: {count}", stacklevel=2) count = torch.where(nfin, count, torch.zeros_like(count)) val = torch.where(nfin, val, torch.zeros_like(val)) diff --git a/monai/metrics/rocauc.py b/monai/metrics/rocauc.py index c8515b3ee5..6b4ac368da 100644 --- a/monai/metrics/rocauc.py +++ b/monai/metrics/rocauc.py @@ -77,13 +77,13 @@ def _calculate(y_pred: torch.Tensor, y: torch.Tensor) -> float: raise AssertionError("y and y_pred must be 1 dimension data with same length.") y_unique = y.unique() if len(y_unique) == 1: - warnings.warn(f"y values can not be all {y_unique.item()}, skip AUC computation and return `Nan`.", - stacklevel=2, + warnings.warn( + f"y values can not be all {y_unique.item()}, skip AUC computation and return `Nan`.", stacklevel=2 ) return float("nan") if not y_unique.equal(torch.tensor([0, 1], dtype=y.dtype, device=y.device)): - warnings.warn(f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AUC computation and return `Nan`.", - stacklevel=2, + warnings.warn( + f"y values must be 0 or 1, but in {y_unique.tolist()}, skip AUC computation and return `Nan`.", stacklevel=2 ) return float("nan") diff --git a/monai/metrics/utils.py b/monai/metrics/utils.py index 0d635f2ff5..bee0d7bf21 100644 --- a/monai/metrics/utils.py +++ b/monai/metrics/utils.py @@ -377,9 +377,7 @@ def is_binary_tensor(input: torch.Tensor, name: str) -> None: if not isinstance(input, torch.Tensor): raise ValueError(f"{name} must be of type PyTorch Tensor.") if not torch.all(input.byte() == input) or input.max() > 1 or input.min() < 0: - warnings.warn(f"{name} should be a binarized tensor.", - stacklevel=2, - ) + warnings.warn(f"{name} should be a binarized tensor.", stacklevel=2) def remap_instance_id(pred: torch.Tensor, by_size: bool = False) -> torch.Tensor: