From dc7bb8e7eb124ba71a604af827f199573d913969 Mon Sep 17 00:00:00 2001 From: asreva Date: Tue, 2 Jun 2026 11:51:37 +0200 Subject: [PATCH] Address PR review comments: rename to __main__.py, improve config examples, document save freeze --- README.md | 22 +++++++++---------- config/config.example.yaml | 4 ++-- .../{annotation_script.py => __main__.py} | 0 src/clip_annotator/annotator.py | 22 ++++++++++++++----- 4 files changed, 29 insertions(+), 19 deletions(-) rename src/clip_annotator/{annotation_script.py => __main__.py} (100%) diff --git a/README.md b/README.md index 2ddad84..b6bff51 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ cp config/clips.example.txt config/clips.txt # Edit config/questions.yaml to customise survey questions (optional) # 4. Run -uv run python -m clip_annotator.annotation_script +uv run python -m clip_annotator ``` ## Installation @@ -81,9 +81,9 @@ The `clips_file` (the list of clip filenames to annotate) is always read from th ## Usage ```sh -uv run python -m clip_annotator.annotation_script +uv run python -m clip_annotator # or, if you have the venv activated: -python -m clip_annotator.annotation_script +python -m clip_annotator ``` ### Arguments @@ -95,23 +95,23 @@ python -m clip_annotator.annotation_script | `--out` | *(from config)* | Override `out_dir` from config | | `--clips` | *(from config)* | Override `clips_file` from config | | `--clip` | *(first unannotated in list)* | Open a specific clip by its stem name (filename without extension, e.g. `clip_20230501T120000`) | -| `--extras` | off | Also save GIFs and extra PNGs (see Output section) | +| `--extras` | off | Also save GIFs and extra PNGs (see Output section). **Note: GIF encoding is slow — saving will freeze the UI for several seconds per clip.** | | `--no-skip` | off | Show already-annotated clips instead of skipping them | ### Typical workflows ```sh # Annotate clips listed in config/clips.txt (default) -uv run python -m clip_annotator.annotation_script +uv run python -m clip_annotator # Use a different config file -uv run python -m clip_annotator.annotation_script --config config/my_config.yaml +uv run python -m clip_annotator --config config/my_config.yaml # Override paths from the command line -uv run python -m clip_annotator.annotation_script --data data/clips --out data/out +uv run python -m clip_annotator --data data/clips --out data/out # Annotate a single specific clip -uv run python -m clip_annotator.annotation_script --clip clip_20230615T120000 +uv run python -m clip_annotator --clip clip_20230615T120000 ``` ## Configuration @@ -179,7 +179,7 @@ Copy `config/clips.example.txt` as a starting point. To distribute clips across multiple annotators, create one clips file per annotator (e.g. `config/annotator_A.txt`) listing their assigned clip filenames, then pass it via `--clips`: ```sh -uv run python -m clip_annotator.annotation_script --clips config/annotator_A.txt +uv run python -m clip_annotator --clips config/annotator_A.txt ``` Assigning non-overlapping clip lists lets each annotator work independently. Intentionally overlapping a subset of clips across annotators enables inter-annotator agreement checks. @@ -263,7 +263,7 @@ Click **↺** below any slider to restore its default value. | Action | How | |---|---| -| 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. | +| 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. The UI freezes during saving; this is normal — wait for it to complete before clicking again. | | 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. | @@ -335,7 +335,7 @@ config/ questions.yaml # Survey question definitions optical_flow_config.yaml # Optical flow parameters (set enabled: false to disable Auto Segment) src/clip_annotator/ - annotation_script.py # Entry point — argument parsing and app launch + __main__.py # Entry point — argument parsing and app launch annotator.py # Main QMainWindow — orchestrates all components clip_selector.py # Reads the clip list and picks the next clip filesystem.py # Storage backend — local passthrough or S3 via s3fs diff --git a/config/config.example.yaml b/config/config.example.yaml index d8febbc..c197db8 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -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: -out_dir: +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// # 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 diff --git a/src/clip_annotator/annotation_script.py b/src/clip_annotator/__main__.py similarity index 100% rename from src/clip_annotator/annotation_script.py rename to src/clip_annotator/__main__.py diff --git a/src/clip_annotator/annotator.py b/src/clip_annotator/annotator.py index be7e992..c6adcee 100644 --- a/src/clip_annotator/annotator.py +++ b/src/clip_annotator/annotator.py @@ -159,7 +159,7 @@ class Annotator(QMainWindow): self.btn_prev = QPushButton("Previous") self.btn_prev.setEnabled(False) - btn_next = QPushButton("Next") + self.btn_next = QPushButton("Next") btn_skip = QPushButton("Skip") btn_clear = QPushButton("Clear") btn_undo = QPushButton("Undo") @@ -172,7 +172,7 @@ class Annotator(QMainWindow): row1 = QHBoxLayout() for b in [ self.btn_prev, - btn_next, + self.btn_next, btn_skip, btn_load_prev_mask, btn_auto_segment, @@ -251,7 +251,7 @@ class Annotator(QMainWindow): self.setCentralWidget(container) self.btn_prev.clicked.connect(self.prev_clip) - btn_next.clicked.connect(self.next_clip) + self.btn_next.clicked.connect(self.next_clip) btn_skip.clicked.connect(self.skip_clip) btn_clear.clicked.connect(self.mc.clear) btn_undo.clicked.connect(self.mc.undo) @@ -350,6 +350,16 @@ class Annotator(QMainWindow): self.fs.pipe(out_path, buf.getvalue()) # ── actions ──────────────────────────────────────────────────── + def _save_locked(self): + self.btn_next.setEnabled(False) + self.btn_prev.setEnabled(False) + QApplication.processEvents() + try: + self.save() + finally: + self.btn_next.setEnabled(True) + self.btn_prev.setEnabled(self.history_pos > 0) + def save(self): out = fsjoin(self.out_dir, fsstem(self.filename)) self._fs_makedirs(out) @@ -421,7 +431,7 @@ class Annotator(QMainWindow): def prev_clip(self): if self.history_pos <= 0: return - self.save() + self._save_locked() self.history_pos -= 1 self._load_clip(path=self.history[self.history_pos]) self._switch_ui_to_clip() @@ -446,13 +456,13 @@ class Annotator(QMainWindow): msg.exec() clicked = msg.clickedButton() if clicked == btn_replace: - self.save() + self._save_locked() self._advance_clip() elif clicked == btn_keep: self._advance_clip() # Cancel: do nothing else: - self.save() + self._save_locked() self._advance_clip() def skip_clip(self):