makakwastaken commited on
Commit
c70f9b8
·
1 Parent(s): 5190f6d

Added stable diffusion inpaint endpoint

Browse files
Files changed (5) hide show
  1. app.py +149 -11
  2. config.py +0 -2
  3. connectionManager.py +7 -2
  4. model.py +2 -3
  5. requirements.txt +3 -0
app.py CHANGED
@@ -4,18 +4,23 @@ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from fastapi.logger import logger
6
 
 
 
 
 
 
7
  # Connection Manager
8
  from connectionManager import ConnectionManager
9
 
10
- # Model
11
  from model import Model
12
  import base64
13
  from io import BytesIO
14
- from pydantic import BaseModel
15
- from config import CONFIG
16
-
17
  from predict import predict
18
 
 
 
 
19
  # About
20
  import torch
21
  import os
@@ -25,7 +30,7 @@ import sys
25
  import uvicorn
26
 
27
  app = FastAPI(
28
- title="AdVisual MaskCut Model",
29
  description="Description of the ML Model",
30
  version="0.0.1",
31
  terms_of_service=None,
@@ -35,7 +40,10 @@ app = FastAPI(
35
  )
36
 
37
  # Allow CORS for local debugging
38
- app.add_middleware(CORSMiddleware, allow_origins=["*"])
 
 
 
39
 
40
  @app.on_event("startup")
41
  async def startup_event():
@@ -46,14 +54,22 @@ async def startup_event():
46
  logger.info('Running envirnoment: {}'.format(CONFIG['ENV']))
47
  logger.info('PyTorch using device: {}'.format(CONFIG['DEVICE']))
48
 
49
- # Initialize the pytorch model
50
- model = Model()
 
 
 
 
 
 
 
51
  connectionManager = ConnectionManager()
52
 
53
  # add model and other preprocess tools too app state
54
  app.package = {
55
  "model": model,
56
- "connectionManager": connectionManager
 
57
  }
58
 
59
  @app.get("/ping")
@@ -82,6 +98,68 @@ def show_about():
82
  "nvidia-smi": bash('nvidia-smi')
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  class ImageBody(BaseModel):
86
  image: str
87
  threshold: float = 0.15
@@ -110,6 +188,66 @@ async def do_predict(body: ImageBody):
110
  return {"ok": True, "status": "FINISHED", "result": img_str}
111
 
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  @app.websocket("/ws")
114
  async def websocket_endpoint(websocket: WebSocket):
115
  connectionManager = app.package.get('connectionManager')
@@ -117,7 +255,7 @@ async def websocket_endpoint(websocket: WebSocket):
117
  await connectionManager.send_json({"ok": True, "status": "CONNECTED"}, websocket)
118
  while True:
119
  try:
120
- data = await connectionManager.receive_json(websocket)
121
  if (data is None):
122
  # Wait for data
123
  if not connectionManager.isConnected(websocket):
@@ -127,6 +265,7 @@ async def websocket_endpoint(websocket: WebSocket):
127
  connectionManager.disconnect(websocket)
128
  break
129
  continue
 
130
  image: str = data.get('image')
131
  threshold: float = data.get('threshold') or 0.15
132
  num_objects: int = data.get('num_objects') or 1
@@ -146,7 +285,6 @@ async def websocket_endpoint(websocket: WebSocket):
146
  await websocket.close()
147
  connectionManager.disconnect(websocket)
148
  except WebSocketDisconnect:
149
- print('websocket disconnect')
150
  connectionManager.disconnect(websocket)
151
  break
152
 
 
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from fastapi.logger import logger
6
 
7
+ # General
8
+ from config import CONFIG
9
+ from pydantic import BaseModel
10
+ from PIL import Image
11
+
12
  # Connection Manager
13
  from connectionManager import ConnectionManager
14
 
15
+ # CutLER Model
16
  from model import Model
17
  import base64
18
  from io import BytesIO
 
 
 
19
  from predict import predict
20
 
21
+ # Stable Diffusion Inpainting Model
22
+ from diffusers import StableDiffusionInpaintPipeline
23
+
24
  # About
25
  import torch
26
  import os
 
30
  import uvicorn
31
 
32
  app = FastAPI(
33
+ title="AdVisual Model Hosting",
34
  description="Description of the ML Model",
35
  version="0.0.1",
36
  terms_of_service=None,
 
40
  )
41
 
42
  # Allow CORS for local debugging
43
+ if CONFIG['ENV'] == 'development':
44
+ app.add_middleware(CORSMiddleware, allow_origins=["*"])
45
+ else:
46
+ app.add_middleware(CORSMiddleware, allow_origins=["https://advisual.io"])
47
 
48
  @app.on_event("startup")
49
  async def startup_event():
 
54
  logger.info('Running envirnoment: {}'.format(CONFIG['ENV']))
55
  logger.info('PyTorch using device: {}'.format(CONFIG['DEVICE']))
56
 
57
+ # Initialize the CutLER model
58
+ model = Model(CONFIG['DEVICE'])
59
+
60
+ # Initialize the stable-diffusion-inpainting model
61
+ pipe = StableDiffusionInpaintPipeline.from_pretrained("stabilityai/stable-diffusion-2-inpainting", safety_checker=None)
62
+
63
+ pipe.to(CONFIG['DEVICE'])
64
+
65
+ # Initialize the connection manager
66
  connectionManager = ConnectionManager()
67
 
68
  # add model and other preprocess tools too app state
69
  app.package = {
70
  "model": model,
71
+ "connectionManager": connectionManager,
72
+ "pipe": pipe
73
  }
74
 
75
  @app.get("/ping")
 
98
  "nvidia-smi": bash('nvidia-smi')
99
  }
