Meet Radadiya commited on
Commit
782e635
·
1 Parent(s): 9a0b03d

DermaScan

Browse files
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.pth filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ venv/
8
+ .venv/
9
+ env/
10
+ ENV/
11
+
12
+ # IDE / Editor
13
+ .vscode/
14
+ .idea/
15
+ *.swp
16
+ *.swo
17
+
18
+ # OS files
19
+ .DS_Store
20
+ Thumbs.db
21
+
22
+ # Jupyter
23
+ .ipynb_checkpoints/
24
+
25
+ # Streamlit secrets
26
+ .streamlit/secrets.toml
27
+
28
+ # Environment files
29
+ .env
30
+
31
+ # Model checkpoints
32
+ checkpoints/*.pth
33
+ checkpoints/*.pt
34
+ checkpoints/*.ckpt
35
+
36
+ # Logs
37
+ logs/
38
+ *.log
39
+
40
+ # Cache
41
+ .cache/
42
+ pytest_cache/
43
+ .mypy_cache/
44
+
45
+ # Build
46
+ build/
47
+ dist/
48
+ *.egg-info/
49
+
50
+ # Local data artifacts
51
+ data/
52
+ uploads/
53
+
54
+ # Generated metrics/history files
55
+ results/*.json
56
+ results/*.txt
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Meet Radadiya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,12 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: DermaScan AI
3
- emoji: 💻
4
- colorFrom: purple
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: AI Skin Disease Detection
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🏥 DermaScan AI
2
+
3
+ <div align="center">
4
+
5
+ ![DermaScan AI](https://img.shields.io/badge/DermaScan-AI-blue?style=for-the-badge)
6
+ ![Python](https://img.shields.io/badge/Python-3.8+-green?style=for-the-badge&logo=python)
7
+ ![PyTorch](https://img.shields.io/badge/PyTorch-2.0+-red?style=for-the-badge&logo=pytorch)
8
+ ![Streamlit](https://img.shields.io/badge/Streamlit-1.28+-FF4B4B?style=for-the-badge&logo=streamlit)
9
+ ![FastAPI](https://img.shields.io/badge/FastAPI-0.104+-009688?style=for-the-badge&logo=fastapi)
10
+
11
+ **Advanced AI-Powered Dermatology Analysis System**
12
+
13
+ *Leveraging Deep Learning for Accurate Skin Condition Detection*
14
+
15
+ [Features](#-features) • [Demo](#-demo) • [Installation](#-installation) • [Usage](#-usage) • [Architecture](#-architecture) • [Documentation](#-documentation)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## 📋 Table of Contents
22
+
23
+ - [Overview](#-overview)
24
+ - [Features](#-features)
25
+ - [Demo](#-demo)
26
+ - [Technology Stack](#-technology-stack)
27
+ - [Architecture](#-architecture)
28
+ - [Installation](#-installation)
29
+ - [Usage](#-usage)
30
+ - [Model Performance](#-model-performance)
31
+ - [Project Structure](#-project-structure)
32
+ - [API Documentation](#-api-documentation)
33
+ - [Contributing](#-contributing)
34
+ - [License](#-license)
35
+ - [Acknowledgments](#-acknowledgments)
36
+
37
+ ---
38
+
39
+ ## 🔬 Overview
40
+
41
+ **DermaScan AI** is a production-grade, AI-powered dermatology analysis system that uses deep learning to detect and classify 13 different skin conditions. Built with state-of-the-art computer vision techniques, it provides real-time analysis with 96% AUC-ROC accuracy.
42
+
43
+ ### 🎯 Key Highlights
44
+
45
+ - **13 Skin Conditions** - Detects 3 cancer types, 4 benign conditions, and 6 skin diseases
46
+ - **96% AUC-ROC** - High accuracy validated on medical datasets
47
+ - **Real-time Analysis** - Fast inference with EfficientNet-B3 architecture
48
+ - **Medical-Grade UI** - Professional dark-mode interface optimized for healthcare
49
+ - **India-Optimized** - Location-based hospital finder with emergency contacts
50
+ - **Production-Ready** - Modular architecture with FastAPI backend and Streamlit frontend
51
+
52
  ---
53
+
54
+ ## ✨ Features
55
+
56
+ ### 🧠 AI-Powered Detection
57
+
58
+ - **EfficientNet-B3 Architecture** - State-of-the-art CNN for image classification
59
+ - **Transfer Learning** - Pre-trained on ImageNet, fine-tuned on medical datasets
60
+ - **Test-Time Augmentation (TTA)** - Enhanced prediction accuracy
61
+ - **Confidence Scoring** - Transparent AI decision-making
62
+
63
+ ### 🔍 Comprehensive Analysis
64
+
65
+ - **13 Condition Types**
66
+ - **Cancer (3)**: Melanoma, Basal Cell Carcinoma, Actinic Keratoses
67
+ - **Benign (4)**: Melanocytic Nevi, Benign Keratosis, Dermatofibroma, Vascular Lesions
68
+ - **Diseases (6)**: Acne & Rosacea, Eczema, Psoriasis, Fungal Infection, Warts, Vitiligo
69
+
70
+ - **Differential Diagnosis** - Top alternative conditions with probabilities
71
+ - **Severity Classification** - Critical, High, Medium, Low risk levels
72
+ - **Care Recommendations** - Personalized advice based on condition
73
+
74
+ ### 🏥 Healthcare Integration
75
+
76
+ - **Hospital Finder** - Google Maps integration for nearby specialists
77
+ - **Emergency Contacts** - Quick access to India helplines
78
+ - **Location-Based** - State and city-specific recommendations
79
+ - **Medical Disclaimer** - Clear guidance on professional consultation
80
+
81
+ ### 🎨 Professional UI
82
+
83
+ - **Dark Mode** - Eye-friendly medical-grade interface
84
+ - **Responsive Design** - Works on desktop, tablet, and mobile
85
+ - **Interactive Charts** - Plotly visualizations for confidence analysis
86
+ - **Real-time Feedback** - Loading states and progress indicators
87
+
88
  ---
89
 
90
+ ## 🎬 Demo
91
+
92
+ ### Upload & Analyze
93
+ ```
94
+ 1. Upload a clear, well-lit skin image
95
+ 2. Select your location (State/City)
96
+ 3. Click "Analyze Image"
97
+ 4. Get instant AI-powered diagnosis
98
+ ```
99
+
100
+ ### Results Dashboard
101
+ - **Severity Banner** - Color-coded risk level
102
+ - **Confidence Metrics** - AI confidence score and classification
103
+ - **Diagnosis Tab** - Detailed condition information
104
+ - **Confidence Chart** - Visual probability distribution
105
+ - **Care Advice** - Recommended actions and risk factors
106
+ - **Hospital Finder** - Embedded Google Maps with nearby specialists
107
+
108
+ ---
109
+
110
+ ## 🛠️ Technology Stack
111
+
112
+ ### Backend
113
+ - **Python 3.8+** - Core programming language
114
+ - **PyTorch 2.0+** - Deep learning framework
115
+ - **FastAPI** - High-performance API framework
116
+ - **Uvicorn** - ASGI server
117
+ - **Pydantic** - Data validation
118
+
119
+ ### Frontend
120
+ - **Streamlit** - Interactive web application
121
+ - **Plotly** - Data visualization
122
+ - **HTML/CSS/JavaScript** - Custom styling
123
+
124
+ ### ML/AI
125
+ - **EfficientNet-B3** - CNN architecture
126
+ - **torchvision** - Image transformations
127
+ - **Albumentations** - Data augmentation
128
+ - **scikit-learn** - Metrics and evaluation
129
+
130
+ ### Data
131
+ - **HAM10000** - 10,000+ dermatoscopic images
132
+ - **DermNet** - Comprehensive dermatology dataset
133
+
134
+ ---
135
+
136
+ ## 🏗️ Architecture
137
+
138
+ ```
139
+ ┌─────────────────────────────────────────────────────────┐
140
+ │ Frontend (Streamlit) │
141
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
142
+ │ │ Header │ │ Sidebar │ │ Upload │ │ Results │ │
143
+ │ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
144
+ └─────────────────────────────────────────────────────────┘
145
+
146
+ │ HTTP/REST API
147
+
148
+ ┌─────────────────────────────────────────────────────────┐
149
+ │ Backend (FastAPI) │
150
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
151
+ │ │ API │ │ Model │ │ Response │ │ Utils │ │
152
+ │ │ Routes │ │ Inference│ │ Engine │ │ │ │
153
+ │ └──────────┘ └──────────┘ └──────────┘ └─────────┘ │
154
+ └─────────────────────────────────────────────────────────┘
155
+
156
+
157
+ ┌─────────────────────────────────────────────────────────┐
158
+ │ ML Model (PyTorch) │
159
+ │ ┌──────────────────────────────────────────────────┐ │
160
+ │ │ EfficientNet-B3 (Pre-trained) │ │
161
+ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
162
+ │ │ │ Conv │→ │ MBConv │→ │ MBConv │→ │ Head │ │ │
163
+ │ │ │ Stem │ │ Blocks │ │ Blocks │ │ (FC) │ │ │
164
+ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
165
+ │ └──────────────────────────────────────────────────┘ │
166
+ └─────────────────────────────────────────────────────────┘
167
+ ```
168
+
169
+ ### Data Flow
170
+ 1. **User uploads image** → Frontend (Streamlit)
171
+ 2. **Image sent to API** → Backend (FastAPI)
172
+ 3. **Preprocessing** → Resize, normalize, augment
173
+ 4. **Model inference** → EfficientNet-B3 prediction
174
+ 5. **Post-processing** → Confidence, severity, recommendations
175
+ 6. **Response generation** → Care advice, hospital finder
176
+ 7. **Results display** → Interactive dashboard
177
+
178
+ ---
179
+
180
+ ## 📦 Installation
181
+
182
+ ### Prerequisites
183
+ - Python 3.8 or higher
184
+ - pip package manager
185
+ - 4GB+ RAM recommended
186
+ - GPU optional (for training)
187
+
188
+ ### Quick Start
189
+
190
+ 1. **Clone the repository**
191
+ ```bash
192
+ git clone https://github.com/yourusername/dermascan-ai.git
193
+ cd dermascan-ai
194
+ ```
195
+
196
+ 2. **Create virtual environment**
197
+ ```bash
198
+ python -m venv venv
199
+
200
+ # Windows
201
+ venv\Scripts\activate
202
+
203
+ # Linux/Mac
204
+ source venv/bin/activate
205
+ ```
206
+
207
+ 3. **Install dependencies**
208
+ ```bash
209
+ pip install -r requirements.txt
210
+ ```
211
+
212
+ ---
213
+
214
+ ## 🚀 Usage
215
+
216
+ ### Running the Application
217
+
218
+ #### 1. Start the Backend API
219
+ ```bash
220
+ # Terminal 1
221
+ python -m api.app
222
+
223
+ # API will be available at http://localhost:8000
224
+ # Swagger docs at http://localhost:8000/docs
225
+ ```
226
+
227
+ #### 2. Start the Frontend
228
+ ```bash
229
+ # Terminal 2
230
+ streamlit run frontend/app.py
231
+
232
+ # App will open at http://localhost:8501
233
+ ```
234
+
235
+ ### Using the Application
236
+
237
+ 1. **Upload Image**
238
+ - Click "Browse files" or drag & drop
239
+ - Supported formats: JPG, JPEG, PNG
240
+ - Recommended: Clear, well-lit close-up photos
241
+
242
+ 2. **Select Location**
243
+ - Choose your State from sidebar
244
+ - Select your City
245
+ - Used for hospital recommendations
246
+
247
+ 3. **Analyze**
248
+ - Click "🔬 Analyze Image" button
249
+ - Wait for AI processing (2-5 seconds)
250
+ - View comprehensive results
251
+
252
+ 4. **Review Results**
253
+ - **Diagnosis Tab**: Condition details and confidence
254
+ - **Confidence Tab**: Visual probability chart
255
+ - **Care Advice Tab**: Recommendations and risk factors
256
+ - **Hospitals Tab**: Find nearby specialists
257
+
258
+ ---
259
+
260
+ ## 📊 Model Performance
261
+
262
+ ### Metrics (Test Set)
263
+
264
+ | Metric | Score |
265
+ |--------|-------|
266
+ | **AUC-ROC** | 96.0% |
267
+ | **Accuracy** | 89.2% |
268
+ | **Precision** | 87.5% |
269
+ | **Recall** | 88.1% |
270
+ | **F1-Score** | 87.8% |
271
+
272
+ ### Per-Class Performance
273
+
274
+ | Condition | Precision | Recall | F1-Score |
275
+ |-----------|-----------|--------|----------|
276
+ | Melanoma | 92.3% | 89.7% | 91.0% |
277
+ | Basal Cell Carcinoma | 88.5% | 91.2% | 89.8% |
278
+ | Actinic Keratoses | 85.7% | 87.3% | 86.5% |
279
+ | Melanocytic Nevi | 90.1% | 88.9% | 89.5% |
280
+ | Benign Keratosis | 86.4% | 85.2% | 85.8% |
281
+ | Eczema | 89.7% | 90.5% | 90.1% |
282
+ | Psoriasis | 87.2% | 88.8% | 88.0% |
283
+
284
+ ### Training Details
285
+ - **Dataset**: HAM10000 + DermNet (10,000+ images)
286
+ - **Architecture**: EfficientNet-B3
287
+ - **Optimizer**: AdamW with cosine annealing
288
+ - **Loss**: Focal Loss (class imbalance handling)
289
+ - **Augmentation**: Rotation, flip, color jitter, cutout
290
+ - **Training Time**: ~6 hours on NVIDIA RTX 3090
291
+
292
+ ---
293
+
294
+ ## 📁 Project Structure
295
+
296
+ ```
297
+ dermascan-ai/
298
+ ├── api/ # Backend API
299
+ │ ├── app.py # FastAPI application
300
+ │ └── schemas.py # Pydantic models
301
+
302
+ ├── frontend/ # Streamlit UI
303
+ │ ├── app.py # Main application
304
+ │ ├── assets/
305
+ │ │ ├── style.css # Dark mode styling
306
+ │ │ └── sample_images/ # Sample test images
307
+ │ ├── components/ # Reusable components
308
+ │ │ ├── header.py # Medical header
309
+ │ │ ├── sidebar.py # Location & info panel
310
+ │ │ ├── result_card.py # Severity banners & metrics
311
+ │ │ ├── confidence_chart.py # Plotly charts
312
+ │ │ ├── care_advice_card.py # Care recommendations
313
+ │ │ └── hospital_map.py # Google Maps integration
314
+ │ └── pages/ # Additional pages (if any)
315
+
316
+ ├── src/ # Core ML code
317
+ │ ├── inference/ # Prediction
318
+ │ │ └── predictor.py # Model inference logic
319
+ │ └── response/ # Response generation
320
+ │ ├── response_engine.py # Response builder
321
+ │ └── hospital_finder.py # Hospital search logic
322
+
323
+ ├── configs/ # Configuration files
324
+ │ ├── config.yaml # Training config
325
+ │ ├── class_config.json # Class mappings
326
+ │ ├── india_cities.json # Location data
327
+ │ └── response_templates.json # Response templates
328
+
329
+ ├── checkpoints/ # Model checkpoints
330
+ │ └── best_model.pth # Trained model (96% AUC)
331
+
332
+ ├── notebooks/ # Jupyter notebooks
333
+ │ ├── 01-data-pipeline.ipynb # Data preprocessing
334
+ │ └── 02-training.ipynb # Model training
335
+
336
+ ├── results/ # Training results
337
+ │ ├── confusion_matrix.png # Confusion matrix
338
+ │ ├── training_curves.png # Loss/accuracy curves
339
+ │ ├── per_class_performance.png
340
+ │ ├── classification_report.txt
341
+ │ ├── test_metrics.json
342
+ │ ├── training_history.json
343
+ │ ├── augmentation_examples.png
344
+ │ └── gradcam_*.png # GradCAM visualizations
345
+
346
+ ├── venv/ # Virtual environment (not in git)
347
+
348
+ ├── .gitignore # Git ignore rules
349
+ ├── LICENSE # MIT License
350
+ ├── README.md # This file
351
+ └── requirements.txt # Python dependencies
352
+ ```
353
+
354
+ ### 📝 Key Files
355
+
356
+ | File | Description |
357
+ |------|-------------|
358
+ | `api/app.py` | FastAPI backend server |
359
+ | `frontend/app.py` | Streamlit web interface |
360
+ | `src/inference/predictor.py` | Model inference engine |
361
+ | `src/response/response_engine.py` | Response generation logic |
362
+ | `checkpoints/best_model.pth` | Trained EfficientNet-B3 model |
363
+ | `configs/class_config.json` | Disease class mappings |
364
+ | `configs/response_templates.json` | Care advice templates |
365
+ | `configs/india_cities.json` | Indian states and cities |
366
+
367
+ ### 🗂️ Directory Purpose
368
+
369
+ - **`api/`** - RESTful API backend with FastAPI
370
+ - **`frontend/`** - User interface with Streamlit
371
+ - **`src/`** - Core ML inference and response logic
372
+ - **`configs/`** - Configuration files and templates
373
+ - **`checkpoints/`** - Trained model weights
374
+ - **`notebooks/`** - Jupyter notebooks for experimentation
375
+ - **`results/`** - Training metrics and visualizations
376
+ - **`venv/`** - Python virtual environment (excluded from git)
377
+
378
+ ---
379
+
380
+ ## 📚 API Documentation
381
+
382
+ ### Endpoints
383
+
384
+ #### `POST /predict`
385
+ Analyze a skin image and return diagnosis.
386
+
387
+ **Request:**
388
+ ```bash
389
+ curl -X POST "http://localhost:8000/predict" \
390
+ -F "file=@image.jpg" \
391
+ -F "city=New Delhi" \
392
+ -F "state=Delhi"
393
+ ```
394
+
395
+ **Response:**
396
+ ```json
397
+ {
398
+ "predicted_class": "Melanoma",
399
+ "confidence": 0.92,
400
+ "tier": "CANCER",
401
+ "severity": "CRITICAL",
402
+ "tagline": "Urgent Medical Attention Required",
403
+ "action": "Consult an oncologist immediately",
404
+ "description": "Melanoma is a serious form of skin cancer...",
405
+ "all_probabilities": {
406
+ "Melanoma": 0.92,
407
+ "Basal Cell Carcinoma": 0.04,
408
+ ...
409
+ },
410
+ "differential_diagnosis": [...],
411
+ "care_advice": [...],
412
+ "risk_factors": [...],
413
+ "hospital_type": "Oncologist",
414
+ "hospital_search_query": "oncologist near me",
415
+ "emergency_numbers": {...},
416
+ "inference_time": 2.34
417
+ }
418
+ ```
419
+
420
+ #### `GET /health`
421
+ Check API health status.
422
+
423
+ **Response:**
424
+ ```json
425
+ {
426
+ "status": "healthy",
427
+ "model_loaded": true,
428
+ "version": "1.0.0"
429
+ }
430
+ ```
431
+
432
+ ### Interactive Documentation
433
+ - Swagger UI: `http://localhost:8000/docs`
434
+ - ReDoc: `http://localhost:8000/redoc`
435
+ ---
436
+
437
+ ## 🤝 Contributing
438
+
439
+ We welcome contributions! Please follow these steps:
440
+
441
+ 1. **Fork the repository**
442
+ 2. **Create a feature branch**
443
+ ```bash
444
+ git checkout -b feature/amazing-feature
445
+ ```
446
+ 3. **Commit your changes**
447
+ ```bash
448
+ git commit -m "Add amazing feature"
449
+ ```
450
+ 4. **Push to the branch**
451
+ ```bash
452
+ git push origin feature/amazing-feature
453
+ ```
454
+ 5. **Open a Pull Request**
455
+
456
+ ### Contribution Guidelines
457
+ - Follow PEP 8 style guide
458
+ - Add unit tests for new features
459
+ - Update documentation
460
+ - Ensure all tests pass
461
+
462
+ ---
463
+
464
+ ## 📄 License
465
+
466
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
467
+
468
+ ---
469
+
470
+ ## 🙏 Acknowledgments
471
+
472
+ ### Datasets
473
+ - **HAM10000**: Harvard Dataverse - Dermatoscopic Images
474
+ - **DermNet**: DermNet New Zealand Trust
475
+
476
+ ### Frameworks & Libraries
477
+ - **PyTorch**: Deep learning framework
478
+ - **FastAPI**: Modern web framework
479
+ - **Streamlit**: Interactive web apps
480
+ - **EfficientNet**: Efficient CNN architecture
481
+
482
+ ### Inspiration
483
+ - Medical professionals and dermatologists
484
+ - Open-source AI/ML community
485
+ - Healthcare accessibility initiatives
486
+
487
+ ---
488
+
489
+ ## ⚠️ Medical Disclaimer
490
+
491
+ **IMPORTANT**: DermaScan AI is an educational and screening tool. It is **NOT** a substitute for professional medical diagnosis, treatment, or advice.
492
+
493
+ - Always consult a qualified dermatologist for proper evaluation
494
+ - Do not use this tool for self-diagnosis or treatment decisions
495
+ - Seek immediate medical attention for concerning symptoms
496
+ - This tool is for research and educational purposes only
497
+
498
+ ---
499
+
500
+ ## 📞 Contact & Support
501
+
502
+ - **Issues**: [GitHub Issues](https://github.com/yourusername/dermascan-ai/issues)
503
+ - **Discussions**: [GitHub Discussions](https://github.com/yourusername/dermascan-ai/discussions)
504
+ - **Email**: your.email@example.com
505
+
506
+ ---
507
+
508
+ ## 🌟 Star History
509
+
510
+ If you find this project useful, please consider giving it a ⭐!
511
+
512
+ ---
513
+
514
+ <div align="center">
515
+
516
+ **Built with ❤️ for Healthcare Accessibility**
517
+
518
+ *DermaScan AI - Empowering Early Detection Through AI*
519
+
520
+ </div>
api/app.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =================================================================
3
+ DERMASCAN-AI — FastAPI Application
4
+ =================================================================
5
+ """
6
+
7
+ import io
8
+ import time
9
+ import numpy as np
10
+ from PIL import Image
11
+ from pathlib import Path
12
+
13
+ from fastapi import FastAPI, File, UploadFile, HTTPException, Query
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from contextlib import asynccontextmanager
16
+
17
+ from src.inference.predictor import SkinPredictor
18
+ from src.response.response_engine import ResponseEngine
19
+ from src.response.hospital_finder import HospitalFinder
20
+ from api.schemas import HealthResponse
21
+
22
+ # ── Global objects ──
23
+ predictor = None
24
+ response_engine = None
25
+ hospital_finder = None
26
+
27
+
28
+ @asynccontextmanager
29
+ async def lifespan(app: FastAPI):
30
+ global predictor, response_engine, hospital_finder
31
+
32
+ print("🚀 Starting DermaScan-AI...")
33
+
34
+ predictor = SkinPredictor(
35
+ model_path="checkpoints/best_model.pth",
36
+ class_config_path="configs/class_config.json",
37
+ device="cpu",
38
+ )
39
+
40
+ response_engine = ResponseEngine(
41
+ class_config_path="configs/class_config.json",
42
+ response_templates_path="configs/response_templates.json",
43
+ )
44
+
45
+ hospital_finder = HospitalFinder()
46
+
47
+ print("✅ DermaScan-AI ready!")
48
+ yield
49
+ print("🛑 Shutting down...")
50
+
51
+
52
+ app = FastAPI(
53
+ title="🔬 DermaScan-AI",
54
+ description="AI-powered skin disease detection with clinical guidance",
55
+ version="1.0.0",
56
+ lifespan=lifespan,
57
+ )
58
+
59
+ app.add_middleware(
60
+ CORSMiddleware,
61
+ allow_origins=["*"],
62
+ allow_methods=["*"],
63
+ allow_headers=["*"],
64
+ )
65
+
66
+
67
+ def convert_numpy(obj):
68
+ """Convert numpy types to Python native for JSON serialization."""
69
+ if isinstance(obj, dict):
70
+ return {k: convert_numpy(v) for k, v in obj.items()}
71
+ elif isinstance(obj, list):
72
+ return [convert_numpy(v) for v in obj]
73
+ elif isinstance(obj, (np.bool_,)):
74
+ return bool(obj)
75
+ elif isinstance(obj, (np.integer,)):
76
+ return int(obj)
77
+ elif isinstance(obj, (np.floating,)):
78
+ return float(obj)
79
+ elif isinstance(obj, np.ndarray):
80
+ return obj.tolist()
81
+ return obj
82
+
83
+
84
+ @app.get("/health", response_model=HealthResponse)
85
+ async def health():
86
+ return HealthResponse(
87
+ status="healthy",
88
+ model_loaded=predictor is not None,
89
+ model_name="EfficientNet-B3",
90
+ version="1.0.0",
91
+ )
92
+
93
+
94
+ @app.post("/predict")
95
+ async def predict(
96
+ file: UploadFile = File(...),
97
+ city: str = Query("Delhi", description="City in India"),
98
+ state: str = Query("Delhi", description="State in India"),
99
+ ):
100
+ if file.content_type not in ["image/jpeg", "image/png", "image/jpg"]:
101
+ raise HTTPException(400, "Only JPG/PNG images supported")
102
+
103
+ contents = await file.read()
104
+ if len(contents) > 10 * 1024 * 1024:
105
+ raise HTTPException(400, "File too large (max 10MB)")
106
+
107
+ try:
108
+ image = Image.open(io.BytesIO(contents)).convert('RGB')
109
+ except Exception:
110
+ raise HTTPException(400, "Invalid image file")
111
+
112
+ start = time.time()
113
+ prediction = predictor.predict(image)
114
+ inference_time = time.time() - start
115
+
116
+ response = response_engine.generate_response(
117
+ predicted_class=prediction['predicted_class'],
118
+ confidence=prediction['confidence'],
119
+ all_probabilities=prediction['all_probabilities'],
120
+ )
121
+
122
+ hospital_result = hospital_finder.search(
123
+ query=response['hospital_search_query'],
124
+ city=city,
125
+ state=state,
126
+ )
127
+ response['maps_url'] = hospital_result['maps_url']
128
+ response['maps_embed_url'] = hospital_result['embed_url']
129
+ response['hospital_location'] = hospital_result['location']
130
+ response['inference_time'] = round(inference_time, 3)
131
+ response['emergency_numbers'] = hospital_finder.get_emergency_numbers()
132
+
133
+ return convert_numpy(response)
134
+
135
+
136
+ if __name__ == "__main__":
137
+ import uvicorn
138
+ uvicorn.run("api.app:app", host="0.0.0.0", port=8000, reload=True)
api/schemas.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pydantic schemas for API."""
2
+ from pydantic import BaseModel, Field
3
+ from typing import List, Dict, Optional
4
+
5
+
6
+ class PredictionResponse(BaseModel):
7
+ predicted_class: str
8
+ confidence: float
9
+ confidence_level: str
10
+ tier: str
11
+ severity: str
12
+ emoji: str
13
+ tagline: str
14
+ action: str
15
+ urgency_message: str
16
+ description: str
17
+ care_advice: List[str]
18
+ risk_factors: List[str]
19
+ hospital_search_query: str
20
+ hospital_type: str
21
+ differential_diagnosis: List[Dict]
22
+ cancer_alert: bool
23
+ cancer_warning: Optional[str] = None
24
+ all_probabilities: Dict[str, float]
25
+ disclaimer: str
26
+ maps_url: str
27
+
28
+
29
+ class HealthResponse(BaseModel):
30
+ status: str
31
+ model_loaded: bool
32
+ model_name: str
33
+ version: str
configs/class_config.json ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "0": {
3
+ "name": "Melanoma",
4
+ "folder": "Melanoma",
5
+ "tier": "CANCER",
6
+ "severity": "CRITICAL",
7
+ "color": "#e74c3c"
8
+ },
9
+ "1": {
10
+ "name": "Basal Cell Carcinoma",
11
+ "folder": "Basal_Cell_Carcinoma",
12
+ "tier": "CANCER",
13
+ "severity": "HIGH",
14
+ "color": "#e67e22"
15
+ },
16
+ "2": {
17
+ "name": "Actinic Keratoses",
18
+ "folder": "Actinic_Keratoses",
19
+ "tier": "PRE-CANCER",
20
+ "severity": "MEDIUM",
21
+ "color": "#f39c12"
22
+ },
23
+ "3": {
24
+ "name": "Melanocytic Nevi",
25
+ "folder": "Melanocytic_Nevi",
26
+ "tier": "BENIGN",
27
+ "severity": "LOW",
28
+ "color": "#27ae60"
29
+ },
30
+ "4": {
31
+ "name": "Benign Keratosis",
32
+ "folder": "Benign_Keratosis",
33
+ "tier": "BENIGN",
34
+ "severity": "LOW",
35
+ "color": "#2ecc71"
36
+ },
37
+ "5": {
38
+ "name": "Dermatofibroma",
39
+ "folder": "Dermatofibroma",
40
+ "tier": "BENIGN",
41
+ "severity": "LOW",
42
+ "color": "#1abc9c"
43
+ },
44
+ "6": {
45
+ "name": "Vascular Lesions",
46
+ "folder": "Vascular_Lesions",
47
+ "tier": "BENIGN",
48
+ "severity": "LOW",
49
+ "color": "#16a085"
50
+ },
51
+ "7": {
52
+ "name": "Acne and Rosacea",
53
+ "folder": "Acne",
54
+ "tier": "DISEASE",
55
+ "severity": "LOW",
56
+ "color": "#3498db"
57
+ },
58
+ "8": {
59
+ "name": "Eczema",
60
+ "folder": "Eczema",
61
+ "tier": "DISEASE",
62
+ "severity": "LOW",
63
+ "color": "#2980b9"
64
+ },
65
+ "9": {
66
+ "name": "Psoriasis",
67
+ "folder": "Psoriasis",
68
+ "tier": "DISEASE",
69
+ "severity": "LOW",
70
+ "color": "#9b59b6"
71
+ },
72
+ "10": {
73
+ "name": "Fungal Infection",
74
+ "folder": "Fungal_Infection",
75
+ "tier": "DISEASE",
76
+ "severity": "LOW",
77
+ "color": "#8e44ad"
78
+ },
79
+ "11": {
80
+ "name": "Warts and Viral",
81
+ "folder": "Warts",
82
+ "tier": "DISEASE",
83
+ "severity": "LOW",
84
+ "color": "#34495e"
85
+ },
86
+ "12": {
87
+ "name": "Vitiligo",
88
+ "folder": "Vitiligo",
89
+ "tier": "DISEASE",
90
+ "severity": "LOW",
91
+ "color": "#7f8c8d"
92
+ }
93
+ }
configs/config.yaml ADDED
File without changes
configs/india_cities.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Andhra Pradesh": ["Visakhapatnam", "Vijayawada", "Guntur", "Tirupati", "Nellore", "Kakinada", "Rajahmundry", "Kadapa", "Anantapur", "Kurnool"],
3
+ "Arunachal Pradesh": ["Itanagar", "Naharlagun", "Tawang", "Pasighat", "Ziro"],
4
+ "Assam": ["Guwahati", "Dibrugarh", "Silchar", "Jorhat", "Tezpur", "Nagaon", "Tinsukia"],
5
+ "Bihar": ["Patna", "Gaya", "Muzaffarpur", "Bhagalpur", "Darbhanga", "Purnia", "Ara", "Begusarai"],
6
+ "Chhattisgarh": ["Raipur", "Bhilai", "Bilaspur", "Korba", "Durg", "Rajnandgaon", "Jagdalpur"],
7
+ "Goa": ["Panaji", "Margao", "Vasco da Gama", "Mapusa", "Ponda"],
8
+ "Gujarat": ["Ahmedabad", "Surat", "Vadodara", "Rajkot", "Gandhinagar", "Bhavnagar", "Junagadh", "Jamnagar", "Anand", "Navsari", "Morbi", "Mehsana"],
9
+ "Haryana": ["Gurgaon", "Faridabad", "Panipat", "Ambala", "Karnal", "Hisar", "Rohtak", "Sonipat", "Yamunanagar", "Panchkula"],
10
+ "Himachal Pradesh": ["Shimla", "Manali", "Dharamshala", "Mandi", "Solan", "Kullu", "Hamirpur", "Una"],
11
+ "Jharkhand": ["Ranchi", "Jamshedpur", "Dhanbad", "Bokaro", "Hazaribagh", "Deoghar", "Giridih"],
12
+ "Karnataka": ["Bangalore", "Mysore", "Hubli", "Mangalore", "Belgaum", "Gulbarga", "Davangere", "Shimoga", "Tumkur", "Udupi"],
13
+ "Kerala": ["Thiruvananthapuram", "Kochi", "Kozhikode", "Thrissur", "Kannur", "Kollam", "Palakkad", "Alappuzha", "Malappuram"],
14
+ "Madhya Pradesh": ["Bhopal", "Indore", "Jabalpur", "Gwalior", "Ujjain", "Sagar", "Rewa", "Satna", "Dewas"],
15
+ "Maharashtra": ["Mumbai", "Pune", "Nagpur", "Thane", "Nashik", "Aurangabad", "Navi Mumbai", "Solapur", "Kolhapur", "Amravati", "Sangli", "Akola"],
16
+ "Manipur": ["Imphal", "Thoubal", "Bishnupur"],
17
+ "Meghalaya": ["Shillong", "Tura", "Jowai"],
18
+ "Mizoram": ["Aizawl", "Lunglei", "Champhai"],
19
+ "Nagaland": ["Kohima", "Dimapur", "Mokokchung", "Tuensang"],
20
+ "Odisha": ["Bhubaneswar", "Cuttack", "Rourkela", "Berhampur", "Sambalpur", "Puri", "Balasore"],
21
+ "Punjab": ["Chandigarh", "Ludhiana", "Amritsar", "Jalandhar", "Patiala", "Mohali", "Bathinda", "Pathankot"],
22
+ "Rajasthan": ["Jaipur", "Jodhpur", "Udaipur", "Kota", "Ajmer", "Bikaner", "Alwar", "Bharatpur", "Sikar", "Bhilwara"],
23
+ "Sikkim": ["Gangtok", "Namchi", "Pelling"],
24
+ "Tamil Nadu": ["Chennai", "Coimbatore", "Madurai", "Salem", "Trichy", "Vellore", "Tirunelveli", "Erode", "Thoothukudi", "Thanjavur"],
25
+ "Telangana": ["Hyderabad", "Warangal", "Nizamabad", "Karimnagar", "Secunderabad", "Khammam", "Mahbubnagar"],
26
+ "Tripura": ["Agartala", "Udaipur", "Dharmanagar"],
27
+ "Uttar Pradesh": ["Lucknow", "Noida", "Kanpur", "Agra", "Varanasi", "Ghaziabad", "Meerut", "Prayagraj", "Bareilly", "Aligarh", "Moradabad", "Gorakhpur", "Mathura"],
28
+ "Uttarakhand": ["Dehradun", "Haridwar", "Rishikesh", "Haldwani", "Nainital", "Roorkee", "Kashipur"],
29
+ "West Bengal": ["Kolkata", "Howrah", "Durgapur", "Siliguri", "Asansol", "Bardhaman", "Kharagpur", "Haldia"],
30
+ "Delhi": ["New Delhi", "Dwarka", "Rohini", "Saket", "Karol Bagh", "Lajpat Nagar", "Pitampura", "Janakpuri"],
31
+ "Chandigarh": ["Chandigarh"],
32
+ "Puducherry": ["Puducherry", "Karaikal"],
33
+ "Jammu and Kashmir": ["Srinagar", "Jammu", "Anantnag", "Baramulla", "Kathua", "Udhampur"],
34
+ "Ladakh": ["Leh", "Kargil"]
35
+ }
configs/response_templates.json ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Melanoma": {
3
+ "severity": "CRITICAL",
4
+ "tier": "CANCER",
5
+ "emoji": "🔴",
6
+ "tagline": "Possible Melanoma Detected",
7
+ "action": "SEEK IMMEDIATE MEDICAL ATTENTION",
8
+ "description": "Melanoma is the most serious type of skin cancer. It develops in the cells that give skin its color (melanocytes). Early detection is critical — when caught early, the 5-year survival rate is over 99%.",
9
+ "urgency_message": "Please consult a dermatologist or oncologist as soon as possible. Do NOT delay. Early treatment dramatically improves outcomes.",
10
+ "care_advice": [
11
+ "Do NOT try to treat this at home",
12
+ "Schedule an appointment with a dermatologist within this week",
13
+ "Take clear photos of the lesion for medical records",
14
+ "Note any recent changes in size, shape, color, or sensation",
15
+ "Avoid sun exposure on the affected area",
16
+ "Do not scratch, pick, or irritate the lesion"
17
+ ],
18
+ "risk_factors": [
19
+ "History of sunburns or excessive UV exposure",
20
+ "Fair skin, light hair, or light eyes",
21
+ "Family history of melanoma",
22
+ "Large number of moles (50+)",
23
+ "Weakened immune system"
24
+ ],
25
+ "hospital_search_query": "oncologist skin cancer specialist near me",
26
+ "hospital_type": "Cancer Hospital / Dermatology Specialist"
27
+ },
28
+
29
+ "Basal Cell Carcinoma": {
30
+ "severity": "HIGH",
31
+ "tier": "CANCER",
32
+ "emoji": "🟠",
33
+ "tagline": "Possible Basal Cell Carcinoma Detected",
34
+ "action": "CONSULT A DERMATOLOGIST PROMPTLY",
35
+ "description": "Basal Cell Carcinoma (BCC) is the most common form of skin cancer. While it rarely spreads to other parts of the body, it can cause significant local tissue damage if left untreated.",
36
+ "urgency_message": "Please schedule an appointment with a dermatologist within 2 weeks. BCC is highly treatable when caught early.",
37
+ "care_advice": [
38
+ "Schedule a dermatologist appointment within 2 weeks",
39
+ "Do not attempt to remove or treat the lesion yourself",
40
+ "Protect the area from sun exposure (SPF 50+)",
41
+ "Monitor for any changes in size or appearance",
42
+ "Take photos to track any changes before your appointment",
43
+ "Avoid tanning beds and prolonged sun exposure"
44
+ ],
45
+ "risk_factors": [
46
+ "Chronic sun exposure over many years",
47
+ "History of sunburns",
48
+ "Fair skin",
49
+ "Age over 50",
50
+ "Previous history of skin cancer"
51
+ ],
52
+ "hospital_search_query": "dermatologist skin cancer near me",
53
+ "hospital_type": "Dermatology Clinic / Skin Cancer Center"
54
+ },
55
+
56
+ "Actinic Keratoses": {
57
+ "severity": "MEDIUM",
58
+ "tier": "PRE-CANCER",
59
+ "emoji": "🟡",
60
+ "tagline": "Possible Pre-Cancerous Lesion Detected",
61
+ "action": "SCHEDULE A DERMATOLOGY APPOINTMENT",
62
+ "description": "Actinic Keratosis (AK) is a rough, scaly patch on the skin caused by years of sun exposure. It's considered pre-cancerous — about 5-10% of AKs can progress to squamous cell carcinoma if untreated.",
63
+ "urgency_message": "While not an emergency, please see a dermatologist within the next month. Early treatment prevents progression to cancer.",
64
+ "care_advice": [
65
+ "Schedule a dermatologist visit within 1 month",
66
+ "Apply broad-spectrum sunscreen (SPF 30+) daily",
67
+ "Wear protective clothing and hats outdoors",
68
+ "Avoid peak sun hours (10 AM – 4 PM)",
69
+ "Monitor the lesion for changes in size or texture",
70
+ "Do not pick or scratch the affected area",
71
+ "Keep the area moisturized"
72
+ ],
73
+ "risk_factors": [
74
+ "Cumulative sun exposure",
75
+ "Fair skin",
76
+ "Age over 40",
77
+ "Living in sunny climates",
78
+ "Outdoor occupation"
79
+ ],
80
+ "hospital_search_query": "dermatologist near me",
81
+ "hospital_type": "Dermatology Clinic"
82
+ },
83
+
84
+ "Melanocytic Nevi": {
85
+ "severity": "LOW",
86
+ "tier": "BENIGN",
87
+ "emoji": "🟢",
88
+ "tagline": "Common Mole (Melanocytic Nevus)",
89
+ "action": "MONITOR AT HOME",
90
+ "description": "This appears to be a common mole (melanocytic nevus). Moles are very common and usually harmless. Most adults have 10-40 moles. However, it's important to monitor moles for changes.",
91
+ "urgency_message": "No immediate medical attention needed. Continue regular self-examinations using the ABCDE rule.",
92
+ "care_advice": [
93
+ "Monitor monthly using the ABCDE rule:",
94
+ " A — Asymmetry: Is one half different from the other?",
95
+ " B — Border: Are edges irregular, ragged, or blurred?",
96
+ " C — Color: Is the color uneven or has it changed?",
97
+ " D — Diameter: Is it larger than 6mm (pencil eraser)?",
98
+ " E — Evolving: Has it changed in size, shape, or color?",
99
+ "Use sunscreen (SPF 30+) to protect moles from UV damage",
100
+ "See a dermatologist if any of the ABCDE signs appear",
101
+ "Annual skin check recommended for people with many moles"
102
+ ],
103
+ "risk_factors": [],
104
+ "hospital_search_query": "dermatologist near me",
105
+ "hospital_type": "Dermatology Clinic"
106
+ },
107
+
108
+ "Benign Keratosis": {
109
+ "severity": "LOW",
110
+ "tier": "BENIGN",
111
+ "emoji": "🟢",
112
+ "tagline": "Benign Keratosis (Non-Cancerous Growth)",
113
+ "action": "NO TREATMENT USUALLY NEEDED",
114
+ "description": "This appears to be a benign keratosis, such as a seborrheic keratosis or solar lentigo. These are very common, harmless skin growths that typically appear with age. They are NOT cancerous.",
115
+ "urgency_message": "No medical attention needed unless the growth is bothersome, irritated, or you notice sudden changes.",
116
+ "care_advice": [
117
+ "No treatment is usually necessary",
118
+ "These growths are harmless and non-cancerous",
119
+ "See a doctor if the growth becomes irritated or bleeds",
120
+ "See a doctor if it changes rapidly in size or color",
121
+ "Can be removed for cosmetic reasons if desired",
122
+ "Protect skin from excessive sun exposure",
123
+ "Use moisturizer to keep the area comfortable"
124
+ ],
125
+ "risk_factors": [],
126
+ "hospital_search_query": "dermatologist near me",
127
+ "hospital_type": "Dermatology Clinic"
128
+ },
129
+
130
+ "Dermatofibroma": {
131
+ "severity": "LOW",
132
+ "tier": "BENIGN",
133
+ "emoji": "🟢",
134
+ "tagline": "Dermatofibroma (Benign Skin Nodule)",
135
+ "action": "USUALLY NO TREATMENT NEEDED",
136
+ "description": "This appears to be a dermatofibroma — a common, harmless firm bump in the skin. They are benign and typically develop on the legs. They may result from minor injuries like insect bites.",
137
+ "urgency_message": "No medical attention needed. These are harmless. Consult a doctor only if it grows rapidly or becomes painful.",
138
+ "care_advice": [
139
+ "No treatment is usually necessary",
140
+ "Dermatofibromas are completely harmless",
141
+ "They may persist indefinitely but don't become cancerous",
142
+ "See a doctor if it grows rapidly or changes significantly",
143
+ "Surgical removal is possible if it's bothersome",
144
+ "Avoid repeated trauma to the area"
145
+ ],
146
+ "risk_factors": [],
147
+ "hospital_search_query": "dermatologist near me",
148
+ "hospital_type": "Dermatology Clinic"
149
+ },
150
+
151
+ "Vascular Lesions": {
152
+ "severity": "LOW",
153
+ "tier": "BENIGN",
154
+ "emoji": "🟢",
155
+ "tagline": "Vascular Lesion (Blood Vessel Related)",
156
+ "action": "USUALLY HARMLESS — MONITOR",
157
+ "description": "This appears to be a vascular lesion, such as a cherry angioma or hemangioma. These are growths made up of blood vessels and are almost always benign. They are very common, especially after age 30.",
158
+ "urgency_message": "No immediate medical attention needed. These are cosmetic concerns only. See a doctor if it bleeds frequently or grows rapidly.",
159
+ "care_advice": [
160
+ "Vascular lesions are almost always harmless",
161
+ "No treatment needed unless cosmetically bothersome",
162
+ "See a doctor if it bleeds repeatedly or won't stop bleeding",
163
+ "See a doctor if it grows rapidly",
164
+ "Laser treatment or electrocautery can remove them if desired",
165
+ "Avoid picking or scratching the lesion"
166
+ ],
167
+ "risk_factors": [],
168
+ "hospital_search_query": "dermatologist near me",
169
+ "hospital_type": "Dermatology Clinic"
170
+ },
171
+
172
+ "Acne and Rosacea": {
173
+ "severity": "LOW",
174
+ "tier": "DISEASE",
175
+ "emoji": "🔵",
176
+ "tagline": "Acne or Rosacea Detected",
177
+ "action": "MANAGEABLE WITH PROPER CARE",
178
+ "description": "This appears to be acne or rosacea — two of the most common skin conditions worldwide. Acne involves clogged pores and inflammation. Rosacea causes redness and visible blood vessels, usually on the face.",
179
+ "urgency_message": "Not urgent. Can be managed with proper skincare. See a dermatologist if over-the-counter treatments don't improve symptoms within 6-8 weeks.",
180
+ "care_advice": [
181
+ "Wash affected area gently twice daily with mild cleanser",
182
+ "Use non-comedogenic (won't clog pores) moisturizer and sunscreen",
183
+ "Avoid touching or picking at pimples — causes scarring",
184
+ "Over-the-counter options: benzoyl peroxide (2.5-5%) or salicylic acid (0.5-2%)",
185
+ "For rosacea: avoid triggers like spicy food, alcohol, hot drinks, extreme temperatures",
186
+ "Use lukewarm water — hot water worsens both conditions",
187
+ "Change pillowcases frequently",
188
+ "Consider seeing a dermatologist for persistent or severe cases",
189
+ "Prescription options (from doctor): retinoids, antibiotics, or azelaic acid"
190
+ ],
191
+ "risk_factors": [
192
+ "Hormonal changes",
193
+ "Stress",
194
+ "Certain medications",
195
+ "Family history"
196
+ ],
197
+ "hospital_search_query": "dermatologist acne treatment near me",
198
+ "hospital_type": "Dermatology Clinic"
199
+ },
200
+
201
+ "Eczema": {
202
+ "severity": "LOW",
203
+ "tier": "DISEASE",
204
+ "emoji": "🔵",
205
+ "tagline": "Eczema (Atopic Dermatitis) Detected",
206
+ "action": "MANAGEABLE WITH PROPER CARE",
207
+ "description": "This appears to be eczema (atopic dermatitis) — a chronic condition that causes the skin to become itchy, red, dry, and cracked. It's very common, affecting about 15-20% of children and 3% of adults.",
208
+ "urgency_message": "Not urgent. Can be managed with moisturizing and avoiding triggers. See a doctor if symptoms are severe, infected, or disrupting sleep.",
209
+ "care_advice": [
210
+ "Moisturize frequently — at least 2-3 times daily",
211
+ "Use thick, fragrance-free moisturizers (like CeraVe, Eucerin, or Vaseline)",
212
+ "Apply moisturizer within 3 minutes of bathing to lock in moisture",
213
+ "Take short, lukewarm baths/showers (not hot)",
214
+ "Use gentle, fragrance-free soap and laundry detergent",
215
+ "Wear soft, breathable cotton clothing",
216
+ "Avoid known triggers: harsh soaps, certain fabrics, stress, allergens",
217
+ "For flare-ups: OTC hydrocortisone cream (1%) for short periods (max 7 days)",
218
+ "Keep nails short to minimize damage from scratching",
219
+ "Use a humidifier in dry weather",
220
+ "See a doctor if: area becomes weepy, crusty, or shows signs of infection"
221
+ ],
222
+ "risk_factors": [
223
+ "Family history of eczema, asthma, or allergies",
224
+ "Dry climate",
225
+ "Stress",
226
+ "Certain foods or allergens"
227
+ ],
228
+ "hospital_search_query": "dermatologist eczema treatment near me",
229
+ "hospital_type": "Dermatology Clinic"
230
+ },
231
+
232
+ "Psoriasis": {
233
+ "severity": "LOW",
234
+ "tier": "DISEASE",
235
+ "emoji": "🔵",
236
+ "tagline": "Psoriasis Detected",
237
+ "action": "CHRONIC BUT MANAGEABLE",
238
+ "description": "This appears to be psoriasis — a chronic autoimmune condition that causes cells to build up rapidly on the skin surface, forming thick, silvery scales and itchy, dry, red patches. It affects about 2-3% of the population.",
239
+ "urgency_message": "Not an emergency. Psoriasis is a chronic condition that can be managed effectively. See a dermatologist for a treatment plan.",
240
+ "care_advice": [
241
+ "Keep skin well moisturized — apply thick cream after bathing",
242
+ "Take daily lukewarm baths with colloidal oatmeal or bath oil",
243
+ "Use OTC treatments: salicylic acid or coal tar products",
244
+ "Get moderate sun exposure (10-15 min) — helps many cases",
245
+ "But avoid sunburn — can trigger flare-ups (Koebner phenomenon)",
246
+ "Avoid skin injuries — cuts, scrapes can trigger new patches",
247
+ "Manage stress — meditation, exercise, adequate sleep",
248
+ "Avoid alcohol and smoking — both worsen psoriasis",
249
+ "Consider vitamin D supplements (consult doctor first)",
250
+ "See a dermatologist for prescription treatments if OTC doesn't help",
251
+ "Joint pain? Tell your doctor — psoriatic arthritis affects 30% of patients"
252
+ ],
253
+ "risk_factors": [
254
+ "Family history of psoriasis",
255
+ "Stress",
256
+ "Smoking",
257
+ "Certain infections",
258
+ "Some medications (lithium, beta-blockers)"
259
+ ],
260
+ "hospital_search_query": "dermatologist psoriasis treatment near me",
261
+ "hospital_type": "Dermatology Clinic"
262
+ },
263
+
264
+ "Fungal Infection": {
265
+ "severity": "LOW",
266
+ "tier": "DISEASE",
267
+ "emoji": "🔵",
268
+ "tagline": "Fungal Skin Infection Detected",
269
+ "action": "TREATABLE WITH ANTIFUNGAL CARE",
270
+ "description": "This appears to be a fungal skin infection such as ringworm (tinea), athlete's foot, or candidiasis. These are very common and highly treatable infections caused by fungi that thrive in warm, moist environments.",
271
+ "urgency_message": "Not urgent but should be treated to prevent spreading. Most cases resolve with over-the-counter antifungal treatment within 2-4 weeks.",
272
+ "care_advice": [
273
+ "Apply OTC antifungal cream/powder (clotrimazole, miconazole, or terbinafine)",
274
+ "Apply antifungal product to the affected area twice daily",
275
+ "Continue treatment for 1-2 weeks AFTER symptoms clear",
276
+ "Keep the affected area clean and DRY",
277
+ "Wash hands after touching the affected area",
278
+ "Don't share towels, clothing, or personal items",
279
+ "Wear loose, breathable clothing and cotton underwear",
280
+ "Change socks daily if feet are affected",
281
+ "Dry feet thoroughly after bathing, especially between toes",
282
+ "See a doctor if: no improvement after 2 weeks of OTC treatment",
283
+ "See a doctor if: infection covers a large area or affects scalp/nails"
284
+ ],
285
+ "risk_factors": [
286
+ "Warm, humid environments",
287
+ "Tight clothing",
288
+ "Weakened immune system",
289
+ "Public showers/pools",
290
+ "Close contact with infected person or animal"
291
+ ],
292
+ "hospital_search_query": "dermatologist fungal infection near me",
293
+ "hospital_type": "Dermatology Clinic / General Practitioner"
294
+ },
295
+
296
+ "Warts and Viral": {
297
+ "severity": "LOW",
298
+ "tier": "DISEASE",
299
+ "emoji": "🔵",
300
+ "tagline": "Wart or Viral Skin Infection Detected",
301
+ "action": "USUALLY RESOLVES — TREATMENT AVAILABLE",
302
+ "description": "This appears to be a wart or viral skin infection (such as molluscum contagiosum). Warts are caused by the Human Papillomavirus (HPV) and are very common, especially in children and young adults. They are usually harmless.",
303
+ "urgency_message": "Not urgent. Many warts resolve on their own within 1-2 years. Treatment can speed up removal if desired.",
304
+ "care_advice": [
305
+ "Many warts resolve on their own without treatment",
306
+ "OTC treatment: salicylic acid pads/solutions (apply daily for weeks)",
307
+ "OTC treatment: freeze-away products (cryotherapy kits)",
308
+ "Cover the wart with a bandage to prevent spreading",
309
+ "Don't pick, scratch, or bite warts — spreads the virus",
310
+ "Wash hands after touching warts",
311
+ "Don't share towels or personal items",
312
+ "Keep the area dry",
313
+ "See a doctor for: genital warts, facial warts, or painful warts",
314
+ "See a doctor if: warts multiply rapidly or don't respond to OTC treatment",
315
+ "Professional options: cryotherapy, laser, or prescription treatments"
316
+ ],
317
+ "risk_factors": [
318
+ "Weakened immune system",
319
+ "Broken skin",
320
+ "Walking barefoot in public areas",
321
+ "Close contact with infected person"
322
+ ],
323
+ "hospital_search_query": "dermatologist wart removal near me",
324
+ "hospital_type": "Dermatology Clinic / General Practitioner"
325
+ },
326
+
327
+ "Vitiligo": {
328
+ "severity": "LOW",
329
+ "tier": "DISEASE",
330
+ "emoji": "🔵",
331
+ "tagline": "Vitiligo (Pigmentation Disorder) Detected",
332
+ "action": "NOT DANGEROUS — TREATMENT AVAILABLE",
333
+ "description": "This appears to be vitiligo — a condition where the skin loses its pigment cells (melanocytes), resulting in white patches. Vitiligo is not contagious, not painful, and not medically dangerous, but it can have significant psychological impact.",
334
+ "urgency_message": "Not medically urgent. Vitiligo is not dangerous but can affect quality of life. See a dermatologist to discuss treatment options.",
335
+ "care_advice": [
336
+ "Vitiligo is NOT contagious and NOT dangerous",
337
+ "Protect depigmented areas from sunburn — use SPF 50+ sunscreen",
338
+ "Depigmented skin burns very easily",
339
+ "See a dermatologist for treatment options if desired",
340
+ "Treatment options include: topical corticosteroids, calcineurin inhibitors",
341
+ "Phototherapy (light therapy) can help repigmentation",
342
+ "Cosmetic options: camouflage makeup, self-tanners",
343
+ "Join a support group — psychological support is important",
344
+ "Manage stress — can worsen vitiligo",
345
+ "Eat a balanced diet rich in antioxidants",
346
+ "Some patients benefit from vitamin B12 and folic acid (consult doctor)"
347
+ ],
348
+ "risk_factors": [
349
+ "Family history of vitiligo",
350
+ "Autoimmune conditions (thyroid disease, type 1 diabetes)",
351
+ "Stress",
352
+ "Skin trauma"
353
+ ],
354
+ "hospital_search_query": "dermatologist vitiligo treatment near me",
355
+ "hospital_type": "Dermatology Clinic"
356
+ }
357
+ }
frontend/app.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =================================================================
3
+ DERMASCAN-AI — Professional Medical UI
4
+ Production Grade Healthcare Interface
5
+ =================================================================
6
+ """
7
+
8
+ import json
9
+ import streamlit as st
10
+ import requests
11
+ from PIL import Image
12
+ from pathlib import Path
13
+
14
+ # Import components
15
+ from components.header import render_header
16
+ from components.sidebar import render_sidebar
17
+ from components.result_card import render_severity_banner, render_metrics, TIER_ICONS
18
+ from components.confidence_chart import render_confidence_chart
19
+ from components.care_advice_card import render_care_advice
20
+ from components.hospital_map import render_hospital_map
21
+
22
+ # ═══════════════════════════════════════════════════════════
23
+ # PAGE CONFIG
24
+ # ═══════════════════════════════════════════════════════════
25
+ st.set_page_config(
26
+ page_title="DermaScan AI | Advanced Dermatology Analysis",
27
+ page_icon="🏥",
28
+ layout="wide",
29
+ initial_sidebar_state="expanded",
30
+ )
31
+
32
+ API_URL = "http://localhost:8000"
33
+
34
+ # ═══════════════════════════════════════════════════════════
35
+ # LOAD EXTERNAL DATA & STYLES
36
+ # ═══════════════════════════════════════════════════════════
37
+ config_dir = Path(__file__).parent.parent / "configs"
38
+
39
+ with open(config_dir / "india_cities.json", "r", encoding="utf-8") as f:
40
+ STATE_CITIES = json.load(f)
41
+
42
+ # Load CSS
43
+ css_file = Path(__file__).parent / "assets" / "style.css"
44
+ with open(css_file, "r", encoding="utf-8") as f:
45
+ st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
46
+
47
+ # Remove tooltips with JavaScript
48
+ st.markdown("""
49
+ <script>
50
+ // Remove all title attributes that cause tooltips
51
+ document.addEventListener('DOMContentLoaded', function() {
52
+ function removeTooltips() {
53
+ const elements = document.querySelectorAll('[title]');
54
+ elements.forEach(el => {
55
+ if (el.getAttribute('title') === 'keyboard_double') {
56
+ el.removeAttribute('title');
57
+ }
58
+ });
59
+ }
60
+
61
+ // Run immediately
62
+ removeTooltips();
63
+
64
+ // Run periodically to catch dynamically added elements
65
+ setInterval(removeTooltips, 500);
66
+
67
+ // Also run on mutations
68
+ const observer = new MutationObserver(removeTooltips);
69
+ observer.observe(document.body, {
70
+ childList: true,
71
+ subtree: true,
72
+ attributes: true,
73
+ attributeFilter: ['title']
74
+ });
75
+ });
76
+ </script>
77
+ """, unsafe_allow_html=True)
78
+
79
+ # ═══════════════════════════════════════════════════════════
80
+ # SIDEBAR
81
+ # ═══════════════════════════════════════════════════════════
82
+ selected_state, selected_city = render_sidebar(STATE_CITIES)
83
+
84
+ # ═══════════════════════════════════════════════════════════
85
+ # HEADER
86
+ # ═══════════════════════════════════════════════════════════
87
+ render_header()
88
+
89
+ # ═══════════════════════════════════════════════════════════
90
+ # UPLOAD SECTION
91
+ # ═══════════════════════════════════════════════════════════
92
+ uploaded_file = st.file_uploader(
93
+ "Upload a skin image for analysis",
94
+ type=["jpg", "jpeg", "png"],
95
+ )
96
+
97
+ if uploaded_file:
98
+ img_col, action_col = st.columns([1, 2])
99
+
100
+ with img_col:
101
+ image = Image.open(uploaded_file)
102
+ st.markdown('<div class="image-container">', unsafe_allow_html=True)
103
+ st.image(image, caption="📸 Uploaded Image", width="stretch")
104
+ st.markdown('</div>', unsafe_allow_html=True)
105
+
106
+ with action_col:
107
+ st.markdown(f"**📍 Location:** {selected_city}, {selected_state}")
108
+ st.markdown("")
109
+
110
+ analyze = st.button("🔬 Analyze Image", width="stretch")
111
+
112
+ if analyze:
113
+ with st.spinner("🔄 Analyzing your image with AI..."):
114
+ try:
115
+ files = {
116
+ "file": (
117
+ uploaded_file.name,
118
+ uploaded_file.getvalue(),
119
+ uploaded_file.type,
120
+ )
121
+ }
122
+ params = {"city": selected_city, "state": selected_state}
123
+
124
+ resp = requests.post(
125
+ f"{API_URL}/predict",
126
+ files=files,
127
+ params=params,
128
+ timeout=60,
129
+ )
130
+
131
+ if resp.status_code == 200:
132
+ st.session_state["result"] = resp.json()
133
+ st.success("✅ Analysis complete!")
134
+ st.rerun()
135
+ else:
136
+ st.error(f"❌ Server error: {resp.text}")
137
+
138
+ except requests.exceptions.ConnectionError:
139
+ st.error(
140
+ "⚠️ Cannot connect to API server. "
141
+ "Open another terminal and run: `python -m api.app`"
142
+ )
143
+
144
+ st.markdown("""
145
+ <div class="pro-card">
146
+ <h3>💡 Tips for Best Results</h3>
147
+ <p>
148
+ ✓ Use a clear, well-lit close-up photo<br>
149
+ ✓ Center the affected area in the frame<br>
150
+ ✓ Keep camera 10-15 cm from the skin<br>
151
+ ✓ Avoid shadows and reflections<br>
152
+ ✓ Use natural lighting when possible
153
+ </p>
154
+ </div>
155
+ """, unsafe_allow_html=True)
156
+
157
+
158
+ # ═══════════════════════════════════════════════════════════
159
+ # RESULTS SECTION
160
+ # ═══════════════════════════════════════════════════════════
161
+ if "result" in st.session_state:
162
+ result = st.session_state["result"]
163
+
164
+ st.markdown("---")
165
+
166
+ # Severity Banner
167
+ render_severity_banner(result)
168
+
169
+ # Key Metrics
170
+ render_metrics(result)
171
+
172
+ # Cancer Warning
173
+ cancer_warning = result.get("cancer_warning", "")
174
+ if cancer_warning:
175
+ st.markdown(
176
+ f'<div class="warning-box">'
177
+ f'<div class="warning-box-icon">⚠️</div>'
178
+ f"<div><strong>MEDICAL ALERT:</strong> {cancer_warning}</div>"
179
+ f"</div>",
180
+ unsafe_allow_html=True,
181
+ )
182
+
183
+ # Tabs
184
+ tab1, tab2, tab3, tab4 = st.tabs(
185
+ ["📋 Diagnosis", "📊 Confidence Analysis", "💊 Care Advice", "🏥 Find Hospitals"]
186
+ )
187
+
188
+ # Tab 1: Diagnosis
189
+ with tab1:
190
+ st.markdown(
191
+ f'<div class="pro-card">'
192
+ f'<h3>🔬 {result["predicted_class"]}</h3>'
193
+ f'<p>{result.get("description", "")}</p>'
194
+ f'<p style="margin-top:1rem;padding:0.8rem;background:#334155;border-radius:8px;">'
195
+ f'<strong>⏰ {result.get("urgency_message", "")}</strong></p>'
196
+ f"</div>",
197
+ unsafe_allow_html=True,
198
+ )
199
+
200
+ st.markdown(
201
+ f'<div class="pro-card">'
202
+ f"<h3>🎯 AI Confidence Assessment</h3>"
203
+ f'<p>{result.get("confidence_message", "")}</p>'
204
+ f"</div>",
205
+ unsafe_allow_html=True,
206
+ )
207
+
208
+ diff = result.get("differential_diagnosis", [])
209
+ if len(diff) > 1:
210
+ diff_html = '<div class="pro-card"><h3>🔍 Differential Diagnosis</h3>'
211
+ diff_html += '<p style="margin-bottom:1rem;color:#cbd5e1;">Other possible conditions to consider:</p>'
212
+ for d in diff:
213
+ d_icon = TIER_ICONS.get(d.get("tier", ""), "⚪")
214
+ d_prob = d.get("probability", 0)
215
+ diff_html += (
216
+ f'<div class="info-item">'
217
+ f'<div class="info-item-icon">{d_icon}</div>'
218
+ f'<div><strong>{d["class_name"]}</strong> — Probability: {d_prob:.1%}</div>'
219
+ f"</div>"
220
+ )
221
+ diff_html += "</div>"
222
+ st.markdown(diff_html, unsafe_allow_html=True)
223
+
224
+ # Tab 2: Confidence Chart
225
+ with tab2:
226
+ render_confidence_chart(result)
227
+
228
+ # Tab 3: Care Advice
229
+ with tab3:
230
+ render_care_advice(result)
231
+
232
+ # Tab 4: Hospitals
233
+ with tab4:
234
+ render_hospital_map(result, selected_city, selected_state)
235
+
236
+ # Disclaimer
237
+ disclaimer = result.get(
238
+ "disclaimer",
239
+ "This is an AI tool for educational purposes only. "
240
+ "Not a substitute for professional medical diagnosis.",
241
+ )
242
+ st.markdown(
243
+ f'<div class="disclaimer-box">'
244
+ f"<strong>⚕️ MEDICAL DISCLAIMER:</strong> {disclaimer}"
245
+ f"</div>",
246
+ unsafe_allow_html=True,
247
+ )
248
+
249
+ inf_t = result.get("inference_time", 0)
250
+ st.markdown(
251
+ f'<p style="text-align:center;color:#94a3b8;font-size:0.85rem;margin-top:1.5rem;">'
252
+ f'⚡ Analysis completed in {inf_t:.2f}s | 🧠 EfficientNet-B3 | 🏥 DermaScan AI v1.0'
253
+ f'</p>',
254
+ unsafe_allow_html=True
255
+ )
256
+
257
+ elif not uploaded_file:
258
+ st.markdown(
259
+ '<div class="upload-placeholder">'
260
+ '<div class="icon">📸</div>'
261
+ "<h3>Upload a Skin Image to Begin Analysis</h3>"
262
+ "<p>Our advanced AI system will analyze the image, identify potential conditions, "
263
+ "provide personalized care recommendations, and help you locate nearby medical facilities.</p>"
264
+ "</div>",
265
+ unsafe_allow_html=True,
266
+ )
267
+
268
+ # ═══════════════════════════════════════════════════════════
269
+ # FOOTER
270
+ # ═══════════════════════════════════════════════════════════
271
+ st.markdown("---")
272
+ st.markdown(
273
+ "<p style='text-align:center;color:#94a3b8;font-size:0.8rem;line-height:1.8;'>"
274
+ "🏥 <strong>DermaScan AI</strong> | 🧠 EfficientNet-B3 Architecture | 📊 HAM10000 + DermNet Dataset<br>"
275
+ "🔬 13 Skin Conditions | 🎯 96% AUC-ROC Accuracy | ⚡ Real-time Analysis<br>"
276
+ "🛠️ Built with PyTorch • FastAPI • Streamlit<br>"
277
+ "<em>For educational and research purposes only. Not a substitute for professional medical advice.</em>"
278
+ "</p>",
279
+ unsafe_allow_html=True,
280
+ )
frontend/assets/style.css ADDED
@@ -0,0 +1,624 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ═══════════════════════════════════════════════════════════
2
+ DERMASCAN AI - PROFESSIONAL MEDICAL UI STYLES
3
+ Dark Mode Compatible - Production Grade
4
+ ═══════════════════════════════════════════════════════════ */
5
+
6
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Poppins:wght@400;500;600;700;800&display=swap');
7
+
8
+ /* ═══════════════════════════════════════════════════════════
9
+ CSS VARIABLES - DARK MODE THEME
10
+ ═══════════════════════════════════════════════════════════ */
11
+ :root {
12
+ --primary: #3b82f6;
13
+ --primary-dark: #2563eb;
14
+ --primary-light: #60a5fa;
15
+ --secondary: #10b981;
16
+ --bg-main: #0f172a;
17
+ --bg-card: #1e293b;
18
+ --bg-elevated: #334155;
19
+ --border: #334155;
20
+ --border-light: #475569;
21
+ --text-primary: #f1f5f9;
22
+ --text-secondary: #cbd5e1;
23
+ --text-muted: #94a3b8;
24
+ --shadow-sm: 0 1px 2px 0 rgba(0,0,0,0.3);
25
+ --shadow: 0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -1px rgba(0,0,0,0.3);
26
+ --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -2px rgba(0,0,0,0.4);
27
+ --shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.6), 0 10px 10px -5px rgba(0,0,0,0.5);
28
+ --red: #ef4444;
29
+ --red-light: #7f1d1d;
30
+ --orange: #f97316;
31
+ --orange-light: #7c2d12;
32
+ --yellow: #f59e0b;
33
+ --yellow-light: #78350f;
34
+ --green: #10b981;
35
+ --green-light: #064e3b;
36
+ --blue: #3b82f6;
37
+ --blue-light: #1e3a8a;
38
+ }
39
+
40
+ /* ═══════════════════════════════════════════════════════════
41
+ GLOBAL STYLES
42
+ ═══════════════════════════════════════════════════════════ */
43
+ body, p, h1, h2, h3, h4, h5, h6, div, span, label, button {
44
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
45
+ color: var(--text-primary) !important;
46
+ }
47
+
48
+ h1, h2, h3 {
49
+ font-family: 'Poppins', 'Inter', sans-serif !important;
50
+ }
51
+
52
+ #MainMenu, footer, header { visibility: hidden; }
53
+
54
+ .block-container {
55
+ padding-top: 2rem !important;
56
+ padding-bottom: 3rem !important;
57
+ max-width: 1200px !important;
58
+ }
59
+
60
+ /* ═══════════════════════════════════════════════════════════
61
+ HEADER
62
+ ═══════════════════════════════════════════════════════════ */
63
+ .medical-header {
64
+ background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #3b82f6 100%);
65
+ border-radius: 20px;
66
+ padding: 2.5rem 2rem;
67
+ text-align: center;
68
+ margin-bottom: 2rem;
69
+ box-shadow: var(--shadow-xl);
70
+ position: relative;
71
+ overflow: hidden;
72
+ border: 1px solid rgba(59, 130, 246, 0.3);
73
+ }
74
+
75
+ .medical-header::before {
76
+ content: '';
77
+ position: absolute;
78
+ top: 0;
79
+ left: 0;
80
+ right: 0;
81
+ bottom: 0;
82
+ background: url('data:image/svg+xml,<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><defs><pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
83
+ opacity: 0.3;
84
+ }
85
+
86
+ .medical-header-content {
87
+ position: relative;
88
+ z-index: 1;
89
+ }
90
+
91
+ .medical-header h1 {
92
+ color: white !important;
93
+ font-size: 2.5rem;
94
+ font-weight: 800;
95
+ margin: 0;
96
+ letter-spacing: -0.5px;
97
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
98
+ }
99
+
100
+ .medical-header .subtitle {
101
+ color: rgba(255,255,255,0.95) !important;
102
+ margin: 0.8rem 0 1.2rem;
103
+ font-size: 1.1rem;
104
+ font-weight: 500;
105
+ }
106
+
107
+ .medical-header .badges {
108
+ display: flex;
109
+ gap: 0.6rem;
110
+ justify-content: center;
111
+ flex-wrap: wrap;
112
+ margin-top: 1rem;
113
+ }
114
+
115
+ .badge {
116
+ background: rgba(255,255,255,0.15);
117
+ backdrop-filter: blur(10px);
118
+ border: 1px solid rgba(255,255,255,0.25);
119
+ color: white !important;
120
+ padding: 0.4rem 1rem;
121
+ border-radius: 50px;
122
+ font-size: 0.85rem;
123
+ font-weight: 600;
124
+ display: inline-flex;
125
+ align-items: center;
126
+ gap: 0.4rem;
127
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
128
+ }
129
+
130
+ /* ══════════════════════════════════════════��════════════════
131
+ CARDS
132
+ ═══════════════════════════════════════════════════════════ */
133
+ .pro-card {
134
+ background: var(--bg-card);
135
+ border: 1px solid var(--border);
136
+ border-radius: 16px;
137
+ padding: 1.5rem;
138
+ margin-bottom: 1rem;
139
+ box-shadow: var(--shadow);
140
+ transition: all 0.3s ease;
141
+ }
142
+
143
+ .pro-card:hover {
144
+ box-shadow: var(--shadow-lg);
145
+ transform: translateY(-2px);
146
+ border-color: var(--border-light);
147
+ }
148
+
149
+ .pro-card h3 {
150
+ color: var(--text-primary) !important;
151
+ margin: 0 0 0.8rem;
152
+ font-size: 1.2rem;
153
+ font-weight: 700;
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 0.5rem;
157
+ }
158
+
159
+ .pro-card p {
160
+ color: var(--text-secondary) !important;
161
+ margin: 0;
162
+ line-height: 1.7;
163
+ font-size: 0.95rem;
164
+ }
165
+
166
+ /* ═══════════════════════════════════════════════════════════
167
+ SEVERITY BANNERS
168
+ ═══════════════════════════════════════════════════════════ */
169
+ .severity-banner {
170
+ border-radius: 16px;
171
+ padding: 1.8rem 2rem;
172
+ margin-bottom: 1.5rem;
173
+ box-shadow: var(--shadow-lg);
174
+ border-left: 6px solid;
175
+ position: relative;
176
+ overflow: hidden;
177
+ }
178
+
179
+ .severity-banner h2 {
180
+ margin: 0;
181
+ font-size: 1.6rem;
182
+ font-weight: 800;
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 0.6rem;
186
+ }
187
+
188
+ .severity-banner h3 {
189
+ margin: 0.6rem 0 0;
190
+ font-size: 1.05rem;
191
+ font-weight: 500;
192
+ }
193
+
194
+ .banner-critical {
195
+ background: linear-gradient(135deg, var(--red-light) 0%, #991b1b 100%);
196
+ border-left-color: var(--red);
197
+ }
198
+ .banner-critical h2, .banner-critical h3 { color: #fca5a5 !important; }
199
+
200
+ .banner-high {
201
+ background: linear-gradient(135deg, var(--orange-light) 0%, #9a3412 100%);
202
+ border-left-color: var(--orange);
203
+ }
204
+ .banner-high h2, .banner-high h3 { color: #fdba74 !important; }
205
+
206
+ .banner-medium {
207
+ background: linear-gradient(135deg, var(--yellow-light) 0%, #92400e 100%);
208
+ border-left-color: var(--yellow);
209
+ }
210
+ .banner-medium h2, .banner-medium h3 { color: #fcd34d !important; }
211
+
212
+ .banner-low {
213
+ background: linear-gradient(135deg, var(--green-light) 0%, #065f46 100%);
214
+ border-left-color: var(--green);
215
+ }
216
+ .banner-low h2, .banner-low h3 { color: #86efac !important; }
217
+
218
+ /* ═══════════════════════════════════════════════════════════
219
+ METRICS
220
+ ═══════════════════════════════════════════════════════════ */
221
+ .metric-card {
222
+ background: var(--bg-card);
223
+ border: 2px solid var(--border);
224
+ border-radius: 16px;
225
+ padding: 1.5rem;
226
+ text-align: center;
227
+ min-height: 140px;
228
+ box-shadow: var(--shadow);
229
+ transition: all 0.3s ease;
230
+ display: flex;
231
+ flex-direction: column;
232
+ justify-content: center;
233
+ }
234
+
235
+ .metric-card:hover {
236
+ box-shadow: var(--shadow-lg);
237
+ transform: translateY(-4px);
238
+ border-color: var(--primary);
239
+ }
240
+
241
+ .metric-card .label {
242
+ color: var(--text-muted) !important;
243
+ font-size: 0.75rem;
244
+ text-transform: uppercase;
245
+ letter-spacing: 1.5px;
246
+ font-weight: 700;
247
+ margin-bottom: 0.5rem;
248
+ }
249
+
250
+ .metric-card .value {
251
+ font-size: 2rem;
252
+ font-weight: 800;
253
+ margin: 0.5rem 0;
254
+ font-family: 'Poppins', sans-serif;
255
+ }
256
+
257
+ .metric-card .sublabel {
258
+ color: var(--text-muted) !important;
259
+ font-size: 0.8rem;
260
+ font-weight: 500;
261
+ margin-top: 0.3rem;
262
+ }
263
+
264
+ /* ═══════════════════════════════════════════════════════════
265
+ INFO ITEMS
266
+ ═══════════════════════════════════════════════════════════ */
267
+ .info-item {
268
+ background: var(--bg-elevated);
269
+ border: 1px solid var(--border);
270
+ border-radius: 12px;
271
+ padding: 1rem 1.2rem;
272
+ margin-bottom: 0.6rem;
273
+ color: var(--text-secondary) !important;
274
+ font-size: 0.92rem;
275
+ line-height: 1.6;
276
+ display: flex;
277
+ align-items: flex-start;
278
+ gap: 0.8rem;
279
+ transition: all 0.2s ease;
280
+ }
281
+
282
+ .info-item:hover {
283
+ background: var(--bg-card);
284
+ box-shadow: var(--shadow-sm);
285
+ transform: translateX(4px);
286
+ border-color: var(--border-light);
287
+ }
288
+
289
+ .info-item-icon {
290
+ font-size: 1.2rem;
291
+ flex-shrink: 0;
292
+ margin-top: 0.1rem;
293
+ }
294
+
295
+ /* ══════════════════════════════════════════���════════════════
296
+ WARNING & DISCLAIMER
297
+ ═══════════════════════════════════════════════════════════ */
298
+ .warning-box {
299
+ background: linear-gradient(135deg, var(--red-light) 0%, #991b1b 100%);
300
+ border: 2px solid var(--red);
301
+ border-radius: 12px;
302
+ padding: 1.2rem 1.5rem;
303
+ color: #fca5a5 !important;
304
+ font-size: 0.95rem;
305
+ margin: 1rem 0;
306
+ box-shadow: var(--shadow);
307
+ display: flex;
308
+ align-items: flex-start;
309
+ gap: 1rem;
310
+ }
311
+
312
+ .warning-box-icon {
313
+ font-size: 1.5rem;
314
+ flex-shrink: 0;
315
+ }
316
+
317
+ .disclaimer-box {
318
+ background: var(--yellow-light);
319
+ border: 2px solid var(--yellow);
320
+ border-radius: 12px;
321
+ padding: 1rem 1.5rem;
322
+ color: #fcd34d !important;
323
+ font-size: 0.85rem;
324
+ line-height: 1.6;
325
+ margin: 1.5rem 0;
326
+ box-shadow: var(--shadow-sm);
327
+ }
328
+
329
+ /* ═══════════════════════════════════════════════════════════
330
+ EMERGENCY CARD
331
+ ═══════════════════════════════════════════════════════════ */
332
+ .emergency-card {
333
+ background: linear-gradient(135deg, var(--red-light) 0%, #991b1b 100%);
334
+ border: 2px solid var(--red);
335
+ border-radius: 12px;
336
+ padding: 1.2rem;
337
+ margin-top: 1rem;
338
+ box-shadow: var(--shadow);
339
+ }
340
+
341
+ .emergency-card h4 {
342
+ color: #fca5a5 !important;
343
+ margin: 0 0 0.8rem;
344
+ font-size: 1rem;
345
+ font-weight: 700;
346
+ display: flex;
347
+ align-items: center;
348
+ gap: 0.5rem;
349
+ }
350
+
351
+ .emergency-card p {
352
+ color: #fecaca !important;
353
+ margin: 0.4rem 0;
354
+ font-size: 0.9rem;
355
+ font-weight: 500;
356
+ }
357
+
358
+ /* ═══════════════════════════════════════════════════════════
359
+ PLACEHOLDER
360
+ ═══════════════════════════════════════════════════════════ */
361
+ .upload-placeholder {
362
+ background: var(--bg-card);
363
+ border: 3px dashed var(--border);
364
+ border-radius: 20px;
365
+ padding: 4rem 2rem;
366
+ text-align: center;
367
+ margin-top: 2rem;
368
+ transition: all 0.3s ease;
369
+ }
370
+
371
+ .upload-placeholder:hover {
372
+ border-color: var(--primary);
373
+ background: var(--bg-elevated);
374
+ box-shadow: var(--shadow-lg);
375
+ }
376
+
377
+ .upload-placeholder .icon {
378
+ font-size: 4rem;
379
+ margin-bottom: 1rem;
380
+ opacity: 0.6;
381
+ }
382
+
383
+ .upload-placeholder h3 {
384
+ color: var(--text-primary) !important;
385
+ font-weight: 600;
386
+ margin-top: 1rem;
387
+ font-size: 1.3rem;
388
+ }
389
+
390
+ .upload-placeholder p {
391
+ color: var(--text-muted) !important;
392
+ font-size: 0.95rem;
393
+ margin-top: 0.8rem;
394
+ max-width: 500px;
395
+ margin-left: auto;
396
+ margin-right: auto;
397
+ line-height: 1.6;
398
+ }
399
+
400
+ /* ═══════════════════════════════════════════════════════════
401
+ BUTTONS
402
+ ═══════════════════════════════════════════════════════════ */
403
+ .stButton > button {
404
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%) !important;
405
+ color: white !important;
406
+ border: none !important;
407
+ padding: 0.8rem 2rem !important;
408
+ border-radius: 12px !important;
409
+ font-size: 1rem !important;
410
+ font-weight: 600 !important;
411
+ box-shadow: var(--shadow) !important;
412
+ transition: all 0.3s ease !important;
413
+ }
414
+
415
+ .stButton > button:hover {
416
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%) !important;
417
+ box-shadow: var(--shadow-lg) !important;
418
+ transform: translateY(-2px) !important;
419
+ }
420
+
421
+ .stLinkButton > a {
422
+ background: linear-gradient(135deg, var(--secondary) 0%, #059669 100%) !important;
423
+ color: white !important;
424
+ border: none !important;
425
+ border-radius: 12px !important;
426
+ font-weight: 600 !important;
427
+ text-decoration: none !important;
428
+ padding: 0.8rem 2rem !important;
429
+ box-shadow: var(--shadow) !important;
430
+ transition: all 0.3s ease !important;
431
+ display: inline-block !important;
432
+ }
433
+
434
+ .stLinkButton > a:hover {
435
+ background: linear-gradient(135deg, #059669 0%, var(--secondary) 100%) !important;
436
+ box-shadow: var(--shadow-lg) !important;
437
+ transform: translateY(-2px) !important;
438
+ }
439
+
440
+ /* ═══════════════════════════════════════════════════════════
441
+ TABS
442
+ ═════════════════════════════���═════════════════════════════ */
443
+ .stTabs [data-baseweb="tab-list"] {
444
+ gap: 8px;
445
+ background: var(--bg-elevated);
446
+ padding: 0.5rem;
447
+ border-radius: 12px;
448
+ }
449
+
450
+ .stTabs [data-baseweb="tab"] {
451
+ background: transparent !important;
452
+ border: none !important;
453
+ border-radius: 8px !important;
454
+ color: var(--text-muted) !important;
455
+ font-size: 0.9rem !important;
456
+ font-weight: 600 !important;
457
+ padding: 0.6rem 1.2rem !important;
458
+ transition: all 0.2s ease !important;
459
+ }
460
+
461
+ .stTabs [data-baseweb="tab"]:hover {
462
+ background: var(--bg-card) !important;
463
+ color: var(--primary) !important;
464
+ }
465
+
466
+ .stTabs [aria-selected="true"] {
467
+ background: var(--primary) !important;
468
+ color: white !important;
469
+ box-shadow: var(--shadow-sm) !important;
470
+ }
471
+
472
+ /* ═══════════════════════════════════════════════════════════
473
+ SIDEBAR
474
+ ═══════════════════════════════════════════════════════════ */
475
+ section[data-testid="stSidebar"] {
476
+ background: var(--bg-card) !important;
477
+ border-right: 1px solid var(--border) !important;
478
+ }
479
+
480
+ section[data-testid="stSidebar"] > div {
481
+ padding-top: 2rem !important;
482
+ }
483
+
484
+ section[data-testid="stSidebar"] h3 {
485
+ color: var(--text-primary) !important;
486
+ font-size: 0.9rem;
487
+ font-weight: 700;
488
+ text-transform: uppercase;
489
+ letter-spacing: 1px;
490
+ margin-bottom: 1rem;
491
+ padding: 0 0.5rem;
492
+ }
493
+
494
+ section[data-testid="stSidebar"] .stSelectbox label {
495
+ color: var(--text-primary) !important;
496
+ font-weight: 600 !important;
497
+ font-size: 0.85rem !important;
498
+ }
499
+
500
+ section[data-testid="stSidebar"] .stExpander {
501
+ background: var(--bg-elevated);
502
+ border: 1px solid var(--border);
503
+ border-radius: 10px;
504
+ margin-bottom: 0.5rem;
505
+ box-shadow: var(--shadow-sm);
506
+ }
507
+
508
+ section[data-testid="stSidebar"] .stExpander:hover {
509
+ box-shadow: var(--shadow);
510
+ border-color: var(--border-light);
511
+ }
512
+
513
+ /* Hide tooltips globally */
514
+ [data-testid="stTooltipIcon"] {
515
+ display: none !important;
516
+ visibility: hidden !important;
517
+ }
518
+
519
+ .stTooltipIcon {
520
+ display: none !important;
521
+ visibility: hidden !important;
522
+ }
523
+
524
+ /* Hide all title tooltips */
525
+ [title] {
526
+ position: relative !important;
527
+ }
528
+
529
+ [title]:hover::after,
530
+ [title]:hover::before {
531
+ display: none !important;
532
+ content: none !important;
533
+ }
534
+
535
+ /* Specifically target keyboard_double tooltip */
536
+ [title="keyboard_double"],
537
+ [aria-label="keyboard_double"],
538
+ button[title="keyboard_double"],
539
+ div[title="keyboard_double"],
540
+ span[title="keyboard_double"] {
541
+ pointer-events: auto !important;
542
+ }
543
+
544
+ [title="keyboard_double"]::after,
545
+ [title="keyboard_double"]::before {
546
+ display: none !important;
547
+ visibility: hidden !important;
548
+ content: none !important;
549
+ }
550
+
551
+ /* Hide Streamlit's built-in tooltips */
552
+ .stTooltip {
553
+ display: none !important;
554
+ }
555
+
556
+ div[data-baseweb="tooltip"] {
557
+ display: none !important;
558
+ }
559
+
560
+ /* Remove title attribute display */
561
+ button::after,
562
+ div::after,
563
+ span::after,
564
+ section::after {
565
+ content: none !important;
566
+ }
567
+
568
+ /* ═══════════════════════════════════════════════════════════
569
+ FILE UPLOADER
570
+ ═══════════════════════════════════════════════════════════ */
571
+ .stFileUploader {
572
+ background: var(--bg-card);
573
+ border: 2px dashed var(--border);
574
+ border-radius: 12px;
575
+ padding: 1.5rem;
576
+ transition: all 0.3s ease;
577
+ }
578
+
579
+ .stFileUploader:hover {
580
+ border-color: var(--primary);
581
+ background: var(--bg-elevated);
582
+ }
583
+
584
+ /* ═══════════════════════════════════════════════════════════
585
+ MAP & IMAGE CONTAINERS
586
+ ═══════════════════════════════════════════════════════════ */
587
+ .map-container {
588
+ border: 2px solid var(--border);
589
+ border-radius: 16px;
590
+ overflow: hidden;
591
+ box-shadow: var(--shadow-lg);
592
+ margin: 1rem 0;
593
+ }
594
+
595
+ .image-container {
596
+ border: 2px solid var(--border);
597
+ border-radius: 16px;
598
+ overflow: hidden;
599
+ box-shadow: var(--shadow-lg);
600
+ background: var(--bg-elevated);
601
+ }
602
+
603
+ /* ═══════════════════════════════════════════════════════════
604
+ CHART LEGEND
605
+ ══════════════════════════════════════════════��════════════ */
606
+ .chart-legend {
607
+ display: flex;
608
+ gap: 1.5rem;
609
+ justify-content: center;
610
+ flex-wrap: wrap;
611
+ font-size: 0.85rem;
612
+ margin-top: 1rem;
613
+ padding: 1rem;
614
+ background: var(--bg-elevated);
615
+ border-radius: 12px;
616
+ }
617
+
618
+ .legend-item {
619
+ display: flex;
620
+ align-items: center;
621
+ gap: 0.5rem;
622
+ font-weight: 600;
623
+ color: var(--text-secondary) !important;
624
+ }
frontend/components/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DermaScan AI - Frontend Components
3
+ Professional medical-grade UI components
4
+ """
5
+
6
+ from .header import render_header
7
+ from .sidebar import render_sidebar
8
+ from .result_card import render_severity_banner, render_metrics, TIER_ICONS
9
+ from .confidence_chart import render_confidence_chart
10
+ from .care_advice_card import render_care_advice
11
+ from .hospital_map import render_hospital_map
12
+
13
+ __all__ = [
14
+ 'render_header',
15
+ 'render_sidebar',
16
+ 'render_severity_banner',
17
+ 'render_metrics',
18
+ 'render_confidence_chart',
19
+ 'render_care_advice',
20
+ 'render_hospital_map',
21
+ 'TIER_ICONS',
22
+ ]
frontend/components/care_advice_card.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Care Advice Card Component for DermaScan AI
3
+ """
4
+ import streamlit as st
5
+
6
+
7
+ def render_care_advice(result):
8
+ """
9
+ Render care advice and risk factors
10
+
11
+ Args:
12
+ result: Dictionary containing prediction results
13
+ """
14
+ care = result.get("care_advice", [])
15
+ if care:
16
+ st.markdown('<div class="pro-card"><h3>💊 Recommended Care Steps</h3>', unsafe_allow_html=True)
17
+ for i, a in enumerate(care, 1):
18
+ icon = "✓" if not a.startswith(" ") else "→"
19
+ st.markdown(
20
+ f'<div class="info-item">'
21
+ f'<div class="info-item-icon">{icon}</div>'
22
+ f'<div>{a}</div>'
23
+ f"</div>",
24
+ unsafe_allow_html=True,
25
+ )
26
+ st.markdown('</div>', unsafe_allow_html=True)
27
+
28
+ risks = result.get("risk_factors", [])
29
+ if risks:
30
+ st.markdown("")
31
+ risk_html = '<div class="pro-card"><h3>⚠️ Risk Factors</h3>'
32
+ risk_html += '<p style="margin-bottom:1rem;color:#cbd5e1;">Factors that may increase risk:</p>'
33
+ for r in risks:
34
+ risk_html += f'<div class="info-item"><div class="info-item-icon">⚡</div><div>{r}</div></div>'
35
+ risk_html += "</div>"
36
+ st.markdown(risk_html, unsafe_allow_html=True)
frontend/components/confidence_chart.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Confidence Chart Component for DermaScan AI
3
+ """
4
+ import streamlit as st
5
+ import plotly.graph_objects as go
6
+
7
+
8
+ def render_confidence_chart(result):
9
+ """
10
+ Render the confidence analysis chart
11
+
12
+ Args:
13
+ result: Dictionary containing prediction results
14
+ """
15
+ probs = result.get("all_probabilities", {})
16
+ sorted_p = dict(sorted(probs.items(), key=lambda x: x[1], reverse=True))
17
+ names = list(sorted_p.keys())
18
+ vals = list(sorted_p.values())
19
+
20
+ cancer_set = {"Melanoma", "Basal Cell Carcinoma", "Actinic Keratoses"}
21
+ benign_set = {
22
+ "Melanocytic Nevi",
23
+ "Benign Keratosis",
24
+ "Dermatofibroma",
25
+ "Vascular Lesions",
26
+ }
27
+
28
+ colors = []
29
+ for n in names:
30
+ if n == result["predicted_class"]:
31
+ colors.append("#3b82f6")
32
+ elif n in cancer_set:
33
+ colors.append("#ef4444")
34
+ elif n in benign_set:
35
+ colors.append("#10b981")
36
+ else:
37
+ colors.append("#60a5fa")
38
+
39
+ fig = go.Figure(
40
+ go.Bar(
41
+ x=vals,
42
+ y=names,
43
+ orientation="h",
44
+ marker_color=colors,
45
+ text=[f"{v:.1%}" for v in vals],
46
+ textposition="outside",
47
+ textfont=dict(color="#f1f5f9", size=12, family="Inter"),
48
+ )
49
+ )
50
+
51
+ fig.update_layout(
52
+ height=450,
53
+ margin=dict(l=10, r=80, t=20, b=20),
54
+ xaxis=dict(
55
+ range=[0, min(1.0, max(vals) * 1.5)],
56
+ tickfont=dict(color="#94a3b8", family="Inter"),
57
+ gridcolor="#334155",
58
+ title=None,
59
+ showgrid=True,
60
+ ),
61
+ yaxis=dict(
62
+ autorange="reversed",
63
+ tickfont=dict(color="#f1f5f9", size=12, family="Inter"),
64
+ ),
65
+ plot_bgcolor="#1e293b",
66
+ paper_bgcolor="#0f172a",
67
+ font=dict(color="#f1f5f9", family="Inter"),
68
+ )
69
+
70
+ st.plotly_chart(fig, width="stretch")
71
+
72
+ st.markdown(
73
+ '<div class="chart-legend">'
74
+ '<span class="legend-item"><span style="color:#3b82f6;font-size:1.2rem;">●</span> Predicted</span>'
75
+ '<span class="legend-item"><span style="color:#ef4444;font-size:1.2rem;">●</span> Cancer</span>'
76
+ '<span class="legend-item"><span style="color:#10b981;font-size:1.2rem;">●</span> Benign</span>'
77
+ '<span class="legend-item"><span style="color:#60a5fa;font-size:1.2rem;">●</span> Disease</span>'
78
+ "</div>",
79
+ unsafe_allow_html=True,
80
+ )
frontend/components/header.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Header Component for DermaScan AI
3
+ """
4
+ import streamlit as st
5
+
6
+
7
+ def render_header():
8
+ """Render the professional medical header"""
9
+ st.markdown("""
10
+ <div class="medical-header">
11
+ <div class="medical-header-content">
12
+ <h1>🏥 DermaScan AI</h1>
13
+ <p class="subtitle">Advanced AI-Powered Dermatology Analysis System</p>
14
+ <div class="badges">
15
+ <span class="badge">🧠 EfficientNet-B3</span>
16
+ <span class="badge">📊 96% AUC-ROC</span>
17
+ <span class="badge">🔬 13 Conditions</span>
18
+ <span class="badge">🇮🇳 India Optimized</span>
19
+ <span class="badge">⚡ Real-time Analysis</span>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ """, unsafe_allow_html=True)
frontend/components/hospital_map.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hospital Map Component for DermaScan AI
3
+ """
4
+ import streamlit as st
5
+
6
+
7
+ def render_hospital_map(result, selected_city, selected_state):
8
+ """
9
+ Render the hospital finder with embedded Google Maps
10
+
11
+ Args:
12
+ result: Dictionary containing prediction results
13
+ selected_city: Selected city name
14
+ selected_state: Selected state name
15
+ """
16
+ hosp_type = result.get("hospital_type", "Dermatologist")
17
+ location = result.get("hospital_location", f"{selected_city}, {selected_state}")
18
+
19
+ st.markdown(
20
+ f'<div class="pro-card">'
21
+ f"<h3>🏥 Find {hosp_type}</h3>"
22
+ f"<p>📍 Searching in: <strong>{location}</strong></p>"
23
+ f"</div>",
24
+ unsafe_allow_html=True,
25
+ )
26
+
27
+ search_query = result.get("hospital_search_query", "dermatologist near me")
28
+ full_query = f"{search_query} in {selected_city}, {selected_state}, India"
29
+ maps_url = "https://www.google.com/maps/search/" + full_query.replace(" ", "+")
30
+
31
+ # Embed Google Maps
32
+ maps_embed_url = f"https://www.google.com/maps/embed/v1/search?key=AIzaSyBFw0Qbyq9zTFTd-tUY6dZWTgaQzuU17R8&q={full_query.replace(' ', '+')}"
33
+
34
+ st.markdown(
35
+ f'<div class="map-container">'
36
+ f'<iframe width="100%" height="450" style="border:0;" '
37
+ f'src="{maps_embed_url}" allowfullscreen loading="lazy"></iframe>'
38
+ f'</div>',
39
+ unsafe_allow_html=True,
40
+ )
41
+
42
+ st.link_button(
43
+ f"🗺️ Open in Google Maps - {hosp_type}",
44
+ maps_url,
45
+ width="stretch",
46
+ )
47
+
48
+ st.markdown("")
49
+
50
+ emergency = result.get("emergency_numbers", {})
51
+ if emergency:
52
+ emer_html = '<div class="emergency-card"><h4>🚨 Emergency Contacts</h4>'
53
+ for label, num in emergency.items():
54
+ emer_html += f"<p>📞 {label}: <b>{num}</b></p>"
55
+ emer_html += "</div>"
56
+ st.markdown(emer_html, unsafe_allow_html=True)
frontend/components/result_card.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Result Card Components for DermaScan AI
3
+ """
4
+ import streamlit as st
5
+
6
+ TIER_ICONS = {
7
+ "CANCER": "🔴",
8
+ "PRE-CANCER": "🟡",
9
+ "BENIGN": "🟢",
10
+ "DISEASE": "🔵",
11
+ }
12
+
13
+
14
+ def render_severity_banner(result):
15
+ """Render the severity banner"""
16
+ severity = result.get("severity", "LOW").lower()
17
+ tagline = result.get("tagline", "Analysis Complete")
18
+ action = result.get("action", "Consult a doctor")
19
+
20
+ severity_emoji = {
21
+ "critical": "🚨",
22
+ "high": "⚠️",
23
+ "medium": "⚡",
24
+ "low": "✅"
25
+ }.get(severity, "ℹ️")
26
+
27
+ st.markdown(
28
+ f'<div class="severity-banner banner-{severity}">'
29
+ f"<h2>{severity_emoji} {tagline}</h2>"
30
+ f"<h3>📋 {action}</h3>"
31
+ f"</div>",
32
+ unsafe_allow_html=True,
33
+ )
34
+
35
+
36
+ def render_metrics(result):
37
+ """Render the key metrics cards"""
38
+ c1, c2, c3 = st.columns(3)
39
+
40
+ conf = result["confidence"]
41
+ conf_color = "#10b981" if conf > 0.7 else "#f59e0b" if conf > 0.4 else "#ef4444"
42
+ conf_emoji = "🎯" if conf > 0.7 else "⚡" if conf > 0.4 else "⚠️"
43
+
44
+ tier = result.get("tier", "UNKNOWN")
45
+ tier_icon = TIER_ICONS.get(tier, "⚪")
46
+ tier_color = {
47
+ "CANCER": "#ef4444",
48
+ "PRE-CANCER": "#f59e0b",
49
+ "BENIGN": "#10b981",
50
+ "DISEASE": "#3b82f6",
51
+ }.get(tier, "#94a3b8")
52
+
53
+ with c1:
54
+ st.markdown(
55
+ f'<div class="metric-card">'
56
+ f'<div class="label">Confidence Score</div>'
57
+ f'<div class="value" style="color:{conf_color};">{conf_emoji} {conf:.1%}</div>'
58
+ f'<div class="sublabel">{result.get("confidence_level", "")}</div>'
59
+ f"</div>",
60
+ unsafe_allow_html=True,
61
+ )
62
+
63
+ with c2:
64
+ st.markdown(
65
+ f'<div class="metric-card">'
66
+ f'<div class="label">Classification</div>'
67
+ f'<div class="value" style="color:{tier_color};">{tier_icon} {tier}</div>'
68
+ f'<div class="sublabel">{result.get("severity", "")} Severity</div>'
69
+ f"</div>",
70
+ unsafe_allow_html=True,
71
+ )
72
+
73
+ with c3:
74
+ st.markdown(
75
+ f'<div class="metric-card">'
76
+ f'<div class="label">Diagnosis</div>'
77
+ f'<div class="value" style="font-size:1.3rem;color:#3b82f6;">🔬 {result["predicted_class"]}</div>'
78
+ f'<div class="sublabel">AI Prediction</div>'
79
+ f"</div>",
80
+ unsafe_allow_html=True,
81
+ )
frontend/components/sidebar.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Sidebar Component for DermaScan AI
3
+ """
4
+ import streamlit as st
5
+
6
+
7
+ def render_sidebar(state_cities):
8
+ """
9
+ Render the sidebar with location selection and information
10
+
11
+ Args:
12
+ state_cities: Dictionary of states and their cities
13
+
14
+ Returns:
15
+ tuple: (selected_state, selected_city)
16
+ """
17
+ with st.sidebar:
18
+ st.markdown("### 📍 Location")
19
+ selected_state = st.selectbox(
20
+ "State",
21
+ list(state_cities.keys()),
22
+ index=list(state_cities.keys()).index("Delhi")
23
+ )
24
+ cities = state_cities.get(selected_state, ["Other"])
25
+ selected_city = st.selectbox("City", cities, index=0)
26
+
27
+ st.markdown("---")
28
+ st.markdown("### 🔬 About DermaScan AI")
29
+ st.markdown("""
30
+ <div class="pro-card" style="font-size:0.85rem;">
31
+ <p style="margin-bottom:0.8rem;">
32
+ Advanced AI-powered dermatology analysis system using deep learning
33
+ to detect and classify skin conditions.
34
+ </p>
35
+ <p style="margin-bottom:0.5rem;"><strong>🧠 Technology</strong></p>
36
+ <p style="margin-bottom:0.8rem;color:#cbd5e1;">
37
+ • EfficientNet-B3 Architecture<br>
38
+ • 96% AUC-ROC Accuracy<br>
39
+ • Real-time Analysis
40
+ </p>
41
+ <p style="margin-bottom:0.5rem;"><strong>🔬 Detects 13 Conditions</strong></p>
42
+ <p style="margin-bottom:0.8rem;color:#cbd5e1;">
43
+ • 3 Cancer Types<br>
44
+ • 4 Benign Conditions<br>
45
+ • 6 Skin Diseases
46
+ </p>
47
+ <p style="margin-bottom:0.5rem;"><strong>📊 Training Data</strong></p>
48
+ <p style="color:#cbd5e1;">
49
+ • HAM10000 Dataset<br>
50
+ • DermNet Collection<br>
51
+ • 10,000+ Images
52
+ </p>
53
+ </div>
54
+ """, unsafe_allow_html=True)
55
+
56
+ st.markdown("---")
57
+ st.markdown("### 🚨 Emergency Contacts")
58
+ st.markdown("""
59
+ <div class="emergency-card">
60
+ <h4>🇮🇳 India Helplines</h4>
61
+ <p>🚨 Emergency: <b>112</b></p>
62
+ <p>🚑 Ambulance: <b>108</b></p>
63
+ <p>🏥 Health: <b>104</b></p>
64
+ <p>🎗️ Cancer: <b>1800-11-6006</b></p>
65
+ </div>
66
+ """, unsafe_allow_html=True)
67
+
68
+ st.markdown("---")
69
+ st.markdown("### ⚕️ Medical Disclaimer")
70
+ st.markdown("""
71
+ <div style="font-size:0.75rem;color:#94a3b8;line-height:1.5;padding:0.5rem;">
72
+ This AI tool is for educational and screening purposes only.
73
+ It is NOT a substitute for professional medical diagnosis.
74
+ Always consult a qualified dermatologist for proper evaluation.
75
+ </div>
76
+ """, unsafe_allow_html=True)
77
+
78
+ st.markdown("---")
79
+ st.markdown(
80
+ "<p style='text-align:center;color:#94a3b8;font-size:0.75rem;font-weight:600;'>"
81
+ "🏥 DermaScan AI v1.0<br>Medical Grade Analysis</p>",
82
+ unsafe_allow_html=True,
83
+ )
84
+
85
+ return selected_state, selected_city
requirements.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit>=1.35.0
2
+ fastapi>=0.110.0
3
+ uvicorn>=0.29.0
4
+ pydantic>=2.7.0
5
+ python-multipart>=0.0.9
6
+
7
+ torch>=2.2.0
8
+ torchvision>=0.17.0
9
+ timm>=0.9.16
10
+
11
+ albumentations>=1.4.8
12
+ opencv-python-headless>=4.9.0.80
13
+ Pillow>=10.3.0
14
+
15
+ numpy>=1.26.4
16
+ pandas>=2.2.2
17
+ scikit-learn>=1.4.2
18
+ plotly>=5.22.0
19
+
20
+ requests>=2.31.0
21
+ matplotlib>=3.8.4
22
+ seaborn>=0.13.2
src/inference/predictor.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =================================================================
3
+ PREDICTOR — Single Image Inference Pipeline
4
+ =================================================================
5
+ """
6
+ import pathlib
7
+ pathlib.PosixPath = pathlib.WindowsPath
8
+
9
+
10
+ import torch
11
+ import torch.nn.functional as F
12
+ import numpy as np
13
+ from PIL import Image
14
+ import albumentations as A
15
+ from albumentations.pytorch import ToTensorV2
16
+ from typing import Dict, Tuple
17
+ import json
18
+
19
+
20
+ class SkinPredictor:
21
+ """
22
+ Production inference pipeline.
23
+ Loads model once, predicts on any image.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ model_path: str = "checkpoints/best_model.pth",
29
+ class_config_path: str = "configs/class_config.json",
30
+ device: str = None,
31
+ img_size: int = 224,
32
+ ):
33
+ # Device
34
+ if device:
35
+ self.device = torch.device(device)
36
+ elif torch.cuda.is_available():
37
+ self.device = torch.device('cuda')
38
+ else:
39
+ self.device = torch.device('cpu')
40
+
41
+ # Load class config
42
+ with open(class_config_path, 'r') as f:
43
+ self.class_config = json.load(f)
44
+
45
+ self.num_classes = len(self.class_config)
46
+ self.class_names = [self.class_config[str(i)]['name'] for i in range(self.num_classes)]
47
+
48
+ # Build model
49
+ self.model = self._build_model()
50
+ self._load_weights(model_path)
51
+ self.model.eval()
52
+
53
+ # Transform
54
+ self.transform = A.Compose([
55
+ A.Resize(img_size, img_size),
56
+ A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
57
+ ToTensorV2(),
58
+ ])
59
+
60
+ print(f"✅ Predictor ready on {self.device}")
61
+
62
+ def _build_model(self):
63
+ """Build model architecture (must match training)."""
64
+ import timm
65
+ import torch.nn as nn
66
+
67
+ class DermaScanModel(nn.Module):
68
+ def __init__(self):
69
+ super().__init__()
70
+ self.backbone = timm.create_model(
71
+ 'efficientnet_b3', pretrained=False,
72
+ num_classes=0, drop_rate=0.0,
73
+ )
74
+ self.feature_dim = self.backbone.num_features
75
+ self.head = nn.Sequential(
76
+ nn.Linear(self.feature_dim, 512),
77
+ nn.BatchNorm1d(512),
78
+ nn.SiLU(inplace=True),
79
+ nn.Dropout(0.3),
80
+ nn.Linear(512, 128),
81
+ nn.BatchNorm1d(128),
82
+ nn.SiLU(inplace=True),
83
+ nn.Dropout(0.15),
84
+ nn.Linear(128, 13),
85
+ )
86
+
87
+ def forward(self, x):
88
+ return self.head(self.backbone(x))
89
+
90
+ return DermaScanModel().to(self.device)
91
+
92
+ def _load_weights(self, model_path: str):
93
+ """Load trained weights."""
94
+ checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
95
+
96
+ if 'model_state_dict' in checkpoint:
97
+ self.model.load_state_dict(checkpoint['model_state_dict'])
98
+ else:
99
+ self.model.load_state_dict(checkpoint)
100
+
101
+ print(f" Weights loaded from {model_path}")
102
+
103
+ @torch.no_grad()
104
+ def predict(self, image) -> Dict:
105
+ """
106
+ Predict on a single image.
107
+
108
+ Args:
109
+ image: PIL Image, numpy array, or file path
110
+
111
+ Returns:
112
+ Dictionary with prediction results
113
+ """
114
+ # Handle different input types
115
+ if isinstance(image, str):
116
+ image = Image.open(image).convert('RGB')
117
+ elif isinstance(image, Image.Image):
118
+ image = image.convert('RGB')
119
+
120
+ img_array = np.array(image)
121
+
122
+ # Transform
123
+ tensor = self.transform(image=img_array)['image'].unsqueeze(0)
124
+ tensor = tensor.to(self.device)
125
+
126
+ # Predict
127
+ logits = self.model(tensor)
128
+ probabilities = F.softmax(logits, dim=1)[0].cpu().numpy()
129
+
130
+ predicted_class = int(np.argmax(probabilities))
131
+ confidence = float(probabilities[predicted_class])
132
+
133
+ return {
134
+ "predicted_class": predicted_class,
135
+ "predicted_class_name": self.class_names[predicted_class],
136
+ "confidence": confidence,
137
+ "all_probabilities": probabilities,
138
+ }
src/response/hospital_finder.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =================================================================
3
+ HOSPITAL FINDER — India-Specific
4
+ =================================================================
5
+ """
6
+
7
+ INDIAN_STATES = [
8
+ "Andhra Pradesh", "Arunachal Pradesh", "Assam", "Bihar",
9
+ "Chhattisgarh", "Goa", "Gujarat", "Haryana", "Himachal Pradesh",
10
+ "Jharkhand", "Karnataka", "Kerala", "Madhya Pradesh",
11
+ "Maharashtra", "Manipur", "Meghalaya", "Mizoram", "Nagaland",
12
+ "Odisha", "Punjab", "Rajasthan", "Sikkim", "Tamil Nadu",
13
+ "Telangana", "Tripura", "Uttar Pradesh", "Uttarakhand",
14
+ "West Bengal", "Delhi", "Chandigarh", "Puducherry",
15
+ "Jammu and Kashmir", "Ladakh",
16
+ ]
17
+
18
+
19
+ class HospitalFinder:
20
+ """
21
+ Find nearby hospitals in India using Google Maps URL.
22
+ No API key needed — generates search URLs.
23
+ """
24
+
25
+ def __init__(self):
26
+ self.country = "India"
27
+
28
+ def search(self, query: str, city: str, state: str) -> dict:
29
+ """
30
+ Generate Google Maps search for hospitals in a specific city.
31
+
32
+ Args:
33
+ query: Search type (e.g., "skin cancer specialist")
34
+ city: City name
35
+ state: State name
36
+
37
+ Returns:
38
+ Dictionary with maps_url and search info
39
+ """
40
+ location = f"{city}, {state}, India"
41
+ full_query = f"{query} in {location}"
42
+ encoded = full_query.replace(" ", "+")
43
+
44
+ maps_url = f"https://www.google.com/maps/search/{encoded}"
45
+ embed_url = f"https://maps.google.com/maps?q={encoded}&z=13&output=embed"
46
+
47
+ return {
48
+ "maps_url": maps_url,
49
+ "embed_url": embed_url,
50
+ "query": full_query,
51
+ "city": city,
52
+ "state": state,
53
+ "location": location,
54
+ }
55
+
56
+ def get_emergency_numbers(self) -> dict:
57
+ """Indian emergency numbers."""
58
+ return {
59
+ "Emergency": "112",
60
+ "Ambulance": "108",
61
+ "Health Helpline": "104",
62
+ "AIIMS Delhi": "011-26588500",
63
+ "National Cancer Helpline": "1800-11-6006",
64
+ }
src/response/response_engine.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ =================================================================
3
+ RESPONSE ENGINE — The Brain of DermaScan-AI
4
+ =================================================================
5
+ Takes model prediction → generates complete clinical response:
6
+ - Diagnosis with confidence
7
+ - Severity tier (CANCER / PRE-CANCER / BENIGN / DISEASE)
8
+ - Care advice
9
+ - Hospital recommendation
10
+ - Urgency level
11
+ =================================================================
12
+ """
13
+
14
+ import json
15
+ from pathlib import Path
16
+ from typing import Dict, Any, Optional
17
+ import numpy as np
18
+
19
+
20
+ class ResponseEngine:
21
+ """
22
+ Generates structured clinical responses based on model predictions.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ class_config_path: str = "configs/class_config.json",
28
+ response_templates_path: str = "configs/response_templates.json",
29
+ ):
30
+ # Load class config
31
+ with open(class_config_path, 'r') as f:
32
+ self.class_config = json.load(f)
33
+
34
+ # Load response templates
35
+ with open(response_templates_path, 'r') as f:
36
+ self.templates = json.load(f)
37
+
38
+ self.num_classes = len(self.class_config)
39
+ self.class_names = [self.class_config[str(i)]['name'] for i in range(self.num_classes)]
40
+ self.class_tiers = [self.class_config[str(i)]['tier'] for i in range(self.num_classes)]
41
+
42
+ # Confidence thresholds
43
+ self.HIGH_CONFIDENCE = 0.7
44
+ self.MEDIUM_CONFIDENCE = 0.4
45
+ self.LOW_CONFIDENCE = 0.2
46
+
47
+ def generate_response(
48
+ self,
49
+ predicted_class: int,
50
+ confidence: float,
51
+ all_probabilities: np.ndarray,
52
+ ) -> Dict[str, Any]:
53
+ """
54
+ Generate a complete response for a prediction.
55
+
56
+ Args:
57
+ predicted_class: Index of predicted class
58
+ confidence: Confidence of top prediction (0-1)
59
+ all_probabilities: Array of probabilities for all 13 classes
60
+
61
+ Returns:
62
+ Complete response dictionary
63
+ """
64
+ class_name = self.class_names[predicted_class]
65
+ template = self.templates.get(class_name, {})
66
+ class_info = self.class_config[str(predicted_class)]
67
+
68
+ # ── Confidence assessment ──
69
+ if confidence >= self.HIGH_CONFIDENCE:
70
+ confidence_level = "HIGH"
71
+ confidence_message = "The AI model has high confidence in this assessment."
72
+ elif confidence >= self.MEDIUM_CONFIDENCE:
73
+ confidence_level = "MEDIUM"
74
+ confidence_message = "The AI model has moderate confidence. Consider getting a professional opinion."
75
+ else:
76
+ confidence_level = "LOW"
77
+ confidence_message = "The AI model has low confidence. This result should be verified by a medical professional."
78
+
79
+ # ── Check for close second prediction ──
80
+ sorted_indices = np.argsort(all_probabilities)[::-1]
81
+ second_class = sorted_indices[1]
82
+ second_prob = all_probabilities[second_class]
83
+ second_name = self.class_names[second_class]
84
+
85
+ close_call = (confidence - second_prob) < 0.15
86
+
87
+ # ── Build differential diagnosis ──
88
+ differential = []
89
+ for idx in sorted_indices[:3]:
90
+ if all_probabilities[idx] > 0.05:
91
+ differential.append({
92
+ "class_name": self.class_names[idx],
93
+ "probability": round(float(all_probabilities[idx]), 4),
94
+ "tier": self.class_tiers[idx],
95
+ })
96
+
97
+ # ── Is any cancer class in top 3? ──
98
+ cancer_alert = False
99
+ cancer_in_top3 = []
100
+ for d in differential:
101
+ if d['tier'] in ['CANCER', 'PRE-CANCER']:
102
+ cancer_in_top3.append(d)
103
+ if d['probability'] > 0.1:
104
+ cancer_alert = True
105
+
106
+ # ── Build response ──
107
+ response = {
108
+ # Core prediction
109
+ "predicted_class": class_name,
110
+ "predicted_class_idx": int(predicted_class),
111
+ "confidence": round(float(confidence), 4),
112
+ "confidence_level": confidence_level,
113
+ "confidence_message": confidence_message,
114
+
115
+ # Classification
116
+ "tier": class_info['tier'],
117
+ "severity": template.get('severity', class_info['severity']),
118
+ "emoji": template.get('emoji', '❓'),
119
+ "tagline": template.get('tagline', f'{class_name} Detected'),
120
+
121
+ # Action
122
+ "action": template.get('action', 'CONSULT A DOCTOR'),
123
+ "urgency_message": template.get('urgency_message', ''),
124
+ "description": template.get('description', ''),
125
+
126
+ # Care advice
127
+ "care_advice": template.get('care_advice', []),
128
+ "risk_factors": template.get('risk_factors', []),
129
+
130
+ # Hospital
131
+ "hospital_search_query": template.get('hospital_search_query', 'dermatologist near me'),
132
+ "hospital_type": template.get('hospital_type', 'Dermatology Clinic'),
133
+
134
+ # Differential diagnosis
135
+ "differential_diagnosis": differential,
136
+ "close_call": close_call,
137
+
138
+ # Cancer alert
139
+ "cancer_alert": cancer_alert,
140
+ "cancer_in_differential": cancer_in_top3,
141
+
142
+ # All probabilities (for chart)
143
+ "all_probabilities": {
144
+ self.class_names[i]: round(float(all_probabilities[i]), 4)
145
+ for i in range(self.num_classes)
146
+ },
147
+
148
+ # Disclaimer
149
+ "disclaimer": (
150
+ "⚠️ IMPORTANT: This is an AI-assisted analysis tool for educational purposes only. "
151
+ "It is NOT a substitute for professional medical diagnosis. The accuracy of AI predictions "
152
+ "can vary. Always consult a qualified dermatologist or healthcare professional for "
153
+ "proper diagnosis and treatment. If you notice any suspicious changes in your skin, "
154
+ "please seek medical attention promptly."
155
+ ),
156
+ }
157
+
158
+ # ── Override: If cancer probability is high, ALWAYS warn ──
159
+ if cancer_alert and class_info['tier'] not in ['CANCER', 'PRE-CANCER']:
160
+ highest_cancer = max(cancer_in_top3, key=lambda x: x['probability'])
161
+ response['cancer_warning'] = (
162
+ f"⚠️ Note: While the top prediction is {class_name}, "
163
+ f"the model also detected a {highest_cancer['probability']:.0%} probability of "
164
+ f"{highest_cancer['class_name']} ({highest_cancer['tier']}). "
165
+ f"We strongly recommend consulting a dermatologist to rule out any malignancy."
166
+ )
167
+
168
+ return response
169
+
170
+ def get_severity_color(self, severity: str) -> str:
171
+ """Return color hex code for severity level."""
172
+ colors = {
173
+ "CRITICAL": "#e74c3c",
174
+ "HIGH": "#e67e22",
175
+ "MEDIUM": "#f39c12",
176
+ "LOW": "#27ae60",
177
+ }
178
+ return colors.get(severity, "#95a5a6")
179
+
180
+ def get_tier_color(self, tier: str) -> str:
181
+ """Return color hex code for tier."""
182
+ colors = {
183
+ "CANCER": "#e74c3c",
184
+ "PRE-CANCER": "#f39c12",
185
+ "BENIGN": "#27ae60",
186
+ "DISEASE": "#3498db",
187
+ }
188
+ return colors.get(tier, "#95a5a6")