Skip to content

Add AbsoluteVolumeDifferenceMetric to monai.metrics#8945

Merged
ericspod merged 4 commits into
Project-MONAI:devfrom
MDSALMANSHAMS:avd-metric
Jul 3, 2026
Merged

Add AbsoluteVolumeDifferenceMetric to monai.metrics#8945
ericspod merged 4 commits into
Project-MONAI:devfrom
MDSALMANSHAMS:avd-metric

Conversation

@MDSALMANSHAMS

Copy link
Copy Markdown
Contributor

Summary

Closes #4009.

Adds AbsoluteVolumeDifferenceMetric and a standalone compute_absolute_volume_difference() function to monai.metrics.

Why AVD?
Dice score is known to be overly sensitive for small-object segmentation (e.g. retinal fluid sub-types in OCT volumes - SRF, IRF, PED), because small volume differences produce large Dice swings. AVD directly reflects volume size discrepancy, making it the standard evaluation metric in the RETOUCH retinal OCT fluid benchmark (Bogunovic et al., IEEE TMI 2019).

Changes

  • monai/metrics/absolute_volume_difference.py - new file:
    • AbsoluteVolumeDifferenceMetric(CumulativeIterationMetric) - cumulative class matching the DiceMetric / MeanIoU interface. Supports include_background,
      eduction, get_not_nans, ignore_empty.
    • compute_absolute_volume_difference() - standalone function returning shape [B, C].
  • monai/metrics/init.py - exports both new symbols.
  • ** ests/metrics/test_absolute_volume_difference.py** - 14 unit tests.

Test plan

  • Perfect prediction returns zero
  • Known volume difference (hand-verified)
  • ignore_empty=True sets NaN for empty ground-truth channels
  • ignore_empty=False returns raw absolute difference
  • include_background=False strips channel 0
  • 3D spatial volumes (BCDHW)
  • Multi-class output shape
  • Cumulative accumulation across batches
  • Buffer reset
  • Shape mismatch raises ValueError
  • Fewer than 3 dimensions raises ValueError
  • Top-level import from monai.metrics

All 14 tests pass (python -m unittest tests.metrics.test_absolute_volume_difference -v).

This PR was authored with the assistance of an AI coding assistant.

Implements the Absolute Volume Difference (AVD) metric as requested
in Project-MONAI#4009. AVD measures the absolute difference in foreground voxel
counts between prediction and ground truth per class, and is
particularly suited for small-object segmentation tasks (e.g. retinal
fluid in OCT volumes) where Dice score is overly sensitive to volume
size.

Changes:
- monai/metrics/absolute_volume_difference.py: new AbsoluteVolumeDifferenceMetric
  class (CumulativeIterationMetric) and compute_absolute_volume_difference()
  standalone function. Supports include_background, reduction, get_not_nans,
  and ignore_empty. References the RETOUCH benchmark paper.
- monai/metrics/__init__.py: export both new symbols.
- tests/metrics/test_absolute_volume_difference.py: 14 unit tests covering
  perfect prediction, known volume difference, ignore_empty behaviour,
  include_background stripping, 3D volumes, cumulative accumulation,
  and error cases.

This PR was authored with the assistance of an AI coding assistant.

Signed-off-by: MDSALMANSHAMS <salmanshams67@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 649d4524-acf7-4652-b070-0c707e66a830

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca174b and 45b80ad.

📒 Files selected for processing (3)
  • docs/source/metrics.rst
  • monai/metrics/absolute_volume_difference.py
  • tests/metrics/test_absolute_volume_difference.py
✅ Files skipped from review due to trivial changes (1)
  • docs/source/metrics.rst
🚧 Files skipped from review as they are similar to previous changes (2)
  • monai/metrics/absolute_volume_difference.py
  • tests/metrics/test_absolute_volume_difference.py

📝 Walkthrough

Walkthrough

