End-to-end pipeline from raw rehabilitation-tank video to:
- Pool cell occupancy — heatmaps, transition matrices, run-length distributions
- Breathing events — per-frame classification, event timeline, CSV export
| Situation | Script |
|---|---|
| One camera, one recording | run_pipeline.py |
| One camera, multiple days | run_batch.py |
| Two cameras (comparison) | run_batch.py |
Video filename format: CAMERA_YYYYMMDDHHMMSS.mp4 (e.g. D01_20250808060000.mp4)
Raw video
│
▼ Step 1 — Calculate working-hours window (default 06:00–18:00)
│
▼ Step 2 — Pool ellipse mask
First run: interactive GUI → drag dots to pool boundary → confirm
Saved to configs/<CAMERA>_pool_config.json, reused on future runs
│
▼ Step 3 — Frame extraction at target FPS
│
▼ Step 4 — YOLOv8m-seg detection (auto-downloaded from HuggingFace)
│
├─────────────────────────────────────────┐
▼ ▼
Step 5 — Cell assignment Step 6 — Breathing classification
Heatmaps, transition matrices, ResNet50 (auto-downloaded from HuggingFace)
run-length plots → breathing_events.csv + timeline plot
Both models are downloaded automatically on first run and cached locally.
Unzip the archive and run setup from inside the folder:
cd sea-turtle-behavior-pipeline-main
bash setup.sh
source .venv/bin/activatesetup.sh installs uv if not present, creates a .venv,
and installs all dependencies. No sudo required.
setup.sh downloads 1-minute sample clips (2 cameras, 2 days) automatically.
Run the full pipeline on them to verify everything works:
python run_batch.py \
--input-dir samples/1min_sample/ \
--duration 1Output lands under results/ with a timestamped name, e.g.:
results/20250808_143022_D01-D03_20250808-09/
Completes in a few minutes and exercises the full pipeline end-to-end. This is also the recommended first step when setting up pool configs for new cameras — the boundary picker will open automatically on first run.
python run_pipeline.py --video /path/to/D01_20250808060000.mp4--camera is inferred from the filename automatically (D01_… → camera D01).
Pass --camera <ID> explicitly only if your filename doesn't follow the standard format.
| Option | Description |
|---|---|
--video |
Path to raw tank video (required) |
--camera |
Camera ID — inferred from filename if omitted |
--output |
Output directory (default: results/<video-stem>/) |
--start |
Recording window start, HH:MM (default: 06:00) |
--end |
Recording window end, HH:MM (default: 18:00) |
--processing-fps |
FPS for frame extraction (default: 8) |
--yolo-fps |
FPS for YOLO (default: same as --processing-fps) |
--breathing-fps |
FPS for breathing classifier (default: same as --processing-fps) |
--verify-trim |
Interactively verify trim window frame-by-frame |
--keep-frames |
Keep extracted JPEG frames after processing (deleted by default) |
--skip-cells |
Skip cell assignment / heatmaps |
--skip-breathing |
Skip breathing classification |
results/D01_20250808060000/
├── D01_20250808060000_detections.csv
├── cells/
│ ├── D01_20250808060000_cells.csv
│ └── plots/
└── breathing/
├── breathing_predictions.csv
├── breathing_events.csv
└── breathing_timeline.png
python run_batch.py \
--input-dir /path/to/videos/ \
--duration 60 # optional: cap to 60 min per dayVideos are found by recursive scan. Camera ID and timestamp are parsed from the filename — the folder layout does not need to follow a fixed pattern.
Required filename format: CAMERA_YYYYMMDDHHMMSS.mp4
D01_20250808060000.mp4 ← camera D01, started 2025-08-08 at 06:00:00
D03_20250809132619.mp4
The camera ID is everything before the first _. Any alphanumeric string
works (D01, D03, cam1, turtle1 …). Only 1 or 2 cameras per
run are supported.
Supported folder layouts (all handled automatically):
# A — camera / day / videos (recommended)
input_dir/
├── D01/
│ ├── 8_8_2025/
│ │ ├── D01_20250808060000.mp4
│ │ └── D01_20250808071746.mp4
│ └── 9_8_2025/
│ └── D01_20250809060000.mp4
└── D03/
└── 8_8_2025/
└── D03_20250808060000.mp4
# B — camera / videos (no day subfolder)
input_dir/
├── D01/
│ ├── D01_20250808060000.mp4
│ └── D01_20250809060000.mp4
└── D03/
└── D03_20250808060000.mp4
# C — camera / subtype / videos (use --subdir to filter)
input_dir/
├── D01/
│ ├── daytime_synchronized/
│ │ └── D01_20250808060000.mp4
│ └── daytime_not_synchronized/
│ └── D01_20250808043608.mp4
└── D03/
└── daytime_synchronized/
└── D03_20250808060000.mp4
Mix of the above also works — the scan is recursive and groups by
camera and calendar day from the filename regardless of folder depth.
Use --subdir <name> to restrict to a specific subdirectory name
anywhere in the path (e.g. --subdir daytime_synchronized).
| Option | Description |
|---|---|
--input-dir |
Root directory to scan for MP4 videos (required) |
--subdir |
Only include videos inside this subdirectory name |
--output |
Exact output directory (default: auto-named under results/) |
--start |
Recording window start, HH:MM (default: 06:00) |
--end |
Recording window end, HH:MM (default: 18:00) |
--duration |
Cap each day to this many minutes from start |
--processing-fps |
FPS for frame extraction (default: 8) |
--yolo-fps |
FPS for YOLO (default: same as --processing-fps) |
--breathing-fps |
FPS for breathing classifier (default: same as --processing-fps) |
--verify-trim |
Interactively verify trim window frame-by-frame |
--verify-config |
Re-verify pool boundary config even if previously approved |
--keep-frames |
Keep extracted JPEG frames after processing (deleted by default) |
--skip-cells |
Skip cell assignment / heatmaps |
--skip-breathing |
Skip breathing classification |
All defaults can be set in pipeline_config.yaml and overridden per-run via CLI.
results/20250808_143022_D01-D03_20250808-09/
├── D01/
│ ├── 2025-08-08/
│ │ ├── D01_2025-08-08.csv ← merged day detections
│ │ ├── cells/
│ │ └── breathing/
│ ├── 2025-08-09/
│ └── combined/ ← all days merged
├── D03/
└── comparison_D01_D03/ ← cross-camera comparison (2-camera runs only)
- Supports 1 or 2 cameras. Cross-camera comparison is automatic when 2 cameras are present, clipped to the shared coverage window per day.
- Pool configs are saved to
configs/and reused across runs. The boundary picker opens automatically on first run for each camera. - Intermediate JPEG frames are deleted after processing by default. Pass
--keep-framesto retain them (needed if you plan to re-run breathing without re-extracting).
On first run for a camera, a fullscreen interactive window opens showing a frame from the source video. Drag the 10 green dots to the pool boundary — the fitted ellipse and cell grid update live. Press Enter to confirm.
The config is saved to configs/<CAMERA>_pool_config.json and reused on future runs.
Use --verify-config to force re-verification.
- Marko Barišić — University of Zagreb, FER, Laboratory for Underwater Systems and Technologies (LABUST)
- Roee Diamant — University of Haifa