Calamar49 commited on
Commit
d7e3c30
·
1 Parent(s): 3a64145

Initial model and API upload

Browse files
VisionEnsembleModel.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contenido de: VisionEnsembleModel.py
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+ import timm
6
+
7
+ class VisionEnsembleModel(nn.Module):
8
+ """
9
+ La misma clase de modelo que definiste en Colab.
10
+ """
11
+ def __init__(self, num_classes, cnn_model_name='efficientnet_b2', vit_model_name='vit_small_patch16_224'):
12
+ super().__init__()
13
+ # Usamos pretrained=False aquí porque cargaremos nuestros propios pesos.
14
+ # Timm cargará los pesos preentrenados si no encuentra un state_dict local,
15
+ # pero es más limpio ser explícito. Al final, los sobrescribiremos.
16
+ self.cnn = timm.create_model(cnn_model_name, pretrained=False, num_classes=num_classes)
17
+ cnn_features = self.cnn.get_classifier().in_features
18
+ self.cnn.reset_classifier(0)
19
+
20
+ self.vit = timm.create_model(vit_model_name, pretrained=False, num_classes=num_classes)
21
+ vit_features = self.vit.head.in_features
22
+ self.vit.head = nn.Identity()
23
+
24
+ self.classifier = nn.Sequential(
25
+ nn.BatchNorm1d(cnn_features + vit_features),
26
+ nn.Linear(cnn_features + vit_features, 512),
27
+ nn.ReLU(),
28
+ nn.Dropout(0.5),
29
+ nn.Linear(512, num_classes)
30
+ )
31
+
32
+ def forward(self, image):
33
+ cnn_feat = self.cnn(image)
34
+ vit_feat = self.vit(image)
35
+ combined = torch.cat([cnn_feat, vit_feat], dim=1)
36
+ output = self.classifier(combined)
37
+ return output
app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contenido de: app.py
2
+
3
+ import torch
4
+ import torchvision.transforms as transforms
5
+ from PIL import Image
6
+ import json
7
+ from fastapi import FastAPI, UploadFile, File
8
+ from fastapi.responses import JSONResponse
9
+ import io
10
+
11
+ # Importamos la definición de nuestro modelo desde el otro archivo
12
+ from VisionEnsembleModel import VisionEnsembleModel
13
+
14
+ # --- 1. Carga del Modelo y Componentes ---
15
+
16
+ # Definimos el dispositivo (en los Spaces de Hugging Face, podemos usar CPU o GPU)
17
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
+ print(f"Usando dispositivo: {device}")
19
+
20
+ # Rutas a los archivos
21
+ MODEL_PATH = "model/best_vision_ensemble_model.pth"
22
+ LABELS_PATH = "model/species_labels_map.json"
23
+ NUM_CLASSES = 156 # El número de clases con el que fue entrenado
24
+
25
+ # Cargamos el mapa de etiquetas
26
+ with open(LABELS_PATH) as f:
27
+ labels_map = json.load(f)
28
+ print("Mapa de etiquetas cargado.")
29
+
30
+ # Instanciamos el modelo y cargamos los pesos
31
+ model = VisionEnsembleModel(num_classes=NUM_CLASSES)
32
+ model.load_state_dict(torch.load(MODEL_PATH, map_location=device))
33
+ model.to(device)
34
+ model.eval() # ¡Muy importante poner el modelo en modo de evaluación!
35
+ print("Modelo cargado y en modo de evaluación.")
36
+
37
+ # Definimos las transformaciones de la imagen (deben ser las mismas que en la validación)
38
+ transforms_val = transforms.Compose([
39
+ transforms.Resize((224, 224)),
40
+ transforms.ToTensor(),
41
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
42
+ ])
43
+
44
+ # --- 2. Creación de la Aplicación FastAPI ---
45
+
46
+ app = FastAPI(title="API de Clasificación de Orquídeas")
47
+
48
+ @app.get("/")
49
+ def read_root():
50
+ return {"message": "Bienvenido a la API de Orquídeas. Envía una imagen al endpoint /predict"}
51
+
52
+ @app.post("/predict")
53
+ async def predict(file: UploadFile = File(...)):
54
+ """
55
+ Endpoint que recibe una imagen, la procesa y devuelve la predicción.
56
+ """
57
+ # Leer el contenido de la imagen en memoria
58
+ image_bytes = await file.read()
59
+ try:
60
+ image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
61
+ except Exception as e:
62
+ return JSONResponse(status_code=400, content={"error": f"Archivo inválido: {e}"})
63
+
64
+ # Preprocesar la imagen
65
+ input_tensor = transforms_val(image).unsqueeze(0).to(device)
66
+
67
+ # Realizar la predicción
68
+ with torch.no_grad():
69
+ output = model(input_tensor)
70
+ probabilities = torch.nn.functional.softmax(output[0], dim=0)
71
+
72
+ # Obtener la predicción principal
73
+ top_prob, top_catid = torch.topk(probabilities, 1)
74
+ predicted_id = top_catid[0].item()
75
+ confidence = top_prob[0].item()
76
+
77
+ # Traducir el ID a un nombre de especie
78
+ predicted_species = labels_map.get(str(predicted_id), "Especie Desconocida")
79
+
80
+ # Devolver el resultado en formato JSON
81
+ return JSONResponse(
82
+ status_code=200,
83
+ content={
84
+ "filename": file.filename,
85
+ "predicted_species": predicted_species,
86
+ "confidence": f"{confidence:.4f}",
87
+ "species_id": predicted_id
88
+ }
89
+ )
model/best_vision_ensemble_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b870a8ea5dc05042e236bacba373e75018bbd8b4efd63a56496143b5fe409d13
3
+ size 122014183
model/species_labels_map.json ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "0": "1",
3
+ "1": "2",
4
+ "2": "3",
5
+ "3": "4",
6
+ "4": "5",
7
+ "5": "6",
8
+ "6": "7",
9
+ "7": "8",
10
+ "8": "9",
11
+ "9": "10",
12
+ "10": "11",
13
+ "11": "12",
14
+ "12": "13",
15
+ "13": "14",
16
+ "14": "15",
17
+ "15": "16",
18
+ "16": "17",
19
+ "17": "18",
20
+ "18": "19",
21
+ "19": "20",
22
+ "20": "21",
23
+ "21": "22",
24
+ "22": "23",
25
+ "23": "24",
26
+ "24": "25",
27
+ "25": "26",
28
+ "26": "27",
29
+ "27": "28",
30
+ "28": "29",
31
+ "29": "30",
32
+ "30": "31",
33
+ "31": "32",
34
+ "32": "33",
35
+ "33": "34",
36
+ "34": "35",
37
+ "35": "36",
38
+ "36": "37",
39
+ "37": "38",
40
+ "38": "39",
41
+ "39": "40",
42
+ "40": "41",
43
+ "41": "42",
44
+ "42": "43",
45
+ "43": "44",
46
+ "44": "45",
47
+ "45": "46",
48
+ "46": "47",
49
+ "47": "48",
50
+ "48": "49",
51
+ "49": "50",
52
+ "50": "51",
53
+ "51": "52",
54
+ "52": "53",
55
+ "53": "54",
56
+ "54": "55",
57
+ "55": "56",
58
+ "56": "57",
59
+ "57": "58",
60
+ "58": "59",
61
+ "59": "60",
62
+ "60": "61",
63
+ "61": "62",
64
+ "62": "63",
65
+ "63": "64",
66
+ "64": "65",
67
+ "65": "66",
68
+ "66": "67",
69
+ "67": "68",
70
+ "68": "69",
71
+ "69": "70",
72
+ "70": "71",
73
+ "71": "72",
74
+ "72": "73",
75
+ "73": "74",
76
+ "74": "75",
77
+ "75": "76",
78
+ "76": "77",
79
+ "77": "78",
80
+ "78": "79",
81
+ "79": "80",
82
+ "80": "81",
83
+ "81": "82",
84
+ "82": "83",
85
+ "83": "84",
86
+ "84": "85",
87
+ "85": "86",
88
+ "86": "87",
89
+ "87": "88",
90
+ "88": "89",
91
+ "89": "90",
92
+ "90": "91",
93
+ "91": "92",
94
+ "92": "93",
95
+ "93": "94",
96
+ "94": "95",
97
+ "95": "96",
98
+ "96": "97",
99
+ "97": "98",
100
+ "98": "99",
101
+ "99": "100",
102
+ "100": "101",
103
+ "101": "102",
104
+ "102": "103",
105
+ "103": "104",
106
+ "104": "105",
107
+ "105": "106",
108
+ "106": "107",
109
+ "107": "108",
110
+ "108": "109",
111
+ "109": "110",
112
+ "110": "111",
113
+ "111": "112",
114
+ "112": "113",
115
+ "113": "114",
116
+ "114": "115",
117
+ "115": "116",
118
+ "116": "117",
119
+ "117": "118",
120
+ "118": "119",
121
+ "119": "120",
122
+ "120": "121",
123
+ "121": "122",
124
+ "122": "123",
125
+ "123": "124",
126
+ "124": "125",
127
+ "125": "126",
128
+ "126": "127",
129
+ "127": "128",
130
+ "128": "129",
131
+ "129": "130",
132
+ "130": "131",
133
+ "131": "132",
134
+ "132": "133",
135
+ "133": "134",
136
+ "134": "135",
137
+ "135": "136",
138
+ "136": "137",
139
+ "137": "138",
140
+ "138": "139",
141
+ "139": "140",
142
+ "140": "141",
143
+ "141": "142",
144
+ "142": "143",
145
+ "143": "144",
146
+ "144": "145",
147
+ "145": "146",
148
+ "146": "147",
149
+ "147": "148",
150
+ "148": "149",
151
+ "149": "150",
152
+ "150": "151",
153
+ "151": "152",
154
+ "152": "153",
155
+ "153": "154",
156
+ "154": "155",
157
+ "155": "156"
158
+ }
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contenido de: requirements.txt
2
+
3
+ fastapi
4
+ uvicorn
5
+ python-multipart
6
+ torch
7
+ torchvision
8
+ timm
9
+ Pillow
10
+ scikit-learn