Attach the catalog row to accepted still-image answers
Some checks failed
tests / model-free (pull_request) Has been cancelled
Some checks failed
tests / model-free (pull_request) Has been cancelled
`classify_boxes` returned only the identity, so every accepted still-image answer reached the client as a bare SKU id with no name or price. Measured through the real server on add/image6.jpg: 41 of 41 accepted detections had no `product`. The live path attached the row; the still path did not, which is the F-02 finding the decision document recorded as still live in production. The row is attached where the decision is made, so both paths carry it, and `recognize` passes it through to the wire. Verified through the running server: 41 of 41 accepted answers now carry product_name, price, and stock counts. Also corrects the decision document's full-resolution claim, which was measured before the reference-upscaling and text-signature fixes: it said 77% precision with 11 false accepts and a hub alarm, where the current lane reads 88% with 5 and no alarm. The accept line does not need to differ by input path - correct facings sit at p25 2.07 (full) and 2.87 (live), wrong ones at p75 1.00 and 1.66, so 2.0 is the boundary in both - and the results table and the stream G1 numbers are refreshed to the current measurements.
This commit is contained in:
parent
b6c87c45ea
commit
771e9285d7
3 changed files with 116 additions and 34 deletions
|
|
@ -685,13 +685,20 @@ class RecognitionEngine:
|
|||
# operational failure must stay distinguishable from absence.
|
||||
decision = decide_identity({}, method=method, scale=self.identity_scale)
|
||||
decision = identity_payload(decision)
|
||||
results.append(
|
||||
{
|
||||
"detection_id": int(box["id"]),
|
||||
"box": list(box["box"]),
|
||||
"identity": decision,
|
||||
}
|
||||
)
|
||||
row = {
|
||||
"detection_id": int(box["id"]),
|
||||
"box": list(box["box"]),
|
||||
"identity": decision,
|
||||
}
|
||||
# The catalog row travels with an accepted answer. Without it the
|
||||
# phone gets a bare SKU id and has to look the name and price up
|
||||
# itself, which is what left 41 of 41 accepted still-image answers
|
||||
# without a product row.
|
||||
if decision.get("state") == STATE_RECOGNIZED:
|
||||
product = self.product(decision.get("sku_id"))
|
||||
if product is not None:
|
||||
row["product"] = product
|
||||
results.append(row)
|
||||
return results
|
||||
|
||||
def inspect(self, image: Any, box: Sequence[int], method: Optional[str] = None) -> Dict[str, Any]:
|
||||
|
|
@ -905,16 +912,18 @@ class RecognitionEngine:
|
|||
identities: List[Dict[str, Any]] = []
|
||||
if classify and boxes:
|
||||
identities = self.classify_boxes(image, boxes, method=method)
|
||||
identities_by_id = {row["detection_id"]: row["identity"] for row in identities}
|
||||
identities_by_id = {row["detection_id"]: row for row in identities}
|
||||
for box in boxes:
|
||||
detections.append(
|
||||
{
|
||||
"id": box["id"],
|
||||
"box": box["box"],
|
||||
"confidence": box["confidence"],
|
||||
"identity": identities_by_id.get(box["id"], identity_payload(None)),
|
||||
}
|
||||
)
|
||||
row = identities_by_id.get(box["id"])
|
||||
detection = {
|
||||
"id": box["id"],
|
||||
"box": box["box"],
|
||||
"confidence": box["confidence"],
|
||||
"identity": (row or {}).get("identity") or identity_payload(None),
|
||||
}
|
||||
if (row or {}).get("product") is not None:
|
||||
detection["product"] = row["product"]
|
||||
detections.append(detection)
|
||||
slots = self.empty_slots(image.shape, boxes) if with_empty_slots else []
|
||||
classified_at = time.perf_counter()
|
||||
summary = self.summarize(detections, slots)
|
||||
|
|
|
|||
|
|
@ -244,6 +244,33 @@ class EngineClassificationTests(unittest.TestCase):
|
|||
self.assertEqual(rows[0]["identity"]["state"], "recognized")
|
||||
self.assertEqual(rows[0]["identity"]["sku_id"], "sku_a")
|
||||
|
||||
def test_an_accepted_detection_carries_its_catalog_row(self):
|
||||
"""The phone must not have to look up a name and price from a bare SKU.
|
||||
|
||||
`classify_boxes` returned only the identity, so every accepted
|
||||
still-image answer reached the client as an id with no product row:
|
||||
measured through the real server, 41 of 41 accepted detections had no
|
||||
`product`. The live path attached it; the still path did not.
|
||||
"""
|
||||
engine, _, _, _ = make_engine()
|
||||
detections = [
|
||||
{"id": 0, "box": [10, 10, 60, 90], "confidence": 0.9,
|
||||
"identity": identity_payload(None)},
|
||||
]
|
||||
rows = engine.classify_boxes(make_image(), detections, method="hybrid")
|
||||
self.assertEqual(rows[0]["identity"]["state"], "recognized")
|
||||
self.assertEqual(rows[0]["product"]["product_name"], "Alpha")
|
||||
self.assertEqual(rows[0]["product"]["price"], 100.0)
|
||||
|
||||
result = engine.recognize(make_image(), method="hybrid")
|
||||
accepted = [
|
||||
row for row in result["detections"]
|
||||
if row["identity"]["identity_level"] == "accepted"
|
||||
]
|
||||
self.assertTrue(accepted, "the fake detector and matcher accept a box")
|
||||
for row in accepted:
|
||||
self.assertIn("product", row, "an accepted answer must carry its row")
|
||||
|
||||
def test_a_failing_crop_reports_unrecognized_not_pending(self):
|
||||
"""One bad crop must not fail the batch, and must not read as pending.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue