Suppress duplicate YOLO tile detections
This commit is contained in:
@@ -226,6 +226,9 @@ logs plus `calibration_summary.json` under
|
||||
`CALIBRATION_OUTPUT_DIR` is set. This is a calibration/benchmarking tool only:
|
||||
it does not seed demo data, enable fixture detections, fetch external data or
|
||||
download model weights.
|
||||
Per-threshold summaries include persisted detection count, raw candidate count
|
||||
before GeoIntel duplicate suppression, suppressed duplicate count and the
|
||||
configured duplicate IoU threshold.
|
||||
|
||||
Run a broader model/tile/threshold quality matrix when multiple local model
|
||||
assets or tile settings need to be compared:
|
||||
|
||||
@@ -98,6 +98,7 @@ for threshold in ${thresholds_normalized}; do
|
||||
threshold_label="$(printf '%s' "${threshold}" | tr '.-' 'pm')"
|
||||
run_log="${CALIBRATION_OUTPUT_DIR}/threshold_${threshold_label}.log"
|
||||
quality_response="${CALIBRATION_OUTPUT_DIR}/threshold_${threshold_label}_quality_checks.json"
|
||||
detection_run_response="${CALIBRATION_OUTPUT_DIR}/threshold_${threshold_label}_detection_run.json"
|
||||
run_summary="${CALIBRATION_OUTPUT_DIR}/threshold_${threshold_label}_summary.json"
|
||||
|
||||
echo "-- Threshold ${threshold} (${run_index}) --"
|
||||
@@ -121,9 +122,11 @@ for threshold in ${thresholds_normalized}; do
|
||||
fi
|
||||
|
||||
curl -fsS "${BASE_URL%/}/api/v1/projects/${project_id}/quality-checks?limit=200" > "${quality_response}"
|
||||
curl -fsS "${BASE_URL%/}/api/v1/detection/runs/${analysis_run_id}" > "${detection_run_response}"
|
||||
|
||||
"${PYTHON_BIN}" - \
|
||||
"${quality_response}" \
|
||||
"${detection_run_response}" \
|
||||
"${run_summary}" \
|
||||
"${threshold}" \
|
||||
"${project_id}" \
|
||||
@@ -134,11 +137,17 @@ for threshold in ${thresholds_normalized}; do
|
||||
import json
|
||||
import sys
|
||||
|
||||
quality_path, output_path, threshold, project_id, analysis_run_id, quality_check_id, detection_count, run_log = sys.argv[1:9]
|
||||
quality_path, detection_run_path, output_path, threshold, project_id, analysis_run_id, quality_check_id, detection_count, run_log = sys.argv[1:10]
|
||||
with open(quality_path, "r", encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
if "data" not in payload:
|
||||
raise SystemExit("Quality-check list is not a canonical GeoIntel data envelope")
|
||||
with open(detection_run_path, "r", encoding="utf-8") as handle:
|
||||
detection_run_payload = json.load(handle)
|
||||
if "data" not in detection_run_payload:
|
||||
raise SystemExit("Detection run detail is not a canonical GeoIntel data envelope")
|
||||
detection_run = detection_run_payload["data"]
|
||||
run_result = detection_run.get("result_json") or {}
|
||||
items = payload["data"].get("items") or []
|
||||
quality_check = next((item for item in items if str(item.get("id")) == quality_check_id), None)
|
||||
if quality_check is None:
|
||||
@@ -157,6 +166,9 @@ summary = {
|
||||
"detection_count": int(detection_count),
|
||||
"quality_status": quality_check.get("status"),
|
||||
"quality_score": quality_check.get("score"),
|
||||
"raw_detection_count": run_result.get("raw_detection_count", int(detection_count)),
|
||||
"suppressed_detection_count": run_result.get("suppressed_detection_count", 0),
|
||||
"duplicate_iou_threshold": run_result.get("duplicate_iou_threshold"),
|
||||
"precision": metrics.get("precision"),
|
||||
"recall": metrics.get("recall"),
|
||||
"f1_score": metrics.get("f1_score", metrics.get("f1")),
|
||||
@@ -169,10 +181,12 @@ summary = {
|
||||
with open(output_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(summary, handle, indent=2, sort_keys=True)
|
||||
print(
|
||||
"threshold={threshold} detections={detections} score={score} "
|
||||
"threshold={threshold} detections={detections} raw={raw} suppressed={suppressed} score={score} "
|
||||
"precision={precision} recall={recall} f1={f1} matches={matches} fp={fp} fn={fn}".format(
|
||||
threshold=summary["threshold"],
|
||||
detections=summary["detection_count"],
|
||||
raw=summary["raw_detection_count"],
|
||||
suppressed=summary["suppressed_detection_count"],
|
||||
score=summary["quality_score"],
|
||||
precision=summary["precision"],
|
||||
recall=summary["recall"],
|
||||
@@ -217,10 +231,10 @@ with open(summary_path, "w", encoding="utf-8") as handle:
|
||||
|
||||
print("")
|
||||
print("Detection calibration summary")
|
||||
print("threshold\tdetections\tscore\tprecision\trecall\tf1\tmatches\tfp\tfn")
|
||||
print("threshold\tdetections\traw\tsuppressed\tscore\tprecision\trecall\tf1\tmatches\tfp\tfn")
|
||||
for item in items:
|
||||
print(
|
||||
"{threshold:.2f}\t{detection_count}\t{quality_score}\t{precision}\t{recall}\t{f1_score}\t{matches}\t{false_positives}\t{false_negatives}".format(
|
||||
"{threshold:.2f}\t{detection_count}\t{raw_detection_count}\t{suppressed_detection_count}\t{quality_score}\t{precision}\t{recall}\t{f1_score}\t{matches}\t{false_positives}\t{false_negatives}".format(
|
||||
**item
|
||||
)
|
||||
)
|
||||
|
||||
@@ -168,6 +168,7 @@ while IFS=$'\t' read -r model_request tile_size tile_overlap threshold run_label
|
||||
run_index=$((run_index + 1))
|
||||
run_log="${QUALITY_OUTPUT_DIR}/${run_label}.log"
|
||||
quality_response="${QUALITY_OUTPUT_DIR}/${run_label}_quality_checks.json"
|
||||
detection_run_response="${QUALITY_OUTPUT_DIR}/${run_label}_detection_run.json"
|
||||
run_summary="${QUALITY_OUTPUT_DIR}/${run_label}_summary.json"
|
||||
model_env="${model_request}"
|
||||
if [ "${model_request}" = "__active__" ]; then
|
||||
@@ -203,9 +204,11 @@ while IFS=$'\t' read -r model_request tile_size tile_overlap threshold run_label
|
||||
fi
|
||||
|
||||
curl -fsS "${BASE_URL%/}/api/v1/projects/${project_id}/quality-checks?limit=200" > "${quality_response}"
|
||||
curl -fsS "${BASE_URL%/}/api/v1/detection/runs/${analysis_run_id}" > "${detection_run_response}"
|
||||
|
||||
"${PYTHON_BIN}" - \
|
||||
"${quality_response}" \
|
||||
"${detection_run_response}" \
|
||||
"${run_summary}" \
|
||||
"${model_request}" \
|
||||
"${selected_model_asset_id}" \
|
||||
@@ -226,6 +229,7 @@ import sys
|
||||
|
||||
(
|
||||
quality_path,
|
||||
detection_run_path,
|
||||
output_path,
|
||||
model_request,
|
||||
selected_model_asset_id,
|
||||
@@ -241,12 +245,18 @@ import sys
|
||||
detection_count,
|
||||
export_id,
|
||||
run_log,
|
||||
) = sys.argv[1:17]
|
||||
) = sys.argv[1:18]
|
||||
|
||||
with open(quality_path, "r", encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
if "data" not in payload:
|
||||
raise SystemExit("Quality-check list is not a canonical GeoIntel data envelope")
|
||||
with open(detection_run_path, "r", encoding="utf-8") as handle:
|
||||
detection_run_payload = json.load(handle)
|
||||
if "data" not in detection_run_payload:
|
||||
raise SystemExit("Detection run detail is not a canonical GeoIntel data envelope")
|
||||
detection_run = detection_run_payload["data"]
|
||||
run_result = detection_run.get("result_json") or {}
|
||||
items = payload["data"].get("items") or []
|
||||
quality_check = next((item for item in items if str(item.get("id")) == quality_check_id), None)
|
||||
if quality_check is None:
|
||||
@@ -270,6 +280,9 @@ summary = {
|
||||
"analysis_run_id": analysis_run_id,
|
||||
"quality_check_id": quality_check_id,
|
||||
"detection_count": int(detection_count),
|
||||
"raw_detection_count": run_result.get("raw_detection_count", int(detection_count)),
|
||||
"suppressed_detection_count": run_result.get("suppressed_detection_count", 0),
|
||||
"duplicate_iou_threshold": run_result.get("duplicate_iou_threshold"),
|
||||
"quality_status": quality_check.get("status"),
|
||||
"quality_score": quality_check.get("score"),
|
||||
"precision": metrics.get("precision"),
|
||||
@@ -285,13 +298,15 @@ summary = {
|
||||
with open(output_path, "w", encoding="utf-8") as handle:
|
||||
json.dump(summary, handle, indent=2, sort_keys=True)
|
||||
print(
|
||||
"model={model} tile={tile} overlap={overlap} threshold={threshold} detections={detections} "
|
||||
"model={model} tile={tile} overlap={overlap} threshold={threshold} detections={detections} raw={raw} suppressed={suppressed} "
|
||||
"score={score} precision={precision} recall={recall} f1={f1} matches={matches} fp={fp} fn={fn}".format(
|
||||
model=summary["model_asset_id"],
|
||||
tile=summary["tile_size"],
|
||||
overlap=summary["tile_overlap"],
|
||||
threshold=summary["threshold"],
|
||||
detections=summary["detection_count"],
|
||||
raw=summary["raw_detection_count"],
|
||||
suppressed=summary["suppressed_detection_count"],
|
||||
score=summary["quality_score"],
|
||||
precision=summary["precision"],
|
||||
recall=summary["recall"],
|
||||
@@ -346,10 +361,10 @@ with open(summary_path, "w", encoding="utf-8") as handle:
|
||||
|
||||
print("")
|
||||
print("Detection quality matrix summary")
|
||||
print("model\ttile\toverlap\tthreshold\tdetections\tscore\tprecision\trecall\tf1\tmatches\tfp\tfn")
|
||||
print("model\ttile\toverlap\tthreshold\tdetections\traw\tsuppressed\tscore\tprecision\trecall\tf1\tmatches\tfp\tfn")
|
||||
for item in items:
|
||||
print(
|
||||
"{model_asset_id}\t{tile_size}\t{tile_overlap}\t{threshold:.2f}\t{detection_count}\t{quality_score}\t{precision}\t{recall}\t{f1_score}\t{matches}\t{false_positives}\t{false_negatives}".format(
|
||||
"{model_asset_id}\t{tile_size}\t{tile_overlap}\t{threshold:.2f}\t{detection_count}\t{raw_detection_count}\t{suppressed_detection_count}\t{quality_score}\t{precision}\t{recall}\t{f1_score}\t{matches}\t{false_positives}\t{false_negatives}".format(
|
||||
**item
|
||||
)
|
||||
)
|
||||
|
||||
@@ -205,10 +205,10 @@ summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True), encoding=
|
||||
|
||||
print("")
|
||||
print("Multi-sample detection quality summary")
|
||||
print("sample\tmodel\ttile\toverlap\tthreshold\tdetections\tscore\tprecision\trecall\tf1\tmatches\tfp\tfn")
|
||||
print("sample\tmodel\ttile\toverlap\tthreshold\tdetections\traw\tsuppressed\tscore\tprecision\trecall\tf1\tmatches\tfp\tfn")
|
||||
for item in flat_items:
|
||||
print(
|
||||
"{sample_slug}\t{model_asset_id}\t{tile_size}\t{tile_overlap}\t{threshold:.2f}\t{detection_count}\t{quality_score}\t{precision}\t{recall}\t{f1_score}\t{matches}\t{false_positives}\t{false_negatives}".format(
|
||||
"{sample_slug}\t{model_asset_id}\t{tile_size}\t{tile_overlap}\t{threshold:.2f}\t{detection_count}\t{raw_detection_count}\t{suppressed_detection_count}\t{quality_score}\t{precision}\t{recall}\t{f1_score}\t{matches}\t{false_positives}\t{false_negatives}".format(
|
||||
**item
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user