Diagnose car faults from audio recordings with calibrated ML
cardiag is an audio-ML pipeline that triages car faults from a sound clip and says UNCERTAIN instead of bluffing when the audio cannot support a call.
1.0.0Add to Favorites
Why it matters
Triage automotive mechanical problems by analyzing audio recordings from phone clips or social media, providing calibrated predictions about fault presence, location zones, and likely component failures while explicitly indicating uncertainty when confidence is low.
Outcomes
What it gets done
Scrape and clean automotive fault audio from YouTube and TikTok, isolating mechanical sounds from speech, music, and background noise
Classify whether a car sound indicates a fault versus normal operation with 0.79 AUROC
Identify which zone of the car (6 regions) the fault originates from with 75% top-3 accuracy
Rank likely faulty parts (12+ component families) with 45-65% top-3 accuracy and calibrated uncertainty
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Overview
cardiag is an end-to-end audio-ML pipeline. It scrapes fault-sound clips from YouTube and TikTok, isolates the mechanical sound from speech, music and noise, embeds it with a frozen CLAP model and trains small linear heads to triage the fault. It is exposed as a CLI and a live web app. Use it to narrow down where to look for a car fault from a deliberately recorded phone clip or social-style audio. It is not a safety-critical or standalone diagnostic, and its cleaning and honest-training recipe is reusable on other audio datasets.
What it does
cardiag is an end-to-end audio-ML pipeline that scrapes fault-sound clips from YouTube and TikTok, cleans the audio by isolating the mechanical sound from speech, music and noise, embeds it with a frozen CLAP model and trains small linear heads to triage the fault. It is exposed as a CLI and a live web app.
It is a proof of concept built as a calibrated triage aid rather than a diagnoser, because diagnosing a car fault from a phone recording is genuinely hard. It tells you whether something sounds wrong, roughly where in the car the problem is, and a ranked shortlist of likely parts. When the audio will not support a call, it returns UNCERTAIN instead of bluffing.
The pipeline runs audio, then a clean() cascade that isolates short mechanical spans, then a frozen 512-dimensional CLAP embedding, then linear heads for fault, region, part and knock, and finally a calibrated, UNCERTAIN-aware diagnosis. There is one segmentation path: scraped clips, your own recordings ingested at any length, and uploads at inference all flow through the same clean() cascade. Spans over about 10 seconds are split into windows so CLAP never silently truncates them. Training and serving share one embedding contract, so there is no train/serve skew.
Measured out-of-sample with leakage-safe by-video grouped cross-validation over 1,031 video groups (permutation p = 0.0005):
- Is something wrong (fault or normal): AUROC 0.79 with interval [0.76, 0.83], against 0.50 for chance.
- Where in the car (6 zones): the right zone in the top 3 about 75% of the time, twice chance.
- Which part (12+ families): the right part in the top 3 about 45-65%, three to four times chance.
- Knowing when it does not know: calibrated with ECE about 0.04. The knock head was demoted for failing out-of-sample. The modest accuracy reflects how hard the problem is from crude phone audio, and the same method reaches 0.93 AUROC on clean engine audio.
Use it for social-style or targeted-upload audio: YouTube, TikTok, or a phone clip a user records deliberately. It is a triage assistant that narrows where to look and is honest about its uncertainty.
It is not a safety-critical or standalone diagnostic. Model files are joblib artifacts, so load only ones you trust. Reddit as a scrape source is deprecated because it is too noisy.
Its cleaning and honest-training recipe is reusable on other audio datasets.
Inputs and outputs
The input is an audio clip (any length for cardiag ingest), or a pasted link in the web app. The output is a verdict, a where-in-the-car zone and a ranked list of parts. Add --json to any inference command for machine-readable output. The web app takes a dropped clip or a pasted link, offers an "explain why" view backed by occlusion saliency, and includes an SSRF guard that rejects internal URLs.
Commands:
cardiag diagnose clip.wav: full model with verdict, region and ranked parts.cardiag triage clip.wav: calibrated engine versus running gear.cardiag clean clip.wav: isolate the mechanical sound, no model needed.cardiag inspect clip.wav -o r.html: see and hear the pipeline through spans, spectrograms and scores.cardiag ingest ./my_audio --kind fault --cause wheel_bearing: bring your own audio.cardiag scrape youtube|tiktokbuilds a corpus, andcardiag traintrains on it.
Integrations
A fresh clone is immediately usable. A small pre-trained model ships in models/ and a synthetic demo clip is bundled, so nothing needs to be downloaded or scraped. Install with uv venv, source .venv/bin/activate and uv pip install -e ".[scrape,web,dev,viz]" on Python 3.11. Then:
cardiag doctor
cardiag train --fixtures
cardiag diagnose <clip.wav>
cardiag doctor is a preflight that shows what is installed. cardiag train --fixtures makes a working model offline in about 2 seconds, with no scrape and no 2 GB download. cardiag serve --model models starts the live web app. Running bash scripts/clone_verify.sh verifies the whole flow end to end in an isolated git worktree.
It is MIT licensed.
Source README
cardiag
CLAP, step by step · Isolating the Engine Audio · Model Card · Defense
cardiag is an end-to-end audio-ML pipeline. It scrapes fault-sound clips from
YouTube/TikTok, cleans the audio (isolating the mechanical sound from speech, music,
and noise), embeds it with a frozen CLAP model, and trains small linear heads to
triage the fault. It is exposed as a CLI and a live web app.
https://github.com/user-attachments/assets/23dc48e7-5571-4890-9a6c-4552d907dd8a
This is a proof of concept, and honest about what that means. Diagnosing a car fault
from a phone recording is genuinely hard, so cardiag is built as a calibrated
triage aid rather than a diagnoser: it tells you whether something sounds wrong,
roughly where in the car it is, and a ranked shortlist of likely parts. When the
audio won't support a call, it says "uncertain" instead of bluffing.
The real contribution is the cleaning + honest-training recipe, which is reusable on other audio datasets. The modest accuracy here reflects how hard the problem is from crude phone audio (we hit the literature ceiling); the same method reaches 0.93 AUROC on clean engine audio. See docs/DEFENSE.md.
Interactive demos
Two pages visualize the first two stages of the pipeline:
- Isolating the Engine Audio - an interactive look at the
clean()cascade pulling a short mechanical span out of noisy YouTube audio (speech, music, road noise). - CLAP, step by step - how the frozen CLAP model turns those spans into the 512-d embedding the linear heads classify.
What it actually achieves
Measured out-of-sample, leakage-safe (by-video grouped CV over 1,031 video groups; permutation p = 0.0005). These are honest numbers, not a leaderboard.
| Capability | Result | vs. chance |
|---|---|---|
| Is something wrong? (fault/normal) | AUROC 0.79 [0.76, 0.83] | 0.50 |
| Where in the car? (6 zones) | right zone in top-3 ≈ 75% | 2× |
| Which part? (12+ families) | right part in top-3 ≈ 45-65% | 3-4× |
| Knows when it doesn't know | calibrated (ECE ≈ 0.04), returns UNCERTAIN |
- |
Full details, and the one head we demoted for failing out-of-sample (knock), are in docs/MODEL_CARD.md.
Quickstart: clone to inference
A fresh clone is immediately usable. A small pre-trained model ships in models/,
and a synthetic demo clip is bundled, so nothing needs to be downloaded or scraped.
git clone <this-repo> && cd car-diagnosis
uv venv && source .venv/bin/activate
uv pip install -e ".[scrape,web,dev,viz]" # Python 3.11
cardiag doctor # preflight: what's installed
cardiag train --fixtures # a working model offline in ~2s (no scrape, no 2 GB download)
cardiag diagnose <clip.wav> # verdict + where-in-the-car + ranked parts
cardiag serve --model models # live web app: drop a clip / paste a link, "explain why"
Verify the whole thing end-to-end in an isolated worktree: bash scripts/clone_verify.sh.
How it works
audio ──► clean() cascade ──► CLAP embedding ──► linear heads ──► Diagnosis
(isolate spans) (frozen, 512-d) (fault/region/ (calibrated,
part/knock) UNCERTAIN-aware)
There is one segmentation path. Scraped clips, your own recordings (cardiag ingest, any length), and uploads at inference all flow through the same clean()
cascade that isolates short mechanical spans. Spans over ~10 s are split into windows
so CLAP never silently truncates them. Training and serving share one embedding
contract, so there is no train/serve skew.
Usage
cardiag diagnose clip.wav # full model: verdict + region + ranked parts
cardiag triage clip.wav # calibrated engine-vs-running-gear
cardiag clean clip.wav # isolate the mechanical sound (no model needed)
cardiag inspect clip.wav -o r.html # SEE/HEAR the pipeline: spans, spectrograms, scores
cardiag ingest ./my_audio --kind fault --cause wheel_bearing # bring your own audio
cardiag scrape youtube|tiktok # build a corpus (Reddit is deprecated — too noisy)
cardiag train # train on your corpus
Add --json to any inference command for machine-readable output.
Documentation
- docs/DEFENSE.md - the honest case that a deliberately crude method earns a real triage result.
- docs/MODEL_CARD.md - per-head metrics, intended use, limitations.
- docs/architecture.md - pipeline diagrams.
- docs/scraping-guide.md - start-to-finish corpus building.
Scope & honesty
Valid for social-style / targeted-upload audio (YouTube, TikTok, or a phone clip a user records deliberately). It is not a safety-critical or standalone diagnostic. It is a triage assistant that narrows where to look and is honest about its uncertainty. Model files are joblib artifacts: load only ones you trust.
License: see LICENSE.
scripts/clone_verify.sh
#!/usr/bin/env bash
# Clone-and-run proof: a clueless developer clones with NOTHING and gets a working
# diagnosis + live web app, step by step, in a clean git worktree. Every step is
# the literal command a cloner would run. Exits non-zero on the first failure.
#
# bash scripts/clone_verify.sh
#
# Needs: git, uv, ffmpeg, yt-dlp on PATH. The ~2GB CLAP weights are reused from the
# shared HuggingFace cache (no re-download). No scrape/train-from-scratch is run.
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && git rev-parse --show-toplevel)"
WT="${TMPDIR:-/tmp}/cardiag_clone_verify"
PASS=0; FAIL=0
step(){ printf "\n\033[1m▶ %s\033[0m\n" "$1"; }
ok(){ printf " \033[32m✓ %s\033[0m\n" "$1"; PASS=$((PASS+1)); }
bad(){ printf " \033[31m✗ %s\033[0m\n" "$1"; FAIL=$((FAIL+1)); }
run(){ if eval "$1" >/tmp/cv.out 2>&1; then ok "$2"; else bad "$2 — see below"; tail -8 /tmp/cv.out|sed 's/^/ /'; fi; }
step "0. fresh worktree at HEAD (a clone with nothing)"
cd "$ROOT"
git worktree remove --force "$WT" 2>/dev/null || true
git worktree add --detach "$WT" HEAD >/tmp/cv.out 2>&1 && ok "worktree at $WT" || { bad "worktree add"; exit 1; }
cd "$WT"
# a fresh clone has no scraped data and no user-trained model
rm -rf data/training 2>/dev/null || true
step "1. install (uv venv + editable install with extras)"
uv venv --python 3.11 .venv >/tmp/cv.out 2>&1 && ok "venv" || bad "venv"
source .venv/bin/activate
run "uv pip install -e '.[scrape,web,dev,viz]'" "pip install -e .[scrape,web,dev,viz]"
step "2. quality gates (what a contributor runs)"
run "ruff check src/cardiag tests scripts --output-format=concise" "ruff"
run "mypy" "mypy"
run "python -m pytest -p no:cacheprovider -q" "pytest (offline suite)"
step "3. wheel builds with the bundled model-less data + demo clip"
run "uv build --wheel" "wheel build"
run "python - <<'PY'
import zipfile,glob
z=zipfile.ZipFile(sorted(glob.glob('dist/*.whl'))[-1])
n=z.namelist()
assert any(p.endswith('_fixtures/demo.wav') for p in n), 'demo.wav missing from wheel'
assert any(p.endswith('web/static/index.html') for p in n), 'web UI missing from wheel'
print('wheel contains demo.wav + web UI')
PY" "wheel packages demo clip + web UI"
step "4. onboarding: cardiag doctor + start (offline)"
run "cardiag doctor" "cardiag doctor"
run "cardiag train --fixtures" "cardiag train --fixtures (offline model in ~2s)"
step "5. diagnose the bundled demo clip (NO scrape, NO model needed beyond fixtures/shipped)"
run "cardiag diagnose \"\$(python -c 'from cardiag import paths;print(paths.DEMO_CLIP)')\"" "cardiag diagnose <demo clip>"
run "cardiag clean \"\$(python -c 'from cardiag import paths;print(paths.DEMO_CLIP)')\" --no-music-gate" "cardiag clean <demo clip>"
step "6. the shipped pre-trained model works via the fallback"
run "rm -rf data/training; python -c 'from cardiag import Classifier; Classifier.load(); print(\"shipped model loads\")'" "shipped model auto-fallback (no data/training)"
step "7. live web app: serve + every endpoint"
PORT=8791
cardiag serve --model "$ROOT/models" --port $PORT >/tmp/cv_srv.log 2>&1 &
SRV=$!
for i in $(seq 1 40); do curl -sf http://127.0.0.1:$PORT/health >/dev/null && break; sleep 0.5; done
run "curl -sf http://127.0.0.1:$PORT/health | grep -q '\"model_loaded\":true'" "GET /health (model loaded)"
run "curl -sf http://127.0.0.1:$PORT/favicon.svg | grep -q svg" "GET /favicon.svg"
DEMO="$(python -c 'from cardiag import paths;print(paths.DEMO_CLIP)')"
run "curl -sf -X POST http://127.0.0.1:$PORT/api/diagnose/stream -F file=@$DEMO | grep -q 'event: diagnosis'" "POST /api/diagnose/stream (upload -> diagnosis)"
run "test \$(curl -s -o /dev/null -w '%{http_code}' -X POST http://127.0.0.1:$PORT/api/diagnose/stream --data 'url=http://169.254.169.254/') = 200 && curl -s -X POST http://127.0.0.1:$PORT/api/diagnose/stream --data 'url=http://169.254.169.254/' | grep -q 'only YouTube'" "SSRF guard rejects internal URL"
run "curl -sf -X POST http://127.0.0.1:$PORT/api/explain -F file=@$DEMO | grep -q '\"available\"'" "POST /api/explain (occlusion saliency)"
run "test \$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:$PORT/api/audio/zzzz) = 400" "GET /api/audio bad-id rejected"
kill $SRV 2>/dev/null
step "summary"
printf "\n \033[32m%d passed\033[0m, \033[31m%d failed\033[0m\n" "$PASS" "$FAIL"
cd "$ROOT"; git worktree remove --force "$WT" >/dev/null 2>&1 || true
[ "$FAIL" -eq 0 ] && { printf "\n\033[1;32mCLONE-AND-RUN VERIFIED — a stupid developer can do this.\033[0m\n"; exit 0; } || exit 1
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.