Skip to content

PRD: Visual-Mic project hardening #2

Description

@joeljose

Problem Statement

The Visual-Mic project lacks the testing, CI/CD, versioning, and documentation infrastructure present in the two sibling repos (EVM, DTCWT Motion Mag). It also ships denoising code that duplicates a separate joeljose/audio_denoising repo, violating single-responsibility. These gaps make the project harder to maintain, contribute to, and trust for correctness.

User Stories

  1. As a contributor, I want to run ./test.sh and get lint + unit test results so I know if my changes break anything.
  2. As a user, I want to run --version to know which version I'm running.
  3. As a maintainer, I want CI to catch regressions on every push.
  4. As a user, I want denoising to live in its dedicated repo (audio_denoising) rather than being baked into the visual mic tool, so each tool has a clear single purpose.
  5. As a user, I want to see a clear error before processing starts if my video will exceed available RAM/VRAM, rather than hitting an OOM crash mid-pipeline.

Proposed Solution

Bring Visual-Mic to parity with EVM and DTCWT Motion Mag by:

  1. Remove denoising — Delete denoise_spectral(), denoise_morphological(), --denoise, --denoise-input and all related code/imports. Remove Part 4 from README. Remove scipy.io.wavfile.read import (only needed for --denoise-input). Remove scipy.ndimage import (only needed for morphological denoising). Update the error message to remove the --denoise-input reference.

  2. Version management — Add VERSION file (2.0.0), __version__ in visualmic.py, --version CLI flag. Update Docker build scripts to read VERSION and tag images with version + latest.

  3. Pre-flight memory check — Estimate peak CPU RAM (and VRAM for GPU mode) before processing. Warn if estimated usage exceeds 70% of available resources, with actionable suggestions (reduce nlevels, use ROI, switch modes). Matching the pattern from EVM and DTCWT Motion Mag repos.

  4. Testing — Add tests/test_visualmic.py with tiered tests:

    • Strict: format_duration, find_best_shift, save_wav output format
    • Moderate: postprocess_phase_signals (cross-correlation alignment, normalization), Butterworth filter (passband/stopband), ROI cropping validation
    • Smoke: extract_audio on a synthetic 256x256 video (correct output shape, finite values), CLI validation error paths
    • Add tests/test_visualmic_gpu.py: GPU DTCWT forward pass (shapes, finite values), extract_audio_gpu smoke test. All wrapped in @pytest.mark.skipif(not HAS_CUDA).
    • Add requirements-dev.txt (pytest, ruff)
    • Add test.sh matching DTCWT pattern (cpu/gpu modes, Docker-based)
  5. CI/CD — Add .github/workflows/ci.yml: lint (ruff), import test, --help test, --version test. No pipeline smoke test in CI.

  6. Docs — Add CHANGELOG.md (start at v2.0.0), CONTRIBUTING.md, docs/design/visualmic-hardening.md ADR.

  7. GPU Dockerfile upgradepytorch/pytorch:1.12.1-cuda11.3pytorch/pytorch:2.1.2-cuda12.1-cudnn8-runtime. Pin numpy<2 in requirements-gpu.txt (pytorch_wavelets compatibility). Add version label and --build-arg VERSION. Live-verified: pytorch_wavelets DTCWTForward works correctly on PyTorch 2.1.2 / CUDA 12.1.

  8. README cleanup — Remove Part 4 (Denoising). Restructure Setup (remove YouTube link, match EVM/DTCWT tone). Add CI badge. Add Development section (tests, versioning, project structure). Update Future Work (remove GPU as done, remove denoising reference). Add --nlevels, --biort, --qshift to CLI table.

  9. Add --nlevels, --biort, --qshift CLI flagsnlevels currently hard-coded to 3 (make configurable, default 3). Add --biort (default near_sym_b) and --qshift (default qshift_b) matching DTCWT Motion Mag v2.0.0 defaults. Apply to both CPU and GPU paths for consistency.

  10. Dockerfiles — Copy tests/ directory and requirements-dev.txt into both images so test.sh works. Add version labels.

Key Decisions

Decision Choice Why
Version 2.0.0 Removing --denoise/--denoise-input CLI flags is a breaking change. Semver requires major bump. Matches precedent from DTCWT Motion Mag v2.0.0.
Wavelet filter defaults near_sym_b/qshift_b (both paths) Matches DTCWT Motion Mag v2.0.0. Longer filters produce fewer block artifacts. Expose --biort/--qshift for user control.
GPU Dockerfile base pytorch/pytorch:2.1.2-cuda12.1 Live-verified compatible with pytorch_wavelets DTCWTForward. Pin numpy<2 for pytorch_wavelets compat.
Refactor GPU/CPU duplication Defer Both functions share postprocess_phase_signals already. Further refactoring is a separate task.
CI scope Lint + import + help + version only Matches other repos. Pipeline smoke tests run locally via ./test.sh during TDD.
Pre-flight memory check Include in v2.0.0 Matches EVM and DTCWT Motion Mag patterns. Prevents OOM crashes mid-pipeline.

Scope

In v2.0.0:

  • Remove denoising code
  • Version management (VERSION, __version__, --version)
  • --nlevels, --biort, --qshift CLI flags
  • Pre-flight memory/VRAM estimation and warning
  • Testing infrastructure (CPU + GPU tests, test.sh)
  • CI/CD pipeline
  • Project docs (CHANGELOG, CONTRIBUTING, ADR)
  • GPU Dockerfile upgrade (PyTorch 2.1.2 / CUDA 12.1)
  • README cleanup
  • Docker version tagging

Deferred:

  • GPU/CPU code deduplication refactor
  • Multiprocessing across frames

Testing Plan

Behaviors needing automated tests:

  • format_duration() — exact string output for edge cases (0s, 59s, 60s, 3661s)
  • find_best_shift() — known shift recovery on synthetic signals
  • save_wav() — output file exists, correct sample rate, int16 range
  • postprocess_phase_signals() — constant input → zero output (no motion), known shift recovery, normalization to [-1, 1]
  • estimate_memory() — arithmetic correctness, frame count scaling
  • Butterworth filter — passband signal preserved, stopband signal attenuated, edge cases (freq_low >= Nyquist)
  • ROI parsing and validation — valid/invalid inputs
  • CLI validation — missing input, bad frequencies, bad ROI format

Hard to test / needs stubbing:

  • extract_audio() full pipeline — create a 256x256 synthetic video (random noise) as a pytest fixture. Verify output shape and finiteness, not exact audio content.
  • GPU tests — @pytest.mark.skipif(not HAS_CUDA), skip in CI (no GPU runners)
  • Video I/O — use cv2.VideoWriter to create temp videos in fixtures

Open Questions

None — all decisions resolved during grill phase.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions