Dan Vancea commited on
Commit
a66cf1e
·
1 Parent(s): b6e1fb1

Update API

Browse files
Files changed (1) hide show
  1. api.py +216 -5
api.py CHANGED
@@ -1,7 +1,218 @@
1
- from fastapi import FastAPI
 
 
 
 
 
 
 
 
 
 
2
 
3
- api = FastAPI()
 
 
4
 
5
- @api.get("/")
6
- def greet_json():
7
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import uvicorn
4
+ import tensorflow as tf
5
+ import numpy as np
6
+ import time
7
+ import io
8
+ import base64
9
+ import logging
10
+ from PIL import Image
11
+ #from architectures import *
12
 
13
+ # Logging configuration for observability and debugging
14
+ logging.basicConfig(level=logging.INFO)
15
+ logger = logging.getLogger("API")
16
 
17
+ # FastAPI application entry point
18
+ app = FastAPI(title="Enhance AI")
19
+
20
+ # CORS configuration to allow frontend communication
21
+ app.add_middleware(
22
+ CORSMiddleware,
23
+ allow_origins=["*"],
24
+ allow_credentials=True,
25
+ allow_methods=["*"],
26
+ allow_headers=["*"],
27
+ )
28
+
29
+ # GPU detection and memory configuration
30
+ # Enables memory growth to avoid TensorFlow pre-allocating all VRAM
31
+ gpus = tf.config.list_physical_devices('GPU')
32
+ if gpus:
33
+ try:
34
+ for gpu in gpus:
35
+ tf.config.experimental.set_memory_growth(gpu, True)
36
+ logger.info(f"Detected {len(gpus)} GPU(s). Memory growth enabled.")
37
+ except RuntimeError as e:
38
+ logger.error(f"GPU configuration error: {e}")
39
+
40
+
41
+ # In-memory cache for loaded models to avoid repeated disk loads
42
+ loaded_models = {}
43
+
44
+
45
+ # Model registry: architecture name -> scale factor -> model file path
46
+ MODEL_PATH = "../models/"
47
+ MODEL_FILES = {
48
+ "Average":{
49
+ 2: MODEL_PATH + "average_x2.keras",
50
+ 4: MODEL_PATH + "average_x4.keras"
51
+ },
52
+ "CNNU": {
53
+ 2: MODEL_PATH + "cnnu_e100_x2.keras",
54
+ 4: MODEL_PATH + "cnnu_e100_x4.keras",
55
+ },
56
+ "ESPCN": {
57
+ 2: MODEL_PATH + "espcn_e100_x2.keras",
58
+ 4: MODEL_PATH + "espcn_e100_x4.keras",
59
+ },
60
+ "SRGAN": {
61
+ 2: MODEL_PATH + "srgan_e100_b8f64_l005_x2.keras",
62
+ 4: MODEL_PATH + "srgan_e100_b8f64_l005_x4.keras",
63
+ },
64
+ "SRResNet": {
65
+ 2: MODEL_PATH + "srrn_e100_b8f64_x2.keras",
66
+ 4: MODEL_PATH + "srrn_e100_b8f64_x4.keras",
67
+ },
68
+ }
69
+
70
+ # Model loader with caching and scale validation
71
+ def get_model(model_name: str, scale: int):
72
+ """
73
+ Loads and caches a TensorFlow super-resolution model
74
+ for a given architecture and scale factor.
75
+ """
76
+ if model_name not in MODEL_FILES:
77
+ raise HTTPException(
78
+ status_code=404,
79
+ detail=f"Architecture '{model_name}' is not configured.",
80
+ )
81
+
82
+ if scale not in MODEL_FILES[model_name]:
83
+ raise HTTPException(
84
+ status_code=404,
85
+ detail=f"Model '{model_name}' x{scale} is not available.",
86
+ )
87
+
88
+ cache_key = f"{model_name}_x{scale}"
89
+
90
+ if cache_key not in loaded_models:
91
+ model_path = MODEL_FILES[model_name][scale]
92
+ logger.info(f"Loading model {cache_key} from {model_path}")
93
+ try:
94
+ loaded_models[cache_key] = tf.keras.models.load_model(
95
+ model_path,
96
+ compile=False,
97
+ )
98
+ except Exception as e:
99
+ logger.error(f"Failed to load model {model_path}: {e}")
100
+ raise HTTPException(
101
+ status_code=500,
102
+ detail=f"Error loading model file: {e}",
103
+ )
104
+
105
+ return loaded_models[cache_key]
106
+
107
+ def predict(
108
+ input_img: np.ndarray,
109
+ model_name: str,
110
+ up_ratio: int,
111
+ device_type: str
112
+ ) -> tuple[tf.Tensor, float]:
113
+ """
114
+ Receives a tensor image and upscales it using a model with an up_ratio.
115
+ Returns the prediction tensor and runtime in seconds.
116
+ """
117
+
118
+ # Select model(s)
119
+ if up_ratio == 8:
120
+ models = [
121
+ get_model(model_name, 2),
122
+ get_model(model_name, 4),
123
+ ]
124
+ else:
125
+ print(model_name, up_ratio)
126
+ models = [get_model(model_name, up_ratio)]
127
+
128
+ # Inference with runtime measurement
129
+ with tf.device(device_type):
130
+ start_time = time.perf_counter()
131
+ prediction = tf.convert_to_tensor(input_img)
132
+
133
+ for model in models:
134
+ prediction = model(prediction, training=False)
135
+
136
+ _ = prediction.shape # Forces execution
137
+ runtime = time.perf_counter() - start_time
138
+
139
+ return prediction, runtime
140
+
141
+ # Image upscaling endpoint
142
+ @app.post("/upscale")
143
+ async def upscale(
144
+ file: UploadFile = File(...),
145
+ model_name: str = Form(...),
146
+ scale: str = Form("4"),
147
+ device: str = Form("GPU"),
148
+ ):
149
+ """
150
+ Receives an image and returns an upscaled version generated
151
+ by the selected model, scale factor, and execution device.
152
+ """
153
+ try:
154
+ print("A")
155
+ scale_factor = int(float(scale))
156
+
157
+ # Select execution device based on availability and user request
158
+ if device.upper() == "GPU" and len(gpus) < 1:
159
+ raise HTTPException(
160
+ status_code=400,
161
+ detail="GPU device is selected but no GPU is detected!"
162
+ )
163
+
164
+ device_type = ("/GPU:0" if device.upper() == "GPU" else "/CPU:0")
165
+
166
+ print("B")
167
+ logger.info(
168
+ f"Request received: {model_name} x{scale_factor} | "
169
+ f"Device: {device_type} | File: {file.filename}"
170
+ )
171
+
172
+ # Input preprocessing
173
+ contents = await file.read()
174
+ pil_img = Image.open(io.BytesIO(contents)).convert("RGB")
175
+ in_w, in_h = pil_img.size
176
+
177
+ img_array = np.array(pil_img).astype(np.float32) / 255.0
178
+ input_tensor = np.expand_dims(img_array, axis=0)
179
+
180
+ # Upscale image
181
+ prediction, runtime = predict(input_tensor, model_name, scale_factor, device_type)
182
+
183
+ # Post-processing
184
+ output_tensor = tf.clip_by_value(tf.squeeze(prediction), 0.0, 1.0)
185
+ output_array = (output_tensor.numpy() * 255).astype(np.uint8)
186
+
187
+ out_pil = Image.fromarray(output_array)
188
+ out_w, out_h = out_pil.size
189
+
190
+ buffer = io.BytesIO()
191
+ out_pil.save(buffer, format="PNG")
192
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
193
+
194
+ # Structured response for frontend visualization
195
+ return {
196
+ "status": "success",
197
+ "image": img_base64,
198
+ "inference_time": f"{runtime:.3f}s",
199
+ "metrics": {
200
+ "Input Res": f"{in_w}x{in_h}",
201
+ "Output Res": f"{out_w}x{out_h}",
202
+ "Scale": f"x{scale_factor}",
203
+ "Device Used": device_type.replace("/", ""),
204
+ },
205
+ }
206
+
207
+ except HTTPException:
208
+ raise
209
+ except Exception as e:
210
+ logger.error(f"Upscale error: {e}")
211
+ return {
212
+ "status": "error",
213
+ "message": str(e),
214
+ }
215
+
216
+ # Development entry point
217
+ if __name__ == "__main__":
218
+ uvicorn.run(app, host="0.0.0.0", port=8000)