@@ -22,6 +22,7 @@ class TestComputeAbsoluteVolumeDifference(unittest.TestCase):
2222 """Tests for the standalone compute_absolute_volume_difference function."""
2323
2424 def test_perfect_prediction_returns_zero (self ):
25+ """Identical prediction and ground truth should yield AVD of zero for all classes."""
2526 # identical masks → AVD = 0 for every class
2627 y = torch .zeros (2 , 3 , 4 , 4 )
2728 y [:, 1 , :2 , :2 ] = 1.0
@@ -31,6 +32,7 @@ def test_perfect_prediction_returns_zero(self):
3132 self .assertTrue (torch .all (result == 0.0 ))
3233
3334 def test_known_volume_difference (self ):
35+ """AVD should equal the absolute difference in foreground voxel counts between prediction and GT."""
3436 # batch=1, 2 classes (background + foreground), 1D spatial of length 10
3537 y_pred = torch .zeros (1 , 2 , 10 )
3638 y_true = torch .zeros (1 , 2 , 10 )
@@ -43,6 +45,7 @@ def test_known_volume_difference(self):
4345 self .assertAlmostEqual (result [0 , 1 ].item (), 3.0 )
4446
4547 def test_ignore_background (self ):
48+ """Setting include_background=False should strip the first channel and reduce output shape accordingly."""
4649 y_pred = torch .zeros (2 , 3 , 8 , 8 )
4750 y_true = torch .zeros (2 , 3 , 8 , 8 )
4851 y_pred [:, 1 , :3 , :3 ] = 1.0
@@ -52,6 +55,7 @@ def test_ignore_background(self):
5255 self .assertEqual (result .shape , torch .Size ([2 , 2 ]))
5356
5457 def test_ignore_empty_sets_nan (self ):
58+ """Channels with no ground-truth foreground voxels should be NaN when ignore_empty=True."""
5559 # channel 1 has no GT voxels → should be NaN when ignore_empty=True
5660 y_pred = torch .zeros (1 , 2 , 6 )
5761 y_true = torch .zeros (1 , 2 , 6 )
@@ -63,6 +67,7 @@ def test_ignore_empty_sets_nan(self):
6367 self .assertTrue (torch .isnan (result [0 , 1 ]))
6468
6569 def test_ignore_empty_false_returns_pred_volume (self ):
70+ """With ignore_empty=False and empty GT, AVD should equal the predicted volume."""
6671 # when GT is all zero and ignore_empty=False, AVD = |V_pred - 0| = V_pred
6772 y_pred = torch .zeros (1 , 2 , 6 )
6873 y_true = torch .zeros (1 , 2 , 6 )
@@ -71,20 +76,23 @@ def test_ignore_empty_false_returns_pred_volume(self):
7176 self .assertAlmostEqual (result [0 , 1 ].item (), 5.0 )
7277
7378 def test_shape_mismatch_raises (self ):
79+ """Mismatched y_pred and y shapes should raise a ValueError."""
7480 with self .assertRaises (ValueError ):
7581 compute_absolute_volume_difference (
7682 y_pred = torch .zeros (2 , 3 , 8 , 8 ),
7783 y = torch .zeros (2 , 3 , 4 , 4 ),
7884 )
7985
8086 def test_too_few_dims_raises (self ):
87+ """Input tensors with fewer than 3 dimensions should raise a ValueError."""
8188 with self .assertRaises (ValueError ):
8289 compute_absolute_volume_difference (
8390 y_pred = torch .zeros (2 , 3 ),
8491 y = torch .zeros (2 , 3 ),
8592 )
8693
8794 def test_3d_volumes (self ):
95+ """AVD should correctly count voxel differences in 3-D spatial inputs."""
8896 # 3-D spatial (D, H, W)
8997 y_pred = torch .zeros (1 , 2 , 8 , 8 , 8 )
9098 y_true = torch .zeros (1 , 2 , 8 , 8 , 8 )
@@ -94,6 +102,7 @@ def test_3d_volumes(self):
94102 self .assertAlmostEqual (result [0 , 1 ].item (), 37.0 )
95103
96104 def test_output_shape_multi_class (self ):
105+ """Output shape should be [batch_size, num_classes] for multi-class inputs."""
97106 y = torch .randint (0 , 2 , (4 , 5 , 16 , 16 )).float ()
98107 result = compute_absolute_volume_difference (y_pred = y , y = y , ignore_empty = False )
99108 self .assertEqual (result .shape , torch .Size ([4 , 5 ]))
@@ -103,6 +112,7 @@ class TestAbsoluteVolumeDifferenceMetric(unittest.TestCase):
103112 """Tests for the AbsoluteVolumeDifferenceMetric class (cumulative interface)."""
104113
105114 def test_aggregate_mean (self ):
115+ """Mean reduction over accumulated batches should return the correct per-class AVD."""
106116 y_pred = torch .zeros (2 , 2 , 8 , 8 )
107117 y_true = torch .zeros (2 , 2 , 8 , 8 )
108118 y_pred [:, 1 , :6 , :6 ] = 1.0 # 36 voxels per batch item
@@ -115,6 +125,7 @@ def test_aggregate_mean(self):
115125 metric .reset ()
116126
117127 def test_aggregate_returns_not_nans_when_requested (self ):
128+ """When get_not_nans=True, aggregate should return a (metric, not_nans) tuple."""
118129 y_pred = torch .zeros (2 , 2 , 4 , 4 )
119130 y_true = torch .zeros (2 , 2 , 4 , 4 )
120131 y_pred [:, 1 , :2 , :2 ] = 1.0
@@ -127,6 +138,7 @@ def test_aggregate_returns_not_nans_when_requested(self):
127138 metric .reset ()
128139
129140 def test_cumulative_accumulation (self ):
141+ """Multiple forward calls before aggregate should use all accumulated data correctly."""
130142 # calling the metric twice and aggregating should use all accumulated data
131143 metric = AbsoluteVolumeDifferenceMetric (include_background = False , reduction = "mean" , ignore_empty = False )
132144 for _ in range (3 ):
@@ -140,6 +152,7 @@ def test_cumulative_accumulation(self):
140152 metric .reset ()
141153
142154 def test_reset_clears_buffer (self ):
155+ """Calling reset() should clear the buffer so a subsequent aggregate() raises."""
143156 metric = AbsoluteVolumeDifferenceMetric (ignore_empty = False )
144157 y = torch .zeros (1 , 2 , 4 )
145158 y [0 , 1 , :2 ] = 1.0
@@ -150,6 +163,7 @@ def test_reset_clears_buffer(self):
150163 metric .aggregate ()
151164
152165 def test_imported_from_top_level (self ):
166+ """AbsoluteVolumeDifferenceMetric should be importable from the monai.metrics top-level namespace."""
153167 # ensure the class is accessible from monai.metrics top-level
154168 from monai .metrics import AbsoluteVolumeDifferenceMetric as _AVD
155169
0 commit comments