Real-world DOA benchmark (Part 1) + Streamlit demo on Render (Part 2) - #3
Conversation
…tAPI+Streamlit demo
Part 1 (real-world data swap):
- src/audio_source.py: generic mono/multi-channel .wav/.flac loader,
keeps the heart-sound .mat path (src/preprocess.py) untouched and
selectable, adds a separate load_multichannel_recording() path for
already-recorded multi-mic uploads that skip room simulation.
- src/pipeline.py: shared DOA + triangulation pipeline (room/mic
geometry constants reused from validation/run_validation.py for a
fair before/after comparison, RT60_LEVELS matched exactly to the
existing low/medium/high baseline configs, bearing-only fallback
when only one mic cluster is available since triangulation needs
>=2 rays).
- experiments/real_world_benchmark.py: runs MUSIC/SRP/TOPS/CSSM/WAVES
across 3 datasets (heart-proxy white noise, LibriSpeech, ESC-50) x 3
RT60 levels x 3 source positions (135 rows), writing
experiments/results/real_world_benchmark.{csv,json}.
- data/: small real-world benchmark clips (LibriSpeech, ESC-50) plus
data/heart_proxy_samples/, which documents and reproduces the
original repo's actual baseline signal -- literal Gaussian white
noise, not real or synthetic heart-sound audio (see its README.md).
- Bug fix: src/triangulation.py's new pairwise_ray_intersections() had
a sign error in the closest-point-between-two-skew-lines formula
(reflected points through the ray origin instead of finding the true
closest approach); fixed and covered by
test/unit/test_pairwise_ray_intersections.py.
- 12 new unit tests (test_audio_source.py, test_pipeline.py,
test_pairwise_ray_intersections.py, test_api.py); full suite 104/104
passing.
Part 2 (interactive demo):
- api/app.py: FastAPI backend wrapping the DOA pipeline
(GET /api/presets, /api/upload-mic-geometry, /api/health,
POST /api/run/preset, /api/run/upload).
- demo/app.py: single-page Streamlit frontend -- preset or upload
mode, 3D Plotly view of mic clusters/rays/pairwise intersection
cloud/estimated vs true position, results panel with angle/position
error and measured RT60.
- Dockerfile + docker-entrypoint.sh: single-container packaging
(uvicorn + streamlit) for Cloud-Run-style hosts. NOT build-tested --
no docker binary was available in the development sandbox; disclosed
in demo/README.md.
- demo/README.md: local run instructions (plain venv and Docker).
…rk data - experiments/results/: full 135-row benchmark output (3 datasets x 3 RT60 levels x 5 algorithms x 3 source positions), zero runtime errors. - reports/part1_results.md: headline finding is that reverberation-driven accuracy degradation is universal across all three datasets (roughly 3-20x worse position error and single-digit-degree to 75-105-degree bearing error swinging from low to high RT60 in every dataset) -- swapping in real-world audio does not fix it, confirming it is a physical/algorithmic DOA limitation and not a heart-sound-data artifact. One narrower exception: TOPS specifically performs badly on the narrowband heart-proxy signal even at low reverberation, and that weakness goes away on broadband real-world audio -- reverberation- independent, algorithm-specific, but does support part of the original hypothesis about narrow frequency range. - reports/deployment_status.md: the FastAPI+Streamlit demo runs correctly locally (verified); it is not deployed to a public URL. No Cloud Run connector exists in this environment (the literal request), and a real, multi-attempt Vercel deployment of the backend hit a 'You don't have permission to create a Production Deployment for this project' error tied to the connected account/team, not the code -- documented plainly with what was tried. - README.md: added a top-level pointer section to both reports plus the new benchmark/demo code, without touching the existing accuracy section's original numbers.
Removes the two-process FastAPI+Streamlit architecture and every GCP reference in favor of one Python process (Streamlit calling the pipeline in-process) in one Dockerized Render Web Service, per updated requirements. - src/service.py: new shared layer (preset catalog, run_preset/ run_upload) used directly by demo/app.py and by the now-optional api/app.py wrapper. Removes the HTTP hop entirely. - demo/app.py: calls src/service.py in-process; no more the `requests` library or DOA_API_BASE_URL. - api/app.py: kept only as an optional, non-deployed dev/test convenience wrapper (not started in the container). - src/paths.py: single APP_DATA_DIR-based root for all runtime-writable data (uploads, generated .mat scratch files), defaulting to ./app_data locally and /var/data in Render. - src/experiment.py / src/pipeline.py: ExperimentalMicData now accepts a configurable output_dir and generates collision-safe filenames (uuid suffix) instead of a hardcoded output/test_first_fun.mat path; scratch files are deleted immediately after use. - Dockerfile / docker-entrypoint.sh: single-process image, non-root user, respects Render's PORT env var via entrypoint expansion, no GCP/Cloud Run references. - .dockerignore, render.yaml, .env.example: new. - vercel.json: removed (no separate backend or frontend to route). - README.md / demo/README.md / reports/deployment_status.md: rewritten for the new architecture, Render deployment steps, storage/health check reasoning, and an honest validation report (no Docker daemon or Render connector available in this session -- see the report for exactly what was and wasn't verified). - requirements.txt: adds streamlit/plotly (previously only installed ad hoc in the old Dockerfile); fastapi/uvicorn/httpx kept for the optional wrapper + its tests. 104/104 tests still pass. Verified end-to-end via Playwright against the literal docker-entrypoint.sh command (no Docker daemon in this sandbox) -- both preset and upload modes render correctly, PORT expansion works, APP_DATA_DIR scratch files are created and cleaned up.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Root cause: python:3.11-slim ships /var as root-owned, mode 755. The
Dockerfile only created and chowned the local-dev default
(/app/app_data) to the non-root appuser at build time -- it never
touched /var/data, the path Render actually sets APP_DATA_DIR to in
production. At runtime, appuser (uid 1000) tried to
os.makedirs("/var/data") and got [Errno 13] Permission denied, since a
non-root process cannot create a directory under a root-owned,
non-writable parent.
Fix:
- Dockerfile: create AND chown both /app/app_data and /var/data to
appuser while still root, before the USER appuser switch. /var/data
now exists and is writable by the time the container starts,
regardless of which APP_DATA_DIR value is set at runtime.
- docker-entrypoint.sh: runs the new scripts/check_app_data_dir.sh
before starting Streamlit -- logs the resolved APP_DATA_DIR and
running user, and verifies real writability (create+remove a scratch
file) before Streamlit or the slow pyroomacoustics/scipy imports
behind it even start. Fails fast with a clear FATAL message instead
of a cryptic error the first time a user clicks "Run".
- src/paths.py: get_app_data_dir() now performs the same real
write-probe and raises a clear AppDataDirError (naming the resolved
path, uid, and pointing at the Dockerfile) instead of letting a bare
PermissionError bubble up to demo/app.py's generic exception handler.
- test/unit/test_app_data_dir.py: 7 new regression tests, run as the
actual non-root sandbox user (uid 2000, never root) -- proves
get_app_data_dir()/get_uploads_dir()/get_generated_dir() can create
and remove scratch files; proves a clear AppDataDirError is raised
against an unwritable directory; runs check_app_data_dir.sh directly
as a subprocess against both a writable and (chmod 555) unwritable
directory and asserts the exact success/failure behavior.
- reports/deployment_status.md: added an honest incident writeup with
cause, fix, and everything verified.
No architecture or deploy target changes: still one Dockerized
Streamlit service on Render, APP_DATA_DIR=/var/data, no persistent
disk. Verified end-to-end by running the real docker-entrypoint.sh
against both a deliberately unwritable directory (fails immediately,
never starts Streamlit) and a writable one (starts cleanly, serves
HTTP 200, leaves no scratch files behind) -- no Docker daemon available
in this sandbox, same limitation noted throughout this report.
111/111 tests passing (104 previous + 7 new).
Fixed:
|
README:
- Rework intro sentence to frame the fork's expanded scope
- Remove internal repair history, old Time-to-Run, original
heart-sound results, Render/Vercel deploy instructions, and
references/credits sections -- all obsolete or internal-only
- Rename real-world benchmark section (drop 'this round's additions')
- Rewrite Algorithm section into 4 clear subsections covering TDOA,
triangulation/geometry, reverberation-is-hard (not solved), and the
existing coplanar/non-planar cluster explanation
Heart-proxy removal:
- Delete the heart-proxy preset from src/service.py's preset registry;
rename the remaining 3 presets to the exact required labels
- Update test/unit/test_api.py (preset count/ids, renamed end-to-end
test, default preset in the bad-algorithm test)
- Update demo/README.md preset count and description wording
- Reword reports/deployment_status.md's historical validation note to
flag the preset it references has since been removed
- Add a one-line historical-context note to reports/part1_results.md
(kept intact -- it's a legitimate benchmark report, not a demo
reference); data/heart_proxy_samples/ intentionally preserved since
experiments/real_world_benchmark.py still depends on it
Demo UX (demo/app.py):
- New title 'Where is that sound coming from?' with a responsive
clamp()-sized font so it doesn't wrap into many lines on a 375px
phone viewport, plus a subtitle
- Replaced the old intro paragraph with the two required plain-language
paragraphs; removed the preset-dropdown caption entirely (no
replacement) per spec
- Added the required captions under the DOA-algorithm and reverb
selectors
- Renamed 'Results' to 'How close did it get?'; angle error is now the
first, most prominent result ('Off by X deg'), with dynamic decimal
precision
- Added an honest, documented 'Roughly X cm at the far wall' line,
computed via ray-box exit distance to the room boundary along the
true bearing and a small-angle arc-length approximation (formula and
assumptions documented in code comments); omitted automatically when
the geometry needed to compute it isn't available
- Moved all secondary metrics (algorithm, runtime, azimuth/colatitude,
coordinates, RT60, distance error) into a collapsed 'Full numbers'
expander
- Added 'How this works' (README Algorithm anchor) and 'Source' (repo)
footer links; added a mobile orientation caption near the top since
Streamlit's sidebar toggle icon has no text label
Verified: full pytest suite (111/111 passing) and a local Streamlit
smoke test via Playwright covering all 3 presets, upload mode, footer
link hrefs, and a 375px mobile viewport (no horizontal overflow,
sidebar expand/collapse, expander, results all readable).
Follow-up pass: documentation + Streamlit UX polish, heart-proxy removedThis pass focused on docs and demo UX only -- no changes to the DOA algorithms, benchmark methodology, or Render deployment architecture. README.md
Heart-proxy removed cleanly
Demo UX (
Validation
Not merging -- leaving this open per the review request. |
- README: remove the stale 'Generic .wav/.flac data loading' bullet referencing a heart_sound mode that no longer exists in src/audio_source.py - Demo intro: 'Pick a sound below' -> 'Pick a sound to the left (or upload your own) and run it.' - Demo results: remove the 'Roughly X cm at the far wall' room-scale line and its now-unused _ray_box_exit_distance helper entirely (no replacement approximation added); drop the now-unused math import - Demo footer: remove the extra 'Source: ... thin UI ...' prose sentence, keeping only the 'How this works · Source' link line Verified: pytest test/ (111/111 passing) + a local Streamlit smoke check confirming the updated intro text, the removed far-wall line, the removed footer prose, and the preserved footer links.
…ference - README.md: add a 'Deploying to Render' section (Blueprint flow + manual-equivalent settings/env vars, matching render.yaml exactly). Three files (render.yaml's own header comment, demo/README.md, and reports/deployment_status.md) already pointed readers at this section, but it did not exist. - reports/deployment_status.md: fix a dangling self-reference to a nonexistent README.md 'Vercel' section (Vercel support was already fully removed in an earlier pass); point at the real 'Deploying to Render' section instead. No behavior change. Verified via a repo-wide markdown link/anchor scan that no broken internal doc links remain.
_run_doa_and_report() (private helper in src/pipeline.py) accepted this parameter but never read it inside the function body. Confirmed via vulture (100% confidence) and a manual read of the full function. Neither of the two call sites (run_doa_from_source_file, run_doa_from_multichannel_upload) referenced it for anything besides this pass-through, and no test, demo, or API code calls _run_doa_and_report directly or references the parameter by name. No behavior change -- pytest test/ passes 111/111 identically before and after.
Documents the Part 2 pre-merge cleanup pass: detected stack, commands verified/run, documentation fixes made, the one dead-code removal made (with evidence), two proposed-but-not-yet-deleted candidates (results/ legacy heart-sound study dump; two orphaned test/unit/ fixture CSVs) with full evidence for each, and every item that was reviewed and intentionally kept because usage was confirmed or safety was uncertain. No files have been deleted in this pass -- deletions are pending explicit approval per the repo's cleanup policy.
Deletions approved by the repository owner after review of the full evidence in docs/repository-hygiene-audit.md: - results/ (601 files, ~11 MB): CSV/PNG dumps and tl;dr.txt/ statistics.txt notes from the original pre-fork heart-sound S1/S2 localization study. Zero references anywhere in code, docs, tests, or CI; already excluded from the Docker build context via .dockerignore. - test/unit/test_no_transform.csv, test/unit/test_transform.csv (~1.1 MB combined): orphaned mic-coordinate dumps predating any current test, never referenced by filename or dynamic discovery in the test suite. Validated: pytest test/ -- 111 passed (identical to pre-deletion).
Summary
Part 1 -- Real-world DOA benchmark: tests whether the reverberation-driven
accuracy degradation seen with the original synthetic heart-proxy signal
also happens with real-world, non-medical audio (LibriSpeech speech,
ESC-50 environmental sound). Verdict: yes, across every dataset tested --
see
reports/part1_results.md.Part 2 -- Interactive demo, now deployment-ready: a single-page
Streamlit app to pick a preset or upload your own multi-channel
recording and see the estimated vs. true source position in 3D.
The deployment architecture changed mid-PR at the user's explicit
request: no Google Cloud services of any kind (no Cloud Run, GCS,
Cloud SQL, Firebase); the demo is now one Dockerized Streamlit
service on Render, not a two-process FastAPI+Streamlit container.
Architecture
demo/app.py(Streamlit) callssrc/service.py->src/pipeline.pydirectly, in-process. No HTTP hop, no second server.
api/app.pystill exists as an optional, non-deployed FastAPIwrapper over the same
src/service.py, kept for localcurl/scripting convenience and fortest/unit/test_api.py. It isnot started in the deployed container.
synchronous (0.3-27s measured) and nothing needs to survive a process
restart. See
README.md's "Architecture" section for the fullreasoning.
.matscratch files)derive from a single
APP_DATA_DIRenv var (src/paths.py) --./app_datalocally,/var/datain Render. No persistent disk isattached: everything under it is per-request scratch data, consumed
and deleted within the same request.
Deployment files
Dockerfile/docker-entrypoint.sh-- single-process image, pinnedpython:3.11-slim, non-root user, respects Render's$PORTviaentrypoint expansion, XSRF protection left enabled.
render.yaml-- Docker-based Web Service,APP_DATA_DIR=/var/data,default TCP health check (reasoning + optional
/_stcore/healthalternative documented in
README.md)..dockerignore,.env.example-- new.vercel.json-- removed (no separate backend or frontend left toroute through Vercel; see
README.md's "Vercel" section).Validation
pytest test/-- 104/104 passing.this was built in, so the literal
docker build/Render deploy stepscould not be executed directly. As a substitute, the exact
docker-entrypoint.shcommand was run directly with a test$PORTand
$APP_DATA_DIR, and a full Playwright browser click-throughexercised both preset mode and upload mode end-to-end against the
running app, confirming correct results rendering and that scratch
files under
APP_DATA_DIRare cleaned up after each run. Full,unvarnished details (including exactly what was and wasn't verified)
are in
reports/deployment_status.md.Required Render environment variables
APP_DATA_DIR/var/dataPORTNo persistent disk required. No separate Vercel frontend exists or was
introduced.