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 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ python -m river_annotation_tool.annotation_script
|
|||||||
| `--clips` | *(from config)* | Override `clips_file` from config |
|
| `--clips` | *(from config)* | Override `clips_file` from config |
|
||||||
| `--clip` | *(first unannotated in list)* | Open a specific clip by stem name (e.g. `left_20230501`) |
|
| `--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) |
|
| `--extras` | off | Also save GIFs and extra PNGs (see Output section) |
|
||||||
|
| `--no-skip` | off | Show already-annotated clips instead of skipping them |
|
||||||
|
|
||||||
### Typical workflows
|
### Typical workflows
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ Add, remove, or reorder questions directly in the YAML — the UI rebuilds autom
|
|||||||
|
|
||||||
## Clip list file
|
## 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
|
# Example clips.txt
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from matplotlib import use
|
from matplotlib import use
|
||||||
@@ -6,7 +7,7 @@ from matplotlib import use
|
|||||||
|
|
||||||
use("QtAgg")
|
use("QtAgg")
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||||
|
|
||||||
from .annotator import Annotator
|
from .annotator import Annotator
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
@@ -30,6 +31,11 @@ def parse_args():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Also save GIFs, frame PNG, overlay PNG, and mask_vis PNG alongside the mask.",
|
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()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -45,6 +51,15 @@ if __name__ == "__main__":
|
|||||||
cfg.clips_file = args.clips
|
cfg.clips_file = args.clips
|
||||||
|
|
||||||
app = QApplication([])
|
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()
|
win.show()
|
||||||
app.exec()
|
app.exec()
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import numpy as np
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PySide6.QtCore import QTimer
|
from PySide6.QtCore import QTimer
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
QButtonGroup,
|
QButtonGroup,
|
||||||
QGroupBox,
|
QGroupBox,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
|
QMessageBox,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QRadioButton,
|
QRadioButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
@@ -29,6 +31,7 @@ class Annotator(QMainWindow):
|
|||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
clip: str = None,
|
clip: str = None,
|
||||||
extras: bool = False,
|
extras: bool = False,
|
||||||
|
skip_annotated: bool = True,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@@ -42,6 +45,7 @@ class Annotator(QMainWindow):
|
|||||||
clips_file=Path(config.clips_file),
|
clips_file=Path(config.clips_file),
|
||||||
mask_filename=config.filenames.mask,
|
mask_filename=config.filenames.mask,
|
||||||
zip_extension=config.filenames.zip_extension,
|
zip_extension=config.filenames.zip_extension,
|
||||||
|
skip_annotated=skip_annotated,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.setWindowTitle("River Annotator")
|
self.setWindowTitle("River Annotator")
|
||||||
@@ -251,7 +255,16 @@ class Annotator(QMainWindow):
|
|||||||
self._set_answers(answers)
|
self._set_answers(answers)
|
||||||
|
|
||||||
def _advance_clip(self):
|
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.frame_i = 0
|
||||||
self.mc.load_clip(
|
self.mc.load_clip(
|
||||||
self.frames,
|
self.frames,
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ class ClipSelector:
|
|||||||
clips_file: Path,
|
clips_file: Path,
|
||||||
mask_filename: str = "mask.png",
|
mask_filename: str = "mask.png",
|
||||||
zip_extension: str = ".zip",
|
zip_extension: str = ".zip",
|
||||||
|
skip_annotated: bool = True,
|
||||||
):
|
):
|
||||||
self.data_dir = data_dir
|
self.data_dir = data_dir
|
||||||
self.out_dir = out_dir
|
self.out_dir = out_dir
|
||||||
self.mask_filename = mask_filename
|
self.mask_filename = mask_filename
|
||||||
self.zip_extension = zip_extension
|
self.zip_extension = zip_extension
|
||||||
|
self.skip_annotated = skip_annotated
|
||||||
self.clips = self._load_clips(clips_file)
|
self.clips = self._load_clips(clips_file)
|
||||||
self.index = 0
|
self.index = 0
|
||||||
|
|
||||||
@@ -46,6 +48,6 @@ class ClipSelector:
|
|||||||
while self.index < len(self.clips):
|
while self.index < len(self.clips):
|
||||||
clip = self.clips[self.index]
|
clip = self.clips[self.index]
|
||||||
self.index += 1
|
self.index += 1
|
||||||
if not self.is_annotated(clip):
|
if not self.skip_annotated or not self.is_annotated(clip):
|
||||||
return clip
|
return clip
|
||||||
raise RuntimeError("No remaining clips to annotate")
|
raise RuntimeError("No remaining clips to annotate")
|
||||||
|
|||||||
Reference in New Issue
Block a user