Files
Copies/copienator/annotation_actions.py

88 lines
3.5 KiB
Python

from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable
from pathlib import Path
from typing import Any
from .json_io import read_json
from .feedback_boxes import valid_feedback_box
Log = Callable[[str], None]
def apply_checkbox_actions(
labels_data: dict[str, dict[str, Any]],
actions: list[dict[str, Any]],
log: Log,
) -> set[str]:
actions_by_label: defaultdict[str, list[dict[str, Any]]] = defaultdict(list)
for action in actions:
actions_by_label[str(action.get("label", ""))].append(action)
dirty_labels: set[str] = set()
for label, label_actions in actions_by_label.items():
if label not in labels_data:
continue
result = labels_data[label]["result"]
feedbacks = result.get("feedback", [])
# Match the renderer's fallback for invalid boxes so checkbox indices
# still address the right comment when returned annotations are read.
global_feedbacks = [item for item in feedbacks if not valid_feedback_box(item.get("box_2d"))]
local_feedbacks = [item for item in feedbacks if valid_feedback_box(item.get("box_2d"))]
local_feedbacks.sort(key=lambda item: item["box_2d"][0])
for action in label_actions:
action_type = action.get("type")
if action_type == "score":
result["score"] = action.get("value")
dirty_labels.add(label)
log(f" > Updated score for {label} to {action.get('value')}")
elif action_type == "clear_all":
for feedback in feedbacks:
feedback["to_delete"] = True
if feedback.get("box_2d"):
feedback["norectangle"] = True
dirty_labels.add(label)
log(f" > Cleared all feedbacks in {label}")
elif action_type == "del_global":
index = int(action.get("index", -1))
if 0 <= index < len(global_feedbacks):
global_feedbacks[index]["to_delete"] = True
dirty_labels.add(label)
log(f" > Deleted global feedback in {label}")
elif action_type in {"del_local", "del_local_rect"}:
index = int(action.get("index", -1))
if 0 <= index < len(local_feedbacks):
target = local_feedbacks[index]
if action_type == "del_local":
target["to_delete"] = True
log(f" > Deleted local feedback in {label}")
else:
target["norectangle"] = True
log(f" > Deleted rectangle in {label}")
dirty_labels.add(label)
return dirty_labels
def apply_score_overrides(
labels_data: dict[str, dict[str, Any]],
score_path: Path,
log: Log,
) -> set[str]:
if not score_path.exists():
return set()
loaded = read_json(score_path)
if not isinstance(loaded, dict):
raise TypeError(f"Expected a JSON object in {score_path}")
dirty: set[str] = set()
for label, score in loaded.items():
if label not in labels_data:
continue
current = str(labels_data[label]["result"].get("score", 0))
if current != str(score):
labels_data[label]["result"]["score"] = score
dirty.add(label)
log(f" > Overrode score for {label} to {score} from score.json")
return dirty