VC.
ProjectsAboutContact

Varun Cumbamangalam.

Senior IoT and Edge AI Engineer, AI Product Manager at OraLens Healthcare

ProjectsAboutContactPrivacy

© 2026 Varun Cumbamangalam. Built with Next.js and Tailwind CSS.

# ONNX inference server - generic classification endpoint import onnxruntime as ort import numpy as np from PIL import Image def preprocess(image_path: str, size: tuple = (224, 224)) -> np.ndarray: img = Image.open(image_path).convert("RGB").resize(size) arr = np.array(img, dtype=np.float32) / 255.0 arr = (arr - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225] return arr.transpose(2, 0, 1)[np.newaxis, :] # NCHW def classify(image_path: str, session: ort.InferenceSession, labels: list) -> dict: tensor = preprocess(image_path) logits = session.run(None, {session.get_inputs()[0].name: tensor})[0][0] probs = np.exp(logits) / np.exp(logits).sum() top_i = int(np.argmax(probs)) return {"label": labels[top_i], "confidence": float(probs[top_i])}
All Projects
OraScan - Oral Disease Detection logo
AI/MLHardwareBackend

OraScan - Oral Disease Detection

94.7% accuracy across 11 oral disease categories, trained on 78k+ images.

Explore interactive demoView on GitHub
0%

Test Accuracy

0

Training Images

0

Disease Classes

Tech Stack

PythonPyTorchONNXGoFastAPIOpenCVEfficientNet-B0

The problem

Over 3.5 billion people worldwide have some form of oral disease, but most never get diagnosed because seeing a specialist is too expensive or too far away. A small camera at a dental kiosk should be enough to flag problems early - if the AI behind it is reliable enough to trust with real patients.

The challenge

Oral disease affects more than 3.5 billion people globally, yet it often goes undiagnosed in low-resource settings. The model had to be accurate enough for clinical use, small enough to run on kiosk hardware without a GPU, and consistent across different lighting conditions, camera angles, and skin tones. It also had to handle class imbalance across 11 categories, including common conditions such as caries and calculus and rare ones such as mucocele and hypodontia, without losing recall on oral cancer.

Architecture & System Design

OraScan - Oral Disease Detection system architecture

Desktop scanning application captures oral images and sends them to inference engine for disease classification. Inference system processes images locally without cloud connectivity. Analysis results and images stored in cloud database and file storage system for historical tracking and specialist review.

Full system schematic available upon request

A custom EfficientNet-B0 backbone (4.67M parameters) was fine-tuned on a combined DENTEX + SMART-OM dataset of 78,058 labelled oral images. Training used PyTorch with AMD GPU acceleration (DirectML on Windows). The data pipeline applies aggressive augmentation: random crops, HSV jitter, horizontal flip, and cutmix. Post-training, the model is exported to ONNX and quantised to INT8 for edge deployment. A FastAPI inference server wraps the ONNX runtime and exposes a REST endpoint consumed by the kiosk scanning application. The Go backend stores session results in PostgreSQL and uploads image artefacts to AWS S3.

Code Walkthrough

