makakwastaken commited on
Commit
7857874
·
1 Parent(s): 2093915

Added subprocess running uvicorn

Browse files
Files changed (8) hide show
  1. .gitmodules +1 -1
  2. app.py +1 -0
  3. config.py +62 -0
  4. main.py +142 -0
  5. model.py +11 -11
  6. predict.py +27 -0
  7. requirements.txt +4 -1
  8. start.py +5 -0
.gitmodules CHANGED
@@ -1,3 +1,3 @@
1
  [submodule "CutLER"]
2
  path = CutLER
3
- url = https://github.com/facebookresearch/CutLER
 
1
  [submodule "CutLER"]
2
  path = CutLER
3
+ url = https://github.com/Ad-Visual/CutLER
app.py CHANGED
@@ -33,6 +33,7 @@ DESCRIPTION = 'This is an unofficial demo for https://github.com/facebookresearc
33
 
34
  paths = sorted(pathlib.Path('CutLER/maskcut/imgs').glob('*.jpg'))
35
  demo = gr.Interface(fn=run,
 
36
  inputs=[
37
  gr.Image(label='Input image', type='filepath'),
38
  gr.Slider(
 
33
 
34
  paths = sorted(pathlib.Path('CutLER/maskcut/imgs').glob('*.jpg'))
35
  demo = gr.Interface(fn=run,
36
+ enable_queue=True,
37
  inputs=[
38
  gr.Image(label='Input image', type='filepath'),
39
  gr.Slider(
config.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ #!/usr/bin/env python
3
+ # -*- coding: utf-8 -*-
4
+
5
+ import os
6
+ import torch
7
+
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
+ }
16
+
17
+ # Environment specific config, or overwrite of GLOBAL_CONFIG
18
+ ENV_CONFIG = {
19
+ "development": {
20
+ "DEBUG": True
21
+ },
22
+
23
+ "staging": {
24
+ "DEBUG": True
25
+ },
26
+
27
+ "production": {
28
+ "DEBUG": False,
29
+ "ROUND_DIGIT": 3
30
+ }
31
+ }
32
+
33
+
34
+ def get_config() -> dict:
35
+ """
36
+ Get config based on running environment
37
+ :return: dict of config
38
+ """
39
+
40
+ # Determine running environment
41
+ ENV = os.environ['PYTHON_ENV'] if 'PYTHON_ENV' in os.environ else 'development'
42
+ ENV = ENV or 'development'
43
+
44
+ # raise error if environment is not expected
45
+ if ENV not in ENV_CONFIG:
46
+ raise EnvironmentError(f'Config for envirnoment {ENV} not found')
47
+
48
+ config = GLOBAL_CONFIG.copy()
49
+ config.update(ENV_CONFIG[ENV])
50
+
51
+ config['ENV'] = ENV
52
+ config['DEVICE'] = 'cuda' if torch.cuda.is_available() and config['USE_CUDE_IF_AVAILABLE'] else 'cpu'
53
+
54
+ return config
55
+
56
+ # load config for import
57
+ CONFIG = get_config()
58
+
59
+ if __name__ == '__main__':
60
+ # for debugging
61
+ import json
62
+ print(json.dumps(CONFIG, indent=4))
main.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.logger import logger
6
+
7
+ from model import Model
8
+ import base64
9
+ from io import BytesIO
10
+ from pydantic import BaseModel
11
+ from config import CONFIG
12
+
13
+ from predict import predict
14
+
15
+ # About
16
+ import torch
17
+ import os
18
+ import sys
19
+
20
+ # Server Framework
21
+ import uvicorn
22
+
23
+ app = FastAPI(
24
+ title="ML Model",
25
+ description="Description of the ML Model",
26
+ version="0.0.1",
27
+ terms_of_service=None,
28
+ contact=None,
29
+ license_info=None
30
+ )
31
+
32
+ # Allow CORS for local debugging
33
+ app.add_middleware(CORSMiddleware, allow_origins=["*"])
34
+
35
+ @app.on_event("startup")
36
+ async def startup_event():
37
+ """
38
+ Initialize FastAPI and add variables
39
+ """
40
+
41
+ logger.info('Running envirnoment: {}'.format(CONFIG['ENV']))
42
+ logger.info('PyTorch using device: {}'.format(CONFIG['DEVICE']))
43
+
44
+ # Initialize the pytorch model
45
+ model = Model()
46
+
47
+ # add model and other preprocess tools too app state
48
+ app.package = {
49
+ "model": model
50
+ }
51
+
52
+ @app.get("/ping")
53
+ def ping():
54
+ return {"ok": True, "message": "Pong"}
55
+
56
+ @app.get("/about")
57
+ def show_about():
58
+ """
59
+ Get deployment information, for debugging
60
+ """
61
+
62
+ logger.info('API /about called')
63
+
64
+ def bash(command):
65
+ output = os.popen(command).read()
66
+ return output
67
+
68
+ return {
69
+ "sys.version": sys.version,
70
+ "torch.__version__": torch.__version__,
71
+ "torch.cuda.is_available()": torch.cuda.is_available(),
72
+ "torch.version.cuda": torch.version.cuda,
73
+ "torch.backends.cudnn.version()": torch.backends.cudnn.version(),
74
+ "torch.backends.cudnn.enabled": torch.backends.cudnn.enabled,
75
+ "nvidia-smi": bash('nvidia-smi')
76
+ }
77
+
78
+ class ImageBody(BaseModel):
79
+ image: str
80
+ threshold: float = 0.15
81
+ num_objects: int = 1
82
+
83
+ @app.post("/predict")
84
+ async def do_predict(body: ImageBody):
85
+ """
86
+ Perform prediction on input data
87
+ """
88
+
89
+ logger.info('API predict called')
90
+
91
+ image: str = body.image
92
+ threshold: float = body.threshold
93
+ num_objects: int = body.num_objects
94
+
95
+ # Run the algorithm
96
+ result = predict(app.package, image, threshold, num_objects)
97
+
98
+ # Convert the result to base64 and send the json back
99
+ buffered = BytesIO()
100
+ result.save(buffered, format="JPEG")
101
+ img_str = 'data:image/jpeg;base64,' + base64.b64encode(buffered.getvalue()).decode("utf-8")
102
+
103
+ return {"ok": True, "status": "FINISHED", "result": img_str}
104
+
105
+
106
+ @app.websocket("/ws")
107
+ async def websocket_endpoint(websocket: WebSocket):
108
+ await websocket.accept()
109
+ while True:
110
+ try:
111
+ data = await websocket.receive_json()
112
+ image: str = data.get('image')
113
+ threshold: float = data.get('threshold') or 0.15
114
+ num_objects: int = data.get('num_objects') or 1
115
+
116
+ await websocket.send_json({"ok": True, "status": "STARTED"})
117
+
118
+ if image == None:
119
+ await websocket.send_json({
120
+ "ok": False,
121
+ "status": "ERROR",
122
+ "message": "No image provided"
123
+ })
124
+ break
125
+
126
+ # Run the algorithm
127
+ result = predict(app.package, image, threshold, num_objects)
128
+
129
+ # Convert the result to base64 and send the json back
130
+ buffered = BytesIO()
131
+ result.save(buffered, format="JPEG")
132
+ img_str = 'data:image/jpeg;base64,' + base64.b64encode(buffered.getvalue()).decode("utf-8")
133
+
134
+ await websocket.send_json({"ok": True, "status": "FINISHED", "result": img_str})
135
+
136
+ await websocket.close()
137
+ except WebSocketDisconnect:
138
+ break
139
+
140
+ if __name__ == '__main__':
141
+ # server api
142
+ uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
model.py CHANGED
@@ -1,20 +1,20 @@
1
  # This file is adapted from https://github.com/facebookresearch/CutLER/blob/077938c626341723050a1971107af552a6ca6697/maskcut/demo.py
2
  # The original license file is the file named LICENSE.CutLER in this repo.
3
 
 
4
  import sys
 
 
5
 
6
  import numpy as np
7
  import PIL.Image as Image
8
  import torch
9
  from scipy import ndimage
10
 
11
- sys.path.append('CutLER/maskcut/')
12
- sys.path.append('CutLER/')
13
- import dino
14
- from colormap import random_color
15
- from crf import densecrf
16
- from maskcut import maskcut
17
- from third_party.TokenCut.unsupervised_saliency_detection import metric
18
 
19
 
20
  class Model:
@@ -33,21 +33,21 @@ class Model:
33
  feat_dim = 768
34
 
35
  # extract patch features with a pretrained DINO model
36
- backbone = dino.ViTFeat(url, feat_dim, vit_arch, vit_feat, patch_size)
37
  backbone.eval()
38
  backbone.to(self.device)
39
  return backbone
40
 
41
- def __call__(self, img_path, tau, n, fixed_size=480):
42
  # get pseudo-masks with MaskCut
43
- bipartitions, _, I_new = maskcut(img_path,
44
  self.backbone,
45
  self.backbone.patch_size,
46
  tau,
47
  N=n,
48
  fixed_size=fixed_size,
49
  cpu=self.device.type == 'cpu')
50
- I = Image.open(img_path).convert('RGB')
51
  width, height = I.size
52
  pseudo_mask_list = []
53
  for idx, bipartition in enumerate(bipartitions):
 
1
  # This file is adapted from https://github.com/facebookresearch/CutLER/blob/077938c626341723050a1971107af552a6ca6697/maskcut/demo.py
2
  # The original license file is the file named LICENSE.CutLER in this repo.
3
 
4
+ import os
5
  import sys
6
+ sys.path.append('./CutLER/')
7
+ sys.path.append('./CutLER/maskcut/')
8
 
9
  import numpy as np
10
  import PIL.Image as Image
11
  import torch
12
  from scipy import ndimage
13
 
14
+ from CutLER.maskcut.dino import ViTFeat # model
15
+ from CutLER.maskcut.crf import densecrf
16
+ from CutLER.maskcut.maskcut import maskcut
17
+ from CutLER.third_party.TokenCut.unsupervised_saliency_detection import metric
 
 
 
18
 
19
 
20
  class Model:
 
33
  feat_dim = 768
34
 
35
  # extract patch features with a pretrained DINO model
36
+ backbone = ViTFeat(url, feat_dim, vit_arch, vit_feat, patch_size)
37
  backbone.eval()
38
  backbone.to(self.device)
39
  return backbone
40
 
41
+ def __call__(self, image, tau, n, fixed_size=480):
42
  # get pseudo-masks with MaskCut
43
+ bipartitions, _, I_new = maskcut(image,
44
  self.backbone,
45
  self.backbone.patch_size,
46
  tau,
47
  N=n,
48
  fixed_size=fixed_size,
49
  cpu=self.device.type == 'cpu')
50
+ I = image.convert('RGB')
51
  width, height = I.size
52
  pseudo_mask_list = []
53
  for idx, bipartition in enumerate(bipartitions):
predict.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ from io import BytesIO
3
+ from PIL import Image
4
+ import numpy as np
5
+ from model import Model
6
+
7
+ def predict(package, image_base64: str, threshold: float, num_objects: int):
8
+ # Decode the image from base64 to PIL.Image
9
+ # We use BytesIO to convert the base64 to bytes
10
+ base64_split = image_base64.split(',')[1]
11
+ buf = BytesIO(base64.b64decode(base64_split))
12
+
13
+ image = Image.open(buf)
14
+
15
+ # Get the image path from tmp_image
16
+ canvas = Image.new('RGB', image.size, (0, 0, 0))
17
+
18
+ # We copy the image that and fill it with black, to get the dimensions
19
+ rgb = np.array(canvas)
20
+ model = package.get('model')
21
+ masks = model(image, threshold, num_objects)
22
+
23
+ for mask in masks:
24
+ fg = mask > 0.5
25
+ rgb[fg] = 255
26
+
27
+ return Image.fromarray(rgb)
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- git+https://github.com/lucasb-eyer/pydensecrf@0d53acb
2
  gradio==3.16.2
3
  numpy==1.23.5
4
  opencv-python==4.6.0.66
@@ -7,3 +7,6 @@ scikit-image==0.19.2
7
  torch==1.13.1
8
  torchvision==0.14.1
9
  tqdm==4.64.1
 
 
 
 
1
+ git+https://github.com/lucasb-eyer/pydensecrf
2
  gradio==3.16.2
3
  numpy==1.23.5
4
  opencv-python==4.6.0.66
 
7
  torch==1.13.1
8
  torchvision==0.14.1
9
  tqdm==4.64.1
10
+ fastapi==0.94.0
11
+ pydantic==1.8.2
12
+ uvicorn==0.21.0
start.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import subprocess
2
+
3
+ # Start HF-MaskCut with uvicorn
4
+
5
+ subprocess.run(["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"])