100
 
101
+ def resize_image(img, height=512, width=512):
102
+ '''Resize image to `size`'''
103
+
104
+ size = (width, height)
105
+
106
+ img_resized = img.resize(size, Image.ANTIALIAS)
107
+ return img_resized
108
+
109
+ def crop_image(img, d=64):
110
+ '''Make dimensions divisible by `d`'''
111
+
112
+ new_size = (img.size[0] - img.size[0] % d,
113
+ img.size[1] - img.size[1] % d)
114
+
115
+ bbox = [
116
+ int((img.size[0] - new_size[0])/2),
117
+ int((img.size[1] - new_size[1])/2),
118
+ int((img.size[0] + new_size[0])/2),
119
+ int((img.size[1] + new_size[1])/2),
120
+ ]
121
+
122
+ img_cropped = img.crop(bbox)
123
+ return img_cropped
124
+
125
+ class InpaintBody(BaseModel):
126
+ image: str
127
+ mask: str
128
+ prompt: str
129
+
130
+ @app.post("/inpaint")
131
+ async def do_inpaint(body: InpaintBody):
132
+ """
133
+ Perform inpainting on input data
134
+ """
135
+
136
+ logger.info('API inpaint called')
137
+ image_data = body.image
138
+ mask_data = body.mask
139
+ prompt = body.prompt
140
+
141
+ # Extract base64 from mask and convert to PIL.Image
142
+ if (',' in image_data):
143
+ image = Image.open(BytesIO(base64.b64decode(image_data.split(',')[1])))
144
+ else:
145
+ image = Image.open(BytesIO(base64.b64decode(image_data)))
146
+
147
+ # Extract base64 from mask and convert to PIL.Image
148
+ if (',' in mask_data):
149
+ mask = Image.open(BytesIO(base64.b64decode(mask_data.split(',')[1])))
150
+ else:
151
+ mask = Image.open(BytesIO(base64.b64decode(mask_data)))
152
+
153
+ # Resize image and mask to 512x512
154
+ image = crop_image(resize_image(image, 512, 512))
155
+ mask = crop_image(resize_image(image, 512, 512))
156
+
157
+ pipe = app.package.get('pipe')
158
+ result = pipe(prompt=prompt, image=image, mask_image=mask, num_inference_steps=10, num_images_per_prompt=1)
159
+ images = result['images']
160
+ return images
161
+
162
+
163
  class ImageBody(BaseModel):
164
  image: str
165
  threshold: float = 0.15
 
188
  return {"ok": True, "status": "FINISHED", "result": img_str}
189
 
190
 
