Sruthi007 commited on
Commit
1275546
·
0 Parent(s):

Initial FastAPI Space (code only)

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ models/
5
+ .cache/
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ RUN apt-get update && apt-get install -y \
4
+ git \
5
+ libgl1 \
6
+ libglib2.0-0 \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ WORKDIR /app
10
+
11
+ COPY requirements.txt .
12
+
13
+ RUN pip install --no-cache-dir --upgrade pip \
14
+ && pip install --no-cache-dir -r requirements.txt
15
+
16
+ COPY . .
17
+
18
+ EXPOSE 7860
19
+
20
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Interior Fastapi
3
+ emoji: 👀
4
+ colorFrom: yellow
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import hashlib
4
+ import numpy as np
5
+ import cv2
6
+ import torch
7
+ from PIL import Image
8
+ from fastapi import FastAPI, UploadFile, File, Form
9
+ from fastapi.responses import Response, JSONResponse, HTMLResponse, FileResponse
10
+ from skimage.measure import label, regionprops
11
+ from sklearn.decomposition import PCA
12
+
13
+ from transformers import (
14
+ OneFormerProcessor,
15
+ OneFormerForUniversalSegmentation,
16
+ Mask2FormerForUniversalSegmentation,
17
+ AutoImageProcessor
18
+ )
19
+ # =========================================================
20
+ # CONFIG
21
+ # =========================================================
22
+
23
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
+ ALPHA = 0.65
25
+
26
+ SEMANTIC_MODEL = "shi-labs/oneformer_ade20k_swin_large"
27
+ INSTANCE_MODEL = "facebook/mask2former-swin-large-coco-instance"
28
+ TEXTURE_ROOT = "textures"
29
+
30
+ OBJECT_CLASSES = {
31
+ "Wall": {"semantic": ["wall"], "panels": True},
32
+ "Floor": {"semantic": ["floor"], "panels": False},
33
+ "Door": {"semantic": ["door"], "panels": False},
34
+ "Cabinet": {"semantic": ["cabinet", "cupboard", "wardrobe"], "panels": True},
35
+ "Counter": {"semantic": ["counter"], "panels": False},
36
+ "Countertop": {"semantic": ["countertop", "worktop"], "panels": False},
37
+ }
38
+
39
+ REMOVE_FROM_WALL_FLOOR = {
40
+ "door", "window", "cabinet",
41
+ "counter", "countertop", "island"
42
+ }
43
+
44
+ # =========================================================
45
+ # FASTAPI
46
+ # =========================================================
47
+
48
+ app = FastAPI(title="Interior Texture API")
49
+
50
+ # =========================================================
51
+ # GLOBAL CACHES (SAFE IF 1 WORKER)
52
+ # =========================================================
53
+
54
+ DETECTION_CACHE = {} # image_hash → (image, objects)
55
+ CURRENT_STATE = { # single user state
56
+ "image_hash": None,
57
+ "image": None,
58
+ "objects": None,
59
+ "object_textures": {},
60
+ "panel_textures": {}
61
+ }
62
+
63
+ # =========================================================
64
+ # LOAD MODELS ONCE
65
+ # =========================================================
66
+
67
+ print("Loading models...")
68
+
69
+ sem_proc = OneFormerProcessor.from_pretrained(SEMANTIC_MODEL)
70
+ sem_model = OneFormerForUniversalSegmentation.from_pretrained(
71
+ SEMANTIC_MODEL
72
+ ).to(DEVICE).eval()
73
+
74
+ inst_proc = AutoImageProcessor.from_pretrained(INSTANCE_MODEL)
75
+ inst_model = Mask2FormerForUniversalSegmentation.from_pretrained(
76
+ INSTANCE_MODEL
77
+ ).to(DEVICE).eval()
78
+
79
+ print("Models loaded")
80
+
81
+ # =========================================================
82
+ # UTILITIES
83
+ # =========================================================
84
+
85
+ def extract_semantic_mask(seg_map, id2label, keywords):
86
+ mask = np.zeros_like(seg_map, dtype=np.uint8)
87
+ for cid, name in id2label.items():
88
+ if any(k in name.lower() for k in keywords):
89
+ mask[seg_map == cid] = 255
90
+ return mask
91
+
92
+
93
+ def subtract_instances(mask, instances, remove_labels, coco_id2label):
94
+ cleaned = mask.copy()
95
+ inst_map = instances["segmentation"].cpu().numpy()
96
+
97
+ for seg in instances["segments_info"]:
98
+ if seg.get("score", 1.0) < 0.7:
99
+ continue
100
+ label_name = coco_id2label.get(seg["label_id"], "")
101
+ if label_name in remove_labels:
102
+ cleaned[inst_map == seg["id"]] = 0
103
+
104
+ return cleaned
105
+
106
+
107
+ def edge_cleanup(mask, image_np):
108
+ gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY)
109
+ edges = cv2.Canny(gray, 80, 160)
110
+ mask[edges > 0] = 0
111
+
112
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
113
+ mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
114
+ mask = cv2.medianBlur(mask, 7)
115
+ return mask
116
+
117
+
118
+ def extract_panels(mask, min_ratio=0.003):
119
+ lbl = label(mask)
120
+ panels = []
121
+ for r in regionprops(lbl):
122
+ if r.area > mask.size * min_ratio:
123
+ p = np.zeros_like(mask)
124
+ p[lbl == r.label] = 255
125
+ panels.append(p.astype(bool))
126
+ return panels
127
+
128
+
129
+ def detect_objects(image_np):
130
+ inputs = sem_proc(
131
+ images=image_np,
132
+ task_inputs=["semantic"],
133
+ return_tensors="pt"
134
+ ).to(DEVICE)
135
+
136
+ with torch.no_grad():
137
+ sem_out = sem_model(**inputs)
138
+
139
+ seg_map = sem_proc.post_process_semantic_segmentation(
140
+ sem_out,
141
+ target_sizes=[image_np.shape[:2]]
142
+ )[0].cpu().numpy()
143
+
144
+ inst_inputs = inst_proc(images=image_np, return_tensors="pt").to(DEVICE)
145
+ with torch.no_grad():
146
+ inst_out = inst_model(**inst_inputs)
147
+
148
+ instances = inst_proc.post_process_instance_segmentation(
149
+ inst_out,
150
+ target_sizes=[image_np.shape[:2]]
151
+ )[0]
152
+
153
+ objects = {}
154
+ for obj, cfg in OBJECT_CLASSES.items():
155
+ mask = extract_semantic_mask(
156
+ seg_map, sem_model.config.id2label, cfg["semantic"]
157
+ )
158
+
159
+ if np.count_nonzero(mask) < image_np.size * 0.002:
160
+ continue
161
+
162
+ if obj in {"Wall", "Floor"}:
163
+ mask = subtract_instances(
164
+ mask, instances,
165
+ REMOVE_FROM_WALL_FLOOR,
166
+ inst_model.config.id2label
167
+ )
168
+ mask = edge_cleanup(mask, image_np)
169
+
170
+ panels = extract_panels(mask) if cfg["panels"] else [mask.astype(bool)]
171
+ objects[obj] = panels
172
+
173
+ return objects
174
+
175
+
176
+ def detect_cached(image_bytes: bytes):
177
+ image_hash = hashlib.md5(image_bytes).hexdigest()
178
+
179
+ if image_hash in DETECTION_CACHE:
180
+ return image_hash, *DETECTION_CACHE[image_hash]
181
+
182
+ image = np.array(Image.open(io.BytesIO(image_bytes)).convert("RGB"))
183
+ objects = detect_objects(image)
184
+
185
+ DETECTION_CACHE[image_hash] = (image, objects)
186
+ return image_hash, image, objects
187
+
188
+
189
+ def apply_texture_panel(image, mask, texture, tile_type):
190
+ H, W = image.shape[:2]
191
+ tile_w, tile_h = (280, 560) if "12" in tile_type else (560, 560)
192
+
193
+ tile = cv2.resize(texture, (tile_w, tile_h), interpolation=cv2.INTER_NEAREST)
194
+ canvas = np.zeros((H, W, 3), dtype=np.uint8)
195
+
196
+ for y in range(0, H, tile_h):
197
+ for x in range(0, W, tile_w):
198
+ canvas[y:y+tile_h, x:x+tile_w] = tile[:H-y, :W-x]
199
+
200
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY).astype(np.float32) / 255.0
201
+ light = cv2.GaussianBlur(gray, (41, 41), 0)
202
+ light = np.repeat(light[:, :, None], 3, axis=2)
203
+
204
+ canvas = canvas.astype(np.float32)
205
+ canvas *= (0.75 + 0.25 * light)
206
+
207
+ out = image.astype(np.float32)
208
+ out[mask] = (1 - ALPHA) * out[mask] + ALPHA * canvas[mask]
209
+
210
+ return out.astype(np.uint8)
211
+
212
+ # =========================================================
213
+ # API ENDPOINTS
214
+ # =========================================================
215
+
216
+
217
+ @app.post("/upload-image")
218
+ async def upload_image(file: UploadFile = File(...)):
219
+ image_bytes = await file.read()
220
+ image_hash, image, objects = detect_cached(image_bytes)
221
+
222
+ CURRENT_STATE["image_hash"] = image_hash
223
+ CURRENT_STATE["image"] = image
224
+ CURRENT_STATE["objects"] = objects
225
+ CURRENT_STATE["object_textures"].clear()
226
+ CURRENT_STATE["panel_textures"].clear()
227
+
228
+ return {"objects": {k: len(v) for k, v in objects.items()}}
229
+
230
+ # =========================================================
231
+ # LIST TEXTURES FOR OBJECT
232
+ # =========================================================
233
+
234
+ @app.get("/textures/{object_name}")
235
+ def list_textures(object_name: str):
236
+ folder = os.path.join(TEXTURE_ROOT, object_name.lower())
237
+ if not os.path.isdir(folder):
238
+ return []
239
+
240
+ return [
241
+ f for f in os.listdir(folder)
242
+ if f.lower().endswith((".png", ".jpg", ".jpeg"))
243
+ ]
244
+
245
+ # =========================================================
246
+ # SERVE TEXTURE FILE
247
+ # =========================================================
248
+
249
+ @app.get("/texture-file/{object_name}/{filename}")
250
+ def get_texture_file(object_name: str, filename: str):
251
+ path = os.path.join(TEXTURE_ROOT, object_name.lower(), filename)
252
+ if not os.path.exists(path):
253
+ return JSONResponse({"error": "Texture not found"}, status_code=404)
254
+
255
+ return FileResponse(path)
256
+
257
+ # =========================================================
258
+ # APPLY TEXTURE
259
+ # =========================================================
260
+
261
+ @app.post("/apply-texture")
262
+ async def apply_texture(
263
+ object_name: str = Form(...),
264
+ filename: str = Form(...),
265
+ panel_index: int | None = Form(None),
266
+ tile_type: str = Form("12 x 24 inches")
267
+ ):
268
+ if CURRENT_STATE["image"] is None:
269
+ return JSONResponse(
270
+ {"error": "Upload image first"},
271
+ status_code=400
272
+ )
273
+
274
+ object_name = object_name.strip().title()
275
+
276
+ if object_name not in CURRENT_STATE["objects"]:
277
+ return JSONResponse(
278
+ {"error": f"{object_name} not detected in image"},
279
+ status_code=400
280
+ )
281
+
282
+ # 🔹 LOAD TEXTURE FROM DISK
283
+ texture_path = os.path.join(
284
+ TEXTURE_ROOT,
285
+ object_name.lower(),
286
+ filename
287
+ )
288
+
289
+ if not os.path.isfile(texture_path):
290
+ return JSONResponse(
291
+ {"error": f"Texture not found: {filename}"},
292
+ status_code=404
293
+ )
294
+
295
+ tex = np.array(
296
+ Image.open(texture_path).convert("RGB")
297
+ )
298
+
299
+ # 🔹 STORE TEXTURE
300
+ if panel_index is None:
301
+ CURRENT_STATE["object_textures"][object_name] = tex
302
+ else:
303
+ CURRENT_STATE["panel_textures"][(object_name, panel_index)] = tex
304
+
305
+ # 🔹 APPLY TEXTURES
306
+ output = CURRENT_STATE["image"].copy()
307
+
308
+ for obj, panels in CURRENT_STATE["objects"].items():
309
+ obj_tex = CURRENT_STATE["object_textures"].get(obj)
310
+ for i, mask in enumerate(panels):
311
+ tex_use = CURRENT_STATE["panel_textures"].get((obj, i), obj_tex)
312
+ if tex_use is not None:
313
+ output = apply_texture_panel(
314
+ output, mask, tex_use, tile_type
315
+ )
316
+
317
+ _, buf = cv2.imencode(
318
+ ".png",
319
+ cv2.cvtColor(output, cv2.COLOR_RGB2BGR)
320
+ )
321
+
322
+ return Response(buf.tobytes(), media_type="image/png")
323
+
324
+
325
+ # =========================================================
326
+ # UI (IMAGE UPLOAD + TEXTURE PREVIEW)
327
+ # =========================================================
328
+
329
+ @app.get("/", response_class=HTMLResponse)
330
+ def ui():
331
+ return """
332
+ <!DOCTYPE html>
333
+ <html>
334
+ <head>
335
+ <title>Interior Texture UI</title>
336
+ <style>
337
+ body { display:flex; margin:0; font-family:Arial }
338
+ #left { width:70%; padding:10px }
339
+ #right { width:30%; padding:10px; border-left:1px solid #ccc; overflow-y:auto }
340
+ .texture {
341
+ width:100%;
342
+ height:120px;
343
+ object-fit:cover;
344
+ margin-bottom:10px;
345
+ cursor:pointer;
346
+ border:2px solid transparent;
347
+ }
348
+ .texture:hover { border-color:#007bff }
349
+ </style>
350
+ </head>
351
+
352
+ <body>
353
+
354
+ <div id="left">
355
+ <h3>Upload Image</h3>
356
+ <input type="file" id="imgInput" />
357
+ <button onclick="uploadImage()">Upload</button>
358
+ <hr/>
359
+ <img id="result" width="100%" />
360
+ </div>
361
+
362
+ <div id="right">
363
+ <h3>Textures</h3>
364
+ <select id="object" onchange="loadTextures()">
365
+ <option>Wall</option>
366
+ <option>Floor</option>
367
+ <option>Door</option>
368
+ <option>Cabinet</option>
369
+ <option>Counter</option>
370
+ <option>Countertop</option>
371
+ </select>
372
+ <div id="textures"></div>
373
+ </div>
374
+
375
+ <script>
376
+ async function loadTextures() {
377
+ const obj = document.getElementById("object").value;
378
+ const res = await fetch(`/textures/${obj}`);
379
+ const files = await res.json();
380
+
381
+ const container = document.getElementById("textures");
382
+ container.innerHTML = "";
383
+
384
+ files.forEach(filename => {
385
+ const img = document.createElement("img");
386
+ img.src = `/texture-file/${obj}/${filename}`;
387
+ img.className = "texture";
388
+
389
+ img.onclick = () => applyTexture(obj, filename);
390
+ container.appendChild(img);
391
+ });
392
+ }
393
+
394
+ async function applyTexture(objectName, filename) {
395
+ const form = new FormData();
396
+ form.append("object_name", objectName);
397
+ form.append("filename", filename);
398
+ form.append("tile_type", "12 x 24 inches");
399
+
400
+ const res = await fetch("/apply-texture", {
401
+ method: "POST",
402
+ body: form
403
+ });
404
+
405
+ const img = document.getElementById("result");
406
+ img.src = URL.createObjectURL(await res.blob());
407
+ }
408
+ </script>
409
+
410
+
411
+ </body>
412
+ </html>
413
+ """
interior-texture-fastapi/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
interior-texture-fastapi/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Interior Texture Fastapi
3
+ emoji: 🚀
4
+ colorFrom: yellow
5
+ colorTo: gray
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+
4
+ torch
5
+ torchvision
6
+ torchaudio
7
+
8
+ transformers
9
+ accelerate
10
+
11
+ opencv-python
12
+ numpy
13
+ Pillow
14
+
15
+ scikit-image
16
+ scikit-learn