Merge pull request 'changes-wrt-feedback' (#1) from changes-wrt-feedback into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-06-03 12:15:49 +02:00
5 changed files with 45 additions and 25 deletions

View File

@@ -89,6 +89,8 @@ The `clips_file` (the list of clip filenames to annotate) is always read from th
## Usage
> **River annotation reference:** If you are annotating river footage, consult the [river annotation guide](https://docs.google.com/document/d/1iPN9JxiDtb60kC0yjO8tTM0XEfDTGM5ysw33WY-BEDQ/edit?usp=sharing) for guidance on how to draw masks correctly.
```sh
uv run python -m clip_annotator
# or, if you have the venv activated:

View File

@@ -1,8 +1,8 @@
storage: local # 'local' or 's3'
# Required: set these to your actual paths (local path or bucket/prefix for S3)
data_dir: # e.g. /data/clips or for S3: hydroscan-data/GRAMMONT/clips
out_dir: # e.g. /data/out or for S3: hydroscan-data/annotations/<name>/ # Put your name here
data_dir: # e.g. data/clips or for S3: hydroscan-data/GRAMMONT/
out_dir: # e.g. data/out or for S3: hydroscan-data/annotations/<name>/ # Put your name here
# For S3 credentials, copy .env.example to .env and fill in:
# S3_ACCESS_KEY, S3_SECRET_ACCESS_KEY, S3_ENDPOINT_URL

View File

@@ -1,34 +1,38 @@
- section: River
items:
- key: flow
label: Flow Regime
options: [Turbulent, Laminar, Uncertain]
default: Laminar
label: Flow
options: ["No", Standard, High, Uncertain]
default: Standard
- key: shadows
label: Strong Shadows
options: [Yes, No, Uncertain]
default: No
options: ["Yes", "No", Uncertain]
default: "No"
- key: sediments
label: Sediments
options: ["Yes", "No", Uncertain]
default: "No"
- key: artifacts
label: Artifacts on River
options: [Yes, No, Uncertain]
default: No
options: ["Yes", "No", Uncertain]
default: "No"
- section: Scene
items:
- key: lighting
label: Lighting
options: [Day, Night, Uncertain]
default: Day
options: [Bright, Dark, Uncertain]
default: Bright
- key: exposure
label: Exposure
options: [Overexposed, Underexposed, Both, Normal, Uncertain]
default: Normal
- section: Weather
items:
- key: snowing
label: Snowing
options: [Yes, No, Uncertain]
default: No
- key: precipitation
label: Precipitation
options: ["Yes", "No", Uncertain]
default: "No"
- key: snow_on_ground
label: Snow on Ground
options: [Yes, No, Uncertain]
default: No
options: ["Yes", "No", Uncertain]
default: "No"

View File

@@ -148,6 +148,15 @@ class Annotator(QMainWindow):
return None
return self._json_read(meta_path)
# ── helpers ────────────────────────────────────────────────────
def _update_window_title(self):
total = len(self.selector.clips)
try:
idx = self.selector.clips.index(self.filename) + 1
except ValueError:
idx = "?"
self.setWindowTitle(f"Clip Annotator ({idx} / {total})")
# ── UI setup ───────────────────────────────────────────────────
def _init_ui(self):
self.mc = MaskCanvas(self.frames, self.dh, self.dw)
@@ -213,9 +222,9 @@ class Annotator(QMainWindow):
vert_panel = QHBoxLayout()
vert_panel.setContentsMargins(0, 0, 4, 0)
for label_text, slider, reset_btn in [
("Brightness", self.mc.brightness_slider, self.mc.brightness_reset),
("Contrast", self.mc.contrast_slider, self.mc.contrast_reset),
("Gamma", self.mc.gamma_slider, self.mc.gamma_reset),
("B", self.mc.brightness_slider, self.mc.brightness_reset),
("C", self.mc.contrast_slider, self.mc.contrast_reset),
("G", self.mc.gamma_slider, self.mc.gamma_reset),
]:
col = QVBoxLayout()
lbl = QLabel(label_text)
@@ -243,8 +252,9 @@ class Annotator(QMainWindow):
right_widget.setLayout(question_panel)
main = QHBoxLayout()
main.addWidget(left_widget, 3)
main.addWidget(right_widget, 1)
right_widget.setMaximumWidth(160)
main.addWidget(left_widget, 1)
main.addWidget(right_widget, 0)
container = QWidget()
container.setLayout(main)
@@ -264,6 +274,8 @@ class Annotator(QMainWindow):
self._set_answers(self._pending_answers)
self._pending_answers = None
self._update_window_title()
def _build_question_panel(self) -> QVBoxLayout:
vbox = QVBoxLayout()
for section, qs in self.cfg.get_questions():
@@ -272,19 +284,19 @@ class Annotator(QMainWindow):
for key, label, options, default in qs:
gvbox.addWidget(QLabel(label))
btn_group = QButtonGroup(self)
row = QHBoxLayout()
col = QVBoxLayout()
buttons = []
for opt in options:
btn = QRadioButton(opt)
btn_group.addButton(btn)
row.addWidget(btn)
col.addWidget(btn)
buttons.append(btn)
if default == opt:
btn.setChecked(True)
if default is None and buttons:
buttons[-1].setChecked(True)
self.q_widgets[key] = (btn_group, buttons, options)
gvbox.addLayout(row)
gvbox.addLayout(col)
group.setLayout(gvbox)
vbox.addWidget(group)
return vbox
@@ -408,6 +420,7 @@ class Annotator(QMainWindow):
self._set_answers(self._pending_answers)
self._pending_answers = None
self.btn_prev.setEnabled(self.history_pos > 0)
self._update_window_title()
def _advance_clip(self):
if self.history_pos < len(self.history) - 1:

View File

@@ -41,6 +41,7 @@ class MaskCanvas:
def _build_figure(self, frames):
self.fig = Figure(figsize=(self.dw / 80, self.dh / 80))
self.fig.subplots_adjust(left=0, right=1, top=0.97, bottom=0)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111)
self.ax.axis("off")