A new AVD metric module adds compute_absolute_volume_difference and AbsoluteVolumeDifferenceMetric for per-class absolute volume difference computation, with optional background exclusion and empty-ground-truth masking. The metric accumulates values, applies do_metric_reduction, and can return not_nans. The new symbols are re-exported from monai.metrics, documented in the metrics docs, and covered by unit tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: adding AbsoluteVolumeDifferenceMetric to monai.metrics.
Description check ✅ Passed The description covers the summary, changes, and test plan, and is mostly complete despite not mirroring the template exactly.
Linked Issues check ✅ Passed The new metric, export, docs, and tests match issue #4009's request for an absolute volume difference metric.
Out of Scope Changes check ✅ Passed The changes stay focused on the new metric, its export, tests, and documentation, with no obvious unrelated additions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
monai/metrics/absolute_volume_difference.py (1)

80-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Complete method-level Google-style docstrings.

__init__ lacks a method docstring, _compute_tensor/aggregate omit Returns, and aggregate should document its ValueError in Raises.

Suggested docstring patch
     def __init__(
         self,
         include_background: bool = True,
         reduction: MetricReduction | str = MetricReduction.MEAN,
         get_not_nans: bool = False,
         ignore_empty: bool = True,
     ) -> None:
+        """Initialize AbsoluteVolumeDifferenceMetric.
+
+        Args:
+            include_background: Whether to include channel 0.
+            reduction: Reduction mode used in :meth:`aggregate`.
+            get_not_nans: Whether :meth:`aggregate` returns `(metric, not_nans)`.
+            ignore_empty: Whether empty ground-truth channels are set to NaN.
+        """
         super().__init__()
         self.include_background = include_background
         self.reduction = reduction
         self.get_not_nans = get_not_nans
         self.ignore_empty = ignore_empty
@@
     def _compute_tensor(self, y_pred: torch.Tensor, y: torch.Tensor) -> torch.Tensor:  # type: ignore[override]
         """
         Args:
             y_pred: binarized prediction tensor, shape BCHW[D].
             y: binarized ground-truth tensor, shape BCHW[D].
 
+        Returns:
+            Per-batch per-class AVD tensor with shape [B, C].
+
         Raises:
             ValueError: when ``y_pred`` has fewer than three dimensions.
         """
