File size: 2,695 Bytes
b5ebe73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""Standalone inference example for adeIaide/skin-lesion-7class-efficientnet."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import torch
from huggingface_hub import hf_hub_download
from PIL import Image
from safetensors.torch import load_file
from torch import nn
from torchvision import models, transforms


REPO_ID = "adeIaide/skin-lesion-7class-efficientnet"


class EfficientNetB0(nn.Module):
    def __init__(self, num_classes: int = 7, dropout: float = 0.3) -> None:
        super().__init__()
        network = models.efficientnet_b0(weights=None)
        embedding_dim = network.classifier[1].in_features
        self.encoder = nn.Sequential(network.features, network.avgpool, nn.Flatten(1))
        self.dropout = nn.Dropout(dropout)
        self.classifier = nn.Linear(embedding_dim, num_classes)

    def forward(self, image: torch.Tensor) -> torch.Tensor:
        return self.classifier(self.dropout(self.encoder(image)))


def load_model(device: torch.device) -> tuple[nn.Module, dict]:
    config_path = hf_hub_download(REPO_ID, "config.json")
    weights_path = hf_hub_download(REPO_ID, "model.safetensors")
    config = json.loads(Path(config_path).read_text(encoding="utf-8"))
    model = EfficientNetB0(
        num_classes=int(config["num_classes"]),
        dropout=float(config["dropout"]),
    )
    model.load_state_dict(load_file(weights_path, device=str(device)))
    return model.to(device).eval(), config


def predict(image_path: str | Path) -> dict[str, float]:
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model, config = load_model(device)
    norm = config["normalization"]
    transform = transforms.Compose(
        [
            transforms.Resize((config["image_size"], config["image_size"])),
            transforms.ToTensor(),
            transforms.Normalize(norm["mean"], norm["std"]),
        ]
    )
    image = Image.open(image_path).convert("RGB")
    batch = transform(image).unsqueeze(0).to(device)
    with torch.inference_mode():
        probabilities = torch.softmax(model(batch), dim=1)[0].cpu().tolist()
    return dict(zip(config["class_names"], probabilities))


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("image", help="Path to one dermoscopic image")
    args = parser.parse_args()
    probabilities = predict(args.image)
    predicted = max(probabilities, key=probabilities.get)
    print(f"prediction: {predicted}")
    for label, probability in sorted(
        probabilities.items(), key=lambda item: item[1], reverse=True
    ):
        print(f"{label:5s} {probability:.4f}")


if __name__ == "__main__":
    main()