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
- As a contributor, I want to run
./test.sh and get lint + unit test results so I know if my changes break anything.
- As a user, I want to run
--version to know which version I'm running.
- As a maintainer, I want CI to catch regressions on every push.
- 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.
- 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:
-
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.
-
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.
-
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.
-
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)
-
CI/CD — Add .github/workflows/ci.yml: lint (ruff), import test, --help test, --version test. No pipeline smoke test in CI.
-
Docs — Add CHANGELOG.md (start at v2.0.0), CONTRIBUTING.md, docs/design/visualmic-hardening.md ADR.
-
GPU Dockerfile upgrade — pytorch/pytorch:1.12.1-cuda11.3 → pytorch/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.
-
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.
-
Add --nlevels, --biort, --qshift CLI flags — nlevels 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.
-
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.
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_denoisingrepo, violating single-responsibility. These gaps make the project harder to maintain, contribute to, and trust for correctness.User Stories
./test.shand get lint + unit test results so I know if my changes break anything.--versionto know which version I'm running.audio_denoising) rather than being baked into the visual mic tool, so each tool has a clear single purpose.Proposed Solution
Bring Visual-Mic to parity with EVM and DTCWT Motion Mag by:
Remove denoising — Delete
denoise_spectral(),denoise_morphological(),--denoise,--denoise-inputand all related code/imports. Remove Part 4 from README. Removescipy.io.wavfile.readimport (only needed for--denoise-input). Removescipy.ndimageimport (only needed for morphological denoising). Update the error message to remove the--denoise-inputreference.Version management — Add
VERSIONfile (2.0.0),__version__invisualmic.py,--versionCLI flag. Update Docker build scripts to readVERSIONand tag images with version + latest.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.
Testing — Add
tests/test_visualmic.pywith tiered tests:format_duration,find_best_shift,save_wavoutput formatpostprocess_phase_signals(cross-correlation alignment, normalization), Butterworth filter (passband/stopband), ROI cropping validationextract_audioon a synthetic 256x256 video (correct output shape, finite values), CLI validation error pathstests/test_visualmic_gpu.py: GPU DTCWT forward pass (shapes, finite values),extract_audio_gpusmoke test. All wrapped in@pytest.mark.skipif(not HAS_CUDA).requirements-dev.txt(pytest, ruff)test.shmatching DTCWT pattern (cpu/gpu modes, Docker-based)CI/CD — Add
.github/workflows/ci.yml: lint (ruff), import test,--helptest,--versiontest. No pipeline smoke test in CI.Docs — Add
CHANGELOG.md(start at v2.0.0),CONTRIBUTING.md,docs/design/visualmic-hardening.mdADR.GPU Dockerfile upgrade —
pytorch/pytorch:1.12.1-cuda11.3→pytorch/pytorch:2.1.2-cuda12.1-cudnn8-runtime. Pinnumpy<2inrequirements-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.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,--qshiftto CLI table.Add
--nlevels,--biort,--qshiftCLI flags —nlevelscurrently hard-coded to 3 (make configurable, default 3). Add--biort(defaultnear_sym_b) and--qshift(defaultqshift_b) matching DTCWT Motion Mag v2.0.0 defaults. Apply to both CPU and GPU paths for consistency.Dockerfiles — Copy
tests/directory andrequirements-dev.txtinto both images sotest.shworks. Add version labels.Key Decisions
2.0.0--denoise/--denoise-inputCLI flags is a breaking change. Semver requires major bump. Matches precedent from DTCWT Motion Mag v2.0.0.near_sym_b/qshift_b(both paths)--biort/--qshiftfor user control.pytorch/pytorch:2.1.2-cuda12.1postprocess_phase_signalsalready. Further refactoring is a separate task../test.shduring TDD.Scope
In v2.0.0:
__version__,--version)--nlevels,--biort,--qshiftCLI flagsDeferred:
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 signalssave_wav()— output file exists, correct sample rate, int16 rangepostprocess_phase_signals()— constant input → zero output (no motion), known shift recovery, normalization to [-1, 1]estimate_memory()— arithmetic correctness, frame count scalingHard 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.@pytest.mark.skipif(not HAS_CUDA), skip in CI (no GPU runners)cv2.VideoWriterto create temp videos in fixturesOpen Questions
None — all decisions resolved during grill phase.