evalsuite-python 0.2.1__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/CHANGELOG.md +59 -0
  2. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/PKG-INFO +82 -21
  3. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/README.md +73 -18
  4. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/pyproject.toml +7 -3
  5. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/__init__.py +42 -1
  6. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/api.py +10 -0
  7. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/benchmarks.py +252 -10
  8. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/calibration.py +1 -1
  9. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/cli/main.py +99 -2
  10. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/validation.py +21 -0
  11. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/plot.py +165 -1
  12. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/regression/metrics.py +9 -1
  13. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/_resolve.py +32 -4
  14. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/compare.py +6 -4
  15. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/version.py +1 -1
  16. evalsuite_python-0.3.1/src/evalsuite/vision/__init__.py +47 -0
  17. evalsuite_python-0.3.1/src/evalsuite/vision/detection.py +648 -0
  18. evalsuite_python-0.3.1/src/evalsuite/vision/segmentation.py +973 -0
  19. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/clinical/test_v020.py +9 -0
  20. evalsuite_python-0.3.1/tests/integration/test_qa.py +311 -0
  21. evalsuite_python-0.3.1/tests/integration/test_readme_examples.py +122 -0
  22. evalsuite_python-0.3.1/tests/integration/test_repo_hygiene.py +97 -0
  23. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_benchmarks.py +24 -2
  24. evalsuite_python-0.3.1/tests/vision/__init__.py +0 -0
  25. evalsuite_python-0.3.1/tests/vision/test_detection.py +200 -0
  26. evalsuite_python-0.3.1/tests/vision/test_edges.py +136 -0
  27. evalsuite_python-0.3.1/tests/vision/test_outputs.py +170 -0
  28. evalsuite_python-0.3.1/tests/vision/test_segmentation.py +156 -0
  29. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/.gitignore +0 -0
  30. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/CONTRIBUTING.md +0 -0
  31. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/LICENSE +0 -0
  32. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/__main__.py +0 -0
  33. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/classification/__init__.py +0 -0
  34. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/classification/_common.py +0 -0
  35. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/classification/metrics.py +0 -0
  36. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/cli/__init__.py +0 -0
  37. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/clinical/__init__.py +0 -0
  38. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/clinical/metrics.py +0 -0
  39. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/clinical/report.py +0 -0
  40. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/__init__.py +0 -0
  41. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/context.py +0 -0
  42. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/exceptions.py +0 -0
  43. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/export.py +0 -0
  44. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/registry.py +0 -0
  45. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/result.py +0 -0
  46. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/types.py +0 -0
  47. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/py.typed +0 -0
  48. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/regression/__init__.py +0 -0
  49. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/reporting.py +0 -0
  50. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/__init__.py +0 -0
  51. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/effect.py +0 -0
  52. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/hypothesis.py +0 -0
  53. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/intervals.py +0 -0
  54. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/paired.py +0 -0
  55. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/results.py +0 -0
  56. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/__init__.py +0 -0
  57. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/classification/__init__.py +0 -0
  58. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/classification/test_against_sklearn.py +0 -0
  59. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/clinical/__init__.py +0 -0
  60. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/clinical/test_outputs.py +0 -0
  61. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/conftest.py +0 -0
  62. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/integration/__init__.py +0 -0
  63. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/__init__.py +0 -0
  64. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_cli.py +0 -0
  65. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_plot.py +0 -0
  66. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_reporting.py +0 -0
  67. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/regression/__init__.py +0 -0
  68. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/regression/test_against_sklearn.py +0 -0
  69. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/__init__.py +0 -0
  70. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/test_branches.py +0 -0
  71. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/test_compare.py +0 -0
  72. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/test_reference.py +0 -0
  73. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/unit/__init__.py +0 -0
  74. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/unit/test_core.py +0 -0
  75. {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/unit/test_edges.py +0 -0
@@ -6,6 +6,65 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-10-09
10
+
11
+ Quality-assurance release: release pipeline, packaging checks, edge-case fixes and an overall benchmark.
12
+ No API was removed or renamed; code written for 0.3.0 runs unchanged.
13
+
14
+ ### Fixed
15
+ - Labels that mix numbers and strings (`[0, "a"]`, or string `y_true` with integer `y_pred`) now raise
16
+ `InputValidationError` instead of being silently compared as text.
17
+ - `rmse` no longer overflows to `inf` when errors exceed ~1e154; the value is computed with rescaling.
18
+ - `bootstrap_ci`, `paired_bootstrap_test` and `compare` raise `StatisticalTestError` for fewer than two
19
+ observations instead of returning a zero-width interval.
20
+ - A 2-D mask given as a nested Python list (`[[1, 1], [0, 0]]`) is read as one image, as a NumPy array is.
21
+ - Release workflow: the GitHub release step no longer fails when the release already exists (for example
22
+ when the tag was created from the GitHub UI); it attaches the files instead.
23
+
24
+ ### Added
25
+ - Benchmarks: an overall summary first (per suite and in total: cases, agreement with the reference,
26
+ how many are faster, geometric-mean and range of speed-ups) and rows in alphabetical order;
27
+ `BenchmarkResult.overall()`, `overall_markdown()` and `sorted_rows()`; `overall` in the JSON export.
28
+ - Release workflow: refuses a tag that does not match the package version or a version already on PyPI,
29
+ checks the built files, uses `--verify-tag`, and can be run by hand to attach a version's PyPI files
30
+ (hash-verified) to its GitHub release.
31
+ - CI: wheel and sdist installed in clean environments with `pip check` and a smoke test that must give the
32
+ same results for both, a distribution check (metadata, forbidden files, credentials), a minimum-dependency
33
+ job (NumPy 1.22, SciPy 1.8, pandas 1.4), README examples run as tests, and repository hygiene tests
34
+ (no credentials, no unsafe calls, least-privilege workflows, single version source).
35
+ - QA tests against scikit-learn and statsmodels for the edge cases in the release checklist.
36
+ - `SECURITY.md` with the reporting process.
37
+
38
+ ## [0.3.0] - 2026-10-09
39
+
40
+ Computer vision (the v0.3.0 roadmap), with comparison, plots, reporting and benchmarks extended to it.
41
+
42
+ ### Added
43
+ - Segmentation: `dice`, `iou`, `miou`, `pixel_accuracy`, `mean_pixel_accuracy`, `boundary_iou`,
44
+ `hausdorff_distance` (HD and HD95, anisotropic `spacing`), `average_surface_distance`,
45
+ `segmentation_confusion`, `per_image_scores` and `segmentation_report`. 2-D and 3-D masks, lists of
46
+ differently sized masks, `ignore_index`, `aggregate="dataset"|"image"`, undefined classes reported as NaN.
47
+ - Object detection: `box_iou`, `detection_report` (the 12 COCO numbers plus AP per class),
48
+ `mean_average_precision`, `average_precision_detection` (COCO or VOC interpolation), `detection_pr_curve`,
49
+ `from_coco`. Matches pycocotools exactly, including crowd regions, area ranges and detection limits.
50
+ - Comparison for vision: `compare`, `bootstrap_ci` and `paired_bootstrap_test` resample images for
51
+ segmentation masks and per-image detections.
52
+ - Plots: `es.plot.segmentation` (prediction fill vs truth outline), `es.plot.per_class` (any per-class
53
+ result, a segmentation report or a detection report), `es.plot.detection_pr`.
54
+ - CLI: `evalsuite segmentation` (.npy/.npz or a folder of mask images) and `evalsuite detection` (COCO JSON).
55
+ - Benchmarks: `--suite vision` against scikit-learn, SciPy and pycocotools.
56
+ - `vision` extra (Pillow, for reading mask image folders); `CITATION.cff`.
57
+
58
+ ### Changed
59
+ - `evaluate()` points segmentation masks and detection annotations to the right functions instead of
60
+ failing with a shape error.
61
+ - CI and release workflows use the Node 24 versions of the GitHub actions.
62
+
63
+ ### Fixed
64
+ - Calibration slope and intercept no longer emit an overflow warning before reporting a perfectly
65
+ separated outcome.
66
+ - The CLI no longer fails on consoles that cannot print Unicode (Windows code pages).
67
+
9
68
  ## [0.2.1] - 2026-10-09
10
69
 
11
70
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evalsuite-python
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: A unified Python framework for machine-learning, clinical, statistical, segmentation, object-detection, uncertainty, and model evaluation.
5
5
  Project-URL: Homepage, https://evalsuite-nine.vercel.app
6
6
  Project-URL: Documentation, https://evalsuite-nine.vercel.app/docs
@@ -11,7 +11,7 @@ Author: Manoj Kumar C S, Nikhil D Bharadwaj
11
11
  Maintainer: Manoj Kumar C S, Nikhil D Bharadwaj
12
12
  License-Expression: MIT
13
13
  License-File: LICENSE
14
- Keywords: calibration,classification,clinical,diagnostic accuracy,evaluation,machine learning,metrics,regression,reproducibility,statistical tests,statistics
14
+ Keywords: calibration,classification,clinical,diagnostic accuracy,evaluation,mAP,machine learning,metrics,object detection,regression,reproducibility,segmentation,statistical tests,statistics
15
15
  Classifier: Development Status :: 5 - Production/Stable
16
16
  Classifier: Intended Audience :: Developers
17
17
  Classifier: Intended Audience :: Healthcare Industry
@@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: 3.13
27
27
  Classifier: Programming Language :: Python :: 3.14
28
28
  Classifier: Topic :: Scientific/Engineering
29
29
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
30
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
30
31
  Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
31
32
  Classifier: Typing :: Typed
32
33
  Requires-Python: >=3.9
@@ -35,21 +36,26 @@ Requires-Dist: pandas>=1.4
35
36
  Requires-Dist: scipy>=1.8
36
37
  Provides-Extra: all
37
38
  Requires-Dist: matplotlib>=3.5; extra == 'all'
39
+ Requires-Dist: pillow>=9; extra == 'all'
38
40
  Provides-Extra: dev
39
41
  Requires-Dist: build; extra == 'dev'
40
42
  Requires-Dist: hypothesis>=6.80; extra == 'dev'
41
43
  Requires-Dist: matplotlib>=3.5; extra == 'dev'
42
44
  Requires-Dist: mypy>=1.10; extra == 'dev'
43
45
  Requires-Dist: pandas-stubs; extra == 'dev'
46
+ Requires-Dist: pillow>=9; extra == 'dev'
44
47
  Requires-Dist: pip-audit; extra == 'dev'
48
+ Requires-Dist: pycocotools>=2.0.7; extra == 'dev'
45
49
  Requires-Dist: pytest-cov>=4; extra == 'dev'
46
50
  Requires-Dist: pytest>=7; extra == 'dev'
47
51
  Requires-Dist: ruff>=0.6; extra == 'dev'
48
- Requires-Dist: scikit-learn>=1.2; extra == 'dev'
52
+ Requires-Dist: scikit-learn>=1.4; extra == 'dev'
49
53
  Requires-Dist: statsmodels>=0.13; extra == 'dev'
50
54
  Requires-Dist: twine; extra == 'dev'
51
55
  Provides-Extra: plot
52
56
  Requires-Dist: matplotlib>=3.5; extra == 'plot'
57
+ Provides-Extra: vision
58
+ Requires-Dist: pillow>=9; extra == 'vision'
53
59
  Description-Content-Type: text/markdown
54
60
 
55
61
  # EvalSuite
@@ -61,10 +67,10 @@ Description-Content-Type: text/markdown
61
67
 
62
68
  **Unified, reproducible evaluation for machine learning and research.**
63
69
 
64
- EvalSuite brings classification, regression, clinical and statistical evaluation (with segmentation and
65
- object-detection evaluation on the roadmap) into one consistent, validated, documented framework.
70
+ EvalSuite brings classification, regression, clinical, statistical, segmentation and object-detection
71
+ evaluation into one consistent, validated, documented framework.
66
72
 
67
- > **Status: stable (0.2.1).** Every item on the 0.1.0 and 0.2.0 roadmaps is implemented and verified.
73
+ > **Status: stable (0.3.1).** Every item on the 0.1.0, 0.2.0 and 0.3.0 roadmaps is implemented and verified.
68
74
 
69
75
  ## Installation
70
76
 
@@ -181,6 +187,45 @@ es.adjust_pvalues(p_values, method="hochberg") # also holm, bonferroni, bh, by
181
187
  Every test returns a `TestResult` with the statistic, p-value and an effect size, computed with SciPy and
182
188
  checked against SciPy and statsmodels in the test suite.
183
189
 
190
+ ## Segmentation
191
+
192
+ ```python
193
+ # label masks: one 2-D image, or images on the first axis (N, H, W) / (N, D, H, W), or a list of masks
194
+ es.dice(y_true, y_pred) # macro over classes, pixel counts summed over the dataset
195
+ es.iou(y_true, y_pred, average=None) # per class; classes absent from both masks are NaN, not 0
196
+ es.miou(y_true, y_pred, ignore_index=255)
197
+ es.dice(y_true, y_pred, aggregate="image") # mean of per-image scores (medical imaging convention)
198
+ es.boundary_iou(y_true, y_pred) # Cheng et al. 2021
199
+ es.hausdorff_distance(y_true, y_pred, percentile=95, spacing=(0.8, 0.8)) # HD95 in mm
200
+
201
+ report = es.segmentation_report(y_true, y_pred, class_names={0: "background", 1: "liver"})
202
+ print(report) # mIoU, Dice, pixel accuracy, Boundary IoU, HD95, ASSD + per-class table
203
+ es.plot.segmentation(image, y_true[0], y_pred[0]) # prediction fill, truth outline
204
+ es.plot.per_class(report, metric="iou") # per-class bars (also takes a detection report)
205
+ ```
206
+
207
+ ## Object detection
208
+
209
+ ```python
210
+ y_true = [{"boxes": [[x1, y1, x2, y2], ...], "labels": [3, ...]}, ...] # one dict per image
211
+ y_pred = [{"boxes": [...], "labels": [...], "scores": [...]}, ...]
212
+
213
+ report = es.detection_report(y_true, y_pred) # the 12 COCO numbers + AP per class
214
+ report["map"], report["map_50"], report["mar_100"]
215
+ es.mean_average_precision(y_true, y_pred, iou_threshold=0.5) # mAP@.50
216
+ es.average_precision_detection(y_true, y_pred, interpolation="voc") # per class, VOC-style
217
+ es.box_iou(boxes_a, boxes_b, box_format="xywh")
218
+ y_true, y_pred = es.from_coco("instances_val.json", "detections.json")
219
+ es.plot.detection_pr(y_true, y_pred)
220
+ ```
221
+
222
+ The COCO protocol (crowd regions, area ranges, max detections, 101-point interpolation) matches
223
+ `pycocotools` to the last digit in the test suite.
224
+
225
+ Models can be compared over the same images with intervals and paired tests, as for every other task:
226
+ `es.compare(y_true, {"unet": masks_a, "deeplab": masks_b})` resamples images; for detection it compares
227
+ mAP.
228
+
184
229
  ## Classification report
185
230
 
186
231
  ```python
@@ -225,6 +270,8 @@ evalsuite plot roc predictions.csv --y-true label --y-prob prob -o roc.png
225
270
  evalsuite diagnostic predictions.csv --y-true label --y-pred pred # sensitivity, LR+, DOR... with CIs
226
271
  evalsuite calibration predictions.csv --y-true label --y-prob prob # slope, intercept, ECE, HL
227
272
  evalsuite plot decision predictions.csv --y-true label --y-prob prob -o dca.png
273
+ evalsuite segmentation true_masks.npy pred_masks.npy --ignore-index 255 --plot per_class.png
274
+ evalsuite detection instances_val.json detections.json --plot pr_curves.png
228
275
  evalsuite metrics --category clinical
229
276
  evalsuite info classification.mcc
230
277
  evalsuite benchmark --quick
@@ -236,21 +283,28 @@ Input files can be CSV, TSV, Parquet or JSON. Output format follows `--format` o
236
283
  ## Performance
237
284
 
238
285
  Benchmarked against reference implementations on the same data (fastest of 5 runs; Python 3.12, NumPy 2.5,
239
- Linux x86_64). Every result agrees with the reference to floating-point rounding (largest difference
240
- 1.4e-14).
241
-
242
- | Case | n | Reference | EvalSuite (ms) | Reference (ms) | Speed-up |
243
- | --- | ---: | --- | ---: | ---: | ---: |
244
- | 8 binary label metrics via `evaluate()` | 1,000,000 | scikit-learn | 30.2 | 1020.2 | **33.8×** |
245
- | macro F1, 10 classes | 1,000,000 | scikit-learn | 22.4 | 128.7 | **5.8×** |
246
- | ROC AUC, binary | 1,000,000 | scikit-learn | 173.2 | 300.6 | **1.7×** |
247
- | MAE, MSE, RMSE, R² via `evaluate()` | 1,000,000 | scikit-learn | 19.1 | 9.7 | 0.51× |
248
- | sensitivity, specificity, LR+, LR− | 1,000,000 | scikit-learn | 66.2 | 392.8 | **5.9×** |
249
- | calibration slope and intercept | 1,000,000 | statsmodels | 178.0 | 1014.8 | **5.7×** |
250
- | decision curve, 99 thresholds | 1,000,000 | NumPy loop | 155.2 | 174.3 | **1.1×** |
251
- | diagnostic report (7 CIs) | 1,000,000 | statsmodels | 16.6 | 4.6 | 0.28× |
252
- | Welch t-test | 1,000,000 | SciPy | 14.5 | 7.3 | 0.51× |
253
- | Hochberg correction | 1,000,000 | statsmodels | 75.4 | 81.9 | **1.1×** |
286
+ Linux x86_64). **Overall: 45 of 45 rows agree with the reference** (largest
287
+ difference 2.8e-14), 32 are faster, and the geometric-mean speed-up across all
288
+ 15 cases and three sizes is **2.22×**. At 1,000,000 samples (pixels for
289
+ segmentation; 1,000 images for detection and 50 for Hausdorff), in alphabetical order:
290
+
291
+ | Case | Reference | EvalSuite (ms) | Reference (ms) | Speed-up |
292
+ | --- | --- | ---: | ---: | ---: |
293
+ | 10 classes: macro F1 | scikit-learn | 29.3 | 130.8 | **4.5×** |
294
+ | binary: 8 label metrics via evaluate() | scikit-learn | 39.5 | 1084.1 | **27.4×** |
295
+ | binary: ROC AUC | scikit-learn | 195.0 | 348.3 | **1.8×** |
296
+ | calibration: slope and intercept | statsmodels | 193.0 | 1068.0 | **5.5×** |
297
+ | clinical: diagnostic report (7 CIs) | statsmodels | 23.5 | 4.7 | 0.20× |
298
+ | clinical: sensitivity, specificity, LR+, LR− | scikit-learn | 69.1 | 414.0 | **6.0×** |
299
+ | decision curve: 99 thresholds | NumPy loop | 156.3 | 181.6 | **1.2×** |
300
+ | detection: COCO evaluation (1000 images) | pycocotools | 817.3 | 983.7 | **1.2×** |
301
+ | multiple testing: Hochberg (n p-values) | statsmodels | 82.0 | 108.0 | **1.3×** |
302
+ | regression: MAE, MSE, RMSE, R² via evaluate() | scikit-learn | 23.1 | 10.8 | 0.47× |
303
+ | segmentation: Dice and IoU per class (n = pixels) | scikit-learn | 43.0 | 290.0 | **6.8×** |
304
+ | segmentation: Hausdorff distance (50 images) | SciPy | 27.0 | 20.2 | 0.75× |
305
+ | statistics: Cramér's V (5×5 table) | SciPy | 0.5 | 0.4 | 0.88× |
306
+ | statistics: Mann–Whitney U | SciPy | 349.7 | 350.2 | **1.0×** |
307
+ | statistics: Welch t-test | SciPy | 14.5 | 7.2 | 0.49× |
254
308
 
255
309
  `evaluate()` validates inputs once and builds the confusion matrix once for all metrics, which is where most
256
310
  of the speed-up comes from. Hypothesis tests use SciPy underneath, so they match its speed at best; rows
@@ -279,6 +333,13 @@ Kruskal–Wallis, Friedman, Shapiro–Wilk, χ², Fisher's exact; effect sizes (
279
333
  delta, Cramér's V); multiple-testing corrections (Bonferroni, Holm, Hochberg, Benjamini–Hochberg,
280
334
  Benjamini–Yekutieli).
281
335
 
336
+ **Segmentation** (2-D and 3-D label masks; `ignore_index`; dataset or per-image aggregation): Dice, IoU,
337
+ mIoU, pixel accuracy, mean pixel accuracy, Boundary IoU, Hausdorff distance and HD95, average symmetric
338
+ surface distance (with pixel spacing), confusion matrix and a full report.
339
+
340
+ **Object detection**: box IoU (xyxy, xywh, cxcywh), COCO mAP@[.50:.95], mAP@.50, mAP@.75, mAP and mAR by
341
+ object size, AP per class with COCO or VOC interpolation, precision-recall curves, COCO file import.
342
+
282
343
  **Regression** (single and multi-output; sample weights): MAE, MSE, RMSE, R², adjusted R², MAPE, sMAPE,
283
344
  MSLE, RMSLE, median absolute error, explained variance, max error, mean bias error, quantile (pinball)
284
345
  loss, Huber loss, relative absolute error, relative squared error.
@@ -7,10 +7,10 @@
7
7
 
8
8
  **Unified, reproducible evaluation for machine learning and research.**
9
9
 
10
- EvalSuite brings classification, regression, clinical and statistical evaluation (with segmentation and
11
- object-detection evaluation on the roadmap) into one consistent, validated, documented framework.
10
+ EvalSuite brings classification, regression, clinical, statistical, segmentation and object-detection
11
+ evaluation into one consistent, validated, documented framework.
12
12
 
13
- > **Status: stable (0.2.1).** Every item on the 0.1.0 and 0.2.0 roadmaps is implemented and verified.
13
+ > **Status: stable (0.3.1).** Every item on the 0.1.0, 0.2.0 and 0.3.0 roadmaps is implemented and verified.
14
14
 
15
15
  ## Installation
16
16
 
@@ -127,6 +127,45 @@ es.adjust_pvalues(p_values, method="hochberg") # also holm, bonferroni, bh, by
127
127
  Every test returns a `TestResult` with the statistic, p-value and an effect size, computed with SciPy and
128
128
  checked against SciPy and statsmodels in the test suite.
129
129
 
130
+ ## Segmentation
131
+
132
+ ```python
133
+ # label masks: one 2-D image, or images on the first axis (N, H, W) / (N, D, H, W), or a list of masks
134
+ es.dice(y_true, y_pred) # macro over classes, pixel counts summed over the dataset
135
+ es.iou(y_true, y_pred, average=None) # per class; classes absent from both masks are NaN, not 0
136
+ es.miou(y_true, y_pred, ignore_index=255)
137
+ es.dice(y_true, y_pred, aggregate="image") # mean of per-image scores (medical imaging convention)
138
+ es.boundary_iou(y_true, y_pred) # Cheng et al. 2021
139
+ es.hausdorff_distance(y_true, y_pred, percentile=95, spacing=(0.8, 0.8)) # HD95 in mm
140
+
141
+ report = es.segmentation_report(y_true, y_pred, class_names={0: "background", 1: "liver"})
142
+ print(report) # mIoU, Dice, pixel accuracy, Boundary IoU, HD95, ASSD + per-class table
143
+ es.plot.segmentation(image, y_true[0], y_pred[0]) # prediction fill, truth outline
144
+ es.plot.per_class(report, metric="iou") # per-class bars (also takes a detection report)
145
+ ```
146
+
147
+ ## Object detection
148
+
149
+ ```python
150
+ y_true = [{"boxes": [[x1, y1, x2, y2], ...], "labels": [3, ...]}, ...] # one dict per image
151
+ y_pred = [{"boxes": [...], "labels": [...], "scores": [...]}, ...]
152
+
153
+ report = es.detection_report(y_true, y_pred) # the 12 COCO numbers + AP per class
154
+ report["map"], report["map_50"], report["mar_100"]
155
+ es.mean_average_precision(y_true, y_pred, iou_threshold=0.5) # mAP@.50
156
+ es.average_precision_detection(y_true, y_pred, interpolation="voc") # per class, VOC-style
157
+ es.box_iou(boxes_a, boxes_b, box_format="xywh")
158
+ y_true, y_pred = es.from_coco("instances_val.json", "detections.json")
159
+ es.plot.detection_pr(y_true, y_pred)
160
+ ```
161
+
162
+ The COCO protocol (crowd regions, area ranges, max detections, 101-point interpolation) matches
163
+ `pycocotools` to the last digit in the test suite.
164
+
165
+ Models can be compared over the same images with intervals and paired tests, as for every other task:
166
+ `es.compare(y_true, {"unet": masks_a, "deeplab": masks_b})` resamples images; for detection it compares
167
+ mAP.
168
+
130
169
  ## Classification report
131
170
 
132
171
  ```python
@@ -171,6 +210,8 @@ evalsuite plot roc predictions.csv --y-true label --y-prob prob -o roc.png
171
210
  evalsuite diagnostic predictions.csv --y-true label --y-pred pred # sensitivity, LR+, DOR... with CIs
172
211
  evalsuite calibration predictions.csv --y-true label --y-prob prob # slope, intercept, ECE, HL
173
212
  evalsuite plot decision predictions.csv --y-true label --y-prob prob -o dca.png
213
+ evalsuite segmentation true_masks.npy pred_masks.npy --ignore-index 255 --plot per_class.png
214
+ evalsuite detection instances_val.json detections.json --plot pr_curves.png
174
215
  evalsuite metrics --category clinical
175
216
  evalsuite info classification.mcc
176
217
  evalsuite benchmark --quick
@@ -182,21 +223,28 @@ Input files can be CSV, TSV, Parquet or JSON. Output format follows `--format` o
182
223
  ## Performance
183
224
 
184
225
  Benchmarked against reference implementations on the same data (fastest of 5 runs; Python 3.12, NumPy 2.5,
185
- Linux x86_64). Every result agrees with the reference to floating-point rounding (largest difference
186
- 1.4e-14).
187
-
188
- | Case | n | Reference | EvalSuite (ms) | Reference (ms) | Speed-up |
189
- | --- | ---: | --- | ---: | ---: | ---: |
190
- | 8 binary label metrics via `evaluate()` | 1,000,000 | scikit-learn | 30.2 | 1020.2 | **33.8×** |
191
- | macro F1, 10 classes | 1,000,000 | scikit-learn | 22.4 | 128.7 | **5.8×** |
192
- | ROC AUC, binary | 1,000,000 | scikit-learn | 173.2 | 300.6 | **1.7×** |
193
- | MAE, MSE, RMSE, R² via `evaluate()` | 1,000,000 | scikit-learn | 19.1 | 9.7 | 0.51× |
194
- | sensitivity, specificity, LR+, LR− | 1,000,000 | scikit-learn | 66.2 | 392.8 | **5.9×** |
195
- | calibration slope and intercept | 1,000,000 | statsmodels | 178.0 | 1014.8 | **5.7×** |
196
- | decision curve, 99 thresholds | 1,000,000 | NumPy loop | 155.2 | 174.3 | **1.1×** |
197
- | diagnostic report (7 CIs) | 1,000,000 | statsmodels | 16.6 | 4.6 | 0.28× |
198
- | Welch t-test | 1,000,000 | SciPy | 14.5 | 7.3 | 0.51× |
199
- | Hochberg correction | 1,000,000 | statsmodels | 75.4 | 81.9 | **1.1×** |
226
+ Linux x86_64). **Overall: 45 of 45 rows agree with the reference** (largest
227
+ difference 2.8e-14), 32 are faster, and the geometric-mean speed-up across all
228
+ 15 cases and three sizes is **2.22×**. At 1,000,000 samples (pixels for
229
+ segmentation; 1,000 images for detection and 50 for Hausdorff), in alphabetical order:
230
+
231
+ | Case | Reference | EvalSuite (ms) | Reference (ms) | Speed-up |
232
+ | --- | --- | ---: | ---: | ---: |
233
+ | 10 classes: macro F1 | scikit-learn | 29.3 | 130.8 | **4.5×** |
234
+ | binary: 8 label metrics via evaluate() | scikit-learn | 39.5 | 1084.1 | **27.4×** |
235
+ | binary: ROC AUC | scikit-learn | 195.0 | 348.3 | **1.8×** |
236
+ | calibration: slope and intercept | statsmodels | 193.0 | 1068.0 | **5.5×** |
237
+ | clinical: diagnostic report (7 CIs) | statsmodels | 23.5 | 4.7 | 0.20× |
238
+ | clinical: sensitivity, specificity, LR+, LR− | scikit-learn | 69.1 | 414.0 | **6.0×** |
239
+ | decision curve: 99 thresholds | NumPy loop | 156.3 | 181.6 | **1.2×** |
240
+ | detection: COCO evaluation (1000 images) | pycocotools | 817.3 | 983.7 | **1.2×** |
241
+ | multiple testing: Hochberg (n p-values) | statsmodels | 82.0 | 108.0 | **1.3×** |
242
+ | regression: MAE, MSE, RMSE, R² via evaluate() | scikit-learn | 23.1 | 10.8 | 0.47× |
243
+ | segmentation: Dice and IoU per class (n = pixels) | scikit-learn | 43.0 | 290.0 | **6.8×** |
244
+ | segmentation: Hausdorff distance (50 images) | SciPy | 27.0 | 20.2 | 0.75× |
245
+ | statistics: Cramér's V (5×5 table) | SciPy | 0.5 | 0.4 | 0.88× |
246
+ | statistics: Mann–Whitney U | SciPy | 349.7 | 350.2 | **1.0×** |
247
+ | statistics: Welch t-test | SciPy | 14.5 | 7.2 | 0.49× |
200
248
 
