Replace hardcoded config and directory scan with YAML config and explicit clip list

- config.py constants -> config/config.yaml (user-editable, git-ignored)
- Questions and defaults now defined in the YAML, including per-question defaults
- ClipSelector no longer scans the data dir; reads a user-provided clips.txt instead
- Removed --daily / --time / --skip-existing-day args
- video_loader now samples frames evenly across the full clip
- pyyaml added as a dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 13:42:48 +02:00
parent 5f8c579247
commit 5468712a4a
11 changed files with 240 additions and 215 deletions

View File

@@ -18,7 +18,7 @@ from PySide6.QtWidgets import (
)
from .clip_selector import ClipSelector
from .config import DEFAULTS, QUESTIONS, Config
from .config import AppConfig
from .mask_canvas import MaskCanvas
from .video_loader import load_frames
@@ -26,25 +26,20 @@ from .video_loader import load_frames
class Annotator(QMainWindow):
def __init__(
self,
data_dir: Path,
out_dir: Path,
config: AppConfig,
clip: str = None,
target_time: str = None,
daily: bool = False,
extras: bool = False,
skip_existing_day: bool = False,
):
super().__init__()
self.out_dir = Path(out_dir)
self.cfg = config
self.out_dir = Path(config.out_dir)
self.extras = extras
self.selector = ClipSelector(
data_dir=Path(data_dir),
data_dir=Path(config.data_dir),
out_dir=self.out_dir,
target_time=target_time,
daily=daily,
skip_existing_day=skip_existing_day,
clips_file=Path(config.clips_file),
)
self.setWindowTitle("River Annotator")
@@ -53,10 +48,13 @@ class Annotator(QMainWindow):
self._init_timer()
# ── clip loading ───────────────────────────────────────────────
def _load_clip(self, specific: str = None, next_day: bool = False):
self.filename = self.selector.next(specific=specific, next_day=next_day)
def _load_clip(self, specific: str = None):
self.filename = self.selector.next(specific=specific)
self.frames, self.fps, self.dh, self.dw, self.h, self.w = load_frames(
self.filename, Config.MAX_FRAMES
self.filename,
self.cfg.max_frames,
self.cfg.display_max,
self.cfg.fps_fallback,
)
self._pending_answers = self._read_saved_answers()
@@ -135,23 +133,22 @@ class Annotator(QMainWindow):
def _build_question_panel(self) -> QVBoxLayout:
vbox = QVBoxLayout()
for section, qs in QUESTIONS:
for section, qs in self.cfg.get_questions():
group = QGroupBox(section)
gvbox = QVBoxLayout()
for key, label, options in qs:
for key, label, options, default in qs:
gvbox.addWidget(QLabel(label))
btn_group = QButtonGroup(self)
row = QHBoxLayout()
buttons = []
default_value = DEFAULTS.get(key)
for opt in options:
btn = QRadioButton(opt)
btn_group.addButton(btn)
row.addWidget(btn)
buttons.append(btn)
if default_value == opt:
if default == opt:
btn.setChecked(True)
if default_value is None and buttons:
if default is None and buttons:
buttons[-1].setChecked(True)
self.q_widgets[key] = (btn_group, buttons, options)
gvbox.addLayout(row)
@@ -246,8 +243,8 @@ class Annotator(QMainWindow):
if answers:
self._set_answers(answers)
def _advance_clip(self, next_day: bool):
self._load_clip(next_day=next_day)
def _advance_clip(self):
self._load_clip()
self.frame_i = 0
self.mc.load_clip(
self.frames,
@@ -262,7 +259,7 @@ class Annotator(QMainWindow):
def next_clip(self):
self.save()
self._advance_clip(next_day=self.selector.daily)
self._advance_clip()
def skip_clip(self):
self._advance_clip(next_day=self.selector.daily)
self._advance_clip()