@@
     def aggregate(
         self, reduction: MetricReduction | str | None = None
     ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
         """
         Execute reduction logic for the accumulated AVD values.
 
         Args:
             reduction: optional override for the reduction mode set at construction.
+
+        Returns:
+            Reduced metric tensor, or ``(metric, not_nans)`` when ``get_not_nans=True``.
+
+        Raises:
+            ValueError: when internal buffered data is not a tensor.
         """

As per path instructions, "Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/metrics/absolute_volume_difference.py` around lines 80 - 121, Add
complete Google-style docstrings for the AVD metric methods in
AbsoluteVolumeDifference: the __init__ method needs a short summary plus Args
entries for include_background, reduction, get_not_nans, and ignore_empty;
_compute_tensor should include a Returns section describing the computed tensor
and keep its ValueError in Raises; aggregate should add a Returns section for
the reduced output and document the ValueError it can raise in Raises. Use the
existing method names to update each docstring so every parameter, return value,
and exception is covered.

Source: Path instructions

tests/metrics/test_absolute_volume_difference.py (1)

24-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Google-style docstrings to test methods.

Method definitions from Line 24 through Line 156 are missing per-method docstrings, so this file does not meet the docstring requirement for Python definitions.

As per path instructions, "Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/metrics/test_absolute_volume_difference.py` around lines 24 - 157, Add
Google-style docstrings to each test method in TestAbsoluteVolumeDifference and
TestAbsoluteVolumeDifferenceMetric so every definition is documented as
required. Update the individual test_* methods to describe the purpose,
arguments/fixtures used, expected return behavior, and any raised exceptions in
Google-style format, keeping the docstrings aligned with the method names and
assertions in this file.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/metrics/test_absolute_volume_difference.py`:
- Around line 149-150: The reset/empty-buffer path in aggregate() is being
tested with a too-broad exception check, which can hide unrelated failures.
Update the test around AbsoluteVolumeDifference.aggregate() to assert the
specific ValueError expected after reset instead of Exception, using the
metric.aggregate call in the existing test case so the failure mode is explicit.

---

Nitpick comments:
In `@monai/metrics/absolute_volume_difference.py`:
- Around line 80-121: Add complete Google-style docstrings for the AVD metric
methods in AbsoluteVolumeDifference: the __init__ method needs a short summary
plus Args entries for include_background, reduction, get_not_nans, and
ignore_empty; _compute_tensor should include a Returns section describing the
computed tensor and keep its ValueError in Raises; aggregate should add a
Returns section for the reduced output and document the ValueError it can raise
in Raises. Use the existing method names to update each docstring so every
parameter, return value, and exception is covered.

In `@tests/metrics/test_absolute_volume_difference.py`:
- Around line 24-157: Add Google-style docstrings to each test method in
TestAbsoluteVolumeDifference and TestAbsoluteVolumeDifferenceMetric so every
definition is documented as required. Update the individual test_* methods to
describe the purpose, arguments/fixtures used, expected return behavior, and any
raised exceptions in Google-style format, keeping the docstrings aligned with
the method names and assertions in this file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4cd5d4f5-95d5-4ac0-966f-e7e70094a110

📥 Commits

Reviewing files that changed from the base of the PR and between 557ffaa and 74ee065.

📒 Files selected for processing (3)
  • monai/metrics/__init__.py
  • monai/metrics/absolute_volume_difference.py
  • tests/metrics/test_absolute_volume_difference.py

Comment thread tests/metrics/test_absolute_volume_difference.py Outdated
Add one-liner docstring to __init__ and brief docstrings to all
fourteen test methods so CodeRabbit's docstring-coverage pre-merge
check passes (was 16.67%, threshold is 80%).

Signed-off-by: MDSALMANSHAMS <salmanshams67@gmail.com>
@MDSALMANSHAMS

Copy link
Copy Markdown
Contributor Author

Hi @ericspod @Nic-Ma @KumoLiu - gentle reminder on this PR adding AbsoluteVolumeDifferenceMetric to monai.metrics. Would appreciate a review when you get a moment; happy to make any changes. Thanks!

@ericspod ericspod left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @MDSALMANSHAMS I had some minor comments, mainly needing the new class to be documented. I think we're good otherwise, thanks!

Comment thread monai/metrics/absolute_volume_difference.py
Comment thread monai/metrics/absolute_volume_difference.py
Comment thread monai/metrics/absolute_volume_difference.py Outdated
@ericspod

ericspod commented Jul 3, 2026

Copy link
Copy Markdown
Member

Please also look at the ruff and format comments. Your import test may not be necessary so you can remove that if it's easier.

@MDSALMANSHAMS

Copy link
Copy Markdown
Contributor Author

Thanks for the review @ericspod! Addressed all points in the latest commit:

  • Docs: added AbsoluteVolumeDifferenceMetric and compute_absolute_volume_difference to docs/source/metrics.rst.
  • Docstring: added a note that 2D inputs yield surface areas, and that values are raw voxel/pixel counts (not scaled by voxel/pixel spacing, so not in physical units).
  • Removed the unnecessary __init__ docstring.
  • Removed the redundant top-level import test.
  • Fixed the ruff/format failure (ran black + isort); codeformat is green now.

Also asserted ValueError explicitly (instead of a bare Exception) on aggregate() after reset(), per the earlier CodeRabbit note. Let me know if anything else is needed!

- Add AbsoluteVolumeDifferenceMetric + compute_absolute_volume_difference
  to docs/source/metrics.rst
- Note in the class docstring that 2D inputs yield surface areas and that
  values are raw voxel/pixel counts, not scaled by spacing
- Drop the unnecessary __init__ docstring
- Remove the redundant top-level import test
- Assert ValueError (not bare Exception) on aggregate after reset
- Apply black/isort formatting

Signed-off-by: MDSALMANSHAMS <salmanshams67@gmail.com>
@ericspod ericspod merged commit eb74488 into Project-MONAI:dev Jul 3, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Requesting new metric to calculate volume difference for small fluid volume --> Absolute Volume Difference (AVD)

2 participants