Alex Vega commited on
Commit
f1acbf1
·
1 Parent(s): 8b441e0
Files changed (3) hide show
  1. Dockerfile +13 -0
  2. main.py +108 -0
  3. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ RUN chmod -R 777 /app
12
+
13
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
main.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pickle
2
+ import numpy as np
3
+ import io
4
+ import math
5
+ import cv2
6
+ from skimage.feature import hog
7
+ from fastapi import FastAPI, File, UploadFile, HTTPException
8
+ from PIL import Image
9
+
10
+ app = FastAPI(
11
+ title="Enhanced Peruvian Sign Language (LSP) Recognition API",
12
+ description="Upload an image of a Peruvian Sign Language alphabet sign to predict the corresponding letter using an enhanced Self-Organizing Map (SOM) with HOG features.",
13
+ version="2.0.0"
14
+ )
15
+
16
+ MODEL_FILENAME = 'lsp_som_model_enhanced.pkl'
17
+ try:
18
+ with open(MODEL_FILENAME, 'rb') as f:
19
+ model_data = pickle.load(f)
20
+ som = model_data['som']
21
+ label_map = model_data['label_map']
22
+ CLASSES = model_data['classes']
23
+ IMG_SIZE = model_data['img_size']
24
+ HOG_PARAMS = model_data['feature_extraction_params'] # Load HOG parameters
25
+ print(f"✅ Model '{MODEL_FILENAME}' and assets loaded successfully.")
26
+ print(f" - Classes: {CLASSES}")
27
+ print(f" - Expected Image Size for Processing: {IMG_SIZE}x{IMG_SIZE}")
28
+ print(f" - Feature Extractor: {HOG_PARAMS['type']}")
29
+ except FileNotFoundError:
30
+ print(f"❌ FATAL ERROR: Model file '{MODEL_FILENAME}' not found.")
31
+ som = None
32
+
33
+ def preprocess_and_extract_features_from_bytes(image_bytes: bytes):
34
+ try:
35
+ nparr = np.frombuffer(image_bytes, np.uint8)
36
+ img_bgr = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
37
+ if img_bgr is None:
38
+ raise ValueError("Could not decode image bytes. The file may be corrupt or not an image.")
39
+ img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
40
+
41
+ ycrcb = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2YCrCb)
42
+ skin_mask = cv2.inRange(ycrcb, np.array([0, 135, 85]), np.array([255, 180, 135]))
43
+
44
+ contours, _ = cv2.findContours(skin_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
45
+ if not contours:
46
+ raise ValueError("No contours found in the image. The hand sign may not be clear enough.")
47
+
48
+ largest_contour = max(contours, key=cv2.contourArea)
49
+ x, y, w, h = cv2.boundingRect(largest_contour)
50
+ cropped_hand = img_rgb[y:y+h, x:x+w]
51
+
52
+ resized_hand = cv2.resize(cropped_hand, (IMG_SIZE, IMG_SIZE))
53
+ gray_hand = cv2.cvtColor(resized_hand, cv2.COLOR_RGB2GRAY)
54
+
55
+ hog_features = hog(gray_hand,
56
+ orientations=HOG_PARAMS['orientations'],
57
+ pixels_per_cell=HOG_PARAMS['pixels_per_cell'],
58
+ cells_per_block=HOG_PARAMS['cells_per_block'],
59
+ transform_sqrt=HOG_PARAMS['transform_sqrt'],
60
+ block_norm=HOG_PARAMS['block_norm'])
61
+
62
+ return hog_features
63
+
64
+ except Exception as e:
65
+ raise HTTPException(status_code=400, detail=f"Image processing failed. Error: {e}")
66
+
67
+
68
+ @app.get("/", tags=["Status"])
69
+ def read_root():
70
+ return {"status": "ok", "message": "Welcome to the Enhanced LSP Recognition API!"}
71
+
72
+ @app.post("/predict", tags=["Prediction"])
73
+ async def predict_sign(file: UploadFile = File(..., description="An image file of a Peruvian Sign Language sign.")):
74
+ if not som:
75
+ raise HTTPException(status_code=503, detail="Model is not available. API cannot process requests.")
76
+
77
+ image_bytes = await file.read()
78
+
79
+ feature_vector = preprocess_and_extract_features_from_bytes(image_bytes)
80
+
81
+ winner_neuron = som.winner(feature_vector)
82
+ predicted_index = label_map.get(winner_neuron, -1)
83
+
84
+ is_best_guess = False
85
+ if predicted_index == -1:
86
+ is_best_guess = True
87
+ min_dist = float('inf')
88
+ for mapped_pos, mapped_label in label_map.items():
89
+ dist = math.sqrt((winner_neuron[0] - mapped_pos[0])**2 + (winner_neuron[1] - mapped_pos[1])**2)
90
+ if dist < min_dist:
91
+ min_dist = dist
92
+ predicted_index = mapped_label
93
+
94
+ if predicted_index != -1:
95
+ predicted_letter = CLASSES[predicted_index]
96
+ prediction_type = "Nearest Neighbor Guess" if is_best_guess else "Direct Match"
97
+ else:
98
+ predicted_letter = "Unknown"
99
+ prediction_type = "Critical Error: No mapped neurons found on the entire map."
100
+
101
+ response = {
102
+ "filename": file.filename,
103
+ "predicted_letter": predicted_letter,
104
+ "prediction_type": prediction_type,
105
+ "winner_neuron_on_map": [int(coord) for coord in winner_neuron]
106
+ }
107
+ print(f"[LOG] Prediction successful. Response: {response}")
108
+ return response
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ minisom
5
+ numpy
6
+ Pillow
7
+ scikit-image
8
+ opencv-python-headless