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:
@@ -131,9 +131,9 @@ The window shows the video on the left (auto-playing) and the survey panel on th
|
||||
| Undo last stroke | **Undo** |
|
||||
| Clear entire mask | **Clear** |
|
||||
| Adjust brush size | Slider next to the erase controls |
|
||||
| Save and continue | **Next** — saves current clip and loads the next one |
|
||||
| Skip without saving | **Skip** — discards changes and loads the next one |
|
||||
| Save only | **Save** — writes to disk without advancing |
|
||||
| Save and continue | **Next** — saves current clip and loads the next one. If the clip already has a saved annotation a dialog asks whether to replace it or keep the existing save. |
|
||||
| Go back | **Previous** — saves current clip and returns to the previously viewed clip. Disabled on the first clip. |
|
||||
| Skip without saving | **Skip** — discards any unsaved changes and loads the next clip without writing anything to disk. |
|
||||
| Restore last save | **Reload Saved** — reverts mask and answers to what was last written |
|
||||
|
||||
## Output
|
||||
|
||||
@@ -48,13 +48,20 @@ 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):
|
||||
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,
|
||||
@@ -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,8 +279,61 @@ 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):
|
||||
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user