ivanm151 commited on
Commit
547626f
·
1 Parent(s): fa2e57f

init comit

Browse files
Files changed (7) hide show
  1. .idea/.gitignore +3 -0
  2. .idea/vcs.xml +4 -0
  3. Dockerfile +18 -0
  4. app.py +103 -0
  5. model.py +76 -0
  6. model/stage1_skin_classifier.pth +3 -0
  7. requirements.txt +14 -0
.idea/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
.idea/vcs.xml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings" defaultProject="true" />
4
+ </project>
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ FROM python:3.9
3
+
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ WORKDIR /app
9
+
10
+ COPY --chown=user ./requirements.txt requirements.txt
11
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
12
+
13
+ COPY --chown=user . /app
14
+
15
+ # Создаем папку для модели
16
+ RUN mkdir -p /home/user/.cache/models
17
+
18
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from fastapi import FastAPI, File, UploadFile, HTTPException
3
+ from fastapi.responses import JSONResponse, HTMLResponse
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.openapi.docs import get_swagger_ui_html
6
+ from typing import List, Optional
7
+ import io
8
+ from PIL import Image
9
+ import numpy as np
10
+
11
+ from model import SkinClassifier
12
+
13
+ # Инициализация FastAPI
14
+ app = FastAPI(
15
+ title="Skin Classification API",
16
+ description="API для классификации кожных заболеваний (clear, acne, ros, black)",
17
+ version="1.0.0",
18
+ docs_url="/docs",
19
+ redoc_url="/redoc"
20
+ )
21
+
22
+ # Инициализация модели
23
+ try:
24
+ # Путь к модели в Hugging Face Spaces
25
+ MODEL_PATH = "model/stage1_skin_classifier.pth"
26
+ classifier = SkinClassifier(MODEL_PATH)
27
+ print("✅ Model loaded successfully!")
28
+ except Exception as e:
29
+ print(f"❌ Error loading model: {e}")
30
+ classifier = None
31
+
32
+
33
+ @app.get("/", response_class=HTMLResponse)
34
+ async def root():
35
+ """Главная страница с информацией об API"""
36
+ return "go to /docs"
37
+
38
+
39
+ @app.post("/predict")
40
+ async def predict(
41
+ file: UploadFile = File(..., description="Изображение для классификации (JPG, PNG)"),
42
+ return_image: Optional[bool] = False
43
+ ):
44
+ """
45
+ Классификация одного изображения
46
+
47
+ - **file**: Изображение в формате JPG или PNG
48
+ - **return_image**: Возвращать ли base64 изображение (по умолчанию False)
49
+ """
50
+ if classifier is None:
51
+ raise HTTPException(status_code=503, detail="Model not loaded")
52
+
53
+ # Проверка формата файла
54
+ allowed_extensions = {".jpg", ".jpeg", ".png", ".bmp"}
55
+ file_ext = os.path.splitext(file.filename)[1].lower()
56
+
57
+ if file_ext not in allowed_extensions:
58
+ raise HTTPException(
59
+ status_code=400,
60
+ detail=f"Unsupported file format. Allowed: {allowed_extensions}"
61
+ )
62
+
63
+ try:
64
+ # Чтение файла
65
+ contents = await file.read()
66
+
67
+ # Предсказание
68
+ result = classifier.predict(contents)
69
+
70
+ response_data = {
71
+ "filename": file.filename,
72
+ "prediction": result["predicted_class"],
73
+ "confidence": result["confidence"],
74
+ "probabilities": result["all_probabilities"]
75
+ }
76
+
77
+ # Добавляем base64 изображение если нужно
78
+ if return_image:
79
+ from io import BytesIO
80
+ import base64
81
+
82
+ img = Image.open(BytesIO(contents))
83
+ buffered = BytesIO()
84
+ img.save(buffered, format="JPEG" if file_ext in {".jpg", ".jpeg"} else "PNG")
85
+ img_str = base64.b64encode(buffered.getvalue()).decode()
86
+ response_data["image_base64"] = f"data:image/jpeg;base64,{img_str}"
87
+
88
+ return response_data
89
+
90
+ except Exception as e:
91
+ raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
92
+
93
+
94
+ # Middleware для обработки CORS
95
+ from fastapi.middleware.cors import CORSMiddleware
96
+
97
+ app.add_middleware(
98
+ CORSMiddleware,
99
+ allow_origins=["*"],
100
+ allow_credentials=True,
101
+ allow_methods=["*"],
102
+ allow_headers=["*"],
103
+ )
model.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import timm
4
+ import albumentations as A
5
+ from albumentations.pytorch import ToTensorV2
6
+ import numpy as np
7
+ from PIL import Image
8
+ import io
9
+
10
+ CLASSES = ["clear", "acne", "ros", "black"]
11
+ IMG_SIZE = 224
12
+
13
+
14
+ class SkinClassifier:
15
+ def __init__(self, model_path="model/stage1_skin_classifier.pth"):
16
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
17
+ self.classes = CLASSES
18
+ self.img_size = IMG_SIZE
19
+
20
+ # Инициализируем модель
21
+ self.model = timm.create_model(
22
+ "efficientnet_b0",
23
+ pretrained=False,
24
+ num_classes=len(self.classes)
25
+ )
26
+
27
+ # Загружаем веса
28
+ state_dict = torch.load(model_path, map_location=self.device)
29
+ self.model.load_state_dict(state_dict)
30
+ self.model.to(self.device)
31
+ self.model.eval()
32
+
33
+ # Трансформации
34
+ self.transform = A.Compose([
35
+ A.Resize(self.img_size, self.img_size),
36
+ A.Normalize(
37
+ mean=(0.485, 0.456, 0.406),
38
+ std=(0.229, 0.224, 0.225)
39
+ ),
40
+ ToTensorV2()
41
+ ])
42
+
43
+ def preprocess(self, image):
44
+ """Препроцессинг изображения"""
45
+ if isinstance(image, bytes):
46
+ image = Image.open(io.BytesIO(image)).convert("RGB")
47
+ elif isinstance(image, np.ndarray):
48
+ image = Image.fromarray(image).convert("RGB")
49
+ else:
50
+ image = image.convert("RGB")
51
+
52
+ image = np.array(image)
53
+ transformed = self.transform(image=image)
54
+ return transformed["image"]
55
+
56
+ def predict(self, image):
57
+ """Предсказание класса"""
58
+ # Препроцессинг
59
+ tensor = self.preprocess(image)
60
+ tensor = tensor.unsqueeze(0).to(self.device)
61
+
62
+ # Предсказание
63
+ with torch.no_grad():
64
+ outputs = self.model(tensor)
65
+ probabilities = torch.nn.functional.softmax(outputs, dim=1)
66
+ prediction = torch.argmax(probabilities, dim=1)
67
+
68
+ # Получаем вероятности для всех классов
69
+ probs = probabilities[0].cpu().numpy()
70
+ class_probs = {self.classes[i]: float(probs[i]) for i in range(len(self.classes))}
71
+
72
+ return {
73
+ "predicted_class": self.classes[prediction.item()],
74
+ "confidence": float(probabilities[0][prediction.item()]),
75
+ "all_probabilities": class_probs
76
+ }
model/stage1_skin_classifier.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:736db77bb261eb8502d54e90a45d20d68b79d6c7852c689cc141fb9411030c32
3
+ size 16351009
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ torch==2.1.0
4
+ torchvision==0.16.0
5
+ timm==0.9.12
6
+ scikit-learn==1.3.2
7
+ albumentations==1.3.1
8
+ Pillow==10.1.0
9
+ numpy==1.24.3
10
+ pandas==2.1.4
11
+ tqdm==4.66.1
12
+ opencv-python-headless==4.8.1.78
13
+ requests==2.31.0
14
+ python-multipart==0.0.6