chore: sync annotation UI, shortcuts, and misc updates
This commit is contained in:
1 parent
cf4c3370e1
commit
51e74a253e
11 files changed
+102
-36
No files matched your search
@@ -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
@@ -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"})
|
||||
|
||||
Reference in new issue
Block a user