From 4aa1e326819e30aa9dae6c7937d35326bd919f47 Mon Sep 17 00:00:00 2001 From: asreva Date: Wed, 20 May 2026 14:08:47 +0200 Subject: [PATCH] Add end-of-clips dialog and --no-skip flag Show a modal dialog when all clips have been processed and quit cleanly. Add --no-skip CLI flag to include already-annotated clips (default remains to skip them). Co-Authored-By: Claude Sonnet 4.6 --- README.md | 3 ++- .../annotation_script.py | 19 +++++++++++++++++-- src/river_annotation_tool/annotator.py | 15 ++++++++++++++- src/river_annotation_tool/clip_selector.py | 4 +++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index edabfa5..be5e0e6 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ python -m river_annotation_tool.annotation_script | `--clips` | *(from config)* | Override `clips_file` from config | | `--clip` | *(first unannotated in list)* | Open a specific clip by stem name (e.g. `left_20230501`) | | `--extras` | off | Also save GIFs and extra PNGs (see Output section) | +| `--no-skip` | off | Show already-annotated clips instead of skipping them | ### Typical workflows @@ -109,7 +110,7 @@ Add, remove, or reorder questions directly in the YAML — the UI rebuilds autom ## Clip list file -`config/clips.txt` lists the clip filenames to annotate, one per line. Lines starting with `#` are ignored. Clips are processed in order; already-annotated clips (those with an existing `mask.png`) are skipped automatically. +`config/clips.txt` lists the clip filenames to annotate, one per line. Lines starting with `#` are ignored. Clips are processed in order; already-annotated clips (those with an existing `mask.png`) are skipped automatically. Pass `--no-skip` to include them. When the last clip is reached, a dialog appears and the app exits. ``` # Example clips.txt diff --git a/src/river_annotation_tool/annotation_script.py b/src/river_annotation_tool/annotation_script.py index 72b3ab7..0ac4864 100644 --- a/src/river_annotation_tool/annotation_script.py +++ b/src/river_annotation_tool/annotation_script.py @@ -1,4 +1,5 @@ import argparse +import sys from pathlib import Path from matplotlib import use @@ -6,7 +7,7 @@ from matplotlib import use use("QtAgg") -from PySide6.QtWidgets import QApplication +from PySide6.QtWidgets import QApplication, QMessageBox from .annotator import Annotator from .config import load_config @@ -30,6 +31,11 @@ def parse_args(): action="store_true", help="Also save GIFs, frame PNG, overlay PNG, and mask_vis PNG alongside the mask.", ) + parser.add_argument( + "--no-skip", + action="store_true", + help="Show already-annotated clips instead of skipping them.", + ) return parser.parse_args() @@ -45,6 +51,15 @@ if __name__ == "__main__": cfg.clips_file = args.clips app = QApplication([]) - win = Annotator(cfg, clip=args.clip, extras=args.extras) + try: + win = Annotator( + cfg, + clip=args.clip, + extras=args.extras, + skip_annotated=not args.no_skip, + ) + except RuntimeError as e: + QMessageBox.information(None, "No clips", str(e)) + sys.exit(0) win.show() app.exec() diff --git a/src/river_annotation_tool/annotator.py b/src/river_annotation_tool/annotator.py index 921920e..f484c9d 100644 --- a/src/river_annotation_tool/annotator.py +++ b/src/river_annotation_tool/annotator.py @@ -6,11 +6,13 @@ import numpy as np from PIL import Image from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( + QApplication, QButtonGroup, QGroupBox, QHBoxLayout, QLabel, QMainWindow, + QMessageBox, QPushButton, QRadioButton, QVBoxLayout, @@ -29,6 +31,7 @@ class Annotator(QMainWindow): config: AppConfig, clip: str = None, extras: bool = False, + skip_annotated: bool = True, ): super().__init__() @@ -42,6 +45,7 @@ class Annotator(QMainWindow): clips_file=Path(config.clips_file), mask_filename=config.filenames.mask, zip_extension=config.filenames.zip_extension, + skip_annotated=skip_annotated, ) self.setWindowTitle("River Annotator") @@ -251,7 +255,16 @@ class Annotator(QMainWindow): self._set_answers(answers) def _advance_clip(self): - self._load_clip() + try: + self._load_clip() + except RuntimeError: + msg = QMessageBox(self) + msg.setWindowTitle("All done!") + msg.setText("You have reached the end of all clips.") + msg.setStandardButtons(QMessageBox.StandardButton.Ok) + msg.exec() + QApplication.instance().quit() + return self.frame_i = 0 self.mc.load_clip( self.frames, diff --git a/src/river_annotation_tool/clip_selector.py b/src/river_annotation_tool/clip_selector.py index c7ef319..02b34ae 100644 --- a/src/river_annotation_tool/clip_selector.py +++ b/src/river_annotation_tool/clip_selector.py @@ -9,11 +9,13 @@ class ClipSelector: clips_file: Path, mask_filename: str = "mask.png", zip_extension: str = ".zip", + skip_annotated: bool = True, ): self.data_dir = data_dir self.out_dir = out_dir self.mask_filename = mask_filename self.zip_extension = zip_extension + self.skip_annotated = skip_annotated self.clips = self._load_clips(clips_file) self.index = 0 @@ -46,6 +48,6 @@ class ClipSelector: while self.index < len(self.clips): clip = self.clips[self.index] self.index += 1 - if not self.is_annotated(clip): + if not self.skip_annotated or not self.is_annotated(clip): return clip raise RuntimeError("No remaining clips to annotate")