A client ran three medical laboratories doing semen analysis — DNA, vitality, motility, morphology — entirely by hand, on local machines, with no shared platform, no central visibility, and no automation.
Each lab worked in isolation. The brief sounded simple: one centralized server all three sites could connect to, AI-based analysis, GPU acceleration, and a web interface so technicians could work from anywhere.
Then came the real constraint: everything had to work in real time. That is where the project stopped being a simple web app and became an infrastructure and performance problem.
Inside the build
The story from where it started to where it landed, then the system one layer at a time — the architecture, the real-time control loop, and the performance work.
The transformation at a glance
One platform, three labs, in real time
Each lab ran its own analysis manually, on local machines, with no shared visibility. The platform pulls all of that behind one Flask + Flask-SocketIO server: technicians at any site open a browser, drive the microscope live, and run AI analysis on a shared GPU server.
Nginx fronts the app as a reverse proxy; the whole thing runs on the client's own hardware. Every arrow on this diagram is a place latency could creep in — which is exactly where the interesting work turned out to be.
Getting the microscope on the web
The first major challenge was the microscope itself. The client wanted the live feed in the browser, with full remote control — lens movement, frame rate, speed, contrast, saturation, gamma, hue, alpha. Everything.
The Toupcam SDK supported streaming through Python, which fit the Flask backend. I used Flask-SocketIO to push frames and control events in real time: move a slider on the dashboard, and the backend updates the microscope instantly.
That part worked surprisingly well — until the client said, "There's a lag when I move the lens." I assumed dropped frames, network delay, the browser struggling. It wasn't any of those. The lag was coming straight from the SDK.
Read the Toupcam SDK source line by line and call its undocumented functions directly
Building a polling / debounce workaround around the laggy public API
Why: The lag was in the SDK, not the network — so working around it would only hide the symptom. Buried in the source were control functions that existed but appeared in no documentation. Wiring those into the control flow removed the lag at its source. When a problem looks impossible, it usually means the answer isn't obvious yet — not that it isn't there.
The motility bottleneck
Most tests were image-based and easy to optimize. Motility was different: it analyzes short video clips of live movement, so you can't just shrink the video or drop quality without hurting results. Processing started at ~17 seconds per sample.
Moving the model to CUDA on the client's new GPU brought that to ~10 seconds — a ~40% win, but not enough. Two more optimizations closed the gap. Frame skipping: out of ~90 frames, 5–10 were near-duplicates, so skipping them cut computation with no accuracy cost. Multithreading: splitting the video into chunks processed in parallel, then aggregating partial scores. Thread count mattered — too many and overhead killed the gains; five turned out to be the sweet spot.
The result: ~17s down to ~5s, and as low as 3.95s on some runs. Roughly a 70% reduction.
AI Models and Real-World Lab Needs
Each test had its own AI model behind it. Different inputs. Different processing logic. Different outputs. But once lab technicians started using the system, new requirements came up. Real, practical ones. For example, debris in samples. Sometimes non-sperm particles showed up and confused the analysis. The client wanted technicians to manually mark debris and exclude it from results. I told them it was doable. On the frontend, I used simple JavaScript and CSS. When a user marked debris, they were really placing an overlay div on top of the image. Behind the scenes, I captured the pixel X and Y coordinates. Those coordinates were sent to the backend with the sample. During processing, the AI pipeline ignored those pixel regions entirely.
Simple idea. Very effective.
That same attention to detail showed up everywhere. Image manipulation. Masking. Preprocessing. Edge cases. Every test had its own quirks, and each one needed careful handling to feel reliable in a lab setting.
Deployment and Real-World Infrastructure
Once the app was ready, I helped deploy it on the client's on-premise server. That included networking setup, NAT configuration with their ISP, and exposing the server securely through a reverse proxy. Everything was stable, and the labs were using it daily. When the client later upgraded that box with a GPU and asked for faster analysis, this on-prem setup is what the motility optimization ran on top of.
Tech Choices and Trade-offs
The backend was built with Flask and Flask-SocketIO. Python made sense given the heavy image manipulation, AI models, and SDK integration. The frontend was a more interesting call.
EJS server-rendered templates for the frontend
A React refactor with a modern UI (which I offered)
Why: It's a private internal app used only by lab staff, where modern design wasn't the priority. The client was comfortable with PHP-style templates and wanted a single index-style structure; I pushed EJS as the middle ground — more maintainable than what they had, still familiar to them. My job was to guide the decision, then respect it.
What I'd watch in production
The system ran daily without drama, but a real-time platform on a single on-prem box has failure modes worth naming. If I owned this in production long-term, these are the first signals I'd instrument — not because they broke, but because they're where this class of system tends to.
- WebSocket health — reconnect rate and dropped-connection count per lab. A site quietly losing its live feed is the worst failure here, because it looks like "the microscope is slow," not "the socket died."
- GPU queue depth & per-sample p95 — with a shared GPU across three labs, contention shows up as tail latency long before it shows up as errors. p95 processing time per test type is the number I'd alert on.
- Frame-drop / skip rate — frame-skipping is a correctness/latency trade. I'd track how many frames get skipped per sample so an accuracy regression can't hide inside a speed win.
- On-prem host basics — disk, memory, and the reverse-proxy error rate, since there's no cloud autoscaling to absorb a bad day.
Looking Back
This project combined deep research, undocumented SDK behavior, real-time systems, GPU optimization, and production deployment work. It reinforced something I strongly believe: if a problem looks impossible, it usually means the answer is not obvious yet.
You dig, read code, test carefully, and keep going. That is where the real engineering work happens.
Key Results
- Centralized semen analysis for three physical labs
- Real-time microscope streaming and control via web
- Multiple AI-powered tests running on a GPU server
- Motility processing reduced from 17s → ~5s (as low as 3.95s)
- Reliable, production-ready system used daily by lab staff