139139
140140
141141class TestComputeFpTp (unittest .TestCase ):
142-
143142 @parameterized .expand (
144143 [TEST_CASE_1 , TEST_CASE_2 , TEST_CASE_3 , TEST_CASE_EXCLUDE_ABSENT , TEST_CASE_EXCLUDE_DUPLICATE ]
145144 )
@@ -151,7 +150,6 @@ def test_value(self, input_data, expected_fp, expected_tp, expected_num):
151150
152151
153152class TestComputeFpTpNd (unittest .TestCase ):
154-
155153 @parameterized .expand ([TEST_CASE_ND_1 , TEST_CASE_ND_2 ])
156154 def test_value (self , input_data , expected_fp , expected_tp , expected_num ):
157155 fp_probs , tp_probs , num_tumors = compute_fp_tp_probs_nd (** input_data )
@@ -161,13 +159,46 @@ def test_value(self, input_data, expected_fp, expected_tp, expected_num):
161159
162160
163161class TestComputeFrocScore (unittest .TestCase ):
164-
165162 @parameterized .expand ([TEST_CASE_4 , TEST_CASE_5 ])
166163 def test_value (self , input_data , thresholds , expected_score ):
167164 fps_per_image , total_sensitivity = compute_froc_curve_data (** input_data )
168165 score = compute_froc_score (fps_per_image , total_sensitivity , thresholds )
169166 np .testing .assert_allclose (score , expected_score , rtol = 1e-5 )
170167
171168
169+ def _reference_compute_froc_curve_data (fp_probs , tp_probs , num_targets , num_images ):
170+ """Original threshold-by-threshold implementation, retained for regression comparisons."""
171+ total_fps , total_tps = [], []
172+ all_probs = sorted (set (list (fp_probs ) + list (tp_probs )))
173+ for thresh in all_probs [1 :]:
174+ total_fps .append ((fp_probs >= thresh ).sum ())
175+ total_tps .append ((tp_probs >= thresh ).sum ())
176+ total_fps .append (0 )
177+ total_tps .append (0 )
178+ return np .asarray (total_fps ) / float (num_images ), np .asarray (total_tps ) / float (num_targets )
179+
180+
181+ class TestComputeFrocCurveData (unittest .TestCase ):
182+ @parameterized .expand (
183+ [
184+ ("duplicates" , [0.2 , 0.5 , 0.5 , 0.9 ], [0.1 , 0.5 , 0.5 , 0.8 ]),
185+ ("mostly_unique" , [0.12 , 0.34 , 0.56 , 0.78 ], [0.23 , 0.45 , 0.67 , 0.89 ]),
186+ ("all_equal" , [0.4 , 0.4 ], [0.4 , 0.4 , 0.4 ]),
187+ ("empty" , [], []),
188+ ("empty_fp" , [], [0.2 , 0.6 , 0.6 ]),
189+ ("empty_tp" , [0.2 , 0.6 , 0.6 ], []),
190+ ("threshold_equality" , [0.2 , 0.5 , 0.8 ], [0.5 , 0.5 , 0.9 ]),
191+ ]
192+ )
193+ def test_matches_reference (self , _ , fp_probs , tp_probs ):
194+ fp_probs = np .asarray (fp_probs )
195+ tp_probs = np .asarray (tp_probs )
196+ actual = compute_froc_curve_data (fp_probs , tp_probs , num_targets = 3 , num_images = 2 )
197+ expected = _reference_compute_froc_curve_data (fp_probs , tp_probs , num_targets = 3 , num_images = 2 )
198+ for actual_array , expected_array in zip (actual , expected ):
199+ np .testing .assert_array_equal (actual_array , expected_array )
200+ self .assertEqual (actual_array .dtype , expected_array .dtype )
201+
202+
172203if __name__ == "__main__" :
173204 unittest .main ()
0 commit comments