191
+ @app.websocket("/ws-inpaint")
192
+ async def inpaint_websocket_endpoint(websocket: WebSocket):
193
+ connectionManager = app.package.get('connectionManager')
194
+ await connectionManager.connect(websocket)
195
+ await connectionManager.send_json({"ok": True, "status": "CONNECTED"}, websocket)
196
+ while True:
197
+ try:
198
+ data: ImageBody = await connectionManager.receive_json(websocket)
199
+ if (data is None):
200
+ # Wait for data
201
+ if not connectionManager.isConnected(websocket):
202
+ break
203
+ if connectionManager.shouldDisconnect(websocket):
204
+ await websocket.close()
205
+ connectionManager.disconnect(websocket)
206
+ break
207
+ continue
208
+
209
+ image_data: str = data.get('image')
210
+ mask_data: str = data.get('mask')
211
+ prompt: str = data.get('prompt')
212
+
213
+ await connectionManager.send_json({"ok": True, "status": "STARTED"}, websocket)
214
+
215
+ # Extract base64 from mask and convert to PIL.Image
216
+ if (',' in image_data):
217
+ image = Image.open(BytesIO(base64.b64decode(image_data.split(',')[1])))
218
+ else:
219
+ image = Image.open(BytesIO(base64.b64decode(image_data)))
220
+
221
+ # Extract base64 from mask and convert to PIL.Image
222
+ if (',' in mask_data):
223
+ mask = Image.open(BytesIO(base64.b64decode(mask_data.split(',')[1])))
224
+ else:
225
+ mask = Image.open(BytesIO(base64.b64decode(mask_data)))
226
+
227
+ # Resize image and mask to 512x512
228
+ image = crop_image(resize_image(image, 512, 512))
229
+ mask = crop_image(resize_image(image, 512, 512))
230
+
231
+ pipe = app.package.get('pipe')
232
+ result = pipe(prompt=prompt, image=image, mask_image=mask, num_inference_steps=10, num_images_per_prompt=1)
233
+ images = result['images']
234
+
235
+ # Convert the result to base64 and send the json back
236
+ result_array = []
237
+ for image in images:
238
+ buffered = BytesIO()
239
+ image.save(buffered, format="JPEG")
240
+ img_str = 'data:image/jpeg;base64,' + base64.b64encode(buffered.getvalue()).decode("utf-8")
241
+ result_array.append(img_str)
242
+
243
+ await connectionManager.send_json({"ok": True, "status": "FINISHED", "result": result_array}, websocket)
244
+
245
+ await websocket.close()
246
+ connectionManager.disconnect(websocket)
247
+ except WebSocketDisconnect:
248
+ connectionManager.disconnect(websocket)
249
+ break
250
+
251
  @app.websocket("/ws")
252
  async def websocket_endpoint(websocket: WebSocket):
253
  connectionManager = app.package.get('connectionManager')
 
255
  await connectionManager.send_json({"ok": True, "status": "CONNECTED"}, websocket)
256
  while True:
257
  try:
258
+ data: ImageBody = await connectionManager.receive_json(websocket)
259
  if (data is None):
260
  # Wait for data
261
  if not connectionManager.isConnected(websocket):
 
265
  connectionManager.disconnect(websocket)
266
  break
267
  continue
268
+
269
  image: str = data.get('image')
270
  threshold: float = data.get('threshold') or 0.15
271
  num_objects: int = data.get('num_objects') or 1
 
285
  await websocket.close()
286
  connectionManager.disconnect(websocket)
287
  except WebSocketDisconnect:
 
288
  connectionManager.disconnect(websocket)
289
  break
290
 
config.py CHANGED
@@ -8,8 +8,6 @@ import torch
8
 
9
  # Config that serves all environment
10
  GLOBAL_CONFIG = {
11
- "MODEL_PATH": "../model/model.pt",
12
- "SCALAR_PATH": "../model/scaler.joblib",
13
  "USE_CUDE_IF_AVAILABLE": True,
14
  "ROUND_DIGIT": 6
15
  }
 
8
 
9
  # Config that serves all environment
10
  GLOBAL_CONFIG = {
 
 
11
  "USE_CUDE_IF_AVAILABLE": True,
12
  "ROUND_DIGIT": 6
13
  }
