Address PR review comments: rename to __main__.py, improve config examples, document save freeze
This commit is contained in:
22
README.md
22
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
|
||||
|
||||
@@ -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/<name>/ # 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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user