Made project river-agnostic
This commit is contained in:
70
README.md
70
README.md
@@ -1,6 +1,6 @@
|
||||
# River Annotation Tool
|
||||
# Video Annotation Tool
|
||||
|
||||
A desktop GUI application for manually annotating river video clips as part of the [HydroScan](https://github.com/HydroScan) project. Annotators draw pixel-level water masks over river footage and answer structured survey questions about flow conditions, lighting, and scene quality.
|
||||
A desktop GUI application for manually annotating video clips. Annotators draw pixel-level segmentation masks over footage and answer structured survey questions defined in a config file.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -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 river_annotation_tool.annotation_script
|
||||
uv run python -m clip_annotator.annotation_script
|
||||
```
|
||||
|
||||
## 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 river_annotation_tool.annotation_script
|
||||
uv run python -m clip_annotator.annotation_script
|
||||
# or, if you have the venv activated:
|
||||
python -m river_annotation_tool.annotation_script
|
||||
python -m clip_annotator.annotation_script
|
||||
```
|
||||
|
||||
### Arguments
|
||||
@@ -94,7 +94,7 @@ python -m river_annotation_tool.annotation_script
|
||||
| `--data` | *(from config)* | Override `data_dir` from config |
|
||||
| `--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 stem name |
|
||||
| `--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) |
|
||||
| `--no-skip` | off | Show already-annotated clips instead of skipping them |
|
||||
|
||||
@@ -102,16 +102,16 @@ python -m river_annotation_tool.annotation_script
|
||||
|
||||
```sh
|
||||
# Annotate clips listed in config/clips.txt (default)
|
||||
uv run python -m river_annotation_tool.annotation_script
|
||||
uv run python -m clip_annotator.annotation_script
|
||||
|
||||
# Use a different config file
|
||||
uv run python -m river_annotation_tool.annotation_script --config config/my_config.yaml
|
||||
uv run python -m clip_annotator.annotation_script --config config/my_config.yaml
|
||||
|
||||
# Override paths from the command line
|
||||
uv run python -m river_annotation_tool.annotation_script --data data/clips --out data/out
|
||||
uv run python -m clip_annotator.annotation_script --data data/clips --out data/out
|
||||
|
||||
# Annotate a single specific clip
|
||||
uv run python -m river_annotation_tool.annotation_script --clip left_20230615T120000
|
||||
uv run python -m clip_annotator.annotation_script --clip clip_20230615T120000
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -121,8 +121,8 @@ Main settings live in `config/config.yaml`. Copy `config/config.example.yaml` to
|
||||
```yaml
|
||||
storage: local # required: 'local' or 's3'
|
||||
|
||||
data_dir: # required: directory containing ZIP archives (local path or bucket/prefix for S3)
|
||||
out_dir: # required: where to write annotations
|
||||
data_dir: # required: read-only source of ZIP archives (local path or bucket/prefix for S3)
|
||||
out_dir: # required: write destination for annotations (can be same bucket as data_dir with a different prefix, or a separate location)
|
||||
|
||||
clips_file: config/clips.txt
|
||||
optical_flow_config_file: config/optical_flow_config.yaml
|
||||
@@ -139,7 +139,7 @@ filenames:
|
||||
zip_extension: .zip
|
||||
```
|
||||
|
||||
Output filenames (`mask.png`, `metadata.json`, etc.) have sensible defaults and can be overridden in the `filenames:` block — see [`config.py`](src/river_annotation_tool/config.py) for the full list.
|
||||
Output filenames (`mask.png`, `metadata.json`, etc.) have sensible defaults and can be overridden in the `filenames:` block — see [`config.py`](src/clip_annotator/config.py) for the full list.
|
||||
|
||||
### Survey questions
|
||||
|
||||
@@ -147,9 +147,9 @@ Survey questions are defined in `config/questions.yaml` (committed to the repo).
|
||||
|
||||
### Optical flow segmentation
|
||||
|
||||
`config/optical_flow_config.yaml` controls the **Auto Segment** button. When pressed, the tool computes a river mask from the loaded frames and replaces the current mask (undoable). The segmentation combines two criteria:
|
||||
`config/optical_flow_config.yaml` controls the **Auto Segment** button. When pressed, the tool computes a segmentation mask from the loaded frames and replaces the current mask (undoable). The segmentation combines two criteria:
|
||||
|
||||
- **Optical flow magnitude** — pixels where the temporal median of frame-to-frame flow (scaled by FPS) exceeds a fraction of the maximum are considered moving water.
|
||||
- **Optical flow magnitude** — pixels where the temporal median of frame-to-frame flow (scaled by FPS) exceeds a fraction of the maximum are considered moving.
|
||||
- **Brightness** — pixels outside a brightness window are excluded (removes sky, saturated glare, etc.).
|
||||
|
||||
```yaml
|
||||
@@ -164,41 +164,25 @@ brightness_range: [2, 253] # [min, max] greyscale brightness to keep
|
||||
|
||||
## 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. Pass `--no-skip` to include them. When the last clip is reached, a dialog appears and the app exits.
|
||||
`config/clips.txt` lists bare clip filenames (not full paths) to annotate, one per line — the tool prepends `data_dir` automatically. Each entry must be the exact filename of the ZIP archive as it appears in `data_dir` (e.g. `clip_20230501T120000.zip`). Lines starting with `#` are ignored. Clips are processed in order; a clip is considered already-annotated when `<out_dir>/<clip_stem>/mask.png` exists and is skipped automatically. Pass `--no-skip` to include already-annotated clips. When the last clip is reached, a dialog appears and the app exits.
|
||||
|
||||
```
|
||||
# Example clips.txt
|
||||
left_20230501T120000.zip
|
||||
left_20230502T120000.zip
|
||||
clip_20230501T120000.zip
|
||||
clip_20230502T120000.zip
|
||||
```
|
||||
|
||||
Copy `config/clips.example.txt` as a starting point.
|
||||
|
||||
## Multi-annotator setup
|
||||
|
||||
Pre-made clip lists for 7 annotators are included in `config/annotator_A.txt` through `config/annotator_G.txt`. Each annotator is assigned exactly 5 recording days (non-consecutive where possible), covering all 24 available days across the dataset.
|
||||
|
||||
To run the tool for a specific annotator, pass their file via `--clips`:
|
||||
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 river_annotation_tool.annotation_script --clips config/annotator_A.txt
|
||||
uv run python -m clip_annotator.annotation_script --clips config/annotator_A.txt
|
||||
```
|
||||
|
||||
### Assignment
|
||||
|
||||
11 of the 24 days are reviewed by two annotators (the theoretical maximum given 7 × 5 = 35 slots and 24 days), giving 11 days with double coverage for inter-annotator agreement checks.
|
||||
|
||||
| Annotator | Days | Clips |
|
||||
|---|---|---|
|
||||
| A | 2025-11-17 · 2025-12-03 · 2026-01-01 · 2026-01-09 · 2026-02-11 | 94 |
|
||||
| B | 2025-11-18 · 2025-12-05 · 2026-01-06 · 2026-02-12 · 2026-03-02 | 128 |
|
||||
| C | 2025-11-22 · 2025-12-12 · 2026-01-07 · 2026-02-16 · 2026-03-03 | 146 |
|
||||
| D | 2025-11-18 · 2025-11-24 · 2025-12-16 · 2026-01-08 · 2026-03-02 | 102 |
|
||||
| E | 2025-11-25 · 2025-12-03 · 2026-01-09 · 2026-01-12 · 2026-03-03 | 80 |
|
||||
| F | 2025-11-25 · 2025-12-16 · 2026-01-10 · 2026-01-13 · 2026-03-11 | 93 |
|
||||
| G | 2025-11-22 · 2025-12-05 · 2026-01-12 · 2026-02-11 · 2026-03-12 | 110 |
|
||||
|
||||
Days covered by two annotators: 2025-11-18 (B, D) · 2025-11-22 (C, G) · 2025-11-25 (E, F) · 2025-12-03 (A, E) · 2025-12-05 (B, G) · 2025-12-16 (D, F) · 2026-01-09 (A, E) · 2026-01-12 (E, G) · 2026-02-11 (A, G) · 2026-03-02 (B, D) · 2026-03-03 (C, E)
|
||||
Assigning non-overlapping clip lists lets each annotator work independently. Intentionally overlapping a subset of clips across annotators enables inter-annotator agreement checks.
|
||||
|
||||
## Controls
|
||||
|
||||
@@ -218,7 +202,7 @@ Three drawing tools are available in the tool row. The active tool is highlighte
|
||||
|
||||
| Action | How |
|
||||
|---|---|
|
||||
| Draw water mask | Click and drag on the video |
|
||||
| Draw mask | Click and drag on the video |
|
||||
| Erase mask | Toggle **Eraser** button (turns orange when active), then drag |
|
||||
| Brush preview | A white circle follows the cursor showing the current brush size |
|
||||
| Adjust brush size | **Brush size** slider (2–50 px, default 5); click **↺** to reset |
|
||||
@@ -261,7 +245,7 @@ Polygons are drawn as overlays and do not affect the mask until you use **Fill**
|
||||
| Action | How |
|
||||
|---|---|
|
||||
| Load mask from previous clip | **Load Prev Mask** — copies the saved mask of the previous clip onto the current one; undoable |
|
||||
| Optical flow first guess | **Auto Segment** — replaces the current mask with an automatic river segmentation; undoable. Disabled when `enabled: false` in `config/optical_flow_config.yaml`. |
|
||||
| Optical flow first guess | **Auto Segment** — replaces the current mask with an automatic segmentation based on motion and brightness; undoable. Disabled when `enabled: false` in `config/optical_flow_config.yaml`. |
|
||||
|
||||
### Image display adjustments
|
||||
|
||||
@@ -288,7 +272,7 @@ Click **↺** below any slider to restore its default value.
|
||||
Each annotated clip produces a folder `<out_dir>/<clip_stem>/` with:
|
||||
|
||||
```
|
||||
mask.png # Binary water mask at full source resolution (always)
|
||||
mask.png # Binary segmentation mask at full source resolution (always)
|
||||
metadata.json # Survey answers as JSON (always)
|
||||
frame.png # Middle frame of the clip (always)
|
||||
overlay.png # That frame with the mask blended in green (always)
|
||||
@@ -323,7 +307,7 @@ Keys and values are determined by `config/questions.yaml`. With the default ques
|
||||
|
||||
### Clip format
|
||||
|
||||
Each clip is a ZIP archive containing a video file (default `left.mp4`, configurable via `filenames.video_in_zip`). The filename encodes the recording timestamp (e.g. `left_20230615T120000.zip`).
|
||||
Each clip is a ZIP archive containing a video file. The video inside the archive must be named `left.mp4` by default; if your archives use a different internal name, set `filenames.video_in_zip` in `config.yaml`. The ZIP filename becomes the output folder name (e.g. `clip_20230501T120000.zip` → `<out_dir>/clip_20230501T120000/`).
|
||||
|
||||
### Frame loading
|
||||
|
||||
@@ -350,14 +334,14 @@ config/
|
||||
clips.example.txt # Example clip list
|
||||
questions.yaml # Survey question definitions
|
||||
optical_flow_config.yaml # Optical flow parameters (set enabled: false to disable Auto Segment)
|
||||
src/river_annotation_tool/
|
||||
src/clip_annotator/
|
||||
annotation_script.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
|
||||
mask_canvas.py # Drawing widget — brush, undo, erase, mouse events
|
||||
video_loader.py # ZIP extraction and frame resizing
|
||||
compute_optical_flow.py # Optical flow river segmentation (Auto Segment button)
|
||||
compute_optical_flow.py # Optical flow segmentation (Auto Segment button)
|
||||
config.py # AppConfig dataclass and YAML loader
|
||||
__init__.py # Package version
|
||||
pyproject.toml # Project metadata and dependencies
|
||||
|
||||
Reference in New Issue
Block a user