Spaces:
Running on Zero
Running on Zero
deploy API
Browse files- serving/app/evidence.py +132 -132
serving/app/evidence.py
CHANGED
|
@@ -1,132 +1,132 @@
|
|
| 1 |
-
"""
|
| 2 |
-
What /v1/model-info says about the served model (blueprint B25): how similarity is computed, and the measured error
|
| 3 |
-
rates with their sources. Every rate is copied from an evaluation output in this repository: `source` is the file and
|
| 4 |
-
`keys` the path of each rate inside it ("/"-separated; `scale` 100 when the file has percent).
|
| 5 |
-
tests/test_serving.py checks the copies against those files when they are present.
|
| 6 |
-
Rates are fractions (0.012 = 1.2%). All are for run 4 (outputs/runs/stage2_run4) at its served threshold and margin.
|
| 7 |
-
"""
|
| 8 |
-
|
| 9 |
-
SIMILARITY = {
|
| 10 |
-
"measure": "cosine similarity of L2-normalised 256-number embeddings (a dot product); 1.0 = same direction",
|
| 11 |
-
"personal_gallery": "/v1/match: each medication is the normalised mean of its enrolment embeddings; a pill takes "
|
| 12 |
-
"the most similar medication, `unknown` below the threshold, `ambiguous` when the runner-up "
|
| 13 |
-
"is within the margin; the imprint read on the pill can then confirm or overturn it",
|
| 14 |
-
"catalogue": "/v1/identify: per product, the best similarity over its catalogue photos (averaged over the photos "
|
| 15 |
-
"sent of one pill); the closest products are re-ranked by the imprint read; exact search over every "
|
| 16 |
-
"photo, no approximate index",
|
| 17 |
-
"not_a_probability": "Similarities are not probabilities: pills that aren't in the catalogue still score about "
|
| 18 |
-
"0.93. /v1/identify's `confidence` is the calibrated probability, `verdict` its reading.",
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
MODEL_VERSION = "stage2_run4" # the rates below hold for this model only
|
| 22 |
-
RUN4 = "outputs/runs/stage2_run4/eval/"
|
| 23 |
-
GALLERY = {"correct_match": "correct_match", "wrong_match": "wrong_match", "own_flip": "own_flip",
|
| 24 |
-
"own_rejected": "own_rejected", "unknown_matched": "unknown_matched", "unknown_rejected": "unknown_rejected"}
|
| 25 |
-
|
| 26 |
-
ERROR_RATES = [
|
| 27 |
-
{
|
| 28 |
-
"id": "personal_gallery",
|
| 29 |
-
"what": "/v1/match, a patient with 10 medications and 3 enrolment photos each, on C3PI photos of 200 "
|
| 30 |
-
"medicines the model never saw in training",
|
| 31 |
-
"rates": {"correct_match": 0.7599, "wrong_match": 0.0004, "own_flip": 0.095, "own_rejected": 0.1447,
|
| 32 |
-
"unknown_matched": 0.0377, "unknown_rejected": 0.9483},
|
| 33 |
-
"meaning": "wrong_match: named as another of the patient's medicines; own_flip: asked to photograph again; "
|
| 34 |
-
"own_rejected: their own pill called unknown; unknown_matched: a pill that isn't theirs named "
|
| 35 |
-
"as one of theirs",
|
| 36 |
-
"source": RUN4 + "c3pi_holdout.json",
|
| 37 |
-
"keys": {k: "open_set/gallery_10/at_calibration/" + v for k, v in GALLERY.items()},
|
| 38 |
-
},
|
| 39 |
-
{
|
| 40 |
-
"id": "personal_gallery_with_imprint",
|
| 41 |
-
"what": "As personal_gallery, with the imprint check on (the GPU reader: Florence-2-large, framed as served; "
|
| 42 |
-
"the pill's own imprint is read on 47% of photos)",
|
| 43 |
-
"rates": {"correct_match": 0.7826, "wrong_match": 0.0006, "own_flip": 0.0721, "unknown_matched": 0.0351},
|
| 44 |
-
"source": RUN4 + "imprint_gallery_large_c92_distinct.json",
|
| 45 |
-
"keys": {k: "test/with_check/" + k for k in ("correct_match", "wrong_match", "own_flip", "unknown_matched")},
|
| 46 |
-
},
|
| 47 |
-
{
|
| 48 |
-
"id": "personal_gallery_other_phones",
|
| 49 |
-
"what": "As personal_gallery on MEDISEG: 32 Hong Kong products, other phones, lighting and backgrounds",
|
| 50 |
-
"rates": {"correct_match": 0.1872, "wrong_match": 0.0069, "own_flip": 0.1021, "own_rejected": 0.7038,
|
| 51 |
-
"unknown_matched": 0.0306, "unknown_rejected": 0.9309},
|
| 52 |
-
"meaning": "Outside C3PI's photo style most of a patient's own pills are called unknown (safe but unhelpful): "
|
| 53 |
-
"enrol with the patient's own phone and background",
|
| 54 |
-
"source": RUN4 + "mediseg.json",
|
| 55 |
-
"keys": {k: "open_set/gallery_10/at_calibration/" + v for k, v in GALLERY.items()},
|
| 56 |
-
},
|
| 57 |
-
{
|
| 58 |
-
"id": "same_ingredient_siblings",
|
| 59 |
-
"what": "Both siblings in one gallery (same ingredients, other strength or release form), with the imprint "
|
| 60 |
-
"check (GPU reader); look_alike: both have the same recorded shape and colour",
|
| 61 |
-
"rates": {"named_as_sibling": 0.0011, "named_as_sibling_look_alike": 0.0058},
|
| 62 |
-
"meaning": "A stress test: in look-alike pairs the imprint check settles more ties (correct 53% -> 65%) and "
|
| 63 |
-
"occasionally picks the sibling (0.35% -> 0.58%); strength is never confirmed from a photo",
|
| 64 |
-
"source": RUN4 + "lookalike_strengths_large_c92_distinct.json",
|
| 65 |
-
"keys": {"named_as_sibling": "all/with imprint/named as the sibling",
|
| 66 |
-
"named_as_sibling_look_alike": "look_alike/with imprint/named as the sibling"},
|
| 67 |
-
"scale": 100,
|
| 68 |
-
},
|
| 69 |
-
{
|
| 70 |
-
"id": "same_maker_imprints",
|
| 71 |
-
"what": "Two different medicines from one maker whose imprints share a token (Teva's '93', 'TEVA', 'TV') in one "
|
| 72 |
-
"gallery, with the imprint check (GPU reader): 3,149 pairs; look_alike: same recorded shape and colour",
|
| 73 |
-
"rates": {"named_as_the_other": 0.0001, "named_as_the_other_look_alike": 0.001,
|
| 74 |
-
"named_as_the_other_shared_tv": 0.0015},
|
| 75 |
-
"meaning": "Rare overall; '93' pairs 0.00%. The worst pair: tramadol 50 mg (TV 58) named as carvedilol 12.5 mg "
|
| 76 |
-
"(TV 7295) on 5.8% of its photos. A token two of the patient's medicines share never confirms either",
|
| 77 |
-
"source": RUN4 + "same_maker_imprints_large_c92_distinct.json",
|
| 78 |
-
"keys": {"named_as_the_other": "all/with imprint/named as the other",
|
| 79 |
-
"named_as_the_other_look_alike": "look_alike/with imprint/named as the other",
|
| 80 |
-
"named_as_the_other_shared_tv": "shared 'TV'/with imprint/named as the other"},
|
| 81 |
-
"scale": 100,
|
| 82 |
-
},
|
| 83 |
-
{
|
| 84 |
-
"id": "catalogue_identify",
|
| 85 |
-
"what": "/v1/identify on held-out C3PI photos of catalogue products (photos not in the catalogue), and on "
|
| 86 |
-
"MEDISEG pills that aren't in the catalogue; imprints read by the GPU reader",
|
| 87 |
-
"rates": {"top1_right": 0.802, "possible_match_share": 0.758, "possible_match_right": 0.867,
|
| 88 |
-
"not_in_catalogue_called_possible_match": 0.07},
|
| 89 |
-
"source": RUN4 + "identify_confidence_large_c92.json",
|
| 90 |
-
"keys": {"top1_right": "test/top1_right",
|
| 91 |
-
"possible_match_share": "test/catalogue_pills_by_verdict/possible_match/share",
|
| 92 |
-
"possible_match_right": "test/catalogue_pills_by_verdict/possible_match/top1_right",
|
| 93 |
-
"not_in_catalogue_called_possible_match": "test/other_pills_by_verdict/possible_match"},
|
| 94 |
-
},
|
| 95 |
-
{
|
| 96 |
-
"id": "pill_count",
|
| 97 |
-
"what": "Stand-in pill finder (/v1/detect, /v1/scan without boxes) on 1,000 one-pill C3PI photos (test case "
|
| 98 |
-
"D01), and 2- and 3-pill photos made from them (D02)",
|
| 99 |
-
"rates": {"exact_count_1": 0.878, "no_boxes_1": 0.068, "over_count_1": 0.054,
|
| 100 |
-
"over_count_while_reliable_1": 0.014, "exact_count_2": 0.86, "exact_count_3": 0.8511},
|
| 101 |
-
"meaning": "over_count_while_reliable: an extra box while count_reliable is true. The backend confirms with "
|
| 102 |
-
"the patient before recording an extra pill (docs/dose_rules.md)",
|
| 103 |
-
"source": "outputs/eval/pill_finder.json",
|
| 104 |
-
"keys": {"exact_count_1": "by_pills/1/exact_count", "no_boxes_1": "by_pills/1/no_boxes",
|
| 105 |
-
"over_count_1": "by_pills/1/over_count",
|
| 106 |
-
"over_count_while_reliable_1": "by_pills/1/over_count_while_reliable",
|
| 107 |
-
"exact_count_2": "by_pills/2/exact_count", "exact_count_3": "by_pills/3/exact_count"},
|
| 108 |
-
},
|
| 109 |
-
{
|
| 110 |
-
"id": "input_check",
|
| 111 |
-
"what": "Input guard (B23) on a small labelled set of web photos: 14 loose-pill photos, 8 blisters, 7 bottles "
|
| 112 |
-
"or boxes, 5 printed labels",
|
| 113 |
-
"rates": {"loose_pills_wrongly_refused": 0.0, "blister_not_caught": 0.125, "package_not_caught": 0.143,
|
| 114 |
-
"printed_not_caught": 0.0},
|
| 115 |
-
"meaning": "A small set: a sanity check, not a measured error rate",
|
| 116 |
-
"source": "src/epills/core/inference/input_type.py (docstring)",
|
| 117 |
-
"keys": {},
|
| 118 |
-
},
|
| 119 |
-
]
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
def error_rates(model_version):
|
| 123 |
-
"""The rates as /v1/model-info shows them; none for a model they weren't measured on."""
|
| 124 |
-
if model_version != MODEL_VERSION:
|
| 125 |
-
return []
|
| 126 |
-
return [{k: v for k, v in e.items() if k != "scale"} for e in ERROR_RATES]
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
def lookup(data, path):
|
| 130 |
-
for key in path.split("/"):
|
| 131 |
-
data = data[key]
|
| 132 |
-
return data
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
What /v1/model-info says about the served model (blueprint B25): how similarity is computed, and the measured error
|
| 3 |
+
rates with their sources. Every rate is copied from an evaluation output in this repository: `source` is the file and
|
| 4 |
+
`keys` the path of each rate inside it ("/"-separated; `scale` 100 when the file has percent).
|
| 5 |
+
tests/test_serving.py checks the copies against those files when they are present.
|
| 6 |
+
Rates are fractions (0.012 = 1.2%). All are for run 4 (outputs/runs/stage2_run4) at its served threshold and margin.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
SIMILARITY = {
|
| 10 |
+
"measure": "cosine similarity of L2-normalised 256-number embeddings (a dot product); 1.0 = same direction",
|
| 11 |
+
"personal_gallery": "/v1/match: each medication is the normalised mean of its enrolment embeddings; a pill takes "
|
| 12 |
+
"the most similar medication, `unknown` below the threshold, `ambiguous` when the runner-up "
|
| 13 |
+
"is within the margin; the imprint read on the pill can then confirm or overturn it",
|
| 14 |
+
"catalogue": "/v1/identify: per product, the best similarity over its catalogue photos (averaged over the photos "
|
| 15 |
+
"sent of one pill); the closest products are re-ranked by the imprint read; exact search over every "
|
| 16 |
+
"photo, no approximate index",
|
| 17 |
+
"not_a_probability": "Similarities are not probabilities: pills that aren't in the catalogue still score about "
|
| 18 |
+
"0.93. /v1/identify's `confidence` is the calibrated probability, `verdict` its reading.",
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
MODEL_VERSION = "stage2_run4@2026-10-09" # the rates below hold for this served model only (its version string)
|
| 22 |
+
RUN4 = "outputs/runs/stage2_run4/eval/"
|
| 23 |
+
GALLERY = {"correct_match": "correct_match", "wrong_match": "wrong_match", "own_flip": "own_flip",
|
| 24 |
+
"own_rejected": "own_rejected", "unknown_matched": "unknown_matched", "unknown_rejected": "unknown_rejected"}
|
| 25 |
+
|
| 26 |
+
ERROR_RATES = [
|
| 27 |
+
{
|
| 28 |
+
"id": "personal_gallery",
|
| 29 |
+
"what": "/v1/match, a patient with 10 medications and 3 enrolment photos each, on C3PI photos of 200 "
|
| 30 |
+
"medicines the model never saw in training",
|
| 31 |
+
"rates": {"correct_match": 0.7599, "wrong_match": 0.0004, "own_flip": 0.095, "own_rejected": 0.1447,
|
| 32 |
+
"unknown_matched": 0.0377, "unknown_rejected": 0.9483},
|
| 33 |
+
"meaning": "wrong_match: named as another of the patient's medicines; own_flip: asked to photograph again; "
|
| 34 |
+
"own_rejected: their own pill called unknown; unknown_matched: a pill that isn't theirs named "
|
| 35 |
+
"as one of theirs",
|
| 36 |
+
"source": RUN4 + "c3pi_holdout.json",
|
| 37 |
+
"keys": {k: "open_set/gallery_10/at_calibration/" + v for k, v in GALLERY.items()},
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"id": "personal_gallery_with_imprint",
|
| 41 |
+
"what": "As personal_gallery, with the imprint check on (the GPU reader: Florence-2-large, framed as served; "
|
| 42 |
+
"the pill's own imprint is read on 47% of photos)",
|
| 43 |
+
"rates": {"correct_match": 0.7826, "wrong_match": 0.0006, "own_flip": 0.0721, "unknown_matched": 0.0351},
|
| 44 |
+
"source": RUN4 + "imprint_gallery_large_c92_distinct.json",
|
| 45 |
+
"keys": {k: "test/with_check/" + k for k in ("correct_match", "wrong_match", "own_flip", "unknown_matched")},
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"id": "personal_gallery_other_phones",
|
| 49 |
+
"what": "As personal_gallery on MEDISEG: 32 Hong Kong products, other phones, lighting and backgrounds",
|
| 50 |
+
"rates": {"correct_match": 0.1872, "wrong_match": 0.0069, "own_flip": 0.1021, "own_rejected": 0.7038,
|
| 51 |
+
"unknown_matched": 0.0306, "unknown_rejected": 0.9309},
|
| 52 |
+
"meaning": "Outside C3PI's photo style most of a patient's own pills are called unknown (safe but unhelpful): "
|
| 53 |
+
"enrol with the patient's own phone and background",
|
| 54 |
+
"source": RUN4 + "mediseg.json",
|
| 55 |
+
"keys": {k: "open_set/gallery_10/at_calibration/" + v for k, v in GALLERY.items()},
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"id": "same_ingredient_siblings",
|
| 59 |
+
"what": "Both siblings in one gallery (same ingredients, other strength or release form), with the imprint "
|
| 60 |
+
"check (GPU reader); look_alike: both have the same recorded shape and colour",
|
| 61 |
+
"rates": {"named_as_sibling": 0.0011, "named_as_sibling_look_alike": 0.0058},
|
| 62 |
+
"meaning": "A stress test: in look-alike pairs the imprint check settles more ties (correct 53% -> 65%) and "
|
| 63 |
+
"occasionally picks the sibling (0.35% -> 0.58%); strength is never confirmed from a photo",
|
| 64 |
+
"source": RUN4 + "lookalike_strengths_large_c92_distinct.json",
|
| 65 |
+
"keys": {"named_as_sibling": "all/with imprint/named as the sibling",
|
| 66 |
+
"named_as_sibling_look_alike": "look_alike/with imprint/named as the sibling"},
|
| 67 |
+
"scale": 100,
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"id": "same_maker_imprints",
|
| 71 |
+
"what": "Two different medicines from one maker whose imprints share a token (Teva's '93', 'TEVA', 'TV') in one "
|
| 72 |
+
"gallery, with the imprint check (GPU reader): 3,149 pairs; look_alike: same recorded shape and colour",
|
| 73 |
+
"rates": {"named_as_the_other": 0.0001, "named_as_the_other_look_alike": 0.001,
|
| 74 |
+
"named_as_the_other_shared_tv": 0.0015},
|
| 75 |
+
"meaning": "Rare overall; '93' pairs 0.00%. The worst pair: tramadol 50 mg (TV 58) named as carvedilol 12.5 mg "
|
| 76 |
+
"(TV 7295) on 5.8% of its photos. A token two of the patient's medicines share never confirms either",
|
| 77 |
+
"source": RUN4 + "same_maker_imprints_large_c92_distinct.json",
|
| 78 |
+
"keys": {"named_as_the_other": "all/with imprint/named as the other",
|
| 79 |
+
"named_as_the_other_look_alike": "look_alike/with imprint/named as the other",
|
| 80 |
+
"named_as_the_other_shared_tv": "shared 'TV'/with imprint/named as the other"},
|
| 81 |
+
"scale": 100,
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"id": "catalogue_identify",
|
| 85 |
+
"what": "/v1/identify on held-out C3PI photos of catalogue products (photos not in the catalogue), and on "
|
| 86 |
+
"MEDISEG pills that aren't in the catalogue; imprints read by the GPU reader",
|
| 87 |
+
"rates": {"top1_right": 0.802, "possible_match_share": 0.758, "possible_match_right": 0.867,
|
| 88 |
+
"not_in_catalogue_called_possible_match": 0.07},
|
| 89 |
+
"source": RUN4 + "identify_confidence_large_c92.json",
|
| 90 |
+
"keys": {"top1_right": "test/top1_right",
|
| 91 |
+
"possible_match_share": "test/catalogue_pills_by_verdict/possible_match/share",
|
| 92 |
+
"possible_match_right": "test/catalogue_pills_by_verdict/possible_match/top1_right",
|
| 93 |
+
"not_in_catalogue_called_possible_match": "test/other_pills_by_verdict/possible_match"},
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"id": "pill_count",
|
| 97 |
+
"what": "Stand-in pill finder (/v1/detect, /v1/scan without boxes) on 1,000 one-pill C3PI photos (test case "
|
| 98 |
+
"D01), and 2- and 3-pill photos made from them (D02)",
|
| 99 |
+
"rates": {"exact_count_1": 0.878, "no_boxes_1": 0.068, "over_count_1": 0.054,
|
| 100 |
+
"over_count_while_reliable_1": 0.014, "exact_count_2": 0.86, "exact_count_3": 0.8511},
|
| 101 |
+
"meaning": "over_count_while_reliable: an extra box while count_reliable is true. The backend confirms with "
|
| 102 |
+
"the patient before recording an extra pill (docs/dose_rules.md)",
|
| 103 |
+
"source": "outputs/eval/pill_finder.json",
|
| 104 |
+
"keys": {"exact_count_1": "by_pills/1/exact_count", "no_boxes_1": "by_pills/1/no_boxes",
|
| 105 |
+
"over_count_1": "by_pills/1/over_count",
|
| 106 |
+
"over_count_while_reliable_1": "by_pills/1/over_count_while_reliable",
|
| 107 |
+
"exact_count_2": "by_pills/2/exact_count", "exact_count_3": "by_pills/3/exact_count"},
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"id": "input_check",
|
| 111 |
+
"what": "Input guard (B23) on a small labelled set of web photos: 14 loose-pill photos, 8 blisters, 7 bottles "
|
| 112 |
+
"or boxes, 5 printed labels",
|
| 113 |
+
"rates": {"loose_pills_wrongly_refused": 0.0, "blister_not_caught": 0.125, "package_not_caught": 0.143,
|
| 114 |
+
"printed_not_caught": 0.0},
|
| 115 |
+
"meaning": "A small set: a sanity check, not a measured error rate",
|
| 116 |
+
"source": "src/epills/core/inference/input_type.py (docstring)",
|
| 117 |
+
"keys": {},
|
| 118 |
+
},
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def error_rates(model_version):
|
| 123 |
+
"""The rates as /v1/model-info shows them; none for a model they weren't measured on."""
|
| 124 |
+
if model_version != MODEL_VERSION:
|
| 125 |
+
return []
|
| 126 |
+
return [{k: v for k, v in e.items() if k != "scale"} for e in ERROR_RATES]
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def lookup(data, path):
|
| 130 |
+
for key in path.split("/"):
|
| 131 |
+
data = data[key]
|
| 132 |
+
return data
|