From 5cd4974871f1b1ad4e86a8d50fa8f389705f05db Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:26:39 +0800 Subject: [PATCH 01/55] Refactor code structure for improved readability and maintainability --- .dockerignore | 54 ++ .python-version | 1 + DOCKER_COMPARISON.md | 200 ++++ DOCKER_INDEX.md | 199 ++++ DOCKER_QUICKREF.md | 172 ++++ DOCKER_USAGE.md | 237 +++++ Dockerfile.cpu | 65 ++ Dockerfile.gpu | 69 ++ MIGRATION_SUMMARY.md | 124 +++ Makefile | 84 ++ README.md | 13 + build-docker.ps1 | 65 ++ build-docker.sh | 65 ++ core/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 169 bytes docker-compose.yml | 47 + main.py | 6 + pyproject.toml | 59 ++ requirements.txt.backup | 14 + uv.lock | 1045 +++++++++++++++++++++ 19 files changed, 2519 insertions(+) create mode 100644 .dockerignore create mode 100644 .python-version create mode 100644 DOCKER_COMPARISON.md create mode 100644 DOCKER_INDEX.md create mode 100644 DOCKER_QUICKREF.md create mode 100644 DOCKER_USAGE.md create mode 100644 Dockerfile.cpu create mode 100644 Dockerfile.gpu create mode 100644 MIGRATION_SUMMARY.md create mode 100644 Makefile create mode 100644 build-docker.ps1 create mode 100644 build-docker.sh create mode 100644 core/__pycache__/__init__.cpython-311.pyc create mode 100644 docker-compose.yml create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 requirements.txt.backup create mode 100644 uv.lock diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..36eab36 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,54 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ +*.egg + +# Virtual environments +.venv/ +venv/ +ENV/ +env/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Git +.git/ +.gitignore + +# Data and models (too large) +data/ +checkpoints/*.pth +raft_model/*.pth + +# Logs +*.log +logs/ +*.csv + +# OS +.DS_Store +Thumbs.db + +# UV +.uv/ + +# Backup files +*.backup +requirements.txt.backup + +# Documentation +MIGRATION_SUMMARY.md + +# Demo videos (optional - comment out if needed) +demo_video/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..641602f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.14 diff --git a/DOCKER_COMPARISON.md b/DOCKER_COMPARISON.md new file mode 100644 index 0000000..35cf865 --- /dev/null +++ b/DOCKER_COMPARISON.md @@ -0,0 +1,200 @@ +# Docker Configuration Comparison + +## Quick Comparison + +| Feature | CPU Version | GPU Version | +|---------|------------|-------------| +| **Base Image** | python:3.11-slim | nvidia/cuda:11.7.1-cudnn8-runtime | +| **Image Size** | ~2-3 GB | ~8-10 GB | +| **PyTorch** | 2.0.0+cpu | 2.0.0+cu117 | +| **Build Time** | ~5-10 min | ~10-20 min | +| **Training Speed** | Slow (baseline) | 10-100x faster | +| **Memory Required** | 4-8 GB RAM | 8+ GB RAM + GPU VRAM | +| **Hardware Required** | Any CPU | NVIDIA GPU + nvidia-docker | +| **Use Case** | Testing, inference on small data | Training, production inference | +| **Cost** | Low | High (GPU required) | + +## When to Use CPU Version + +✅ **Good for:** +- Initial testing and development +- Small-scale inference +- Environments without GPU access +- Budget-constrained deployments +- CI/CD pipelines for testing + +❌ **Not recommended for:** +- Training large models +- Processing high-resolution videos +- Batch processing many videos +- Production workloads with time constraints + +## When to Use GPU Version + +✅ **Good for:** +- Training models +- Large-scale inference +- Video processing pipelines +- Production deployments +- Research and experimentation + +❌ **Not recommended for:** +- Simple testing +- Environments without NVIDIA GPU +- Cost-sensitive deployments where speed isn't critical + +## Resource Requirements + +### CPU Version +```yaml +Minimum: + RAM: 4 GB + Storage: 10 GB + CPU: 2 cores + +Recommended: + RAM: 8 GB + Storage: 20 GB + CPU: 4+ cores +``` + +### GPU Version +```yaml +Minimum: + RAM: 8 GB + GPU VRAM: 6 GB + Storage: 20 GB + GPU: NVIDIA GPU with CUDA 11.7 support + +Recommended: + RAM: 16 GB + GPU VRAM: 12+ GB + Storage: 50 GB + GPU: NVIDIA RTX 3080 or better +``` + +## Performance Comparison + +### Training (1000 iterations) +| Hardware | Time | Relative Speed | +|----------|------|----------------| +| CPU (Intel i7) | ~4-6 hours | 1x | +| GPU (GTX 1080 Ti) | ~20-30 min | 10-15x | +| GPU (RTX 3090) | ~10-15 min | 20-30x | +| GPU (A100) | ~5-8 min | 40-60x | + +### Inference (Single Video) +| Hardware | Time | Relative Speed | +|----------|------|----------------| +| CPU (Intel i7) | ~5-10 min | 1x | +| GPU (GTX 1080 Ti) | ~30-60 sec | 5-10x | +| GPU (RTX 3090) | ~15-30 sec | 10-20x | +| GPU (A100) | ~10-20 sec | 15-30x | + +*Note: Times vary based on video resolution, length, and model complexity* + +## Build Time Comparison + +### Initial Build +| Version | Download Size | Build Time | +|---------|--------------|------------| +| CPU | ~500 MB | 5-10 min | +| GPU | ~2-3 GB | 10-20 min | + +### Rebuild (after changes) +| Version | Time | +|---------|------| +| CPU | 1-2 min | +| GPU | 2-5 min | + +## Docker Compose Configuration + +The included `docker-compose.yml` is configured with optimal defaults: + +### CPU Container +```yaml +Resources: + - 4 GB shared memory + - All CPU cores available + - Port 6007 for TensorBoard +``` + +### GPU Container +```yaml +Resources: + - 8 GB shared memory + - All NVIDIA GPUs available + - Port 6006 for TensorBoard + - CUDA environment configured +``` + +## Cost Comparison (Cloud Deployment) + +### AWS EC2 Approximate Hourly Costs +| Instance Type | Specs | Cost/Hour | Best For | +|--------------|-------|-----------|----------| +| t3.2xlarge | 8 vCPU, 32 GB RAM | $0.33 | CPU Testing | +| p3.2xlarge | V100 GPU, 16 GB VRAM | $3.06 | GPU Training | +| p4d.24xlarge | 8x A100 GPUs | $32.77 | Large-scale Training | + +*Prices as of 2024, may vary by region* + +## Recommendations by Use Case + +### Research & Development +- **Start with**: GPU version +- **Why**: Faster iteration, better for experimentation +- **Fallback**: CPU for quick tests + +### Production Inference +- **Small scale**: CPU version +- **Large scale**: GPU version +- **Why**: Cost vs. speed tradeoff + +### CI/CD Testing +- **Use**: CPU version +- **Why**: No GPU required, faster builds, lower cost + +### Training +- **Always use**: GPU version +- **Why**: CPU training is impractically slow + +## Environment Variables + +Both versions support these environment variables: + +```bash +# PyTorch settings +CUDA_VISIBLE_DEVICES=0,1 # GPU only: Select GPUs +OMP_NUM_THREADS=4 # CPU only: Thread count + +# Application settings +PYTHONUNBUFFERED=1 # Real-time logging +PYTHONDONTWRITEBYTECODE=1 # Smaller image size + +# UV package manager +UV_SYSTEM_PYTHON=1 # Use system Python +``` + +## Summary + +Choose your version based on: + +1. **Hardware Available** + - No GPU? → CPU version + - Have GPU? → GPU version + +2. **Use Case** + - Testing/Development? → Start with CPU, move to GPU + - Training? → GPU version mandatory + - Production inference? → Depends on scale + +3. **Budget** + - Limited? → CPU version + - Performance critical? → GPU version + +4. **Time Constraints** + - Quick results needed? → GPU version + - Can wait? → CPU version acceptable + +For most users working on AI-Generated Video Detection, the **GPU version is recommended** for any serious work beyond initial exploration. diff --git a/DOCKER_INDEX.md b/DOCKER_INDEX.md new file mode 100644 index 0000000..f2b125a --- /dev/null +++ b/DOCKER_INDEX.md @@ -0,0 +1,199 @@ +# AIGVDet Docker Documentation Index + +Welcome to the AIGVDet Docker implementation! This project now supports containerized deployment with both CPU and GPU configurations. + +## 📚 Documentation Overview + +### Quick Start +- **[DOCKER_QUICKREF.md](DOCKER_QUICKREF.md)** - Quick reference for common Docker commands + - Build commands + - Run commands + - Common tasks + - One-line examples + +### Detailed Guides +- **[DOCKER_USAGE.md](DOCKER_USAGE.md)** - Comprehensive usage guide + - Prerequisites and setup + - Detailed build instructions + - Training and testing examples + - Troubleshooting + - Volume management + - TensorBoard access + +- **[DOCKER_COMPARISON.md](DOCKER_COMPARISON.md)** - CPU vs GPU comparison + - Performance benchmarks + - Cost analysis + - Resource requirements + - Use case recommendations + +### Main Documentation +- **[README.md](README.md)** - Project overview and setup (now includes Docker section) +- **[MIGRATION_SUMMARY.md](MIGRATION_SUMMARY.md)** - UV migration details + +## 🎯 Getting Started in 3 Steps + +### 1. Choose Your Version +- **GPU** - For training and fast inference (requires NVIDIA GPU + nvidia-docker) +- **CPU** - For testing and CPU-only environments + +### 2. Build the Image + +**Windows (PowerShell):** +```powershell +.\build-docker.ps1 gpu # or 'cpu' +``` + +**Linux/Mac:** +```bash +./build-docker.sh gpu # or 'cpu' +``` + +**Using Make:** +```bash +make build-gpu # or 'build-cpu' +``` + +### 3. Run the Container + +**With Docker Compose (Recommended):** +```bash +docker-compose up aigvdet-gpu +``` + +**Direct Docker Run:** +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:gpu +``` + +## 📁 File Structure + +``` +AIGVDet/ +├── 🐳 Docker Configuration +│ ├── Dockerfile.gpu # GPU version +│ ├── Dockerfile.cpu # CPU version +│ ├── docker-compose.yml # Compose configuration +│ └── .dockerignore # Build exclusions +│ +├── 🔧 Build Scripts +│ ├── build-docker.ps1 # Windows script +│ ├── build-docker.sh # Linux/Mac script +│ └── Makefile # Make targets +│ +├── 📖 Documentation +│ ├── DOCKER_INDEX.md # This file +│ ├── DOCKER_QUICKREF.md # Quick reference +│ ├── DOCKER_USAGE.md # Detailed guide +│ ├── DOCKER_COMPARISON.md # CPU vs GPU +│ └── README.md # Main README +│ +└── 🐍 Project Files + ├── pyproject.toml # UV project config + ├── core/ # Core modules + ├── networks/ # Network definitions + ├── train.py # Training script + ├── test.py # Testing script + └── demo.py # Demo script +``` + +## 🎓 Common Use Cases + +### Training a Model +```bash +# GPU training +docker-compose run aigvdet-gpu python3.11 train.py \ + --gpus 0 \ + --exp_name TRAIN_RGB \ + datasets RGB_TRAINSET \ + datasets_test RGB_TESTSET +``` + +### Running Tests +```bash +# Test on dataset +docker-compose run aigvdet-gpu python3.11 test.py \ + -fop "data/test/hotshot" \ + -mop "checkpoints/optical_aug.pth" \ + -for "data/test/original/hotshot" \ + -mor "checkpoints/original_aug.pth" \ + -e "results/hotshot.csv" +``` + +### Demo on Video +```bash +# Process a single video +docker-compose run aigvdet-gpu python3.11 demo.py \ + --path "demo_video/video.mp4" \ + --folder_original_path "frame/output" \ + --folder_optical_flow_path "optical_result/output" \ + -mop "checkpoints/optical.pth" \ + -mor "checkpoints/original.pth" +``` + +### Interactive Development +```bash +# Open bash shell in container +docker-compose run aigvdet-gpu /bin/bash + +# Or using Make +make shell-gpu +``` + +## 🔍 Quick Command Reference + +| Task | Command | +|------|---------| +| Build GPU | `.\build-docker.ps1 gpu` or `make build-gpu` | +| Build CPU | `.\build-docker.ps1 cpu` or `make build-cpu` | +| Run GPU | `docker-compose up aigvdet-gpu` | +| Run CPU | `docker-compose up aigvdet-cpu` | +| Shell GPU | `make shell-gpu` | +| Shell CPU | `make shell-cpu` | +| Clean up | `make clean` | +| View logs | `docker-compose logs -f` | + +## 🆘 Need Help? + +1. **Quick commands?** → [DOCKER_QUICKREF.md](DOCKER_QUICKREF.md) +2. **Detailed setup?** → [DOCKER_USAGE.md](DOCKER_USAGE.md) +3. **CPU or GPU?** → [DOCKER_COMPARISON.md](DOCKER_COMPARISON.md) +4. **Project info?** → [README.md](README.md) + +## 💡 Tips + +- **First time?** Start with [DOCKER_QUICKREF.md](DOCKER_QUICKREF.md) +- **Troubleshooting?** Check [DOCKER_USAGE.md](DOCKER_USAGE.md) troubleshooting section +- **Performance tuning?** See [DOCKER_COMPARISON.md](DOCKER_COMPARISON.md) +- **Windows user?** All PowerShell commands are in the guides + +## 🎯 What's Different from Regular Installation? + +| Aspect | Regular Setup | Docker Setup | +|--------|--------------|--------------| +| Installation | Manual dependencies | One `docker build` command | +| Reproducibility | Varies by system | Identical everywhere | +| GPU Setup | Manual CUDA install | Pre-configured in image | +| Isolation | System-wide packages | Containerized environment | +| Portability | Machine-specific | Runs anywhere Docker runs | +| Cleanup | Manual uninstall | `docker rmi` | + +## 🚀 Ready to Start? + +1. Pick your documentation: + - **Quick start** → [DOCKER_QUICKREF.md](DOCKER_QUICKREF.md) + - **Full guide** → [DOCKER_USAGE.md](DOCKER_USAGE.md) + +2. Choose your version: + - **GPU** → For training and production + - **CPU** → For testing and development + +3. Build and run: + ```bash + .\build-docker.ps1 gpu + docker-compose up aigvdet-gpu + ``` + +Happy containerizing! 🐳 diff --git a/DOCKER_QUICKREF.md b/DOCKER_QUICKREF.md new file mode 100644 index 0000000..abb84b6 --- /dev/null +++ b/DOCKER_QUICKREF.md @@ -0,0 +1,172 @@ +# AIGVDet Docker Quick Reference + +## Build Images + +### Windows (PowerShell) +```powershell +# Build both +.\build-docker.ps1 all + +# Build GPU only +.\build-docker.ps1 gpu + +# Build CPU only +.\build-docker.ps1 cpu +``` + +### Linux/Mac (Bash) +```bash +# Build both +./build-docker.sh all + +# Build GPU only +./build-docker.sh gpu + +# Build CPU only +./build-docker.sh cpu +``` + +## Run with Docker Compose (Recommended) + +```bash +# GPU version +docker-compose up aigvdet-gpu + +# CPU version +docker-compose up aigvdet-cpu + +# Build and run +docker-compose up --build aigvdet-gpu + +# Run in background +docker-compose up -d aigvdet-gpu +``` + +## Run with Docker Run + +### GPU Version (Windows PowerShell) +```powershell +docker run --gpus all -it --rm ` + -v ${PWD}/data:/app/data ` + -v ${PWD}/checkpoints:/app/checkpoints ` + -v ${PWD}/raft_model:/app/raft_model ` + -p 6006:6006 ` + aigvdet:gpu ` + python3.11 train.py --gpus 0 --exp_name my_experiment +``` + +### CPU Version (Windows PowerShell) +```powershell +docker run -it --rm ` + -v ${PWD}/data:/app/data ` + -v ${PWD}/checkpoints:/app/checkpoints ` + -p 6006:6006 ` + aigvdet:cpu ` + python train.py --exp_name my_experiment +``` + +### GPU Version (Linux/Mac) +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft_model:/app/raft_model \ + -p 6006:6006 \ + aigvdet:gpu \ + python3.11 train.py --gpus 0 --exp_name my_experiment +``` + +## Common Tasks + +### Training RGB Branch +```bash +docker-compose run aigvdet-gpu python3.11 train.py --gpus 0 --exp_name TRAIN_RGB datasets RGB_TRAINSET datasets_test RGB_TESTSET +``` + +### Training Optical Flow Branch +```bash +docker-compose run aigvdet-gpu python3.11 train.py --gpus 0 --exp_name TRAIN_OF datasets OpticalFlow_TRAINSET datasets_test OpticalFlow_TESTSET +``` + +### Testing +```bash +docker-compose run aigvdet-gpu python3.11 test.py \ + -fop "data/test/hotshot" \ + -mop "checkpoints/optical_aug.pth" \ + -for "data/test/original/hotshot" \ + -mor "checkpoints/original_aug.pth" \ + -e "data/results/T2V/hotshot.csv" \ + -ef "data/results/frame/T2V/hotshot.csv" \ + -t 0.5 +``` + +### Demo on Video +```bash +docker-compose run aigvdet-gpu python3.11 demo.py \ + --path "demo_video/video.mp4" \ + --folder_original_path "frame/000000" \ + --folder_optical_flow_path "optical_result/000000" \ + -mop "checkpoints/optical.pth" \ + -mor "checkpoints/original.pth" +``` + +### Interactive Shell +```bash +# GPU +docker-compose run aigvdet-gpu /bin/bash + +# CPU +docker-compose run aigvdet-cpu /bin/bash +``` + +### TensorBoard +```bash +# Access at http://localhost:6006 +docker-compose up aigvdet-gpu +``` + +## Cleanup + +```bash +# Stop containers +docker-compose down + +# Remove images +docker rmi aigvdet:gpu aigvdet:cpu + +# Full cleanup +docker system prune -a +``` + +## File Structure + +``` +AIGVDet/ +├── Dockerfile.gpu # GPU version Dockerfile +├── Dockerfile.cpu # CPU version Dockerfile +├── docker-compose.yml # Orchestration config +├── .dockerignore # Files to exclude from build +├── build-docker.sh # Linux/Mac build script +├── build-docker.ps1 # Windows build script +├── DOCKER_USAGE.md # Detailed usage guide +└── DOCKER_QUICKREF.md # This file +``` + +## Volumes Mounted + +- `./data` → `/app/data` - Training/test data +- `./checkpoints` → `/app/checkpoints` - Model weights +- `./raft_model` → `/app/raft_model` - RAFT model +- `./logs` → `/app/logs` - Training logs + +## Ports Exposed + +- `6006` - TensorBoard (GPU container) +- `6007` - TensorBoard (CPU container, in compose) + +## Image Details + +| Version | Base Image | Size | Python | PyTorch | CUDA | +|---------|-----------|------|--------|---------|------| +| GPU | nvidia/cuda:11.7.1-cudnn8-runtime | ~8-10GB | 3.11 | 2.0.0+cu117 | 11.7 | +| CPU | python:3.11-slim | ~2-3GB | 3.11 | 2.0.0+cpu | N/A | diff --git a/DOCKER_USAGE.md b/DOCKER_USAGE.md new file mode 100644 index 0000000..66ffc2a --- /dev/null +++ b/DOCKER_USAGE.md @@ -0,0 +1,237 @@ +# Docker Usage Guide for AIGVDet + +This guide explains how to build and run AIGVDet using Docker with both CPU and GPU support. + +## Prerequisites + +### For CPU Version +- Docker installed +- At least 8GB RAM recommended + +### For GPU Version +- Docker installed +- NVIDIA Docker runtime (nvidia-docker2) +- NVIDIA GPU with CUDA support +- NVIDIA drivers installed + +## Quick Start + +### Build Docker Images + +**Build GPU version:** +```bash +docker build -f Dockerfile.gpu -t aigvdet:gpu . +``` + +**Build CPU version:** +```bash +docker build -f Dockerfile.cpu -t aigvdet:cpu . +``` + +### Run Containers + +**Run GPU version:** +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft_model:/app/raft_model \ + -p 6006:6006 \ + aigvdet:gpu +``` + +**Run CPU version:** +```bash +docker run -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft_model:/app/raft_model \ + -p 6006:6006 \ + aigvdet:cpu +``` + +## Using Docker Compose + +Docker Compose simplifies running the containers with all necessary configurations. + +**Start GPU service:** +```bash +docker-compose up aigvdet-gpu +``` + +**Start CPU service:** +```bash +docker-compose up aigvdet-cpu +``` + +**Build and start:** +```bash +docker-compose up --build aigvdet-gpu +``` + +## Training Examples + +### GPU Training - RGB Branch +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:gpu \ + python3.11 train.py --gpus 0 --exp_name TRAIN_RGB_BRANCH \ + datasets RGB_TRAINSET datasets_test RGB_TESTSET +``` + +### GPU Training - Optical Flow Branch +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:gpu \ + python3.11 train.py --gpus 0 --exp_name TRAIN_OF_BRANCH \ + datasets OpticalFlow_TRAINSET datasets_test OpticalFlow_TESTSET +``` + +### CPU Training +```bash +docker run -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:cpu \ + python train.py --exp_name TRAIN_RGB_BRANCH \ + datasets RGB_TRAINSET datasets_test RGB_TESTSET +``` + +## Testing + +### Test on Dataset +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:gpu \ + python3.11 test.py \ + -fop "data/test/hotshot" \ + -mop "checkpoints/optical_aug.pth" \ + -for "data/test/original/hotshot" \ + -mor "checkpoints/original_aug.pth" \ + -e "data/results/T2V/hotshot.csv" \ + -ef "data/results/frame/T2V/hotshot.csv" \ + -t 0.5 +``` + +## Demo on Video + +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft_model:/app/raft_model \ + -v $(pwd)/demo_video:/app/demo_video \ + aigvdet:gpu \ + python3.11 demo.py \ + --path "demo_video/fake_sora/video.mp4" \ + --folder_original_path "frame/000000" \ + --folder_optical_flow_path "optical_result/000000" \ + -mop "checkpoints/optical.pth" \ + -mor "checkpoints/original.pth" +``` + +## TensorBoard + +Access TensorBoard by visiting `http://localhost:6006` in your browser after starting the container. + +To explicitly run TensorBoard: +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/logs:/app/logs \ + -p 6006:6006 \ + aigvdet:gpu \ + tensorboard --logdir=/app/logs --host=0.0.0.0 --port=6006 +``` + +## Interactive Shell + +**GPU container:** +```bash +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:gpu \ + /bin/bash +``` + +**CPU container:** +```bash +docker run -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + aigvdet:cpu \ + /bin/bash +``` + +## Volume Mounts Explained + +- `-v $(pwd)/data:/app/data` - Mount your training/test data +- `-v $(pwd)/checkpoints:/app/checkpoints` - Mount model checkpoints +- `-v $(pwd)/raft_model:/app/raft_model` - Mount RAFT model weights +- `-v $(pwd)/logs:/app/logs` - Mount training logs for TensorBoard + +## PowerShell Commands (Windows) + +On Windows PowerShell, use `${PWD}` instead of `$(pwd)`: + +```powershell +docker run --gpus all -it --rm ` + -v ${PWD}/data:/app/data ` + -v ${PWD}/checkpoints:/app/checkpoints ` + -v ${PWD}/raft_model:/app/raft_model ` + -p 6006:6006 ` + aigvdet:gpu +``` + +## Troubleshooting + +### GPU not detected +Ensure nvidia-docker2 is installed: +```bash +# Install nvidia-docker2 +sudo apt-get install nvidia-docker2 +sudo systemctl restart docker + +# Verify +docker run --rm --gpus all nvidia/cuda:11.7.1-base-ubuntu22.04 nvidia-smi +``` + +### Out of memory +Increase shared memory: +```bash +docker run --gpus all -it --rm --shm-size=8g ... +``` + +### Permission issues +If you encounter permission issues with mounted volumes: +```bash +docker run --gpus all -it --rm --user $(id -u):$(id -g) ... +``` + +## Image Sizes + +- GPU image: ~8-10GB (includes CUDA runtime) +- CPU image: ~2-3GB (lighter weight) + +## Cleaning Up + +Remove containers: +```bash +docker-compose down +``` + +Remove images: +```bash +docker rmi aigvdet:gpu aigvdet:cpu +``` + +Clean up all stopped containers and unused images: +```bash +docker system prune -a +``` diff --git a/Dockerfile.cpu b/Dockerfile.cpu new file mode 100644 index 0000000..b1a8454 --- /dev/null +++ b/Dockerfile.cpu @@ -0,0 +1,65 @@ +# Dockerfile for AIGVDet with CPU support +FROM python:3.11-slim-bookworm + +# Set environment variables +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + UV_SYSTEM_PYTHON=1 + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git \ + wget \ + curl \ + libglib2.0-0 \ + libsm6 \ + libxext6 \ + libxrender-dev \ + libgomp1 \ + libgl1-mesa-glx \ + && rm -rf /var/lib/apt/lists/* + +# Install uv +RUN pip install --no-cache-dir uv + +# Set working directory +WORKDIR /app + +# Copy project files +COPY pyproject.toml ./ +COPY README.md ./ +COPY core/ ./core/ +COPY networks/ ./networks/ +COPY train.py test.py demo.py ./ +COPY train.sh test.sh demo.sh ./ + +# Create directories for data and checkpoints +RUN mkdir -p /app/data /app/checkpoints /app/raft_model + +# Install Python dependencies with CPU support +RUN uv pip install --system torch==2.0.0+cpu torchvision==0.15.1+cpu \ + --index-url https://download.pytorch.org/whl/cpu + +# Install other dependencies +RUN uv pip install --system \ + einops \ + imageio \ + ipympl \ + matplotlib \ + "numpy<2.0" \ + opencv-python \ + pandas \ + scikit-learn \ + tensorboard \ + tensorboardX \ + tqdm \ + "blobfile>=1.0.5" + +# Set Python path +ENV PYTHONPATH=/app:$PYTHONPATH + +# Expose tensorboard port +EXPOSE 6006 + +# Default command +CMD ["python", "train.py", "--help"] diff --git a/Dockerfile.gpu b/Dockerfile.gpu new file mode 100644 index 0000000..144716b --- /dev/null +++ b/Dockerfile.gpu @@ -0,0 +1,69 @@ +# Dockerfile for AIGVDet with GPU support (CUDA 11.7) +FROM nvidia/cuda:11.7.1-cudnn8-runtime-ubuntu22.04 + +# Set environment variables +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + UV_SYSTEM_PYTHON=1 + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + python3.11 \ + python3.11-dev \ + python3-pip \ + git \ + wget \ + curl \ + libglib2.0-0 \ + libsm6 \ + libxext6 \ + libxrender-dev \ + libgomp1 \ + libgl1-mesa-glx \ + && rm -rf /var/lib/apt/lists/* + +# Install uv +RUN pip3 install --no-cache-dir uv + +# Set working directory +WORKDIR /app + +# Copy project files +COPY pyproject.toml ./ +COPY README.md ./ +COPY core/ ./core/ +COPY networks/ ./networks/ +COPY train.py test.py demo.py ./ +COPY train.sh test.sh demo.sh ./ + +# Create directories for data and checkpoints +RUN mkdir -p /app/data /app/checkpoints /app/raft_model + +# Install Python dependencies with GPU support +RUN uv pip install --system torch==2.0.0+cu117 torchvision==0.15.1+cu117 \ + --index-url https://download.pytorch.org/whl/cu117 + +# Install other dependencies +RUN uv pip install --system \ + einops \ + imageio \ + ipympl \ + matplotlib \ + "numpy<2.0" \ + opencv-python \ + pandas \ + scikit-learn \ + tensorboard \ + tensorboardX \ + tqdm \ + "blobfile>=1.0.5" + +# Set Python path +ENV PYTHONPATH=/app:$PYTHONPATH + +# Expose tensorboard port +EXPOSE 6006 + +# Default command +CMD ["python3.11", "train.py", "--help"] diff --git a/MIGRATION_SUMMARY.md b/MIGRATION_SUMMARY.md new file mode 100644 index 0000000..799cef4 --- /dev/null +++ b/MIGRATION_SUMMARY.md @@ -0,0 +1,124 @@ +# Migration Summary: AIGVDet Project from requirements.txt to uv + pyproject.toml + +## Overview +Successfully migrated the AIGVDet project from traditional pip/requirements.txt setup to modern uv package manager with pyproject.toml configuration. + +## Changes Made + +### 1. Installed uv Package Manager +- Installed uv Python package manager for faster dependency resolution and project management + +### 2. Created pyproject.toml Configuration +- Migrated all dependencies from `requirements.txt` to `pyproject.toml` +- Added project metadata including: + - Project name: `aigvdet` + - Version: `0.1.0` + - Description: AI-Generated Video Detection via Spatial-Temporal Anomaly Learning + - Authors: Jianfa Bai, Man Lin, Gang Cao, Zijie Lou + - Python version requirement: `>=3.11, <3.12` + +### 3. Dependencies Migrated +**Core Dependencies:** +- torch==2.0.0+cu117 (with CUDA 11.7 support) +- torchvision==0.15.1+cu117 +- einops +- imageio +- ipympl +- matplotlib +- numpy +- opencv-python +- pandas +- scikit-learn +- tensorboard +- tensorboardX +- tqdm +- blobfile>=1.0.5 +- pip + +**Development Dependencies:** +- hatchling (build backend) +- setuptools +- wheel + +### 4. PyTorch Configuration +- Configured custom PyTorch index for CUDA support +- Used PyTorch official wheel repository: https://download.pytorch.org/whl/cu117 +- Successfully installed PyTorch 2.0.0 with CUDA 11.7 support + +### 5. Build System Configuration +- Set up hatchling as the build backend +- Configured proper package structure for building +- Added console scripts for easy execution: + - `aigvdet-train` → `train:main` + - `aigvdet-test` → `test:main` + - `aigvdet-demo` → `demo:main` + +### 6. Virtual Environment +- Created Python 3.11.14 virtual environment +- All dependencies successfully installed and tested +- Project modules can be imported without issues + +### 7. Build Output +- Successfully built both source distribution (`.tar.gz`) and wheel (`.whl`) +- Files located in `dist/` directory: + - `aigvdet-0.1.0-py3-none-any.whl` (37 KB) + - `aigvdet-0.1.0.tar.gz` (27 MB) + +## Files Modified/Created +- ✅ Created: `pyproject.toml` (main configuration) +- ✅ Modified: `.python-version` (set to 3.11.14) +- ✅ Created: `.venv/` (virtual environment) +- ✅ Created: `dist/` (build outputs) +- ✅ Backup: `requirements.txt.backup` (original requirements preserved) + +## Verification Tests +- ✅ PyTorch imports successfully +- ✅ PyTorch version: 2.0.0+cu117 +- ✅ All project dependencies import correctly +- ✅ Core module imports without errors +- ✅ Project builds successfully + +## How to Use + +### Installation +```bash +# Clone/navigate to project directory +cd AIGVDet + +# Install all dependencies +uv sync + +# Or install with development dependencies +uv sync --group dev +``` + +### Running Scripts +```bash +# Activate the environment and run scripts +uv run python train.py --gpus 0 --exp_name TRAIN_RGB_BRANCH + +# Or use console scripts (if configured) +uv run aigvdet-train --gpus 0 --exp_name TRAIN_RGB_BRANCH +``` + +### Building +```bash +# Build source and wheel distributions +uv build +``` + +## Benefits of Migration +1. **Faster dependency resolution** - uv is significantly faster than pip +2. **Better dependency management** - lockfile support and conflict resolution +3. **Modern Python packaging** - follows PEP 621 standards +4. **Reproducible builds** - exact dependency versions locked +5. **Build system integration** - proper packaging with build backends +6. **Development workflow** - better separation of runtime and dev dependencies + +## Notes +- Original `requirements.txt` backed up as `requirements.txt.backup` +- Python 3.11 is used for compatibility with PyTorch 2.0.0+cu117 +- CUDA availability depends on system configuration +- All original functionality preserved + +The project is now ready for modern Python development workflows with uv! \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dba7818 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +.PHONY: help build-gpu build-cpu build-all run-gpu run-cpu up-gpu up-cpu shell-gpu shell-cpu clean + +help: + @echo "AIGVDet Docker Makefile" + @echo "" + @echo "Build commands:" + @echo " make build-gpu - Build GPU Docker image" + @echo " make build-cpu - Build CPU Docker image" + @echo " make build-all - Build both images" + @echo "" + @echo "Run commands:" + @echo " make up-gpu - Start GPU container with docker-compose" + @echo " make up-cpu - Start CPU container with docker-compose" + @echo " make run-gpu - Run GPU container interactively" + @echo " make run-cpu - Run CPU container interactively" + @echo "" + @echo "Development:" + @echo " make shell-gpu - Open bash shell in GPU container" + @echo " make shell-cpu - Open bash shell in CPU container" + @echo "" + @echo "Cleanup:" + @echo " make clean - Remove containers and images" + +build-gpu: + docker build -f Dockerfile.gpu -t aigvdet:gpu . + +build-cpu: + docker build -f Dockerfile.cpu -t aigvdet:cpu . + +build-all: build-cpu build-gpu + +run-gpu: + docker run --gpus all -it --rm \ + -v $(PWD)/data:/app/data \ + -v $(PWD)/checkpoints:/app/checkpoints \ + -v $(PWD)/raft_model:/app/raft_model \ + -v $(PWD)/logs:/app/logs \ + -p 6006:6006 \ + aigvdet:gpu + +run-cpu: + docker run -it --rm \ + -v $(PWD)/data:/app/data \ + -v $(PWD)/checkpoints:/app/checkpoints \ + -v $(PWD)/raft_model:/app/raft_model \ + -v $(PWD)/logs:/app/logs \ + -p 6006:6006 \ + aigvdet:cpu + +up-gpu: + docker-compose up aigvdet-gpu + +up-cpu: + docker-compose up aigvdet-cpu + +shell-gpu: + docker run --gpus all -it --rm \ + -v $(PWD)/data:/app/data \ + -v $(PWD)/checkpoints:/app/checkpoints \ + -v $(PWD)/raft_model:/app/raft_model \ + aigvdet:gpu /bin/bash + +shell-cpu: + docker run -it --rm \ + -v $(PWD)/data:/app/data \ + -v $(PWD)/checkpoints:/app/checkpoints \ + aigvdet:cpu /bin/bash + +clean: + docker-compose down + docker rmi aigvdet:gpu aigvdet:cpu || true + +train-gpu: + docker-compose run aigvdet-gpu python3.11 train.py --gpus 0 --exp_name default_exp + +train-cpu: + docker-compose run aigvdet-cpu python train.py --exp_name default_exp + +tensorboard: + docker run --gpus all -it --rm \ + -v $(PWD)/logs:/app/logs \ + -p 6006:6006 \ + aigvdet:gpu \ + tensorboard --logdir=/app/logs --host=0.0.0.0 --port=6006 diff --git a/README.md b/README.md index b3eb259..f343623 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ ## AIGVDet An official implementation code for paper "AI-Generated Video Detection via Spatial-Temporal Anomaly Learning", PRCV 2024. This repo will provide codes, trained weights, and our training datasets. +## 🐳 Docker Support +Now supports Docker with both CPU and GPU versions! See [DOCKER_QUICKREF.md](DOCKER_QUICKREF.md) for quick commands or [DOCKER_USAGE.md](DOCKER_USAGE.md) for detailed instructions. + +**Quick Start with Docker:** +```bash +# Build and run GPU version +docker-compose up --build aigvdet-gpu + +# Or build manually +./build-docker.ps1 gpu # Windows +./build-docker.sh gpu # Linux/Mac +``` + ## Network Architecture
architecture
diff --git a/build-docker.ps1 b/build-docker.ps1 new file mode 100644 index 0000000..a545b26 --- /dev/null +++ b/build-docker.ps1 @@ -0,0 +1,65 @@ +# PowerShell build script for AIGVDet Docker images + +Write-Host "======================================" -ForegroundColor Cyan +Write-Host "AIGVDet Docker Build Script" -ForegroundColor Cyan +Write-Host "======================================" -ForegroundColor Cyan +Write-Host "" + +function Build-Image { + param( + [string]$Dockerfile, + [string]$Tag + ) + + Write-Host "Building $Tag image..." -ForegroundColor Yellow + docker build -f $Dockerfile -t aigvdet:$Tag . + + if ($LASTEXITCODE -eq 0) { + Write-Host "✅ Successfully built aigvdet:$Tag" -ForegroundColor Green + } else { + Write-Host "❌ Failed to build aigvdet:$Tag" -ForegroundColor Red + exit 1 + } + Write-Host "" +} + +# Parse arguments +$BuildType = if ($args.Count -gt 0) { $args[0] } else { "all" } + +switch ($BuildType) { + "gpu" { + Write-Host "Building GPU image only..." -ForegroundColor Yellow + Build-Image -Dockerfile "Dockerfile.gpu" -Tag "gpu" + } + "cpu" { + Write-Host "Building CPU image only..." -ForegroundColor Yellow + Build-Image -Dockerfile "Dockerfile.cpu" -Tag "cpu" + } + "all" { + Write-Host "Building both CPU and GPU images..." -ForegroundColor Yellow + Build-Image -Dockerfile "Dockerfile.cpu" -Tag "cpu" + Build-Image -Dockerfile "Dockerfile.gpu" -Tag "gpu" + } + default { + Write-Host "Usage: .\build-docker.ps1 {gpu|cpu|all}" -ForegroundColor Red + Write-Host " gpu - Build GPU image only" + Write-Host " cpu - Build CPU image only" + Write-Host " all - Build both images (default)" + exit 1 + } +} + +Write-Host "======================================" -ForegroundColor Cyan +Write-Host "Build completed!" -ForegroundColor Green +Write-Host "======================================" -ForegroundColor Cyan +Write-Host "" +Write-Host "Available images:" -ForegroundColor Yellow +docker images | Select-String aigvdet +Write-Host "" +Write-Host "To run:" -ForegroundColor Yellow +Write-Host " GPU: docker run --gpus all -it --rm aigvdet:gpu" +Write-Host " CPU: docker run -it --rm aigvdet:cpu" +Write-Host "" +Write-Host "Or use docker-compose:" -ForegroundColor Yellow +Write-Host " docker-compose up aigvdet-gpu" +Write-Host " docker-compose up aigvdet-cpu" diff --git a/build-docker.sh b/build-docker.sh new file mode 100644 index 0000000..5320cca --- /dev/null +++ b/build-docker.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Build script for AIGVDet Docker images + +set -e + +echo "======================================" +echo "AIGVDet Docker Build Script" +echo "======================================" +echo "" + +# Function to build images +build_image() { + local dockerfile=$1 + local tag=$2 + + echo "Building $tag image..." + docker build -f $dockerfile -t aigvdet:$tag . + + if [ $? -eq 0 ]; then + echo "✅ Successfully built aigvdet:$tag" + else + echo "❌ Failed to build aigvdet:$tag" + exit 1 + fi + echo "" +} + +# Parse arguments +case "$1" in + gpu) + echo "Building GPU image only..." + build_image Dockerfile.gpu gpu + ;; + cpu) + echo "Building CPU image only..." + build_image Dockerfile.cpu cpu + ;; + all|"") + echo "Building both CPU and GPU images..." + build_image Dockerfile.cpu cpu + build_image Dockerfile.gpu gpu + ;; + *) + echo "Usage: $0 {gpu|cpu|all}" + echo " gpu - Build GPU image only" + echo " cpu - Build CPU image only" + echo " all - Build both images (default)" + exit 1 + ;; +esac + +echo "======================================" +echo "Build completed!" +echo "======================================" +echo "" +echo "Available images:" +docker images | grep aigvdet +echo "" +echo "To run:" +echo " GPU: docker run --gpus all -it --rm aigvdet:gpu" +echo " CPU: docker run -it --rm aigvdet:cpu" +echo "" +echo "Or use docker-compose:" +echo " docker-compose up aigvdet-gpu" +echo " docker-compose up aigvdet-cpu" diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3953acc28e6810e2d9f6bc4e548bdcc774d98ca9 GIT binary patch literal 169 zcmZ3^%ge<81Qpk1GePuY5CH>>P{wCAAY(d13PUi1CZpd=1.0.5", + "pip", +] + +[project.urls] +Repository = "https://github.com/sacdalance/AIGVDet" + +[project.scripts] +aigvdet-train = "train:main" +aigvdet-test = "test:main" +aigvdet-demo = "demo:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["core"] + +[tool.uv.sources] +torch = { index = "pytorch" } +torchvision = { index = "pytorch" } + +[[tool.uv.index]] +name = "pytorch" +url = "https://download.pytorch.org/whl/cu117" + +[dependency-groups] +dev = [ + "hatchling", + "setuptools", + "wheel", +] diff --git a/requirements.txt.backup b/requirements.txt.backup new file mode 100644 index 0000000..ce83367 --- /dev/null +++ b/requirements.txt.backup @@ -0,0 +1,14 @@ +# conda create -n aigvdet python=3.9 +# pip install torch==2.0.0+cu117 torchvision==0.15.1+cu117 -f https://download.pytorch.org/whl/torch_stable.html +einops +imageio +ipympl +matplotlib +numpy +opencv-python +pandas +scikit-learn +tensorboard +tensorboardX +tqdm +blobfile>=1.0.5 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..afddae4 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1045 @@ +version = 1 +revision = 3 +requires-python = "==3.11.*" +resolution-markers = [ + "sys_platform == 'darwin'", + "platform_machine == 'aarch64' and sys_platform == 'linux'", + "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", +] + +[[package]] +name = "absl-py" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, +] + +[[package]] +name = "aigvdet" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "blobfile" }, + { name = "einops" }, + { name = "imageio" }, + { name = "ipympl" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pip" }, + { name = "scikit-learn" }, + { name = "tensorboard" }, + { name = "tensorboardx" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, +] + +[package.dev-dependencies] +dev = [ + { name = "hatchling" }, + { name = "setuptools" }, + { name = "wheel" }, +] + +[package.metadata] +requires-dist = [ + { name = "blobfile", specifier = ">=1.0.5" }, + { name = "einops" }, + { name = "imageio" }, + { name = "ipympl" }, + { name = "matplotlib" }, + { name = "numpy", specifier = "<2.0" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pip" }, + { name = "scikit-learn" }, + { name = "tensorboard" }, + { name = "tensorboardx" }, + { name = "torch", specifier = "==2.0.0+cu117", index = "https://download.pytorch.org/whl/cu117" }, + { name = "torchvision", specifier = "==0.15.1+cu117", index = "https://download.pytorch.org/whl/cu117" }, + { name = "tqdm" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "hatchling" }, + { name = "setuptools" }, + { name = "wheel" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "blobfile" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "lxml" }, + { name = "pycryptodomex" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/6d/2e7567da75ddbb24fe979f52284b708da349d67a41042635af36071a5a6b/blobfile-3.1.0.tar.gz", hash = "sha256:d45b6b1fa3b0920732314c23ddbdb4f494ca12f787c2b6eb6bba6faa51382671", size = 77229, upload-time = "2025-09-06T00:36:15.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/a7/51af11120d75af2828f8eede0b13a4caff650d708ac50e62d000aefe1ffb/blobfile-3.1.0-py3-none-any.whl", hash = "sha256:2b4c5e766ebb7dfa20e4990cf6ec3d2106bdc91d632fb9377f170a234c5a5c6a", size = 75741, upload-time = "2025-09-06T00:36:14.11Z" }, +] + +[[package]] +name = "certifi" +version = "2022.12.7" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" }, +] + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" }, +] + +[[package]] +name = "cmake" +version = "3.25.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/cmake-3.25.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7fd744a90e4d804ff77ac50d3570009911fbfdad29c59fc93d2a82faaeb371" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "einops" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/81/df4fbe24dff8ba3934af99044188e20a98ed441ad17a274539b74e82e126/einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84", size = 54805, upload-time = "2025-02-09T03:17:00.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359, upload-time = "2025-02-09T03:17:01.998Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/filelock-3.19.1-py3-none-any.whl" }, +] + +[[package]] +name = "fonttools" +version = "4.60.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872, upload-time = "2025-09-29T21:11:20.329Z" }, + { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990, upload-time = "2025-09-29T21:11:22.754Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189, upload-time = "2025-09-29T21:11:25.061Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683, upload-time = "2025-09-29T21:11:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372, upload-time = "2025-09-29T21:11:30.257Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562, upload-time = "2025-09-29T21:11:32.737Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288, upload-time = "2025-09-29T21:11:35.015Z" }, + { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184, upload-time = "2025-09-29T21:11:37.434Z" }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, + { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, + { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, + { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, +] + +[[package]] +name = "hatchling" +version = "1.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/8a4a67a8174ce59cf49e816e38e9502900aea9b4af672d0127df8e10d3b0/hatchling-1.25.0.tar.gz", hash = "sha256:7064631a512610b52250a4d3ff1bd81551d6d1431c4eb7b72e734df6c74f4262", size = 64632, upload-time = "2024-06-22T17:27:01.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/8b/90e80904fdc24ce33f6fc6f35ebd2232fe731a8528a22008458cf197bc4d/hatchling-1.25.0-py3-none-any.whl", hash = "sha256:b47948e45d4d973034584dd4cb39c14b6a70227cf287ab7ec0ad7983408a882c", size = 84077, upload-time = "2024-06-22T17:26:59.671Z" }, +] + +[[package]] +name = "idna" +version = "3.4" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" }, +] + +[[package]] +name = "imageio" +version = "2.37.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/6f/606be632e37bf8d05b253e8626c2291d74c691ddc7bcdf7d6aaf33b32f6a/imageio-2.37.2.tar.gz", hash = "sha256:0212ef2727ac9caa5ca4b2c75ae89454312f440a756fcfc8ef1993e718f50f8a", size = 389600, upload-time = "2025-11-04T14:29:39.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/fe/301e0936b79bcab4cacc7548bf2853fc28dced0a578bab1f7ef53c9aa75b/imageio-2.37.2-py3-none-any.whl", hash = "sha256:ad9adfb20335d718c03de457358ed69f141021a333c40a53e57273d8a5bd0b9b", size = 317646, upload-time = "2025-11-04T14:29:37.948Z" }, +] + +[[package]] +name = "ipympl" +version = "0.9.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython" }, + { name = "ipywidgets" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/8c/f9e60abf409cef8234e66e69ce3fe263f1236b285f9105ea125e4660b77a/ipympl-0.9.8.tar.gz", hash = "sha256:6d7230d518384521093f3854f7db89d069dcd9c28a935b371e9c9f126354dee1", size = 58483988, upload-time = "2025-10-09T14:20:07.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/6e/9148bfed8ca535e4c61ce7843327c76ec7c63c40e33848ec03aa844a26af/ipympl-0.9.8-py3-none-any.whl", hash = "sha256:4a03612f77d92c9e2160c9e0d2a80b277e30387126399088f780dba9622247be", size = 515832, upload-time = "2025-10-09T14:20:05.39Z" }, +] + +[[package]] +name = "ipython" +version = "9.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/e6/48c74d54039241a456add616464ea28c6ebf782e4110d419411b83dae06f/ipython-9.7.0.tar.gz", hash = "sha256:5f6de88c905a566c6a9d6c400a8fed54a638e1f7543d17aae2551133216b1e4e", size = 4422115, upload-time = "2025-11-05T12:18:54.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl", hash = "sha256:bce8ac85eb9521adc94e1845b4c03d88365fd6ac2f4908ec4ed1eb1b0a065f9f", size = 618911, upload-time = "2025-11-05T12:18:52.484Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "markupsafe" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, + { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, + { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, + { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +] + +[[package]] +name = "lit" +version = "15.0.7" +source = { registry = "https://download.pytorch.org/whl/cu117" } +sdist = { url = "https://download.pytorch.org/whl/lit-15.0.7.tar.gz", hash = "sha256:ed08ac55afe714a193653df293ae8a6ee6c45d6fb11eeca72ce347d99b88ecc8" } + +[[package]] +name = "lxml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, + { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, + { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, + { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, + { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, + { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, +] + +[[package]] +name = "markdown" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +source = { registry = "https://download.pytorch.org/whl/cu117" } +sdist = { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b" } +wheels = [ + { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f" }, + { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2" }, + { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced" }, + { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5" }, + { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c" }, + { url = "https://download.pytorch.org/whl/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865, upload-time = "2025-10-09T00:28:00.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507, upload-time = "2025-10-09T00:26:19.073Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565, upload-time = "2025-10-09T00:26:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668, upload-time = "2025-10-09T00:26:22.967Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/664d2b97016f46683a02d854d730cfcf54ff92c1dafa424beebef50f831d/matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", size = 9521051, upload-time = "2025-10-09T00:26:25.041Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a3/37aef1404efa615f49b5758a5e0261c16dd88f389bc1861e722620e4a754/matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", size = 9576878, upload-time = "2025-10-09T00:26:27.478Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", size = 8115142, upload-time = "2025-10-09T00:26:29.774Z" }, + { url = "https://files.pythonhosted.org/packages/2e/39/63bca9d2b78455ed497fcf51a9c71df200a11048f48249038f06447fa947/matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", size = 7992439, upload-time = "2025-10-09T00:26:40.32Z" }, + { url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283, upload-time = "2025-10-09T00:27:54.739Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733, upload-time = "2025-10-09T00:27:56.406Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919, upload-time = "2025-10-09T00:27:58.41Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/networkx-3.5-py3-none-any.whl" }, +] + +[[package]] +name = "numpy" +version = "1.26.3" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374" }, + { url = "https://download.pytorch.org/whl/numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6" }, + { url = "https://download.pytorch.org/whl/numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2" }, + { url = "https://download.pytorch.org/whl/numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda" }, + { url = "https://download.pytorch.org/whl/numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4" }, +] + +[[package]] +name = "opencv-python" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322, upload-time = "2025-01-16T13:52:25.887Z" }, + { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197, upload-time = "2025-01-16T13:55:21.222Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439, upload-time = "2025-01-16T13:51:35.822Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597, upload-time = "2025-01-16T13:52:08.836Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337, upload-time = "2025-01-16T13:52:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-win_amd64.whl" }, + { url = "https://download.pytorch.org/whl/pillow-11.3.0-cp311-cp311-win_arm64.whl" }, +] + +[[package]] +name = "pip" +version = "25.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/6e/74a3f0179a4a73a53d66ce57fdb4de0080a8baa1de0063de206d6167acc2/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343", size = 1803014, upload-time = "2025-10-25T00:55:41.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/3c/d717024885424591d5376220b5e836c2d5293ce2011523c9de23ff7bf068/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd", size = 1778622, upload-time = "2025-10-25T00:55:39.247Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432, upload-time = "2025-11-13T16:44:18.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593, upload-time = "2025-11-13T16:44:06.275Z" }, + { url = "https://files.pythonhosted.org/packages/a6/79/8780a378c650e3df849b73de8b13cf5412f521ca2ff9b78a45c247029440/protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", size = 436883, upload-time = "2025-11-13T16:44:09.222Z" }, + { url = "https://files.pythonhosted.org/packages/cd/93/26213ff72b103ae55bb0d73e7fb91ea570ef407c3ab4fd2f1f27cac16044/protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490", size = 427522, upload-time = "2025-11-13T16:44:10.475Z" }, + { url = "https://files.pythonhosted.org/packages/c2/32/df4a35247923393aa6b887c3b3244a8c941c32a25681775f96e2b418f90e/protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", size = 324445, upload-time = "2025-11-13T16:44:11.869Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d0/d796e419e2ec93d2f3fa44888861c3f88f722cde02b7c3488fcc6a166820/protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", size = 339161, upload-time = "2025-11-13T16:44:12.778Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2a/3c5f05a4af06649547027d288747f68525755de692a26a7720dced3652c0/protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", size = 323171, upload-time = "2025-11-13T16:44:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477, upload-time = "2025-11-13T16:44:17.633Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycryptodomex" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/85/e24bf90972a30b0fcd16c73009add1d7d7cd9140c2498a68252028899e41/pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da", size = 4922157, upload-time = "2025-05-17T17:23:41.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/9c/1a8f35daa39784ed8adf93a694e7e5dc15c23c741bbda06e1d45f8979e9e/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6", size = 2499240, upload-time = "2025-05-17T17:22:46.953Z" }, + { url = "https://files.pythonhosted.org/packages/7a/62/f5221a191a97157d240cf6643747558759126c76ee92f29a3f4aee3197a5/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545", size = 1644042, upload-time = "2025-05-17T17:22:49.098Z" }, + { url = "https://files.pythonhosted.org/packages/8c/fd/5a054543c8988d4ed7b612721d7e78a4b9bf36bc3c5ad45ef45c22d0060e/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587", size = 2186227, upload-time = "2025-05-17T17:22:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c", size = 2272578, upload-time = "2025-05-17T17:22:53.676Z" }, + { url = "https://files.pythonhosted.org/packages/46/9f/bda9c49a7c1842820de674ab36c79f4fbeeee03f8ff0e4f3546c3889076b/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c", size = 2312166, upload-time = "2025-05-17T17:22:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/03/cc/870b9bf8ca92866ca0186534801cf8d20554ad2a76ca959538041b7a7cf4/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003", size = 2185467, upload-time = "2025-05-17T17:22:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/96/e3/ce9348236d8e669fea5dd82a90e86be48b9c341210f44e25443162aba187/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744", size = 2346104, upload-time = "2025-05-17T17:23:02.112Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e869bcee87beb89040263c416a8a50204f7f7a83ac11897646c9e71e0daf/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd", size = 2271038, upload-time = "2025-05-17T17:23:04.872Z" }, + { url = "https://files.pythonhosted.org/packages/8d/67/09ee8500dd22614af5fbaa51a4aee6e342b5fa8aecf0a6cb9cbf52fa6d45/pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c", size = 1771969, upload-time = "2025-05-17T17:23:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/69/96/11f36f71a865dd6df03716d33bd07a67e9d20f6b8d39820470b766af323c/pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9", size = 1803124, upload-time = "2025-05-17T17:23:09.267Z" }, + { url = "https://files.pythonhosted.org/packages/f9/93/45c1cdcbeb182ccd2e144c693eaa097763b08b38cded279f0053ed53c553/pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51", size = 1707161, upload-time = "2025-05-17T17:23:11.414Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "requests" +version = "2.28.1" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881, upload-time = "2025-10-28T17:31:47.104Z" }, + { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012, upload-time = "2025-10-28T17:31:53.411Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935, upload-time = "2025-10-28T17:31:57.361Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466, upload-time = "2025-10-28T17:32:01.875Z" }, + { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618, upload-time = "2025-10-28T17:32:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798, upload-time = "2025-10-28T17:32:12.665Z" }, + { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154, upload-time = "2025-10-28T17:32:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540, upload-time = "2025-10-28T17:32:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107, upload-time = "2025-10-28T17:32:29.921Z" }, + { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" }, +] + +[[package]] +name = "setuptools" +version = "70.2.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "mpmath" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/sympy-1.14.0-py3-none-any.whl" }, +] + +[[package]] +name = "tensorboard" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "grpcio" }, + { name = "markdown" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "setuptools" }, + { name = "tensorboard-data-server" }, + { name = "werkzeug" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, +] + +[[package]] +name = "tensorboardx" +version = "2.6.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/c5/d4cc6e293fb837aaf9f76dd7745476aeba8ef7ef5146c3b3f9ee375fe7a5/tensorboardx-2.6.4.tar.gz", hash = "sha256:b163ccb7798b31100b9f5fa4d6bc22dad362d7065c2f24b51e50731adde86828", size = 4769801, upload-time = "2025-06-10T22:37:07.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/1d/b5d63f1a6b824282b57f7b581810d20b7a28ca951f2d5b59f1eb0782c12b/tensorboardx-2.6.4-py3-none-any.whl", hash = "sha256:5970cf3a1f0a6a6e8b180ccf46f3fe832b8a25a70b86e5a237048a7c0beb18e2", size = 87201, upload-time = "2025-06-10T22:37:05.44Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "torch" +version = "2.0.0+cu117" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "filelock" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cu117/torch-2.0.0%2Bcu117-cp311-cp311-linux_x86_64.whl", hash = "sha256:b078675648025f1dae1cdc8955f975f5ca81167809e9662b1481e456171ebfb9" }, + { url = "https://download.pytorch.org/whl/cu117/torch-2.0.0%2Bcu117-cp311-cp311-win_amd64.whl", hash = "sha256:f0b525686f25c30e1de87d0fbdcd0b373f4c70a0f72bd854389e601a52fdc5e5" }, +] + +[[package]] +name = "torchvision" +version = "0.15.1+cu117" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "requests" }, + { name = "torch" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cu117/torchvision-0.15.1%2Bcu117-cp311-cp311-linux_x86_64.whl", hash = "sha256:67ff110078913bf684893c4a13d9415f11f6908f40f33fa6626726913196b279" }, + { url = "https://download.pytorch.org/whl/cu117/torchvision-0.15.1%2Bcu117-cp311-cp311-win_amd64.whl", hash = "sha256:6d82ede144cabe85f21814c723b3a666d427de9566056701863663ea0f2ad7d9" }, +] + +[[package]] +name = "tqdm" +version = "4.66.5" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "triton" +version = "2.0.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +dependencies = [ + { name = "cmake" }, + { name = "filelock" }, + { name = "lit" }, + { name = "torch" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1" }, + { url = "https://download.pytorch.org/whl/triton-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4b99ca3c6844066e516658541d876c28a5f6e3a852286bbc97ad57134827fd" }, +] + +[[package]] +name = "trove-classifiers" +version = "2025.11.14.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/a9/880cccf76af9e7b322112f52e4e2dbb3534cbe671197b8f443a42189dfc7/trove_classifiers-2025.11.14.15.tar.gz", hash = "sha256:6b60f49d40bbd895bc61d8dc414fc2f2286d70eb72ed23548db8cf94f62804ca", size = 16995, upload-time = "2025-11-14T15:23:13.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/f6/73c4aa003d1237ee9bea8a46f49dc38c45dfe95af4f0da7e60678d388011/trove_classifiers-2025.11.14.15-py3-none-any.whl", hash = "sha256:d1dac259c1e908939862e3331177931c6df0a37af2c1a8debcc603d9115fcdd9", size = 14191, upload-time = "2025-11-14T15:23:12.467Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/typing_extensions-4.15.0-py3-none-any.whl" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "1.26.13" +source = { registry = "https://download.pytorch.org/whl/cu117" } +wheels = [ + { url = "https://download.pytorch.org/whl/urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, +] From 2cb6881a50dec6e6fa6bac2cd2fbd526b1938bbe Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:22:42 +0800 Subject: [PATCH 02/55] Enhance Docker support with updated Dockerfiles, push scripts, and quick reference documentation --- DOCKER_QUICKREF.md | 16 ++++- Dockerfile.cpu | 12 ++-- Dockerfile.gpu | 12 ++-- Dockerfile.gpu-alt | 67 ++++++++++++++++++++ docker-compose.yml | 4 +- push-docker.ps1 | 144 +++++++++++++++++++++++++++++++++++++++++++ push-docker.sh | 150 +++++++++++++++++++++++++++++++++++++++++++++ push-simple.ps1 | 49 +++++++++++++++ 8 files changed, 437 insertions(+), 17 deletions(-) create mode 100644 Dockerfile.gpu-alt create mode 100644 push-docker.ps1 create mode 100644 push-docker.sh create mode 100644 push-simple.ps1 diff --git a/DOCKER_QUICKREF.md b/DOCKER_QUICKREF.md index abb84b6..a50b348 100644 --- a/DOCKER_QUICKREF.md +++ b/DOCKER_QUICKREF.md @@ -1,5 +1,15 @@ # AIGVDet Docker Quick Reference +## Pull from Docker Hub + +```bash +# Pull GPU version +docker pull sacdalance/thesis-aigvdet:gpu + +# Pull CPU version +docker pull sacdalance/thesis-aigvdet:cpu +``` + ## Build Images ### Windows (PowerShell) @@ -51,7 +61,7 @@ docker run --gpus all -it --rm ` -v ${PWD}/checkpoints:/app/checkpoints ` -v ${PWD}/raft_model:/app/raft_model ` -p 6006:6006 ` - aigvdet:gpu ` + sacdalance/thesis-aigvdet:gpu ` python3.11 train.py --gpus 0 --exp_name my_experiment ``` @@ -61,7 +71,7 @@ docker run -it --rm ` -v ${PWD}/data:/app/data ` -v ${PWD}/checkpoints:/app/checkpoints ` -p 6006:6006 ` - aigvdet:cpu ` + sacdalance/thesis-aigvdet:cpu ` python train.py --exp_name my_experiment ``` @@ -72,7 +82,7 @@ docker run --gpus all -it --rm \ -v $(pwd)/checkpoints:/app/checkpoints \ -v $(pwd)/raft_model:/app/raft_model \ -p 6006:6006 \ - aigvdet:gpu \ + sacdalance/thesis-aigvdet:gpu \ python3.11 train.py --gpus 0 --exp_name my_experiment ``` diff --git a/Dockerfile.cpu b/Dockerfile.cpu index b1a8454..d803641 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -36,11 +36,7 @@ COPY train.sh test.sh demo.sh ./ # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model -# Install Python dependencies with CPU support -RUN uv pip install --system torch==2.0.0+cpu torchvision==0.15.1+cpu \ - --index-url https://download.pytorch.org/whl/cpu - -# Install other dependencies +# Install other dependencies first RUN uv pip install --system \ einops \ imageio \ @@ -55,8 +51,12 @@ RUN uv pip install --system \ tqdm \ "blobfile>=1.0.5" +# Install PyTorch CPU version using pip directly +RUN pip3 install torch==2.0.0+cpu torchvision==0.15.1+cpu \ + --index-url https://download.pytorch.org/whl/cpu + # Set Python path -ENV PYTHONPATH=/app:$PYTHONPATH +ENV PYTHONPATH=/app # Expose tensorboard port EXPOSE 6006 diff --git a/Dockerfile.gpu b/Dockerfile.gpu index 144716b..d3568ab 100644 --- a/Dockerfile.gpu +++ b/Dockerfile.gpu @@ -40,11 +40,7 @@ COPY train.sh test.sh demo.sh ./ # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model -# Install Python dependencies with GPU support -RUN uv pip install --system torch==2.0.0+cu117 torchvision==0.15.1+cu117 \ - --index-url https://download.pytorch.org/whl/cu117 - -# Install other dependencies +# Install other dependencies first RUN uv pip install --system \ einops \ imageio \ @@ -59,8 +55,12 @@ RUN uv pip install --system \ tqdm \ "blobfile>=1.0.5" +# Install PyTorch GPU version using pip directly +RUN pip3 install torch==2.0.0+cu117 torchvision==0.15.1+cu117 \ + --index-url https://download.pytorch.org/whl/cu117 + # Set Python path -ENV PYTHONPATH=/app:$PYTHONPATH +ENV PYTHONPATH=/app # Expose tensorboard port EXPOSE 6006 diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt new file mode 100644 index 0000000..f521a83 --- /dev/null +++ b/Dockerfile.gpu-alt @@ -0,0 +1,67 @@ +# Alternative GPU Dockerfile using PyTorch base image (avoids large download) +FROM pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime + +# Set environment variables +ENV DEBIAN_FRONTEND=noninteractive \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + UV_SYSTEM_PYTHON=1 + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git \ + wget \ + curl \ + libglib2.0-0 \ + libsm6 \ + libxext6 \ + libxrender-dev \ + libgomp1 \ + libgl1-mesa-glx \ + && rm -rf /var/lib/apt/lists/* + +# Install uv +RUN pip3 install --no-cache-dir uv + +# Set working directory +WORKDIR /app + +# Copy project files +COPY pyproject.toml ./ +COPY README.md ./ +COPY core/ ./core/ +COPY networks/ ./networks/ +COPY train.py test.py demo.py ./ +COPY train.sh test.sh demo.sh ./ + +# Create directories for data and checkpoints +RUN mkdir -p /app/data /app/checkpoints /app/raft_model + +# Install dependencies (PyTorch already included in base image) +RUN uv pip install --system \ + einops \ + imageio \ + ipympl \ + matplotlib \ + "numpy<2.0" \ + opencv-python \ + pandas \ + scikit-learn \ + tensorboard \ + tensorboardX \ + tqdm \ + "blobfile>=1.0.5" + +# Install torchvision separately (base image has torch but may need torchvision update) +RUN pip3 install torchvision==0.15.1+cu117 \ + --index-url https://download.pytorch.org/whl/cu117 || \ + pip3 install torchvision==0.15.1 + +# Set Python path +ENV PYTHONPATH=/app + +# Expose tensorboard port +EXPOSE 6006 + +# Default command +CMD ["/bin/bash"] diff --git a/docker-compose.yml b/docker-compose.yml index 0b6ec85..14febbf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: build: context: . dockerfile: Dockerfile.gpu - image: aigvdet:gpu + image: sacdalance/thesis-aigvdet:gpu container_name: aigvdet-gpu runtime: nvidia environment: @@ -34,7 +34,7 @@ services: build: context: . dockerfile: Dockerfile.cpu - image: aigvdet:cpu + image: sacdalance/thesis-aigvdet:cpu container_name: aigvdet-cpu volumes: - ./data:/app/data diff --git a/push-docker.ps1 b/push-docker.ps1 new file mode 100644 index 0000000..d5ecafb --- /dev/null +++ b/push-docker.ps1 @@ -0,0 +1,144 @@ +# Docker Push Script for AIGVDet +# Pushes images to Docker Hub: sacdalance/thesis-aigvdet + +param( + [Parameter(Mandatory=$false)] + [ValidateSet("gpu", "cpu", "all")] + [string]$Version = "all", + + [Parameter(Mandatory=$false)] + [string]$Tag = "latest" +) + +$ErrorActionPreference = "Stop" +$Repository = "sacdalance/thesis-aigvdet" + +Write-Host "`n╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan +Write-Host "║ Docker Push Script - AIGVDet ║" -ForegroundColor Cyan +Write-Host "║ Repository: $Repository ║" -ForegroundColor Cyan +Write-Host "╚═══════════════════════════════════════════════════════════╝`n" -ForegroundColor Cyan + +function Build-And-Push-Image { + param( + [string]$Dockerfile, + [string]$ImageTag, + [string]$VersionTag + ) + + $LocalTag = "aigvdet:$ImageTag" + $RemoteTag = "${Repository}:${VersionTag}" + $RemoteLatestTag = "${Repository}:${ImageTag}" + + Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray + Write-Host "🔨 Building $ImageTag image..." -ForegroundColor Yellow + Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray + + docker build -f $Dockerfile -t $LocalTag -t $RemoteTag -t $RemoteLatestTag . + + if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Failed to build $ImageTag image" -ForegroundColor Red + exit 1 + } + + Write-Host "✅ Successfully built $ImageTag image" -ForegroundColor Green + Write-Host "" + + Write-Host "📤 Pushing to Docker Hub..." -ForegroundColor Yellow + + # Push with version tag + Write-Host " → Pushing ${RemoteTag}..." -ForegroundColor Cyan + docker push $RemoteTag + + if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Failed to push ${RemoteTag}" -ForegroundColor Red + exit 1 + } + + # Push with latest tag + Write-Host " → Pushing ${RemoteLatestTag}..." -ForegroundColor Cyan + docker push $RemoteLatestTag + + if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Failed to push ${RemoteLatestTag}" -ForegroundColor Red + exit 1 + } + + Write-Host "✅ Successfully pushed $ImageTag image to Docker Hub" -ForegroundColor Green + Write-Host "" +} + +# Check if logged in to Docker Hub +Write-Host "🔐 Checking Docker Hub authentication..." -ForegroundColor Yellow +docker info | Out-Null +if ($LASTEXITCODE -ne 0) { + Write-Host "❌ Docker is not running or not accessible" -ForegroundColor Red + exit 1 +} + +# Try to verify login (will fail if not logged in) +$loginCheck = docker info 2>&1 | Select-String "Username" +if (-not $loginCheck) { + Write-Host "⚠️ You may not be logged in to Docker Hub" -ForegroundColor Yellow + Write-Host "Please run: docker login" -ForegroundColor Yellow + $continue = Read-Host "`nContinue anyway? (y/N)" + if ($continue -ne "y" -and $continue -ne "Y") { + exit 0 + } +} +Write-Host "✅ Docker is ready`n" -ForegroundColor Green + +# Build and push based on version +switch ($Version) { + "gpu" { + Write-Host "Building and pushing GPU version only...`n" -ForegroundColor Yellow + Build-And-Push-Image -Dockerfile "Dockerfile.gpu" -ImageTag "gpu" -VersionTag "$Tag-gpu" + } + "cpu" { + Write-Host "Building and pushing CPU version only...`n" -ForegroundColor Yellow + Build-And-Push-Image -Dockerfile "Dockerfile.cpu" -ImageTag "cpu" -VersionTag "$Tag-cpu" + } + "all" { + Write-Host "Building and pushing both CPU and GPU versions...`n" -ForegroundColor Yellow + Build-And-Push-Image -Dockerfile "Dockerfile.cpu" -ImageTag "cpu" -VersionTag "$Tag-cpu" + Build-And-Push-Image -Dockerfile "Dockerfile.gpu" -ImageTag "gpu" -VersionTag "$Tag-gpu" + } +} + +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray +Write-Host "🎉 All images pushed successfully!" -ForegroundColor Green +Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray +Write-Host "" +Write-Host "📦 Available images on Docker Hub:" -ForegroundColor Yellow + +if ($Version -eq "cpu" -or $Version -eq "all") { + Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan + Write-Host ":cpu" -ForegroundColor Cyan + Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan + Write-Host ":$Tag-cpu" -ForegroundColor Cyan +} + +if ($Version -eq "gpu" -or $Version -eq "all") { + Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan + Write-Host ":gpu" -ForegroundColor Cyan + Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan + Write-Host ":$Tag-gpu" -ForegroundColor Cyan +} + +Write-Host "" +Write-Host "🚀 Pull commands:" -ForegroundColor Yellow + +if ($Version -eq "cpu" -or $Version -eq "all") { + Write-Host " CPU: docker pull $Repository" -NoNewline -ForegroundColor White + Write-Host ":cpu" -ForegroundColor White +} + +if ($Version -eq "gpu" -or $Version -eq "all") { + Write-Host " GPU: docker pull $Repository" -NoNewline -ForegroundColor White + Write-Host ":gpu" -ForegroundColor White +} + +Write-Host "" +Write-Host "💡 View on Docker Hub:" -ForegroundColor Yellow +$DockerHubUrl = "https://hub.docker.com/r/$Repository" +Write-Host " $DockerHubUrl" -ForegroundColor Cyan +Write-Host "" diff --git a/push-docker.sh b/push-docker.sh new file mode 100644 index 0000000..c59a862 --- /dev/null +++ b/push-docker.sh @@ -0,0 +1,150 @@ +#!/bin/bash +# Docker Push Script for AIGVDet +# Pushes images to Docker Hub: sacdalance/thesis-aigvdet + +set -e + +VERSION="${1:-all}" +TAG="${2:-latest}" +REPOSITORY="sacdalance/thesis-aigvdet" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +echo "" +echo "╔═══════════════════════════════════════════════════════════╗" +echo "║ Docker Push Script - AIGVDet ║" +echo "║ Repository: ${REPOSITORY}" +echo "╚═══════════════════════════════════════════════════════════╝" +echo "" + +build_and_push_image() { + local dockerfile=$1 + local image_tag=$2 + local version_tag=$3 + + local local_tag="aigvdet:${image_tag}" + local remote_tag="${REPOSITORY}:${version_tag}" + local remote_latest_tag="${REPOSITORY}:${image_tag}" + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "${YELLOW}🔨 Building ${image_tag} image...${NC}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + docker build -f "${dockerfile}" -t "${local_tag}" -t "${remote_tag}" -t "${remote_latest_tag}" . + + if [ $? -ne 0 ]; then + echo -e "${RED}❌ Failed to build ${image_tag} image${NC}" + exit 1 + fi + + echo -e "${GREEN}✅ Successfully built ${image_tag} image${NC}" + echo "" + + echo -e "${YELLOW}📤 Pushing to Docker Hub...${NC}" + + # Push with version tag + echo -e "${CYAN} → Pushing ${remote_tag}...${NC}" + docker push "${remote_tag}" + + if [ $? -ne 0 ]; then + echo -e "${RED}❌ Failed to push ${remote_tag}${NC}" + exit 1 + fi + + # Push with latest tag + echo -e "${CYAN} → Pushing ${remote_latest_tag}...${NC}" + docker push "${remote_latest_tag}" + + if [ $? -ne 0 ]; then + echo -e "${RED}❌ Failed to push ${remote_latest_tag}${NC}" + exit 1 + fi + + echo -e "${GREEN}✅ Successfully pushed ${image_tag} image to Docker Hub${NC}" + echo "" +} + +# Check if Docker is running +echo -e "${YELLOW}🔐 Checking Docker authentication...${NC}" +if ! docker info > /dev/null 2>&1; then + echo -e "${RED}❌ Docker is not running or not accessible${NC}" + exit 1 +fi + +# Check if logged in +if ! docker info 2>&1 | grep -q "Username"; then + echo -e "${YELLOW}⚠️ You may not be logged in to Docker Hub${NC}" + echo -e "${YELLOW}Please run: docker login${NC}" + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 0 + fi +fi +echo -e "${GREEN}✅ Docker is ready${NC}" +echo "" + +# Build and push based on version +case "$VERSION" in + gpu) + echo -e "${YELLOW}Building and pushing GPU version only...${NC}" + echo "" + build_and_push_image "Dockerfile.gpu" "gpu" "${TAG}-gpu" + ;; + cpu) + echo -e "${YELLOW}Building and pushing CPU version only...${NC}" + echo "" + build_and_push_image "Dockerfile.cpu" "cpu" "${TAG}-cpu" + ;; + all) + echo -e "${YELLOW}Building and pushing both CPU and GPU versions...${NC}" + echo "" + build_and_push_image "Dockerfile.cpu" "cpu" "${TAG}-cpu" + build_and_push_image "Dockerfile.gpu" "gpu" "${TAG}-gpu" + ;; + *) + echo -e "${RED}Usage: $0 {gpu|cpu|all} [tag]${NC}" + echo " gpu - Push GPU image only" + echo " cpu - Push CPU image only" + echo " all - Push both images (default)" + echo " tag - Version tag (default: latest)" + exit 1 + ;; +esac + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "${GREEN}🎉 All images pushed successfully!${NC}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +echo -e "${YELLOW}📦 Available images on Docker Hub:${NC}" + +if [ "$VERSION" = "cpu" ] || [ "$VERSION" = "all" ]; then + echo -e "${CYAN} • ${REPOSITORY}:cpu${NC}" + echo -e "${CYAN} • ${REPOSITORY}:${TAG}-cpu${NC}" +fi + +if [ "$VERSION" = "gpu" ] || [ "$VERSION" = "all" ]; then + echo -e "${CYAN} • ${REPOSITORY}:gpu${NC}" + echo -e "${CYAN} • ${REPOSITORY}:${TAG}-gpu${NC}" +fi + +echo "" +echo -e "${YELLOW}🚀 Pull commands:${NC}" + +if [ "$VERSION" = "cpu" ] || [ "$VERSION" = "all" ]; then + echo " CPU: docker pull ${REPOSITORY}:cpu" +fi + +if [ "$VERSION" = "gpu" ] || [ "$VERSION" = "all" ]; then + echo " GPU: docker pull ${REPOSITORY}:gpu" +fi + +echo "" +echo -e "${YELLOW}💡 View on Docker Hub:${NC}" +echo -e "${CYAN} https://hub.docker.com/r/${REPOSITORY}${NC}" +echo "" diff --git a/push-simple.ps1 b/push-simple.ps1 new file mode 100644 index 0000000..b95146f --- /dev/null +++ b/push-simple.ps1 @@ -0,0 +1,49 @@ +# Simple Docker Push - Complete Workflow +# Run this after images are built + +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host "Docker Push to sacdalance/thesis-aigvdet" -ForegroundColor Green +Write-Host "========================================`n" -ForegroundColor Cyan + +$repo = "sacdalance/thesis-aigvdet" + +# Push CPU image +Write-Host "📤 Pushing CPU image..." -ForegroundColor Yellow +docker push "${repo}:cpu" +docker push "${repo}:latest-cpu" + +if ($LASTEXITCODE -eq 0) { + Write-Host "✅ CPU image pushed successfully!`n" -ForegroundColor Green +} else { + Write-Host "❌ Failed to push CPU image`n" -ForegroundColor Red +} + +# Build and push GPU image +Write-Host "🔨 Building GPU image..." -ForegroundColor Yellow +docker build -f Dockerfile.gpu -t "${repo}:gpu" -t "${repo}:latest-gpu" . + +if ($LASTEXITCODE -eq 0) { + Write-Host "✅ GPU image built successfully!`n" -ForegroundColor Green + + Write-Host "📤 Pushing GPU image..." -ForegroundColor Yellow + docker push "${repo}:gpu" + docker push "${repo}:latest-gpu" + + if ($LASTEXITCODE -eq 0) { + Write-Host "✅ GPU image pushed successfully!`n" -ForegroundColor Green + } else { + Write-Host "❌ Failed to push GPU image`n" -ForegroundColor Red + } +} else { + Write-Host "❌ Failed to build GPU image`n" -ForegroundColor Red +} + +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host "✨ Push Complete!" -ForegroundColor Green +Write-Host "========================================`n" -ForegroundColor Cyan + +Write-Host "📦 Your images are now available at:" -ForegroundColor Yellow +Write-Host " • docker pull ${repo}:cpu" -ForegroundColor Cyan +Write-Host " • docker pull ${repo}:gpu" -ForegroundColor Cyan +Write-Host "`n🌐 View on Docker Hub:" -ForegroundColor Yellow +Write-Host " https://hub.docker.com/r/${repo}`n" -ForegroundColor Cyan From 439e096a6437933d334c82df376fde11978e7bb8 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:09:10 +0800 Subject: [PATCH 03/55] Add data setup instructions and Docker usage guidelines --- DATA_SETUP.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 DATA_SETUP.md diff --git a/DATA_SETUP.md b/DATA_SETUP.md new file mode 100644 index 0000000..b25d2f0 --- /dev/null +++ b/DATA_SETUP.md @@ -0,0 +1,51 @@ +# Data Setup Instructions + +## Required Datasets + +### Training Data +- Download from Baiduyun Link (extract code: ra95) +- Extract to: `./data/train/` + +### Test Data +**Note:** Training/validation data are frame sequences (PNG), but testing typically needs video files. + +**Solutions:** +1. **Reconstruct videos from frames:** Convert frame sequences back to videos for testing +2. **Use alternative video datasets:** + - FaceForensics++: https://github.com/ondyari/FaceForensics (has videos) + - Celeb-DF: https://github.com/yuezunli/celeb-deepfakeforensics + - DFDC: https://dfdc.ai/ +3. **Test with frame sequences:** Modify test scripts to work with PNG sequences +4. **Create test split:** Use some frame sequences as test data + +### Data Structure +``` +data/ +├── train/ +│ └── trainset_1/ +│ ├── 0_real/ +│ │ ├── video_00000/ +│ │ │ ├── 00000.png +│ │ │ └── ... +│ │ └── ... +│ └── 1_fake/ +│ └── ... +├── val/ +│ └── val_set_1/ +│ └── ... (same structure) +└── test/ + └── testset_1/ + └── ... (same structure) +``` + +## Docker Usage +```bash +# Mount your data when running containers +docker run -v /path/to/data:/app/data sacdalance/thesis-aigvdet:gpu +``` + +## Local Development +```bash +# Set environment variable +export AIGVDET_DATA_PATH="/path/to/your/data" +``` \ No newline at end of file From 3100844a3e6849f6f8cdb402e1e031759d5883b3 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:10:58 +0800 Subject: [PATCH 04/55] Add natsort dependency to Dockerfile, pyproject.toml, and requirements.txt --- DATA_SETUP.md | 381 ++++++++++++++++++++++++++++++++++++++++++++- Dockerfile.gpu | 69 -------- Dockerfile.gpu-alt | 1 + pyproject.toml | 1 + requirements.txt | 1 + 5 files changed, 378 insertions(+), 75 deletions(-) delete mode 100644 Dockerfile.gpu diff --git a/DATA_SETUP.md b/DATA_SETUP.md index b25d2f0..ddcb814 100644 --- a/DATA_SETUP.md +++ b/DATA_SETUP.md @@ -38,14 +38,383 @@ data/ └── ... (same structure) ``` +## Getting the Original Data + +### Step 1: Download Training Data +**Original Baiduyun Link:** https://pan.baidu.com/s/17xmDyFjtcmNsoxmUeImMTQ?pwd=ra95 +- Extract code: `ra95` +- Download the preprocessed training frames +- Extract to: `./data/train/` + +### Step 2: Download Test Videos +**Original Google Drive Link:** https://drive.google.com/drive/folders/1D84SRWEJ8BK8KBpTMuGi3BUM80mW_dKb?usp=sharing +- Download test videos +- Extract to: `./data/test/` + +### Step 3: Download Model Weights +**Model Checkpoints:** https://drive.google.com/drive/folders/18JO_YxOEqwJYfbVvy308XjoV-N6fE4yP?usp=share_link +- Download trained model weights +- Move to: `./checkpoints/` + +**RAFT Model (for demo.py):** https://drive.google.com/file/d/1MqDajR89k-xLV0HIrmJ0k-n8ZpG6_suM/view +- Download RAFT model weights +- Move to: `./raft_model/` + +### Backup Plan: If Original Links Are Broken +1. Contact paper authors: lyan924@cuc.edu.cn +2. Check paper's GitHub issues for updated links +3. Look for replication studies with alternative datasets + +### Step 3: Alternative if Original Data Unavailable +- Use FaceForensics++ dataset (widely used benchmark) +- Download from: https://github.com/ondyari/FaceForensics +- Convert to the same folder structure + ## Docker Usage ```bash -# Mount your data when running containers -docker run -v /path/to/data:/app/data sacdalance/thesis-aigvdet:gpu +# Windows +docker run -it -v C:\path\to\your\data:/app/data sacdalance/thesis-aigvdet:gpu bash + +# Linux/Mac +docker run -it -v /path/to/your/data:/app/data sacdalance/thesis-aigvdet:gpu bash + +# With GPU support +docker run --gpus all -it -v /path/to/data:/app/data sacdalance/thesis-aigvdet:gpu bash +``` + +## Local Development with venv +```bash +# Activate venv +source .venv/bin/activate # Linux/Mac +.\.venv\Scripts\Activate.ps1 # Windows + +# Install dependencies +uv pip install -e . + +# Set data path +export AIGVDET_DATA_PATH="/path/to/your/data" # Linux/Mac +$env:AIGVDET_DATA_PATH = "C:\path\to\your\data" # Windows +``` + +## Running the Paper + +### Training +```bash +# Basic training command +python train.py --gpus 0 --exp_name TRAIN_RGB_BRANCH datasets RGB_TRAINSET datasets_test RGB_TESTSET + +# For optical flow branch +python train.py --gpus 0 --exp_name TRAIN_OF_BRANCH datasets OpticalFlow_TRAINSET datasets_test OpticalFlow_TESTSET + +# Using the provided script +./train.sh +``` + +### Testing on Dataset +```bash +python test.py \ + -fop "data/test/hotshot" \ + -mop "checkpoints/optical_aug.pth" \ + -for "data/test/original/hotshot" \ + -mor "checkpoints/original_aug.pth" \ + -e "data/results/T2V/hotshot.csv" \ + -ef "data/results/frame/T2V/hotshot.csv" \ + -t 0.5 +``` + +### Demo on Video +```bash +python demo.py \ + --path "demo_video/fake_sora/video.mp4" \ + --folder_original_path "frame/000000" \ + --folder_optical_flow_path "optical_result/000000" \ + -mop "checkpoints/optical.pth" \ + -mor "checkpoints/original.pth" +``` + +### Docker Examples +```bash +# Training with Docker +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + sacdalance/thesis-aigvdet:gpu \ + python3.11 train.py --gpus 0 --exp_name TRAIN_RGB datasets RGB_TRAINSET datasets_test RGB_TESTSET + +# Testing with Docker +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + sacdalance/thesis-aigvdet:gpu \ + python3.11 test.py \ + -fop "data/test/hotshot" \ + -mop "checkpoints/optical_aug.pth" \ + -for "data/test/original/hotshot" \ + -mor "checkpoints/original_aug.pth" \ + -e "data/results/T2V/hotshot.csv" + +# Demo with Docker +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft_model:/app/raft_model \ + -v $(pwd)/demo_video:/app/demo_video \ + sacdalance/thesis-aigvdet:gpu \ + python3.11 demo.py \ + --path "demo_video/fake_sora/video.mp4" \ + --folder_original_path "frame/000000" \ + --folder_optical_flow_path "optical_result/000000" \ + -mop "checkpoints/optical.pth" \ + -mor "checkpoints/original.pth" +``` + +## 🚀 Cross-Device Deployment Guide + +### Option 1: Using Pre-built Docker Images (Recommended) + +#### Prerequisites +```bash +# Install Docker +# Windows: Download Docker Desktop from docker.com +# Linux: sudo apt install docker.io docker-compose +# Mac: brew install docker docker-compose + +# For GPU support (Linux/WSL2) +sudo apt install nvidia-docker2 +sudo systemctl restart docker +``` + +#### Quick Setup on New Device +```bash +# 1. Clone the repository +git clone https://github.com/sacdalance/AIGVDet.git +cd AIGVDet # You are now INSIDE the repo folder + +# 2. Create data directories INSIDE the repo +mkdir -p data/train data/test data/results checkpoints raft_model demo_video + +# 3. Copy your downloaded data INTO the repo folders (REQUIRED BEFORE RUNNING): +# - Training data → ./data/train/ (inside the AIGVDet repo) +# - Test videos → ./data/test/ (inside the AIGVDet repo) +# - Model weights → ./checkpoints/ (inside the AIGVDet repo) +# - RAFT model → ./raft_model/ (inside the AIGVDet repo) +# - Demo videos → ./demo_video/ (inside the AIGVDet repo) + +# 4. Pull and run (GPU version) - ONLY AFTER DATA IS COPIED +docker pull sacdalance/thesis-aigvdet:gpu +docker run --gpus all -it --rm \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft_model:/app/raft_model \ + -v $(pwd)/demo_video:/app/demo_video \ + sacdalance/thesis-aigvdet:gpu bash + +# 5. Inside container, run your experiments +python3.11 train.py --gpus 0 --exp_name my_experiment +``` + +#### Windows PowerShell Version +```powershell +# Clone and setup on new device +git clone https://github.com/sacdalance/AIGVDet.git +cd AIGVDet +mkdir data\train, data\test, data\results, checkpoints, raft_model, demo_video + +# IMPORTANT: Copy your data to these folders FIRST, then: +docker pull sacdalance/thesis-aigvdet:gpu +docker run --gpus all -it --rm ` + -v ${PWD}/data:/app/data ` + -v ${PWD}/checkpoints:/app/checkpoints ` + -v ${PWD}/raft_model:/app/raft_model ` + -v ${PWD}/demo_video:/app/demo_video ` + sacdalance/thesis-aigvdet:gpu bash +``` + +### Option 2: Local Python Environment + +#### Prerequisites +```bash +# Install Python 3.11 +# Windows: Download from python.org +# Linux: sudo apt install python3.11 python3.11-venv +# Mac: brew install python@3.11 + +# Install uv (fast Python package manager) +curl -LsSf https://astral.sh/uv/install.sh | sh # Linux/Mac +# Windows: iwr https://astral.sh/uv/install.ps1 | iex +``` + +#### Setup Steps +```bash +# 1. Clone repository on new device +git clone https://github.com/sacdalance/AIGVDet.git +cd AIGVDet + +# 2. Create virtual environment and install dependencies +uv venv --python 3.11 +source .venv/bin/activate # Linux/Mac +# .\.venv\Scripts\Activate.ps1 # Windows + +# 3. Install project +uv pip install -e . + +# 4. Copy your data to appropriate folders: +# - data/train/ (training frames) +# - data/test/ (test videos) +# - checkpoints/ (model weights) +# - raft_model/ (RAFT weights) +# - demo_video/ (demo videos) + +# 5. Run experiments +python train.py --gpus 0 --exp_name my_experiment +python test.py -fop "data/test/hotshot" -mop "checkpoints/optical_aug.pth" ... +python demo.py --path "demo_video/video.mp4" ... ``` -## Local Development +### 📁 Expected Folder Structure After Setup +``` +AIGVDet/ # ← This is your cloned GitHub repo +├── pyproject.toml # ← Project files from GitHub +├── train.py # ← Python scripts from GitHub +├── test.py # ← Python scripts from GitHub +├── demo.py # ← Python scripts from GitHub +├── core/ # ← Code folders from GitHub +├── networks/ # ← Code folders from GitHub +├── data/ # ← YOU CREATE & FILL THIS +│ ├── train/ # ← Copy training data here +│ │ └── trainset_1/ +│ │ ├── 0_real/ +│ │ └── 1_fake/ +│ ├── test/ # ← Copy test videos here +│ └── results/ # ← Output results go here +├── checkpoints/ # ← YOU CREATE & FILL THIS +│ ├── optical_aug.pth # ← Copy model weights here +│ └── original_aug.pth +├── raft_model/ # ← YOU CREATE & FILL THIS +│ └── raft-things.pth # ← Copy RAFT model here +└── demo_video/ # ← YOU CREATE & FILL THIS + ├── fake_sora/ # ← Copy demo videos here + └── real/ +``` + +**Key Point:** Everything goes INSIDE the AIGVDet folder you cloned from GitHub! + +**Note:** The data folders (`data/`, `checkpoints/`, `raft_model/`, `demo_video/`) are automatically ignored by git (see `.gitignore`), so your large data files won't be committed to GitHub. + +## 🔄 Updating Docker Images + +### If you need to rebuild the Docker image (e.g., after dependency changes): + +#### Build locally: ```bash -# Set environment variable -export AIGVDET_DATA_PATH="/path/to/your/data" -``` \ No newline at end of file +# Build GPU version +docker build -f Dockerfile.gpu -t sacdalance/thesis-aigvdet:gpu . + +# Build CPU version +docker build -f Dockerfile.cpu -t sacdalance/thesis-aigvdet:cpu . + +# Test the build +docker run --rm sacdalance/thesis-aigvdet:gpu python3.11 --version +``` + +#### Push to Docker Hub (for maintainers): +```bash +# Login to Docker Hub +docker login + +# Push updated images +docker push sacdalance/thesis-aigvdet:gpu +docker push sacdalance/thesis-aigvdet:cpu + +# Or use the provided scripts +./push-docker.sh # Linux/Mac +./push-docker.ps1 # Windows +``` + +#### For users - pull latest updates: +```bash +# Pull latest version +docker pull sacdalance/thesis-aigvdet:gpu + +# Force rebuild without cache if needed +docker build --no-cache -f Dockerfile.gpu -t sacdalance/thesis-aigvdet:gpu . +``` + +### 📦 Data Transfer to New Device + +Since you've already downloaded all the data, you need to transfer it to your new device: + +#### Option 1: Cloud Storage (Recommended) +```bash +# Upload from current device to cloud (Google Drive, OneDrive, etc.) +# Then download on new device to the appropriate folders + +# Or use cloud sync folders: +# 1. Put data in cloud sync folder on current device +# 2. Access from new device once synced +``` + +#### Option 2: External Drive/USB +```bash +# Copy from current device to external drive: +# - data/ folder (training + test data) +# - checkpoints/ folder (model weights) +# - raft_model/ folder (RAFT weights) +# - demo_video/ folder (demo videos) + +# Then copy to new device after cloning repo +``` + +#### Option 3: Network Transfer +```bash +# If both devices are on same network: +# Use scp, rsync, or network sharing to transfer data folders +``` + +### 🔧 Troubleshooting + +#### GPU Issues +```bash +# Check GPU availability +nvidia-smi +docker run --gpus all nvidia/cuda:11.7-base nvidia-smi + +# If GPU not detected in Docker: +# 1. Install nvidia-docker2 +# 2. Restart Docker service +# 3. Use --gpus all flag +``` + +#### Permission Issues (Linux) +```bash +# Add user to docker group +sudo usermod -aG docker $USER +# Logout and login again +``` + +#### Memory Issues +```bash +# For large datasets, increase Docker memory limit +# Docker Desktop → Settings → Resources → Memory → 8GB+ +``` + +### 📋 Quick Deployment Checklist (IN ORDER!) +- [ ] Docker installed (with GPU support if needed) +- [ ] Repository cloned: `git clone https://github.com/sacdalance/AIGVDet.git` +- [ ] Data folders created: `mkdir data/train data/test checkpoints raft_model demo_video` +- [ ] **DATA TRANSFER COMPLETE** ⚠️ (Required before Docker run): + - [ ] Training data copied to `./data/train/` + - [ ] Test videos copied to `./data/test/` + - [ ] Model weights copied to `./checkpoints/` + - [ ] RAFT model copied to `./raft_model/` + - [ ] Demo videos copied to `./demo_video/` +- [ ] Docker image pulled: `docker pull sacdalance/thesis-aigvdet:gpu` +- [ ] Test run: `docker run --gpus all -it sacdalance/thesis-aigvdet:gpu python3.11 --version` + +**⚠️ CRITICAL:** The `-v $(pwd)/data:/app/data` flag mounts your local folders into the container. If the data isn't there, the container will see empty folders! + +## Notes +- **Research use only**: The datasets are only allowed for research purposes +- **Citation required**: If you use this work, please cite the PRCV 2024 paper +- **Contact**: For questions, contact lyan924@cuc.edu.cn +- **Docker Hub**: Pre-built images available at `sacdalance/thesis-aigvdet:gpu` and `sacdalance/thesis-aigvdet:cpu` \ No newline at end of file diff --git a/Dockerfile.gpu b/Dockerfile.gpu deleted file mode 100644 index d3568ab..0000000 --- a/Dockerfile.gpu +++ /dev/null @@ -1,69 +0,0 @@ -# Dockerfile for AIGVDet with GPU support (CUDA 11.7) -FROM nvidia/cuda:11.7.1-cudnn8-runtime-ubuntu22.04 - -# Set environment variables -ENV DEBIAN_FRONTEND=noninteractive \ - PYTHONUNBUFFERED=1 \ - PYTHONDONTWRITEBYTECODE=1 \ - UV_SYSTEM_PYTHON=1 - -# Install system dependencies -RUN apt-get update && apt-get install -y \ - python3.11 \ - python3.11-dev \ - python3-pip \ - git \ - wget \ - curl \ - libglib2.0-0 \ - libsm6 \ - libxext6 \ - libxrender-dev \ - libgomp1 \ - libgl1-mesa-glx \ - && rm -rf /var/lib/apt/lists/* - -# Install uv -RUN pip3 install --no-cache-dir uv - -# Set working directory -WORKDIR /app - -# Copy project files -COPY pyproject.toml ./ -COPY README.md ./ -COPY core/ ./core/ -COPY networks/ ./networks/ -COPY train.py test.py demo.py ./ -COPY train.sh test.sh demo.sh ./ - -# Create directories for data and checkpoints -RUN mkdir -p /app/data /app/checkpoints /app/raft_model - -# Install other dependencies first -RUN uv pip install --system \ - einops \ - imageio \ - ipympl \ - matplotlib \ - "numpy<2.0" \ - opencv-python \ - pandas \ - scikit-learn \ - tensorboard \ - tensorboardX \ - tqdm \ - "blobfile>=1.0.5" - -# Install PyTorch GPU version using pip directly -RUN pip3 install torch==2.0.0+cu117 torchvision==0.15.1+cu117 \ - --index-url https://download.pytorch.org/whl/cu117 - -# Set Python path -ENV PYTHONPATH=/app - -# Expose tensorboard port -EXPOSE 6006 - -# Default command -CMD ["python3.11", "train.py", "--help"] diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index f521a83..94ba401 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -43,6 +43,7 @@ RUN uv pip install --system \ imageio \ ipympl \ matplotlib \ + natsort \ "numpy<2.0" \ opencv-python \ pandas \ diff --git a/pyproject.toml b/pyproject.toml index 4e9d878..738690b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "imageio", "ipympl", "matplotlib", + "natsort", "numpy<2.0", "opencv-python", "pandas", diff --git a/requirements.txt b/requirements.txt index ce83367..57dc112 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ einops imageio ipympl matplotlib +natsort numpy opencv-python pandas From 3b2747abc91909ed33e3a0e52a06e9f5df50036a Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Thu, 20 Nov 2025 17:59:59 +0800 Subject: [PATCH 05/55] Refactor video frame extraction and optical flow generation for improved clarity and functionality --- demo.py | 69 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/demo.py b/demo.py index 9700ef8..4d4a6d5 100644 --- a/demo.py +++ b/demo.py @@ -44,9 +44,9 @@ def viz(img, flo, folder_optical_flow_path, imfile1): # print(folder_optical_flow_path) - parts=imfile1.rsplit('\\',1) - content=parts[1] - folder_optical_flow_path=folder_optical_flow_path+'/'+content.strip() + # Use os.path.basename to get filename (works on both Windows and Linux) + content = os.path.basename(imfile1) + folder_optical_flow_path = os.path.join(folder_optical_flow_path, content) print(folder_optical_flow_path) cv2.imwrite(folder_optical_flow_path, flo) @@ -55,19 +55,24 @@ def video_to_frames(video_path, output_folder): if not os.path.exists(output_folder): os.makedirs(output_folder) + print(f"Extracting frames from video: {video_path}") cap = cv2.VideoCapture(video_path) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) frame_count = 0 - while cap.isOpened(): - ret, frame = cap.read() - if not ret: - break - - frame_filename = os.path.join(output_folder, f"frame_{frame_count:05d}.png") - cv2.imwrite(frame_filename, frame) - frame_count += 1 + with tqdm(total=total_frames, desc="Extracting frames", unit="frame") as pbar: + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + frame_filename = os.path.join(output_folder, f"frame_{frame_count:05d}.png") + cv2.imwrite(frame_filename, frame) + frame_count += 1 + pbar.update(1) cap.release() + print(f"✓ Extracted {frame_count} frames") images = glob.glob(os.path.join(output_folder, '*.png')) + \ glob.glob(os.path.join(output_folder, '*.jpg')) @@ -78,12 +83,14 @@ def video_to_frames(video_path, output_folder): # generate optical flow images def OF_gen(args): + print("Loading RAFT model...") model = torch.nn.DataParallel(RAFT(args)) model.load_state_dict(torch.load(args.model, map_location=torch.device(DEVICE))) model = model.module model.to(DEVICE) model.eval() + print("✓ RAFT model loaded") if not os.path.exists(args.folder_optical_flow_path): os.makedirs(args.folder_optical_flow_path) @@ -94,7 +101,8 @@ def OF_gen(args): images = video_to_frames(args.path, args.folder_original_path) images = natsorted(images) - for imfile1, imfile2 in zip(images[:-1], images[1:]): + print("Generating optical flow...") + for imfile1, imfile2 in tqdm(zip(images[:-1], images[1:]), total=len(images)-1, desc="Processing optical flow", unit="frame"): image1 = load_image(imfile1) image2 = load_image(imfile2) @@ -104,6 +112,8 @@ def OF_gen(args): flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) viz(image1, flow_up,args.folder_optical_flow_path,imfile1) + + print("✓ Optical flow generation complete") if __name__ == '__main__': @@ -138,8 +148,16 @@ def OF_gen(args): parser.add_argument("--aug_norm", type=str2bool, default=True) args = parser.parse_args() + print("=" * 60) + print("AIGVDet - AI-Generated Video Detection") + print("=" * 60) + print(f"Input video: {args.path}") + print(f"Using device: {'GPU' if not args.use_cpu else 'CPU'}") + print("=" * 60) + OF_gen(args) + print("\nLoading detection models...") model_op = get_network(args.arch) state_dict = torch.load(args.model_optical_flow_path, map_location="cpu") if "model" in state_dict: @@ -148,6 +166,7 @@ def OF_gen(args): model_op.eval() if not args.use_cpu: model_op.cuda() + print("✓ Optical flow model loaded") model_or = get_network(args.arch) state_dict = torch.load(args.model_original_path, map_location="cpu") @@ -157,6 +176,7 @@ def OF_gen(args): model_or.eval() if not args.use_cpu: model_or.cuda() + print("✓ RGB frame model loaded") trans = transforms.Compose( @@ -166,7 +186,9 @@ def OF_gen(args): ) ) - print("*" * 30) + print("\n" + "=" * 60) + print("Running detection...") + print("=" * 60) # optical_subfolder_path = args.folder_optical_flow_path # original_subfolder_path = args.folder_original_path @@ -176,9 +198,10 @@ def OF_gen(args): optical_subsubfolder_path = args.folder_optical_flow_path #RGB frame detection + print("\nAnalyzing RGB frames...") original_file_list = sorted(glob.glob(os.path.join(original_subsubfolder_path, "*.jpg")) + glob.glob(os.path.join(original_subsubfolder_path, "*.png"))+glob.glob(os.path.join(original_subsubfolder_path, "*.JPEG"))) original_prob_sum=0 - for img_path in tqdm(original_file_list, dynamic_ncols=True, disable=len(original_file_list) <= 1): + for img_path in tqdm(original_file_list, desc="RGB detection", unit="frame"): img = Image.open(img_path).convert("RGB") img = trans(img) @@ -196,12 +219,13 @@ def OF_gen(args): original_predict=original_prob_sum/len(original_file_list) - print("original prob",original_predict) + print(f"✓ RGB detection probability: {original_predict:.4f}") #optical flow detection + print("\nAnalyzing optical flow...") optical_file_list = sorted(glob.glob(os.path.join(optical_subsubfolder_path, "*.jpg")) + glob.glob(os.path.join(optical_subsubfolder_path, "*.png"))+glob.glob(os.path.join(optical_subsubfolder_path, "*.JPEG"))) optical_prob_sum=0 - for img_path in tqdm(optical_file_list, dynamic_ncols=True, disable=len(original_file_list) <= 1): + for img_path in tqdm(optical_file_list, desc="Optical flow detection", unit="frame"): img = Image.open(img_path).convert("RGB") img = trans(img) @@ -217,11 +241,16 @@ def OF_gen(args): optical_predict=optical_prob_sum/len(optical_file_list) - print("optical prob",optical_predict) + print(f"✓ Optical flow detection probability: {optical_predict:.4f}") predict=original_predict*0.5+optical_predict*0.5 - print(f"predict:{predict}") + print("\n" + "=" * 60) + print("RESULTS") + print("=" * 60) + print(f"Combined probability: {predict:.4f}") + print(f"Threshold: {args.threshold}") if predict Date: Thu, 20 Nov 2025 19:21:21 +0800 Subject: [PATCH 08/55] Update dataset names in configuration and enhance training script output for clarity --- core/utils1/config.py | 4 ++-- train.py | 28 +++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index 1abb3c6..ef05fa7 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -10,8 +10,8 @@ class DefaultConfigs(ABC): gpus = [0] seed = 3407 arch = "resnet50" - datasets = ["zhaolian_train"] - datasets_test = ["adm_res_abs_ddim20s"] + datasets = ["train"] + datasets_test = ["val"] mode = "binary" class_bal = False batch_size = 64 diff --git a/train.py b/train.py index 4792f2e..87004e6 100644 --- a/train.py +++ b/train.py @@ -17,27 +17,47 @@ if __name__ == "__main__": + print("=" * 60) + print("AIGVDet Training") + print("=" * 60) + + print("\nInitializing...") val_cfg = get_val_cfg(cfg, split="val", copy=True) cfg.dataset_root = os.path.join(cfg.dataset_root, "train") + + print("Loading training data...") data_loader = create_dataloader(cfg) dataset_size = len(data_loader) + print(f"✓ Loaded {dataset_size * cfg.batch_size} training images") log = Logger() log.open(cfg.logs_path, mode="a") log.write("Num of training images = %d\n" % (dataset_size * cfg.batch_size)) log.write("Config:\n" + str(cfg.to_dict()) + "\n") + print("Setting up TensorBoard...") train_writer = SummaryWriter(os.path.join(cfg.exp_dir, "train")) val_writer = SummaryWriter(os.path.join(cfg.exp_dir, "val")) + print(f"✓ Logs will be saved to: {cfg.exp_dir}") + print("\nInitializing model...") trainer = Trainer(cfg) early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.001, verbose=True) + print(f"✓ Model ready (Architecture: {cfg.arch})") + + print("\n" + "=" * 60) + print(f"Starting training for {cfg.nepoch} epochs") + print("=" * 60 + "\n") + for epoch in range(cfg.nepoch): + print(f"\n{'='*60}") + print(f"Epoch {epoch+1}/{cfg.nepoch}") + print(f"{'='*60}") epoch_start_time = time.time() iter_data_time = time.time() epoch_iter = 0 - for data in tqdm(data_loader, dynamic_ncols=True): + for data in tqdm(data_loader, desc=f"Training Epoch {epoch+1}", unit="batch"): trainer.total_steps += 1 epoch_iter += cfg.batch_size @@ -49,6 +69,7 @@ train_writer.add_scalar("loss", trainer.loss, trainer.total_steps) if trainer.total_steps % cfg.save_latest_freq == 0: + print(f"💾 Saving checkpoint (epoch {epoch+1}, step {trainer.total_steps})") log.write( "saving the latest model %s (epoch %d, model.total_steps %d)\n" % (cfg.exp_name, epoch, trainer.total_steps) @@ -56,11 +77,13 @@ trainer.save_networks("latest") if epoch % cfg.save_epoch_freq == 0: + print(f"💾 Saving epoch checkpoint {epoch+1}") log.write("saving the model at the end of epoch %d, iters %d\n" % (epoch, trainer.total_steps)) trainer.save_networks("latest") trainer.save_networks(epoch) # Validation + print("\n🔍 Running validation...") trainer.eval() val_results = validate(trainer.model, val_cfg) val_writer.add_scalar("AP", val_results["AP"], trainer.total_steps) @@ -70,15 +93,18 @@ val_writer.add_scalar("TPR", val_results["TPR"], trainer.total_steps) val_writer.add_scalar("TNR", val_results["TNR"], trainer.total_steps) + print(f"✓ Validation Results - AP: {val_results['AP']:.4f} | ACC: {val_results['ACC']:.4f} | AUC: {val_results['AUC']:.4f}") log.write(f"(Val @ epoch {epoch}) AP: {val_results['AP']}; ACC: {val_results['ACC']}\n") if cfg.earlystop: early_stopping(val_results["ACC"], trainer) if early_stopping.early_stop: if trainer.adjust_learning_rate(): + print("📉 Learning rate dropped by 10, continuing training...") log.write("Learning rate dropped by 10, continue training...\n") early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.002, verbose=True) else: + print("\n⏹️ Early stopping triggered") log.write("Early stopping.\n") break if cfg.warmup: From 7426607c6b70eb9384f7b8024ea40d2b11ed5f5a Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Fri, 21 Nov 2025 00:00:48 +0800 Subject: [PATCH 09/55] Add natsort dependency to Dockerfile --- Dockerfile.cpu | 1 + demo.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Dockerfile.cpu b/Dockerfile.cpu index d803641..9bd6b9c 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -42,6 +42,7 @@ RUN uv pip install --system \ imageio \ ipympl \ matplotlib \ + natsort \ "numpy<2.0" \ opencv-python \ pandas \ diff --git a/demo.py b/demo.py index 7aae7ec..4d4a6d5 100644 --- a/demo.py +++ b/demo.py @@ -42,9 +42,12 @@ def viz(img, flo, folder_optical_flow_path, imfile1): flo = flow_viz.flow_to_image(flo) img_flo = np.concatenate([img, flo], axis=0) + + # print(folder_optical_flow_path) # Use os.path.basename to get filename (works on both Windows and Linux) content = os.path.basename(imfile1) folder_optical_flow_path = os.path.join(folder_optical_flow_path, content) + print(folder_optical_flow_path) cv2.imwrite(folder_optical_flow_path, flo) @@ -98,7 +101,8 @@ def OF_gen(args): images = video_to_frames(args.path, args.folder_original_path) images = natsorted(images) - for imfile1, imfile2 in tqdm(zip(images[:-1], images[1:]), total=len(images)-1, desc="Generating optical flow", unit="frame"): + print("Generating optical flow...") + for imfile1, imfile2 in tqdm(zip(images[:-1], images[1:]), total=len(images)-1, desc="Processing optical flow", unit="frame"): image1 = load_image(imfile1) image2 = load_image(imfile2) From d81f7fb93a7eadad411d121d49e7c36bbb7cc1a6 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:34:41 +0800 Subject: [PATCH 10/55] Refactor import statements to use core.utils1 module path --- core/utils1/datasets.py | 2 +- core/utils1/earlystop.py | 2 +- core/utils1/eval.py | 6 +++--- core/utils1/trainer.py | 6 +++--- demo.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index 4a150de..72d4654 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -13,7 +13,7 @@ from scipy.ndimage import gaussian_filter from torch.utils.data.sampler import WeightedRandomSampler -from utils1.config import CONFIGCLASS +from core.utils1.config import CONFIGCLASS ImageFile.LOAD_TRUNCATED_IMAGES = True diff --git a/core/utils1/earlystop.py b/core/utils1/earlystop.py index 741d07e..257e391 100644 --- a/core/utils1/earlystop.py +++ b/core/utils1/earlystop.py @@ -1,6 +1,6 @@ import numpy as np -from utils1.trainer import Trainer +from core.utils1.trainer import Trainer class EarlyStopping: diff --git a/core/utils1/eval.py b/core/utils1/eval.py index eaf62c2..731ebf1 100644 --- a/core/utils1/eval.py +++ b/core/utils1/eval.py @@ -6,8 +6,8 @@ import torch import torch.nn as nn -from utils1.config import CONFIGCLASS -from utils1.utils import to_cuda +from core.utils1.config import CONFIGCLASS +from core.utils1.utils import to_cuda def get_val_cfg(cfg: CONFIGCLASS, split="val", copy=True): @@ -37,7 +37,7 @@ def get_val_cfg(cfg: CONFIGCLASS, split="val", copy=True): def validate(model: nn.Module, cfg: CONFIGCLASS): from sklearn.metrics import accuracy_score, average_precision_score, roc_auc_score - from utils1.datasets import create_dataloader + from core.utils1.datasets import create_dataloader data_loader = create_dataloader(cfg) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") diff --git a/core/utils1/trainer.py b/core/utils1/trainer.py index 8599603..3a5abc6 100644 --- a/core/utils1/trainer.py +++ b/core/utils1/trainer.py @@ -4,9 +4,9 @@ import torch.nn as nn from torch.nn import init -from utils1.config import CONFIGCLASS -from utils1.utils import get_network -from utils1.warmup import GradualWarmupScheduler +from core.utils1.config import CONFIGCLASS +from core.utils1.utils import get_network +from core.utils1.warmup import GradualWarmupScheduler class BaseModel(nn.Module): diff --git a/demo.py b/demo.py index 4d4a6d5..fc526e5 100644 --- a/demo.py +++ b/demo.py @@ -18,7 +18,7 @@ from utils import flow_viz from utils.utils import InputPadder from natsort import natsorted -from utils1.utils import get_network, str2bool, to_cuda +from core.utils1.utils import get_network, str2bool, to_cuda from sklearn.metrics import accuracy_score, average_precision_score, roc_auc_score,roc_auc_score From 88070e4545654e03234a1eccf6e9fd4d38ef7f9c Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 22 Nov 2025 06:54:01 +0800 Subject: [PATCH 11/55] Remove unused Python files and compiled bytecode from utils1 module --- .../utils1/__pycache__/config.cpython-310.pyc | Bin 4296 -> 0 bytes .../utils1/__pycache__/config.cpython-38.pyc | Bin 4293 -> 0 bytes .../utils1/__pycache__/config.cpython-39.pyc | Bin 4258 -> 0 bytes .../__pycache__/datasets.cpython-310.pyc | Bin 5863 -> 0 bytes .../__pycache__/datasets.cpython-38.pyc | Bin 5890 -> 0 bytes .../__pycache__/datasets.cpython-39.pyc | Bin 6022 -> 0 bytes .../__pycache__/earlystop.cpython-310.pyc | Bin 1938 -> 0 bytes .../__pycache__/earlystop.cpython-38.pyc | Bin 1927 -> 0 bytes .../utils1/__pycache__/eval.cpython-310.pyc | Bin 1851 -> 0 bytes .../utils1/__pycache__/eval.cpython-38.pyc | Bin 1847 -> 0 bytes .../utils1/__pycache__/eval.cpython-39.pyc | Bin 1866 -> 0 bytes .../__pycache__/trainer.cpython-310.pyc | Bin 5892 -> 0 bytes .../utils1/__pycache__/trainer.cpython-38.pyc | Bin 5862 -> 0 bytes .../utils1/__pycache__/utils.cpython-310.pyc | Bin 3658 -> 0 bytes .../utils1/__pycache__/utils.cpython-38.pyc | Bin 3643 -> 0 bytes .../utils1/__pycache__/utils.cpython-39.pyc | Bin 3623 -> 0 bytes .../utils1/__pycache__/warmup.cpython-310.pyc | Bin 3040 -> 0 bytes .../utils1/__pycache__/warmup.cpython-38.pyc | Bin 3105 -> 0 bytes core/utils1/utils1/config.py | 157 --------------- core/utils1/utils1/datasets.py | 178 ------------------ core/utils1/utils1/earlystop.py | 46 ----- core/utils1/utils1/eval.py | 66 ------- core/utils1/utils1/trainer.py | 169 ----------------- core/utils1/utils1/utils.py | 109 ----------- core/utils1/utils1/warmup.py | 70 ------- 25 files changed, 795 deletions(-) delete mode 100644 core/utils1/utils1/__pycache__/config.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/config.cpython-38.pyc delete mode 100644 core/utils1/utils1/__pycache__/config.cpython-39.pyc delete mode 100644 core/utils1/utils1/__pycache__/datasets.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/datasets.cpython-38.pyc delete mode 100644 core/utils1/utils1/__pycache__/datasets.cpython-39.pyc delete mode 100644 core/utils1/utils1/__pycache__/earlystop.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/earlystop.cpython-38.pyc delete mode 100644 core/utils1/utils1/__pycache__/eval.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/eval.cpython-38.pyc delete mode 100644 core/utils1/utils1/__pycache__/eval.cpython-39.pyc delete mode 100644 core/utils1/utils1/__pycache__/trainer.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/trainer.cpython-38.pyc delete mode 100644 core/utils1/utils1/__pycache__/utils.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/utils.cpython-38.pyc delete mode 100644 core/utils1/utils1/__pycache__/utils.cpython-39.pyc delete mode 100644 core/utils1/utils1/__pycache__/warmup.cpython-310.pyc delete mode 100644 core/utils1/utils1/__pycache__/warmup.cpython-38.pyc delete mode 100644 core/utils1/utils1/config.py delete mode 100644 core/utils1/utils1/datasets.py delete mode 100644 core/utils1/utils1/earlystop.py delete mode 100644 core/utils1/utils1/eval.py delete mode 100644 core/utils1/utils1/trainer.py delete mode 100644 core/utils1/utils1/utils.py delete mode 100644 core/utils1/utils1/warmup.py diff --git a/core/utils1/utils1/__pycache__/config.cpython-310.pyc b/core/utils1/utils1/__pycache__/config.cpython-310.pyc deleted file mode 100644 index 4df9fd83223c481b3d9dc2734ecd33181e0b0a94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4296 zcmZu!&2QYs73Yv#E_bzB$+A8yS+dr$6MJJzv7E$h9mkF=Ij+-02_oA?r%qXFXLe1A zORk4pIZ|bysN7?K1n8k{5I{QSQuJD&#~xdthn|QY(iS}k?O)(a`g_BbZ8_y)hHu_{ zaNgJNy^Z_(%LX2A`H#)N9Wji5(ct-qX5U6jE}4dL#}I~Kp~3Sv<4liDZsKi*R@>$_ zqd6-qv>om+ExHc|F)+WUGp0&r+=hDR7abX;u-81)0E#*Ww*`DH4 z_*zw-5XY|=ktK^e2A{$i)9T-{MDHEOr{(l+ZO>RTK3=+K@{@czI9XmNuDL1tjL)iwgcH7>|(~1b@@uu+A!4Ld-MHr z=GlH>j2l>O_SWYooO$kqk+pMj$~z}rNHVp1Oq>wYj}1Asc5Vs#_825SFKSl|(gxN| z^fWNFHVe+&o(bNovJA;DV9&fP@0g!4HBR*IMR5}Qr@VQ=R^TlwM*Y-2dDL>9W!A#= zMZE0Wn;qF8=JQVxgSXL=cbNf0k#oVZSc`E>nA{c?F9;hWM-QMhHS#?P^{ce~p#!)k9B?|gAf)Dx) zgS$#5kxbvXz^hyL{WuK#$V-(UMEs~P+8$;-e>L%h2-+7fB-~jIB42I3vrl%~H*n7W z*Pt>d^i!Fn`@$kt5=MUFf;N$uF8n+r77x#Yvy2ru^7DoF8blmoZ z-wr;*vB6*A4f}M@{crHJ{_WK>10NOb{{-MQ(Ht!9CX{pnz3Sh#mFm59yWN9-pk)US z(KedC*(!)_wrzMu%GS(X3%9h<3VSAa&QqQvnC29p;{cSf#s#N@9geVs{V8CoH!dEG z1*EPQmNUDt-buIIFm5Gt=_l!JNDfhgB=zF;dRYzOBAFQ{nceZz`z0Ft{6KMeiVP}XfN2Cyh-aJL!AP^`;M zUe=U0>kGnV#WaRe8tGjUuu3wp3M;co<}&l=HrpC~!Ap9}m+RLv*VFEZ>yS6u2LXyR0DS$GxSnW(l*@5dtNk~qB7QvPOdf!}Qr=;+%JO*i5$ zQOz*uQ55)@^UzoAZiiR$x2G?hl>j>7W|GF8Y~U%G&+$UsNrN^oMe?E7Bf}W_Xe#+2 z8%+ES>E$Sva|Sd=AFCO>il9$}s4IH_*_Y2X;%=1kGE|%PF5yJE_6n=;r+IFMikE^Y zNWBzUuSd2BV#^&RHP}j_Cg30HIJ&F|z@-RMlX?WtB!iPnGy`%2UZN1BaGM8w_sFc$ zv$&_@bBa!*7*;2!o2IUYE-UF!%>xu>>PVGt=F#lHabUyDN>ZiH(1vHRswo|?6oxKZ zl4`;+ZDunEKYAbjIhJjLk9KGl+{5d>Jvw1kSd|Svcb;jMnQaZSN=eZTG8ehU3xQUL zaDo6HX(NZy>mYKB5Ns6H>AIN=zKhCq+xP-C>6gHUU$B&=08P}Zs8fDvjRR3c;ebZ% zL@41b?-N4nl(}`{gAJ)vAY_fcUAylmwOEK+D5F+-|8i|>^37V1*4kant;(98uZo+s zjt?AE$hSb9%))XcDgZ-fZ3Ue=Q&;H&&$La7&Z>|w(IQ@?r5<<*iC$wfOi`}L*hc2S zf4VCAmJ%$?>wIzv1Sug!DX5Cu!2WGx*W81+C>#fb`E*Q${cR??)3K?`{sL@s1%rdHEJU40f}+LQ-& z3j)~xh{Q%pBnmb!rY)W_Fo6OMFVbJuan5d-)0b@e}s(6Luac@qK(Iv&ryT zA?k&Cwb|k8}BdNT3)*G5i$U+rg>Ca)aNO{(Un-DGl0@q zt_v;)K?T6hoZKAR2-$$=`B4<3a)I^}&r?1wm=%Ilwv&vlYp=_SfU3N-xl0A;4W(kG zok5+(YTh5KpoPjl)F~<}!mnwyxmC7+1tma9qu) zh`}cXJ}4booG^G*8rwrV1|OEgs|Q3?7;Pp;#E=;N4Bykd17bv$#lg?4{&-Z3?HiA` z4QZ?%6o+z&?NRJDCfVxv_95LLmc`WxIkq|}4)0hy>?u<F!s;ym^-G{aUz`R zO!H}cuPSH6p58V2QGQGwm(%j-mid$= z4f&clB9DG;4y-{+{an65Tv?IFw%8VQjILtNm34WlWvw}o z_+wvUP#$}+hcG4#$eVh_a9`r&-X2fcgDW`UX*unkmM(Of+CD6%#mo~!POqL`!pa>6 z-Oq^GO9oj6a+7^qOs}2=XKu#??@d{T4bEZBd0B=|rn*4%_PnTJ{j_&puoa_zY?r)h zxy~|c;p!q@_O11B*iz*}!h#@{AhCv+!H7X8(8WSVv$HhT@2y%zfCeS9) zj$rMS80FKTXT%tv72~`H-cfOg9}|c9aqRyZ#;=PBenL$0liT1h7QY~l@o%Ah8|_`Ri)io3 zOX9eA?Fp`izc1fk{Xkw8uOpmJfM*j=oD^?7G58fxhsjT6)g{^Tdttg5N3EcpDA?2p zZVnm-ca=;cnZ9$5S2ynaaTxfKmnuJq_(5NEJj{B2Bk_a?I`iie?lgkPSL+|{(%tMD z*k|`^P?;0@sZ7${Q4DTzw-JOvBz@KX?QiJ$!{s*kc)q^Ot>(k|-S2|#v#^7O(QW}9 zw|(Jvf~VLv_zOH>m(IESA3W_p|B0S|Uv8gWoch;4E=*qLP86$-AGUuo`uppD-}u$# z4RdZTvxT4f+>)PmGowkX_a36DEutk3x=mx%fNdCgV#&h-FY-H*{4tSX>nlSYgwV6v zcP`!yI(}Q;UAS@m&XP>;_R=6s?l!d{=ep~eySMZ@?F?)*40GeqetXaD$zc1!LqF`v zrWPb&JVe`Q`fLpln{3nYjFhdK+ZN7gqZM{c@SNv7M=;GPzQh3_LB<8AgcXjkg#8&H zsy{C7jRomlSkCO`S~uNr!?>NyrJts^a5yLvB&ip#)yoP=z{t!v$?UG5-p}mSIEZp> zRUD^kjHZjEm(SDv@;&%KsiP@xXsu75*aq7sfc}LEo_aS#kD(DQ;VWVt> zjhMr1-}RAso||Y1gTQ#qQe5H|1Dei1HdB~v)7&y2Tbt%KfMSOwmDJj_w(!;*1R5v{ zrZq1a4=S64MbqRUD;I`f#s-E^f&*Yf~&;4Qeg%t}(FPSAoE;i)MdqZD~ATGAx~ zF>2%I;79L$Kg%&OL+#~R%LeEF{Hz&PWy3F|Op}!@n+=x~ef=w76Vizn0-p8(17SKc zMUI_ULFAwy*f{Sf*_-$sQ*-T6X3sTY@0h!f6Y-d!hAkP{ix=Iy#h(X)fJdX zCyKh0*{SMDZmlEg%{a=~ddAX>oxfFgc%kKoiR5M!b1QDOGKOjN=RT}kSvg382uK<= zWo9?xILr!R{79-gQ`c~+S2cFwg!3fU#AH%MX8d1>kr7SI`E@eu(btKsbHS#eL-OLedQbTyMwks!Oz_p!rCJq&lz2PmFX}$NlxX0vM;{5a!s`N5Hu*Wqg1nrEa~#rxm5=We;ivtJ!KRx~Yw^+mVvZ zxE%qtMU9{}ju08OG5+gpkczbEVyK`gZ#?_Cmc}v&s+*rJ}qR2m}-g^uAM76uV zLNpTU;{Mi?%<>yeMMumWSTKm%%ECZ{kD~OgLrxz{-J~J;qP_<{uy}L%!yDHZKU!E> z;nfAz?$O15toP?m{vBTNh4AoMJ(pQXm%Q}JweKz5SYEpJ2@(NG(>%&8>hnzC=yEL4 zi9l&|*HxE;paPI^C%+DT3E7b6`B4<3e1Y{7&r=N?Fe?P9>?9dm(_WVq0bhB2bBC%> zjYHZQ)Nzs?j8)J^fgkEjl@;OFB(3g{u0Npe6p0mO^e|9ylxLVj#DAAMiVSs{I!@gS zJbRu=GWSwm%+S9&Bl!rOX}Iud7eQXJhVN7h!viB_*K|<^lE;rQ*PKDMR58n@11?2C W1rbmMH3IsWIciog@3O;YmHihgn^)cd diff --git a/core/utils1/utils1/__pycache__/config.cpython-39.pyc b/core/utils1/utils1/__pycache__/config.cpython-39.pyc deleted file mode 100644 index 449975bc3f0e5ff8d077191a95b6569bb080e15d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4258 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 diff --git a/core/utils1/utils1/__pycache__/datasets.cpython-39.pyc b/core/utils1/utils1/__pycache__/datasets.cpython-39.pyc deleted file mode 100644 index 47acb95ae8ed84ae14ff5313d42c002b97abd6bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6022 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 diff --git a/core/utils1/utils1/__pycache__/eval.cpython-310.pyc b/core/utils1/utils1/__pycache__/eval.cpython-310.pyc deleted file mode 100644 index ffcbe358dfb7dc8d180ee7c4a4d7b36342b88965..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1851 zcmZ8h&1)Pt6qhvLvpZ|A<0KAE+^_Vor33;cg%X+?hnPT`mLw!}a2e#0yq>joW}?x$ zF=$TCz4YG0Zg0Kz+`mMpLQnn|LTURnyAGie^FE~C+trhP?^Q_>BN%rd{c(6FMCdQ; zygOWQzJ{-P1%e@l1xlA1r?~UU)Ja{uT=P=DTMN>l)8RBqBiJL%5%IK__Bz`Y{b`aW zU?(i#y+gu67QOb;0gHJwS?ktZ)?@wGZo1C!Q#2Z!!@r})=NRM&o8IoXj~_hT+kLe2 z^eMQ3ssBuoHcxIgZaGX0fU^N2JHjiESn%_ZaA`^xg5on5A2P5rhbcS2EBpS7@ zA`@rsYIovkydc`mJdGwk=zuvO!%jvF5LwK;F$UdJeZVJYnEA&<-m`Y=X^-J+2xM># zT}8})4|Uk2!wvu&96Ry_U`iv^taFxlbQYKk>O{RFGOnBj%plF^+ z3iy!D#@V4LYBOlKEU3s1sLGFcW8z6Y&Zb-)Rm?bo1N1=5WY!d86WXnpGb)Tzlxh@6 z-yX$lYEdZJv#0IO7Qky|@K&jJV8c>>(gpp75A@o6ZGq;=@O3-b(^UWT_( zQ~n4@JHLld4d)v=T;NVe3%oif(V-3*J|@86=)z**u?y!}*UMe!?QUnSc8A?=wDLBf zwn802)Fr*4FX?qXWYI5fyQw#U)$xkejv3HApGlgZW{rJpbCsTONyj{^CC`hdsLItw zBC9;3vwZ1xyoVMr%q6FaXO_XDqKr$Zg03ax_3Yd>gYoj6|GYPHjkB}6Ysk)bsrMuU zGxZ)=HM*3pHOGQeS>6I-%OY>&2M{Gcw8FAW4z2hYgz;1*^CROkep2LI-n47|qRHq9 zEd)If+yrHnjU{FBiVd>;#{@w0X;m=e^H++O%mjymDzJ>NDp5cQlCm})m6D#0F3b0A zpsk(%AEP@#&*qvfla-$WD)mn@C1>1fxHV>6i+~!Oj*SOr;=;I)7fpkrgn};{6IfW0 ztJISj+<)pFT4fw72(3*Zc{3BL=?ZcwZrD6RD7ylFoPPpd^Err3+$SN~g4&Cr;%?#? z<^XTtO-$Zg|L01)jkm#%fn5aK>HqEe@-769i1a``8x7%b(5GY{JTnBd7L^jk0hGG6 zjK9)OEtK)f*%VsbZNSQr4G6pExK);LgK(R9RUQ^&Y2V#=UEk;)U|IYAHx-)+-u?YM jnc1czm+9Sw?f{6dY{zXjL4+u@2|{Xp(0#n_Y;FAq%>~*s diff --git a/core/utils1/utils1/__pycache__/eval.cpython-38.pyc b/core/utils1/utils1/__pycache__/eval.cpython-38.pyc deleted file mode 100644 index 5fede421ef691570d37b5ab60f1ea81f86a1b502..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1847 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 diff --git a/core/utils1/utils1/__pycache__/trainer.cpython-38.pyc b/core/utils1/utils1/__pycache__/trainer.cpython-38.pyc deleted file mode 100644 index 015428d5b713e55ae0c7ab413a4cd1e823afee47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5862 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 F{{Umvj#&Tz diff --git a/core/utils1/utils1/__pycache__/utils.cpython-310.pyc b/core/utils1/utils1/__pycache__/utils.cpython-310.pyc deleted file mode 100644 index e17d0762fb3a145c6ad8e8c1f7bd9cafd2c34aec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3658 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 diff --git a/core/utils1/utils1/__pycache__/warmup.cpython-310.pyc b/core/utils1/utils1/__pycache__/warmup.cpython-310.pyc deleted file mode 100644 index ab707b7764aa20d3cb4e393148d4c860d8fb6f17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3040 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 diff --git a/core/utils1/utils1/config.py b/core/utils1/utils1/config.py deleted file mode 100644 index e16101d..0000000 --- a/core/utils1/utils1/config.py +++ /dev/null @@ -1,157 +0,0 @@ -import argparse -import os -import sys -from abc import ABC -from typing import Type - - -class DefaultConfigs(ABC): - ####### base setting ###### - gpus = [0] - seed = 3407 - arch = "resnet50" - datasets = ["zhaolian_train"] - datasets_test = ["adm_res_abs_ddim20s"] - mode = "binary" - class_bal = False - batch_size = 64 - loadSize = 256 - cropSize = 224 - epoch = "latest" - num_workers = 20 - serial_batches = False - isTrain = True - - # data augmentation - rz_interp = ["bilinear"] - # blur_prob = 0.0 - blur_prob = 0.1 - blur_sig = [0.5] - # jpg_prob = 0.0 - jpg_prob = 0.1 - jpg_method = ["cv2"] - jpg_qual = [75] - gray_prob = 0.0 - aug_resize = True - aug_crop = True - aug_flip = True - aug_norm = True - - ####### train setting ###### - warmup = False - # warmup = True - warmup_epoch = 3 - earlystop = True - earlystop_epoch = 5 - optim = "adam" - new_optim = False - loss_freq = 400 - save_latest_freq = 2000 - save_epoch_freq = 20 - continue_train = False - epoch_count = 1 - last_epoch = -1 - nepoch = 400 - beta1 = 0.9 - lr = 0.0001 - init_type = "normal" - init_gain = 0.02 - pretrained = True - - # paths information - root_dir1 = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - root_dir = os.path.dirname(root_dir1) - dataset_root = os.path.join(root_dir, "data") - exp_root = os.path.join(root_dir, "data", "exp") - _exp_name = "" - exp_dir = "" - ckpt_dir = "" - logs_path = "" - ckpt_path = "" - - @property - def exp_name(self): - return self._exp_name - - @exp_name.setter - def exp_name(self, value: str): - self._exp_name = value - self.exp_dir: str = os.path.join(self.exp_root, self.exp_name) - self.ckpt_dir: str = os.path.join(self.exp_dir, "ckpt") - self.logs_path: str = os.path.join(self.exp_dir, "logs.txt") - - os.makedirs(self.exp_dir, exist_ok=True) - os.makedirs(self.ckpt_dir, exist_ok=True) - - def to_dict(self): - dic = {} - for fieldkey in dir(self): - fieldvalue = getattr(self, fieldkey) - if not fieldkey.startswith("__") and not callable(fieldvalue) and not fieldkey.startswith("_"): - dic[fieldkey] = fieldvalue - return dic - - -def args_list2dict(arg_list: list): - assert len(arg_list) % 2 == 0, f"Override list has odd length: {arg_list}; it must be a list of pairs" - return dict(zip(arg_list[::2], arg_list[1::2])) - - -def str2bool(v: str) -> bool: - if isinstance(v, bool): - return v - elif v.lower() in ("true", "yes", "on", "y", "t", "1"): - return True - elif v.lower() in ("false", "no", "off", "n", "f", "0"): - return False - else: - return bool(v) - - -def str2list(v: str, element_type=None) -> list: - if not isinstance(v, (list, tuple, set)): - v = v.lstrip("[").rstrip("]") - v = v.split(",") - v = list(map(str.strip, v)) - if element_type is not None: - v = list(map(element_type, v)) - return v - - -CONFIGCLASS = Type[DefaultConfigs] - -parser = argparse.ArgumentParser() -parser.add_argument("--gpus", default=[0], type=int, nargs="+") -parser.add_argument("--exp_name", default="", type=str) -parser.add_argument("--ckpt", default="model_epoch_latest.pth", type=str) -parser.add_argument("opts", default=[], nargs=argparse.REMAINDER) -args = parser.parse_args() - -if os.path.exists(os.path.join(DefaultConfigs.exp_root, args.exp_name, "config.py")): - sys.path.insert(0, os.path.join(DefaultConfigs.exp_root, args.exp_name)) - from config import cfg - - cfg: CONFIGCLASS -else: - cfg = DefaultConfigs() - -if args.opts: - opts = args_list2dict(args.opts) - for k, v in opts.items(): - if not hasattr(cfg, k): - raise ValueError(f"Unrecognized option: {k}") - original_type = type(getattr(cfg, k)) - if original_type == bool: - setattr(cfg, k, str2bool(v)) - elif original_type in (list, tuple, set): - setattr(cfg, k, str2list(v, type(getattr(cfg, k)[0]))) - else: - setattr(cfg, k, original_type(v)) - -cfg.gpus: list = args.gpus -os.environ["CUDA_VISIBLE_DEVICES"] = ", ".join([str(gpu) for gpu in cfg.gpus]) -cfg.exp_name = args.exp_name -cfg.ckpt_path: str = os.path.join(cfg.ckpt_dir, args.ckpt) - -if isinstance(cfg.datasets, str): - cfg.datasets = cfg.datasets.split(",") diff --git a/core/utils1/utils1/datasets.py b/core/utils1/utils1/datasets.py deleted file mode 100644 index a35863a..0000000 --- a/core/utils1/utils1/datasets.py +++ /dev/null @@ -1,178 +0,0 @@ -import os -from io import BytesIO -from random import choice, random - -import cv2 -import numpy as np -import torch -import torch.utils.data -import torchvision.datasets as datasets -import torchvision.transforms as transforms -import torchvision.transforms.functional as TF -from PIL import Image, ImageFile -from scipy.ndimage import gaussian_filter -from torch.utils.data.sampler import WeightedRandomSampler - -from .config import CONFIGCLASS - -ImageFile.LOAD_TRUNCATED_IMAGES = True - - -def dataset_folder(root: str, cfg: CONFIGCLASS): - if cfg.mode == "binary": - return binary_dataset(root, cfg) - if cfg.mode == "filename": - return FileNameDataset(root, cfg) - raise ValueError("cfg.mode needs to be binary or filename.") - - -def binary_dataset(root: str, cfg: CONFIGCLASS): - identity_transform = transforms.Lambda(lambda img: img) - - rz_func = identity_transform - - if cfg.isTrain: - crop_func = transforms.RandomCrop((448,448)) - else: - crop_func = transforms.CenterCrop((448,448)) if cfg.aug_crop else identity_transform - - if cfg.isTrain and cfg.aug_flip: - flip_func = transforms.RandomHorizontalFlip() - else: - flip_func = identity_transform - - - return datasets.ImageFolder( - root, - transforms.Compose( - [ - rz_func, - #change - transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), - crop_func, - flip_func, - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - if cfg.aug_norm - else identity_transform, - ] - ) - ) - - -class FileNameDataset(datasets.ImageFolder): - def name(self): - return 'FileNameDataset' - - def __init__(self, opt, root): - self.opt = opt - super().__init__(root) - - def __getitem__(self, index): - # Loading sample - path, target = self.samples[index] - return path - - -def blur_jpg_augment(img: Image.Image, cfg: CONFIGCLASS): - img: np.ndarray = np.array(img) - if cfg.isTrain: - if random() < cfg.blur_prob: - sig = sample_continuous(cfg.blur_sig) - gaussian_blur(img, sig) - - if random() < cfg.jpg_prob: - method = sample_discrete(cfg.jpg_method) - qual = sample_discrete(cfg.jpg_qual) - img = jpeg_from_key(img, qual, method) - - return Image.fromarray(img) - - -def sample_continuous(s: list): - if len(s) == 1: - return s[0] - if len(s) == 2: - rg = s[1] - s[0] - return random() * rg + s[0] - raise ValueError("Length of iterable s should be 1 or 2.") - - -def sample_discrete(s: list): - return s[0] if len(s) == 1 else choice(s) - - -def gaussian_blur(img: np.ndarray, sigma: float): - gaussian_filter(img[:, :, 0], output=img[:, :, 0], sigma=sigma) - gaussian_filter(img[:, :, 1], output=img[:, :, 1], sigma=sigma) - gaussian_filter(img[:, :, 2], output=img[:, :, 2], sigma=sigma) - - -def cv2_jpg(img: np.ndarray, compress_val: int) -> np.ndarray: - img_cv2 = img[:, :, ::-1] - encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), compress_val] - result, encimg = cv2.imencode(".jpg", img_cv2, encode_param) - decimg = cv2.imdecode(encimg, 1) - return decimg[:, :, ::-1] - - -def pil_jpg(img: np.ndarray, compress_val: int): - out = BytesIO() - img = Image.fromarray(img) - img.save(out, format="jpeg", quality=compress_val) - img = Image.open(out) - # load from memory before ByteIO closes - img = np.array(img) - out.close() - return img - - -jpeg_dict = {"cv2": cv2_jpg, "pil": pil_jpg} - - -def jpeg_from_key(img: np.ndarray, compress_val: int, key: str) -> np.ndarray: - method = jpeg_dict[key] - return method(img, compress_val) - - -rz_dict = {'bilinear': Image.BILINEAR, - 'bicubic': Image.BICUBIC, - 'lanczos': Image.LANCZOS, - 'nearest': Image.NEAREST} -def custom_resize(img: Image.Image, cfg: CONFIGCLASS) -> Image.Image: - interp = sample_discrete(cfg.rz_interp) - return TF.resize(img, cfg.loadSize, interpolation=rz_dict[interp]) - - -def get_dataset(cfg: CONFIGCLASS): - dset_lst = [] - for dataset in cfg.datasets: - root = os.path.join(cfg.dataset_root, dataset) - dset = dataset_folder(root, cfg) - dset_lst.append(dset) - return torch.utils.data.ConcatDataset(dset_lst) - - -def get_bal_sampler(dataset: torch.utils.data.ConcatDataset): - targets = [] - for d in dataset.datasets: - targets.extend(d.targets) - - ratio = np.bincount(targets) - w = 1.0 / torch.tensor(ratio, dtype=torch.float) - sample_weights = w[targets] - return WeightedRandomSampler(weights=sample_weights, num_samples=len(sample_weights)) - - -def create_dataloader(cfg: CONFIGCLASS): - shuffle = not cfg.serial_batches if (cfg.isTrain and not cfg.class_bal) else False - dataset = get_dataset(cfg) - sampler = get_bal_sampler(dataset) if cfg.class_bal else None - - return torch.utils.data.DataLoader( - dataset, - batch_size=cfg.batch_size, - shuffle=shuffle, - sampler=sampler, - num_workers=int(cfg.num_workers), - ) diff --git a/core/utils1/utils1/earlystop.py b/core/utils1/utils1/earlystop.py deleted file mode 100644 index 25aef71..0000000 --- a/core/utils1/utils1/earlystop.py +++ /dev/null @@ -1,46 +0,0 @@ -import numpy as np - -from .trainer import Trainer - - -class EarlyStopping: - """Early stops the training if validation loss doesn't improve after a given patience.""" - - def __init__(self, patience=1, verbose=False, delta=0): - """ - Args: - patience (int): How long to wait after last time validation loss improved. - Default: 7 - verbose (bool): If True, prints a message for each validation loss improvement. - Default: False - delta (float): Minimum change in the monitored quantity to qualify as an improvement. - Default: 0 - """ - self.patience = patience - self.verbose = verbose - self.counter = 0 - self.best_score = None - self.early_stop = False - self.score_max = -np.Inf - self.delta = delta - - def __call__(self, score: float, trainer: Trainer): - if self.best_score is None: - self.best_score = score - self.save_checkpoint(score, trainer) - elif score < self.best_score - self.delta: - self.counter += 1 - print(f"EarlyStopping counter: {self.counter} out of {self.patience}") - if self.counter >= self.patience: - self.early_stop = True - else: - self.best_score = score - self.save_checkpoint(score, trainer) - self.counter = 0 - - def save_checkpoint(self, score: float, trainer: Trainer): - """Saves model when validation loss decrease.""" - if self.verbose: - print(f"Validation accuracy increased ({self.score_max:.6f} --> {score:.6f}). Saving model ...") - trainer.save_networks("best") - self.score_max = score diff --git a/core/utils1/utils1/eval.py b/core/utils1/utils1/eval.py deleted file mode 100644 index 5784de3..0000000 --- a/core/utils1/utils1/eval.py +++ /dev/null @@ -1,66 +0,0 @@ -import math -import os - -import matplotlib.pyplot as plt -import numpy as np -import torch -import torch.nn as nn - -from .config import CONFIGCLASS -from .utils import to_cuda - - -def get_val_cfg(cfg: CONFIGCLASS, split="val", copy=True): - if copy: - from copy import deepcopy - - val_cfg = deepcopy(cfg) - else: - val_cfg = cfg - val_cfg.dataset_root = os.path.join(val_cfg.dataset_root, split) - val_cfg.datasets = cfg.datasets_test - val_cfg.isTrain = False - # val_cfg.aug_resize = False - # val_cfg.aug_crop = False - val_cfg.aug_flip = False - val_cfg.serial_batches = True - val_cfg.jpg_method = ["pil"] - # Currently assumes jpg_prob, blur_prob 0 or 1 - if len(val_cfg.blur_sig) == 2: - b_sig = val_cfg.blur_sig - val_cfg.blur_sig = [(b_sig[0] + b_sig[1]) / 2] - if len(val_cfg.jpg_qual) != 1: - j_qual = val_cfg.jpg_qual - val_cfg.jpg_qual = [int((j_qual[0] + j_qual[-1]) / 2)] - return val_cfg - -def validate(model: nn.Module, cfg: CONFIGCLASS): - from sklearn.metrics import accuracy_score, average_precision_score, roc_auc_score - - from .datasets import create_dataloader - - data_loader = create_dataloader(cfg) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - with torch.no_grad(): - y_true, y_pred = [], [] - for data in data_loader: - img, label, meta = data if len(data) == 3 else (*data, None) - in_tens = to_cuda(img, device) - meta = to_cuda(meta, device) - predict = model(in_tens, meta).sigmoid() - y_pred.extend(predict.flatten().tolist()) - y_true.extend(label.flatten().tolist()) - - y_true, y_pred = np.array(y_true), np.array(y_pred) - r_acc = accuracy_score(y_true[y_true == 0], y_pred[y_true == 0] > 0.5) - f_acc = accuracy_score(y_true[y_true == 1], y_pred[y_true == 1] > 0.5) - acc = accuracy_score(y_true, y_pred > 0.5) - ap = average_precision_score(y_true, y_pred) - results = { - "ACC": acc, - "AP": ap, - "R_ACC": r_acc, - "F_ACC": f_acc, - } - return results diff --git a/core/utils1/utils1/trainer.py b/core/utils1/utils1/trainer.py deleted file mode 100644 index 7a4ce8b..0000000 --- a/core/utils1/utils1/trainer.py +++ /dev/null @@ -1,169 +0,0 @@ -import os - -import torch -import torch.nn as nn -from torch.nn import init - -from .config import CONFIGCLASS -from .utils import get_network -from .warmup import GradualWarmupScheduler - - -class BaseModel(nn.Module): - def __init__(self, cfg: CONFIGCLASS): - super().__init__() - self.cfg = cfg - self.total_steps = 0 - self.isTrain = cfg.isTrain - self.save_dir = cfg.ckpt_dir - self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") - self.model:nn.Module - self.model=nn.Module.to(self.device) - # self.model.to(self.device) - self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) - self.optimizer: torch.optim.Optimizer - - def save_networks(self, epoch: int): - save_filename = f"model_epoch_{epoch}.pth" - save_path = os.path.join(self.save_dir, save_filename) - - # serialize model and optimizer to dict - state_dict = { - "model": self.model.state_dict(), - "optimizer": self.optimizer.state_dict(), - "total_steps": self.total_steps, - } - - torch.save(state_dict, save_path) - - # load models from the disk - def load_networks(self, epoch: int): - load_filename = f"model_epoch_{epoch}.pth" - load_path = os.path.join(self.save_dir, load_filename) - - if epoch==0: - # load_filename = f"lsun_adm.pth" - load_path="checkpoints/optical.pth" - print("loading optical path") - else : - print(f"loading the model from {load_path}") - - # print(f"loading the model from {load_path}") - - # if you are using PyTorch newer than 0.4 (e.g., built from - # GitHub source), you can remove str() on self.device - state_dict = torch.load(load_path, map_location=self.device) - if hasattr(state_dict, "_metadata"): - del state_dict._metadata - - self.model.load_state_dict(state_dict["model"]) - self.total_steps = state_dict["total_steps"] - - if self.isTrain and not self.cfg.new_optim: - self.optimizer.load_state_dict(state_dict["optimizer"]) - # move optimizer state to GPU - for state in self.optimizer.state.values(): - for k, v in state.items(): - if torch.is_tensor(v): - state[k] = v.to(self.device) - - for g in self.optimizer.param_groups: - g["lr"] = self.cfg.lr - - def eval(self): - self.model.eval() - - def test(self): - with torch.no_grad(): - self.forward() - - -def init_weights(net: nn.Module, init_type="normal", gain=0.02): - def init_func(m: nn.Module): - classname = m.__class__.__name__ - if hasattr(m, "weight") and (classname.find("Conv") != -1 or classname.find("Linear") != -1): - if init_type == "normal": - init.normal_(m.weight.data, 0.0, gain) - elif init_type == "xavier": - init.xavier_normal_(m.weight.data, gain=gain) - elif init_type == "kaiming": - init.kaiming_normal_(m.weight.data, a=0, mode="fan_in") - elif init_type == "orthogonal": - init.orthogonal_(m.weight.data, gain=gain) - else: - raise NotImplementedError(f"initialization method [{init_type}] is not implemented") - if hasattr(m, "bias") and m.bias is not None: - init.constant_(m.bias.data, 0.0) - elif classname.find("BatchNorm2d") != -1: - init.normal_(m.weight.data, 1.0, gain) - init.constant_(m.bias.data, 0.0) - - print(f"initialize network with {init_type}") - net.apply(init_func) - - -class Trainer(BaseModel): - def name(self): - return "Trainer" - - def __init__(self, cfg: CONFIGCLASS): - super().__init__(cfg) - self.arch = cfg.arch - self.model = get_network(self.arch, cfg.isTrain, cfg.continue_train, cfg.init_gain, cfg.pretrained) - - self.loss_fn = nn.BCEWithLogitsLoss() - # initialize optimizers - if cfg.optim == "adam": - self.optimizer = torch.optim.Adam(self.model.parameters(), lr=cfg.lr, betas=(cfg.beta1, 0.999)) - elif cfg.optim == "sgd": - self.optimizer = torch.optim.SGD(self.model.parameters(), lr=cfg.lr, momentum=0.9, weight_decay=5e-4) - else: - raise ValueError("optim should be [adam, sgd]") - if cfg.warmup: - scheduler_cosine = torch.optim.lr_scheduler.CosineAnnealingLR( - self.optimizer, cfg.nepoch - cfg.warmup_epoch, eta_min=1e-6 - ) - self.scheduler = GradualWarmupScheduler( - self.optimizer, multiplier=1, total_epoch=cfg.warmup_epoch, after_scheduler=scheduler_cosine - ) - self.scheduler.step() - if cfg.continue_train: - self.load_networks(cfg.epoch) - self.model.to(self.device) - - # self.model.load_state_dict(torch.load('checkpoints/optical.pth')) - load_path='checkpoints/optical.pth' - state_dict = torch.load(load_path, map_location=self.device) - - - self.model.load_state_dict(state_dict["model"]) - - - def adjust_learning_rate(self, min_lr=1e-6): - for param_group in self.optimizer.param_groups: - param_group["lr"] /= 10.0 - if param_group["lr"] < min_lr: - return False - return True - - def set_input(self, input): - img, label, meta = input if len(input) == 3 else (input[0], input[1], {}) - self.input = img.to(self.device) - self.label = label.to(self.device).float() - for k in meta.keys(): - if isinstance(meta[k], torch.Tensor): - meta[k] = meta[k].to(self.device) - self.meta = meta - - def forward(self): - self.output = self.model(self.input, self.meta) - - def get_loss(self): - return self.loss_fn(self.output.squeeze(1), self.label) - - def optimize_parameters(self): - self.forward() - self.loss = self.loss_fn(self.output.squeeze(1), self.label) - self.optimizer.zero_grad() - self.loss.backward() - self.optimizer.step() diff --git a/core/utils1/utils1/utils.py b/core/utils1/utils1/utils.py deleted file mode 100644 index d52ebbd..0000000 --- a/core/utils1/utils1/utils.py +++ /dev/null @@ -1,109 +0,0 @@ -import argparse -import os -import sys -import time -import warnings -from importlib import import_module - -import numpy as np -import torch -import torch.nn as nn -from PIL import Image - -warnings.filterwarnings("ignore", category=UserWarning, module="torch.nn.functional") - - -def str2bool(v: str, strict=True) -> bool: - if isinstance(v, bool): - return v - elif isinstance(v, str): - if v.lower() in ("true", "yes", "on" "t", "y", "1"): - return True - elif v.lower() in ("false", "no", "off", "f", "n", "0"): - return False - if strict: - raise argparse.ArgumentTypeError("Unsupported value encountered.") - else: - return True - - -def to_cuda(data, device="cuda", exclude_keys: "list[str]" = None): - if isinstance(data, torch.Tensor): - data = data.to(device) - elif isinstance(data, (tuple, list, set)): - data = [to_cuda(b, device) for b in data] - elif isinstance(data, dict): - if exclude_keys is None: - exclude_keys = [] - for k in data.keys(): - if k not in exclude_keys: - data[k] = to_cuda(data[k], device) - else: - # raise TypeError(f"Unsupported type: {type(data)}") - data = data - return data - - -class HiddenPrints: - def __enter__(self): - self._original_stdout = sys.stdout - sys.stdout = open(os.devnull, "w") - - def __exit__(self, exc_type, exc_val, exc_tb): - sys.stdout.close() - sys.stdout = self._original_stdout - - -class Logger(object): - def __init__(self): - self.terminal = sys.stdout - self.file = None - - def open(self, file, mode=None): - if mode is None: - mode = "w" - self.file = open(file, mode) - - def write(self, message, is_terminal=1, is_file=1): - if "\r" in message: - is_file = 0 - if is_terminal == 1: - self.terminal.write(message) - self.terminal.flush() - if is_file == 1: - self.file.write(message) - self.file.flush() - - def flush(self): - # this flush method is needed for python 3 compatibility. - # this handles the flush command by doing nothing. - # you might want to specify some extra behavior here. - pass - - -def get_network(arch: str, isTrain=False, continue_train=False, init_gain=0.02, pretrained=True): - if "resnet" in arch: - from networks.resnet import ResNet - - resnet = getattr(import_module("networks.resnet"), arch) - if isTrain: - if continue_train: - model: ResNet = resnet(num_classes=1) - else: - model: ResNet = resnet(pretrained=pretrained) - model.fc = nn.Linear(2048, 1) - nn.init.normal_(model.fc.weight.data, 0.0, init_gain) - else: - model: ResNet = resnet(num_classes=1) - return model - else: - raise ValueError(f"Unsupported arch: {arch}") - - -def pad_img_to_square(img: np.ndarray): - H, W = img.shape[:2] - if H != W: - new_size = max(H, W) - img = np.pad(img, ((0, new_size - H), (0, new_size - W), (0, 0)), mode="constant") - assert img.shape[0] == img.shape[1] == new_size - return img diff --git a/core/utils1/utils1/warmup.py b/core/utils1/utils1/warmup.py deleted file mode 100644 index c193a6c..0000000 --- a/core/utils1/utils1/warmup.py +++ /dev/null @@ -1,70 +0,0 @@ -from torch.optim.lr_scheduler import ReduceLROnPlateau, _LRScheduler - - -class GradualWarmupScheduler(_LRScheduler): - """Gradually warm-up(increasing) learning rate in optimizer. - Proposed in 'Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour'. - - Args: - optimizer (Optimizer): Wrapped optimizer. - multiplier: target learning rate = base lr * multiplier if multiplier > 1.0. if multiplier = 1.0, lr starts from 0 and ends up with the base_lr. - total_epoch: target learning rate is reached at total_epoch, gradually - after_scheduler: after target_epoch, use this scheduler(eg. ReduceLROnPlateau) - """ - - def __init__(self, optimizer, multiplier, total_epoch, after_scheduler=None): - self.multiplier = multiplier - if self.multiplier < 1.0: - raise ValueError("multiplier should be greater thant or equal to 1.") - self.total_epoch = total_epoch - self.after_scheduler = after_scheduler - self.finished = False - super().__init__(optimizer) - - def get_lr(self): - if self.last_epoch > self.total_epoch: - if self.after_scheduler: - if not self.finished: - self.after_scheduler.base_lrs = [base_lr * self.multiplier for base_lr in self.base_lrs] - self.finished = True - return self.after_scheduler.get_last_lr() - return [base_lr * self.multiplier for base_lr in self.base_lrs] - - if self.multiplier == 1.0: - return [base_lr * (float(self.last_epoch) / self.total_epoch) for base_lr in self.base_lrs] - else: - return [ - base_lr * ((self.multiplier - 1.0) * self.last_epoch / self.total_epoch + 1.0) - for base_lr in self.base_lrs - ] - - def step_ReduceLROnPlateau(self, metrics, epoch=None): - if epoch is None: - epoch = self.last_epoch + 1 - self.last_epoch = ( - epoch if epoch != 0 else 1 - ) # ReduceLROnPlateau is called at the end of epoch, whereas others are called at beginning - if self.last_epoch <= self.total_epoch: - warmup_lr = [ - base_lr * ((self.multiplier - 1.0) * self.last_epoch / self.total_epoch + 1.0) - for base_lr in self.base_lrs - ] - for param_group, lr in zip(self.optimizer.param_groups, warmup_lr): - param_group["lr"] = lr - else: - if epoch is None: - self.after_scheduler.step(metrics, None) - else: - self.after_scheduler.step(metrics, epoch - self.total_epoch) - - def step(self, epoch=None, metrics=None): - if type(self.after_scheduler) != ReduceLROnPlateau: - if self.finished and self.after_scheduler: - if epoch is None: - self.after_scheduler.step(None) - else: - self.after_scheduler.step(epoch - self.total_epoch) - else: - return super().step(epoch) - else: - self.step_ReduceLROnPlateau(metrics, epoch) From a7ea9c02e72804a5ca7bf7a5af7e70c98d8b7e60 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 22 Nov 2025 06:57:27 +0800 Subject: [PATCH 12/55] Update dataset names in DefaultConfigs class for consistency --- core/utils1/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index ef05fa7..d399e41 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -10,8 +10,8 @@ class DefaultConfigs(ABC): gpus = [0] seed = 3407 arch = "resnet50" - datasets = ["train"] - datasets_test = ["val"] + datasets = ["trainset_1"] + datasets_test = ["val_set_1"] mode = "binary" class_bal = False batch_size = 64 From b4806f1330ff2d184a8349da9629a134935e4cb2 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 22 Nov 2025 09:48:49 +0800 Subject: [PATCH 13/55] Update root directory path in DefaultConfigs class for correct data access --- core/utils1/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index d399e41..c76ea41 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -59,7 +59,7 @@ class DefaultConfigs(ABC): pretrained = True # paths information - root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) dataset_root = os.path.join(root_dir, "data") exp_root = os.path.join(root_dir, "data", "exp") _exp_name = "" From 9fd55529737453f94ca76147b5e8a70d17087729 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 22 Nov 2025 10:03:09 +0800 Subject: [PATCH 14/55] Add AUC, TPR, and TNR metrics to validation results; fix model initialization in Trainer class --- core/utils1/eval.py | 9 +++++++++ core/utils1/trainer.py | 1 - 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/utils1/eval.py b/core/utils1/eval.py index 731ebf1..b93d2f8 100644 --- a/core/utils1/eval.py +++ b/core/utils1/eval.py @@ -57,10 +57,19 @@ def validate(model: nn.Module, cfg: CONFIGCLASS): f_acc = accuracy_score(y_true[y_true == 1], y_pred[y_true == 1] > 0.5) acc = accuracy_score(y_true, y_pred > 0.5) ap = average_precision_score(y_true, y_pred) + auc = roc_auc_score(y_true, y_pred) + + # Calculate TPR (True Positive Rate / Recall) and TNR (True Negative Rate / Specificity) + tpr = f_acc # TPR is the accuracy on fake samples (class 1) + tnr = r_acc # TNR is the accuracy on real samples (class 0) + results = { "ACC": acc, "AP": ap, + "AUC": auc, "R_ACC": r_acc, "F_ACC": f_acc, + "TPR": tpr, + "TNR": tnr, } return results diff --git a/core/utils1/trainer.py b/core/utils1/trainer.py index 3a5abc6..6739243 100644 --- a/core/utils1/trainer.py +++ b/core/utils1/trainer.py @@ -18,7 +18,6 @@ def __init__(self, cfg: CONFIGCLASS): self.save_dir = cfg.ckpt_dir self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") self.model:nn.Module - self.model=nn.Module.to(self.device) # self.model.to(self.device) #self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) self.optimizer: torch.optim.Optimizer From 3f55265645b513414eab80fade90b8e337abe1fe Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 22 Nov 2025 10:35:18 +0800 Subject: [PATCH 15/55] Reduce batch size to 16 and num_workers to 4 for improved compatibility with 4GB GPU --- core/utils1/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index c76ea41..d1901d7 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -14,11 +14,11 @@ class DefaultConfigs(ABC): datasets_test = ["val_set_1"] mode = "binary" class_bal = False - batch_size = 64 + batch_size = 16 # Reduced from 64 to 16 for 4GB GPU loadSize = 256 cropSize = 224 epoch = "latest" - num_workers = 20 + num_workers = 4 # Reduced from 20 to 4 to avoid shared memory issues serial_batches = False isTrain = True From 71db91869bcea1c0a0a18f8eec4be4d1b9c629bb Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:52:43 +0800 Subject: [PATCH 16/55] Integrate Weights & Biases (W&B) for enhanced training tracking; reduce training epochs from 400 to 100; update dependencies in requirements and project files. --- WANDB_SETUP.md | 216 +++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + requirements.txt | 1 + train.py | 37 ++++++++ 4 files changed, 255 insertions(+) create mode 100644 WANDB_SETUP.md diff --git a/WANDB_SETUP.md b/WANDB_SETUP.md new file mode 100644 index 0000000..07dc9ad --- /dev/null +++ b/WANDB_SETUP.md @@ -0,0 +1,216 @@ +# Weights & Biases (W&B) Integration Guide + +## What is W&B? +Weights & Biases tracks your training runs, logs metrics, and **stores model checkpoints in the cloud** so you can access them from any device. + +## Setup Instructions + +### 1. Install wandb +```bash +# Local installation +pip install wandb + +# Or with Docker (already included in requirements.txt) +# Just rebuild the Docker image +docker build -f Dockerfile.gpu-alt -t sacdalance/thesis-aigvdet:gpu . +``` + +### 2. Create W&B Account +1. Go to https://wandb.ai/signup +2. Sign up (free for personal use) +3. Get your API key from https://wandb.ai/authorize + +### 3. Login to W&B + +**On local machine:** +```bash +wandb login +# Paste your API key when prompted +``` + +**On Vast.ai or remote server:** +```bash +# Option 1: Interactive login +wandb login + +# Option 2: Use API key directly +export WANDB_API_KEY="your-api-key-here" +# Or add to Docker run command: +docker run --gpus all -e WANDB_API_KEY="your-key" ... +``` + +### 4. Train with W&B Tracking + +```bash +# Train normally - W&B will automatically track +python train.py --gpus 0 --exp_name TRAIN_RGB datasets trainset_1_RGB datasets_test val_set_1_RGB + +# With Docker on Vast.ai +docker run --gpus all --shm-size=8g -it --rm \ + -e WANDB_API_KEY="your-api-key" \ + -v /workspace/data:/app/data \ + -v /workspace/checkpoints:/app/checkpoints \ + sacdalance/thesis-aigvdet:gpu \ + python3.11 train.py --gpus 0 --exp_name TRAIN_RGB datasets trainset_1_RGB datasets_test val_set_1_RGB +``` + +## What W&B Tracks + +### Metrics Logged: +- **Training loss** - Every batch +- **Validation metrics** - Every epoch + - Accuracy (ACC) + - Average Precision (AP) + - AUC (Area Under Curve) + - TPR (True Positive Rate) + - TNR (True Negative Rate) +- **System metrics** - GPU usage, CPU, memory + +### Model Checkpoints: +- **Automatically uploads** `model_epoch_best.pth` to W&B cloud +- **Access from anywhere** - Download checkpoints from any device +- **Version control** - All checkpoint versions saved + +## Accessing Your Results + +### View Training Dashboard: +1. Go to https://wandb.ai/ +2. Navigate to your project: `aigvdet-training` +3. Click on your run (e.g., `TRAIN_RGB`) +4. View real-time metrics, charts, and system stats + +### Download Checkpoints: +```python +# From any device with Python +import wandb + +# Login +wandb.login() + +# Download checkpoint +api = wandb.Api() +run = api.run("your-username/aigvdet-training/run-id") +artifact = run.use_artifact('TRAIN_RGB_best_model:latest') +artifact_dir = artifact.download() + +# The checkpoint will be in: artifact_dir/model_epoch_best.pth +``` + +**Or via Web UI:** +1. Go to your run page +2. Click "Artifacts" tab +3. Click on `TRAIN_RGB_best_model` +4. Click "Download" button + +## Benefits for Vast.ai Training + +### Problem W&B Solves: +- ❌ **Without W&B**: Must manually download checkpoints before stopping Vast.ai instance +- ✅ **With W&B**: Checkpoints automatically saved to cloud, accessible from anywhere + +### Workflow: +```bash +# 1. Start training on Vast.ai +docker run --gpus all -e WANDB_API_KEY="your-key" ... python train.py ... + +# 2. Training runs (checkpoints auto-upload to W&B cloud) + +# 3. Stop Vast.ai instance (no need to manually download!) + +# 4. Later, on your local machine: +wandb artifact get your-username/aigvdet-training/TRAIN_RGB_best_model:latest +# Checkpoint downloaded to local machine! +``` + +## Configuration Options + +### Disable W&B (if needed): +```bash +# Set environment variable +export WANDB_MODE=disabled + +# Or in code (add to train.py): +os.environ["WANDB_MODE"] = "disabled" +``` + +### Change Project Name: +Edit `train.py` line 43: +```python +wandb.init( + project="your-custom-project-name", # Change this + name=cfg.exp_name, + ... +) +``` + +### Resume Training: +W&B automatically handles resume if you use `continue_train True`: +```bash +python train.py --gpus 0 --exp_name TRAIN_RGB continue_train True epoch latest +``` + +## Cost +- **Free tier**: Unlimited personal projects, 100GB storage +- **Teams tier**: $50/user/month for collaboration +- For academic research, you can apply for free team accounts + +## Troubleshooting + +### "wandb: ERROR api_key not configured" +```bash +# Set API key +export WANDB_API_KEY="your-key" +# Or login +wandb login +``` + +### Checkpoint upload fails +```bash +# Check internet connection +# Check W&B storage quota (free tier = 100GB) +# Verify checkpoint file exists +ls -lh data/exp/TRAIN_RGB/ckpt/model_epoch_best.pth +``` + +### Disable W&B temporarily +```bash +export WANDB_MODE=offline # Run offline, sync later +# Or +export WANDB_MODE=disabled # Completely disable +``` + +## Example Output + +When training starts: +``` +Setting up TensorBoard... +✓ Logs will be saved to: data/exp/TRAIN_RGB + +Initializing Weights & Biases... +wandb: Currently logged in as: your-username (use `wandb login --relogin` to force relogin) +wandb: Tracking run with wandb version 0.16.0 +wandb: Run data is saved locally in data/exp/TRAIN_RGB/wandb +wandb: Run `wandb offline` to turn off syncing. +wandb: Syncing run TRAIN_RGB +wandb: ⭐️ View project at https://wandb.ai/your-username/aigvdet-training +wandb: 🚀 View run at https://wandb.ai/your-username/aigvdet-training/runs/abc123 +✓ W&B tracking enabled: https://wandb.ai/your-username/aigvdet-training/runs/abc123 +``` + +Click the URL to view your training in real-time! + +## Summary + +**With W&B integration:** +1. ✅ Track training metrics in real-time from any device +2. ✅ Automatically save checkpoints to cloud storage +3. ✅ No need to manually download before stopping Vast.ai +4. ✅ Compare multiple training runs easily +5. ✅ Share results with collaborators + +**Setup is simple:** +```bash +pip install wandb +wandb login +python train.py # W&B automatically tracks! +``` diff --git a/pyproject.toml b/pyproject.toml index 738690b..552ef44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "tensorboardX", "tqdm", "blobfile>=1.0.5", + "wandb", "pip", ] diff --git a/requirements.txt b/requirements.txt index 57dc112..d8dbfc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ tensorboard tensorboardX tqdm blobfile>=1.0.5 +wandb diff --git a/train.py b/train.py index 87004e6..1020670 100644 --- a/train.py +++ b/train.py @@ -5,6 +5,7 @@ from tensorboardX import SummaryWriter from tqdm import tqdm +import wandb from core.utils1.datasets import create_dataloader from core.utils1.earlystop import EarlyStopping @@ -40,6 +41,17 @@ val_writer = SummaryWriter(os.path.join(cfg.exp_dir, "val")) print(f"✓ Logs will be saved to: {cfg.exp_dir}") + # Initialize wandb + print("\nInitializing wandb...") + wandb.init( + project="aigvdet-training", + name=cfg.exp_name, + config=cfg.to_dict(), + dir=cfg.exp_dir, + resume="allow" if cfg.continue_train else None + ) + print(f"✓ wandb tracking enabled: {wandb.run.url}") + print("\nInitializing model...") trainer = Trainer(cfg) early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.001, verbose=True) @@ -67,6 +79,7 @@ # if trainer.total_steps % cfg.loss_freq == 0: # log.write(f"Train loss: {trainer.loss} at step: {trainer.total_steps}\n") train_writer.add_scalar("loss", trainer.loss, trainer.total_steps) + wandb.log({"train/loss": trainer.loss, "train/step": trainer.total_steps}) if trainer.total_steps % cfg.save_latest_freq == 0: print(f"💾 Saving checkpoint (epoch {epoch+1}, step {trainer.total_steps})") @@ -93,6 +106,26 @@ val_writer.add_scalar("TPR", val_results["TPR"], trainer.total_steps) val_writer.add_scalar("TNR", val_results["TNR"], trainer.total_steps) + # Log validation metrics to wandb + wandb.log({ + "val/AP": val_results["AP"], + "val/ACC": val_results["ACC"], + "val/AUC": val_results["AUC"], + "val/TPR": val_results["TPR"], + "val/TNR": val_results["TNR"], + "epoch": epoch + }) + + # Save best checkpoint as wandb artifact + if os.path.exists(os.path.join(cfg.ckpt_dir, "model_epoch_best.pth")): + artifact = wandb.Artifact( + name=f"{cfg.exp_name}_best_model", + type="model", + description=f"Best model checkpoint at epoch {epoch}" + ) + artifact.add_file(os.path.join(cfg.ckpt_dir, "model_epoch_best.pth")) + wandb.log_artifact(artifact) + print(f"✓ Validation Results - AP: {val_results['AP']:.4f} | ACC: {val_results['ACC']:.4f} | AUC: {val_results['AUC']:.4f}") log.write(f"(Val @ epoch {epoch}) AP: {val_results['AP']}; ACC: {val_results['ACC']}\n") @@ -111,3 +144,7 @@ # print(trainer.scheduler.get_lr()[0]) trainer.scheduler.step() trainer.train() + + # Finish wandb run + print("\n✓ Training complete! Finalizing W&B...") + wandb.finish() From 93ebc595e56ca80222e37aecf4957cd3ff358ad2 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:15:42 +0800 Subject: [PATCH 17/55] Add environment variable files to .gitignore for security --- .gitignore | 4 ++++ train.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2fab0b4..0d394e4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ data/ checkpoints/ raft_model/ +# Environment variables (contains secrets) +.env +.env.example + # Generated output folders frame/ optical_result/ diff --git a/train.py b/train.py index 1020670..f740416 100644 --- a/train.py +++ b/train.py @@ -46,7 +46,7 @@ wandb.init( project="aigvdet-training", name=cfg.exp_name, - config=cfg.to_dict(), + config=cfg.to_dict(), dir=cfg.exp_dir, resume="allow" if cfg.continue_train else None ) From c59f3abf5d0d5706342f3a0e5adeb8d535182122 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:58:02 +0800 Subject: [PATCH 18/55] Add Weights & Biases (W&B) to Dockerfiles for enhanced tracking --- Dockerfile.cpu | 3 ++- Dockerfile.gpu-alt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.cpu b/Dockerfile.cpu index 9bd6b9c..2ef81b9 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -50,7 +50,8 @@ RUN uv pip install --system \ tensorboard \ tensorboardX \ tqdm \ - "blobfile>=1.0.5" + "blobfile>=1.0.5" \ + wandb # Install PyTorch CPU version using pip directly RUN pip3 install torch==2.0.0+cpu torchvision==0.15.1+cpu \ diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 94ba401..a59b1c0 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -51,7 +51,8 @@ RUN uv pip install --system \ tensorboard \ tensorboardX \ tqdm \ - "blobfile>=1.0.5" + "blobfile>=1.0.5" \ + wandb # Install torchvision separately (base image has torch but may need torchvision update) RUN pip3 install torchvision==0.15.1+cu117 \ From e07001237448cb60cea5bb13e14a3a6ddbc9e56f Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sun, 23 Nov 2025 18:13:42 +0800 Subject: [PATCH 19/55] Reduce training epochs from 400 to 100 for improved efficiency --- core/utils1/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index d1901d7..7aae627 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -51,7 +51,7 @@ class DefaultConfigs(ABC): continue_train = False epoch_count = 1 last_epoch = -1 - nepoch = 400 + nepoch = 100 beta1 = 0.9 lr = 0.0001 init_type = "normal" From 4658a745f41d1821a457ffdbbca9399d91b424f6 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sun, 23 Nov 2025 23:48:46 +0800 Subject: [PATCH 20/55] Add python-dotenv for environment variable management; update batch size and num_workers for GPU compatibility; enhance early stopping with W&B logging --- Dockerfile.cpu | 3 ++- Dockerfile.gpu-alt | 3 ++- core/utils1/config.py | 4 ++-- core/utils1/earlystop.py | 8 ++++++++ pyproject.toml | 1 + requirements.txt | 1 + train.py | 14 ++++++++++++++ 7 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Dockerfile.cpu b/Dockerfile.cpu index 2ef81b9..e5444d5 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -51,7 +51,8 @@ RUN uv pip install --system \ tensorboardX \ tqdm \ "blobfile>=1.0.5" \ - wandb + wandb \ + python-dotenv # Install PyTorch CPU version using pip directly RUN pip3 install torch==2.0.0+cpu torchvision==0.15.1+cpu \ diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index a59b1c0..50e4fa4 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -52,7 +52,8 @@ RUN uv pip install --system \ tensorboardX \ tqdm \ "blobfile>=1.0.5" \ - wandb + wandb \ + python-dotenv # Install torchvision separately (base image has torch but may need torchvision update) RUN pip3 install torchvision==0.15.1+cu117 \ diff --git a/core/utils1/config.py b/core/utils1/config.py index 7aae627..81fac4f 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -14,11 +14,11 @@ class DefaultConfigs(ABC): datasets_test = ["val_set_1"] mode = "binary" class_bal = False - batch_size = 16 # Reduced from 64 to 16 for 4GB GPU + batch_size = 64 # RTX 3090 24GB can handle original batch size loadSize = 256 cropSize = 224 epoch = "latest" - num_workers = 4 # Reduced from 20 to 4 to avoid shared memory issues + num_workers = 16 # Increased for faster data loading on 24GB GPU serial_batches = False isTrain = True diff --git a/core/utils1/earlystop.py b/core/utils1/earlystop.py index 257e391..3112d65 100644 --- a/core/utils1/earlystop.py +++ b/core/utils1/earlystop.py @@ -1,4 +1,5 @@ import numpy as np +import wandb from core.utils1.trainer import Trainer @@ -44,3 +45,10 @@ def save_checkpoint(self, score: float, trainer: Trainer): print(f"Validation accuracy increased ({self.score_max:.6f} --> {score:.6f}). Saving model ...") trainer.save_networks("best") self.score_max = score + + # Log best model save to wandb + if wandb.run is not None: + wandb.log({ + "best_model/score": score, + "best_model/improvement": score - self.score_max if self.score_max != -np.Inf else 0 + }) diff --git a/pyproject.toml b/pyproject.toml index 552ef44..93e2090 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "tqdm", "blobfile>=1.0.5", "wandb", + "python-dotenv", "pip", ] diff --git a/requirements.txt b/requirements.txt index d8dbfc4..8b5b0b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ tensorboardX tqdm blobfile>=1.0.5 wandb +python-dotenv diff --git a/train.py b/train.py index f740416..bb68523 100644 --- a/train.py +++ b/train.py @@ -3,6 +3,7 @@ import os import time +from dotenv import load_dotenv from tensorboardX import SummaryWriter from tqdm import tqdm import wandb @@ -16,6 +17,17 @@ import ssl ssl._create_default_https_context = ssl._create_unverified_context +# Load environment variables from .env file +load_dotenv() + +# Set wandb API key from environment +wandb_api_key = os.getenv("WANDB") +if wandb_api_key: + os.environ["WANDB_API_KEY"] = wandb_api_key + print(f"✓ Loaded wandb API key from .env") +else: + print("⚠️ Warning: WANDB API key not found in .env file") + if __name__ == "__main__": print("=" * 60) @@ -135,10 +147,12 @@ if trainer.adjust_learning_rate(): print("📉 Learning rate dropped by 10, continuing training...") log.write("Learning rate dropped by 10, continue training...\n") + wandb.log({"early_stopping/lr_dropped": True, "epoch": epoch}) early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.002, verbose=True) else: print("\n⏹️ Early stopping triggered") log.write("Early stopping.\n") + wandb.log({"early_stopping/triggered": True, "epoch": epoch}) break if cfg.warmup: # print(trainer.scheduler.get_lr()[0]) From df351bba4cb9553dfb39763d56652fc98be655cf Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Mon, 24 Nov 2025 00:15:56 +0800 Subject: [PATCH 21/55] Update docker-compose.yml to include logs volume and ensure .env file is referenced; add Docker VM setup guide for environment configuration and troubleshooting --- DOCKER_VM_SETUP.md | 171 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 6 ++ 2 files changed, 177 insertions(+) create mode 100644 DOCKER_VM_SETUP.md diff --git a/DOCKER_VM_SETUP.md b/DOCKER_VM_SETUP.md new file mode 100644 index 0000000..7014925 --- /dev/null +++ b/DOCKER_VM_SETUP.md @@ -0,0 +1,171 @@ +# Docker Setup Guide for VM + +## Quick Start + +### 1. Create Data Structure +```bash +# Run the setup script +python setup_data_structure.py + +# Or manually create: +mkdir -p data/train/trainset_1/0_real/video_00000 +mkdir -p data/train/trainset_1/1_fake/video_00000 +mkdir -p data/val/val_set_1/0_real/video_00000 +mkdir -p data/val/val_set_1/1_fake/video_00000 +``` + +### 2. Add Your Training Data +Place your extracted frames in: +- `data/train/trainset_1/0_real/` - Real video frames +- `data/train/trainset_1/1_fake/` - Fake video frames + +Each video should be in its own directory with frames named sequentially: +``` +data/train/trainset_1/0_real/ +├── video_00000/ +│ ├── 00000.png +│ ├── 00001.png +│ └── ... +├── video_00001/ +│ └── ... +``` + +### 3. Ensure .env File Exists +Your `.env` file should contain your wandb API key: +```bash +WANDB="your_api_key_here" +``` + +### 4. Run with Docker + +#### Using docker-compose (Recommended): +```bash +# GPU version +docker-compose up aigvdet-gpu + +# CPU version +docker-compose up aigvdet-cpu +``` + +#### Using docker run: +```bash +# GPU version +docker run --gpus all \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/.env:/app/.env:ro \ + --env-file .env \ + sacdalance/thesis-aigvdet:gpu \ + python train.py --exp_name my_experiment + +# CPU version +docker run \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/.env:/app/.env:ro \ + --env-file .env \ + sacdalance/thesis-aigvdet:cpu \ + python train.py --exp_name my_experiment +``` + +#### On Windows PowerShell: +```powershell +# GPU version +docker run --gpus all ` + -v ${PWD}/data:/app/data ` + -v ${PWD}/checkpoints:/app/checkpoints ` + -v ${PWD}/.env:/app/.env:ro ` + --env-file .env ` + sacdalance/thesis-aigvdet:gpu ` + python train.py --exp_name my_experiment + +# CPU version +docker run ` + -v ${PWD}/data:/app/data ` + -v ${PWD}/checkpoints:/app/checkpoints ` + -v ${PWD}/.env:/app/.env:ro ` + --env-file .env ` + sacdalance/thesis-aigvdet:cpu ` + python train.py --exp_name my_experiment +``` + +### 5. Verify Setup Before Training + +Run this to check if data is properly mounted: +```bash +docker run -v $(pwd)/data:/app/data sacdalance/thesis-aigvdet:gpu ls -la /app/data/train/trainset_1/ +``` + +## Troubleshooting + +### Error: "No such file or directory: '/app/data/train/trainset_1'" + +**Solutions:** +1. **Create the directory structure** (see step 1 above) +2. **Verify volume mount** - ensure data directory exists locally +3. **Check permissions** - ensure Docker can read the data directory + +### Error: "WANDB API key not found" + +**Solution:** +- Create `.env` file with: `WANDB="your_api_key_here"` +- Make sure it's mounted: `-v $(pwd)/.env:/app/.env:ro` +- Or pass directly: `-e WANDB_API_KEY=your_api_key` + +### No GPU detected + +**Solutions:** +1. Install nvidia-docker2: + ```bash + sudo apt-get install -y nvidia-docker2 + sudo systemctl restart docker + ``` +2. Use `--gpus all` flag in docker run +3. Or use CPU version instead + +### Permission Denied + +**Solution:** +```bash +# Fix data directory permissions +chmod -R 755 data/ +chmod -R 755 checkpoints/ +``` + +## Data Download Instructions + +See `DATA_SETUP.md` for complete instructions on downloading the training dataset from Baiduyun. + +Quick summary: +1. Download from: https://pan.baidu.com/s/17xmDyFjtcmNsoxmUeImMTQ?pwd=ra95 +2. Extract to `data/train/trainset_1/` +3. Ensure structure matches above format + +## Testing Without Full Dataset + +For quick testing, you can create a minimal dataset: +1. Create a few sample frames (any images will do) +2. Place in `data/train/trainset_1/0_real/video_00000/` +3. Name them `00000.png`, `00001.png`, etc. +4. Copy to `1_fake/` directory as well +5. Run training to verify setup works + +## Training Options + +```bash +# Basic training +python train.py --exp_name my_experiment + +# With custom settings +python train.py \ + --exp_name my_experiment \ + --batch_size 32 \ + --nepoch 50 \ + --lr 0.0001 + +# Continue from checkpoint +python train.py \ + --exp_name my_experiment \ + --continue_train \ + --epoch 10 +``` diff --git a/docker-compose.yml b/docker-compose.yml index 14febbf..7f880ad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,11 +12,14 @@ services: environment: - NVIDIA_VISIBLE_DEVICES=all - CUDA_VISIBLE_DEVICES=0 + env_file: + - .env volumes: - ./data:/app/data - ./checkpoints:/app/checkpoints - ./raft_model:/app/raft_model - ./logs:/app/logs + - ./.env:/app/.env:ro ports: - "6006:6006" shm_size: '8gb' @@ -36,11 +39,14 @@ services: dockerfile: Dockerfile.cpu image: sacdalance/thesis-aigvdet:cpu container_name: aigvdet-cpu + env_file: + - .env volumes: - ./data:/app/data - ./checkpoints:/app/checkpoints - ./raft_model:/app/raft_model - ./logs:/app/logs + - ./.env:/app/.env:ro ports: - "6007:6006" shm_size: '4gb' From 815d63584f25b4215f16f64e3847afd3376ca644 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Mon, 24 Nov 2025 01:19:12 +0800 Subject: [PATCH 22/55] Update docker-compose.yml to use Dockerfile.gpu-alt for GPU service; add environment variables for WANDB and PYTHONPATH; fix command to use python instead of python3.11 --- docker-compose.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7f880ad..e38e3b3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,13 +5,15 @@ services: aigvdet-gpu: build: context: . - dockerfile: Dockerfile.gpu + dockerfile: Dockerfile.gpu-alt image: sacdalance/thesis-aigvdet:gpu container_name: aigvdet-gpu runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=all - CUDA_VISIBLE_DEVICES=0 + - WANDB=${WANDB} + - PYTHONPATH=/app env_file: - .env volumes: @@ -30,7 +32,7 @@ services: - driver: nvidia count: all capabilities: [gpu] - command: python3.11 train.py --gpus 0 --exp_name default_exp + command: python train.py --gpus 0 --exp_name default_exp # CPU service aigvdet-cpu: @@ -39,6 +41,9 @@ services: dockerfile: Dockerfile.cpu image: sacdalance/thesis-aigvdet:cpu container_name: aigvdet-cpu + environment: + - WANDB=${WANDB} + - PYTHONPATH=/app env_file: - .env volumes: From 2bc6c1a4a865e87cbed655a83d126e13dbef1068 Mon Sep 17 00:00:00 2001 From: Kyle Pagunsan Date: Mon, 24 Nov 2025 01:32:10 +0800 Subject: [PATCH 23/55] added unzip --- DOCKER_VM_SETUP.md | 17 ++++++++++++++++- Dockerfile.cpu | 1 + Dockerfile.gpu-alt | 4 +++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/DOCKER_VM_SETUP.md b/DOCKER_VM_SETUP.md index 7014925..f28e112 100644 --- a/DOCKER_VM_SETUP.md +++ b/DOCKER_VM_SETUP.md @@ -2,7 +2,22 @@ ## Quick Start -### 1. Create Data Structure +### 1. Automated Data Deployment (Recommended for Vast.ai) +Since the dataset is large (27GB+), we use a startup script to download it directly to the VM. + +1. **Start your instance** (Vast.ai, AWS, etc.). +2. **Open a terminal** in the container. +3. **Run the download script**: + ```bash + # This will download the dataset from Google Drive and unzip it + chmod +x download_data.sh + ./download_data.sh + ``` + *Note: The script defaults to the project's Google Drive ID. You can pass a different ID if needed: `./download_data.sh `* + +### 2. Manual Data Setup (Local Development) +If you are running locally or want to set up manually: + ```bash # Run the setup script python setup_data_structure.py diff --git a/Dockerfile.cpu b/Dockerfile.cpu index e5444d5..4cb6bcb 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -17,6 +17,7 @@ RUN apt-get update && apt-get install -y \ libxrender-dev \ libgomp1 \ libgl1-mesa-glx \ + unzip \ && rm -rf /var/lib/apt/lists/* # Install uv diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 50e4fa4..15879fc 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -18,6 +18,7 @@ RUN apt-get update && apt-get install -y \ libxrender-dev \ libgomp1 \ libgl1-mesa-glx \ + unzip \ && rm -rf /var/lib/apt/lists/* # Install uv @@ -53,7 +54,8 @@ RUN uv pip install --system \ tqdm \ "blobfile>=1.0.5" \ wandb \ - python-dotenv + python-dotenv \ + gdown # Install torchvision separately (base image has torch but may need torchvision update) RUN pip3 install torchvision==0.15.1+cu117 \ From 94bed71c40a69134cbf8eea27dff2c3cdcdac664 Mon Sep 17 00:00:00 2001 From: Kyle Pagunsan Date: Mon, 24 Nov 2025 01:40:44 +0800 Subject: [PATCH 24/55] feat: Add script to download and extract dataset from Google Drive. --- download_data.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 download_data.sh diff --git a/download_data.sh b/download_data.sh new file mode 100644 index 0000000..f9a4879 --- /dev/null +++ b/download_data.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +# Configuration +# Default File ID provided by user, can be overridden by argument +FILE_ID="${1:-1YO3qRKbWxOYEm86Vy9QlGMjyi5Q_A6m0}" +DATA_DIR="/app/data" +ZIP_FILE="${DATA_DIR}/data.zip" + +echo "Starting data download setup..." + +# 1. Install dependencies if missing +if ! command -v gdown &> /dev/null; then + echo "Installing gdown..." + pip install gdown +fi + +if ! command -v unzip &> /dev/null; then + echo "Installing unzip..." + apt-get update && apt-get install -y unzip +fi + +# 2. Check if data already exists +if [ -d "${DATA_DIR}/train" ]; then + echo "Data directory ${DATA_DIR}/train already exists." + read -p "Do you want to re-download? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Skipping download." + exit 0 + fi +fi + +# 3. Create data directory +mkdir -p "$DATA_DIR" + +# 4. Download file +echo "Downloading data from Google Drive (ID: $FILE_ID)..." +gdown "$FILE_ID" -O "$ZIP_FILE" + +# 5. Extract +echo "Extracting data..." +unzip -o "$ZIP_FILE" -d "$DATA_DIR" + +# 6. Cleanup +echo "Cleaning up zip file..." +rm "$ZIP_FILE" + +echo "Data setup complete!" +ls -F "$DATA_DIR" From 2646387e96dd80ae080dc9e2170e82d071468666 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Mon, 24 Nov 2025 02:50:10 +0800 Subject: [PATCH 25/55] fix: Specify .env file for loading environment variables and correct WANDB API key assignment --- Dockerfile.cpu | 2 +- Dockerfile.gpu-alt | 3 ++- train.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile.cpu b/Dockerfile.cpu index 4cb6bcb..5c4afe4 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -32,7 +32,7 @@ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ COPY train.py test.py demo.py ./ -COPY train.sh test.sh demo.sh ./ +COPY train.sh test.sh demo.sh download_data.sh ./ # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 15879fc..43e5b29 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -33,7 +33,8 @@ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ COPY train.py test.py demo.py ./ -COPY train.sh test.sh demo.sh ./ +COPY train.sh test.sh demo.sh download_data.sh ./ + # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model diff --git a/train.py b/train.py index bb68523..a4e0444 100644 --- a/train.py +++ b/train.py @@ -18,12 +18,12 @@ ssl._create_default_https_context = ssl._create_unverified_context # Load environment variables from .env file -load_dotenv() +load_dotenv('thesis.env') # Set wandb API key from environment wandb_api_key = os.getenv("WANDB") if wandb_api_key: - os.environ["WANDB_API_KEY"] = wandb_api_key + os.environ["WANDB"] = wandb_api_key print(f"✓ Loaded wandb API key from .env") else: print("⚠️ Warning: WANDB API key not found in .env file") From 1ee6796b0427e785dcb7da8b2126ecc4097d95b3 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:07:57 +0800 Subject: [PATCH 26/55] fix: Update configuration parameters for training; adjust loadSize and cropSize, and modify num_workers for optimal performance --- core/utils1/config.py | 6 +++--- train.py | 34 ++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index 81fac4f..48a5f71 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -15,10 +15,10 @@ class DefaultConfigs(ABC): mode = "binary" class_bal = False batch_size = 64 # RTX 3090 24GB can handle original batch size - loadSize = 256 - cropSize = 224 + loadSize = 448 + cropSize = 448 epoch = "latest" - num_workers = 16 # Increased for faster data loading on 24GB GPU + num_workers = 8 # Increased for faster data loading on 24GB GPU serial_batches = False isTrain = True diff --git a/train.py b/train.py index a4e0444..cb0e3a6 100644 --- a/train.py +++ b/train.py @@ -69,6 +69,9 @@ early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.001, verbose=True) print(f"✓ Model ready (Architecture: {cfg.arch})") + # Track best model accuracy + best_acc = 0.0 + print("\n" + "=" * 60) print(f"Starting training for {cfg.nepoch} epochs") print("=" * 60 + "\n") @@ -128,19 +131,30 @@ "epoch": epoch }) - # Save best checkpoint as wandb artifact - if os.path.exists(os.path.join(cfg.ckpt_dir, "model_epoch_best.pth")): - artifact = wandb.Artifact( - name=f"{cfg.exp_name}_best_model", - type="model", - description=f"Best model checkpoint at epoch {epoch}" - ) - artifact.add_file(os.path.join(cfg.ckpt_dir, "model_epoch_best.pth")) - wandb.log_artifact(artifact) - print(f"✓ Validation Results - AP: {val_results['AP']:.4f} | ACC: {val_results['ACC']:.4f} | AUC: {val_results['AUC']:.4f}") log.write(f"(Val @ epoch {epoch}) AP: {val_results['AP']}; ACC: {val_results['ACC']}\n") + # Save best model if accuracy improves + if val_results['ACC'] > best_acc: + print(f"⭐ New best model! (ACC: {best_acc:.4f} -> {val_results['ACC']:.4f})") + best_acc = val_results['ACC'] + trainer.save_networks("best") + + best_model_path = os.path.join(cfg.ckpt_dir, "model_epoch_best.pth") + if os.path.exists(best_model_path): + # 1. Log as Artifact + artifact = wandb.Artifact( + name=f"{cfg.exp_name}_best_model", + type="model", + description=f"Best model checkpoint at epoch {epoch} (ACC: {best_acc:.4f})" + ) + artifact.add_file(best_model_path) + wandb.log_artifact(artifact) + + # 2. Force upload to Files tab immediately + wandb.save(best_model_path, base_path=cfg.root_dir, policy="now") + print(f"✓ Uploaded best model to WandB Files: {best_model_path}") + if cfg.earlystop: early_stopping(val_results["ACC"], trainer) if early_stopping.early_stop: From d30ac612c78c5935f447f604be074a59a8f15642 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:34:27 +0800 Subject: [PATCH 27/55] fix: Update configuration parameters for image processing; adjust loadSize, blur_prob, and nepoch for improved performance and alignment with research paper --- core/utils1/config.py | 21 ++++++++++++--------- core/utils1/datasets.py | 26 ++++++++++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index 48a5f71..6db7e0f 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -15,20 +15,21 @@ class DefaultConfigs(ABC): mode = "binary" class_bal = False batch_size = 64 # RTX 3090 24GB can handle original batch size - loadSize = 448 + loadSize = 512 # Resizes the image, this is used as it is standard for HIGH RES CNNs and must have margin before cropping cropSize = 448 epoch = "latest" - num_workers = 8 # Increased for faster data loading on 24GB GPU + num_workers = 8 # Increased for faster data loading on 24GB GPU (choose 8 or 16) - doesnt affect accuracy but only throughput serial_batches = False isTrain = True - # data augmentation + # data augmentation - to match the paper's augmentation rate (10%), set blur and jpg to 0.5 rz_interp = ["bilinear"] - # blur_prob = 0.0 - blur_prob = 0.1 + # blur_prob = 0.1 - resnet50 template + blur_prob = 0.05 blur_sig = [0.5] - # jpg_prob = 0.0 - jpg_prob = 0.1 + # jpg_prob = 0.1 - resnet50 template + jpg_prob = 0.05 + # P(augmented) = 1-(1-0.5)(1-0.05) = 0.975 jpg_method = ["cv2"] jpg_qual = [75] gray_prob = 0.0 @@ -41,7 +42,8 @@ class DefaultConfigs(ABC): warmup = False # warmup = True warmup_epoch = 3 - earlystop = True + # earlystop = True - resnet50 template, training ends only when lr reaches 1e-6 which trainer already supports (Trainer.adjust_learning_rate(min_lr=1e-6)) + earlystop = False earlystop_epoch = 5 optim = "adam" new_optim = False @@ -51,7 +53,8 @@ class DefaultConfigs(ABC): continue_train = False epoch_count = 1 last_epoch = -1 - nepoch = 100 + # nepoch = 100, try + nepoch = 400 beta1 = 0.9 lr = 0.0001 init_type = "normal" diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index 72d4654..ec6bd4e 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -27,20 +27,30 @@ def dataset_folder(root: str, cfg: CONFIGCLASS): def binary_dataset(root: str, cfg: CONFIGCLASS): + # identity_transform = transforms.Lambda(lambda img: img) + # rz_func = identity_transform + # issue here, destroys performance as no resizing happens at all that go straight to cropping even if they are different resolutions + identity_transform = transforms.Lambda(lambda img: img) - - rz_func = identity_transform - + + # Enable resize (paper implies resize > crop for random cropping) + if cfg.aug_resize: + rz_func = transforms.Lambda(lambda img: custom_resize(img, cfg)) + else: + rz_func = identity_transform + + # Crop to cfg.cropSize (paper uses 448) if cfg.isTrain: - crop_func = transforms.RandomCrop((448,448)) + crop_func = transforms.RandomCrop((cfg.cropSize, cfg.cropSize)) else: - crop_func = transforms.CenterCrop((448,448)) if cfg.aug_crop else identity_transform + crop_func = transforms.CenterCrop((cfg.cropSize, cfg.cropSize)) if cfg.aug_crop else identity_transform + # Flip only in training if enabled if cfg.isTrain and cfg.aug_flip: flip_func = transforms.RandomHorizontalFlip() else: - flip_func = identity_transform - + flip_func = identity_transform # fallback + return datasets.ImageFolder( root, @@ -48,8 +58,8 @@ def binary_dataset(root: str, cfg: CONFIGCLASS): [ rz_func, #change - transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), crop_func, + transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), flip_func, transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) From 89656a8aa58ecab1b22af6bee99df7061f08fe07 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 00:43:49 +0800 Subject: [PATCH 28/55] feat: Add data preparation and evaluation script for dataset processing; includes frame extraction and optical flow generation --- prepare_data.py | 141 ++++++++++++++++++++++++++++++++++++++++++++ recreate_table_2.py | 63 ++++++++++++++++++++ test.py | 64 +++++++++++++++----- 3 files changed, 253 insertions(+), 15 deletions(-) create mode 100644 prepare_data.py create mode 100644 recreate_table_2.py diff --git a/prepare_data.py b/prepare_data.py new file mode 100644 index 0000000..d96f827 --- /dev/null +++ b/prepare_data.py @@ -0,0 +1,141 @@ +import sys +import argparse +import os +import cv2 +import glob +import numpy as np +import torch +from PIL import Image +from tqdm import tqdm +from natsort import natsorted + +# Add core to path for imports +sys.path.append('core') +from raft import RAFT +from utils import flow_viz +from utils.utils import InputPadder + +DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' + +def load_image(imfile): + img = np.array(Image.open(imfile)).astype(np.uint8) + img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) + +def save_flow(img, flo, output_path): + img = img[0].permute(1,2,0).cpu().numpy() + flo = flo[0].permute(1,2,0).cpu().numpy() + + # map flow to rgb image + flo = flow_viz.flow_to_image(flo) + cv2.imwrite(output_path, flo) + +def video_to_frames(video_path, output_folder): + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # Check if frames already exist to skip + existing_frames = glob.glob(os.path.join(output_folder, "*.png")) + if len(existing_frames) > 0: + return sorted(existing_frames) + + cap = cv2.VideoCapture(video_path) + frame_count = 0 + + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + frame_filename = os.path.join(output_folder, f"frame_{frame_count:05d}.png") + cv2.imwrite(frame_filename, frame) + frame_count += 1 + + cap.release() + + images = glob.glob(os.path.join(output_folder, '*.png')) + \ + glob.glob(os.path.join(output_folder, '*.jpg')) + return sorted(images) + +def process_dataset(args): + # Load RAFT model once + print(f"Loading RAFT model from {args.model}...") + model = torch.nn.DataParallel(RAFT(args)) + model.load_state_dict(torch.load(args.model, map_location=torch.device(DEVICE))) + model = model.module + model.to(DEVICE) + model.eval() + print("✓ RAFT model loaded") + + # Structure: source_dir / [0_real, 1_fake] / video.mp4 + for label in ["0_real", "1_fake"]: + source_label_dir = os.path.join(args.source_dir, label) + if not os.path.exists(source_label_dir): + print(f"Skipping {label}, directory not found: {source_label_dir}") + continue + + videos = glob.glob(os.path.join(source_label_dir, "*.mp4")) + \ + glob.glob(os.path.join(source_label_dir, "*.avi")) + \ + glob.glob(os.path.join(source_label_dir, "*.mov")) + + print(f"Found {len(videos)} videos in {label}") + + for video_path in tqdm(videos, desc=f"Processing {label}"): + video_name = os.path.splitext(os.path.basename(video_path))[0] + + # Define output paths + # Original frames: args.output_rgb_dir / label / video_name / frames + rgb_out_dir = os.path.join(args.output_rgb_dir, label, video_name) + # Optical flow: args.output_flow_dir / label / video_name / frames + flow_out_dir = os.path.join(args.output_flow_dir, label, video_name) + + if not os.path.exists(flow_out_dir): + os.makedirs(flow_out_dir) + + # 1. Extract Frames + images = video_to_frames(video_path, rgb_out_dir) + images = natsorted(images) + + if len(images) < 2: + continue + + # 2. Generate Optical Flow + # Check if flow already exists + existing_flow = glob.glob(os.path.join(flow_out_dir, "*.png")) + if len(existing_flow) >= len(images) - 1: + continue + + with torch.no_grad(): + for i, (imfile1, imfile2) in enumerate(zip(images[:-1], images[1:])): + # Output filename matches input filename + flow_filename = os.path.basename(imfile1) + flow_output_path = os.path.join(flow_out_dir, flow_filename) + + if os.path.exists(flow_output_path): + continue + + image1 = load_image(imfile1) + image2 = load_image(imfile2) + + padder = InputPadder(image1.shape) + image1, image2 = padder.pad(image1, image2) + + flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) + + save_flow(image1, flow_up, flow_output_path) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--source_dir', required=True, help="Path to folder containing 0_real/1_fake video folders") + parser.add_argument('--output_rgb_dir', required=True, help="Output path for RGB frames") + parser.add_argument('--output_flow_dir', required=True, help="Output path for Optical Flow frames") + parser.add_argument('--model', default="raft_model/raft-things.pth", help="Path to RAFT model checkpoint") + + # RAFT args + parser.add_argument('--small', action='store_true', help='use small model') + parser.add_argument('--mixed_precision', action='store_true', help='use mixed precision') + parser.add_argument('--alternate_corr', action='store_true', help='use efficent correlation implementation') + + args = parser.parse_args() + + process_dataset(args) diff --git a/recreate_table_2.py b/recreate_table_2.py new file mode 100644 index 0000000..61f26aa --- /dev/null +++ b/recreate_table_2.py @@ -0,0 +1,63 @@ +import argparse +import subprocess +import os +import sys + +def run_command(command): + print(f"Running: {command}") + try: + subprocess.check_call(command, shell=True) + except subprocess.CalledProcessError as e: + print(f"Error running command: {command}") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description="Recreate Table 2 results for a specific dataset.") + parser.add_argument("dataset", help="Name of the dataset (e.g., moonvalley, videocraft, pika, neverends)") + args = parser.parse_args() + + dataset_name = args.dataset + + # Define paths + # Using /app/data for Docker/Jupyter environment compatibility + source_video_dir = f"/app/data/test/T2V/{dataset_name}" + output_rgb_dir = f"/app/data/test/original/T2V/{dataset_name}" + output_flow_dir = f"/app/data/test/T2V/{dataset_name}_flow" + + result_csv = f"/app/data/results/{dataset_name}.csv" + result_no_cp_csv = f"/app/data/results/{dataset_name}_no_cp.csv" + + raft_model = "/app/raft_model/raft-things.pth" + optical_model = "/app/checkpoints/optical.pth" + original_model = "/app/checkpoints/original.pth" + + print(f"--- Processing Dataset: {dataset_name} ---") + + # Check if source directory exists + if not os.path.exists(source_video_dir): + print(f"Error: Source video directory not found: {source_video_dir}") + print("Please download the test videos and place them in the correct folder.") + sys.exit(1) + + # Step 1: Prepare Data + print("\n[Step 1] Preparing Data (Extracting Frames & Generating Optical Flow)...") + # Note: We use sys.executable to ensure we use the same python interpreter + cmd_prepare = f'"{sys.executable}" prepare_data.py --source_dir "{source_video_dir}" --output_rgb_dir "{output_rgb_dir}" --output_flow_dir "{output_flow_dir}" --model "{raft_model}"' + run_command(cmd_prepare) + + # Step 2: Run Standard Evaluation + print("\n[Step 2] Running Standard Evaluation (AIGVDet, Sspatial, Soptical)...") + cmd_test = f'"{sys.executable}" test.py -fop "{output_flow_dir}" -for "{output_rgb_dir}" -mop "{optical_model}" -mor "{original_model}" -e "{result_csv}"' + run_command(cmd_test) + + # Step 3: Run No-Crop Evaluation + print("\n[Step 3] Running No-Crop Evaluation (Soptical no cp)...") + cmd_test_no_cp = f'"{sys.executable}" test.py --no_crop -fop "{output_flow_dir}" -for "{output_rgb_dir}" -mop "{optical_model}" -mor "{original_model}" -e "{result_no_cp_csv}"' + run_command(cmd_test_no_cp) + + print("\n--- Done! ---") + print(f"Standard Results saved to: {result_csv}") + print(f"No-Crop Results saved to: {result_no_cp_csv}") + +if __name__ == "__main__": + main() diff --git a/test.py b/test.py index e25e90b..18a8e61 100644 --- a/test.py +++ b/test.py @@ -64,6 +64,7 @@ parser.add_argument("--use_cpu", action="store_true", help="uses gpu by default, turn on to use cpu") parser.add_argument("--arch", type=str, default="resnet50") parser.add_argument("--aug_norm", type=str2bool, default=True) + parser.add_argument("--no_crop", action="store_true", help="disable center crop") args = parser.parse_args() subfolder_count = 0 @@ -88,12 +89,19 @@ model_or.cuda() - trans = transforms.Compose( - ( - transforms.CenterCrop((448,448)), - transforms.ToTensor(), + if args.no_crop: + trans = transforms.Compose( + ( + transforms.ToTensor(), + ) + ) + else: + trans = transforms.Compose( + ( + transforms.CenterCrop((448,448)), + transforms.ToTensor(), + ) ) - ) print("*" * 50) @@ -104,6 +112,8 @@ tn=0 y_true=[] y_pred=[] + y_pred_original=[] + y_pred_optical=[] # create an empty DataFrame df = pd.DataFrame(columns=['name', 'pro','flag','optical_pro','original_pro']) @@ -130,7 +140,8 @@ print("test subfolder:", subfolder_name) # Traverse through sub-subfolders within a subfolder. - for subsubfolder_name in os.listdir(original_subfolder_path): + video_list = os.listdir(original_subfolder_path) + for subsubfolder_name in tqdm(video_list, desc=f"Testing {subfolder_name}"): original_subsubfolder_path = os.path.join(original_subfolder_path, subsubfolder_name) optical_subsubfolder_path = os.path.join(optical_subfolder_path, subsubfolder_name) if os.path.isdir(optical_subsubfolder_path): @@ -145,7 +156,8 @@ original_file_list = sorted(glob.glob(os.path.join(original_subsubfolder_path, "*.jpg")) + glob.glob(os.path.join(original_subsubfolder_path, "*.png"))+glob.glob(os.path.join(original_subsubfolder_path, "*.JPEG"))) original_prob_sum=0 - for img_path in tqdm(original_file_list, dynamic_ncols=True, disable=len(original_file_list) <= 1): + # Inner loop for frames - disable tqdm to avoid nested clutter + for img_path in original_file_list: img = Image.open(img_path).convert("RGB") img = trans(img) @@ -163,12 +175,13 @@ original_predict=original_prob_sum/len(original_file_list) - print("original prob",original_predict) + # print("original prob",original_predict) #Detect optical flow optical_file_list = sorted(glob.glob(os.path.join(optical_subsubfolder_path, "*.jpg")) + glob.glob(os.path.join(optical_subsubfolder_path, "*.png"))+glob.glob(os.path.join(optical_subsubfolder_path, "*.JPEG"))) optical_prob_sum=0 - for img_path in tqdm(optical_file_list, dynamic_ncols=True, disable=len(original_file_list) <= 1): + # Inner loop for frames - disable tqdm + for img_path in optical_file_list: img = Image.open(img_path).convert("RGB") img = trans(img) @@ -188,13 +201,15 @@ index1=index1+1 optical_predict=optical_prob_sum/len(optical_file_list) - print("optical prob",optical_predict) + # print("optical prob",optical_predict) predict=original_predict*0.5+optical_predict*0.5 - print(f"flag:{flag} predict:{predict}") + # print(f"flag:{flag} predict:{predict}") # y_true.append((float)(flag)) y_true.append((flag)) y_pred.append(predict) + y_pred_original.append(original_predict) + y_pred_optical.append(optical_predict) if flag==0: n+=1 if predict= args.threshold else 0 for p in y_pred]) + + # Calculate metrics for individual streams + acc_original = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred_original]) + auc_original = roc_auc_score(y_true, y_pred_original) + + acc_optical = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred_optical]) + auc_optical = roc_auc_score(y_true, y_pred_optical) + + print("-" * 30) + print("AIGVDet (Fused) Results:") print(f"tnr:{tn/n}") - # print(f"f_acc:{f_acc}") print(f"tpr:{tp/p}") - print(f"acc:{(tp+tn)/(p+n)}") - # print(f"acc:{acc}") + print(f"acc:{acc}") print(f"ap:{ap}") print(f"auc:{auc}") + + print("-" * 30) + print("Sspatial (Original RGB) Results:") + print(f"acc:{acc_original}") + print(f"auc:{auc_original}") + + print("-" * 30) + print("Soptical (Optical Flow) Results:") + print(f"acc:{acc_optical}") + print(f"auc:{auc_optical}") + print("-" * 30) print(f"p:{p}") print(f"n:{n}") print(f"tp:{tp}") From 51a45062e32c53d1ed4e10479730dc4834faaf54 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 00:47:53 +0800 Subject: [PATCH 29/55] fix: Update configuration parameters for data augmentation and training; adjust jpg_prob, earlystop, save_epoch_freq, and nepoch for improved training dynamics --- core/utils1/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index 6db7e0f..07af9b5 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -29,7 +29,7 @@ class DefaultConfigs(ABC): blur_sig = [0.5] # jpg_prob = 0.1 - resnet50 template jpg_prob = 0.05 - # P(augmented) = 1-(1-0.5)(1-0.05) = 0.975 + # P(augmented) = 1-(1-0.5)(1-0.05) = 0.9 jpg_method = ["cv2"] jpg_qual = [75] gray_prob = 0.0 @@ -43,18 +43,18 @@ class DefaultConfigs(ABC): # warmup = True warmup_epoch = 3 # earlystop = True - resnet50 template, training ends only when lr reaches 1e-6 which trainer already supports (Trainer.adjust_learning_rate(min_lr=1e-6)) - earlystop = False + earlystop = True earlystop_epoch = 5 optim = "adam" new_optim = False loss_freq = 400 save_latest_freq = 2000 - save_epoch_freq = 20 + save_epoch_freq = 5 continue_train = False epoch_count = 1 last_epoch = -1 # nepoch = 100, try - nepoch = 400 + nepoch = 20 beta1 = 0.9 lr = 0.0001 init_type = "normal" From 821da35ba5fa1958de7973b77c9916fcba1319a9 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:52:49 +0800 Subject: [PATCH 30/55] fix: Update configuration parameters for training; increase num_workers to 16 and nepoch to 50 for improved performance, adjust jpg_qual to a range for better augmentation --- core/utils1/config.py | 8 ++++---- train.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index 07af9b5..a66d379 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -14,11 +14,11 @@ class DefaultConfigs(ABC): datasets_test = ["val_set_1"] mode = "binary" class_bal = False - batch_size = 64 # RTX 3090 24GB can handle original batch size + batch_size = 64 # RTX 3090 24GB can handle original batch size loadSize = 512 # Resizes the image, this is used as it is standard for HIGH RES CNNs and must have margin before cropping cropSize = 448 epoch = "latest" - num_workers = 8 # Increased for faster data loading on 24GB GPU (choose 8 or 16) - doesnt affect accuracy but only throughput + num_workers = 16 # Increased for faster data loading on 24GB GPU (choose 8 or 16) - doesnt affect accuracy but only throughput serial_batches = False isTrain = True @@ -31,7 +31,7 @@ class DefaultConfigs(ABC): jpg_prob = 0.05 # P(augmented) = 1-(1-0.5)(1-0.05) = 0.9 jpg_method = ["cv2"] - jpg_qual = [75] + jpg_qual = list(range(70, 91)) gray_prob = 0.0 aug_resize = True aug_crop = True @@ -54,7 +54,7 @@ class DefaultConfigs(ABC): epoch_count = 1 last_epoch = -1 # nepoch = 100, try - nepoch = 20 + nepoch = 50 beta1 = 0.9 lr = 0.0001 init_type = "normal" diff --git a/train.py b/train.py index cb0e3a6..6b9c862 100644 --- a/train.py +++ b/train.py @@ -26,7 +26,7 @@ os.environ["WANDB"] = wandb_api_key print(f"✓ Loaded wandb API key from .env") else: - print("⚠️ Warning: WANDB API key not found in .env file") + print("⚠️ Warning: WANDB API key not found in .env file") if __name__ == "__main__": @@ -164,7 +164,7 @@ wandb.log({"early_stopping/lr_dropped": True, "epoch": epoch}) early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.002, verbose=True) else: - print("\n⏹️ Early stopping triggered") + print("\n⏹️ Early stopping triggered") log.write("Early stopping.\n") wandb.log({"early_stopping/triggered": True, "epoch": epoch}) break From 748248cc34914b3e5a5faad2ee9de91faea21deb Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 08:36:11 +0800 Subject: [PATCH 31/55] fix: Update early stopping and training metrics; remove local model saving from early stopping, define metrics for tracking best values in wandb summary --- core/utils1/earlystop.py | 11 +++-------- train.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/core/utils1/earlystop.py b/core/utils1/earlystop.py index 3112d65..2f0164c 100644 --- a/core/utils1/earlystop.py +++ b/core/utils1/earlystop.py @@ -42,13 +42,8 @@ def __call__(self, score: float, trainer: Trainer): def save_checkpoint(self, score: float, trainer: Trainer): """Saves model when validation loss decrease.""" if self.verbose: - print(f"Validation accuracy increased ({self.score_max:.6f} --> {score:.6f}). Saving model ...") - trainer.save_networks("best") + print(f"Validation accuracy increased ({self.score_max:.6f} --> {score:.6f}).") + # trainer.save_networks("best") - Handled globally in train.py to avoid overwriting with local bests self.score_max = score - # Log best model save to wandb - if wandb.run is not None: - wandb.log({ - "best_model/score": score, - "best_model/improvement": score - self.score_max if self.score_max != -np.Inf else 0 - }) + self.score_max = score diff --git a/train.py b/train.py index 6b9c862..a98a33e 100644 --- a/train.py +++ b/train.py @@ -64,6 +64,11 @@ ) print(f"✓ wandb tracking enabled: {wandb.run.url}") + # Define metrics to track max value in summary table + wandb.define_metric("val/ACC", summary="max") + wandb.define_metric("val/AUC", summary="max") + wandb.define_metric("val/AP", summary="max") + print("\nInitializing model...") trainer = Trainer(cfg) early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.001, verbose=True) @@ -128,6 +133,7 @@ "val/AUC": val_results["AUC"], "val/TPR": val_results["TPR"], "val/TNR": val_results["TNR"], + "val/best_ACC": max(best_acc, val_results["ACC"]), "epoch": epoch }) @@ -137,7 +143,22 @@ # Save best model if accuracy improves if val_results['ACC'] > best_acc: print(f"⭐ New best model! (ACC: {best_acc:.4f} -> {val_results['ACC']:.4f})") + # Calculate improvement + improvement = val_results['ACC'] - best_acc best_acc = val_results['ACC'] + + # Explicitly update summary for the table with ALL metrics from this best epoch + wandb.run.summary["best_acc_score"] = best_acc + wandb.run.summary["best_epoch"] = epoch + wandb.run.summary["best_AUC"] = val_results["AUC"] + wandb.run.summary["best_AP"] = val_results["AP"] + wandb.run.summary["best_TPR"] = val_results["TPR"] + wandb.run.summary["best_TNR"] = val_results["TNR"] + + # Force update the specific columns you want in the table + wandb.run.summary["best_model/score"] = best_acc + wandb.run.summary["best_model/improvement"] = improvement + trainer.save_networks("best") best_model_path = os.path.join(cfg.ckpt_dir, "model_epoch_best.pth") From 9117dc38d9f66f10bb2bee95155f564953f5c357 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 08:37:14 +0800 Subject: [PATCH 32/55] fix: Remove redundant assignment of score_max in EarlyStopping class --- core/utils1/earlystop.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/utils1/earlystop.py b/core/utils1/earlystop.py index 2f0164c..3fe8b50 100644 --- a/core/utils1/earlystop.py +++ b/core/utils1/earlystop.py @@ -44,6 +44,4 @@ def save_checkpoint(self, score: float, trainer: Trainer): if self.verbose: print(f"Validation accuracy increased ({self.score_max:.6f} --> {score:.6f}).") # trainer.save_networks("best") - Handled globally in train.py to avoid overwriting with local bests - self.score_max = score - - self.score_max = score + self.score_max = score From b70220ccc31925acc33a80cfb5ce316313160524 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 08:40:15 +0800 Subject: [PATCH 33/55] fix: Remove redundant metric definitions and summary updates for wandb tracking --- train.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/train.py b/train.py index a98a33e..03c7839 100644 --- a/train.py +++ b/train.py @@ -64,11 +64,6 @@ ) print(f"✓ wandb tracking enabled: {wandb.run.url}") - # Define metrics to track max value in summary table - wandb.define_metric("val/ACC", summary="max") - wandb.define_metric("val/AUC", summary="max") - wandb.define_metric("val/AP", summary="max") - print("\nInitializing model...") trainer = Trainer(cfg) early_stopping = EarlyStopping(patience=cfg.earlystop_epoch, delta=-0.001, verbose=True) @@ -148,7 +143,6 @@ best_acc = val_results['ACC'] # Explicitly update summary for the table with ALL metrics from this best epoch - wandb.run.summary["best_acc_score"] = best_acc wandb.run.summary["best_epoch"] = epoch wandb.run.summary["best_AUC"] = val_results["AUC"] wandb.run.summary["best_AP"] = val_results["AP"] From 2e595391e1f714b86e8c3ae1b23ee4deff453e81 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:45:07 +0800 Subject: [PATCH 34/55] fix: Update import statement for Trainer class to clarify usage context --- core/utils1/trainer_optical.py | 169 +++++++++++++++++++++++++++++++++ train.py | 2 +- 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 core/utils1/trainer_optical.py diff --git a/core/utils1/trainer_optical.py b/core/utils1/trainer_optical.py new file mode 100644 index 0000000..7a37fb3 --- /dev/null +++ b/core/utils1/trainer_optical.py @@ -0,0 +1,169 @@ +import os + +import torch +import torch.nn as nn +from torch.nn import init + +from core.utils1.config import CONFIGCLASS +from core.utils1.utils import get_network +from core.utils1.warmup import GradualWarmupScheduler + + +class BaseModel(nn.Module): + def __init__(self, cfg: CONFIGCLASS): + super().__init__() + self.cfg = cfg + self.total_steps = 0 + self.isTrain = cfg.isTrain + self.save_dir = cfg.ckpt_dir + self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") + self.model:nn.Module + # self.model.to(self.device) + #self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) + self.optimizer: torch.optim.Optimizer + + def save_networks(self, epoch: int): + save_filename = f"model_epoch_{epoch}.pth" + save_path = os.path.join(self.save_dir, save_filename) + + # serialize model and optimizer to dict + state_dict = { + "model": self.model.state_dict(), + "optimizer": self.optimizer.state_dict(), + "total_steps": self.total_steps, + } + + torch.save(state_dict, save_path) + + # load models from the disk + def load_networks(self, epoch: int): + load_filename = f"model_epoch_{epoch}.pth" + load_path = os.path.join(self.save_dir, load_filename) + + if epoch==0: + # load_filename = f"lsun_adm.pth" + load_path="checkpoints/optical.pth" + print("loading optical path") + else : + print(f"loading the model from {load_path}") + + # print(f"loading the model from {load_path}") + + # if you are using PyTorch newer than 0.4 (e.g., built from + # GitHub source), you can remove str() on self.device + state_dict = torch.load(load_path, map_location=self.device) + if hasattr(state_dict, "_metadata"): + del state_dict._metadata + + self.model.load_state_dict(state_dict["model"]) + self.total_steps = state_dict["total_steps"] + + if self.isTrain and not self.cfg.new_optim: + self.optimizer.load_state_dict(state_dict["optimizer"]) + # move optimizer state to GPU + for state in self.optimizer.state.values(): + for k, v in state.items(): + if torch.is_tensor(v): + state[k] = v.to(self.device) + + for g in self.optimizer.param_groups: + g["lr"] = self.cfg.lr + + def eval(self): + self.model.eval() + + def test(self): + with torch.no_grad(): + self.forward() + + +def init_weights(net: nn.Module, init_type="normal", gain=0.02): + def init_func(m: nn.Module): + classname = m.__class__.__name__ + if hasattr(m, "weight") and (classname.find("Conv") != -1 or classname.find("Linear") != -1): + if init_type == "normal": + init.normal_(m.weight.data, 0.0, gain) + elif init_type == "xavier": + init.xavier_normal_(m.weight.data, gain=gain) + elif init_type == "kaiming": + init.kaiming_normal_(m.weight.data, a=0, mode="fan_in") + elif init_type == "orthogonal": + init.orthogonal_(m.weight.data, gain=gain) + else: + raise NotImplementedError(f"initialization method [{init_type}] is not implemented") + if hasattr(m, "bias") and m.bias is not None: + init.constant_(m.bias.data, 0.0) + elif classname.find("BatchNorm2d") != -1: + init.normal_(m.weight.data, 1.0, gain) + init.constant_(m.bias.data, 0.0) + + print(f"initialize network with {init_type}") + net.apply(init_func) + + +class Trainer(BaseModel): + def name(self): + return "Trainer" + + def __init__(self, cfg: CONFIGCLASS): + super().__init__(cfg) + self.arch = cfg.arch + self.model = get_network(self.arch, cfg.isTrain, cfg.continue_train, cfg.init_gain, cfg.pretrained) + + self.loss_fn = nn.BCEWithLogitsLoss() + # initialize optimizers + if cfg.optim == "adam": + self.optimizer = torch.optim.Adam(self.model.parameters(), lr=cfg.lr, betas=(cfg.beta1, 0.999)) + elif cfg.optim == "sgd": + self.optimizer = torch.optim.SGD(self.model.parameters(), lr=cfg.lr, momentum=0.9, weight_decay=5e-4) + else: + raise ValueError("optim should be [adam, sgd]") + if cfg.warmup: + scheduler_cosine = torch.optim.lr_scheduler.CosineAnnealingLR( + self.optimizer, cfg.nepoch - cfg.warmup_epoch, eta_min=1e-6 + ) + self.scheduler = GradualWarmupScheduler( + self.optimizer, multiplier=1, total_epoch=cfg.warmup_epoch, after_scheduler=scheduler_cosine + ) + self.scheduler.step() + if cfg.continue_train: + self.load_networks(cfg.epoch) + self.model.to(self.device) + + # OPTICAL FLOW: Force load pretrained optical.pth checkpoint + print("Loading pretrained optical flow checkpoint...") + load_path = 'checkpoints/optical.pth' + state_dict = torch.load(load_path, map_location=self.device) + self.model.load_state_dict(state_dict["model"]) + print(f"✓ Loaded optical flow checkpoint from {load_path}") + + + + def adjust_learning_rate(self, min_lr=1e-6): + for param_group in self.optimizer.param_groups: + param_group["lr"] /= 10.0 + if param_group["lr"] < min_lr: + return False + return True + + def set_input(self, input): + img, label, meta = input if len(input) == 3 else (input[0], input[1], {}) + self.input = img.to(self.device) + self.label = label.to(self.device).float() + for k in meta.keys(): + if isinstance(meta[k], torch.Tensor): + meta[k] = meta[k].to(self.device) + self.meta = meta + + def forward(self): + self.output = self.model(self.input, self.meta) + + def get_loss(self): + return self.loss_fn(self.output.squeeze(1), self.label) + + def optimize_parameters(self): + self.forward() + self.loss = self.loss_fn(self.output.squeeze(1), self.label) + self.optimizer.zero_grad() + self.loss.backward() + self.optimizer.step() diff --git a/train.py b/train.py index 03c7839..055d5f3 100644 --- a/train.py +++ b/train.py @@ -11,7 +11,7 @@ from core.utils1.datasets import create_dataloader from core.utils1.earlystop import EarlyStopping from core.utils1.eval import get_val_cfg, validate -from core.utils1.trainer import Trainer +from core.utils1.trainer import Trainer # change this if optical or rgb from core.utils1.utils import Logger import ssl From 77a465e94352d3e744f7042d45e9aeef316945c2 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:09:34 +0800 Subject: [PATCH 35/55] fix: Update Dockerfile and add download_data.py script for improved data management --- DOWNLOAD_GUIDE.md | 162 +++++++++++++++++++++++++ Dockerfile.cpu | 5 +- download_data.py | 303 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 467 insertions(+), 3 deletions(-) create mode 100644 DOWNLOAD_GUIDE.md create mode 100644 download_data.py diff --git a/DOWNLOAD_GUIDE.md b/DOWNLOAD_GUIDE.md new file mode 100644 index 0000000..9d07aa2 --- /dev/null +++ b/DOWNLOAD_GUIDE.md @@ -0,0 +1,162 @@ +# Download Data Script - Usage Guide + +This Python script downloads all necessary files for AIGVDet: +- **Dataset** (training/validation data) +- **Checkpoints** (original.pth and optical.pth) +- **RAFT Model** (for optical flow computation) + +## Quick Start + +### Download Everything +```bash +cd AIGVDet +python download_data.py +``` + +This will download: +- Dataset → `data/` directory +- Checkpoints → `checkpoints/` directory (contains original.pth, optical.pth) +- RAFT model → `raft_model/` directory + +## Advanced Usage + +### Download Specific Components + +**Only dataset:** +```bash +python download_data.py --skip-checkpoints --skip-raft +``` + +**Only checkpoints:** +```bash +python download_data.py --skip-data --skip-raft +``` + +**Only RAFT model:** +```bash +python download_data.py --skip-data --skip-checkpoints +``` + +### Custom Directories + +```bash +python download_data.py \ + --data-dir /path/to/data \ + --checkpoint-dir /path/to/checkpoints \ + --raft-dir /path/to/raft_model +``` + +### Custom Google Drive IDs + +```bash +python download_data.py \ + --data-id YOUR_FILE_ID \ + --checkpoint-folder https://drive.google.com/drive/folders/YOUR_FOLDER_ID \ + --raft-file https://drive.google.com/file/d/YOUR_FILE_ID/view +``` + +## What Gets Downloaded + +### 1. Dataset (data/) +- Training and validation data +- Extracted from a zip file +- Default ID: `1YO3qRKbWxOYEm86Vy9QlGMjyi5Q_A6m0` + +### 2. Checkpoints (checkpoints/) +- `original.pth` - Pre-trained RGB model +- `optical.pth` - Pre-trained optical flow model +- Folder URL: `https://drive.google.com/drive/folders/18JO_YxOEqwJYfbVvy308XjoV-N6fE4yP` + +### 3. RAFT Model (raft_model/) +- `raft_things.pth` - Pre-trained RAFT model for optical flow computation +- File URL: `https://drive.google.com/file/d/1MqDajR89k-xLV0HIrmJ0k-n8ZpG6_suM/view` + +## Dependencies + +The script will automatically install `gdown` if not present: +```bash +pip install gdown +``` + +Or install manually: +```bash +pip install gdown +``` + +## Features + +✅ **Auto-install dependencies** - Installs gdown if missing +✅ **Check existing files** - Asks before re-downloading +✅ **Progress indicators** - Shows download progress +✅ **Auto-extract** - Extracts zip files automatically +✅ **Cleanup** - Removes zip files after extraction +✅ **Detailed output** - Shows file sizes and directory contents +✅ **Error handling** - Gracefully handles download failures + +## Troubleshooting + +### gdown installation fails +```bash +python -m pip install --upgrade pip +pip install gdown +``` + +### Authentication errors +Some Google Drive files may require authentication. If you get quota errors: +1. Wait a few hours and try again +2. Download manually from the links above +3. Place files in the correct directories + +### Download fails mid-way +Re-run the script - it will ask if you want to re-download existing files. + +## File Structure After Download + +``` +AIGVDet/ +├── data/ +│ ├── train/ +│ └── val/ +├── checkpoints/ +│ ├── original.pth +│ └── optical.pth +└── raft_model/ + └── raft_things.pth +``` + +## Command Line Options + +``` +usage: download_data.py [-h] [--data-id DATA_ID] [--data-dir DATA_DIR] + [--checkpoint-dir CHECKPOINT_DIR] + [--checkpoint-folder CHECKPOINT_FOLDER] + [--raft-dir RAFT_DIR] [--raft-file RAFT_FILE] + [--skip-data] [--skip-checkpoints] [--skip-raft] + +optional arguments: + -h, --help show this help message and exit + --data-id DATA_ID Google Drive file ID for dataset + --data-dir DATA_DIR Directory to save dataset (default: data) + --checkpoint-dir CHECKPOINT_DIR + Directory to save checkpoints (default: checkpoints) + --checkpoint-folder CHECKPOINT_FOLDER + Google Drive folder URL for checkpoints + --raft-dir RAFT_DIR Directory to save RAFT model (default: raft_model) + --raft-file RAFT_FILE + Google Drive file URL for RAFT model + --skip-data Skip dataset download + --skip-checkpoints Skip checkpoint download + --skip-raft Skip RAFT model download +``` + +## Original Shell Script + +The original `download_data.sh` is still available if you prefer to use bash: +```bash +bash download_data.sh [FILE_ID] +``` + +However, the Python version is recommended as it: +- Works on Windows, Linux, and macOS +- Downloads checkpoints and RAFT model automatically +- Has better error handling and progress indicators diff --git a/Dockerfile.cpu b/Dockerfile.cpu index 5c4afe4..090da6a 100644 --- a/Dockerfile.cpu +++ b/Dockerfile.cpu @@ -31,11 +31,10 @@ COPY pyproject.toml ./ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ -COPY train.py test.py demo.py ./ -COPY train.sh test.sh demo.sh download_data.sh ./ +COPY train.py test.py demo.py download_data.py ./ # Create directories for data and checkpoints -RUN mkdir -p /app/data /app/checkpoints /app/raft_model +RUN mkdir -p /app/data /app/checkpoints /app/raft_model # Install other dependencies first RUN uv pip install --system \ diff --git a/download_data.py b/download_data.py new file mode 100644 index 0000000..f3369c9 --- /dev/null +++ b/download_data.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +""" +Download script for AIGVDet data, checkpoints, and RAFT model. +Converts the original download_data.sh to Python with additional downloads. + +Usage: + python download_data.py [--data-id FILE_ID] [--skip-data] [--skip-checkpoints] [--skip-raft] +""" + +import os +import sys +import argparse +import subprocess +import zipfile +from pathlib import Path + + +def check_and_install_gdown(): + """Check if gdown is installed, install if missing.""" + try: + import gdown + print("✓ gdown is already installed") + return True + except ImportError: + print("Installing gdown...") + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "gdown"]) + print("✓ gdown installed successfully") + return True + except subprocess.CalledProcessError as e: + print(f"✗ Failed to install gdown: {e}") + return False + + +def ask_redownload(path_name): + """Ask user if they want to re-download existing data.""" + response = input(f"Do you want to re-download? (y/N): ").strip().lower() + return response in ['y', 'yes'] + + +def download_from_drive(url_or_id, output_path, is_folder=False): + """ + Download file or folder from Google Drive using gdown. + + Args: + url_or_id: Google Drive URL or file ID + output_path: Path where to save the downloaded file/folder + is_folder: True if downloading a folder, False for single file + """ + import gdown + + try: + if is_folder: + # Extract folder ID from URL if needed + if 'folders/' in url_or_id: + folder_id = url_or_id.split('folders/')[-1].split('?')[0] + else: + folder_id = url_or_id + + print(f"Downloading folder (ID: {folder_id})...") + gdown.download_folder(id=folder_id, output=str(output_path), quiet=False) + else: + # Extract file ID from URL if needed + if 'drive.google.com' in url_or_id: + if '/file/d/' in url_or_id: + file_id = url_or_id.split('/file/d/')[-1].split('/')[0] + elif 'id=' in url_or_id: + file_id = url_or_id.split('id=')[-1].split('&')[0] + else: + file_id = url_or_id + else: + file_id = url_or_id + + print(f"Downloading file (ID: {file_id})...") + gdown.download(id=file_id, output=str(output_path), quiet=False) + + return True + except Exception as e: + print(f"✗ Download failed: {e}") + return False + + +def extract_zip(zip_path, extract_to): + """Extract a zip file.""" + print(f"Extracting {zip_path.name}...") + try: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + print(f"✓ Extracted to {extract_to}") + return True + except Exception as e: + print(f"✗ Extraction failed: {e}") + return False + + +def download_dataset(data_dir, file_id): + """Download and extract the main dataset.""" + print("\n" + "="*60) + print("DOWNLOADING DATASET") + print("="*60) + + data_dir = Path(data_dir) + zip_file = data_dir / "data.zip" + + # Check if data already exists + train_dir = data_dir / "train" + if train_dir.exists(): + print(f"Data directory {train_dir} already exists.") + if not ask_redownload("dataset"): + print("Skipping dataset download.") + return True + + # Create data directory + data_dir.mkdir(parents=True, exist_ok=True) + + # Download + if not download_from_drive(file_id, zip_file, is_folder=False): + return False + + # Extract + if not extract_zip(zip_file, data_dir): + return False + + # Cleanup + print("Cleaning up zip file...") + zip_file.unlink() + + print(f"✓ Dataset setup complete!") + print(f"Contents of {data_dir}:") + for item in sorted(data_dir.iterdir()): + print(f" {'📁' if item.is_dir() else '📄'} {item.name}") + + return True + + +def download_checkpoints(checkpoint_dir, folder_url): + """Download checkpoint files (original.pth and optical.pth).""" + print("\n" + "="*60) + print("DOWNLOADING CHECKPOINTS") + print("="*60) + + checkpoint_dir = Path(checkpoint_dir) + + # Check if checkpoints already exist + if checkpoint_dir.exists() and any(checkpoint_dir.glob('*.pth')): + print(f"Checkpoint directory {checkpoint_dir} already has .pth files:") + for pth in checkpoint_dir.glob('*.pth'): + print(f" 📄 {pth.name}") + if not ask_redownload("checkpoints"): + print("Skipping checkpoint download.") + return True + + # Create checkpoint directory + checkpoint_dir.mkdir(parents=True, exist_ok=True) + + # Download folder from Google Drive + # gdown will create files directly in the checkpoint_dir + if not download_from_drive(folder_url, checkpoint_dir, is_folder=True): + return False + + print(f"✓ Checkpoints download complete!") + print(f"Contents of {checkpoint_dir}:") + for item in sorted(checkpoint_dir.iterdir()): + size_mb = item.stat().st_size / (1024 * 1024) if item.is_file() else 0 + size_str = f"({size_mb:.1f} MB)" if item.is_file() else "" + print(f" {'📁' if item.is_dir() else '📄'} {item.name} {size_str}") + + return True + + +def download_raft_model(raft_dir, file_url): + """Download RAFT model file.""" + print("\n" + "="*60) + print("DOWNLOADING RAFT MODEL") + print("="*60) + + raft_dir = Path(raft_dir) + model_file = raft_dir / "raft_things.pth" + + # Check if RAFT model already exists + if model_file.exists(): + size_mb = model_file.stat().st_size / (1024 * 1024) + print(f"RAFT model already exists: {model_file.name} ({size_mb:.1f} MB)") + if not ask_redownload("RAFT model"): + print("Skipping RAFT model download.") + return True + + # Create RAFT directory + raft_dir.mkdir(parents=True, exist_ok=True) + + # Download + if not download_from_drive(file_url, model_file, is_folder=False): + return False + + print(f"✓ RAFT model download complete!") + if model_file.exists(): + size_mb = model_file.stat().st_size / (1024 * 1024) + print(f" 📄 {model_file.name} ({size_mb:.1f} MB)") + + return True + + +def main(): + parser = argparse.ArgumentParser( + description='Download AIGVDet data, checkpoints, and RAFT model' + ) + parser.add_argument( + '--data-id', + default='1YO3qRKbWxOYEm86Vy9QlGMjyi5Q_A6m0', + help='Google Drive file ID for dataset (default: from original script)' + ) + parser.add_argument( + '--data-dir', + default='data', + help='Directory to save dataset (default: data)' + ) + parser.add_argument( + '--checkpoint-dir', + default='checkpoints', + help='Directory to save checkpoints (default: checkpoints)' + ) + parser.add_argument( + '--checkpoint-folder', + default='https://drive.google.com/drive/folders/18JO_YxOEqwJYfbVvy308XjoV-N6fE4yP', + help='Google Drive folder URL for checkpoints' + ) + parser.add_argument( + '--raft-dir', + default='raft_model', + help='Directory to save RAFT model (default: raft_model)' + ) + parser.add_argument( + '--raft-file', + default='https://drive.google.com/file/d/1MqDajR89k-xLV0HIrmJ0k-n8ZpG6_suM/view', + help='Google Drive file URL for RAFT model' + ) + parser.add_argument( + '--skip-data', + action='store_true', + help='Skip dataset download' + ) + parser.add_argument( + '--skip-checkpoints', + action='store_true', + help='Skip checkpoint download' + ) + parser.add_argument( + '--skip-raft', + action='store_true', + help='Skip RAFT model download' + ) + + args = parser.parse_args() + + print("="*60) + print("AIGVDet Download Script") + print("="*60) + + # Check and install gdown + if not check_and_install_gdown(): + print("\n✗ Cannot proceed without gdown. Please install it manually:") + print(" pip install gdown") + return 1 + + success = True + + # Download dataset + if not args.skip_data: + if not download_dataset(args.data_dir, args.data_id): + print("\n✗ Dataset download failed!") + success = False + else: + print("\nSkipping dataset download (--skip-data)") + + # Download checkpoints + if not args.skip_checkpoints: + if not download_checkpoints(args.checkpoint_dir, args.checkpoint_folder): + print("\n✗ Checkpoint download failed!") + success = False + else: + print("\nSkipping checkpoint download (--skip-checkpoints)") + + # Download RAFT model + if not args.skip_raft: + if not download_raft_model(args.raft_dir, args.raft_file): + print("\n✗ RAFT model download failed!") + success = False + else: + print("\nSkipping RAFT model download (--skip-raft)") + + # Summary + print("\n" + "="*60) + if success: + print("✓ ALL DOWNLOADS COMPLETE!") + else: + print("⚠ SOME DOWNLOADS FAILED - Check errors above") + print("="*60) + + return 0 if success else 1 + + +if __name__ == "__main__": + sys.exit(main()) From 6387e820223db9fa175fa97887ca9c0e458d778c Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:09:47 +0800 Subject: [PATCH 36/55] fix: Clean up Dockerfile by removing unnecessary script copies --- Dockerfile.gpu-alt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 43e5b29..2b23046 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -32,12 +32,10 @@ COPY pyproject.toml ./ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ -COPY train.py test.py demo.py ./ -COPY train.sh test.sh demo.sh download_data.sh ./ - +COPY train.py test.py demo.py download_data.py ./ # Create directories for data and checkpoints -RUN mkdir -p /app/data /app/checkpoints /app/raft_model +RUN mkdir -p /app/data /app/checkpoints /app/raft_model # Install dependencies (PyTorch already included in base image) RUN uv pip install --system \ From ead7f974710510963f0e9f074f0634312e3e9421 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:13:34 +0800 Subject: [PATCH 37/55] fix: Update configuration and dataset handling for optical flow training --- DOWNLOAD_GUIDE.md | 162 --------------------------------- core/utils1/config.py | 11 ++- core/utils1/datasets.py | 28 +++--- core/utils1/trainer_optical.py | 2 +- train.py | 2 +- 5 files changed, 22 insertions(+), 183 deletions(-) delete mode 100644 DOWNLOAD_GUIDE.md diff --git a/DOWNLOAD_GUIDE.md b/DOWNLOAD_GUIDE.md deleted file mode 100644 index 9d07aa2..0000000 --- a/DOWNLOAD_GUIDE.md +++ /dev/null @@ -1,162 +0,0 @@ -# Download Data Script - Usage Guide - -This Python script downloads all necessary files for AIGVDet: -- **Dataset** (training/validation data) -- **Checkpoints** (original.pth and optical.pth) -- **RAFT Model** (for optical flow computation) - -## Quick Start - -### Download Everything -```bash -cd AIGVDet -python download_data.py -``` - -This will download: -- Dataset → `data/` directory -- Checkpoints → `checkpoints/` directory (contains original.pth, optical.pth) -- RAFT model → `raft_model/` directory - -## Advanced Usage - -### Download Specific Components - -**Only dataset:** -```bash -python download_data.py --skip-checkpoints --skip-raft -``` - -**Only checkpoints:** -```bash -python download_data.py --skip-data --skip-raft -``` - -**Only RAFT model:** -```bash -python download_data.py --skip-data --skip-checkpoints -``` - -### Custom Directories - -```bash -python download_data.py \ - --data-dir /path/to/data \ - --checkpoint-dir /path/to/checkpoints \ - --raft-dir /path/to/raft_model -``` - -### Custom Google Drive IDs - -```bash -python download_data.py \ - --data-id YOUR_FILE_ID \ - --checkpoint-folder https://drive.google.com/drive/folders/YOUR_FOLDER_ID \ - --raft-file https://drive.google.com/file/d/YOUR_FILE_ID/view -``` - -## What Gets Downloaded - -### 1. Dataset (data/) -- Training and validation data -- Extracted from a zip file -- Default ID: `1YO3qRKbWxOYEm86Vy9QlGMjyi5Q_A6m0` - -### 2. Checkpoints (checkpoints/) -- `original.pth` - Pre-trained RGB model -- `optical.pth` - Pre-trained optical flow model -- Folder URL: `https://drive.google.com/drive/folders/18JO_YxOEqwJYfbVvy308XjoV-N6fE4yP` - -### 3. RAFT Model (raft_model/) -- `raft_things.pth` - Pre-trained RAFT model for optical flow computation -- File URL: `https://drive.google.com/file/d/1MqDajR89k-xLV0HIrmJ0k-n8ZpG6_suM/view` - -## Dependencies - -The script will automatically install `gdown` if not present: -```bash -pip install gdown -``` - -Or install manually: -```bash -pip install gdown -``` - -## Features - -✅ **Auto-install dependencies** - Installs gdown if missing -✅ **Check existing files** - Asks before re-downloading -✅ **Progress indicators** - Shows download progress -✅ **Auto-extract** - Extracts zip files automatically -✅ **Cleanup** - Removes zip files after extraction -✅ **Detailed output** - Shows file sizes and directory contents -✅ **Error handling** - Gracefully handles download failures - -## Troubleshooting - -### gdown installation fails -```bash -python -m pip install --upgrade pip -pip install gdown -``` - -### Authentication errors -Some Google Drive files may require authentication. If you get quota errors: -1. Wait a few hours and try again -2. Download manually from the links above -3. Place files in the correct directories - -### Download fails mid-way -Re-run the script - it will ask if you want to re-download existing files. - -## File Structure After Download - -``` -AIGVDet/ -├── data/ -│ ├── train/ -│ └── val/ -├── checkpoints/ -│ ├── original.pth -│ └── optical.pth -└── raft_model/ - └── raft_things.pth -``` - -## Command Line Options - -``` -usage: download_data.py [-h] [--data-id DATA_ID] [--data-dir DATA_DIR] - [--checkpoint-dir CHECKPOINT_DIR] - [--checkpoint-folder CHECKPOINT_FOLDER] - [--raft-dir RAFT_DIR] [--raft-file RAFT_FILE] - [--skip-data] [--skip-checkpoints] [--skip-raft] - -optional arguments: - -h, --help show this help message and exit - --data-id DATA_ID Google Drive file ID for dataset - --data-dir DATA_DIR Directory to save dataset (default: data) - --checkpoint-dir CHECKPOINT_DIR - Directory to save checkpoints (default: checkpoints) - --checkpoint-folder CHECKPOINT_FOLDER - Google Drive folder URL for checkpoints - --raft-dir RAFT_DIR Directory to save RAFT model (default: raft_model) - --raft-file RAFT_FILE - Google Drive file URL for RAFT model - --skip-data Skip dataset download - --skip-checkpoints Skip checkpoint download - --skip-raft Skip RAFT model download -``` - -## Original Shell Script - -The original `download_data.sh` is still available if you prefer to use bash: -```bash -bash download_data.sh [FILE_ID] -``` - -However, the Python version is recommended as it: -- Works on Windows, Linux, and macOS -- Downloads checkpoints and RAFT model automatically -- Has better error handling and progress indicators diff --git a/core/utils1/config.py b/core/utils1/config.py index a66d379..f0c6702 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -25,18 +25,19 @@ class DefaultConfigs(ABC): # data augmentation - to match the paper's augmentation rate (10%), set blur and jpg to 0.5 rz_interp = ["bilinear"] # blur_prob = 0.1 - resnet50 template - blur_prob = 0.05 + blur_prob = 0 # optical flow blur_sig = [0.5] # jpg_prob = 0.1 - resnet50 template - jpg_prob = 0.05 + jpg_prob = 0 # optical flow # P(augmented) = 1-(1-0.5)(1-0.05) = 0.9 jpg_method = ["cv2"] jpg_qual = list(range(70, 91)) gray_prob = 0.0 aug_resize = True - aug_crop = True - aug_flip = True - aug_norm = True + optical_crop = True # comment out to disable cropping + aug_crop = True + aug_flip = False # optical flow + aug_norm = False # optical flow ####### train setting ###### warmup = False diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index ec6bd4e..4e4f589 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -40,34 +40,34 @@ def binary_dataset(root: str, cfg: CONFIGCLASS): rz_func = identity_transform # Crop to cfg.cropSize (paper uses 448) - if cfg.isTrain: + if cfg.isTrain and not cfg.optical_crop: # standard RGB training crop_func = transforms.RandomCrop((cfg.cropSize, cfg.cropSize)) else: crop_func = transforms.CenterCrop((cfg.cropSize, cfg.cropSize)) if cfg.aug_crop else identity_transform - # Flip only in training if enabled + # Flip only in training if enabled (disabled for optical flow) if cfg.isTrain and cfg.aug_flip: flip_func = transforms.RandomHorizontalFlip() else: - flip_func = identity_transform # fallback + flip_func = identity_transform # fallback return datasets.ImageFolder( root, transforms.Compose( [ - rz_func, - #change - crop_func, - transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), - flip_func, - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - if cfg.aug_norm - else identity_transform, - ] + rz_func, + #change + crop_func, + transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), + flip_func, + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + if cfg.aug_norm + else identity_transform, + ] + ) ) - ) class FileNameDataset(datasets.ImageFolder): diff --git a/core/utils1/trainer_optical.py b/core/utils1/trainer_optical.py index 7a37fb3..5f500ff 100644 --- a/core/utils1/trainer_optical.py +++ b/core/utils1/trainer_optical.py @@ -19,7 +19,7 @@ def __init__(self, cfg: CONFIGCLASS): self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") self.model:nn.Module # self.model.to(self.device) - #self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) + self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) self.optimizer: torch.optim.Optimizer def save_networks(self, epoch: int): diff --git a/train.py b/train.py index 055d5f3..15ef4fe 100644 --- a/train.py +++ b/train.py @@ -11,7 +11,7 @@ from core.utils1.datasets import create_dataloader from core.utils1.earlystop import EarlyStopping from core.utils1.eval import get_val_cfg, validate -from core.utils1.trainer import Trainer # change this if optical or rgb +from core.utils1.trainer_optical import Trainer # change this if optical or rgb from core.utils1.utils import Logger import ssl From 96399fdcb348fe217bb954ae23ff5a58d891df28 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:12:23 +0800 Subject: [PATCH 38/55] fix: Update model initialization in BaseModel to prevent premature loading --- core/utils1/trainer_optical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/utils1/trainer_optical.py b/core/utils1/trainer_optical.py index 5f500ff..7a37fb3 100644 --- a/core/utils1/trainer_optical.py +++ b/core/utils1/trainer_optical.py @@ -19,7 +19,7 @@ def __init__(self, cfg: CONFIGCLASS): self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") self.model:nn.Module # self.model.to(self.device) - self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) + #self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) self.optimizer: torch.optim.Optimizer def save_networks(self, epoch: int): From 59796bb2fc23f5a78b09d03e090cce87dfd6b031 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:15:35 +0800 Subject: [PATCH 39/55] fix: Update optical flow augmentation settings and improve pretrained checkpoint loading logic --- core/utils1/config.py | 5 ++--- download_data.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index f0c6702..94efb7b 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -34,10 +34,9 @@ class DefaultConfigs(ABC): jpg_qual = list(range(70, 91)) gray_prob = 0.0 aug_resize = True - optical_crop = True # comment out to disable cropping aug_crop = True - aug_flip = False # optical flow - aug_norm = False # optical flow + aug_flip = True # optical flow + aug_norm = True # optical flow ####### train setting ###### warmup = False diff --git a/download_data.py b/download_data.py index f3369c9..c74e34b 100644 --- a/download_data.py +++ b/download_data.py @@ -206,7 +206,7 @@ def main(): ) parser.add_argument( '--data-id', - default='1YO3qRKbWxOYEm86Vy9QlGMjyi5Q_A6m0', + default='1BGsUw_A7YT3fCuaCMXcaSMQBBvDyE_59 ', help='Google Drive file ID for dataset (default: from original script)' ) parser.add_argument( From 1d76a53a9bfdb8dae1890d6f84d4db920742f48c Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:16:43 +0800 Subject: [PATCH 40/55] fix: Update trainer import in train.py for consistency --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 15ef4fe..a87f639 100644 --- a/train.py +++ b/train.py @@ -11,7 +11,7 @@ from core.utils1.datasets import create_dataloader from core.utils1.earlystop import EarlyStopping from core.utils1.eval import get_val_cfg, validate -from core.utils1.trainer_optical import Trainer # change this if optical or rgb +from core.utils1.trainer import Trainer # change from core.utils1.utils import Logger import ssl From 7e6a1241b4bd223dc53dcf0fca4e1fbb8da5c88e Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 23:04:23 +0800 Subject: [PATCH 41/55] fix: Update data augmentation parameters for optical flow training --- core/utils1/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/utils1/config.py b/core/utils1/config.py index 94efb7b..6231905 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -25,10 +25,10 @@ class DefaultConfigs(ABC): # data augmentation - to match the paper's augmentation rate (10%), set blur and jpg to 0.5 rz_interp = ["bilinear"] # blur_prob = 0.1 - resnet50 template - blur_prob = 0 # optical flow + blur_prob = 0.1 # optical flow blur_sig = [0.5] # jpg_prob = 0.1 - resnet50 template - jpg_prob = 0 # optical flow + jpg_prob = 0.1 # optical flow # P(augmented) = 1-(1-0.5)(1-0.05) = 0.9 jpg_method = ["cv2"] jpg_qual = list(range(70, 91)) From ee09cf989827e2860f833812320d0b65e64daed4 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 23:06:00 +0800 Subject: [PATCH 42/55] fix: Remove trailing whitespace from default data ID in download_data.py --- download_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/download_data.py b/download_data.py index c74e34b..2855fde 100644 --- a/download_data.py +++ b/download_data.py @@ -206,7 +206,7 @@ def main(): ) parser.add_argument( '--data-id', - default='1BGsUw_A7YT3fCuaCMXcaSMQBBvDyE_59 ', + default='1BGsUw_A7YT3fCuaCMXcaSMQBBvDyE_59', help='Google Drive file ID for dataset (default: from original script)' ) parser.add_argument( From a988c7f911fb6c97332c745520b150deeeb7bbd9 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Tue, 25 Nov 2025 23:26:34 +0800 Subject: [PATCH 43/55] fix: Update default data ID in download_data.py and enhance docstrings in prepare_data.py --- Dockerfile.gpu-alt | 2 +- download_data.py | 2 +- prepare_data.py | 43 ++++++++++++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 2b23046..d63a70d 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -32,7 +32,7 @@ COPY pyproject.toml ./ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ -COPY train.py test.py demo.py download_data.py ./ +COPY train.py test.py demo.py download_data.py recreate_table_2.py prepare_data.py ./ # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model diff --git a/download_data.py b/download_data.py index 2855fde..999405b 100644 --- a/download_data.py +++ b/download_data.py @@ -206,7 +206,7 @@ def main(): ) parser.add_argument( '--data-id', - default='1BGsUw_A7YT3fCuaCMXcaSMQBBvDyE_59', + default='1Bgv_TA18CIXwxQ5EQoSybsSC3EcivVv1', help='Google Drive file ID for dataset (default: from original script)' ) parser.add_argument( diff --git a/prepare_data.py b/prepare_data.py index d96f827..65791b8 100644 --- a/prepare_data.py +++ b/prepare_data.py @@ -18,19 +18,28 @@ DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' def load_image(imfile): + """ + Load an image, convert to tensor, and move to the appropriate device. + """ img = np.array(Image.open(imfile)).astype(np.uint8) - img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) def save_flow(img, flo, output_path): - img = img[0].permute(1,2,0).cpu().numpy() - flo = flo[0].permute(1,2,0).cpu().numpy() - - # map flow to rgb image + """ + Save optical flow as a color image. + """ + img = img[0].permute(1, 2, 0).cpu().numpy() + flo = flo[0].permute(1, 2, 0).cpu().numpy() + + # Convert flow to RGB flo = flow_viz.flow_to_image(flo) cv2.imwrite(output_path, flo) def video_to_frames(video_path, output_folder): + """ + Extract frames from a video and save them as images in the output folder. + """ if not os.path.exists(output_folder): os.makedirs(output_folder) @@ -53,11 +62,15 @@ def video_to_frames(video_path, output_folder): cap.release() + # Return sorted list of extracted frame files images = glob.glob(os.path.join(output_folder, '*.png')) + \ glob.glob(os.path.join(output_folder, '*.jpg')) return sorted(images) def process_dataset(args): + """ + Process each video in the dataset, extracting frames and computing optical flow. + """ # Load RAFT model once print(f"Loading RAFT model from {args.model}...") model = torch.nn.DataParallel(RAFT(args)) @@ -83,10 +96,8 @@ def process_dataset(args): for video_path in tqdm(videos, desc=f"Processing {label}"): video_name = os.path.splitext(os.path.basename(video_path))[0] - # Define output paths - # Original frames: args.output_rgb_dir / label / video_name / frames + # Define output paths for RGB frames and optical flow rgb_out_dir = os.path.join(args.output_rgb_dir, label, video_name) - # Optical flow: args.output_flow_dir / label / video_name / frames flow_out_dir = os.path.join(args.output_flow_dir, label, video_name) if not os.path.exists(flow_out_dir): @@ -100,7 +111,7 @@ def process_dataset(args): continue # 2. Generate Optical Flow - # Check if flow already exists + # Check if flow already exists for the extracted frames existing_flow = glob.glob(os.path.join(flow_out_dir, "*.png")) if len(existing_flow) >= len(images) - 1: continue @@ -114,28 +125,34 @@ def process_dataset(args): if os.path.exists(flow_output_path): continue + # Load the consecutive frames image1 = load_image(imfile1) image2 = load_image(imfile2) + # Pad images if necessary padder = InputPadder(image1.shape) image1, image2 = padder.pad(image1, image2) + # Compute flow with the RAFT model flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) + # Save the optical flow save_flow(image1, flow_up, flow_output_path) if __name__ == '__main__': + # Parse command-line arguments parser = argparse.ArgumentParser() parser.add_argument('--source_dir', required=True, help="Path to folder containing 0_real/1_fake video folders") parser.add_argument('--output_rgb_dir', required=True, help="Output path for RGB frames") parser.add_argument('--output_flow_dir', required=True, help="Output path for Optical Flow frames") parser.add_argument('--model', default="raft_model/raft-things.pth", help="Path to RAFT model checkpoint") - # RAFT args - parser.add_argument('--small', action='store_true', help='use small model') - parser.add_argument('--mixed_precision', action='store_true', help='use mixed precision') - parser.add_argument('--alternate_corr', action='store_true', help='use efficent correlation implementation') + # RAFT arguments + parser.add_argument('--small', action='store_true', help='Use small model') + parser.add_argument('--mixed_precision', action='store_true', help='Use mixed precision') + parser.add_argument('--alternate_corr', action='store_true', help='Use efficient correlation implementation') args = parser.parse_args() + # Process the dataset with the provided arguments process_dataset(args) From ca2f67fdc64f1a978f193a98c96b0966aad326b1 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Wed, 26 Nov 2025 02:10:36 +0800 Subject: [PATCH 44/55] feat: Add video downloading and processing script with optical flow computation --- download_t2v.py | 31 +++++++ process_custom_videos.py | 184 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 download_t2v.py create mode 100644 process_custom_videos.py diff --git a/download_t2v.py b/download_t2v.py new file mode 100644 index 0000000..19a20f9 --- /dev/null +++ b/download_t2v.py @@ -0,0 +1,31 @@ +import gdown +import zipfile +import os + +# Google Drive file ID +file_id = "1FT06IRiy1oB1jHWBEarUI99DFCk6VHxf" + +# Output path for the downloaded file +output_path = "downloaded_file.zip" + +# Download the file from Google Drive +gdown.download(f"https://drive.google.com/uc?id={file_id}", output_path, quiet=False) + +# Check if the file was downloaded +if os.path.exists(output_path): + print(f"File downloaded successfully: {output_path}") + + # Unzip the file + unzip_dir = "unzipped_files" + if not os.path.exists(unzip_dir): + os.makedirs(unzip_dir) + + with zipfile.ZipFile(output_path, 'r') as zip_ref: + zip_ref.extractall(unzip_dir) + print(f"Files extracted to: {unzip_dir}") + + # Optionally, you can delete the zip file after extraction + os.remove(output_path) + print("Zip file removed after extraction.") +else: + print("Download failed.") diff --git a/process_custom_videos.py b/process_custom_videos.py new file mode 100644 index 0000000..c759f24 --- /dev/null +++ b/process_custom_videos.py @@ -0,0 +1,184 @@ +import sys +import argparse +import os +import cv2 +import glob +import numpy as np +import torch +from PIL import Image +from tqdm import tqdm +from natsort import natsorted +import warnings +warnings.filterwarnings("ignore", category=UserWarning, message="torch.meshgrid") + +# Add core to path for imports +sys.path.append('core') +from raft import RAFT +from utils import flow_viz +from utils.utils import InputPadder + +if not torch.cuda.is_available(): + raise RuntimeError("CUDA is not available. This script requires a GPU.") +DEVICE = 'cuda' + +def load_image(imfile): + img = np.array(Image.open(imfile)).astype(np.uint8) + img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) + +def save_flow(img, flo, output_path): + img = img[0].permute(1, 2, 0).cpu().numpy() + flo = flo[0].permute(1, 2, 0).cpu().numpy() + + # map flow to rgb image + flo = flow_viz.flow_to_image(flo) + # Save only the flow image as per typical requirements, or concatenated if requested. + # The user's code had: img_flo = np.concatenate([img, flo], axis=0) + # But usually for training we just want the flow. + # The user's demo code saved 'flo'. + cv2.imwrite(output_path, flo) + +def video_to_frames(video_path, output_folder, max_frames=95, resize=None): + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + cap = cv2.VideoCapture(video_path) + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + + # Print resolution to help user understand speed + print(f" -> Video: {os.path.basename(video_path)} | Resolution: {width}x{height}") + + frame_count = 0 + saved_frames = [] + + while cap.isOpened() and frame_count < max_frames: + ret, frame = cap.read() + if not ret: + break + + # Resize if requested + if resize is not None: + h, w = frame.shape[:2] + if min(h, w) > resize: + scale = resize / min(h, w) + new_h, new_w = int(h * scale), int(w * scale) + frame = cv2.resize(frame, (new_w, new_h)) + + video_name = os.path.splitext(os.path.basename(video_path))[0] + frame_filename = os.path.join(output_folder, f"{video_name}_{frame_count+1:05d}.jpg") + cv2.imwrite(frame_filename, frame) + saved_frames.append(frame_filename) + frame_count += 1 + + cap.release() + return sorted(saved_frames) + +def process_videos(args): + # Verify model path + if not os.path.exists(args.model): + # Try fallback for common issue (hyphen vs underscore) + if "raft-model" in args.model and os.path.exists(args.model.replace("raft-model", "raft_model")): + args.model = args.model.replace("raft-model", "raft_model") + print(f"Found model at alternate path: {args.model}") + elif "raft_model" in args.model and os.path.exists(args.model.replace("raft_model", "raft-model")): + args.model = args.model.replace("raft_model", "raft-model") + print(f"Found model at alternate path: {args.model}") + else: + print(f"Error: Model file not found at {args.model}") + print(f"Current working directory: {os.getcwd()}") + print(f"Available directories: {[d for d in os.listdir('.') if os.path.isdir(d)]}") + raise FileNotFoundError(f"Model file not found: {args.model}") + + # Load model + model = torch.nn.DataParallel(RAFT(args)) + model.load_state_dict(torch.load(args.model, map_location=torch.device(DEVICE))) + model = model.module + model.to(DEVICE) + model.eval() + + print(f"Processing on device: {DEVICE}") + if args.resize: + print(f"Resizing frames to short edge: {args.resize}px") + + # Get list of videos + videos = glob.glob(os.path.join(args.input_path, '*.mp4')) + \ + glob.glob(os.path.join(args.input_path, '*.avi')) + \ + glob.glob(os.path.join(args.input_path, '*.mov')) + + print(f"Found {len(videos)} videos in {args.input_path}") + + for video_path in tqdm(videos): + video_name = os.path.splitext(os.path.basename(video_path))[0] + + # Use main output directories directly (flat structure) + video_rgb_dir = args.output_rgb + video_flow_dir = args.output_flow + + if not os.path.exists(video_rgb_dir): + os.makedirs(video_rgb_dir) + if not os.path.exists(video_flow_dir): + os.makedirs(video_flow_dir) + + # 1. Extract RGB frames (max 95) + # Pass resize argument here + images = video_to_frames(video_path, video_rgb_dir, max_frames=95, resize=args.resize) + + # 2. Compute Optical Flow (max 94) + if len(images) < 2: + continue + + with torch.no_grad(): + images = natsorted(images) + + # Load first image + image1 = load_image(images[0]) + + for i in range(len(images) - 1): + if i >= 94: + break + + imfile1 = images[i] + imfile2 = images[i+1] + + # Check if flow already exists + flow_filename = os.path.basename(imfile1) + flow_output_path = os.path.join(video_flow_dir, flow_filename) + + if os.path.exists(flow_output_path): + # If skipping, we still need to update image1 for the next iteration if we weren't reloading + # But since we are skipping, we might not have loaded image2 yet. + # To be safe and simple with skipping logic: + image1 = load_image(imfile2) # Prepare for next iter + continue + + image2 = load_image(imfile2) + + # Debug: Print shape once to confirm resize + if i == 0 and video_path == videos[0]: + print(f" -> Model input shape: {image1.shape}") + + padder = InputPadder(image1.shape) + image1_padded, image2_padded = padder.pad(image1, image2) + + flow_low, flow_up = model(image1_padded, image2_padded, iters=20, test_mode=True) + + save_flow(image1, flow_up, flow_output_path) + + # Move image2 to image1 for next iteration + image1 = image2 + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--model', help="restore checkpoint", default="raft-model/raft-things.pth") + parser.add_argument('--input_path', help="path to videos", default="data/T2V/moonvalley_mp4") + parser.add_argument('--output_rgb', help="output path for RGB frames", default="data/T2V/moonvalley_rgb") + parser.add_argument('--output_flow', help="output path for Optical Flow frames", default="data/T2V/moonvalley_flow") + parser.add_argument('--small', action='store_true', help='use small model') + parser.add_argument('--mixed_precision', action='store_true', help='use mixed precision') + parser.add_argument('--alternate_corr', action='store_true', help='use efficent correlation implementation') + parser.add_argument('--resize', type=int, default=None, help='Resize smaller edge of frames to this value (e.g. 512) for faster processing') + + args = parser.parse_args() + + process_videos(args) From 39aa5d589f63d892cc6856f306198172a319800b Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Wed, 26 Nov 2025 02:27:31 +0800 Subject: [PATCH 45/55] fix: Update import path for CONFIGCLASS and adjust dataset transformations for training --- core/utils1/datasets.py | 57 +++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index 4e4f589..6236369 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -13,7 +13,7 @@ from scipy.ndimage import gaussian_filter from torch.utils.data.sampler import WeightedRandomSampler -from core.utils1.config import CONFIGCLASS +from utils1.config import CONFIGCLASS ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -27,47 +27,37 @@ def dataset_folder(root: str, cfg: CONFIGCLASS): def binary_dataset(root: str, cfg: CONFIGCLASS): - # identity_transform = transforms.Lambda(lambda img: img) - # rz_func = identity_transform - # issue here, destroys performance as no resizing happens at all that go straight to cropping even if they are different resolutions - identity_transform = transforms.Lambda(lambda img: img) - - # Enable resize (paper implies resize > crop for random cropping) - if cfg.aug_resize: - rz_func = transforms.Lambda(lambda img: custom_resize(img, cfg)) - else: - rz_func = identity_transform - - # Crop to cfg.cropSize (paper uses 448) - if cfg.isTrain and not cfg.optical_crop: # standard RGB training - crop_func = transforms.RandomCrop((cfg.cropSize, cfg.cropSize)) + + rz_func = identity_transform + + if cfg.isTrain: + crop_func = transforms.RandomCrop((448,448)) else: - crop_func = transforms.CenterCrop((cfg.cropSize, cfg.cropSize)) if cfg.aug_crop else identity_transform + crop_func = transforms.CenterCrop((448,448)) if cfg.aug_crop else identity_transform - # Flip only in training if enabled (disabled for optical flow) - if cfg.isTrain and cfg.aug_flip: + if cfg.isTrain and cfg.aug_flip and not (hasattr(cfg, 'isOptical') and cfg.isOptical): flip_func = transforms.RandomHorizontalFlip() else: - flip_func = identity_transform # fallback - + flip_func = identity_transform + return datasets.ImageFolder( root, transforms.Compose( [ - rz_func, - #change - crop_func, - transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), - flip_func, - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - if cfg.aug_norm - else identity_transform, - ] - ) + rz_func, + #change + transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), + crop_func, + flip_func, + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + if cfg.aug_norm + else identity_transform, + ] ) + ) class FileNameDataset(datasets.ImageFolder): @@ -86,6 +76,11 @@ def __getitem__(self, index): def blur_jpg_augment(img: Image.Image, cfg: CONFIGCLASS): img: np.ndarray = np.array(img) + + # Skip augmentations for optical flow to preserve motion vector integrity + if hasattr(cfg, 'isOptical') and cfg.isOptical: + return Image.fromarray(img) + if cfg.isTrain: if random() < cfg.blur_prob: sig = sample_continuous(cfg.blur_sig) From 6d74fd55f19989d1aa1ca23977566a6de3d57502 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Wed, 26 Nov 2025 02:28:58 +0800 Subject: [PATCH 46/55] fix: Update import path for CONFIGCLASS and enhance dataset transformations for training --- core/utils1/datasets.py | 57 ++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index 6236369..4e4f589 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -13,7 +13,7 @@ from scipy.ndimage import gaussian_filter from torch.utils.data.sampler import WeightedRandomSampler -from utils1.config import CONFIGCLASS +from core.utils1.config import CONFIGCLASS ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -27,37 +27,47 @@ def dataset_folder(root: str, cfg: CONFIGCLASS): def binary_dataset(root: str, cfg: CONFIGCLASS): + # identity_transform = transforms.Lambda(lambda img: img) + # rz_func = identity_transform + # issue here, destroys performance as no resizing happens at all that go straight to cropping even if they are different resolutions + identity_transform = transforms.Lambda(lambda img: img) - - rz_func = identity_transform - - if cfg.isTrain: - crop_func = transforms.RandomCrop((448,448)) + + # Enable resize (paper implies resize > crop for random cropping) + if cfg.aug_resize: + rz_func = transforms.Lambda(lambda img: custom_resize(img, cfg)) else: - crop_func = transforms.CenterCrop((448,448)) if cfg.aug_crop else identity_transform + rz_func = identity_transform - if cfg.isTrain and cfg.aug_flip and not (hasattr(cfg, 'isOptical') and cfg.isOptical): + # Crop to cfg.cropSize (paper uses 448) + if cfg.isTrain and not cfg.optical_crop: # standard RGB training + crop_func = transforms.RandomCrop((cfg.cropSize, cfg.cropSize)) + else: + crop_func = transforms.CenterCrop((cfg.cropSize, cfg.cropSize)) if cfg.aug_crop else identity_transform + + # Flip only in training if enabled (disabled for optical flow) + if cfg.isTrain and cfg.aug_flip: flip_func = transforms.RandomHorizontalFlip() else: - flip_func = identity_transform - + flip_func = identity_transform # fallback + return datasets.ImageFolder( root, transforms.Compose( [ - rz_func, - #change - transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), - crop_func, - flip_func, - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - if cfg.aug_norm - else identity_transform, - ] + rz_func, + #change + crop_func, + transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), + flip_func, + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + if cfg.aug_norm + else identity_transform, + ] + ) ) - ) class FileNameDataset(datasets.ImageFolder): @@ -76,11 +86,6 @@ def __getitem__(self, index): def blur_jpg_augment(img: Image.Image, cfg: CONFIGCLASS): img: np.ndarray = np.array(img) - - # Skip augmentations for optical flow to preserve motion vector integrity - if hasattr(cfg, 'isOptical') and cfg.isOptical: - return Image.fromarray(img) - if cfg.isTrain: if random() < cfg.blur_prob: sig = sample_continuous(cfg.blur_sig) From 89411320435860b186a55b7477f454a321014b4f Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:54:22 +0800 Subject: [PATCH 47/55] Add W&B integration guide, data download scripts, and processing utilities - Created WANDB_SETUP.md for detailed W&B integration instructions. - Implemented download_data.py to handle dataset, checkpoint, and RAFT model downloads. - Added download_t2v.py for downloading a specific file from Google Drive. - Developed prepare_data.py to extract frames from videos and compute optical flow using RAFT. - Introduced process_custom_videos.py for processing custom videos with optical flow extraction. - Added recreate_table_2.py to facilitate the recreation of evaluation results for specific datasets. - Implemented test_flat.py for evaluating model performance on RGB and optical flow frames. --- build-docker.ps1 | 65 ------- build-docker.sh | 65 ------- core/utils1/config.py | 2 +- core/utils1/datasets.py | 2 +- demo.sh | 1 - download_data.sh | 50 ------ DATA_SETUP.md => md-files/DATA_SETUP.md | 0 .../DOCKER_COMPARISON.md | 0 DOCKER_INDEX.md => md-files/DOCKER_INDEX.md | 0 .../DOCKER_QUICKREF.md | 0 DOCKER_USAGE.md => md-files/DOCKER_USAGE.md | 0 .../DOCKER_VM_SETUP.md | 0 WANDB_SETUP.md => md-files/WANDB_SETUP.md | 0 push-docker.ps1 | 144 ---------------- push-docker.sh | 150 ----------------- push-simple.ps1 | 49 ------ .../download_data.py | 0 .../download_t2v.py | 0 .../prepare_data.py | 0 .../process_custom_videos.py | 0 python-utils/recreate_table_2.py | 82 +++++++++ python-utils/test_flat.py | 159 ++++++++++++++++++ recreate_table_2.py | 63 ------- test.sh | 1 - train.sh | 4 - 25 files changed, 243 insertions(+), 594 deletions(-) delete mode 100644 build-docker.ps1 delete mode 100644 build-docker.sh delete mode 100644 demo.sh delete mode 100644 download_data.sh rename DATA_SETUP.md => md-files/DATA_SETUP.md (100%) rename DOCKER_COMPARISON.md => md-files/DOCKER_COMPARISON.md (100%) rename DOCKER_INDEX.md => md-files/DOCKER_INDEX.md (100%) rename DOCKER_QUICKREF.md => md-files/DOCKER_QUICKREF.md (100%) rename DOCKER_USAGE.md => md-files/DOCKER_USAGE.md (100%) rename DOCKER_VM_SETUP.md => md-files/DOCKER_VM_SETUP.md (100%) rename WANDB_SETUP.md => md-files/WANDB_SETUP.md (100%) delete mode 100644 push-docker.ps1 delete mode 100644 push-docker.sh delete mode 100644 push-simple.ps1 rename download_data.py => python-utils/download_data.py (100%) rename download_t2v.py => python-utils/download_t2v.py (100%) rename prepare_data.py => python-utils/prepare_data.py (100%) rename process_custom_videos.py => python-utils/process_custom_videos.py (100%) create mode 100644 python-utils/recreate_table_2.py create mode 100644 python-utils/test_flat.py delete mode 100644 recreate_table_2.py delete mode 100644 test.sh delete mode 100644 train.sh diff --git a/build-docker.ps1 b/build-docker.ps1 deleted file mode 100644 index a545b26..0000000 --- a/build-docker.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -# PowerShell build script for AIGVDet Docker images - -Write-Host "======================================" -ForegroundColor Cyan -Write-Host "AIGVDet Docker Build Script" -ForegroundColor Cyan -Write-Host "======================================" -ForegroundColor Cyan -Write-Host "" - -function Build-Image { - param( - [string]$Dockerfile, - [string]$Tag - ) - - Write-Host "Building $Tag image..." -ForegroundColor Yellow - docker build -f $Dockerfile -t aigvdet:$Tag . - - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ Successfully built aigvdet:$Tag" -ForegroundColor Green - } else { - Write-Host "❌ Failed to build aigvdet:$Tag" -ForegroundColor Red - exit 1 - } - Write-Host "" -} - -# Parse arguments -$BuildType = if ($args.Count -gt 0) { $args[0] } else { "all" } - -switch ($BuildType) { - "gpu" { - Write-Host "Building GPU image only..." -ForegroundColor Yellow - Build-Image -Dockerfile "Dockerfile.gpu" -Tag "gpu" - } - "cpu" { - Write-Host "Building CPU image only..." -ForegroundColor Yellow - Build-Image -Dockerfile "Dockerfile.cpu" -Tag "cpu" - } - "all" { - Write-Host "Building both CPU and GPU images..." -ForegroundColor Yellow - Build-Image -Dockerfile "Dockerfile.cpu" -Tag "cpu" - Build-Image -Dockerfile "Dockerfile.gpu" -Tag "gpu" - } - default { - Write-Host "Usage: .\build-docker.ps1 {gpu|cpu|all}" -ForegroundColor Red - Write-Host " gpu - Build GPU image only" - Write-Host " cpu - Build CPU image only" - Write-Host " all - Build both images (default)" - exit 1 - } -} - -Write-Host "======================================" -ForegroundColor Cyan -Write-Host "Build completed!" -ForegroundColor Green -Write-Host "======================================" -ForegroundColor Cyan -Write-Host "" -Write-Host "Available images:" -ForegroundColor Yellow -docker images | Select-String aigvdet -Write-Host "" -Write-Host "To run:" -ForegroundColor Yellow -Write-Host " GPU: docker run --gpus all -it --rm aigvdet:gpu" -Write-Host " CPU: docker run -it --rm aigvdet:cpu" -Write-Host "" -Write-Host "Or use docker-compose:" -ForegroundColor Yellow -Write-Host " docker-compose up aigvdet-gpu" -Write-Host " docker-compose up aigvdet-cpu" diff --git a/build-docker.sh b/build-docker.sh deleted file mode 100644 index 5320cca..0000000 --- a/build-docker.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash -# Build script for AIGVDet Docker images - -set -e - -echo "======================================" -echo "AIGVDet Docker Build Script" -echo "======================================" -echo "" - -# Function to build images -build_image() { - local dockerfile=$1 - local tag=$2 - - echo "Building $tag image..." - docker build -f $dockerfile -t aigvdet:$tag . - - if [ $? -eq 0 ]; then - echo "✅ Successfully built aigvdet:$tag" - else - echo "❌ Failed to build aigvdet:$tag" - exit 1 - fi - echo "" -} - -# Parse arguments -case "$1" in - gpu) - echo "Building GPU image only..." - build_image Dockerfile.gpu gpu - ;; - cpu) - echo "Building CPU image only..." - build_image Dockerfile.cpu cpu - ;; - all|"") - echo "Building both CPU and GPU images..." - build_image Dockerfile.cpu cpu - build_image Dockerfile.gpu gpu - ;; - *) - echo "Usage: $0 {gpu|cpu|all}" - echo " gpu - Build GPU image only" - echo " cpu - Build CPU image only" - echo " all - Build both images (default)" - exit 1 - ;; -esac - -echo "======================================" -echo "Build completed!" -echo "======================================" -echo "" -echo "Available images:" -docker images | grep aigvdet -echo "" -echo "To run:" -echo " GPU: docker run --gpus all -it --rm aigvdet:gpu" -echo " CPU: docker run -it --rm aigvdet:cpu" -echo "" -echo "Or use docker-compose:" -echo " docker-compose up aigvdet-gpu" -echo " docker-compose up aigvdet-cpu" diff --git a/core/utils1/config.py b/core/utils1/config.py index 6231905..c8c9f3e 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -29,7 +29,7 @@ class DefaultConfigs(ABC): blur_sig = [0.5] # jpg_prob = 0.1 - resnet50 template jpg_prob = 0.1 # optical flow - # P(augmented) = 1-(1-0.5)(1-0.05) = 0.9 + # P(augmented) = 1-(1-0.5)(1-0.05) = 0.975 jpg_method = ["cv2"] jpg_qual = list(range(70, 91)) gray_prob = 0.0 diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index 4e4f589..aa20f29 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -40,7 +40,7 @@ def binary_dataset(root: str, cfg: CONFIGCLASS): rz_func = identity_transform # Crop to cfg.cropSize (paper uses 448) - if cfg.isTrain and not cfg.optical_crop: # standard RGB training + if cfg.isTrain: crop_func = transforms.RandomCrop((cfg.cropSize, cfg.cropSize)) else: crop_func = transforms.CenterCrop((cfg.cropSize, cfg.cropSize)) if cfg.aug_crop else identity_transform diff --git a/demo.sh b/demo.sh deleted file mode 100644 index c81762a..0000000 --- a/demo.sh +++ /dev/null @@ -1 +0,0 @@ -python demo.py --use_cpu --path "video/000000.mp4" --folder_original_path "frame/000000" --folder_optical_flow_path "optical_result/000000" -mop "checkpoints/optical.pth" -mor "checkpoints/original.pth" \ No newline at end of file diff --git a/download_data.sh b/download_data.sh deleted file mode 100644 index f9a4879..0000000 --- a/download_data.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -set -e - -# Configuration -# Default File ID provided by user, can be overridden by argument -FILE_ID="${1:-1YO3qRKbWxOYEm86Vy9QlGMjyi5Q_A6m0}" -DATA_DIR="/app/data" -ZIP_FILE="${DATA_DIR}/data.zip" - -echo "Starting data download setup..." - -# 1. Install dependencies if missing -if ! command -v gdown &> /dev/null; then - echo "Installing gdown..." - pip install gdown -fi - -if ! command -v unzip &> /dev/null; then - echo "Installing unzip..." - apt-get update && apt-get install -y unzip -fi - -# 2. Check if data already exists -if [ -d "${DATA_DIR}/train" ]; then - echo "Data directory ${DATA_DIR}/train already exists." - read -p "Do you want to re-download? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo "Skipping download." - exit 0 - fi -fi - -# 3. Create data directory -mkdir -p "$DATA_DIR" - -# 4. Download file -echo "Downloading data from Google Drive (ID: $FILE_ID)..." -gdown "$FILE_ID" -O "$ZIP_FILE" - -# 5. Extract -echo "Extracting data..." -unzip -o "$ZIP_FILE" -d "$DATA_DIR" - -# 6. Cleanup -echo "Cleaning up zip file..." -rm "$ZIP_FILE" - -echo "Data setup complete!" -ls -F "$DATA_DIR" diff --git a/DATA_SETUP.md b/md-files/DATA_SETUP.md similarity index 100% rename from DATA_SETUP.md rename to md-files/DATA_SETUP.md diff --git a/DOCKER_COMPARISON.md b/md-files/DOCKER_COMPARISON.md similarity index 100% rename from DOCKER_COMPARISON.md rename to md-files/DOCKER_COMPARISON.md diff --git a/DOCKER_INDEX.md b/md-files/DOCKER_INDEX.md similarity index 100% rename from DOCKER_INDEX.md rename to md-files/DOCKER_INDEX.md diff --git a/DOCKER_QUICKREF.md b/md-files/DOCKER_QUICKREF.md similarity index 100% rename from DOCKER_QUICKREF.md rename to md-files/DOCKER_QUICKREF.md diff --git a/DOCKER_USAGE.md b/md-files/DOCKER_USAGE.md similarity index 100% rename from DOCKER_USAGE.md rename to md-files/DOCKER_USAGE.md diff --git a/DOCKER_VM_SETUP.md b/md-files/DOCKER_VM_SETUP.md similarity index 100% rename from DOCKER_VM_SETUP.md rename to md-files/DOCKER_VM_SETUP.md diff --git a/WANDB_SETUP.md b/md-files/WANDB_SETUP.md similarity index 100% rename from WANDB_SETUP.md rename to md-files/WANDB_SETUP.md diff --git a/push-docker.ps1 b/push-docker.ps1 deleted file mode 100644 index d5ecafb..0000000 --- a/push-docker.ps1 +++ /dev/null @@ -1,144 +0,0 @@ -# Docker Push Script for AIGVDet -# Pushes images to Docker Hub: sacdalance/thesis-aigvdet - -param( - [Parameter(Mandatory=$false)] - [ValidateSet("gpu", "cpu", "all")] - [string]$Version = "all", - - [Parameter(Mandatory=$false)] - [string]$Tag = "latest" -) - -$ErrorActionPreference = "Stop" -$Repository = "sacdalance/thesis-aigvdet" - -Write-Host "`n╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan -Write-Host "║ Docker Push Script - AIGVDet ║" -ForegroundColor Cyan -Write-Host "║ Repository: $Repository ║" -ForegroundColor Cyan -Write-Host "╚═══════════════════════════════════════════════════════════╝`n" -ForegroundColor Cyan - -function Build-And-Push-Image { - param( - [string]$Dockerfile, - [string]$ImageTag, - [string]$VersionTag - ) - - $LocalTag = "aigvdet:$ImageTag" - $RemoteTag = "${Repository}:${VersionTag}" - $RemoteLatestTag = "${Repository}:${ImageTag}" - - Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray - Write-Host "🔨 Building $ImageTag image..." -ForegroundColor Yellow - Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray - - docker build -f $Dockerfile -t $LocalTag -t $RemoteTag -t $RemoteLatestTag . - - if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Failed to build $ImageTag image" -ForegroundColor Red - exit 1 - } - - Write-Host "✅ Successfully built $ImageTag image" -ForegroundColor Green - Write-Host "" - - Write-Host "📤 Pushing to Docker Hub..." -ForegroundColor Yellow - - # Push with version tag - Write-Host " → Pushing ${RemoteTag}..." -ForegroundColor Cyan - docker push $RemoteTag - - if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Failed to push ${RemoteTag}" -ForegroundColor Red - exit 1 - } - - # Push with latest tag - Write-Host " → Pushing ${RemoteLatestTag}..." -ForegroundColor Cyan - docker push $RemoteLatestTag - - if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Failed to push ${RemoteLatestTag}" -ForegroundColor Red - exit 1 - } - - Write-Host "✅ Successfully pushed $ImageTag image to Docker Hub" -ForegroundColor Green - Write-Host "" -} - -# Check if logged in to Docker Hub -Write-Host "🔐 Checking Docker Hub authentication..." -ForegroundColor Yellow -docker info | Out-Null -if ($LASTEXITCODE -ne 0) { - Write-Host "❌ Docker is not running or not accessible" -ForegroundColor Red - exit 1 -} - -# Try to verify login (will fail if not logged in) -$loginCheck = docker info 2>&1 | Select-String "Username" -if (-not $loginCheck) { - Write-Host "⚠️ You may not be logged in to Docker Hub" -ForegroundColor Yellow - Write-Host "Please run: docker login" -ForegroundColor Yellow - $continue = Read-Host "`nContinue anyway? (y/N)" - if ($continue -ne "y" -and $continue -ne "Y") { - exit 0 - } -} -Write-Host "✅ Docker is ready`n" -ForegroundColor Green - -# Build and push based on version -switch ($Version) { - "gpu" { - Write-Host "Building and pushing GPU version only...`n" -ForegroundColor Yellow - Build-And-Push-Image -Dockerfile "Dockerfile.gpu" -ImageTag "gpu" -VersionTag "$Tag-gpu" - } - "cpu" { - Write-Host "Building and pushing CPU version only...`n" -ForegroundColor Yellow - Build-And-Push-Image -Dockerfile "Dockerfile.cpu" -ImageTag "cpu" -VersionTag "$Tag-cpu" - } - "all" { - Write-Host "Building and pushing both CPU and GPU versions...`n" -ForegroundColor Yellow - Build-And-Push-Image -Dockerfile "Dockerfile.cpu" -ImageTag "cpu" -VersionTag "$Tag-cpu" - Build-And-Push-Image -Dockerfile "Dockerfile.gpu" -ImageTag "gpu" -VersionTag "$Tag-gpu" - } -} - -Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray -Write-Host "🎉 All images pushed successfully!" -ForegroundColor Green -Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray -Write-Host "" -Write-Host "📦 Available images on Docker Hub:" -ForegroundColor Yellow - -if ($Version -eq "cpu" -or $Version -eq "all") { - Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan - Write-Host ":cpu" -ForegroundColor Cyan - Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan - Write-Host ":$Tag-cpu" -ForegroundColor Cyan -} - -if ($Version -eq "gpu" -or $Version -eq "all") { - Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan - Write-Host ":gpu" -ForegroundColor Cyan - Write-Host " • $Repository" -NoNewline -ForegroundColor Cyan - Write-Host ":$Tag-gpu" -ForegroundColor Cyan -} - -Write-Host "" -Write-Host "🚀 Pull commands:" -ForegroundColor Yellow - -if ($Version -eq "cpu" -or $Version -eq "all") { - Write-Host " CPU: docker pull $Repository" -NoNewline -ForegroundColor White - Write-Host ":cpu" -ForegroundColor White -} - -if ($Version -eq "gpu" -or $Version -eq "all") { - Write-Host " GPU: docker pull $Repository" -NoNewline -ForegroundColor White - Write-Host ":gpu" -ForegroundColor White -} - -Write-Host "" -Write-Host "💡 View on Docker Hub:" -ForegroundColor Yellow -$DockerHubUrl = "https://hub.docker.com/r/$Repository" -Write-Host " $DockerHubUrl" -ForegroundColor Cyan -Write-Host "" diff --git a/push-docker.sh b/push-docker.sh deleted file mode 100644 index c59a862..0000000 --- a/push-docker.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/bin/bash -# Docker Push Script for AIGVDet -# Pushes images to Docker Hub: sacdalance/thesis-aigvdet - -set -e - -VERSION="${1:-all}" -TAG="${2:-latest}" -REPOSITORY="sacdalance/thesis-aigvdet" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' # No Color - -echo "" -echo "╔═══════════════════════════════════════════════════════════╗" -echo "║ Docker Push Script - AIGVDet ║" -echo "║ Repository: ${REPOSITORY}" -echo "╚═══════════════════════════════════════════════════════════╝" -echo "" - -build_and_push_image() { - local dockerfile=$1 - local image_tag=$2 - local version_tag=$3 - - local local_tag="aigvdet:${image_tag}" - local remote_tag="${REPOSITORY}:${version_tag}" - local remote_latest_tag="${REPOSITORY}:${image_tag}" - - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo -e "${YELLOW}🔨 Building ${image_tag} image...${NC}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - - docker build -f "${dockerfile}" -t "${local_tag}" -t "${remote_tag}" -t "${remote_latest_tag}" . - - if [ $? -ne 0 ]; then - echo -e "${RED}❌ Failed to build ${image_tag} image${NC}" - exit 1 - fi - - echo -e "${GREEN}✅ Successfully built ${image_tag} image${NC}" - echo "" - - echo -e "${YELLOW}📤 Pushing to Docker Hub...${NC}" - - # Push with version tag - echo -e "${CYAN} → Pushing ${remote_tag}...${NC}" - docker push "${remote_tag}" - - if [ $? -ne 0 ]; then - echo -e "${RED}❌ Failed to push ${remote_tag}${NC}" - exit 1 - fi - - # Push with latest tag - echo -e "${CYAN} → Pushing ${remote_latest_tag}...${NC}" - docker push "${remote_latest_tag}" - - if [ $? -ne 0 ]; then - echo -e "${RED}❌ Failed to push ${remote_latest_tag}${NC}" - exit 1 - fi - - echo -e "${GREEN}✅ Successfully pushed ${image_tag} image to Docker Hub${NC}" - echo "" -} - -# Check if Docker is running -echo -e "${YELLOW}🔐 Checking Docker authentication...${NC}" -if ! docker info > /dev/null 2>&1; then - echo -e "${RED}❌ Docker is not running or not accessible${NC}" - exit 1 -fi - -# Check if logged in -if ! docker info 2>&1 | grep -q "Username"; then - echo -e "${YELLOW}⚠️ You may not be logged in to Docker Hub${NC}" - echo -e "${YELLOW}Please run: docker login${NC}" - read -p "Continue anyway? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 0 - fi -fi -echo -e "${GREEN}✅ Docker is ready${NC}" -echo "" - -# Build and push based on version -case "$VERSION" in - gpu) - echo -e "${YELLOW}Building and pushing GPU version only...${NC}" - echo "" - build_and_push_image "Dockerfile.gpu" "gpu" "${TAG}-gpu" - ;; - cpu) - echo -e "${YELLOW}Building and pushing CPU version only...${NC}" - echo "" - build_and_push_image "Dockerfile.cpu" "cpu" "${TAG}-cpu" - ;; - all) - echo -e "${YELLOW}Building and pushing both CPU and GPU versions...${NC}" - echo "" - build_and_push_image "Dockerfile.cpu" "cpu" "${TAG}-cpu" - build_and_push_image "Dockerfile.gpu" "gpu" "${TAG}-gpu" - ;; - *) - echo -e "${RED}Usage: $0 {gpu|cpu|all} [tag]${NC}" - echo " gpu - Push GPU image only" - echo " cpu - Push CPU image only" - echo " all - Push both images (default)" - echo " tag - Version tag (default: latest)" - exit 1 - ;; -esac - -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo -e "${GREEN}🎉 All images pushed successfully!${NC}" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" -echo -e "${YELLOW}📦 Available images on Docker Hub:${NC}" - -if [ "$VERSION" = "cpu" ] || [ "$VERSION" = "all" ]; then - echo -e "${CYAN} • ${REPOSITORY}:cpu${NC}" - echo -e "${CYAN} • ${REPOSITORY}:${TAG}-cpu${NC}" -fi - -if [ "$VERSION" = "gpu" ] || [ "$VERSION" = "all" ]; then - echo -e "${CYAN} • ${REPOSITORY}:gpu${NC}" - echo -e "${CYAN} • ${REPOSITORY}:${TAG}-gpu${NC}" -fi - -echo "" -echo -e "${YELLOW}🚀 Pull commands:${NC}" - -if [ "$VERSION" = "cpu" ] || [ "$VERSION" = "all" ]; then - echo " CPU: docker pull ${REPOSITORY}:cpu" -fi - -if [ "$VERSION" = "gpu" ] || [ "$VERSION" = "all" ]; then - echo " GPU: docker pull ${REPOSITORY}:gpu" -fi - -echo "" -echo -e "${YELLOW}💡 View on Docker Hub:${NC}" -echo -e "${CYAN} https://hub.docker.com/r/${REPOSITORY}${NC}" -echo "" diff --git a/push-simple.ps1 b/push-simple.ps1 deleted file mode 100644 index b95146f..0000000 --- a/push-simple.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -# Simple Docker Push - Complete Workflow -# Run this after images are built - -Write-Host "`n========================================" -ForegroundColor Cyan -Write-Host "Docker Push to sacdalance/thesis-aigvdet" -ForegroundColor Green -Write-Host "========================================`n" -ForegroundColor Cyan - -$repo = "sacdalance/thesis-aigvdet" - -# Push CPU image -Write-Host "📤 Pushing CPU image..." -ForegroundColor Yellow -docker push "${repo}:cpu" -docker push "${repo}:latest-cpu" - -if ($LASTEXITCODE -eq 0) { - Write-Host "✅ CPU image pushed successfully!`n" -ForegroundColor Green -} else { - Write-Host "❌ Failed to push CPU image`n" -ForegroundColor Red -} - -# Build and push GPU image -Write-Host "🔨 Building GPU image..." -ForegroundColor Yellow -docker build -f Dockerfile.gpu -t "${repo}:gpu" -t "${repo}:latest-gpu" . - -if ($LASTEXITCODE -eq 0) { - Write-Host "✅ GPU image built successfully!`n" -ForegroundColor Green - - Write-Host "📤 Pushing GPU image..." -ForegroundColor Yellow - docker push "${repo}:gpu" - docker push "${repo}:latest-gpu" - - if ($LASTEXITCODE -eq 0) { - Write-Host "✅ GPU image pushed successfully!`n" -ForegroundColor Green - } else { - Write-Host "❌ Failed to push GPU image`n" -ForegroundColor Red - } -} else { - Write-Host "❌ Failed to build GPU image`n" -ForegroundColor Red -} - -Write-Host "`n========================================" -ForegroundColor Cyan -Write-Host "✨ Push Complete!" -ForegroundColor Green -Write-Host "========================================`n" -ForegroundColor Cyan - -Write-Host "📦 Your images are now available at:" -ForegroundColor Yellow -Write-Host " • docker pull ${repo}:cpu" -ForegroundColor Cyan -Write-Host " • docker pull ${repo}:gpu" -ForegroundColor Cyan -Write-Host "`n🌐 View on Docker Hub:" -ForegroundColor Yellow -Write-Host " https://hub.docker.com/r/${repo}`n" -ForegroundColor Cyan diff --git a/download_data.py b/python-utils/download_data.py similarity index 100% rename from download_data.py rename to python-utils/download_data.py diff --git a/download_t2v.py b/python-utils/download_t2v.py similarity index 100% rename from download_t2v.py rename to python-utils/download_t2v.py diff --git a/prepare_data.py b/python-utils/prepare_data.py similarity index 100% rename from prepare_data.py rename to python-utils/prepare_data.py diff --git a/process_custom_videos.py b/python-utils/process_custom_videos.py similarity index 100% rename from process_custom_videos.py rename to python-utils/process_custom_videos.py diff --git a/python-utils/recreate_table_2.py b/python-utils/recreate_table_2.py new file mode 100644 index 0000000..2674edb --- /dev/null +++ b/python-utils/recreate_table_2.py @@ -0,0 +1,82 @@ +import argparse +import subprocess +import os +import sys + +def run_command(command): + print(f"Running: {command}") + try: + subprocess.check_call(command, shell=True) + except subprocess.CalledProcessError as e: + print(f"Error running command: {command}") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description="Recreate Table 2 results for a specific dataset.") + parser.add_argument("dataset", help="Name of the dataset (e.g., moonvalley, videocraft, pika, neverends)") + parser.add_argument("--rgb_dir", help="Path to RGB frames directory", default=None) + parser.add_argument("--flow_dir", help="Path to Optical Flow frames directory", default=None) + args = parser.parse_args() + + dataset_name = args.dataset + + # Define paths + # Default paths (relative to current directory) + base_data_dir = "data" + + # If arguments are provided, use them. Otherwise, construct default paths. + if args.rgb_dir: + output_rgb_dir = args.rgb_dir + else: + # Matches structure: data/test/videocraft_rgb + output_rgb_dir = os.path.join(base_data_dir, "test", f"{dataset_name}_rgb") + + if args.flow_dir: + output_flow_dir = args.flow_dir + else: + # Matches structure: data/test/videocraft_flow + output_flow_dir = os.path.join(base_data_dir, "test", f"{dataset_name}_flow") + + # Source video dir (only needed for preparation step, which is skipped) + source_video_dir = os.path.join(base_data_dir, "test", "T2V", dataset_name) + + result_csv = os.path.join(base_data_dir, "results", f"{dataset_name}.csv") + result_no_cp_csv = os.path.join(base_data_dir, "results", f"{dataset_name}_no_cp.csv") + + # Model paths (relative) + raft_model = "raft_model/raft-things.pth" + optical_model = "checkpoints/optical.pth" + original_model = "checkpoints/original.pth" + + print(f"--- Processing Dataset: {dataset_name} ---") + print(f"RGB Directory: {output_rgb_dir}") + print(f"Flow Directory: {output_flow_dir}") + + # Check if directories exist + if not os.path.exists(output_rgb_dir): + print(f"Warning: RGB directory not found: {output_rgb_dir}") + if not os.path.exists(output_flow_dir): + print(f"Warning: Flow directory not found: {output_flow_dir}") + + # Step 1: Prepare Data + # print("\n[Step 1] Preparing Data (Extracting Frames & Generating Optical Flow)...") + # # Note: We use sys.executable to ensure we use the same python interpreter + # cmd_prepare = f'"{sys.executable}" prepare_data.py --source_dir "{source_video_dir}" --output_rgb_dir "{output_rgb_dir}" --output_flow_dir "{output_flow_dir}" --model "{raft_model}"' + # run_command(cmd_prepare) + + # Step 2: Run Standard Evaluation + print("\n[Step 2] Running Standard Evaluation (AIGVDet, Sspatial, Soptical)...") + cmd_test = f'"{sys.executable}" test_flat.py -fop "{output_flow_dir}" -for "{output_rgb_dir}" -mop "{optical_model}" -mor "{original_model}" -e "{result_csv}"' + run_command(cmd_test) + + # Step 3: Run No-Crop Evaluation + print("\n[Step 3] Running No-Crop Evaluation (Soptical no cp)...") + cmd_test_no_cp = f'"{sys.executable}" test_flat.py --no_crop -fop "{output_flow_dir}" -for "{output_rgb_dir}" -mop "{optical_model}" -mor "{original_model}" -e "{result_no_cp_csv}"' + run_command(cmd_test_no_cp) + + print("\n--- Done! ---") + print(f"Standard Results saved to: {result_csv}") + print(f"No-Crop Results saved to: {result_no_cp_csv}") + +if __name__ == "__main__": + main() diff --git a/python-utils/test_flat.py b/python-utils/test_flat.py new file mode 100644 index 0000000..cc2668b --- /dev/null +++ b/python-utils/test_flat.py @@ -0,0 +1,159 @@ +import argparse +import glob +import os +import pandas as pd +import torch +import torch.nn +import torchvision.transforms as transforms +import torchvision.transforms.functional as TF +from PIL import Image +from tqdm import tqdm +from sklearn.metrics import accuracy_score, roc_auc_score +from core.utils1.utils import get_network, str2bool + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-fop", "--folder_optical_flow_path", type=str, required=True) + parser.add_argument("-for", "--folder_original_path", type=str, required=True) + parser.add_argument("-mop", "--model_optical_flow_path", type=str, default="checkpoints/optical.pth") + parser.add_argument("-mor", "--model_original_path", type=str, default="checkpoints/original.pth") + parser.add_argument("-t", "--threshold", type=float, default=0.5) + parser.add_argument("-e", "--excel_path", type=str, default="results.csv") + parser.add_argument("--use_cpu", action="store_true") + parser.add_argument("--arch", type=str, default="resnet50") + parser.add_argument("--aug_norm", type=str2bool, default=True) + parser.add_argument("--no_crop", action="store_true") + + args = parser.parse_args() + + # Load Models + device = torch.device("cpu" if args.use_cpu else "cuda") + + print("Loading models...") + model_op = get_network(args.arch).to(device) + model_op.load_state_dict(torch.load(args.model_optical_flow_path, map_location=device)["model"]) + model_op.eval() + + model_or = get_network(args.arch).to(device) + model_or.load_state_dict(torch.load(args.model_original_path, map_location=device)["model"]) + model_or.eval() + + # Transforms + if args.no_crop: + trans = transforms.Compose([transforms.ToTensor()]) + else: + trans = transforms.Compose([transforms.CenterCrop((448, 448)), transforms.ToTensor()]) + + print(f"Processing flat directories...") + print(f"RGB: {args.folder_original_path}") + print(f"Flow: {args.folder_optical_flow_path}") + + # Get list of images + # We assume filenames match between RGB and Flow (except maybe extension) + # Actually, process_custom_videos outputs: + # RGB: video_name_frame_00001.jpg + # Flow: video_name_frame_00001.jpg (or .png) + + rgb_files = sorted(glob.glob(os.path.join(args.folder_original_path, "*"))) + rgb_files = [f for f in rgb_files if f.lower().endswith(('.png', '.jpg', '.jpeg'))] + + if len(rgb_files) == 0: + print("No RGB images found!") + return + + print(f"Found {len(rgb_files)} RGB frames.") + + y_true = [] + y_pred = [] + y_pred_rgb = [] + y_pred_flow = [] + + results = [] + + # Iterate + for rgb_path in tqdm(rgb_files): + filename = os.path.basename(rgb_path) + # Try to find corresponding flow file + # Flow might be .png even if RGB is .jpg + flow_path = os.path.join(args.folder_optical_flow_path, filename) + if not os.path.exists(flow_path): + # Try replacing extension + name, ext = os.path.splitext(filename) + flow_path_png = os.path.join(args.folder_optical_flow_path, name + ".png") + flow_path_jpg = os.path.join(args.folder_optical_flow_path, name + ".jpg") + if os.path.exists(flow_path_png): + flow_path = flow_path_png + elif os.path.exists(flow_path_jpg): + flow_path = flow_path_jpg + else: + # print(f"Warning: Flow file not found for {filename}") + continue + + # Load and Preprocess + try: + img_rgb = Image.open(rgb_path).convert("RGB") + img_flow = Image.open(flow_path).convert("RGB") + except Exception as e: + print(f"Error loading {filename}: {e}") + continue + + # Transform + t_rgb = trans(img_rgb) + t_flow = trans(img_flow) + + if args.aug_norm: + t_rgb = TF.normalize(t_rgb, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + t_flow = TF.normalize(t_flow, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + t_rgb = t_rgb.unsqueeze(0).to(device) + t_flow = t_flow.unsqueeze(0).to(device) + + # Inference + with torch.no_grad(): + prob_rgb = model_or(t_rgb).sigmoid().item() + prob_flow = model_op(t_flow).sigmoid().item() + + prob_fused = 0.5 * prob_rgb + 0.5 * prob_flow + + # Assume Fake (1) since we are testing generators + label = 1 + + y_true.append(label) + y_pred.append(prob_fused) + y_pred_rgb.append(prob_rgb) + y_pred_flow.append(prob_flow) + + results.append({ + "filename": filename, + "prob_fused": prob_fused, + "prob_rgb": prob_rgb, + "prob_flow": prob_flow, + "label": label + }) + + # Metrics + if len(y_true) == 0: + print("No valid pairs processed.") + return + + acc_fused = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred]) + acc_rgb = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred_rgb]) + acc_flow = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred_flow]) + + print("-" * 30) + print(f"Results (Assuming all inputs are FAKE/Generated)") + print(f"Total Frames: {len(y_true)}") + print("-" * 30) + print(f"Fused Accuracy (Recall): {acc_fused:.4f}") + print(f"RGB Accuracy (Recall): {acc_rgb:.4f}") + print(f"Flow Accuracy (Recall): {acc_flow:.4f}") + print("-" * 30) + + # Save CSV + df = pd.DataFrame(results) + os.makedirs(os.path.dirname(args.excel_path), exist_ok=True) + df.to_csv(args.excel_path, index=False) + print(f"Saved results to {args.excel_path}") + +if __name__ == "__main__": + main() diff --git a/recreate_table_2.py b/recreate_table_2.py deleted file mode 100644 index 61f26aa..0000000 --- a/recreate_table_2.py +++ /dev/null @@ -1,63 +0,0 @@ -import argparse -import subprocess -import os -import sys - -def run_command(command): - print(f"Running: {command}") - try: - subprocess.check_call(command, shell=True) - except subprocess.CalledProcessError as e: - print(f"Error running command: {command}") - sys.exit(1) - -def main(): - parser = argparse.ArgumentParser(description="Recreate Table 2 results for a specific dataset.") - parser.add_argument("dataset", help="Name of the dataset (e.g., moonvalley, videocraft, pika, neverends)") - args = parser.parse_args() - - dataset_name = args.dataset - - # Define paths - # Using /app/data for Docker/Jupyter environment compatibility - source_video_dir = f"/app/data/test/T2V/{dataset_name}" - output_rgb_dir = f"/app/data/test/original/T2V/{dataset_name}" - output_flow_dir = f"/app/data/test/T2V/{dataset_name}_flow" - - result_csv = f"/app/data/results/{dataset_name}.csv" - result_no_cp_csv = f"/app/data/results/{dataset_name}_no_cp.csv" - - raft_model = "/app/raft_model/raft-things.pth" - optical_model = "/app/checkpoints/optical.pth" - original_model = "/app/checkpoints/original.pth" - - print(f"--- Processing Dataset: {dataset_name} ---") - - # Check if source directory exists - if not os.path.exists(source_video_dir): - print(f"Error: Source video directory not found: {source_video_dir}") - print("Please download the test videos and place them in the correct folder.") - sys.exit(1) - - # Step 1: Prepare Data - print("\n[Step 1] Preparing Data (Extracting Frames & Generating Optical Flow)...") - # Note: We use sys.executable to ensure we use the same python interpreter - cmd_prepare = f'"{sys.executable}" prepare_data.py --source_dir "{source_video_dir}" --output_rgb_dir "{output_rgb_dir}" --output_flow_dir "{output_flow_dir}" --model "{raft_model}"' - run_command(cmd_prepare) - - # Step 2: Run Standard Evaluation - print("\n[Step 2] Running Standard Evaluation (AIGVDet, Sspatial, Soptical)...") - cmd_test = f'"{sys.executable}" test.py -fop "{output_flow_dir}" -for "{output_rgb_dir}" -mop "{optical_model}" -mor "{original_model}" -e "{result_csv}"' - run_command(cmd_test) - - # Step 3: Run No-Crop Evaluation - print("\n[Step 3] Running No-Crop Evaluation (Soptical no cp)...") - cmd_test_no_cp = f'"{sys.executable}" test.py --no_crop -fop "{output_flow_dir}" -for "{output_rgb_dir}" -mop "{optical_model}" -mor "{original_model}" -e "{result_no_cp_csv}"' - run_command(cmd_test_no_cp) - - print("\n--- Done! ---") - print(f"Standard Results saved to: {result_csv}") - print(f"No-Crop Results saved to: {result_no_cp_csv}") - -if __name__ == "__main__": - main() diff --git a/test.sh b/test.sh deleted file mode 100644 index c870a97..0000000 --- a/test.sh +++ /dev/null @@ -1 +0,0 @@ -python test.py -fop "data/test/T2V/hotshot" -mop "checkpoints/optical_aug.pth" -for "data/test/original/T2V/hotshot" -mor "checkpoints/original_aug.pth" -e "data/results/T2V/hotshot.csv" -ef "data/results/frame/T2V/hotshot.csv" -t 0.5 \ No newline at end of file diff --git a/train.sh b/train.sh deleted file mode 100644 index d8dd0b3..0000000 --- a/train.sh +++ /dev/null @@ -1,4 +0,0 @@ -EXP_NAME="moonvalley_vos2_crop" -DATASETS="moonvalley_vos2_crop" -DATASETS_TEST="moonvalley_vos2_crop" -python train.py --gpus 0 --exp_name $EXP_NAME datasets $DATASETS datasets_test $DATASETS_TEST \ No newline at end of file From 4567fa80d8eba6dd31695bbd606f68a56b250699 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:15:52 +0800 Subject: [PATCH 48/55] fix: Update custom_resize function and enhance validation results for W&B integration --- core/utils1/datasets.py | 4 ++-- core/utils1/eval.py | 8 +++++--- core/utils1/trainer.py | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index aa20f29..273e495 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -149,11 +149,11 @@ def jpeg_from_key(img: np.ndarray, compress_val: int, key: str) -> np.ndarray: 'bicubic': Image.BICUBIC, 'lanczos': Image.LANCZOS, 'nearest': Image.NEAREST} -def custom_resize(img: Image.Image, cfg: CONFIGCLASS) -> Image.Image: + +def custom_resize(img: Image.Image, cfg: CONFIGCLASS) -> Image.Image: # added to implement 70 to 90 interp = sample_discrete(cfg.rz_interp) return TF.resize(img, cfg.loadSize, interpolation=rz_dict[interp]) - def get_dataset(cfg: CONFIGCLASS): dset_lst = [] for dataset in cfg.datasets: diff --git a/core/utils1/eval.py b/core/utils1/eval.py index b93d2f8..d896977 100644 --- a/core/utils1/eval.py +++ b/core/utils1/eval.py @@ -63,13 +63,15 @@ def validate(model: nn.Module, cfg: CONFIGCLASS): tpr = f_acc # TPR is the accuracy on fake samples (class 1) tnr = r_acc # TNR is the accuracy on real samples (class 0) + + # added values for results - wandb integration results = { "ACC": acc, "AP": ap, "AUC": auc, - "R_ACC": r_acc, + "R_ACC": r_acc, "F_ACC": f_acc, - "TPR": tpr, - "TNR": tnr, + "TPR": tpr, # True Positive Rate / Recall + "TNR": tnr, # True Negative Rate / Specificity } return results diff --git a/core/utils1/trainer.py b/core/utils1/trainer.py index 6739243..99534d0 100644 --- a/core/utils1/trainer.py +++ b/core/utils1/trainer.py @@ -20,6 +20,7 @@ def __init__(self, cfg: CONFIGCLASS): self.model:nn.Module # self.model.to(self.device) #self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) + # removed self.model=nnModule.to(self.device) breaks the model assignment self.optimizer: torch.optim.Optimizer def save_networks(self, epoch: int): From 26be75ba19ec658fc25ca8eada6533cc88981d99 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:41:50 +0800 Subject: [PATCH 49/55] fix: Update Dockerfile to remove unnecessary scripts and enhance dataset transformation --- Dockerfile.gpu-alt | 2 +- core/utils1/datasets.py | 3 +-- python-utils/download_data.py => download_data.py | 0 3 files changed, 2 insertions(+), 3 deletions(-) rename python-utils/download_data.py => download_data.py (100%) diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index d63a70d..2b23046 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -32,7 +32,7 @@ COPY pyproject.toml ./ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ -COPY train.py test.py demo.py download_data.py recreate_table_2.py prepare_data.py ./ +COPY train.py test.py demo.py download_data.py ./ # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model diff --git a/core/utils1/datasets.py b/core/utils1/datasets.py index 273e495..cac43e8 100644 --- a/core/utils1/datasets.py +++ b/core/utils1/datasets.py @@ -57,9 +57,8 @@ def binary_dataset(root: str, cfg: CONFIGCLASS): transforms.Compose( [ rz_func, - #change + transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), #change crop_func, - transforms.Lambda(lambda img: blur_jpg_augment(img, cfg)), flip_func, transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) diff --git a/python-utils/download_data.py b/download_data.py similarity index 100% rename from python-utils/download_data.py rename to download_data.py From 5dbfa7928c4870856ddfeed8af93ba295e30db43 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 29 Nov 2025 02:02:00 +0800 Subject: [PATCH 50/55] fix: Update dataset names in DefaultConfigs to align with test/train structure and remove trainer_optical.py --- core/utils1/config.py | 4 +- core/utils1/trainer_optical.py | 169 --------------------------------- 2 files changed, 2 insertions(+), 171 deletions(-) delete mode 100644 core/utils1/trainer_optical.py diff --git a/core/utils1/config.py b/core/utils1/config.py index c8c9f3e..96cf963 100644 --- a/core/utils1/config.py +++ b/core/utils1/config.py @@ -10,8 +10,8 @@ class DefaultConfigs(ABC): gpus = [0] seed = 3407 arch = "resnet50" - datasets = ["trainset_1"] - datasets_test = ["val_set_1"] + datasets = ["trainset_1"] # Changed from trainset_1 to match test/train structure + datasets_test = ["val_set_1"] # Changed from val_set_1 to match test/val structure mode = "binary" class_bal = False batch_size = 64 # RTX 3090 24GB can handle original batch size diff --git a/core/utils1/trainer_optical.py b/core/utils1/trainer_optical.py deleted file mode 100644 index 7a37fb3..0000000 --- a/core/utils1/trainer_optical.py +++ /dev/null @@ -1,169 +0,0 @@ -import os - -import torch -import torch.nn as nn -from torch.nn import init - -from core.utils1.config import CONFIGCLASS -from core.utils1.utils import get_network -from core.utils1.warmup import GradualWarmupScheduler - - -class BaseModel(nn.Module): - def __init__(self, cfg: CONFIGCLASS): - super().__init__() - self.cfg = cfg - self.total_steps = 0 - self.isTrain = cfg.isTrain - self.save_dir = cfg.ckpt_dir - self.device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") - self.model:nn.Module - # self.model.to(self.device) - #self.model.load_state_dict(torch.load('./checkpoints/optical.pth')) - self.optimizer: torch.optim.Optimizer - - def save_networks(self, epoch: int): - save_filename = f"model_epoch_{epoch}.pth" - save_path = os.path.join(self.save_dir, save_filename) - - # serialize model and optimizer to dict - state_dict = { - "model": self.model.state_dict(), - "optimizer": self.optimizer.state_dict(), - "total_steps": self.total_steps, - } - - torch.save(state_dict, save_path) - - # load models from the disk - def load_networks(self, epoch: int): - load_filename = f"model_epoch_{epoch}.pth" - load_path = os.path.join(self.save_dir, load_filename) - - if epoch==0: - # load_filename = f"lsun_adm.pth" - load_path="checkpoints/optical.pth" - print("loading optical path") - else : - print(f"loading the model from {load_path}") - - # print(f"loading the model from {load_path}") - - # if you are using PyTorch newer than 0.4 (e.g., built from - # GitHub source), you can remove str() on self.device - state_dict = torch.load(load_path, map_location=self.device) - if hasattr(state_dict, "_metadata"): - del state_dict._metadata - - self.model.load_state_dict(state_dict["model"]) - self.total_steps = state_dict["total_steps"] - - if self.isTrain and not self.cfg.new_optim: - self.optimizer.load_state_dict(state_dict["optimizer"]) - # move optimizer state to GPU - for state in self.optimizer.state.values(): - for k, v in state.items(): - if torch.is_tensor(v): - state[k] = v.to(self.device) - - for g in self.optimizer.param_groups: - g["lr"] = self.cfg.lr - - def eval(self): - self.model.eval() - - def test(self): - with torch.no_grad(): - self.forward() - - -def init_weights(net: nn.Module, init_type="normal", gain=0.02): - def init_func(m: nn.Module): - classname = m.__class__.__name__ - if hasattr(m, "weight") and (classname.find("Conv") != -1 or classname.find("Linear") != -1): - if init_type == "normal": - init.normal_(m.weight.data, 0.0, gain) - elif init_type == "xavier": - init.xavier_normal_(m.weight.data, gain=gain) - elif init_type == "kaiming": - init.kaiming_normal_(m.weight.data, a=0, mode="fan_in") - elif init_type == "orthogonal": - init.orthogonal_(m.weight.data, gain=gain) - else: - raise NotImplementedError(f"initialization method [{init_type}] is not implemented") - if hasattr(m, "bias") and m.bias is not None: - init.constant_(m.bias.data, 0.0) - elif classname.find("BatchNorm2d") != -1: - init.normal_(m.weight.data, 1.0, gain) - init.constant_(m.bias.data, 0.0) - - print(f"initialize network with {init_type}") - net.apply(init_func) - - -class Trainer(BaseModel): - def name(self): - return "Trainer" - - def __init__(self, cfg: CONFIGCLASS): - super().__init__(cfg) - self.arch = cfg.arch - self.model = get_network(self.arch, cfg.isTrain, cfg.continue_train, cfg.init_gain, cfg.pretrained) - - self.loss_fn = nn.BCEWithLogitsLoss() - # initialize optimizers - if cfg.optim == "adam": - self.optimizer = torch.optim.Adam(self.model.parameters(), lr=cfg.lr, betas=(cfg.beta1, 0.999)) - elif cfg.optim == "sgd": - self.optimizer = torch.optim.SGD(self.model.parameters(), lr=cfg.lr, momentum=0.9, weight_decay=5e-4) - else: - raise ValueError("optim should be [adam, sgd]") - if cfg.warmup: - scheduler_cosine = torch.optim.lr_scheduler.CosineAnnealingLR( - self.optimizer, cfg.nepoch - cfg.warmup_epoch, eta_min=1e-6 - ) - self.scheduler = GradualWarmupScheduler( - self.optimizer, multiplier=1, total_epoch=cfg.warmup_epoch, after_scheduler=scheduler_cosine - ) - self.scheduler.step() - if cfg.continue_train: - self.load_networks(cfg.epoch) - self.model.to(self.device) - - # OPTICAL FLOW: Force load pretrained optical.pth checkpoint - print("Loading pretrained optical flow checkpoint...") - load_path = 'checkpoints/optical.pth' - state_dict = torch.load(load_path, map_location=self.device) - self.model.load_state_dict(state_dict["model"]) - print(f"✓ Loaded optical flow checkpoint from {load_path}") - - - - def adjust_learning_rate(self, min_lr=1e-6): - for param_group in self.optimizer.param_groups: - param_group["lr"] /= 10.0 - if param_group["lr"] < min_lr: - return False - return True - - def set_input(self, input): - img, label, meta = input if len(input) == 3 else (input[0], input[1], {}) - self.input = img.to(self.device) - self.label = label.to(self.device).float() - for k in meta.keys(): - if isinstance(meta[k], torch.Tensor): - meta[k] = meta[k].to(self.device) - self.meta = meta - - def forward(self): - self.output = self.model(self.input, self.meta) - - def get_loss(self): - return self.loss_fn(self.output.squeeze(1), self.label) - - def optimize_parameters(self): - self.forward() - self.loss = self.loss_fn(self.output.squeeze(1), self.label) - self.optimizer.zero_grad() - self.loss.backward() - self.optimizer.step() From 14677f5db8f47905735b38d49aa4ee1bfe779262 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:42:38 +0800 Subject: [PATCH 51/55] feat: Add comprehensive Table 2 recreation and evaluation framework, data preparation and utility scripts, and a GUI, while updating training and testing modules. --- batch_prepare.py | 48 +++++ batch_prepare_all.py | 200 ++++++++++++++++++ batch_prepare_i2v.py | 199 ++++++++++++++++++ clean_extra_frames.py | 81 ++++++++ compile_table2_data2.py | 70 +++++++ compile_table2_data3.py | 70 +++++++ extract_test_data.py | 160 +++++++++++++++ gui_app.py | 364 +++++++++++++++++++++++++++++++++ prepare_real_data.py | 175 ++++++++++++++++ python-utils/prepare_data.py | 9 +- recreate_table2_data2.py | 309 ++++++++++++++++++++++++++++ recreate_table2_data3.py | 309 ++++++++++++++++++++++++++++ recreate_table2_final.py | 382 +++++++++++++++++++++++++++++++++++ run_gui.bat | 5 + setup_test_data.py | 254 +++++++++++++++++++++++ test.py | 4 +- test_single_stream.py | 333 ++++++++++++++++++++++++++++++ train.py | 23 +++ 18 files changed, 2989 insertions(+), 6 deletions(-) create mode 100644 batch_prepare.py create mode 100644 batch_prepare_all.py create mode 100644 batch_prepare_i2v.py create mode 100644 clean_extra_frames.py create mode 100644 compile_table2_data2.py create mode 100644 compile_table2_data3.py create mode 100644 extract_test_data.py create mode 100644 gui_app.py create mode 100644 prepare_real_data.py create mode 100644 recreate_table2_data2.py create mode 100644 recreate_table2_data3.py create mode 100644 recreate_table2_final.py create mode 100644 run_gui.bat create mode 100644 setup_test_data.py create mode 100644 test_single_stream.py diff --git a/batch_prepare.py b/batch_prepare.py new file mode 100644 index 0000000..11485f3 --- /dev/null +++ b/batch_prepare.py @@ -0,0 +1,48 @@ +""" +Batch Data Preparation Script +Automatically finds all *_mp4 folders in data/test/T2V and processes them. +""" +import os +import glob +import subprocess +from pathlib import Path + +# Configuration +SOURCE_ROOT = "data/test/T2V" +RGB_OUTPUT_ROOT = "data/test/original/T2V" +FLOW_OUTPUT_ROOT = "data/test/T2V" +RAFT_MODEL = "raft-model/raft-things.pth" +MAX_VIDEOS = 100 # Limit to 100 videos per dataset for faster processing + +def main(): + # Find all folders ending in _mp4 + source_folders = glob.glob(os.path.join(SOURCE_ROOT, "*_mp4")) + + print(f"Found {len(source_folders)} datasets to process: {[os.path.basename(f) for f in source_folders]}") + + for source_dir in source_folders: + dataset_name = os.path.basename(source_dir).replace("_mp4", "") + + print(f"\n{'='*60}") + print(f"Processing: {dataset_name}") + print(f"{'='*60}") + + # Define output paths + rgb_out = os.path.join(RGB_OUTPUT_ROOT, dataset_name) + flow_out = os.path.join(FLOW_OUTPUT_ROOT, dataset_name) + + cmd = [ + "python", "prepare_data.py", + "--source_dir", source_dir, + "--output_rgb_dir", rgb_out, + "--output_flow_dir", flow_out, + "--model", RAFT_MODEL, + "--label", "1_fake", # Assuming these are all generated video folders + "--max_videos", str(MAX_VIDEOS) + ] + + print(f"Command: {' '.join(cmd)}") + subprocess.run(cmd) + +if __name__ == "__main__": + main() diff --git a/batch_prepare_all.py b/batch_prepare_all.py new file mode 100644 index 0000000..fa212fb --- /dev/null +++ b/batch_prepare_all.py @@ -0,0 +1,200 @@ +""" +Batch Data Preparation Script for All T2V Models +Automatically finds all *_mp4 folders in data/test/T2V and processes them. +""" +import os +import glob +import cv2 +import numpy as np +import torch +import sys +from PIL import Image +from tqdm import tqdm +from pathlib import Path + +# Add core to path for RAFT +sys.path.append('core') +from raft import RAFT +from utils import flow_viz +from utils.utils import InputPadder + +DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' + +# Configuration +SOURCE_ROOT = "data/test/T2V" +RGB_OUTPUT_ROOT = "data/test/original/T2V" +FLOW_OUTPUT_ROOT = "data/test/T2V" +RAFT_MODEL = "raft_model/raft-things.pth" +MAX_VIDEOS = 500 # Limit to 500 videos per dataset + +def load_image(imfile): + """Load image and convert to tensor""" + img = Image.open(imfile) + + # Resize if too large to prevent OOM + max_dim = 1024 + if max(img.size) > max_dim: + scale = max_dim / max(img.size) + new_size = (int(img.size[0] * scale), int(img.size[1] * scale)) + img = img.resize(new_size, Image.BILINEAR) + + img = np.array(img).astype(np.uint8) + img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) + +def save_flow(img, flo, output_path): + """Save optical flow as image""" + img = img[0].permute(1, 2, 0).cpu().numpy() + flo = flo[0].permute(1, 2, 0).cpu().numpy() + flo = flow_viz.flow_to_image(flo) + cv2.imwrite(output_path, flo) + +def extract_frames(video_path, output_folder, max_frames=95): + """Extract frames from video (limit to max_frames)""" + os.makedirs(output_folder, exist_ok=True) + + # Check if already extracted + existing = glob.glob(os.path.join(output_folder, "*.png")) + if len(existing) > 0: + return sorted(existing)[:max_frames] + + cap = cv2.VideoCapture(video_path) + frame_count = 0 + frames = [] + + while cap.isOpened() and frame_count < max_frames: + ret, frame = cap.read() + if not ret: + break + + frame_path = os.path.join(output_folder, f"{frame_count:08d}.png") + cv2.imwrite(frame_path, frame) + frames.append(frame_path) + frame_count += 1 + + cap.release() + return frames + +def generate_optical_flow(model, frames, output_dir): + """Generate optical flow for frame sequence""" + os.makedirs(output_dir, exist_ok=True) + + with torch.no_grad(): + for i, (imfile1, imfile2) in enumerate(tqdm(zip(frames[:-1], frames[1:]), + total=len(frames)-1, + desc=" Generating flow")): + flow_path = os.path.join(output_dir, f"{i:08d}.png") + + if os.path.exists(flow_path): + continue + + image1 = load_image(imfile1) + image2 = load_image(imfile2) + + padder = InputPadder(image1.shape) + image1, image2 = padder.pad(image1, image2) + + flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) + save_flow(image1, flow_up, flow_path) + +def process_dataset(dataset_name, source_dir, model): + """Process a single T2V dataset""" + print(f"\n{'='*60}") + print(f"Processing: {dataset_name}") + print(f"{'='*60}") + + # Get all videos + videos = glob.glob(os.path.join(source_dir, "*.mp4")) + \ + glob.glob(os.path.join(source_dir, "*.avi")) + \ + glob.glob(os.path.join(source_dir, "*.mov")) + + # Apply limit + if len(videos) > MAX_VIDEOS: + videos = videos[:MAX_VIDEOS] + print(f"Limited to {MAX_VIDEOS} videos") + + print(f"Found {len(videos)} videos to process") + + # Output directories + rgb_base = os.path.join(RGB_OUTPUT_ROOT, dataset_name, "1_fake") + flow_base = os.path.join(FLOW_OUTPUT_ROOT, dataset_name, "1_fake") + + # Process each video + for idx, video_path in enumerate(videos, 1): + video_name = Path(video_path).stem + print(f"\n[{idx}/{len(videos)}] {video_name}") + + rgb_out = os.path.join(rgb_base, video_name) + flow_out = os.path.join(flow_base, video_name) + + # Extract frames + print(" Extracting frames...") + frames = extract_frames(video_path, rgb_out) + + if len(frames) < 2: + print(" ⚠ Skipping (too few frames)") + continue + + # Generate optical flow + generate_optical_flow(model, frames, flow_out) + + print(f"\n✅ Completed {dataset_name}") + +def main(): + # Find all T2V dataset folders + source_folders = glob.glob(os.path.join(SOURCE_ROOT, "*_mp4")) + + if not source_folders: + print(f"❌ No *_mp4 folders found in {SOURCE_ROOT}") + return + + print("="*60) + print("BATCH T2V DATA PREPARATION") + print("="*60) + print(f"\nFound {len(source_folders)} dataset(s):") + for folder in source_folders: + dataset_name = os.path.basename(folder).replace("_mp4", "") + print(f" 📁 {dataset_name}") + + print(f"\nConfiguration:") + print(f" • Max videos per dataset: {MAX_VIDEOS}") + print(f" • Max frames per video: 95 (RGB) / 94 (Flow)") + print(f" • RAFT model: {RAFT_MODEL}") + print("="*60) + + # Load RAFT model + print("\nLoading RAFT model...") + import argparse + raft_args = argparse.Namespace( + model=RAFT_MODEL, + small=False, + mixed_precision=False, + alternate_corr=False, + dropout=0 + ) + + model = torch.nn.DataParallel(RAFT(raft_args)) + model.load_state_dict(torch.load(RAFT_MODEL, map_location=torch.device(DEVICE))) + model = model.module + model.to(DEVICE) + model.eval() + print("✓ RAFT model loaded") + + # Process each dataset + for idx, source_dir in enumerate(source_folders, 1): + dataset_name = os.path.basename(source_dir).replace("_mp4", "") + + try: + process_dataset(dataset_name, source_dir, model) + except Exception as e: + print(f"\n❌ Error processing {dataset_name}: {e}") + response = input("Continue? (Y/n): ").strip().lower() + if response == 'n': + break + + print("\n" + "="*60) + print("✅ BATCH PROCESSING COMPLETE!") + print("="*60) + +if __name__ == "__main__": + main() diff --git a/batch_prepare_i2v.py b/batch_prepare_i2v.py new file mode 100644 index 0000000..d97ba8a --- /dev/null +++ b/batch_prepare_i2v.py @@ -0,0 +1,199 @@ +""" +Batch Data Preparation Script for I2V Models +Processes all *_mp4 folders in data/I2V +""" +import os +import glob +import cv2 +import numpy as np +import torch +import sys +from PIL import Image +from tqdm import tqdm +from pathlib import Path + +# Add core to path for RAFT +sys.path.append('core') +from raft import RAFT +from utils import flow_viz +from utils.utils import InputPadder + +DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' + +# Configuration +SOURCE_ROOT = "data/I2V" +RGB_OUTPUT_ROOT = "data/test/original/I2V" +FLOW_OUTPUT_ROOT = "data/test/I2V" +RAFT_MODEL = "raft_model/raft-things.pth" +MAX_VIDEOS = 200 # Limit to 200 videos per dataset + +def load_image(imfile): + """Load image and convert to tensor""" + img = Image.open(imfile) + + # Resize if too large to prevent OOM + max_dim = 1024 + if max(img.size) > max_dim: + scale = max_dim / max(img.size) + new_size = (int(img.size[0] * scale), int(img.size[1] * scale)) + img = img.resize(new_size, Image.BILINEAR) + + img = np.array(img).astype(np.uint8) + img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) + +def save_flow(img, flo, output_path): + """Save optical flow as image""" + img = img[0].permute(1, 2, 0).cpu().numpy() + flo = flo[0].permute(1, 2, 0).cpu().numpy() + flo = flow_viz.flow_to_image(flo) + cv2.imwrite(output_path, flo) + +def extract_frames(video_path, output_folder, max_frames=95): + """Extract frames from video (limit to max_frames)""" + os.makedirs(output_folder, exist_ok=True) + + # Check if already extracted + existing = glob.glob(os.path.join(output_folder, "*.png")) + if len(existing) > 0: + return sorted(existing)[:max_frames] # Return only up to max_frames + + cap = cv2.VideoCapture(video_path) + frame_count = 0 + frames = [] + + while cap.isOpened() and frame_count < max_frames: + ret, frame = cap.read() + if not ret: + break + + frame_path = os.path.join(output_folder, f"{frame_count:08d}.png") + cv2.imwrite(frame_path, frame) + frames.append(frame_path) + frame_count += 1 + + cap.release() + return frames + +def generate_optical_flow(model, frames, output_dir): + """Generate optical flow for frame sequence""" + os.makedirs(output_dir, exist_ok=True) + + with torch.no_grad(): + for i, (imfile1, imfile2) in enumerate(tqdm(zip(frames[:-1], frames[1:]), + total=len(frames)-1, + desc=" Generating flow")): + flow_path = os.path.join(output_dir, f"{i:08d}.png") + + if os.path.exists(flow_path): + continue + + image1 = load_image(imfile1) + image2 = load_image(imfile2) + + padder = InputPadder(image1.shape) + image1, image2 = padder.pad(image1, image2) + + flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) + save_flow(image1, flow_up, flow_path) + +def process_dataset(dataset_name, source_dir, model): + """Process a single I2V dataset""" + print(f"\n{'='*60}") + print(f"Processing: {dataset_name}") + print(f"{'='*60}") + + # Get all videos + videos = glob.glob(os.path.join(source_dir, "*.mp4")) + \ + glob.glob(os.path.join(source_dir, "*.avi")) + \ + glob.glob(os.path.join(source_dir, "*.mov")) + + # Apply limit + if len(videos) > MAX_VIDEOS: + videos = videos[:MAX_VIDEOS] + print(f"Limited to {MAX_VIDEOS} videos") + + print(f"Found {len(videos)} videos to process") + + # Output directories + rgb_base = os.path.join(RGB_OUTPUT_ROOT, dataset_name, "1_fake") + flow_base = os.path.join(FLOW_OUTPUT_ROOT, dataset_name, "1_fake") + + # Process each video + for idx, video_path in enumerate(videos, 1): + video_name = Path(video_path).stem + print(f"\n[{idx}/{len(videos)}] {video_name}") + + rgb_out = os.path.join(rgb_base, video_name) + flow_out = os.path.join(flow_base, video_name) + + # Extract frames + print(" Extracting frames...") + frames = extract_frames(video_path, rgb_out) + + if len(frames) < 2: + print(" ⚠ Skipping (too few frames)") + continue + + # Generate optical flow + generate_optical_flow(model, frames, flow_out) + + print(f"\n✅ Completed {dataset_name}") + +def main(): + # Find all I2V dataset folders + source_folders = glob.glob(os.path.join(SOURCE_ROOT, "*_mp4")) + + if not source_folders: + print(f"❌ No *_mp4 folders found in {SOURCE_ROOT}") + return + + print("="*60) + print("BATCH I2V DATA PREPARATION") + print("="*60) + print(f"\nFound {len(source_folders)} dataset(s):") + for folder in source_folders: + dataset_name = os.path.basename(folder).replace("_mp4", "") + print(f" 📁 {dataset_name}") + + print(f"\nConfiguration:") + print(f" • Max videos per dataset: {MAX_VIDEOS}") + print(f" • RAFT model: {RAFT_MODEL}") + print("="*60) + + # Load RAFT model + print("\nLoading RAFT model...") + import argparse + raft_args = argparse.Namespace( + model=RAFT_MODEL, + small=False, + mixed_precision=False, + alternate_corr=False, + dropout=0 + ) + + model = torch.nn.DataParallel(RAFT(raft_args)) + model.load_state_dict(torch.load(RAFT_MODEL, map_location=torch.device(DEVICE))) + model = model.module + model.to(DEVICE) + model.eval() + print("✓ RAFT model loaded") + + # Process each dataset + for idx, source_dir in enumerate(source_folders, 1): + dataset_name = os.path.basename(source_dir).replace("_mp4", "") + + try: + process_dataset(dataset_name, source_dir, model) + except Exception as e: + print(f"\n❌ Error processing {dataset_name}: {e}") + response = input("Continue? (Y/n): ").strip().lower() + if response == 'n': + break + + print("\n" + "="*60) + print("✅ BATCH PROCESSING COMPLETE!") + print("="*60) + +if __name__ == "__main__": + main() diff --git a/clean_extra_frames.py b/clean_extra_frames.py new file mode 100644 index 0000000..8109291 --- /dev/null +++ b/clean_extra_frames.py @@ -0,0 +1,81 @@ +import os +import glob +import argparse + +def clean_extra_frames(base_dir, max_frame_index=94): + print(f"Cleaning frames with index > {max_frame_index} in {base_dir}") + + # Datasets to check + datasets = ["moonvalley", "videocraft", "pika", "neverends"] + + total_deleted = 0 + + for dataset in datasets: + target_dir = os.path.join(base_dir, dataset, "0_real") + + if not os.path.exists(target_dir): + print(f"Skipping {target_dir} (not found)") + continue + + print(f"Scanning {target_dir}...") + + # Check if it's flat files or folders + # Based on previous ls, it seems to be flat files for 0_real in some contexts, + # but prepare_real_data.py copies folders: shutil.copytree(temp_flow_dir, flow_dest) + # Let's handle both cases. + + items = os.listdir(target_dir) + for item in items: + item_path = os.path.join(target_dir, item) + + if os.path.isdir(item_path): + # It's a video folder + video_name = item + frames = glob.glob(os.path.join(item_path, "*")) + for frame in frames: + if should_delete(frame, max_frame_index): + os.remove(frame) + total_deleted += 1 + else: + # It's a flat file + if should_delete(item_path, max_frame_index): + os.remove(item_path) + total_deleted += 1 + + print(f"\n✓ Cleanup complete. Deleted {total_deleted} extra frames.") + +def should_delete(filepath, max_index): + filename = os.path.basename(filepath) + name_part = os.path.splitext(filename)[0] + + # Case 1: Filename is just a number (e.g. 00000094.jpg) + if name_part.isdigit(): + idx = int(name_part) + if idx > 90: + print(f" Checking {filename}: Index {idx} > {max_index}? {idx > max_index}") + if idx > max_index: + return True + + # Case 2: Filename has underscores (e.g. video_00000094.jpg) + parts = name_part.split('_') + if len(parts) > 1: + last_part = parts[-1] + if last_part.isdigit(): + idx = int(last_part) + if idx > 90: + print(f" Checking {filename}: Index {idx} > {max_index}? {idx > max_index}") + if idx > max_index: + return True + + return False + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dry-run", action="store_true", help="Print what would be deleted without deleting") + args = parser.parse_args() + + # Optical Flow path (Limit to 94 frames -> max index 93) + clean_extra_frames("data/test/T2V", max_frame_index=93) + + # RGB path (Limit to 95 frames -> max index 94) + clean_extra_frames("data/test/original/T2V", max_frame_index=94) diff --git a/compile_table2_data2.py b/compile_table2_data2.py new file mode 100644 index 0000000..b0b8b19 --- /dev/null +++ b/compile_table2_data2.py @@ -0,0 +1,70 @@ + """ + Quick script to compile Table 2 from existing CSV files in data2/results/table2/ + """ + + import pandas as pd + from pathlib import Path + from sklearn.metrics import accuracy_score, roc_auc_score, average_precision_score + + RESULTS_DIR = Path("data2/results/table2") + + DATASETS = ["emu", "hotshot", "sora"] + VARIANTS = ["S_spatial", "S_optical", "AIGVDet"] + + def get_metrics_from_csv(csv_path): + """Calculate metrics from CSV file""" + try: + df = pd.read_csv(csv_path) + + if len(df) == 0: + return None + + y_true = df['flag'].values + y_pred = df['pro'].values + + acc = accuracy_score(y_true, (y_pred >= 0.5).astype(int)) + auc = roc_auc_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + ap = average_precision_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + + return {'ACC': acc, 'AUC': auc, 'AP': ap} + except Exception as e: + print(f"Error reading {csv_path}: {e}") + return None + + # Compile results + rows = [] + for dataset in DATASETS: + row = {"Dataset": dataset} + + for variant in VARIANTS: + csv_file = RESULTS_DIR / f"{dataset}_{variant}_video.csv" + + if csv_file.exists(): + metrics = get_metrics_from_csv(csv_file) + if metrics: + acc = metrics['ACC'] * 100 + auc = metrics['AUC'] * 100 + row[variant] = f"{acc:.1f}/{auc:.1f}" + print(f"✓ {dataset} - {variant}: ACC={acc:.1f}%, AUC={auc:.1f}%") + else: + row[variant] = "N/A" + print(f"✗ {dataset} - {variant}: No data") + else: + row[variant] = "N/A" + print(f"✗ {dataset} - {variant}: File not found") + + rows.append(row) + + # Create and save table + df = pd.DataFrame(rows) + + output_file = RESULTS_DIR / "table2_data2_compiled.csv" + df.to_csv(output_file, index=False) + + print("\n" + "="*60) + print("TABLE 2 (DATA2)") + print("="*60) + print(df.to_string(index=False)) + print("\n" + "="*60) + print(f"✓ Saved to: {output_file}") + print("="*60) diff --git a/compile_table2_data3.py b/compile_table2_data3.py new file mode 100644 index 0000000..b3656c2 --- /dev/null +++ b/compile_table2_data3.py @@ -0,0 +1,70 @@ +""" +Quick script to compile Table 2 from existing CSV files in data3/results/table2/ +""" + +import pandas as pd +from pathlib import Path +from sklearn.metrics import accuracy_score, roc_auc_score, average_precision_score + +RESULTS_DIR = Path("data3/results/table2") + +DATASETS = ["moonvalley", "pika", "neverends"] +VARIANTS = ["S_spatial", "S_optical", "AIGVDet"] + +def get_metrics_from_csv(csv_path): + """Calculate metrics from CSV file""" + try: + df = pd.read_csv(csv_path) + + if len(df) == 0: + return None + + y_true = df['flag'].values + y_pred = df['pro'].values + + acc = accuracy_score(y_true, (y_pred >= 0.5).astype(int)) + auc = roc_auc_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + ap = average_precision_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + + return {'ACC': acc, 'AUC': auc, 'AP': ap} + except Exception as e: + print(f"Error reading {csv_path}: {e}") + return None + +# Compile results +rows = [] +for dataset in DATASETS: + row = {"Dataset": dataset} + + for variant in VARIANTS: + csv_file = RESULTS_DIR / f"{dataset}_{variant}_video.csv" + + if csv_file.exists(): + metrics = get_metrics_from_csv(csv_file) + if metrics: + acc = metrics['ACC'] * 100 + auc = metrics['AUC'] * 100 + row[variant] = f"{acc:.1f}/{auc:.1f}" + print(f"✓ {dataset} - {variant}: ACC={acc:.1f}%, AUC={auc:.1f}%") + else: + row[variant] = "N/A" + print(f"✗ {dataset} - {variant}: No data") + else: + row[variant] = "N/A" + print(f"✗ {dataset} - {variant}: File not found") + + rows.append(row) + +# Create and save table +df = pd.DataFrame(rows) + +output_file = RESULTS_DIR / "table2_data3_i2v_compiled.csv" +df.to_csv(output_file, index=False) + +print("\n" + "="*60) +print("TABLE 2 (DATA3 - I2V)") +print("="*60) +print(df.to_string(index=False)) +print("\n" + "="*60) +print(f"✓ Saved to: {output_file}") +print("="*60) diff --git a/extract_test_data.py b/extract_test_data.py new file mode 100644 index 0000000..508d146 --- /dev/null +++ b/extract_test_data.py @@ -0,0 +1,160 @@ +""" +Manual extraction script for test data +Use this if automatic download fails due to Google Drive rate limits + +INSTRUCTIONS: +1. Manually download the test folder from Google Drive +2. Place all zip files in: data/temp_download/ +3. Run this script to extract everything + +Expected zip files: +- moonvalley-optical.zip +- videocraft-optical.zip +- pika-optical.zip +- neverends-optical.zip +- moonvalley-rgb.zip +- videocraft-rgb.zip +- pika-rgb.zip +- neverends-rgb.zip +""" + +import zipfile +from pathlib import Path +import shutil + +# Paths +DATA_DIR = Path("data") +TEST_DIR = DATA_DIR / "test" +TEMP_DIR = DATA_DIR / "temp_download" + +def extract_zip(zip_path, extract_to): + """Extract a single zip file""" + print(f" Extracting: {zip_path.name}") + print(f" → {extract_to}") + try: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + print(f" ✓ Done") + return True + except Exception as e: + print(f" ❌ Error: {e}") + return False + +def main(): + print("="*80) + print("MANUAL TEST DATA EXTRACTION") + print("="*80) + + # Check if temp directory exists + if not TEMP_DIR.exists(): + print(f"\n❌ Directory not found: {TEMP_DIR}") + print(f"\nPlease create it and place your zip files there:") + print(f" mkdir {TEMP_DIR}") + return + + # Find all zip files + all_zips = list(TEMP_DIR.rglob("*.zip")) + + if not all_zips: + print(f"\n❌ No zip files found in: {TEMP_DIR}") + print("\nPlease download the following files from Google Drive:") + print(" https://drive.google.com/drive/folders/1D1jm1_HCu0Nv21NVjuyL1CB5gF5sy0hx") + print(f"\nAnd place them in: {TEMP_DIR.absolute()}") + return + + print(f"\n✓ Found {len(all_zips)} zip files") + for z in all_zips: + print(f" - {z.name}") + + # Create target directories + optical_base = TEST_DIR / "T2V" + rgb_base = TEST_DIR / "original" / "T2V" + optical_base.mkdir(parents=True, exist_ok=True) + rgb_base.mkdir(parents=True, exist_ok=True) + + # Extract optical flow data + print(f"\n{'='*80}") + print("EXTRACTING OPTICAL FLOW DATA") + print("="*80) + + optical_zips = [z for z in all_zips if "-optical" in z.name.lower()] + for zip_file in optical_zips: + dataset_name = zip_file.stem.replace("-optical", "").replace("-Optical", "") + target_dir = optical_base / dataset_name + target_dir.mkdir(parents=True, exist_ok=True) + extract_zip(zip_file, target_dir) + + # Extract RGB data + print(f"\n{'='*80}") + print("EXTRACTING RGB DATA") + print("="*80) + + rgb_zips = [z for z in all_zips if "-rgb" in z.name.lower()] + for zip_file in rgb_zips: + dataset_name = zip_file.stem.replace("-rgb", "").replace("-RGB", "") + target_dir = rgb_base / dataset_name + target_dir.mkdir(parents=True, exist_ok=True) + extract_zip(zip_file, target_dir) + + # Verify structure + print(f"\n{'='*80}") + print("VERIFYING STRUCTURE") + print("="*80) + + datasets = ["moonvalley", "videocraft", "pika", "neverends"] + all_good = True + + print("\nOptical flow:") + for dataset in datasets: + path = TEST_DIR / "T2V" / dataset + if path.exists(): + real = (path / "0_real").exists() + fake = (path / "1_fake").exists() + status = f"[Real: {'✓' if real else '✗'}, Fake: {'✓' if fake else '✗'}]" + print(f" {'✓' if (real and fake) else '⚠️ '} {dataset}: {status}") + if not (real and fake): + all_good = False + else: + print(f" ❌ {dataset}: NOT FOUND") + all_good = False + + print("\nRGB:") + for dataset in datasets: + path = TEST_DIR / "original" / "T2V" / dataset + if path.exists(): + real = (path / "0_real").exists() + fake = (path / "1_fake").exists() + status = f"[Real: {'✓' if real else '✗'}, Fake: {'✓' if fake else '✗'}]" + print(f" {'✓' if (real and fake) else '⚠️ '} {dataset}: {status}") + if not (real and fake): + all_good = False + else: + print(f" ❌ {dataset}: NOT FOUND") + all_good = False + + # Cleanup + print(f"\n{'='*80}") + cleanup = input("\nRemove temporary zip files? (y/n): ").strip().lower() + if cleanup == 'y': + try: + shutil.rmtree(TEMP_DIR) + print(f"✓ Removed: {TEMP_DIR}") + except Exception as e: + print(f"⚠️ Could not remove: {e}") + + # Final message + print(f"\n{'='*80}") + if all_good: + print("✓ EXTRACTION COMPLETE!") + print("="*80) + print("\nAll test data is ready!") + print("\nNext step:") + print(" python recreate_table2_final.py") + else: + print("⚠️ EXTRACTION COMPLETED WITH WARNINGS") + print("="*80) + print("\nSome data may be missing. Check the warnings above.") + print("="*80) + +if __name__ == "__main__": + main() diff --git a/gui_app.py b/gui_app.py new file mode 100644 index 0000000..373b032 --- /dev/null +++ b/gui_app.py @@ -0,0 +1,364 @@ +import streamlit as st +import sys +import os +import time +import torch +import numpy as np +import cv2 +import glob +import argparse +import tempfile +import shutil +from PIL import Image +import torchvision.transforms as transforms +import torchvision.transforms.functional as TF +from natsort import natsorted + +# Add core to path +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.join(current_dir, 'core')) + +try: + from core.raft import RAFT + from core.utils import flow_viz + from core.utils.utils import InputPadder + from core.utils1.utils import get_network +except ImportError as e: + st.error(f"Error importing core modules: {e}. Please ensure you are in the AIGVDet root directory.") + +st.set_page_config(page_title="AIGVDet GUI", layout="wide") + +st.title("AIGVDet: AI-Generated Video Detection") + +# Tabs +tab1, tab2 = st.tabs(["1. Extract (RGB & Optical Flow)", "2. Detection (Architecture)"]) + +# Global Device +DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' + +def load_image(imfile): + img = np.array(Image.open(imfile)).astype(np.uint8) + img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) + +def save_vis(img, flo, folder_optical_flow_path, imfile1): + img = img[0].permute(1,2,0).cpu().numpy() + flo = flo[0].permute(1,2,0).cpu().numpy() + + # map flow to rgb image + flo = flow_viz.flow_to_image(flo) + + # We only save the flow image as per demo.py logic (it saves 'flo') + # demo.py: cv2.imwrite(folder_optical_flow_path, flo) + + content = os.path.basename(imfile1) + save_path = os.path.join(folder_optical_flow_path, content) + + # cv2 expects BGR, flow_viz returns RGB likely? + # flow_viz.flow_to_image returns RGB. cv2.imwrite expects BGR. + # demo.py uses cv2.imwrite(..., flo). + # Let's check flow_viz.flow_to_image implementation if possible, but assuming demo.py works, we follow it. + # Actually demo.py does: `flo = flow_viz.flow_to_image(flo)` then `cv2.imwrite(..., flo)`. + # If flow_viz returns RGB, cv2 saves it as BGR (swapping channels), so colors might be inverted if not handled. + # But we will stick to demo.py logic. + + # Convert RGB to BGR for cv2 + flo_bgr = cv2.cvtColor(flo, cv2.COLOR_RGB2BGR) + cv2.imwrite(save_path, flo_bgr) + +def video_to_frames(video_path, output_folder, progress_bar=None): + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + cap = cv2.VideoCapture(video_path) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + frame_count = 0 + + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + frame_filename = os.path.join(output_folder, f"frame_{frame_count:05d}.png") + cv2.imwrite(frame_filename, frame) + frame_count += 1 + + if progress_bar: + progress_bar.progress(min(frame_count / total_frames, 1.0), text=f"Extracting frame {frame_count}/{total_frames}") + + cap.release() + return sorted(glob.glob(os.path.join(output_folder, '*.png'))) + +# --- TAB 1: EXTRACTION --- +with tab1: + st.header("Extract RGB Frames and Optical Flow") + + # Model Upload + raft_model_file = st.file_uploader("Upload RAFT Model (raft.pth)", type=['pth'], key="raft_uploader") + + # Video Upload (Batch or Solo) + uploaded_videos = st.file_uploader("Upload Video(s)", type=['mp4', 'avi', 'mov', 'mkv'], accept_multiple_files=True, key="video_uploader") + + # Output Directory + output_root = st.text_input("Output Directory Root", value="output_data") + + if st.button("Start Extraction", key="extract_btn"): + if not raft_model_file: + st.error("Please upload the RAFT model.") + elif not uploaded_videos: + st.error("Please upload at least one video.") + else: + # Save RAFT model to temp file + with tempfile.NamedTemporaryFile(delete=False, suffix=".pth") as tmp_raft: + tmp_raft.write(raft_model_file.read()) + raft_model_path = tmp_raft.name + + st.info(f"Loaded RAFT model. Using device: {DEVICE}") + + # Load RAFT + try: + # Mock args for RAFT + class Args: + model = raft_model_path + small = False + mixed_precision = False + alternate_corr = False + + args = Args() + model = torch.nn.DataParallel(RAFT(args)) + model.load_state_dict(torch.load(args.model, map_location=torch.device(DEVICE))) + model = model.module + model.to(DEVICE) + model.eval() + + st.success("RAFT Model Loaded Successfully!") + + # Process Videos + total_videos = len(uploaded_videos) + + for i, video_file in enumerate(uploaded_videos): + video_name = video_file.name + st.subheader(f"Processing: {video_name} ({i+1}/{total_videos})") + + # Save video to temp + with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(video_name)[1]) as tmp_vid: + tmp_vid.write(video_file.read()) + video_path = tmp_vid.name + + # Define output paths + base_name = os.path.splitext(video_name)[0] + frame_output_dir = os.path.join(output_root, "frames", base_name) + flow_output_dir = os.path.join(output_root, "optical_flow", base_name) + + # 1. Extract Frames + st.write("Extracting frames...") + p_bar = st.progress(0) + images = video_to_frames(video_path, frame_output_dir, p_bar) + st.write(f"Extracted {len(images)} frames to `{frame_output_dir}`") + + # 2. Generate Optical Flow + if not os.path.exists(flow_output_dir): + os.makedirs(flow_output_dir) + + st.write("Generating Optical Flow...") + images = natsorted(images) + flow_p_bar = st.progress(0) + + with torch.no_grad(): + for idx, (imfile1, imfile2) in enumerate(zip(images[:-1], images[1:])): + image1 = load_image(imfile1) + image2 = load_image(imfile2) + + padder = InputPadder(image1.shape) + image1, image2 = padder.pad(image1, image2) + + flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) + + save_vis(image1, flow_up, flow_output_dir, imfile1) + + flow_p_bar.progress((idx + 1) / (len(images) - 1)) + + st.write(f"Optical Flow saved to `{flow_output_dir}`") + + # Cleanup temp video + os.remove(video_path) + + st.success("All videos processed!") + # Cleanup temp model + os.remove(raft_model_path) + + except Exception as e: + st.error(f"An error occurred: {e}") + import traceback + st.code(traceback.format_exc()) + +# --- TAB 2: DETECTION --- +with tab2: + st.header("Run Detection (AIGVDet)") + + col1, col2 = st.columns(2) + with col1: + optical_model_file = st.file_uploader("Upload Optical Flow Model (optical.pth)", type=['pth'], key="opt_uploader") + with col2: + original_model_file = st.file_uploader("Upload RGB Model (original.pth)", type=['pth'], key="orig_uploader") + + # Input for processed data path + # Default to the output of Tab 1 if available + target_dir = st.text_input("Path to Processed Data (Root folder containing 'frames' and 'optical_flow')", value="output_data") + + threshold = st.slider("Threshold", 0.0, 1.0, 0.5) + + if st.button("Run Detection", key="detect_btn"): + if not optical_model_file or not original_model_file: + st.error("Please upload both Optical Flow and RGB models.") + elif not os.path.exists(target_dir): + st.error(f"Directory `{target_dir}` does not exist.") + else: + start_time = time.time() + + # Save models to temp + with tempfile.NamedTemporaryFile(delete=False, suffix=".pth") as tmp_opt: + tmp_opt.write(optical_model_file.read()) + opt_model_path = tmp_opt.name + + with tempfile.NamedTemporaryFile(delete=False, suffix=".pth") as tmp_orig: + tmp_orig.write(original_model_file.read()) + orig_model_path = tmp_orig.name + + try: + st.info("Loading models...") + + # Load Models + # Assuming ResNet50 as per demo.py default + model_op = get_network("resnet50") + state_dict_op = torch.load(opt_model_path, map_location="cpu") + if "model" in state_dict_op: + state_dict_op = state_dict_op["model"] + model_op.load_state_dict(state_dict_op) + model_op.eval() + if DEVICE == 'cuda': + model_op.cuda() + + model_or = get_network("resnet50") + state_dict_or = torch.load(orig_model_path, map_location="cpu") + if "model" in state_dict_or: + state_dict_or = state_dict_or["model"] + model_or.load_state_dict(state_dict_or) + model_or.eval() + if DEVICE == 'cuda': + model_or.cuda() + + load_duration = time.time() - start_time + st.write(f"Models loaded in {load_duration:.2f} seconds.") + + # Find subfolders in frames + frames_root = os.path.join(target_dir, "frames") + flow_root = os.path.join(target_dir, "optical_flow") + + if not os.path.exists(frames_root): + st.error(f"Could not find `frames` folder in {target_dir}") + st.stop() + + # Get list of video folders + video_folders = [f for f in os.listdir(frames_root) if os.path.isdir(os.path.join(frames_root, f))] + + if not video_folders: + st.warning("No subfolders found in `frames` directory.") + + # Transforms + trans = transforms.Compose(( + transforms.CenterCrop((448,448)), + transforms.ToTensor(), + )) + + results = [] + + for vid_folder in video_folders: + st.subheader(f"Analyzing: {vid_folder}") + + rgb_path = os.path.join(frames_root, vid_folder) + opt_path = os.path.join(flow_root, vid_folder) + + if not os.path.exists(opt_path): + st.warning(f"No optical flow found for {vid_folder}, skipping.") + continue + + # RGB Detection + rgb_files = sorted(glob.glob(os.path.join(rgb_path, "*.jpg")) + + glob.glob(os.path.join(rgb_path, "*.png")) + + glob.glob(os.path.join(rgb_path, "*.JPEG"))) + + rgb_prob_sum = 0 + rgb_bar = st.progress(0, text="RGB Detection") + + for i, img_path in enumerate(rgb_files): + img = Image.open(img_path).convert("RGB") + img = trans(img) + img = TF.normalize(img, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + in_tens = img.unsqueeze(0).to(DEVICE) + + with torch.no_grad(): + prob = model_or(in_tens).sigmoid().item() + rgb_prob_sum += prob + + rgb_bar.progress((i + 1) / len(rgb_files)) + + rgb_score = rgb_prob_sum / len(rgb_files) if rgb_files else 0 + st.write(f"RGB Score: {rgb_score:.4f}") + + # Optical Flow Detection + opt_files = sorted(glob.glob(os.path.join(opt_path, "*.jpg")) + + glob.glob(os.path.join(opt_path, "*.png")) + + glob.glob(os.path.join(opt_path, "*.JPEG"))) + + opt_prob_sum = 0 + opt_bar = st.progress(0, text="Optical Flow Detection") + + for i, img_path in enumerate(opt_files): + img = Image.open(img_path).convert("RGB") + img = trans(img) + img = TF.normalize(img, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + in_tens = img.unsqueeze(0).to(DEVICE) + + with torch.no_grad(): + prob = model_op(in_tens).sigmoid().item() + opt_prob_sum += prob + + opt_bar.progress((i + 1) / len(opt_files)) + + opt_score = opt_prob_sum / len(opt_files) if opt_files else 0 + st.write(f"Optical Flow Score: {opt_score:.4f}") + + # Final Decision + final_score = (rgb_score * 0.5) + (opt_score * 0.5) + decision = "FAKE VIDEO (AI-Generated)" if final_score >= threshold else "REAL VIDEO" + color = "red" if final_score >= threshold else "green" + + st.markdown(f"### Result: :{color}[{decision}]") + st.write(f"**Combined Probability:** {final_score:.4f}") + st.divider() + + results.append({ + "Video": vid_folder, + "RGB Score": rgb_score, + "Optical Score": opt_score, + "Final Score": final_score, + "Decision": decision + }) + + # Summary Table + if results: + st.subheader("Batch Summary") + st.dataframe(results) + + total_duration = time.time() - start_time + st.success(f"Total Processing Time: {total_duration:.2f} seconds") + + # Cleanup + os.remove(opt_model_path) + os.remove(orig_model_path) + + except Exception as e: + st.error(f"An error occurred during detection: {e}") + import traceback + st.code(traceback.format_exc()) diff --git a/prepare_real_data.py b/prepare_real_data.py new file mode 100644 index 0000000..51f89b7 --- /dev/null +++ b/prepare_real_data.py @@ -0,0 +1,175 @@ +import argparse +import os +import glob +import shutil +import cv2 +import numpy as np +import torch +import torch.nn +from PIL import Image +from tqdm import tqdm +import sys + +# Add core to path to import RAFT +sys.path.append('core') +from raft import RAFT +from utils import flow_viz +from utils.utils import InputPadder +from natsort import natsorted + +DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' + +def load_image(imfile): + img = Image.open(imfile) + + # Resize if too large (e.g. > 1024px) to prevent OOM + max_dim = 1024 + if max(img.size) > max_dim: + scale = max_dim / max(img.size) + new_size = (int(img.size[0] * scale), int(img.size[1] * scale)) + img = img.resize(new_size, Image.BILINEAR) + + img = np.array(img).astype(np.uint8) + img = torch.from_numpy(img).permute(2, 0, 1).float() + return img[None].to(DEVICE) + +def viz(img, flo, output_dir, filename): + img = img[0].permute(1,2,0).cpu().numpy() + flo = flo[0].permute(1,2,0).cpu().numpy() + + # map flow to rgb image + flo = flow_viz.flow_to_image(flo) + + # Save flow image + # The filename should match the input frame filename + save_path = os.path.join(output_dir, os.path.basename(filename)) + cv2.imwrite(save_path, flo) + +def generate_optical_flow(model, frames_dir, output_dir): + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Get all frames + images = sorted(glob.glob(os.path.join(frames_dir, '*.jpg')) + + glob.glob(os.path.join(frames_dir, '*.png'))) + images = natsorted(images) + + print(f"Generating optical flow for {len(images)} frames...") + + with torch.no_grad(): + for imfile1, imfile2 in tqdm(zip(images[:-1], images[1:]), total=len(images)-1, desc="Flow Generation"): + image1 = load_image(imfile1) + image2 = load_image(imfile2) + + padder = InputPadder(image1.shape) + image1, image2 = padder.pad(image1, image2) + + flow_low, flow_up = model(image1, image2, iters=20, test_mode=True) + + viz(image1, flow_up, output_dir, imfile1) + +def main(): + parser = argparse.ArgumentParser(description="Prepare Real Data for all datasets") + parser.add_argument("--source", type=str, required=True, help="Path to source folder containing multiple video folders (e.g. 1_real)") + parser.add_argument("--raft_model", type=str, default="raft_model/raft-things.pth", help="Path to RAFT model checkpoint") + parser.add_argument("--start_index", type=int, default=1, help="Index to start processing from (1-based)") + args = parser.parse_args() + + if not os.path.exists(args.source): + print(f"❌ Source folder not found: {args.source}") + return + + # Find all subdirectories (video folders) + video_folders = [f.path for f in os.scandir(args.source) if f.is_dir()] + video_folders = sorted(video_folders) + + if not video_folders: + print(f"❌ No video folders found in {args.source}") + return + + print(f"Found {len(video_folders)} video folders to process") + + # Load RAFT model + print("Loading RAFT model...") + + raft_args = argparse.Namespace( + model=args.raft_model, + small=False, + mixed_precision=False, + alternate_corr=False, + dropout=0 + ) + + model = torch.nn.DataParallel(RAFT(raft_args)) + + try: + model.load_state_dict(torch.load(args.raft_model, map_location=torch.device(DEVICE))) + except FileNotFoundError: + print(f"❌ RAFT model not found at {args.raft_model}") + print("Please download it first using: python download_data.py --skip-data --skip-checkpoints") + return + + model = model.module + model.to(DEVICE) + model.eval() + print("✓ RAFT model loaded") + + # Target datasets + datasets = ["moonvalley", "videocraft", "pika", "neverends"] + + # Process each video folder + for idx, video_path in enumerate(video_folders, 1): + if idx < args.start_index: + continue + + video_name = os.path.basename(video_path) + print(f"\n{'='*60}") + print(f"Processing Video {idx}/{len(video_folders)}: {video_name}") + print(f"{'='*60}") + + # 1. Generate Optical Flow ONCE in a temp location + temp_flow_dir = os.path.join("data", "temp_flow", video_name) + # Skip if already generated in temp + if os.path.exists(temp_flow_dir) and len(os.listdir(temp_flow_dir)) > 0: + print(f" Using existing flow in temp: {temp_flow_dir}") + else: + print(f" Generating optical flow to temp location: {temp_flow_dir}") + generate_optical_flow(model, video_path, temp_flow_dir) + + # 2. Distribute to all datasets + print(f" Distributing to {len(datasets)} datasets...") + + for dataset in datasets: + # Paths + rgb_dest = os.path.join("data", "test", "original", "T2V", dataset, "0_real", video_name) + flow_dest = os.path.join("data", "test", "T2V", dataset, "0_real", video_name) + + # Create directories + os.makedirs(rgb_dest, exist_ok=True) + os.makedirs(flow_dest, exist_ok=True) + + # Copy RGB frames + # print(f" -> RGB: {dataset}") + if os.path.exists(rgb_dest): + shutil.rmtree(rgb_dest) + shutil.copytree(video_path, rgb_dest) + + # Copy Flow frames + # print(f" -> Flow: {dataset}") + if os.path.exists(flow_dest): + shutil.rmtree(flow_dest) + shutil.copytree(temp_flow_dir, flow_dest) + + print("\n" + "="*80) + print("✓ REAL DATA PREPARATION COMPLETE") + print("="*80) + print(f"Source: {args.source}") + print(f"Processed {len(video_folders)} videos") + print(f"Distributed to: {', '.join(datasets)}") + + # Clean up temp + if os.path.exists(os.path.join("data", "temp_flow")): + shutil.rmtree(os.path.join("data", "temp_flow")) + +if __name__ == "__main__": + main() diff --git a/python-utils/prepare_data.py b/python-utils/prepare_data.py index 65791b8..b72bb1a 100644 --- a/python-utils/prepare_data.py +++ b/python-utils/prepare_data.py @@ -36,9 +36,10 @@ def save_flow(img, flo, output_path): flo = flow_viz.flow_to_image(flo) cv2.imwrite(output_path, flo) -def video_to_frames(video_path, output_folder): +def video_to_frames(video_path, output_folder, max_frames=95): """ Extract frames from a video and save them as images in the output folder. + Limits to max_frames (default 95) to match paper methodology. """ if not os.path.exists(output_folder): os.makedirs(output_folder) @@ -46,12 +47,12 @@ def video_to_frames(video_path, output_folder): # Check if frames already exist to skip existing_frames = glob.glob(os.path.join(output_folder, "*.png")) if len(existing_frames) > 0: - return sorted(existing_frames) + return sorted(existing_frames)[:max_frames] cap = cv2.VideoCapture(video_path) frame_count = 0 - while cap.isOpened(): + while cap.isOpened() and frame_count < max_frames: ret, frame = cap.read() if not ret: break @@ -65,7 +66,7 @@ def video_to_frames(video_path, output_folder): # Return sorted list of extracted frame files images = glob.glob(os.path.join(output_folder, '*.png')) + \ glob.glob(os.path.join(output_folder, '*.jpg')) - return sorted(images) + return sorted(images)[:max_frames] def process_dataset(args): """ diff --git a/recreate_table2_data2.py b/recreate_table2_data2.py new file mode 100644 index 0000000..819c80e --- /dev/null +++ b/recreate_table2_data2.py @@ -0,0 +1,309 @@ +""" +Script to recreate Table 2 for data2 (Emu, Hotshot, Sora) +Evaluates only the 3 main variants: AIGVDet (fused), Spatial, Optical +""" + +import os +import subprocess +import pandas as pd +from pathlib import Path +import re +import argparse + +# Configuration for data2 datasets +DATASETS = { + "emu": { + "optical": "data2/test/T2V/emu", + "rgb": "data2/test/original/T2V/emu" + }, + "hotshot": { + "optical": "data2/test/T2V/hotshot", + "rgb": "data2/test/original/T2V/hotshot" + }, + "sora": { + "optical": "data2/test/T2V/sora", + "rgb": "data2/test/original/T2V/sora" + } +} + +# Model configurations - Only 3 variants for Table 2 +VARIANTS = { + "S_spatial": { + "eval_mode": "rgb_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + }, + "S_optical": { + "eval_mode": "optical_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + }, + "AIGVDet": { + "eval_mode": "fused", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + } +} + +RESULTS_DIR = Path("data2/results/table2") +RESULTS_DIR.mkdir(parents=True, exist_ok=True) + +def check_prerequisites(): + """Check if all required datasets and models exist""" + print("="*80) + print("CHECKING PREREQUISITES FOR DATA2") + print("="*80) + + # Check datasets + print("\n1. Checking datasets...") + missing_data = [] + for dataset_name, paths in DATASETS.items(): + for stream_type, path in paths.items(): + if not os.path.exists(path): + missing_data.append(f" ❌ {dataset_name} ({stream_type}): {path}") + else: + real_path = os.path.join(path, "0_real") + fake_path = os.path.join(path, "1_fake") + + status = [] + if os.path.exists(real_path): + status.append("Real ✓") + else: + status.append("Real ✗") + + if os.path.exists(fake_path): + status.append("Fake ✓") + else: + status.append("Fake ✗") + + print(f" ✓ {dataset_name} ({stream_type}): {path} [{', '.join(status)}]") + + if not os.path.exists(real_path) and not os.path.exists(fake_path): + missing_data.append(f" ⚠️ {dataset_name} ({stream_type}): Missing BOTH 0_real and 1_fake folders") + + if missing_data: + print("\n⚠️ Some data is missing:") + for item in missing_data: + print(item) + + # Check models + print("\n2. Checking model checkpoints...") + models = ["checkpoints/optical.pth", "checkpoints/original.pth"] + missing_models = [] + for model in models: + if os.path.exists(model): + print(f" ✓ {model}") + else: + print(f" ❌ {model}") + missing_models.append(model) + + if missing_models: + print("\n❌ Missing models. Please ensure checkpoints are available.") + return False + + if missing_data: + print("\n⚠️ Some datasets are incomplete but will continue...") + + return True + +def run_evaluation(dataset_name, variant_name, variant_config, limit=None): + """Run evaluation for a specific dataset and variant""" + dataset_paths = DATASETS[dataset_name] + + # Build command + cmd = [ + "python", "test_single_stream.py", + "-fop", dataset_paths["optical"], + "-for", dataset_paths["rgb"], + "-mop", variant_config["optical_model"], + "-mor", variant_config["rgb_model"], + "--eval_mode", variant_config["eval_mode"], + "-e", str(RESULTS_DIR / f"{dataset_name}_{variant_name}_video.csv"), + "-ef", str(RESULTS_DIR / f"{dataset_name}_{variant_name}_frame.csv"), + "-t", "0.5" + ] + + if variant_config["no_crop"]: + cmd.append("--no_crop") + + if limit: + cmd.extend(["--limit", str(limit)]) + + print(f"\n{'='*80}") + print(f"Running: {variant_name} on {dataset_name}") + print(f"{'='*80}") + print("Command:", " ".join(cmd)) + print(f"{'='*80}\n") + + # Run the command with real-time output + try: + # Use Popen to stream output in real-time + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Collect output while displaying it + output_lines = [] + for line in process.stdout: + print(line, end='', flush=True) # Print in real-time + output_lines.append(line) + + # Wait for process to complete + return_code = process.wait(timeout=3600) + output = ''.join(output_lines) + + if return_code != 0: + print(f"\n⚠️ Command exited with code {return_code}") + + # Parse metrics from CSV file + csv_path = RESULTS_DIR / f"{dataset_name}_{variant_name}_video.csv" + metrics = parse_metrics(output, csv_path) + return metrics + except subprocess.TimeoutExpired: + print(f"\n⚠️ Timeout while running {variant_name} on {dataset_name}") + process.kill() + return None + except Exception as e: + print(f"\n❌ Error running {variant_name} on {dataset_name}: {e}") + return None + +def parse_metrics(output, csv_path): + """Parse metrics from CSV file (test_single_stream.py doesn't print to stdout)""" + try: + # Read the CSV file + if not os.path.exists(csv_path): + print(f" ⚠️ CSV file not found: {csv_path}") + return None + + df = pd.read_csv(csv_path) + + if len(df) == 0: + print(f" ⚠️ CSV file is empty: {csv_path}") + return None + + # Calculate metrics from the CSV data + from sklearn.metrics import accuracy_score, roc_auc_score, average_precision_score + + y_true = df['flag'].values # Ground truth (0=real, 1=fake) + y_pred = df['pro'].values # Predicted probability + + # Calculate metrics + acc = accuracy_score(y_true, (y_pred >= 0.5).astype(int)) + auc = roc_auc_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + ap = average_precision_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + + metrics = { + 'ACC': acc, + 'AUC': auc, + 'AP': ap + } + + print(f"\n ✓ Metrics: ACC={acc:.4f}, AUC={auc:.4f}, AP={ap:.4f}") + return metrics + + except Exception as e: + print(f" ⚠️ Error reading metrics from CSV: {e}") + return None + +def run_all_evaluations(limit=None): + """Run evaluations for all variants and datasets""" + print("\n" + "="*80) + print("RUNNING EVALUATIONS FOR DATA2") + print("="*80) + + all_results = {} + + total_evals = len(VARIANTS) * len(DATASETS) + current_eval = 0 + + for variant_idx, (variant_name, variant_config) in enumerate(VARIANTS.items(), 1): + all_results[variant_name] = {} + + print(f"\n{'='*80}") + print(f"VARIANT {variant_idx}/{len(VARIANTS)}: {variant_name}") + print(f"{'='*80}") + + for dataset_idx, dataset_name in enumerate(DATASETS.keys(), 1): + current_eval += 1 + overall_progress = (current_eval / total_evals) * 100 + + print(f"\n[Overall: {current_eval}/{total_evals} - {overall_progress:.1f}%]") + print(f"[Variant: {variant_idx}/{len(VARIANTS)}] [{variant_name}]") + print(f"[Dataset: {dataset_idx}/{len(DATASETS)}] [{dataset_name}]") + + metrics = run_evaluation(dataset_name, variant_name, variant_config, limit) + all_results[variant_name][dataset_name] = metrics + + return all_results + +def compile_table2(results): + """Compile results into Table 2 format""" + print("\n" + "="*80) + print("COMPILING TABLE 2 (DATA2)") + print("="*80) + + # Create DataFrame + rows = [] + for dataset in DATASETS.keys(): + row = {"Dataset": dataset} + for variant in VARIANTS.keys(): + if results[variant][dataset]: + auc = results[variant][dataset].get('AUC', 0) * 100 + ap = results[variant][dataset].get('AP', 0) * 100 + row[variant] = f"{auc:.1f}/{ap:.1f}" + else: + row[variant] = "N/A" + rows.append(row) + + df = pd.DataFrame(rows) + + # Save to CSV + output_file = RESULTS_DIR / "table2_data2.csv" + df.to_csv(output_file, index=False) + + print(f"\n✓ Table 2 saved to: {output_file}") + print("\nTable 2 Preview:") + print(df.to_string(index=False)) + + return df + +def main(): + """Main execution function""" + parser = argparse.ArgumentParser() + parser.add_argument("--limit", type=int, default=None, help="Limit number of videos per class") + args = parser.parse_args() + + print("="*80) + print("RECREATING TABLE 2 FOR DATA2 (EMU, HOTSHOT, SORA)") + print("="*80) + print("\nThis script will:") + print("1. Check prerequisites (data and models)") + print("2. Run evaluations for 3 variants (AIGVDet, Spatial, Optical)") + print("3. Compile results into Table 2 format") + print("="*80) + + # Check prerequisites + if not check_prerequisites(): + print("\n❌ Prerequisites not satisfied. Please fix the issues above.") + return + + # Run all evaluations + all_results = run_all_evaluations(limit=args.limit) + + # Compile and display Table 2 + table = compile_table2(all_results) + + print("\n" + "="*80) + print("✅ TABLE 2 RECREATION COMPLETE!") + print("="*80) + +if __name__ == "__main__": + main() diff --git a/recreate_table2_data3.py b/recreate_table2_data3.py new file mode 100644 index 0000000..96fc079 --- /dev/null +++ b/recreate_table2_data3.py @@ -0,0 +1,309 @@ +""" +Script to recreate Table 2 for data3 (I2V: Moonvalley, Pika, NeverEnds) +Evaluates only the 3 main variants: AIGVDet (fused), Spatial, Optical +""" + +import os +import subprocess +import pandas as pd +from pathlib import Path +import re +import argparse + +# Configuration for data3 I2V datasets +DATASETS = { + "moonvalley": { + "optical": "data3/test/I2V/moonvalley", + "rgb": "data3/test/original/I2V/moonvalley" + }, + "pika": { + "optical": "data3/test/I2V/pika", + "rgb": "data3/test/original/I2V/pika" + }, + "neverends": { + "optical": "data3/test/I2V/neverends", + "rgb": "data3/test/original/I2V/neverends" + } +} + +# Model configurations - Only 3 variants for Table 2 +VARIANTS = { + "S_spatial": { + "eval_mode": "rgb_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + }, + "S_optical": { + "eval_mode": "optical_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + }, + "AIGVDet": { + "eval_mode": "fused", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + } +} + +RESULTS_DIR = Path("data3/results/table2") +RESULTS_DIR.mkdir(parents=True, exist_ok=True) + +def check_prerequisites(): + """Check if all required datasets and models exist""" + print("="*80) + print("CHECKING PREREQUISITES FOR DATA3 (I2V)") + print("="*80) + + # Check datasets + print("\n1. Checking I2V datasets...") + missing_data = [] + for dataset_name, paths in DATASETS.items(): + for stream_type, path in paths.items(): + if not os.path.exists(path): + missing_data.append(f" ❌ {dataset_name} ({stream_type}): {path}") + else: + real_path = os.path.join(path, "0_real") + fake_path = os.path.join(path, "1_fake") + + status = [] + if os.path.exists(real_path): + status.append("Real ✓") + else: + status.append("Real ✗") + + if os.path.exists(fake_path): + status.append("Fake ✓") + else: + status.append("Fake ✗") + + print(f" ✓ {dataset_name} ({stream_type}): {path} [{', '.join(status)}]") + + if not os.path.exists(real_path) and not os.path.exists(fake_path): + missing_data.append(f" ⚠️ {dataset_name} ({stream_type}): Missing BOTH 0_real and 1_fake folders") + + if missing_data: + print("\n⚠️ Some data is missing:") + for item in missing_data: + print(item) + + # Check models + print("\n2. Checking model checkpoints...") + models = ["checkpoints/optical.pth", "checkpoints/original.pth"] + missing_models = [] + for model in models: + if os.path.exists(model): + print(f" ✓ {model}") + else: + print(f" ❌ {model}") + missing_models.append(model) + + if missing_models: + print("\n❌ Missing models. Please ensure checkpoints are available.") + return False + + if missing_data: + print("\n⚠️ Some datasets are incomplete but will continue...") + + return True + +def run_evaluation(dataset_name, variant_name, variant_config, limit=None): + """Run evaluation for a specific dataset and variant""" + dataset_paths = DATASETS[dataset_name] + + # Build command + cmd = [ + "python", "test_single_stream.py", + "-fop", dataset_paths["optical"], + "-for", dataset_paths["rgb"], + "-mop", variant_config["optical_model"], + "-mor", variant_config["rgb_model"], + "--eval_mode", variant_config["eval_mode"], + "-e", str(RESULTS_DIR / f"{dataset_name}_{variant_name}_video.csv"), + "-ef", str(RESULTS_DIR / f"{dataset_name}_{variant_name}_frame.csv"), + "-t", "0.5" + ] + + if variant_config["no_crop"]: + cmd.append("--no_crop") + + if limit: + cmd.extend(["--limit", str(limit)]) + + print(f"\n{'='*80}") + print(f"Running: {variant_name} on {dataset_name}") + print(f"{'='*80}") + print("Command:", " ".join(cmd)) + print(f"{'='*80}\n") + + # Run the command with real-time output + try: + # Use Popen to stream output in real-time + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Collect output while displaying it + output_lines = [] + for line in process.stdout: + print(line, end='', flush=True) # Print in real-time + output_lines.append(line) + + # Wait for process to complete + return_code = process.wait(timeout=3600) + output = ''.join(output_lines) + + if return_code != 0: + print(f"\n⚠️ Command exited with code {return_code}") + + # Parse metrics from CSV file + csv_path = RESULTS_DIR / f"{dataset_name}_{variant_name}_video.csv" + metrics = parse_metrics(output, csv_path) + return metrics + except subprocess.TimeoutExpired: + print(f"\n⚠️ Timeout while running {variant_name} on {dataset_name}") + process.kill() + return None + except Exception as e: + print(f"\n❌ Error running {variant_name} on {dataset_name}: {e}") + return None + +def parse_metrics(output, csv_path): + """Parse metrics from CSV file (test_single_stream.py doesn't print to stdout)""" + try: + # Read the CSV file + if not os.path.exists(csv_path): + print(f" ⚠️ CSV file not found: {csv_path}") + return None + + df = pd.read_csv(csv_path) + + if len(df) == 0: + print(f" ⚠️ CSV file is empty: {csv_path}") + return None + + # Calculate metrics from the CSV data + from sklearn.metrics import accuracy_score, roc_auc_score, average_precision_score + + y_true = df['flag'].values # Ground truth (0=real, 1=fake) + y_pred = df['pro'].values # Predicted probability + + # Calculate metrics + acc = accuracy_score(y_true, (y_pred >= 0.5).astype(int)) + auc = roc_auc_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + ap = average_precision_score(y_true, y_pred) if len(set(y_true)) > 1 else 0.0 + + metrics = { + 'acc': acc, + 'auc': auc, + 'ap': ap + } + + print(f"\n ✓ Metrics: ACC={acc:.4f}, AUC={auc:.4f}, AP={ap:.4f}") + return metrics + + except Exception as e: + print(f" ⚠️ Error reading metrics from CSV: {e}") + return None + +def run_all_evaluations(limit=None): + """Run evaluations for all variants and datasets""" + print("\n" + "="*80) + print("RUNNING EVALUATIONS FOR DATA3 (I2V)") + print("="*80) + + all_results = {} + + total_evals = len(VARIANTS) * len(DATASETS) + current_eval = 0 + + for variant_idx, (variant_name, variant_config) in enumerate(VARIANTS.items(), 1): + all_results[variant_name] = {} + + print(f"\n{'='*80}") + print(f"VARIANT {variant_idx}/{len(VARIANTS)}: {variant_name}") + print(f"{'='*80}") + + for dataset_idx, dataset_name in enumerate(DATASETS.keys(), 1): + current_eval += 1 + overall_progress = (current_eval / total_evals) * 100 + + print(f"\n[Overall: {current_eval}/{total_evals} - {overall_progress:.1f}%]") + print(f"[Variant: {variant_idx}/{len(VARIANTS)}] [{variant_name}]") + print(f"[Dataset: {dataset_idx}/{len(DATASETS)}] [{dataset_name}]") + + metrics = run_evaluation(dataset_name, variant_name, variant_config, limit) + all_results[variant_name][dataset_name] = metrics + + return all_results + +def compile_table2(results): + """Compile results into Table 2 format""" + print("\n" + "="*80) + print("COMPILING TABLE 2 (DATA3 - I2V)") + print("="*80) + + # Create DataFrame + rows = [] + for dataset in DATASETS.keys(): + row = {"Dataset": dataset} + for variant in VARIANTS.keys(): + if results[variant][dataset]: + auc = results[variant][dataset].get('auc', 0) * 100 + acc = results[variant][dataset].get('acc', 0) * 100 + row[variant] = f"{auc:.1f}/{acc:.1f}" + else: + row[variant] = "N/A" + rows.append(row) + + df = pd.DataFrame(rows) + + # Save to CSV + output_file = RESULTS_DIR / "table2_data3_i2v.csv" + df.to_csv(output_file, index=False) + + print(f"\n✓ Table 2 (I2V) saved to: {output_file}") + print("\nTable 2 Preview:") + print(df.to_string(index=False)) + + return df + +def main(): + """Main execution function""" + parser = argparse.ArgumentParser() + parser.add_argument("--limit", type=int, default=None, help="Limit number of videos per class") + args = parser.parse_args() + + print("="*80) + print("RECREATING TABLE 2 FOR DATA3 (I2V: MOONVALLEY, PIKA, NEVERENDS)") + print("="*80) + print("\nThis script will:") + print("1. Check prerequisites (data and models)") + print("2. Run evaluations for 3 variants (AIGVDet, Spatial, Optical)") + print("3. Compile results into Table 2 format") + print("="*80) + + # Check prerequisites + if not check_prerequisites(): + print("\n❌ Prerequisites not satisfied. Please fix the issues above.") + return + + # Run all evaluations + all_results = run_all_evaluations(limit=args.limit) + + # Compile and display Table 2 + table = compile_table2(all_results) + + print("\n" + "="*80) + print("✅ TABLE 2 (I2V) RECREATION COMPLETE!") + print("="*80) + +if __name__ == "__main__": + main() diff --git a/recreate_table2_final.py b/recreate_table2_final.py new file mode 100644 index 0000000..b68754d --- /dev/null +++ b/recreate_table2_final.py @@ -0,0 +1,382 @@ +""" +Comprehensive script to recreate Table 2 from the AIGVDet paper +This script: +1. Checks for required data and models +2. Runs evaluations for all variants (Sspatial, Soptical, Soptical_no_cp, AIGVDet) +3. Compiles results into Table 2 format +""" + +import os +import subprocess +import pandas as pd +from pathlib import Path +import re +import argparse + +# Configuration for datasets +DATASETS = { + "moonvalley": { + "optical": "data/test/T2V/moonvalley", + "rgb": "data/test/original/T2V/moonvalley" + }, + "videocraft": { + "optical": "data/test/T2V/videocraft", + "rgb": "data/test/original/T2V/videocraft" + }, + "pika": { + "optical": "data/test/T2V/pika", + "rgb": "data/test/original/T2V/pika" + }, + "neverends": { + "optical": "data/test/T2V/neverends", + "rgb": "data/test/original/T2V/neverends" + } +} + +# Model configurations for each variant +VARIANTS = { + "S_spatial": { + "eval_mode": "rgb_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + }, + "S_optical": { + "eval_mode": "optical_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + }, + "S_optical_no_cp": { + "eval_mode": "optical_only", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": True + }, + "AIGVDet": { + "eval_mode": "fused", + "optical_model": "checkpoints/optical.pth", + "rgb_model": "checkpoints/original.pth", + "no_crop": False + } +} + +RESULTS_DIR = Path("data/results/table2") +RESULTS_DIR.mkdir(parents=True, exist_ok=True) + +def check_prerequisites(): + """Check if all required datasets and models exist""" + print("="*80) + print("CHECKING PREREQUISITES") + print("="*80) + + # Check datasets + print("\n1. Checking datasets...") + missing_data = [] + for dataset_name, paths in DATASETS.items(): + for stream_type, path in paths.items(): + if not os.path.exists(path): + missing_data.append(f" ❌ {dataset_name} ({stream_type}): {path}") + else: + # Check if has 0_real and 1_fake subfolders + real_path = os.path.join(path, "0_real") + fake_path = os.path.join(path, "1_fake") + + status = [] + if os.path.exists(real_path): + status.append("Real ✓") + else: + status.append("Real ✗") + + if os.path.exists(fake_path): + status.append("Fake ✓") + else: + status.append("Fake ✗") + + print(f" ✓ {dataset_name} ({stream_type}): {path} [{', '.join(status)}]") + + if not os.path.exists(real_path) and not os.path.exists(fake_path): + missing_data.append(f" ⚠️ {dataset_name} ({stream_type}): Missing BOTH 0_real and 1_fake folders") + + if missing_data: + print("\n⚠️ Critical issues found:") + for issue in missing_data: + print(issue) + print("\nPlease run prepare_data.py to extract frames from videos first.") + return False + + # Check models + print("\n2. Checking model checkpoints...") + required_models = set() + for variant_config in VARIANTS.values(): + required_models.add(variant_config["optical_model"]) + required_models.add(variant_config["rgb_model"]) + + missing_models = [] + for model_path in required_models: + if not os.path.exists(model_path): + missing_models.append(f" ❌ {model_path}") + else: + print(f" ✓ {model_path}") + + if missing_models: + print("\n⚠️ Missing model checkpoints:") + for missing in missing_models: + print(missing) + print("\nPlease ensure you have trained models or download pre-trained checkpoints.") + return False + + print("\n✓ All prerequisites satisfied!") + return True + +def run_evaluation(dataset_name, variant_name, variant_config, limit=None): + """ + Run evaluation for a specific dataset and variant + """ + dataset_paths = DATASETS[dataset_name] + + # Build command + cmd = [ + "python", "test_single_stream.py", + "-fop", dataset_paths["optical"], + "-for", dataset_paths["rgb"], + "-mop", variant_config["optical_model"], + "-mor", variant_config["rgb_model"], + "--eval_mode", variant_config["eval_mode"], + "-e", str(RESULTS_DIR / f"{dataset_name}_{variant_name}_video.csv"), + "-ef", str(RESULTS_DIR / f"{dataset_name}_{variant_name}_frame.csv"), + "-t", "0.5" + ] + + if variant_config["no_crop"]: + cmd.append("--no_crop") + + if limit: + cmd.extend(["--limit", str(limit)]) + + print(f"\n{'='*80}") + print(f"Running: {variant_name} on {dataset_name}") + print(f"{'='*80}") + print("Command:", " ".join(cmd)) + print(f"{'='*80}\n") + + # Run the command with real-time output + try: + # Use Popen to stream output in real-time + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Collect output while displaying it + output_lines = [] + for line in process.stdout: + print(line, end='', flush=True) # Print in real-time + output_lines.append(line) + + # Wait for process to complete + return_code = process.wait(timeout=3600) + output = ''.join(output_lines) + + if return_code != 0: + print(f"\n⚠️ Command exited with code {return_code}") + + # Parse metrics from output + metrics = parse_metrics(output) + return metrics + except subprocess.TimeoutExpired: + print(f"\n⚠️ Timeout while running {variant_name} on {dataset_name}") + process.kill() + return None + except Exception as e: + print(f"\n❌ Error running {variant_name} on {dataset_name}: {e}") + return None + +def parse_metrics(output): + """Parse accuracy and AUC from test output""" + metrics = {'acc': None, 'auc': None} + + lines = output.split('\n') + for line in lines: + # Look for "acc: 0.XXXX (XX.X%)" or "acc: 0.XXXX" or "Accuracy: XX.X%" + if 'acc' in line.lower(): + # Try pattern 1: "acc: 0.XXXX" + match = re.search(r'acc[:\s]+([0-9.]+)', line, re.IGNORECASE) + if match: + metrics['acc'] = float(match.group(1)) + + # Look for "auc: 0.XXXX (XX.X%)" or "auc: 0.XXXX" or "AUC: XX.X%" + if 'auc' in line.lower(): + # Try pattern 1: "auc: 0.XXXX" + match = re.search(r'auc[:\s]+([0-9.]+)', line, re.IGNORECASE) + if match: + metrics['auc'] = float(match.group(1)) + + # Debug output if metrics not found + if metrics['acc'] is None or metrics['auc'] is None: + print("\n ⚠️ Warning: Could not parse all metrics from output") + print(f" Found ACC: {metrics['acc']}, AUC: {metrics['auc']}") + print(" Last 10 lines of output:") + for line in lines[-10:]: + if line.strip(): + print(f" {line}") + + return metrics + +def run_all_evaluations(limit=None): + """Run evaluations for all variants and datasets""" + print("\n" + "="*80) + print("RUNNING EVALUATIONS") + print("="*80) + + all_results = {} + + # Calculate total number of evaluations + total_evals = len(VARIANTS) * len(DATASETS) + current_eval = 0 + + for variant_idx, (variant_name, variant_config) in enumerate(VARIANTS.items(), 1): + all_results[variant_name] = {} + + print(f"\n{'='*80}") + print(f"VARIANT {variant_idx}/{len(VARIANTS)}: {variant_name}") + print(f"{'='*80}") + + for dataset_idx, dataset_name in enumerate(DATASETS.keys(), 1): + current_eval += 1 + overall_progress = (current_eval / total_evals) * 100 + + print(f"\n[Overall: {current_eval}/{total_evals} - {overall_progress:.1f}%]") + print(f"[Variant: {variant_idx}/{len(VARIANTS)}] [{variant_name}]") + print(f"[Dataset: {dataset_idx}/{len(DATASETS)}] [{dataset_name}]") + + metrics = run_evaluation(dataset_name, variant_name, variant_config, limit=limit) + all_results[variant_name][dataset_name] = metrics + + if metrics: + acc_str = f"{metrics['acc']*100:.1f}%" if metrics['acc'] is not None else "N/A" + auc_str = f"{metrics['auc']*100:.1f}%" if metrics['auc'] is not None else "N/A" + print(f" ✓ ACC: {acc_str}, AUC: {auc_str}") + else: + print(f" ✗ Failed to get results") + + print(f"\n{'='*80}") + print(f"✓ ALL EVALUATIONS COMPLETE ({total_evals}/{total_evals})") + print(f"{'='*80}") + + return all_results + +def compile_table2(all_results): + """ + Compile all results into Table 2 format + """ + print("\n" + "="*80) + print("TABLE 2: Ablation test results") + print("Format: ACC(%)/AUC(%)") + print("="*80) + + # Create table data + table_data = [] + + for variant_name in ["S_spatial", "S_optical", "S_optical_no_cp", "AIGVDet"]: + row = {"Variants": variant_name} + + acc_values = [] + auc_values = [] + + for dataset_name in ["moonvalley", "videocraft", "pika", "neverends"]: + metrics = all_results.get(variant_name, {}).get(dataset_name) + + if metrics: + acc_pct = metrics['acc'] * 100 if metrics['acc'] is not None else None + auc_pct = metrics['auc'] * 100 if metrics['auc'] is not None else None + + acc_str = f"{acc_pct:.1f}" if acc_pct is not None else "N/A" + auc_str = f"{auc_pct:.1f}" if auc_pct is not None else "N/A" + + result_str = f"{acc_str}/{auc_str}" + + if acc_pct is not None: acc_values.append(acc_pct) + if auc_pct is not None: auc_values.append(auc_pct) + else: + result_str = "N/A" + + # Map dataset name to column name + column_name = { + "moonvalley": "Moonvalley", + "videocraft": "VideoCraft", + "pika": "Pika", + "neverends": "NeverEnds" + }[dataset_name] + + row[column_name] = result_str + + # Calculate average + avg_acc_str = f"{sum(acc_values) / len(acc_values):.1f}" if acc_values else "N/A" + avg_auc_str = f"{sum(auc_values) / len(auc_values):.1f}" if auc_values else "N/A" + row["Average"] = f"{avg_acc_str}/{avg_auc_str}" + + table_data.append(row) + + # Create DataFrame + df = pd.DataFrame(table_data) + df = df[["Variants", "Moonvalley", "VideoCraft", "Pika", "NeverEnds", "Average"]] + + # Display table + print("\n" + df.to_string(index=False)) + print("\n" + "="*80) + + # Save to CSV + output_path = RESULTS_DIR / "table2_recreation.csv" + df.to_csv(output_path, index=False) + print(f"\n✓ Table saved to: {output_path}") + + return df + +def main(): + parser = argparse.ArgumentParser(description="Recreate Table 2 from AIGVDet paper") + parser.add_argument("--skip-checks", action="store_true", help="Skip prerequisite checks") + parser.add_argument("--limit", type=int, default=None, help="Limit number of videos per class for quick testing") + args = parser.parse_args() + + print("="*80) + print("RECREATING TABLE 2 FROM AI-GENERATED VIDEO DETECTION PAPER") + print("="*80) + print("\nThis script will:") + print("1. Check prerequisites (data and models)") + print("2. Run evaluations for all variants on all datasets") + print("3. Compile results into Table 2 format") + + if args.limit: + print(f"⚠️ QUICK MODE: Limiting to {args.limit} videos per class") + else: + print("\nEstimated time: 30-60 minutes depending on dataset sizes") + print("="*80) + + # Check prerequisites + if not args.skip_checks: + if not check_prerequisites(): + print("\n❌ Prerequisites not satisfied. Please fix the issues above.") + return + + # Run all evaluations + all_results = run_all_evaluations(limit=args.limit) + + # Compile and display Table 2 + table = compile_table2(all_results) + + print("\n" + "="*80) + print("✓ TABLE 2 RECREATION COMPLETE!") + print("="*80) + print(f"\nResults saved to: {RESULTS_DIR}") + print("\nFiles generated:") + print(f" - table2_recreation.csv (summary table)") + print(f" - [dataset]_[variant]_video.csv (per-video results)") + print(f" - [dataset]_[variant]_frame.csv (per-frame results)") + +if __name__ == "__main__": + main() diff --git a/run_gui.bat b/run_gui.bat new file mode 100644 index 0000000..6157fa2 --- /dev/null +++ b/run_gui.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting AIGVDet GUI... +echo Ensure you have installed requirements: pip install streamlit +streamlit run gui_app.py +pause diff --git a/setup_test_data.py b/setup_test_data.py new file mode 100644 index 0000000..701a101 --- /dev/null +++ b/setup_test_data.py @@ -0,0 +1,254 @@ +""" +Simple script to download and extract test data for AIGVDet evaluation +Google Drive folder: https://drive.google.com/drive/u/3/folders/1gSAUUqYK33262aukdTjZIxUuGrgU8REU + +This script: +1. Downloads the test folder from Google Drive using gdown +2. Extracts all zip files to the correct locations +3. Organizes data structure for recreate_table2_final.py +""" + +import os +import subprocess +import zipfile +from pathlib import Path +import sys +import shutil + +# Google Drive folder link +GDRIVE_FOLDER = "https://drive.google.com/drive/folders/1D1jm1_HCu0Nv21NVjuyL1CB5gF5sy0hx" + +# Paths +DATA_DIR = Path("data") +TEST_DIR = DATA_DIR / "test" +TEMP_DIR = DATA_DIR / "temp_download" + +def install_gdown(): + """Install gdown if not already installed""" + print("Checking for gdown...") + try: + import gdown + print("✓ gdown is already installed") + return True + except ImportError: + print("Installing gdown...") + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "gdown"]) + print("✓ gdown installed successfully") + return True + except Exception as e: + print(f"❌ Failed to install gdown: {e}") + return False + +def download_data(): + """Download test data from Google Drive""" + print(f"\n{'='*80}") + print("DOWNLOADING TEST DATA") + print(f"{'='*80}") + print(f"Source: {GDRIVE_FOLDER}") + print(f"Destination: {TEMP_DIR}") + + # Create temp directory + TEMP_DIR.mkdir(parents=True, exist_ok=True) + + # Check if files already exist + existing_zips = list(TEMP_DIR.rglob("*.zip")) + if existing_zips: + print(f"\n⚠️ Found {len(existing_zips)} existing zip files in {TEMP_DIR}") + print("Skipping download. If you want to re-download, delete the temp_download folder first.") + return True + + # Try method 1: gdown with cookies + try: + import gdown + print("\nMethod 1: Trying download with cookies authentication...") + gdown.download_folder(GDRIVE_FOLDER, output=str(TEMP_DIR), quiet=False, use_cookies=True) + print("\n✓ Download complete") + return True + except Exception as e: + print(f"\n⚠️ Method 1 failed: {e}") + + # Try method 2: gdown without cookies + try: + import gdown + print("\nMethod 2: Trying download without cookies...") + gdown.download_folder(GDRIVE_FOLDER, output=str(TEMP_DIR), quiet=False, use_cookies=False) + print("\n✓ Download complete") + return True + except Exception as e: + print(f"\n⚠️ Method 2 failed: {e}") + + # Try method 3: Command line with cookies + try: + print("\nMethod 3: Trying command line with cookies...") + cmd = ["gdown", "--folder", GDRIVE_FOLDER, "-O", str(TEMP_DIR), "--use-cookies"] + subprocess.run(cmd, check=True) + print("\n✓ Download complete") + return True + except Exception as e: + print(f"\n⚠️ Method 3 failed: {e}") + + # All methods failed + print("\n" + "="*80) + print("❌ AUTOMATIC DOWNLOAD FAILED - MANUAL DOWNLOAD REQUIRED") + print("="*80) + print("\nGoogle Drive has rate-limited downloads. Please download manually:") + print(f"\n1. Open in browser: {GDRIVE_FOLDER}") + print("2. Download all files/folders") + print(f"3. Extract to: {TEMP_DIR.absolute()}") + print("4. Run this script again") + print("\nAlternatively, wait 24 hours and try again.") + print("="*80) + return False + +def extract_zip(zip_path, extract_to): + """Extract a single zip file""" + print(f" Extracting: {zip_path.name} → {extract_to.name}") + try: + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + return True + except Exception as e: + print(f" ❌ Error: {e}") + return False + +def organize_data(): + """Extract and organize all zip files""" + print(f"\n{'='*80}") + print("EXTRACTING AND ORGANIZING DATA") + print(f"{'='*80}") + + # Create target directories + optical_base = TEST_DIR / "T2V" + rgb_base = TEST_DIR / "original" / "T2V" + optical_base.mkdir(parents=True, exist_ok=True) + rgb_base.mkdir(parents=True, exist_ok=True) + + # Find all zip files in temp directory + all_zips = list(TEMP_DIR.rglob("*.zip")) + + if not all_zips: + print("❌ No zip files found in downloaded data") + return False + + print(f"\nFound {len(all_zips)} zip files") + + # Process optical flow zips + print("\n1. Processing optical flow data...") + optical_zips = [z for z in all_zips if "-optical" in z.name.lower()] + for zip_file in optical_zips: + # Extract dataset name (e.g., "videocraft" from "videocraft-optical.zip") + dataset_name = zip_file.stem.replace("-optical", "").replace("-Optical", "") + target_dir = optical_base / dataset_name + target_dir.mkdir(parents=True, exist_ok=True) + extract_zip(zip_file, target_dir) + + # Process RGB zips + print("\n2. Processing RGB data...") + rgb_zips = [z for z in all_zips if "-rgb" in z.name.lower()] + for zip_file in rgb_zips: + # Extract dataset name (e.g., "videocraft" from "videocraft-rgb.zip") + dataset_name = zip_file.stem.replace("-rgb", "").replace("-RGB", "") + target_dir = rgb_base / dataset_name + target_dir.mkdir(parents=True, exist_ok=True) + extract_zip(zip_file, target_dir) + + print("\n✓ Extraction complete") + return True + +def verify_structure(): + """Verify the extracted data structure""" + print(f"\n{'='*80}") + print("VERIFYING DATA STRUCTURE") + print(f"{'='*80}") + + datasets = ["moonvalley", "videocraft", "pika", "neverends"] + all_good = True + + print("\nOptical flow data:") + for dataset in datasets: + path = TEST_DIR / "T2V" / dataset + if path.exists(): + real = (path / "0_real").exists() + fake = (path / "1_fake").exists() + status = f"[Real: {'✓' if real else '✗'}, Fake: {'✓' if fake else '✗'}]" + print(f" {'✓' if (real and fake) else '⚠️ '} {dataset}: {status}") + if not (real and fake): + all_good = False + else: + print(f" ❌ {dataset}: NOT FOUND") + all_good = False + + print("\nRGB data:") + for dataset in datasets: + path = TEST_DIR / "original" / "T2V" / dataset + if path.exists(): + real = (path / "0_real").exists() + fake = (path / "1_fake").exists() + status = f"[Real: {'✓' if real else '✗'}, Fake: {'✓' if fake else '✗'}]" + print(f" {'✓' if (real and fake) else '⚠️ '} {dataset}: {status}") + if not (real and fake): + all_good = False + else: + print(f" ❌ {dataset}: NOT FOUND") + all_good = False + + return all_good + +def cleanup(): + """Remove temporary files""" + print(f"\n{'='*80}") + print("CLEANUP") + print(f"{'='*80}") + + if TEMP_DIR.exists(): + try: + shutil.rmtree(TEMP_DIR) + print(f"✓ Removed temporary directory: {TEMP_DIR}") + except Exception as e: + print(f"⚠️ Could not remove temp directory: {e}") + print(f"You can manually delete: {TEMP_DIR}") + +def main(): + print("="*80) + print("AIGVDET TEST DATA SETUP") + print("="*80) + + # Step 1: Install gdown + if not install_gdown(): + print("\n❌ Setup failed: Could not install gdown") + return + + # Step 2: Download data + if not download_data(): + print("\n❌ Setup failed: Could not download data") + return + + # Step 3: Extract and organize + if not organize_data(): + print("\n❌ Setup failed: Could not extract data") + return + + # Step 4: Verify structure + success = verify_structure() + + # Step 5: Cleanup + cleanup() + + # Final message + print(f"\n{'='*80}") + if success: + print("✓ SETUP COMPLETE!") + print("="*80) + print("\nAll test data is ready!") + print("\nNext step:") + print(" python recreate_table2_final.py") + else: + print("⚠️ SETUP COMPLETED WITH WARNINGS") + print("="*80) + print("\nSome data may be missing. Please check the warnings above.") + print("You may need to manually extract some files.") + print("="*80) + +if __name__ == "__main__": + main() diff --git a/test.py b/test.py index 18a8e61..804e1cc 100644 --- a/test.py +++ b/test.py @@ -171,7 +171,7 @@ prob = model_or(in_tens).sigmoid().item() original_prob_sum+=prob - df1 = df1.append({'original_path': img_path, 'original_pro': prob , 'flag':flag}, ignore_index=True) + df1 = pd.concat([df1, pd.DataFrame([{'original_path': img_path, 'original_pro': prob , 'flag':flag}])], ignore_index=True) original_predict=original_prob_sum/len(original_file_list) @@ -218,7 +218,7 @@ p+=1 if predict>=args.threshold: tp+=1 - df = df.append({'name': subsubfolder_name, 'pro': predict , 'flag':flag ,'optical_pro':optical_predict,'original_pro':original_predict}, ignore_index=True) + df = pd.concat([df, pd.DataFrame([{'name': subsubfolder_name, 'pro': predict , 'flag':flag ,'optical_pro':optical_predict,'original_pro':original_predict}])], ignore_index=True) else: print("Subfolder does not exist:", original_subfolder_path) # r_acc = accuracy_score(y_true[y_true == 0], y_pred[y_true == 0] > args.threshold) diff --git a/test_single_stream.py b/test_single_stream.py new file mode 100644 index 0000000..d401104 --- /dev/null +++ b/test_single_stream.py @@ -0,0 +1,333 @@ +""" +Modified test.py that supports single-stream evaluation AND flat directory structures. +Supports: RGB-only (Sspatial), Optical-only (Soptical), or Fused (AIGVDet) +""" +import argparse +import glob +import os +import pandas as pd +import re + +import torch +import torch.nn +import torchvision.transforms as transforms +import torchvision.transforms.functional as TF +from PIL import Image +from tqdm import tqdm + +from core.utils1.utils import get_network, str2bool, to_cuda +from sklearn.metrics import accuracy_score, average_precision_score, roc_auc_score + +def get_video_name_from_filename(filename): + # Assumes format: video_name_XXXXX.png + # We split by underscore and take everything except the last part (frame number) + parts = os.path.basename(filename).rsplit('_', 1) + if len(parts) > 1: + return parts[0] + return "unknown_video" + +if __name__=="__main__": + + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("-fop", "--folder_optical_flow_path", default="data/test/T2V/videocraft", type=str) + parser.add_argument("-for", "--folder_original_path", default="data/test/original/T2V/videocraft", type=str) + parser.add_argument("-mop", "--model_optical_flow_path", type=str, default="checkpoints/optical.pth") + parser.add_argument("-mor", "--model_original_path", type=str, default="checkpoints/original.pth") + parser.add_argument("--eval_mode", type=str, choices=["fused", "rgb_only", "optical_only"], default="fused") + parser.add_argument("-t", "--threshold", type=float, default=0.5) + parser.add_argument("-e", "--excel_path", type=str, default="data/results/result.csv") + parser.add_argument("-ef", "--excel_frame_path", type=str, default="data/results/frame_result.csv") + parser.add_argument("--use_cpu", action="store_true") + parser.add_argument("--arch", type=str, default="resnet50") + parser.add_argument("--aug_norm", type=str2bool, default=True) + parser.add_argument("--no_crop", action="store_true") + parser.add_argument("--limit", type=int, default=None, help="Limit number of videos per class (optional)") + + args = parser.parse_args() + + # Load models + if args.eval_mode in ["fused", "optical_only"]: + print(f"Loading optical flow model: {args.model_optical_flow_path}") + model_op = get_network(args.arch) + state_dict = torch.load(args.model_optical_flow_path, map_location="cpu") + if "model" in state_dict: state_dict = state_dict["model"] + model_op.load_state_dict(state_dict) + model_op.eval() + if not args.use_cpu: model_op.cuda() + else: model_op = None + + if args.eval_mode in ["fused", "rgb_only"]: + print(f"Loading RGB model: {args.model_original_path}") + model_or = get_network(args.arch) + state_dict = torch.load(args.model_original_path, map_location="cpu") + if "model" in state_dict: state_dict = state_dict["model"] + model_or.load_state_dict(state_dict) + model_or.eval() + if not args.use_cpu: model_or.cuda() + else: model_or = None + + if args.no_crop: + trans = transforms.Compose((transforms.ToTensor(),)) + else: + trans = transforms.Compose((transforms.CenterCrop((448,448)), transforms.ToTensor(),)) + + print("*" * 50) + print(f"Evaluation Mode: {args.eval_mode}") + print("*" * 50) + + flag=0 + p=0; n=0; tp=0; tn=0 + y_true=[]; y_pred=[] + y_pred_original=[]; y_pred_optical=[] + + df = pd.DataFrame(columns=['name', 'pro','flag','optical_pro','original_pro']) + df1 = pd.DataFrame(columns=['original_path', 'original_pro','optical_path','optical_pro','flag']) + index1=0 + + # Check if standard structure (0_real/1_fake) exists + has_standard_structure = os.path.exists(os.path.join(args.folder_original_path, "1_fake")) or \ + os.path.exists(os.path.join(args.folder_optical_flow_path, "1_fake")) + + if has_standard_structure: + print("Detected standard folder structure (0_real/1_fake)") + subfolders = ["0_real", "1_fake"] + else: + print("Detected FLAT folder structure (treating all as 1_fake)") + subfolders = ["flat_fake"] + + for subfolder_name in subfolders: + if subfolder_name == "0_real": + flag = 0 + current_label_path = "0_real" + elif subfolder_name == "1_fake": + flag = 1 + current_label_path = "1_fake" + else: + flag = 1 # Flat structure assumed to be fake/generated videos + current_label_path = "" # Root dir + + optical_subfolder_path = os.path.join(args.folder_optical_flow_path, current_label_path) + original_subfolder_path = os.path.join(args.folder_original_path, current_label_path) + + # Get list of videos + # In flat structure, we need to group images by video prefix + video_groups = {} + + if args.eval_mode != "optical_only": + # Scan RGB folder + if os.path.exists(original_subfolder_path): + files = sorted(glob.glob(os.path.join(original_subfolder_path, "*.png")) + + glob.glob(os.path.join(original_subfolder_path, "*.jpg")) + + glob.glob(os.path.join(original_subfolder_path, "*.JPEG"))) + + # If files found directly, it's flat structure + if len(files) > 0 and not os.path.isdir(files[0]): + for f in files: + vname = get_video_name_from_filename(f) + if vname not in video_groups: video_groups[vname] = [] + video_groups[vname].append(f) + else: + # Standard structure: folders are videos + try: + video_list = os.listdir(original_subfolder_path) + for v in video_list: + v_path = os.path.join(original_subfolder_path, v) + if os.path.isdir(v_path): + frames = sorted(glob.glob(os.path.join(v_path, "*"))) + if frames: + video_groups[v] = frames + except Exception as e: + print(f"Error listing directory: {e}") + + # If optical_only, we MUST scan the optical folder + if args.eval_mode == "optical_only": + if os.path.exists(optical_subfolder_path): + files = sorted(glob.glob(os.path.join(optical_subfolder_path, "*.png")) + + glob.glob(os.path.join(optical_subfolder_path, "*.jpg")) + + glob.glob(os.path.join(optical_subfolder_path, "*.JPEG"))) + + if len(files) > 0 and not os.path.isdir(files[0]): + for f in files: + vname = get_video_name_from_filename(f) + if vname not in video_groups: video_groups[vname] = [] + video_groups[vname].append(f) + else: + try: + video_list = os.listdir(optical_subfolder_path) + for v in video_list: + v_path = os.path.join(optical_subfolder_path, v) + if os.path.isdir(v_path): + frames = sorted(glob.glob(os.path.join(v_path, "*"))) + if frames: + video_groups[v] = frames + except Exception as e: + print(f"Error listing directory: {e}") + + # If optical only or fused, we might need to check optical folder too + # But usually RGB folder structure defines the videos. + + print(f"Found {len(video_groups)} videos in {subfolder_name}") + + video_list = list(video_groups.items()) + + # Apply limit if specified + if args.limit is not None and len(video_list) > args.limit: + print(f"⚠️ Limiting to first {args.limit} videos (out of {len(video_list)})") + video_list = video_list[:args.limit] + + for idx, (video_name, frames) in enumerate(video_list, 1): + progress_pct = (idx / len(video_list)) * 100 + print(f"\r[{idx}/{len(video_list)} - {progress_pct:.1f}%] Processing: {video_name[:50]}...", end='', flush=True) + + # Detect RGB stream + original_predict = 0 + if args.eval_mode in ["fused", "rgb_only"] and model_or is not None: + original_prob_sum=0 + count = 0 + for img_path in frames: + try: + img = Image.open(img_path).convert("RGB") + img = trans(img) + if args.aug_norm: + img = TF.normalize(img, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + in_tens = img.unsqueeze(0) + if not args.use_cpu: in_tens = in_tens.cuda() + + with torch.no_grad(): + prob = model_or(in_tens).sigmoid().item() + original_prob_sum+=prob + + df1 = pd.concat([df1, pd.DataFrame([{'original_path': img_path, 'original_pro': prob , 'flag':flag}])], ignore_index=True) + count += 1 + except Exception as e: + print(f"Error processing RGB frame {img_path}: {e}") + + if count > 0: + original_predict = original_prob_sum/count + + # Detect Optical Flow stream + optical_predict = 0 + if args.eval_mode in ["fused", "optical_only"] and model_op is not None: + # Construct optical flow paths + # In flat structure: optical_path/video_name_XXXX.png + # In standard: optical_path/video_name/frame_XXXX.png + + optical_prob_sum=0 + count = 0 + + # We iterate through the SAME frames as RGB to ensure alignment + # But we need to find the corresponding optical flow file + for img_path in frames: + basename = os.path.basename(img_path) + + # Construct optical flow path + # Try standard path: .../1_fake/video_name/frame.png + opt_path_standard = os.path.join(optical_subfolder_path, video_name, basename) + # Try flat path: .../1_fake/frame.png + opt_path_flat = os.path.join(optical_subfolder_path, basename) + + if os.path.exists(opt_path_standard): + opt_path = opt_path_standard + elif os.path.exists(opt_path_flat): + opt_path = opt_path_flat + else: + # Fallback to standard for error reporting, or try root if flat structure + if current_label_path == "": + opt_path = os.path.join(optical_subfolder_path, basename) + else: + opt_path = opt_path_standard + + if os.path.exists(opt_path): + try: + img = Image.open(opt_path).convert("RGB") + img = trans(img) + if args.aug_norm: + img = TF.normalize(img, mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + in_tens = img.unsqueeze(0) + if not args.use_cpu: in_tens = in_tens.cuda() + + with torch.no_grad(): + prob = model_op(in_tens).sigmoid().item() + optical_prob_sum+=prob + + df1.loc[index1, 'optical_path'] = opt_path + df1.loc[index1, 'optical_pro'] = prob + index1+=1 + count+=1 + except Exception as e: + print(f"Error processing Flow frame {opt_path}: {e}") + index1+=1 + else: + # Flow frame missing + index1+=1 + + if count > 0: + optical_predict = optical_prob_sum/count + + # Final Prediction + if args.eval_mode == "fused": + predict = original_predict * 0.5 + optical_predict * 0.5 + elif args.eval_mode == "rgb_only": + predict = original_predict + else: + predict = optical_predict + + y_true.append(flag) + y_pred.append(predict) + y_pred_original.append(original_predict) + y_pred_optical.append(optical_predict) + + if flag==0: + n+=1 + if predict=args.threshold: tp+=1 + + df = pd.concat([df, pd.DataFrame([{'name': video_name, 'pro': predict , 'flag':flag , + 'optical_pro':optical_predict,'original_pro':original_predict}])], ignore_index=True) + + # Print newline after completing subfolder + print(f"\n✓ Completed {subfolder_name}: {len(video_list)} videos processed") + + # Metrics + # Metrics + try: + if len(y_true) == 0: + print("Error: No videos were processed. Cannot calculate metrics.") + ap = 0.0; auc = 0.0; acc = 0.0 + elif len(set(y_true)) > 1: + ap = average_precision_score(y_true, y_pred) + auc = roc_auc_score(y_true,y_pred) + acc = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred]) + else: + ap = 0.0; auc = 0.0 + # Calculate accuracy even if only one class + acc = accuracy_score(y_true, [1 if p >= args.threshold else 0 for p in y_pred]) + print(f"Warning: Only one class present (Class {y_true[0]}). AUC/AP cannot be calculated.") + except Exception as e: + print(f"Error calculating metrics: {e}") + ap=0.0; auc=0.0; acc=0.0 + + print("-" * 30) + print(f"Evaluation Mode: {args.eval_mode}") + print(f"tnr: {tn/n if n > 0 else 0:.4f}") + print(f"tpr: {tp/p if p > 0 else 0:.4f}") + print(f"acc: {acc:.4f}") + print(f"auc: {auc:.4f}") + print("-" * 30) + + # Save results + csv_folder = os.path.dirname(args.excel_path) + if not os.path.exists(csv_folder): os.makedirs(csv_folder) + + if not os.path.exists(args.excel_path): df.to_csv(args.excel_path, index=False) + else: df.to_csv(args.excel_path, mode='a', header=False, index=False) + + csv_folder1 = os.path.dirname(args.excel_frame_path) + if not os.path.exists(csv_folder1): os.makedirs(csv_folder1) + + if not os.path.exists(args.excel_frame_path): df1.to_csv(args.excel_frame_path, index=False) + else: df1.to_csv(args.excel_frame_path, mode='a', header=False, index=False) + + print(f"Results saved to {args.excel_path}") diff --git a/train.py b/train.py index a87f639..956099f 100644 --- a/train.py +++ b/train.py @@ -121,6 +121,29 @@ val_writer.add_scalar("TPR", val_results["TPR"], trainer.total_steps) val_writer.add_scalar("TNR", val_results["TNR"], trainer.total_steps) + # Log validation metrics to wandb + "saving the latest model %s (epoch %d, model.total_steps %d)\n" + % (cfg.exp_name, epoch, trainer.total_steps) + ) + trainer.save_networks("latest") + + if epoch % cfg.save_epoch_freq == 0: + print(f"💾 Saving epoch checkpoint {epoch+1}") + log.write("saving the model at the end of epoch %d, iters %d\n" % (epoch, trainer.total_steps)) + trainer.save_networks("latest") + trainer.save_networks(epoch) + + # Validation + print("\n🔍 Running validation...") + trainer.eval() + val_results = validate(trainer.model, val_cfg) + val_writer.add_scalar("AP", val_results["AP"], trainer.total_steps) + val_writer.add_scalar("ACC", val_results["ACC"], trainer.total_steps) + # add + val_writer.add_scalar("AUC", val_results["AUC"], trainer.total_steps) + val_writer.add_scalar("TPR", val_results["TPR"], trainer.total_steps) + val_writer.add_scalar("TNR", val_results["TNR"], trainer.total_steps) + # Log validation metrics to wandb wandb.log({ "val/AP": val_results["AP"], From 0593026ee0a31ee41204f9463b4e26c56d82a3ff Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:11:19 +0800 Subject: [PATCH 52/55] feat: Add scripts for local and Docker GUI execution on Windows and Linux, and update Dockerfile with GUI app and dependencies. --- Dockerfile.gpu-alt | 7 +++++-- run_gui.bat | 13 ++++++++++++- run_gui.sh | 18 ++++++++++++++++++ run_gui_docker.bat | 14 ++++++++++++++ run_gui_docker.sh | 14 ++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 run_gui.sh create mode 100644 run_gui_docker.bat create mode 100644 run_gui_docker.sh diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 2b23046..52031c7 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -32,7 +32,7 @@ COPY pyproject.toml ./ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ -COPY train.py test.py demo.py download_data.py ./ +COPY train.py test.py demo.py download_data.py gui_app.py ./ # Create directories for data and checkpoints RUN mkdir -p /app/data /app/checkpoints /app/raft_model @@ -52,10 +52,13 @@ RUN uv pip install --system \ tensorboardX \ tqdm \ "blobfile>=1.0.5" \ - wandb \ + "wandb==0.16.6" \ python-dotenv \ gdown +# Install pyarrow with a version that has pre-built wheels, then streamlit +RUN uv pip install --system "pyarrow>=14.0.0,<15.0.0" streamlit + # Install torchvision separately (base image has torch but may need torchvision update) RUN pip3 install torchvision==0.15.1+cu117 \ --index-url https://download.pytorch.org/whl/cu117 || \ diff --git a/run_gui.bat b/run_gui.bat index 6157fa2..c2011a1 100644 --- a/run_gui.bat +++ b/run_gui.bat @@ -1,5 +1,16 @@ @echo off echo Starting AIGVDet GUI... -echo Ensure you have installed requirements: pip install streamlit +echo. +echo Checking if streamlit is installed... +python -c "import streamlit" 2>nul +if errorlevel 1 ( + echo Streamlit not found. Installing required packages... + pip install streamlit torch torchvision opencv-python numpy pillow natsort tqdm +) + +echo. +echo Starting GUI on http://localhost:8501 +echo Press Ctrl+C to stop +echo. streamlit run gui_app.py pause diff --git a/run_gui.sh b/run_gui.sh new file mode 100644 index 0000000..9581029 --- /dev/null +++ b/run_gui.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Run AIGVDet GUI locally (without Docker) + +echo "Starting AIGVDet GUI..." +echo "" + +# Check if streamlit is installed +if ! python -c "import streamlit" 2>/dev/null; then + echo "Streamlit not found. Installing required packages..." + pip install streamlit torch torchvision opencv-python numpy pillow natsort tqdm +fi + +echo "" +echo "Starting GUI on http://localhost:8501" +echo "Press Ctrl+C to stop" +echo "" + +streamlit run gui_app.py diff --git a/run_gui_docker.bat b/run_gui_docker.bat new file mode 100644 index 0000000..95f59ae --- /dev/null +++ b/run_gui_docker.bat @@ -0,0 +1,14 @@ +@echo off +REM Run AIGVDet GUI in Docker (Windows) + +echo Starting AIGVDet GUI... +echo Installing Streamlit and starting server... +echo Access the GUI at: http://localhost:8501 +echo. + +docker run --gpus all -p 8501:8501 -it ^ + -v %cd%/output_data:/app/output_data ^ + -v %cd%/checkpoints:/app/checkpoints ^ + -v %cd%/raft-model:/app/raft-model ^ + sacdalance/thesis-aigvdet:latest-gpu ^ + streamlit run gui_app.py --server.address=0.0.0.0 diff --git a/run_gui_docker.sh b/run_gui_docker.sh new file mode 100644 index 0000000..7e0223b --- /dev/null +++ b/run_gui_docker.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Run AIGVDet GUI in Docker + +echo "Starting AIGVDet GUI..." +echo "Installing Streamlit and starting server..." +echo "Access the GUI at: http://localhost:8501" +echo "" + +docker run --gpus all -p 8501:8501 -it \ + -v $(pwd)/output_data:/app/output_data \ + -v $(pwd)/checkpoints:/app/checkpoints \ + -v $(pwd)/raft-model:/app/raft-model \ + sacdalance/thesis-aigvdet:latest-gpu \ + streamlit run gui_app.py --server.address=0.0.0.0 From 35e1c5a0475b8bf37ba63b215822ed0d9adda2f3 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 6 Dec 2025 01:22:48 +0800 Subject: [PATCH 53/55] feat: Add `__contains__` method to RAFT Args object for compatibility with `in` operator. --- gui_app.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/gui_app.py b/gui_app.py index 373b032..08f92de 100644 --- a/gui_app.py +++ b/gui_app.py @@ -117,12 +117,16 @@ def video_to_frames(video_path, output_folder, progress_bar=None): # Load RAFT try: - # Mock args for RAFT + # Args object for RAFT - needs to support 'in' operator class Args: - model = raft_model_path - small = False - mixed_precision = False - alternate_corr = False + def __init__(self): + self.model = raft_model_path + self.small = False + self.mixed_precision = False + self.alternate_corr = False + + def __contains__(self, key): + return hasattr(self, key) args = Args() model = torch.nn.DataParallel(RAFT(args)) From e702155e87b2b0ed15b2cf5eb15c967852a0d964 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 6 Dec 2025 02:15:31 +0800 Subject: [PATCH 54/55] feat: Implement model file size validation and display detailed processing times for video extraction and detection steps. --- gui_app.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/gui_app.py b/gui_app.py index 08f92de..4f7b84e 100644 --- a/gui_app.py +++ b/gui_app.py @@ -96,6 +96,15 @@ def video_to_frames(video_path, output_folder, progress_bar=None): # Model Upload raft_model_file = st.file_uploader("Upload RAFT Model (raft.pth)", type=['pth'], key="raft_uploader") + # Check file size (500MB limit) + if raft_model_file: + file_size_mb = raft_model_file.size / (1024 * 1024) + if file_size_mb > 500: + st.error(f"File size ({file_size_mb:.1f} MB) exceeds 500MB limit. Please upload a smaller model.") + raft_model_file = None + else: + st.success(f"Model size: {file_size_mb:.1f} MB") + # Video Upload (Batch or Solo) uploaded_videos = st.file_uploader("Upload Video(s)", type=['mp4', 'avi', 'mov', 'mkv'], accept_multiple_files=True, key="video_uploader") @@ -103,6 +112,8 @@ def video_to_frames(video_path, output_folder, progress_bar=None): output_root = st.text_input("Output Directory Root", value="output_data") if st.button("Start Extraction", key="extract_btn"): + extraction_start_time = time.time() + if not raft_model_file: st.error("Please upload the RAFT model.") elif not uploaded_videos: @@ -141,6 +152,7 @@ def __contains__(self, key): total_videos = len(uploaded_videos) for i, video_file in enumerate(uploaded_videos): + video_start_time = time.time() video_name = video_file.name st.subheader(f"Processing: {video_name} ({i+1}/{total_videos})") @@ -156,15 +168,18 @@ def __contains__(self, key): # 1. Extract Frames st.write("Extracting frames...") + frame_start = time.time() p_bar = st.progress(0) images = video_to_frames(video_path, frame_output_dir, p_bar) - st.write(f"Extracted {len(images)} frames to `{frame_output_dir}`") + frame_duration = time.time() - frame_start + st.write(f"✓ Extracted {len(images)} frames to `{frame_output_dir}` in {frame_duration:.2f}s") # 2. Generate Optical Flow if not os.path.exists(flow_output_dir): os.makedirs(flow_output_dir) st.write("Generating Optical Flow...") + flow_start = time.time() images = natsorted(images) flow_p_bar = st.progress(0) @@ -182,12 +197,17 @@ def __contains__(self, key): flow_p_bar.progress((idx + 1) / (len(images) - 1)) - st.write(f"Optical Flow saved to `{flow_output_dir}`") + flow_duration = time.time() - flow_start + st.write(f"✓ Optical Flow saved to `{flow_output_dir}` in {flow_duration:.2f}s") + + video_duration = time.time() - video_start_time + st.info(f"**Video processed in {video_duration:.2f} seconds**") # Cleanup temp video os.remove(video_path) - st.success("All videos processed!") + total_extraction_time = time.time() - extraction_start_time + st.success(f"✓ All videos processed! Total time: {total_extraction_time:.2f} seconds") # Cleanup temp model os.remove(raft_model_path) @@ -203,8 +223,23 @@ def __contains__(self, key): col1, col2 = st.columns(2) with col1: optical_model_file = st.file_uploader("Upload Optical Flow Model (optical.pth)", type=['pth'], key="opt_uploader") + if optical_model_file: + opt_size_mb = optical_model_file.size / (1024 * 1024) + if opt_size_mb > 500: + st.error(f"File size ({opt_size_mb:.1f} MB) exceeds 500MB limit.") + optical_model_file = None + else: + st.success(f"Optical model: {opt_size_mb:.1f} MB") + with col2: original_model_file = st.file_uploader("Upload RGB Model (original.pth)", type=['pth'], key="orig_uploader") + if original_model_file: + orig_size_mb = original_model_file.size / (1024 * 1024) + if orig_size_mb > 500: + st.error(f"File size ({orig_size_mb:.1f} MB) exceeds 500MB limit.") + original_model_file = None + else: + st.success(f"RGB model: {orig_size_mb:.1f} MB") # Input for processed data path # Default to the output of Tab 1 if available @@ -278,6 +313,7 @@ def __contains__(self, key): results = [] for vid_folder in video_folders: + video_detect_start = time.time() st.subheader(f"Analyzing: {vid_folder}") rgb_path = os.path.join(frames_root, vid_folder) @@ -294,6 +330,7 @@ def __contains__(self, key): rgb_prob_sum = 0 rgb_bar = st.progress(0, text="RGB Detection") + rgb_start = time.time() for i, img_path in enumerate(rgb_files): img = Image.open(img_path).convert("RGB") @@ -307,8 +344,9 @@ def __contains__(self, key): rgb_bar.progress((i + 1) / len(rgb_files)) + rgb_duration = time.time() - rgb_start rgb_score = rgb_prob_sum / len(rgb_files) if rgb_files else 0 - st.write(f"RGB Score: {rgb_score:.4f}") + st.write(f"✓ RGB Score: {rgb_score:.4f} ({len(rgb_files)} frames in {rgb_duration:.2f}s)") # Optical Flow Detection opt_files = sorted(glob.glob(os.path.join(opt_path, "*.jpg")) + @@ -317,6 +355,7 @@ def __contains__(self, key): opt_prob_sum = 0 opt_bar = st.progress(0, text="Optical Flow Detection") + opt_start = time.time() for i, img_path in enumerate(opt_files): img = Image.open(img_path).convert("RGB") @@ -329,17 +368,21 @@ def __contains__(self, key): opt_prob_sum += prob opt_bar.progress((i + 1) / len(opt_files)) - + + opt_duration = time.time() - opt_start opt_score = opt_prob_sum / len(opt_files) if opt_files else 0 - st.write(f"Optical Flow Score: {opt_score:.4f}") + st.write(f"✓ Optical Flow Score: {opt_score:.4f} ({len(opt_files)} frames in {opt_duration:.2f}s)") # Final Decision final_score = (rgb_score * 0.5) + (opt_score * 0.5) decision = "FAKE VIDEO (AI-Generated)" if final_score >= threshold else "REAL VIDEO" color = "red" if final_score >= threshold else "green" + video_detect_duration = time.time() - video_detect_start + st.markdown(f"### Result: :{color}[{decision}]") st.write(f"**Combined Probability:** {final_score:.4f}") + st.write(f"**Detection Time:** {video_detect_duration:.2f}s") st.divider() results.append({ From 960109347fd06b5bf3bc3f538eacd056b2488020 Mon Sep 17 00:00:00 2001 From: Lance Sacdalan <63360390+sacdalance@users.noreply.github.com> Date: Sat, 6 Dec 2025 02:23:54 +0800 Subject: [PATCH 55/55] feat: Add Streamlit application configuration and include it in the Docker image. --- .streamlit/config.toml | 5 +++++ Dockerfile.gpu-alt | 1 + 2 files changed, 6 insertions(+) create mode 100644 .streamlit/config.toml diff --git a/.streamlit/config.toml b/.streamlit/config.toml new file mode 100644 index 0000000..3a5bd47 --- /dev/null +++ b/.streamlit/config.toml @@ -0,0 +1,5 @@ +[server] +maxUploadSize = 500 + +[browser] +gatherUsageStats = false diff --git a/Dockerfile.gpu-alt b/Dockerfile.gpu-alt index 52031c7..daf0570 100644 --- a/Dockerfile.gpu-alt +++ b/Dockerfile.gpu-alt @@ -32,6 +32,7 @@ COPY pyproject.toml ./ COPY README.md ./ COPY core/ ./core/ COPY networks/ ./networks/ +COPY .streamlit/ ./.streamlit/ COPY train.py test.py demo.py download_data.py gui_app.py ./ # Create directories for data and checkpoints