Add Previous button, remove Save button, warn before overwriting annotations

- Previous: saves current clip and navigates back through session history;
  disabled on the first clip, re-enabled automatically as you advance.
- Next: shows a dialog when a saved annotation already exists, letting the
  annotator choose to replace it or keep the existing save before advancing.
- Removed the standalone Save button; Next auto-saves on every advance.
- Skip already wrote nothing to disk; clarified in README.
- Refactored _advance_clip into _switch_ui_to_clip (shared with prev/next).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:17:45 +02:00
parent 4aa1e32681
commit 5b6efc7158
2 changed files with 77 additions and 21 deletions

View File

@@ -48,14 +48,21 @@ class Annotator(QMainWindow):
skip_annotated=skip_annotated,
)
self.history: list[Path] = []
self.history_pos: int = -1
self.setWindowTitle("River Annotator")
self._load_clip(specific=clip)
self._history_push()
self._init_ui()
self._init_timer()
# ── clip loading ───────────────────────────────────────────────
def _load_clip(self, specific: str = None):
self.filename = self.selector.next(specific=specific)
def _load_clip(self, specific: str = None, path: Path = None):
if path is not None:
self.filename = path
else:
self.filename = self.selector.next(specific=specific)
self.frames, self.fps, self.dh, self.dw, self.h, self.w = load_frames(
self.filename,
self.cfg.max_frames,
@@ -66,6 +73,11 @@ class Annotator(QMainWindow):
)
self._pending_answers = self._read_saved_answers()
def _history_push(self):
del self.history[self.history_pos + 1 :]
self.history.append(self.filename)
self.history_pos = len(self.history) - 1
def _read_saved_mask(self):
mask_path = self.out_dir / self.filename.stem / self.cfg.filenames.mask
if not mask_path.exists():
@@ -93,7 +105,8 @@ class Annotator(QMainWindow):
self.q_widgets = {}
question_panel = self._build_question_panel()
btn_save = QPushButton("Save")
self.btn_prev = QPushButton("Previous")
self.btn_prev.setEnabled(False)
btn_next = QPushButton("Next")
btn_skip = QPushButton("Skip")
btn_clear = QPushButton("Clear")
@@ -101,7 +114,7 @@ class Annotator(QMainWindow):
btn_reload = QPushButton("Reload Saved")
row1 = QHBoxLayout()
for b in [btn_save, btn_next, btn_skip]:
for b in [self.btn_prev, btn_next, btn_skip]:
row1.addWidget(b)
row2 = QHBoxLayout()
@@ -128,7 +141,7 @@ class Annotator(QMainWindow):
container.setLayout(main)
self.setCentralWidget(container)
btn_save.clicked.connect(self.save)
self.btn_prev.clicked.connect(self.prev_clip)
btn_next.clicked.connect(self.next_clip)
btn_skip.clicked.connect(self.skip_clip)
btn_clear.clicked.connect(self.mc.clear)
@@ -254,17 +267,7 @@ class Annotator(QMainWindow):
if answers:
self._set_answers(answers)
def _advance_clip(self):
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
def _switch_ui_to_clip(self):
self.frame_i = 0
self.mc.load_clip(
self.frames,
@@ -276,10 +279,63 @@ class Annotator(QMainWindow):
if self._pending_answers:
self._set_answers(self._pending_answers)
self._pending_answers = None
self.btn_prev.setEnabled(self.history_pos > 0)
def _advance_clip(self):
if self.history_pos < len(self.history) - 1:
self.history_pos += 1
self._load_clip(path=self.history[self.history_pos])
self._switch_ui_to_clip()
return
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._history_push()
self._switch_ui_to_clip()
def prev_clip(self):
if self.history_pos <= 0:
return
self.save()
self.history_pos -= 1
self._load_clip(path=self.history[self.history_pos])
self._switch_ui_to_clip()
def next_clip(self):
self.save()
self._advance_clip()
mask_path = self.out_dir / self.filename.stem / self.cfg.filenames.mask
if mask_path.exists():
msg = QMessageBox(self)
msg.setWindowTitle("Existing annotation found")
msg.setText(
f"'{self.filename.stem}' already has a saved annotation.\n"
"Replace it with your current work, or keep the existing save?"
)
btn_replace = msg.addButton(
"Replace & Continue", QMessageBox.ButtonRole.AcceptRole
)
btn_keep = msg.addButton(
"Keep Existing & Continue", QMessageBox.ButtonRole.AcceptRole
)
msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
msg.setDefaultButton(btn_replace)
msg.exec()
clicked = msg.clickedButton()
if clicked == btn_replace:
self.save()
self._advance_clip()
elif clicked == btn_keep:
self._advance_clip()
# Cancel: do nothing
else:
self.save()
self._advance_clip()
def skip_clip(self):
self._advance_clip()