201
249
  `evaluate()` validates inputs once and builds the confusion matrix once for all metrics, which is where most
202
250
  of the speed-up comes from. Hypothesis tests use SciPy underneath, so they match its speed at best; rows
@@ -225,6 +273,13 @@ Kruskal–Wallis, Friedman, Shapiro–Wilk, χ², Fisher's exact; effect sizes (
225
273
  delta, Cramér's V); multiple-testing corrections (Bonferroni, Holm, Hochberg, Benjamini–Hochberg,
226
274
  Benjamini–Yekutieli).
227
275
 
276
+ **Segmentation** (2-D and 3-D label masks; `ignore_index`; dataset or per-image aggregation): Dice, IoU,
277
+ mIoU, pixel accuracy, mean pixel accuracy, Boundary IoU, Hausdorff distance and HD95, average symmetric
278
+ surface distance (with pixel spacing), confusion matrix and a full report.
279
+
280
+ **Object detection**: box IoU (xyxy, xywh, cxcywh), COCO mAP@[.50:.95], mAP@.50, mAP@.75, mAP and mAR by
281
+ object size, AP per class with COCO or VOC interpolation, precision-recall curves, COCO file import.
282
+
228
283
  **Regression** (single and multi-output; sample weights): MAE, MSE, RMSE, R², adjusted R², MAPE, sMAPE,