3-step walk-through of the production implementation. File paths and intent appear above each block.

  1. 01

    Step 1 of 3

    Mouth-open detection as a capture trigger

    OraScan-Facemesh/mouth.py

    The scanner shouldn't shoot at random - it should fire the moment the patient's mouth is open enough for a clean intraoral view. MediaPipe's face mesh gives us the inner upper/lower lip landmarks as normalised (y) coordinates, and the per-frame pixel delta is a clean-enough signal to gate acquisition without any ML at all.

    python
    UPPER_LIP_IDX = 13   # MediaPipe face mesh: inner upper lip
    LOWER_LIP_IDX = 14   # MediaPipe face mesh: inner lower lip
    OPEN_THRESHOLD_PX = int(os.getenv("MOUTH_OPEN_THRESHOLD_PX", "45"))
    
    def mouth_open_trigger(on_open: Callable[[np.ndarray], None]) -> None:
        cap  = cv2.VideoCapture(0)
        mesh = mp.solutions.face_mesh.FaceMesh(
            min_detection_confidence=0.5,
            min_tracking_confidence=0.5,
        )
        try:
            while cap.isOpened():
                ok, frame = cap.read()
                if not ok:
                    continue
                result = mesh.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
                if not result.multi_face_landmarks:
                    continue
    
                lm     = result.multi_face_landmarks[0].landmark
                height = frame.shape[0]
                gap_px = int((lm[LOWER_LIP_IDX].y - lm[UPPER_LIP_IDX].y) * height)
    
                if gap_px > OPEN_THRESHOLD_PX:
                    on_open(frame)           # hand the frame to the acquisition pipeline
        finally:
            cap.release()
            mesh.close()
    Takeaway

    Two landmarks and a threshold replace an entire 'press to capture' UX. The CV runs in a tight loop; the rest of the pipeline stays frame-agnostic.

  2. 02

    Step 2 of 3

    Bounding hardware calls with a timeout decorator

    OraScan_Automated_Scanning/motor_driver.py

    GPIO and servo drivers hang. Not crash - hang, indefinitely, when the bus misbehaves or a pin is stuck. An ordinary Python call into a blocked driver freezes the scanner UI and the operator has to hard-reboot. A one-line decorator submits every hardware call into a thread, waits up to N seconds, and returns None on timeout, so the UI always stays responsive.

    python
    def hardware_timeout(seconds: int = 5):
        """Wrap a hardware call so a hung device can never block the UI."""
        def decorator(fn):
            @functools.wraps(fn)
            def wrapper(*args, **kwargs):
                with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
                    future = executor.submit(fn, *args, **kwargs)
                    try:
                        return future.result(timeout=seconds)
                    except concurrent.futures.TimeoutError:
                        logging.warning(
                            "hardware: %s timed out after %ds", fn.__name__, seconds
                        )
                        return None
            return wrapper
        return decorator
    
    
    @hardware_timeout(seconds=3)
    def move_servo(axis: str, angle: int) -> None:
        """Move a servo to an absolute angle; blocks until the pulse is sent."""
        servo = _servos[axis]
        servo.move_to_angle(max(0, min(180, angle)))
    Takeaway

    Hardware is the one place you should never trust a library's own timeout handling - wrap every motor/sensor call in a bounded executor, treat None as 'try again', and the UI is freed from the physical layer.

  3. 03

    Step 3 of 3

    Desktop ↔ cloud patient reconciliation

    OraScan_backend/sync_handler.go

    The desktop scanner stores every patient locally in MySQL so scans work offline. When the operator reconnects, each record is POSTed to this handler for reconciliation against the cloud store. The important design decision: we refuse to auto-create patients from sync data - sync is reconciliation, not a registration shortcut. Bypassing the signup flow would skip consent capture and identity checks.

    go
    type SyncPatientInput struct {
        Email         string `json:"email" binding:"required"`
        FirstName     string `json:"first_name"`
        LastName      string `json:"last_name"`
        Contact       string `json:"contact"`
        Place         string `json:"place"`
        LocalID       int    `json:"local_id"`       // desktop app's row ID
        SyncTimestamp string `json:"sync_timestamp"` // ISO 8601
    }
    
    func (s *ApiServer) SyncPatient(c *gin.Context) {
        userID, ok := c.Get("userID")
        if !ok {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "missing user context"})
            return
        }
    
        var in SyncPatientInput
        if err := c.ShouldBindJSON(&in); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
            return
        }
    
        existing, err := s.store.GetUserByEmail(in.Email)
        if err == nil && existing != nil {
            s.audit.SyncMerge(userID, in.Email, in.LocalID, existing.ID)
            c.JSON(http.StatusOK, gin.H{
                "status":   "merged",
                "cloud_id": existing.ID,
                "at":       time.Now().UTC().Format(time.RFC3339),
            })
            return
        }
    
        // Strict mode: never auto-create on sync. If the patient isn't
        // already in the cloud, the desktop operator must route them through
        // the normal registration flow first.
        c.JSON(http.StatusNotFound, gin.H{
            "error": "patient not found in cloud; please register first",
        })
    }
    Takeaway

    Offline-first apps have their own IDs; the cloud has its own. Sync handlers merge on a stable external key, audit every reconciliation, and reject rather than invent records that didn't come through the proper signup flow.

Results

The final model achieves 94.7% test accuracy and 95.2% best validation accuracy across all 11 disease classes. ONNX INT8 quantisation reduces model size by ~70% while keeping accuracy drop under 1%. Inference latency on the kiosk CPU is under 200ms per frame. The model is integrated into both the automated scanning application (MediaPipe FaceMesh-triggered) and the manual scanning desktop interface.

Explore the product system

This case study covers the main product surfaces. Use the buttons to move between the dashboard, app, website, and brand work.

