Tantawi65 commited on
Commit
22a70b4
·
0 Parent(s):

Deploy to Hugging Face Spaces - clean commit

Browse files
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pkl
5
+ *.h5
6
+ .env
.gitignore ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ venv/
9
+ env/
10
+
11
+ # Environment variables files
12
+ .env
13
+ *.env
14
+
15
+ # VSCode settings
16
+ .vscode/
17
+
18
+ # MacOS files
19
+ .DS_Store
20
+
21
+ # Logs
22
+ *.log
23
+
24
+ # Model files
25
+ app/model/efficientnetv2s.h5
26
+
27
+ # Jupyter Notebook checkpoints
28
+ .ipynb_checkpoints/
29
+
30
+ # Build / distribution folders
31
+ build/
32
+ dist/
33
+ *.egg-info/
34
+
35
+ # Pytest cache
36
+ .pytest_cache/
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM tensorflow/tensorflow:2.10.0-gpu
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY app/ ./app/
9
+
10
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Miguel Plana
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 ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SkinAI Diagnostics
3
+ emoji: 🔬
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ ---
10
+
11
+ # Skin Cancer Classifier API
12
+
13
+ SkinAI Diagnostics — a FastAPI-powered web application for skin cancer image classification using deep learning.
14
+
15
+ ---
16
+
17
+ ## Overview
18
+
19
+ This project delivers a professional web interface for classifying dermatoscopic images. It enables users to upload a skin lesion image and obtain a prediction with calibrated confidence scores, alongside links to technical details of the underlying model.
20
+
21
+ ---
22
+
23
+ ## Repository Structure
24
+
25
+ ```
26
+ skin-cancer-api/
27
+ ├── app/
28
+ │ ├── main.py # FastAPI application entry point
29
+ │ ├── predict.py # Image preprocessing and prediction logic
30
+ │ ├── model_loader.py # Loads the trained EfficientNetV2S model
31
+ │ ├── model/ # Local model storage (auto-downloaded if missing)
32
+ │ │ └── efficientnetv2s.h5 # Pretrained model file
33
+ ├── test_images/ # Sample images for testing the app
34
+ │ └── ...
35
+ ├── requirements.txt # Python dependencies
36
+ ├── Dockerfile # Docker setup for deployment
37
+ ├── .dockerignore # Docker ignore rules
38
+ └── README.md # Project documentation (this file)
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Features
44
+
45
+ - Seven-class classification of dermatoscopic images:
46
+ - Actinic Keratoses and Intraepithelial Carcinoma (AKIEC)
47
+ - Basal Cell Carcinoma (BCC)
48
+ - Benign Keratosis-like Lesions (BKL)
49
+ - Dermatofibroma (DF)
50
+ - Melanoma (MEL)
51
+ - Melanocytic Nevi (NV)
52
+ - Vascular Lesions (VASC)
53
+ - Professional web interface built with FastAPI and Jinja2
54
+ - Temperature Scaling (T-scaling) for calibrated probabilities
55
+ - Technical transparency: model architecture, training setup, and metrics
56
+ - Confidence visualization with a probability chart
57
+ - Sample images available in `test_images/`
58
+
59
+ ---
60
+
61
+ ## Model Details
62
+
63
+ - Base Model: EfficientNetV2S (20.5 million parameters)
64
+ - Dataset: [HAM10000](https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/DBW86T)
65
+ - Calibration: Temperature Scaling (optimal T=2.77)
66
+ - Performance:
67
+ - Accuracy: 0.88
68
+ - Macro F1-score: 0.80
69
+ - Expected Calibration Error (ECE): 0.022 (after T-scaling)
70
+
71
+ For full technical details, see the Model Technical Information section in the app.
72
+
73
+ ---
74
+
75
+ ## Model Download from Hugging Face
76
+
77
+ The model file `efficientnetv2s.h5` is hosted on [Hugging Face Hub](https://huggingface.co/Miguel764/efficientnetv2s-skin-cancer-classifier) and is automatically downloaded the first time the app runs.
78
+
79
+ How it works:
80
+
81
+ - Expected local path: `app/model/efficientnetv2s.h5`
82
+ - On startup, `app/model_loader.py` checks for the file
83
+ - If missing, it is downloaded via `huggingface_hub` and saved to `app/model/`
84
+ - Subsequent runs load the local copy
85
+
86
+ Note for Docker users: When running inside a container, the downloaded model is stored inside the container’s filesystem. Mount a volume if you need to persist it on the host.
87
+
88
+ Manual download link (optional): https://huggingface.co/Miguel764/efficientnetv2s-skin-cancer-classifier
89
+
90
+ ---
91
+
92
+ ## Installation & Usage
93
+
94
+ ### 1) Clone the repository
95
+
96
+ ```sh
97
+ git clone https://github.com/yourusername/skin-cancer-api.git
98
+ cd skin-cancer-api
99
+ ```
100
+
101
+ ### 2) Install dependencies
102
+
103
+ ```sh
104
+ pip install -r requirements.txt
105
+ ```
106
+
107
+ Note: Requires Python 3.8+ and TensorFlow 2.10.0 (GPU recommended).
108
+
109
+ ### 3) Run the application
110
+
111
+ ```sh
112
+ uvicorn app.main:app --reload
113
+ ```
114
+
115
+ The app will be available at http://localhost:8000
116
+
117
+ Access the API docs at http://127.0.0.1:8000/docs
118
+
119
+ ### 4) Try with sample images
120
+
121
+ Use the images in the `test_images/` folder to test the classifier.
122
+
123
+ ---
124
+
125
+ ## Docker Deployment
126
+
127
+ Build and run the app in a container:
128
+
129
+ ```sh
130
+ docker build -t skin-cancer-api .
131
+ docker run -p 8000:8000 skin-cancer-api
132
+ ```
133
+
134
+ ---
app/main.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/main.py
2
+
3
+ import shutil
4
+ import os
5
+ import uuid
6
+ from fastapi import FastAPI, Request, File, UploadFile, Form
7
+ from fastapi.responses import HTMLResponse
8
+ from fastapi.templating import Jinja2Templates
9
+ from fastapi.staticfiles import StaticFiles
10
+ from app.predict import predict_image
11
+
12
+ app = FastAPI()
13
+
14
+ templates = Jinja2Templates(directory="app/templates")
15
+ app.mount("/static", StaticFiles(directory="app/static"), name="static")
16
+
17
+ os.makedirs("app/uploads", exist_ok=True)
18
+ app.mount("/uploads", StaticFiles(directory="app/uploads"), name="uploads")
19
+
20
+
21
+ @app.get("/", response_class=HTMLResponse)
22
+ async def index(request: Request):
23
+ return templates.TemplateResponse("main.html", {"request": request})
24
+
25
+ @app.get("/model-info", response_class=HTMLResponse)
26
+ async def model_info(request: Request):
27
+ return templates.TemplateResponse("model_info.html", {"request": request})
28
+
29
+ @app.post("/upload-image", response_class=HTMLResponse)
30
+ async def upload_image(request: Request, file: UploadFile = File(...)):
31
+
32
+ unique_filename = f"{uuid.uuid4().hex}_{file.filename}"
33
+ file_path = f"app/uploads/{unique_filename}"
34
+
35
+ with open(file_path, "wb") as buffer:
36
+ shutil.copyfileobj(file.file, buffer)
37
+
38
+ return templates.TemplateResponse("main.html", {
39
+ "request": request,
40
+ "image_path": f"/uploads/{unique_filename}"
41
+ })
42
+
43
+
44
+ @app.post("/predict", response_class=HTMLResponse)
45
+ async def predict(request: Request, image_path: str = Form(...)):
46
+ label, confidence, all_predictions = predict_image(f"app{image_path}")
47
+ confidence_percent = f"{confidence * 100:.2f}%"
48
+
49
+ return templates.TemplateResponse("main.html", {
50
+ "request": request,
51
+ "image_path": image_path,
52
+ "label": label,
53
+ "confidence": confidence_percent,
54
+ "predictions": all_predictions
55
+ })
app/model_loader.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/model_loader.py
2
+ import os
3
+ import tensorflow as tf
4
+ from huggingface_hub import hf_hub_download
5
+
6
+ MODEL_PATH = "app/model/efficientnetv2s.h5"
7
+ REPO_ID = "Miguel764/efficientnetv2s-skin-cancer-classifier"
8
+ FILENAME = "efficientnetv2s.h5"
9
+
10
+ def load_model():
11
+ if not os.path.exists(MODEL_PATH):
12
+ print("Model not found locally. Downloading from Hugging Face...")
13
+ os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
14
+ hf_hub_download(
15
+ repo_id=REPO_ID,
16
+ filename=FILENAME,
17
+ local_dir="app/model"
18
+ )
19
+ else:
20
+ print("Model already exists locally.")
21
+
22
+ return tf.keras.models.load_model(MODEL_PATH)
app/predict.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/predict.py
2
+ from tensorflow.keras.preprocessing import image
3
+ import numpy as np
4
+ import tensorflow as tf
5
+ from app.model_loader import load_model
6
+
7
+ model = load_model()
8
+
9
+ # Pre-calculated optimal temperature value
10
+ TEMPERATURE = 2.77
11
+
12
+ class_names_mapping = {
13
+ 0: "AKIEC",
14
+ 1: "BCC",
15
+ 2: "BKL",
16
+ 3: "DF",
17
+ 4: "MEL",
18
+ 5: "NV",
19
+ 6: "VASC"
20
+ }
21
+
22
+ full_names = {
23
+ "AKIEC": "Actinic Keratoses and Intraepithelial Carcinoma (AKIEC)",
24
+ "BCC": "Basal Cell Carcinoma (BCC)",
25
+ "BKL": "Benign Keratosis-like Lesions (BKL)",
26
+ "DF": "Dermatofibroma (DF)",
27
+ "MEL": "Melanoma (MEL)",
28
+ "NV": "Melanocytic Nevi (NV)",
29
+ "VASC": "Vascular Lesions (VASC)"
30
+ }
31
+
32
+ def preprocess_image(file):
33
+ img = image.load_img(file, target_size=(224, 224))
34
+ img_array = image.img_to_array(img) / 255.0
35
+ img_array = (img_array - 0.5) * 2
36
+ return np.expand_dims(img_array, axis=0)
37
+
38
+ def predict_image(file):
39
+ processed = preprocess_image(file)
40
+
41
+ # Obtaining logits by disabling the final softmax
42
+ logits_model = tf.keras.Model(inputs=model.input, outputs=model.layers[-2].output)
43
+ logits = logits_model(processed)
44
+
45
+ # Apply final layer without softmax (if the last layer is Dense with softmax)
46
+ final_dense = model.layers[-1]
47
+ logits = final_dense(logits)
48
+
49
+ # Apply T-scaling: logits / T, then softmax
50
+ scaled_logits = logits / TEMPERATURE
51
+ scaled_probs = tf.nn.softmax(scaled_logits).numpy()[0]
52
+
53
+ class_idx = int(np.argmax(scaled_probs))
54
+ top_label = full_names[class_names_mapping[class_idx]]
55
+ top_confidence = float(scaled_probs[class_idx])
56
+
57
+ all_predictions = [
58
+ {"label": class_names_mapping[i], "confidence": float(pred)}
59
+ for i, pred in enumerate(scaled_probs)
60
+ ]
61
+
62
+ return top_label, top_confidence, all_predictions
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ numpy
4
+ pillow
5
+ jinja2
6
+ python-multipart
7
+ huggingface_hub
test_images/akiec_1.jpg ADDED
test_images/akiec_2.jpg ADDED
test_images/bcc_1.jpg ADDED
test_images/bcc_2.jpg ADDED
test_images/bkl_1.jpg ADDED
test_images/bkl_2.jpg ADDED
test_images/df_1.jpg ADDED
test_images/df_2.jpg ADDED
test_images/mel_1.jpg ADDED
test_images/mel_2.jpg ADDED
test_images/nv_1.jpg ADDED
test_images/nv_2.jpg ADDED
test_images/vasc_1.jpg ADDED
test_images/vasc_2.jpg ADDED