Skip to content
Huy Vo
Side projects

Graduation thesis · AI skincare tracking · mobile + ML backend

GlowTrack

An app that scans your face for acne with a YOLOv8 detector, explains the result, and turns a skincare routine into a streak you keep with a partner.

GlowTrack, the real app on a phone

GlowTrack app screen: Routines that stick

Real screens

The Expo app built for the web with sample data, in English with a Vietnamese switch; the scan runs on my own selfie.

Role
Lead developer
Year
2025
Platforms
iOS · Android · API
Stack
  • Expo
  • Tamagui
  • React Query
  • FastAPI
  • Celery
  • YOLOv8

Skincare progress is slow and easy to misread, and routines are easy to drop. GlowTrack detects and counts the kinds of acne in a photo, explains them in plain Vietnamese, and keeps you on a routine with a partner.

My part. The Expo app and the FastAPI backend with its Celery workers and detection pipeline; my graduation thesis, defended in September 2025.

At a glance

  • YOLOv8

    acne detection on queued Celery workers

    Source: backend repo

  • 2

    people per routine in partner mode

    Source: app

  • Daily

    reminders, streaks and session resets on a schedule

    Source: backend

What I built

Scan and explain

The photo goes through FastAPI to a Redis queue and Celery workers running a YOLOv8 detector; the app draws the detections and a short explanation, and a chat assistant answers follow-up questions.

GlowTrack app screen: Scan and explain

Routines that stick

Sessions and steps per day, scheduled reminders, streak records and daily resets run by Celery, plus a partner mode where two people keep the same routine and nudge each other.

GlowTrack app screen: Routines that stick

History you can read

A calendar of past scans and check-ins, grouped by month, so progress is visible over weeks rather than guessed from a mirror.

GlowTrack app screen: History you can read

Architecture

API LAYERMESSAGE BROKERWORKER LAYERSTORAGE & RESULTCLIENTMONITORINGFastAPIport 8080Async jobsto RedisSync tasksin requestRedisRedis CloudTask queueCelery brokerYOLO inferenceacne detectionTask scheduleremindersCelery workersCloudinaryimage storageResponseto the clientMobile appExpo · TamaguiPrometheusmetricsHealth checksendpointsFlowerport 5001ASYNCSYNCGPUCPU · RAMRESULT
The app sends a photo to FastAPI, which enqueues it to Redis. Celery workers run YOLO inference on the GPU and scheduled routines on the CPU, store images in Cloudinary and return the result. Flower, Prometheus and health checks watch the workers and the API.
  • Components:
    • API layer: FastAPI (port 8080), built by me
    • API layer: Async jobs (to Redis), built by me
    • API layer: Sync tasks (in request), built by me
    • Message broker: Redis (Redis Cloud)
    • Message broker: Task queue (Celery broker)
    • Worker layer: YOLO inference (acne detection), built by me
    • Worker layer: Task schedule (reminders), built by me
    • Worker layer: Celery workers, built by me
    • Storage & result: Cloudinary (image storage)
    • Storage & result: Response (to the client), built by me
    • Client: Mobile app (Expo · Tamagui), built by me
    • Monitoring: Prometheus (metrics)
    • Monitoring: Health checks (endpoints), built by me
    • Monitoring: Flower (port 5001)
  • Connections:
    • Mobile app to FastAPI
    • FastAPI to Async jobs: async
    • FastAPI to Sync tasks: sync
    • Async jobs to Redis
    • Redis to Task queue
    • Task queue to Celery workers
    • Celery workers to YOLO inference: GPU
    • Celery workers to Task schedule: CPU · RAM
    • YOLO inference to Cloudinary
    • Cloudinary to Response
    • Sync tasks to Response
    • Response to Mobile app: result
    • Sync tasks to Prometheus
    • Celery workers to Flower
    • Celery workers to Health checks

Key decisions

  1. Detector first, language model second

    YOLOv8 counts the lesions first; Gemini only explains those counts, under fixed safety rules and a required JSON shape.

    Problem, choice and trade-off: Detector first, language model second
    Problem
    An app that gives skincare advice from a face photo can easily slide into diagnosis.
    Choice
    A YOLOv8 model on the FastAPI server finds and counts lesions by type and draws them on the photo. The counts and the image then go to Gemini at temperature 0.1, with a prompt kept in a markdown file that sets safety rules (no diagnosis, no prescriptions, refer to a dermatologist) and a required JSON shape. If the reply does not parse, the API returns a safe default.
    Why
    The advice starts from lesions the detector actually counted, and the fixed JSON shape lets the app show overview, severity, care steps and warnings as separate sections.
    Trade-off
    The severity note comes from fixed count bands (under 5, 15 and 30), not a clinical scale, and the advice text itself cannot be verified, so every answer carries a medical disclaimer.
  2. Scheduler enqueues, Celery works

    APScheduler only enqueues; Celery workers send the reminders and reset streaks at midnight, outside the API's request path.

    Problem, choice and trade-off: Scheduler enqueues, Celery works
    Problem
    Routines need reminders every minute, missed sessions marked, and streaks and session statuses reset at midnight for every user.
    Choice
    An APScheduler inside the FastAPI process only enqueues Celery tasks on Redis: reminders at second 0 of each minute, missed-session marking at second 10, session reset and streak update at midnight. The workers send the email and push notifications.
    Why
    The loop over every user's routine runs in workers, outside the API's request path.
    Trade-off
    The worker is its own process and has to initialize Beanie itself; when it did not, tasks failed with CollectionWasNotInitialized, which the README now documents.

Credit. Graduation thesis. The screens come from a polish branch of the app (type ramp, contrast, harmonized colours, complete English, layout fixes) run on the web target.

Next side project

Review video pipeline

Automated 9:16 product-review videos