MoleCheck: Privacy-Preserving Skin-Lesion Analysis
A mobile application that classifies a photographed skin lesion as lower- or higher-risk using a convolutional neural network. All inference runs on-device — no server, no upload, and full offline operation — establishing a strong privacy guarantee for sensitive medical imagery.
Try it in your browser →
Inference runs entirely on your device — your photo is never uploaded.
The full application: capture or select a photo of a lesion and receive an on-device estimate, with the same disclaimer acknowledgement flow. No data is uploaded.
Download APK →Installation (sideloading)
- On an Android phone, open this page and tap Download APK.
- When prompted, permit your browser to install from unknown sources (Android requests this once, for safety).
- Open the downloaded file and tap Install.
Educational application — not a medical device and not a diagnosis. It is distributed outside the Play Store and signed with a debug key, so Android displays a warning before installation; this is expected for a sideloaded educational build. Built for arm64 devices (the large majority of Android phones from recent years). Android only at present — an iOS build can be produced from the same codebase.
Summary
| Task | Binary classification of skin lesions (benign vs. malignant) from photographs |
|---|---|
| Dataset | ISIC Archive — 11,720 dermatoscopic images (a superset of HAM10000) |
| Model | YOLO11s-cls, transfer-learned, 224×224 input (5.4M parameters) |
| Result | v1 baseline: ROC-AUC 0.914 on dermoscopy (held-out test set) |
| Shipped now | v2 — retrained on smartphone photos: ROC-AUC 0.920 on phone photos, threshold 0.368 (see the v2 write-up below) |
| Deployment | TensorFlow Lite in the app, ONNX in this browser demo — both on-device |
| Application | Flutter — a single codebase targeting Android and iOS |
Rationale for on-device inference
Photographs of skin are sensitive personal data. The most robust way to protect them is to ensure they never leave the device: the model is bundled inside the application, inference executes on the phone's own hardware, and the app functions in airplane mode. This design also eliminates an entire class of engineering concerns — servers, APIs, uptime, and data retention policy — from the project.
Dataset and labeling
Training used a public ISIC Archive collection of 11,720 dermatoscopic images (a superset of the HAM10000 dataset). The diagnosis field for each image was mapped to a binary label — benign or malignant — with indeterminate cases excluded. The dataset is heavily imbalanced toward benign lesions, which informed both the training procedure and the selection of the decision threshold.
Model and training
The classifier is YOLO11s-cls, the classification variant of Ultralytics' YOLO11, fine-tuned for 40 epochs on an NVIDIA RTX 5060 Ti. A pretrained backbone with a clean mobile-export path was chosen deliberately: a strong, reproducible baseline that deploys reliably is more valuable for this project than a bespoke architecture that is difficult to ship.
Threshold selection: prioritizing sensitivity
The default 0.5 decision threshold is inappropriate for a screening task because the two error types are asymmetric: a false negative (a malignant lesion reported as benign) is far more costly than a false positive (a benign lesion flagged for a dermatologist visit). The final model achieves ROC-AUC 0.914, and the operating threshold was set to 0.137 — the point at which the model detects 90% of malignant lesions while still correctly clearing 75% of benign ones. The application is deliberately tuned to err toward recommending a professional evaluation.
Training progression
The chart tracks validation accuracy across training. A key nuance: at epoch 1 the model already scores roughly 80% — which appears strong until one observes that a trivial "always benign" baseline also scores 80%, because most lesions in the dataset are benign. The meaningful learning is everything above that dashed baseline.
Model export and verification
On-device inference required converting the PyTorch model to TensorFlow Lite. Because the direct export path is unsupported on Windows, the model was routed through ONNX and converted with onnx2tf. Conversion correctness was verified rather than assumed: the exported model was evaluated against the original PyTorch model and reproduced its predictions to within 0.001 probability, with identical decisions. Several preprocessing parameters (input layout and normalization) had to match exactly between model and application, or predictions would silently degrade.
Application
The Flutter application allows the user to capture or select an image, runs the TensorFlow Lite model locally, and presents the output as risk awareness rather than a diagnosis. A first-run onboarding flow requires the user to acknowledge the educational disclaimer, and every result screen reiterates the recommendation to consult a dermatologist.
Limitations
- The v1 model was trained on dermatoscopic images, which differ substantially from ordinary smartphone photographs; real-world accuracy is expected to be lower. This is the limitation v2 below measures and closes.
- Public dermatology datasets under-represent darker skin tones, so performance is not guaranteed to be uniform across skin types — a recognized equity concern in dermatology AI.
- A 90% sensitivity target still implies roughly one in ten malignant lesions is missed, which is precisely why the tool must never substitute for a clinician.
v2 — closing the smartphone gap
The first limitation above is the important one, and it is a real flaw: v1 was trained on dermatoscopic images — taken through a clinical lens pressed against the skin, under controlled light — but the app receives an ordinary smartphone photo. The 0.914 headline was therefore measured on a kind of image the app never actually sees. v2 sets out to measure how much that mismatch costs, and then to fix it.
The test data is PAD-UFES-20: 2,298 real smartphone photos of skin lesions collected in a Brazilian screening program, with a patient ID attached to every image. The data is split by patient — no patient on both sides — using the same benign/malignant labels as v1. Actinic keratosis is excluded, because v1's source data marked it "indeterminate" and dropped it, so scoring it here would not be a fair test of the same model. That leaves 969 patients and 1,564 photos.
| Model | Tested on | ROC-AUC | Specificity at 90% sensitivity |
|---|---|---|---|
| v1 — trained on dermoscopy | its own dermoscopy test | 0.914 | 75% |
| v1 — trained on dermoscopy | real smartphone photos | 0.743 | 32% |
| v2 — fine-tuned on smartphone | the same smartphone photos | 0.920 | 75% |
- The drop is real. On smartphone photos the dermoscopy model fell from ROC-AUC 0.914 to 0.743, and at the app's 90%-sensitivity operating point its specificity collapsed to 32% — it was falsely flagging roughly two out of three benign lesions. A model trained on a clinical lens does not simply transfer to a phone camera.
- Fine-tuning recovers it. Retraining that same model on smartphone photos brought it back to ROC-AUC 0.920 — as strong on phone photos as v1 ever was on dermatoscopes — with specificity restored to 75%.
- Why it matters. v2's 0.920 is the number that describes what the app can actually do, because it is measured on the kind of image the app actually receives. The higher-looking 0.914 was measured on the wrong domain.
This is the same idea that runs through the rest of the portfolio — the ECG model tested on patients it never saw, the leukemia model tested on held-out patients: measure the model on what it will really face, not on the friendliest version of the problem. The prep step also records each photo's Fitzpatrick skin type, which makes the fairness audit below possible.
Fairness across skin tones
A model that catches cancer well on average can still fail unevenly — and in dermatology AI that usually means worse performance on darker skin, because public datasets under-represent it. Since the prep step recorded each photo's Fitzpatrick skin type, v2 can be audited for exactly that. The test is deliberately strict: one global decision threshold — the app's 90%-sensitivity operating point — is applied to every group, and the question is simply whether the errors land unevenly.
| Skin type (Fitzpatrick) | Images | Malignant lesions | Sensitivity (cancers caught) |
|---|---|---|---|
| I–II (lighter) | 130 | 123 | 94.3% |
| III–IV (medium) | 48 | 41 | 78.0% |
| V–VI (darker) | 3 | 3 | too few to assess |
- There is a real gap. The model catches 94% of cancers on lighter skin but only 78% on medium skin — roughly a 16-point drop in the metric that matters most for a screening tool. Both groups have enough malignant cases (123 and 41) for this to be a genuine signal rather than noise, and it points the same way the dermatology-AI literature warns: the tool is less safe on darker skin.
- The most important comparison can't be made. The dataset contains just three Fitzpatrick V images and no Fitzpatrick VI at all, so performance on the darkest skin — the group most affected by this equity gap — simply cannot be evaluated here. That absence is itself the finding.
- Specificity is left out by group on purpose. In this dataset skin type was recorded mainly for biopsied (mostly malignant) lesions, so almost none of the benign cases carry a skin-type label — too few per group to measure specificity honestly. Reporting it on a handful of samples would be worse than leaving it out.
The honest takeaway is uncomfortable and worth stating plainly: v2 is measurably better at catching cancer on lighter skin than on darker skin, and the data available cannot even test the darkest skin at all. Closing that gap is not a modelling trick — it needs training and test data that actually include darker skin, which is the real next step for this project.