Open Screening dashboards
Screening dashboards

Interactive clinician and operations views for the oral-screening workflow.

Gallery & Demos

AI Analysis Dashboard

AI Analysis Dashboard

Real-time disease classification results showing confidence scores across 11 oral disease categories (caries, gingivitis, cancer, etc.).

Desktop Sync Workflow

Desktop Sync Workflow

Offline-first architecture: local MySQL stores patient scans, cloud sync reconciles on reconnect matching by email, enforcing signup flow for consent.

Kiosk Hardware Layout

Kiosk Hardware Layout

MediaPipe FaceMesh detects mouth-open trigger, motorized camera captures intraoral image, ONNX INT8 inference runs on CPU in <200ms.

Automated Scan - Full Kiosk Workflow
Click to expand

Automated Scan - Full Kiosk Workflow

End-to-end automated scan: MediaPipe FaceMesh detects mouth-open trigger, motorized camera captures intraoral image sequence, ONNX INT8 model runs on-device inference across 11 disease classes in under 200ms, and results are displayed on-screen.

Automated Scan - Patient-Facing UX
Click to expand

Automated Scan - Patient-Facing UX

Patient perspective during an unassisted kiosk scan. Guided mouth positioning prompts, real-time capture feedback, and disease classification results displayed without clinician involvement - designed for dental clinic waiting areas.

Manual Telemedicine Consultation
Click to expand

Manual Telemedicine Consultation

Dentist-assisted remote consultation: clinician captures targeted intraoral images via the desktop app, uploads to the cloud sync layer, and conducts a live video consultation with a remote specialist through the web portal.

Manual Scan - Clinician-Assisted Mode
Click to expand

Manual Scan - Clinician-Assisted Mode

Clinician-operated scanning mode bypassing the automated FaceMesh trigger. Used for targeted imaging of specific quadrants or lesions requiring precision capture beyond what the automated motor sweep provides.

Click any image or video to expand · ← → keys navigate

OraLens Healthcare Pvt. Ltd.

More from OraLens Healthcare Pvt. Ltd.

OraScan - AI Oral Screening Platform

Product Owner

Product definition and ownership for OraScan, an oral disease classifier designed for dental kiosks. Set per-class accuracy targets, defined the dataset strategy for 78K+ images, and specified the kiosk hardware integration.

PRD AuthoringDataset StrategyModel Evaluation Criteria

OraScan H - Halitosis IoT Device

End-to-end ownership

Full-stack IoT device for halitosis (bad breath) detection: a Raspberry Pi Zero 2W with H2S gas sensors communicates via BLE to a Flutter mobile app, with on-device TFLite AI classification and PHP backend.

PythonFlutterRaspberry Pi

OraScan H - Connected Halitosis Device

Product Owner

Product ownership of OraScan H, a consumer IoT halitosis detector built with a Raspberry Pi Zero 2W, H2S gas sensors, and a Flutter mobile app. Defined the BLE pairing flow and sprint milestones through 92% software completion.

PRD AuthoringHardware Requirements SpecBLE UX Definition

ArogyaLens - Unified Health Platform

End-to-end ownership

A multilingual healthcare platform connecting patient records, doctor workflows, hospital operations, appointments, pharmacy, labs, telemedicine, and oral-health screening.

Node.jsNext.jsFlutter

ArogyaLens - Unified Health Platform

Product Owner

Product ownership for a health platform spanning 15+ modules, 13 languages, telemedicine, and oral-health screening. Authored the PRD, SRS, and technology specification.

PRD AuthoringSRS DocumentationClinical Workflow Mapping

LuxySmile Oral Care - Product Website & Dashboard

End-to-end ownership

Full product suite for LuxySmile Oral Care (Oralens Healthcare): a React + Vite marketing site targeting B2B dental wellness programs for schools and corporates, plus an internal case management dashboard with direct-to-S3 uploads and CSV bulk import.

React 18ViteTypeScript

LuxySmile - Oral Care Brand & Operations

Product Owner

Product and go-to-market ownership for LuxySmile Oral Care - OraLens Healthcare's B2B wellness brand. Defined the dual B2B/B2C positioning strategy, school and corporate wellness program inquiry flow, and the AI kiosk upsell path on a premium product website.

PRD AuthoringB2B GTM StrategyBrand Positioning

Interested in this work?

I can walk through the architecture and code during an interview.

Get in Touch All Projects