229
284
  MSLE, RMSLE, median absolute error, explained variance, max error, mean bias error, quantile (pinball)
230
285
  loss, Huber loss, relative absolute error, relative squared error.
@@ -12,12 +12,13 @@ license-files = ["LICENSE"]
12
12
  requires-python = ">=3.9"
13
13
  authors = [{ name = "Manoj Kumar C S" }, { name = "Nikhil D Bharadwaj" }]
14
14
  maintainers = [{ name = "Manoj Kumar C S" }, { name = "Nikhil D Bharadwaj" }]
15
- keywords = ["evaluation", "metrics", "clinical", "diagnostic accuracy", "calibration", "statistical tests", "machine learning", "statistics", "classification", "regression", "reproducibility"]
15
+ keywords = ["evaluation", "metrics", "segmentation", "object detection", "mAP", "clinical", "diagnostic accuracy", "calibration", "statistical tests", "machine learning", "statistics", "classification", "regression", "reproducibility"]
16
16
  classifiers = [
17
17
  "Development Status :: 5 - Production/Stable",
18
18
  "Intended Audience :: Science/Research",
19
19
  "Intended Audience :: Healthcare Industry",
20
20
  "Topic :: Scientific/Engineering :: Medical Science Apps.",
21
+ "Topic :: Scientific/Engineering :: Image Recognition",
21
22
  "Intended Audience :: Developers",
22
23
  "Operating System :: OS Independent",
23
24
  "Programming Language :: Python :: 3",
@@ -36,14 +37,17 @@ dependencies = ["numpy>=1.22", "scipy>=1.8", "pandas>=1.4"]
36
37
 
37
38
  [project.optional-dependencies]
38
39
  plot = ["matplotlib>=3.5"]
39
- all = ["matplotlib>=3.5"]
40
+ vision = ["pillow>=9"]
41
+ all = ["matplotlib>=3.5", "pillow>=9"]
40
42
  dev = [
41
43
  "pytest>=7",
42
44
  "pytest-cov>=4",
43
45
  "hypothesis>=6.80",
44
- "scikit-learn>=1.2",
46
+ "scikit-learn>=1.4",
45
47
  "statsmodels>=0.13",
46
48
  "matplotlib>=3.5",
49
+ "pillow>=9",
50
+ "pycocotools>=2.0.7",
47
51
  "ruff>=0.6",
48
52
  "mypy>=1.10",
49
53
  "pandas-stubs",
@@ -5,7 +5,7 @@
5
5
  >>> print(result.summary()) # doctest: +SKIP
6
6
  """
7
7
 
8
- from . import calibration, classification, clinical, plot, regression, stats
8
+ from . import calibration, classification, clinical, plot, regression, stats, vision
9
9
  from .api import evaluate
10
10
  from .calibration import (
11
11
  CalibrationReport,
@@ -111,8 +111,49 @@ from .stats import (
111
111
  wilcoxon_test,
112
112
  )
113
113
  from .version import __version__
114
+ from .vision import (
115
+ DetectionReport,
116
+ SegmentationReport,
117
+ average_precision_detection,
118
+ average_surface_distance,
119
+ boundary_iou,
120
+ box_iou,
121
+ detection_pr_curve,
122
+ detection_report,
123
+ dice,
124
+ from_coco,
125
+ hausdorff_distance,
126
+ iou,
127
+ mean_average_precision,
128
+ mean_pixel_accuracy,
129
+ miou,
130
+ per_image_scores,
131
+ pixel_accuracy,
132
+ segmentation_confusion,
133
+ segmentation_report,
134
+ )
114
135
 
115
136
  __all__ = [
137
+ "SegmentationReport",
138
+ "segmentation_report",
139
+ "detection_pr_curve",
140
+ "vision",
141
+ "DetectionReport",
142
+ "average_precision_detection",
143
+ "average_surface_distance",
144
+ "boundary_iou",
145
+ "box_iou",
146
+ "detection_report",
147
+ "dice",
148
+ "from_coco",
149
+ "hausdorff_distance",
150
+ "iou",
151
+ "mean_average_precision",
152
+ "mean_pixel_accuracy",
153
+ "miou",
154
+ "per_image_scores",
155
+ "pixel_accuracy",
156
+ "segmentation_confusion",
116
157
  "CalibrationReport",
117
158
  "calibration_report",
118
159
  "calibration",
@@ -149,6 +149,16 @@ def evaluate(
149
149
  >>> round(r["accuracy"], 2)
150
150
  0.75
151
151
  """
152
+ if isinstance(y_true, (list, tuple)) and y_true and isinstance(y_true[0], dict):
153
+ raise UnsupportedTaskError(
154
+ "y_true looks like object detection annotations (one dict per image); use "
155
+ "evalsuite.detection_report(y_true, y_pred) or evalsuite.mean_average_precision(...)."
156
+ )
157
+ if np.ndim(y_true) >= 3:
158
+ raise UnsupportedTaskError(
159
+ "y_true has 3 or more dimensions, which looks like segmentation masks (images first); use "
160
+ "evalsuite.segmentation_report(y_true, y_pred) or evalsuite.dice / evalsuite.iou."
161
+ )
152
162
  task = task or _infer_task(y_true, y_prob)
153
163
  if task == "classification":
154
164
  return _evaluate_classification(