LungLens is a chest X-ray education companion built with Next.js. It helps users understand imaging terms, view attention overlays, and prepare better questions for clinicians.
Important: this project is educational and research-oriented. It is not a medical diagnostic tool.
| Role | GitHub | Live |
|---|---|---|
| Frontend (this repo) | github.com/Charleschtsoi/LungLens (main) |
lung-lens-five.vercel.app |
| Backend (ML API) | github.com/Charleschtsoi/lunglens-backend (main) |
charleschtsoi-lunglens-backend.hf.space |
| HF Space admin | — | huggingface.co/spaces/Charleschtsoi/lunglens-backend |
Production flow: browser → Vercel BFF → Hugging Face backend. See PRODUCTION_DEPLOY.md for env vars and smoke tests.
The backend/ folder in this repo is a lightweight sample (Gemini + questions only). Full chest X-ray inference lives in lunglens-backend.
- Install dependencies:
npm install - Copy env template:
cp .env.example .env.local - Set
BACKEND_API_BASE_URLandBACKEND_API_KEYfor the ML backend. - Run
npm run dev. - Open http://localhost:3000, upload a chest image, and confirm the backend-backed results page renders.
- Guides users through doctor-review + disclaimer-aware upload flow.
- Runs analysis through async job routes (
POST /api/analyze/jobs+ polling) so each Vercel function stays under Hobby time limits while HF runs full inference. - Shows educational results:
- original image,
- AI attention overlay,
- anatomy guide,
- primary finding explanations,
- suggested doctor questions.
- Frontend: Next.js 14 (App Router), TypeScript, Tailwind CSS
- State: Zustand
- UI: Radix primitives + reusable UI components
- Upload: react-dropzone
- Charts: Recharts
- Integration:
- Upload path:
src/lib/api.ts→src/app/api/analyze/jobs(submit + poll) - Backend warm-up:
GET /api/health(BFF proxy to HF/health) - Gemini key probe:
src/app/api/gemini/health-check/route.ts
- Upload path:
- Node.js 20+ recommended
- npm 10+
- Optional (for local backend sample): Python 3.10+
npm installcp .env.example .env.localSet values in .env.local:
NEXT_PUBLIC_API_URL- Used only for silent warm-up ping (
${NEXT_PUBLIC_API_URL}/health).
- Used only for silent warm-up ping (
BACKEND_API_BASE_URL(server-only)- Backend root — must match your uvicorn port (often
http://127.0.0.1:7861, not 8000) - Production BFF forwards to:
${BACKEND_API_BASE_URL}/api/v1/analyze/jobs(submit + poll; primary path)${BACKEND_API_BASE_URL}/api/v1/gemini/health-check(BYOK; proxied asPOST /api/gemini/health-check)${BACKEND_API_BASE_URL}/api/v1/generate-questions${BACKEND_API_BASE_URL}/predict/densenet(if used)
- Local full backend: clone lunglens-backend, run on port
7861, or point at the HF URL above.
- Backend root — must match your uvicorn port (often
BACKEND_API_KEY(server-only)- Sent by Next.js API routes as
X-API-Key.
- Sent by Next.js API routes as
Never store secrets in NEXT_PUBLIC_* vars.
npm run devOpen http://localhost:3000.
npm run buildThis repo includes a lightweight FastAPI sample under backend/ for doctor-question endpoint testing and local wiring checks.
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --host 0.0.0.0 --port 7861Then set:
BACKEND_API_BASE_URL=http://127.0.0.1:7861- restart Next dev server if already running.
API fields: model1, model2, model3, clinical_risk, model4, model4_swint, model5_densenet, copd_screening.
Model stages:
- Model 1 (ResNet-50) — visual X-ray: 3-class (Normal / Pneumonia-Bacteria / Pneumonia-Virus)
- Model 2 (ResNet-152V2, Edward) — visual X-ray in the pipeline card (
model6_vision_h5; legacy H5_MODEL2 naming) - Model 6 (Chronic Lung Risk / COPD) — clinical questionnaire: tabular neural network (
model6orcopd_screening); shown under Clinical Patient Assessment, not in the visual X-ray list - Gate:
early_stoporcontinue - Model 3 (DenseNet-121) — visual X-ray:
COVID-19/Normal/Pneumonia(+ optional Grad-CAM) - Model 4 (Swin-T) — visual X-ray:
model4_swint - Model 5 (DenseNet-121 H5) — visual X-ray:
model5_densenet - clinical_risk: rule-based questionnaire severity (separate from Model 6 COPD score)
- model4: report synthesis
Primary predictions dictionary is now expected to use:
PneumoniaLung OpacityCOVID-19
See docs/BACKEND_MODELS.md for detailed payload contracts.
Use:
- valid
BACKEND_API_BASE_URLandBACKEND_API_KEY
Steps:
- Repeat upload flow
- Open browser devtools -> Network
- Confirm browser calls Next routes (not HF directly):
POST /api/analyze/jobsthenGET /api/analyze/jobs/{job_id}untilcompletePOST /api/generate-questions
- Verify findings section:
- explanations for
Pneumonia,Lung Opacity,COVID-19 - provenance badge aligns with backend source metadata
- text: "These findings are generated directly from the AI models' primary classifications."
- explanations for
- Stop backend.
- Upload an image
- Confirm user-facing error is graceful and app does not crash.
- Switch language in UI
- Verify findings titles/descriptions and provenance messages render in selected locale.
Run:
npx tsc --noEmit
npm run lint
npm run buildOptional:
npm audit --audit-level=highNote: current audit may report high-severity advisories tied to Next.js/eslint-config-next major upgrade path; track separately if not upgrading framework in the same PR.
src/app/results/page.tsx # Results orchestration and provenance summaries
src/components/results/FindingsCard.tsx # Findings UI + provenance notice
src/lib/constants.ts # Finding labels and English explanations
src/lib/i18n.ts # Localized copy
src/lib/provenance-ui.ts # Badge normalization and provenance mapping
src/app/api/analyze/jobs/route.ts # Async job submit (Hobby-safe)
src/app/api/analyze/jobs/[jobId]/route.ts # Job status polling
src/lib/analyze-bff-normalize.ts # Backend response normalization
src/lib/high-attention-findings.ts # Mapping to doctor-question triggers
src/types/index.ts # Shared API/types contract
docs/BACKEND_MODELS.md # Backend payload expectations
Network error contacting backend API from the Next BFF means fetch to BACKEND_API_BASE_URL failed (connection refused, wrong host/port, timeout)—not a bad JSON schema.
| Check | Action |
|---|---|
| Port | BACKEND_API_BASE_URL must match uvicorn (e.g. http://127.0.0.1:7861) |
| API key | BACKEND_API_KEY = backend API_KEY when REQUIRE_API_KEY=true |
| Backend running | uvicorn main:app --host 0.0.0.0 --port 7861 (restart after backend code changes) |
| CORS | Backend ALLOWED_ORIGINS includes http://localhost:3000 |
API slots (current backend): model2 = Edward ResNet vision (input_type: "vision"); model6 = COPD tabular (input_type: "tabular" after questionnaire). BFF maps legacy model6_vision_h5 / tabular model2 when present.
Insights: POST /api/generate-questions returns educational_insights[] (not suggested_questions). UI section: “Health information for your scan”.
- Production frontend: Vercel — repo
Charleschtsoi/LungLens, branchmain, buildnpm run build - Production backend: Hugging Face Space — repo
Charleschtsoi/lunglens-backend; push withgit push hf main:main - Vercel env (required):
BACKEND_API_BASE_URL,BACKEND_API_KEY(must match HFAPI_KEY) - Analyze uses async jobs + polling (works on Vercel Hobby; first run may take 1–2 minutes)
- Optional alt host: Cloudflare Workers (OpenNext) — see below
Cloudflare’s auto-migrate installs @opennextjs/cloudflare@latest, which requires Next.js 15.5+ and breaks this repo on Next 14.2.35. Use the pinned adapter already in package.json:
| Setting | Value |
|---|---|
| Build command | npm run build:cloudflare |
| Deploy (CLI) | npm run deploy:cloudflare |
| Adapter | @opennextjs/cloudflare@1.15.1 (supports Next 14.2.35) |
Worker secrets / vars (Dashboard → Workers → lunglens → Settings → Variables):
BACKEND_API_BASE_URL— your ML API root (HTTPS)BACKEND_API_KEY— server API keyNEXT_PUBLIC_API_URL— optional warm-up health URL
The log line WARN Failed to set up cache for your project is expected when R2 is not enabled; caching uses the default in-memory config in open-next.config.ts until you add an R2 bucket binding.
Local Workers preview: cp .dev.vars.example .dev.vars then npm run preview:cloudflare.
- MIT, see LICENSE
- Copyright (c) 2026 Chung Him TSOI
- Educational/research use only
- Not a substitute for medical diagnosis
- Always consult a qualified healthcare professional