AnemiaLens / backend /scripts /evaluate_deployed_screening.py
asnannp's picture
sync: sync backend code, models, schemas, and API routers to Hugging Face Space cleanly
f559cc0
Raw
History Blame Contribute Delete
3.8 kB
from __future__ import annotations
import json
import numpy as np
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
from app.config import DEFAULT_DEPLOYED_SCREENING_REPORT_PATH
from app.services.image_quality import ImageQualityService
from app.services.prediction import ScreeningPredictor
from train_efficientnet import ARCHIVE_ROOT, _balanced_group_split, _build_records, _load_image_with_fallback
def main() -> None:
records = _build_records(ARCHIVE_ROOT)
if not records:
raise RuntimeError(f"No evaluation records found in {ARCHIVE_ROOT}.")
_, val_records = _balanced_group_split(records, test_size=0.2, n_splits=32)
roi_records = [record for record in val_records if record.source == "roi_original"]
quality_service = ImageQualityService()
predictor = ScreeningPredictor()
labels: list[int] = []
predictions: list[int] = []
blocked_positive = 0
blocked_negative = 0
likely_count = 0
uncertain_count = 0
for record in roi_records:
with record.image_path.open("rb") as handle:
quality, processed = quality_service.evaluate(handle.read())
labels.append(int(record.label))
prediction = predictor.predict(processed, quality) if quality.passed else None
if prediction is None and quality_service.allows_raw_frame_rescue(quality):
raw_image = _load_image_with_fallback(record.image_path).convert("RGB")
raw_prediction = predictor.predict(raw_image, quality)
if predictor.should_accept_raw_frame_rescue(raw_prediction):
quality = quality_service.build_raw_frame_rescue_assessment(quality)
prediction = raw_prediction
if prediction is None:
predictions.append(0)
if record.label:
blocked_positive += 1
else:
blocked_negative += 1
continue
predictions.append(int(prediction.screening_label == "anemia_likely"))
likely_count += int(prediction.screening_label == "anemia_likely")
uncertain_count += int(prediction.screening_label == "uncertain")
labels_array = np.asarray(labels, dtype=np.int32)
predictions_array = np.asarray(predictions, dtype=np.int32)
report = {
"evaluation_scope": "deployed_roi_screening",
"record_count": len(records),
"validation_size": len(roi_records),
"metrics": {
"accuracy": round(float(accuracy_score(labels_array, predictions_array)), 4),
"precision": round(float(precision_score(labels_array, predictions_array, zero_division=0)), 4),
"recall": round(float(recall_score(labels_array, predictions_array, zero_division=0)), 4),
"f1": round(float(f1_score(labels_array, predictions_array, zero_division=0)), 4),
"split_strategy": "group-shuffle-balance-select: roi_original + deployed quality gate",
},
"operating_counts": {
"blocked_positive": blocked_positive,
"blocked_negative": blocked_negative,
"blocked_total": blocked_positive + blocked_negative,
"likely_count": likely_count,
"uncertain_count": uncertain_count,
},
}
DEFAULT_DEPLOYED_SCREENING_REPORT_PATH.write_text(json.dumps(report, indent=2), encoding="utf-8")
print("\nDeployed ROI screening metrics")
for key in ("accuracy", "precision", "recall", "f1"):
print(f"{key}: {report['metrics'][key]:.4f}")
print(f"blocked_total: {report['operating_counts']['blocked_total']}")
print(f"likely_count: {report['operating_counts']['likely_count']}")
print(f"uncertain_count: {report['operating_counts']['uncertain_count']}")
if __name__ == "__main__":
main()