Streaming Violence Anticipation System
My senior capstone from the same school year: warning before violence starts in video, with a benchmark I rebuilt in October 2026.
View project: Streaming Violence Anticipation SystemDetroit Pulse listens to public scanner radio for metro Detroit and turns it into a live map of incidents. I built it on my own for my Deep Learning class at Lawrence Tech in spring 2026. Whisper transcribes the audio, a local language model reads each transmission for its address and what kind of call it is, and a correlation engine decides whether it starts a new incident or belongs to one already on the map. For the address step I trained a LoRA adapter and ran the fine-tuned model.
A drawing of the pipeline, not a screenshot of the app.
Scanner radio is not natural language. It is full of unit numbers, signal codes, floor numbers and round counts, and one channel carries several calls at once. Detroit Pulse listens to public scanner feeds, transcribes them, pulls out the address, the kind of call, its priority and the units sent, and groups transmissions about the same event into one incident. New incidents and updates reach the map in the browser as they happen.
The speech and language models ran locally on one RTX 4070 Ti Super: Whisper through faster-whisper, and Qwen through Ollama. It is non-commercial and not affiliated with any agency.
The hardest part was the grouping: working out that two transmissions a few seconds apart are about the same structure fire.
Pipeline
Correlation
The same event comes up again and again as units respond. The correlation engine scores each new transmission against every open incident, and the score decides whether the code or the model settles it.
Architecture
One Python process runs the pipeline: a thread for each feed, one Whisper thread, four workers for the language model steps (the number is a setting) and a thread that retires stale incidents from matching. Redis queues sit between the stages. A thread that crashes restarts itself after a pause, and every minute the process logs which threads are alive.
Loading models from several threads at once caused segfaults, because PyTorch’s CUDA initialization is not thread-safe. The fix was to load Whisper and Silero VAD on the main thread before any worker starts, keep VAD on the CPU behind a lock, and start the feed workers 2 seconds apart.
FastAPI serves the REST API, the WebSocket and the built React app. PostgreSQL with PostGIS and Redis run in Docker Compose, Ollama serves the language model locally, and a start script waits for the databases, builds the frontend and starts the rest.
Frontend
The map is React 19 and Mapbox GL JS, built with Vite. Incidents appear as dots colored by type, and the incident list sorts them by latest update, newest, priority or number of updates, and filters them by type.
Opening an incident shows a short plain-English summary that the model rewrites every few updates, the units on the call with cleared ones marked, and the transcripts behind it. Inside Detroit it also shows the police precinct and fire battalion the incident falls in, and for fires and hazmat calls, the three nearest Detroit Fire firehouses and how far away each one is. The pipeline works those out when an incident opens or an update moves it, with a point-in-polygon test against the precinct and battalion boundaries and a distance to every firehouse, from the same GeoJSON the map layers use.
Optional layers add Detroit police precincts, Detroit Fire battalions and firehouses from GeoJSON bundled with the app, county lines, the last 48 hours of the city’s crime reports from its open data portal, and DTE Energy outage areas. The browser keeps one WebSocket open, pings it every 25 seconds, and reconnects 3 seconds after it drops.
Fine-tuning
Address extraction is where I spent the most time. A general model reads unit numbers, floors and round counts as house numbers and mile roads, and will sometimes give an address when there is none. I built a QLoRA pipeline to fine-tune Qwen2.5-7B-Instruct for this one step, with checks between the stages before a model goes live. I trained four versions of the LoRA adapter with it, mostly on the RTX 4070 Ti Super and, when a run was taking a long time, on a server with two A100s. Each version went through shadow mode before I promoted it, and I ran the fine-tuned model in Detroit Pulse. The training examples use the exact prompt the pipeline sends, in Qwen’s chat format.
My senior capstone from the same school year: warning before violence starts in video, with a benchmark I rebuilt in October 2026.
View project: Streaming Violence Anticipation SystemLanguage models at work in production at Perfect Afternoon, with a person approving anything that would change a live site.
View project: PANoodle: AI Operations PlatformI am happy to go deeper on any part of the pipeline. nathanpaulgamache@gmail.com
Ask me about it