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.
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/CHANGELOG.md +59 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/PKG-INFO +82 -21
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/README.md +73 -18
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/pyproject.toml +7 -3
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/__init__.py +42 -1
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/api.py +10 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/benchmarks.py +252 -10
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/calibration.py +1 -1
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/cli/main.py +99 -2
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/validation.py +21 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/plot.py +165 -1
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/regression/metrics.py +9 -1
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/_resolve.py +32 -4
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/compare.py +6 -4
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/version.py +1 -1
- evalsuite_python-0.3.1/src/evalsuite/vision/__init__.py +47 -0
- evalsuite_python-0.3.1/src/evalsuite/vision/detection.py +648 -0
- evalsuite_python-0.3.1/src/evalsuite/vision/segmentation.py +973 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/clinical/test_v020.py +9 -0
- evalsuite_python-0.3.1/tests/integration/test_qa.py +311 -0
- evalsuite_python-0.3.1/tests/integration/test_readme_examples.py +122 -0
- evalsuite_python-0.3.1/tests/integration/test_repo_hygiene.py +97 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_benchmarks.py +24 -2
- evalsuite_python-0.3.1/tests/vision/__init__.py +0 -0
- evalsuite_python-0.3.1/tests/vision/test_detection.py +200 -0
- evalsuite_python-0.3.1/tests/vision/test_edges.py +136 -0
- evalsuite_python-0.3.1/tests/vision/test_outputs.py +170 -0
- evalsuite_python-0.3.1/tests/vision/test_segmentation.py +156 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/.gitignore +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/CONTRIBUTING.md +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/LICENSE +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/__main__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/classification/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/classification/_common.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/classification/metrics.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/cli/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/clinical/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/clinical/metrics.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/clinical/report.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/context.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/exceptions.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/export.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/registry.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/result.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/core/types.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/py.typed +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/regression/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/reporting.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/effect.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/hypothesis.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/intervals.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/paired.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/src/evalsuite/stats/results.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/classification/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/classification/test_against_sklearn.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/clinical/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/clinical/test_outputs.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/conftest.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/integration/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_cli.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_plot.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/output/test_reporting.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/regression/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/regression/test_against_sklearn.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/test_branches.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/test_compare.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/stats/test_reference.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/unit/__init__.py +0 -0
- {evalsuite_python-0.2.1 → evalsuite_python-0.3.1}/tests/unit/test_core.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
65
|
-
|
|
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.
|
|
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).
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
| calibration slope and intercept |
|
|
250
|
-
|
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
|
|
|
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
|
|
11
|
-
|
|
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.
|
|
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).
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
| calibration slope and intercept |
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
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
|
-
|
|
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.
|
|
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(
|