connectionManager.py CHANGED
@@ -1,8 +1,7 @@
1
  from fastapi import WebSocket
2
 
3
  from datetime import datetime
4
- import json
5
- from typing import Dict, List
6
 
7
  class Connection:
8
  websocket: WebSocket
@@ -19,6 +18,7 @@ class ConnectionManager:
19
  self.active_connections: List[Connection] = []
20
 
21
  async def connect(self, websocket: WebSocket):
 
22
  await websocket.accept()
23
  # Add connection time and websocket to active connections
24
  self.active_connections.append(Connection(websocket=websocket, connection_time=datetime.now()))
@@ -33,16 +33,20 @@ class ConnectionManager:
33
  for connection in self.active_connections:
34
  if connection.websocket == websocket:
35
  if (datetime.now() - connection.connection_time).total_seconds() > self.timeout:
 
36
  return True
37
  return False
38
 
39
  async def receive_json(self, websocket: WebSocket):
40
  if not self.isConnected(websocket):
41
  return None
 
42
  data = await websocket.receive_json()
 
43
  return data
44
 
45
  def disconnect(self, websocket: WebSocket):
 
46
  for connection in self.active_connections:
47
  if connection.websocket == websocket:
48
  self.active_connections.remove(connection)
@@ -50,6 +54,7 @@ class ConnectionManager:
50
  return False
51
 
52
  async def send_json(self, json, websocket: WebSocket):
 
53
  # Only send the message if the connection is still active
54
  if self.isConnected(websocket):
55
  await websocket.send_json(json)
 
1
  from fastapi import WebSocket
2
 
3
  from datetime import datetime
4
+ from typing import List
 
5
 
6
  class Connection:
7
  websocket: WebSocket
 
18
  self.active_connections: List[Connection] = []
19
 
20
  async def connect(self, websocket: WebSocket):
21
+ print('Connecting')
22
  await websocket.accept()
23
  # Add connection time and websocket to active connections
24
  self.active_connections.append(Connection(websocket=websocket, connection_time=datetime.now()))
 
33
  for connection in self.active_connections:
34
  if connection.websocket == websocket:
35
  if (datetime.now() - connection.connection_time).total_seconds() > self.timeout:
36
+ print('Disconnecting...')
37
  return True
38
  return False
39
 
40
  async def receive_json(self, websocket: WebSocket):
41
  if not self.isConnected(websocket):
42
  return None
43
+ print('Receiving...')
44
  data = await websocket.receive_json()
45
+ print('Received')
46
  return data
47
 
48
  def disconnect(self, websocket: WebSocket):
49
+ print('Disconnecting...')
50
  for connection in self.active_connections:
51
  if connection.websocket == websocket:
52
  self.active_connections.remove(connection)
 
54
  return False
55
 
56
  async def send_json(self, json, websocket: WebSocket):
57
+ print('Sending JSON...')
58
  # Only send the message if the connection is still active
59
  if self.isConnected(websocket):
60
  await websocket.send_json(json)
model.py CHANGED
@@ -18,9 +18,8 @@ from CutLER.third_party.TokenCut.unsupervised_saliency_detection import metric
18
 
19
 
20
  class Model:
21
- def __init__(self):
22
- self.device = torch.device(
23
- 'cuda:0' if torch.cuda.is_available() else 'cpu')
24
  self.backbone = self.load_backbone()
25
 
26
  def load_backbone(self):
 
18
 
19
 
20
  class Model:
21
+ def __init__(self, device: str):
22
+ self.device = torch.device(device)
 
23
  self.backbone = self.load_backbone()
24
 
25
  def load_backbone(self):
requirements.txt CHANGED
@@ -6,6 +6,9 @@ scikit-image==0.19.2
6
  torch==1.13.1
7
  torchvision==0.14.1
8
  tqdm==4.64.1
 
 
 
9
 
10
  pycocotools==2.0.6
11
  fastapi==0.94.1
 
6
  torch==1.13.1
7
  torchvision==0.14.1
8
  tqdm==4.64.1
9
+ diffusers==0.14.0
10
+ transformers==4.27.1
11
+ accelerate==0.17.1
12
 
13
  pycocotools==2.0.6
14
  fastapi==0.94.1