chore: sync annotation UI, shortcuts, and misc updates

This commit is contained in:
Andrew-AAAA committed 2026-09-10 09:14:28 +07:00
1 parent cf4c3370e1
commit 51e74a253e
11 files changed
+102 -36

No files matched your search

+4 -2
View File
@@ -41,9 +41,11 @@ class AssistRequest(BaseModel):
class PoolExemplar(BaseModel):
# Normalized xyxy against the frame, as drawn on the review canvas.
box: List[float]
# Normalized xyxy box or [x, y] point against the frame.
box: Optional[List[float]] = None
point: Optional[List[float]] = None
positive: bool = True
kind: Optional[str] = None
class ExemplarLabelRequest(BaseModel):
+29 -16
View File
@@ -143,8 +143,17 @@ def label(frame_id: int, class_id: int, exemplars: List[dict],
positives, negatives = [], []
for item in exemplars:
box = review.validate({"type": "bbox", "points": item["box"]}, "bbox")["points"]
(positives if item.get("positive", True) else negatives).append(box)
is_pos = bool(item.get("positive", True))
target_list = positives if is_pos else negatives
if item.get("point") is not None:
px, py = float(item["point"][0]), float(item["point"][1])
target_list.append({"kind": "point", "cxcywh": [px, py, 0.03, 0.03], "point": [px, py]})
elif item.get("box") is not None:
box = review.validate({"type": "bbox", "points": item["box"]}, "bbox")["points"]
target_list.append({"kind": "box", "cxcywh": _cxcywh(box), "box": box})
elif "points" in item and len(item["points"]) == 2:
px, py = float(item["points"][0]), float(item["points"][1])
target_list.append({"kind": "point", "cxcywh": [px, py, 0.03, 0.03], "point": [px, py]})
if not positives and not negatives:
raise review.ReviewError("No exemplars to run")
@@ -154,15 +163,11 @@ def label(frame_id: int, class_id: int, exemplars: List[dict],
if not jobs.gpu_lock.acquire(timeout=5):
busy = jobs.running_types()
kind = busy[0] if busy else "background"
drawn = [{"geometry": _rect(box, label_type), "score": 1.0, "source": "manual"}
for box in positives]
drawn = [{"geometry": _rect(p["box"], label_type), "score": 1.0, "source": "manual"}
for p in positives if p["kind"] == "box"]
if apply:
# Never the replace path here: with no detections to put back, it
# would wipe the class and leave only the drawings. Applying a
# detection-less run just files the drawings and honours the
# negatives.
_append_drawn(frame_id, class_id, drawn)
_drop_negative_overlaps(frame_id, class_id, negatives)
_drop_negative_overlaps(frame_id, class_id, [n["box"] for n in negatives if n["kind"] == "box"])
return _result(frame_id, drawn, apply, redetected=False,
message=f"The GPU is busy with a {kind} job — this is your drawing "
"only, nothing was detected")
@@ -178,8 +183,8 @@ def label(frame_id: int, class_id: int, exemplars: List[dict],
state = engine.open_state(image)
found = engine.apply_prompts(
state, threshold=threshold, text=prompt,
exemplars=[{"box": _cxcywh(box), "positive": True} for box in positives]
+ [{"box": _cxcywh(box), "positive": False} for box in negatives],
exemplars=[{"box": p["cxcywh"], "positive": True} for p in positives]
+ [{"box": n["cxcywh"], "positive": False} for n in negatives],
)
finally:
jobs.gpu_lock.release()
@@ -198,9 +203,11 @@ def label(frame_id: int, class_id: int, exemplars: List[dict],
detections = [(_norm_box(d.box, width, height), d) for d in found]
items: List[dict] = []
# The user's own boxes first, so the duplicate check below measures against
# what they drew rather than the other way round.
for box in positives:
# Manual drawn boxes first:
for item in positives:
if item["kind"] != "box":
continue
box = item["box"]
geometry = _rect(box, label_type)
if label_type != "bbox":
snapped = _snap(box, detections, width, height)
@@ -208,10 +215,16 @@ def label(frame_id: int, class_id: int, exemplars: List[dict],
geometry = snapped
items.append({"geometry": geometry, "score": 1.0, "source": "manual"})
manual_boxes = [p["box"] for p in positives if p["kind"] == "box"]
negative_boxes = [n["box"] for n in negatives if n["kind"] == "box"]
negative_points = [n["point"] for n in negatives if n["kind"] == "point"]
for norm, detection in detections:
if any(_iou(norm, box) >= NEGATIVE_IOU for box in negatives):
if any(_iou(norm, box) >= NEGATIVE_IOU for box in negative_boxes):
continue
if any(_iou(norm, box) >= DUPLICATE_IOU for box in positives):
if any(norm[0] <= pt[0] <= norm[2] and norm[1] <= pt[1] <= norm[3] for pt in negative_points):
continue
if any(_iou(norm, box) >= DUPLICATE_IOU for box in manual_boxes):
continue
for geometry in _detection_shapes(detection, norm, width, height, label_type):
items.append({"geometry": geometry, "score": detection.score, "source": "auto"})