ayushpfullstack commited on
Commit
8f78b88
·
verified ·
1 Parent(s): e6e1288

Initial Commit

Browse files
Files changed (4) hide show
  1. Dockerfile +29 -0
  2. README.md +5 -7
  3. main.py +114 -0
  4. requirements.txt +12 -0
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+
3
+ # Start with a base image that has Python and NVIDIA's CUDA drivers
4
+ FROM nvidia/cuda:12.1.1-devel-ubuntu22.04
5
+
6
+ # Set up the environment
7
+ ENV DEBIAN_FRONTEND=noninteractive
8
+ ENV PYTHONUNBUFFERED=1
9
+
10
+ # Install Python and pip
11
+ RUN apt-get update && apt-get install -y \
12
+ python3.10 python3-pip \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Set the working directory inside the container
16
+ WORKDIR /app
17
+
18
+ # Copy and install Python dependencies
19
+ COPY requirements.txt .
20
+ RUN pip3 install --no-cache-dir -r requirements.txt
21
+
22
+ # Copy your main application code
23
+ COPY main.py .
24
+
25
+ # Expose the port your API will run on
26
+ EXPOSE 8000
27
+
28
+ # The command to start the Uvicorn server when the container runs
29
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -1,11 +1,9 @@
1
  ---
2
- title: Virtual Staging Api
3
- emoji: 📊
4
- colorFrom: blue
5
  colorTo: blue
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
 
1
  ---
2
+ title: Virtual Staging API
3
+ emoji: 🛋️
4
+ colorFrom: pink
5
  colorTo: blue
6
  sdk: docker
7
+ app_port: 8000
8
+ hardware: nvidia-t4-small
9
  ---
 
 
main.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+ import torch
3
+ import numpy as np
4
+ import cv2
5
+ from PIL import Image
6
+ import base64
7
+ import io
8
+ from fastapi import FastAPI, HTTPException
9
+ from pydantic import BaseModel
10
+ from contextlib import asynccontextmanager
11
+ from transformers import pipeline
12
+ from diffusers import StableDiffusionControlNetInpaintPipeline, ControlNetModel, UniPCMultepScheduler
13
+
14
+ # --- API Data Models ---
15
+ class StagingRequest(BaseModel):
16
+ image_base64: str
17
+ prompt: str
18
+ negative_prompt: str = "blurry, low quality, unrealistic, distorted, ugly, watermark, text, messy, deformed, extra windows, extra doors"
19
+ seed: int = 1234
20
+
21
+ # --- Global State & Model Loading ---
22
+ models = {}
23
+
24
+ @asynccontextmanager
25
+ async def lifespan(app: FastAPI):
26
+ # STARTUP: Load all models
27
+ print("🚀 Server starting up: Loading AI models...")
28
+ device = "cuda" if torch.cuda.is_available() else "cpu"
29
+ torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
30
+
31
+ models['segmentation_pipeline'] = pipeline("image-segmentation", model="Intel/dpt-large-ade", device=device)
32
+ models['depth_estimator'] = pipeline("depth-estimation", model="Intel/dpt-hybrid-midas", device=device)
33
+
34
+ controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-depth", torch_dtype=torch_dtype)
35
+
36
+ models['inpainting_pipe'] = StableDiffusionControlNetInpaintPipeline.from_pretrained(
37
+ "runwayml/stable-diffusion-v1-5",
38
+ controlnet=controlnet,
39
+ torch_dtype=torch_dtype,
40
+ safety_checker=None
41
+ ).to(device)
42
+ models['inpainting_pipe'].scheduler = UniPCMultistepScheduler.from_config(models['inpainting_pipe'].scheduler.config)
43
+
44
+ print("✅ All models loaded and ready.")
45
+ yield
46
+ # SHUTDOWN: Clean up
47
+ print("⚡ Server shutting down.")
48
+ models.clear()
49
+
50
+ app = FastAPI(lifespan=lifespan)
51
+
52
+ # --- Helper Functions (Core Logic) ---
53
+ def create_precise_mask(image_pil: Image.Image) -> Image.Image:
54
+ segments = models['segmentation_pipeline'](image_pil)
55
+ W, H = image_pil.size
56
+ inclusion_mask_np = np.zeros((H, W), dtype=np.uint8)
57
+ exclusion_mask_np = np.zeros((H, W), dtype=np.uint8)
58
+ inclusion_labels = {"wall", "floor", "ceiling"}
59
+ base_exclusion_labels = {"door", "window", "windowpane", "window blind"}
60
+ insert_labels = {"painting", "picture", "shelf", "showcase", "cabinet", "mirror", "television", "radiator"}
61
+ walls, inserts = [], []
62
+ for segment in segments:
63
+ label, mask = segment['label'], np.array(segment['mask'])
64
+ if label in inclusion_labels:
65
+ inclusion_mask_np = np.maximum(inclusion_mask_np, mask)
66
+ if label == "wall": walls.append(mask)
67
+ if label in base_exclusion_labels:
68
+ exclusion_mask_np = np.maximum(exclusion_mask_np, mask)
69
+ if label in insert_labels:
70
+ inserts.append(mask)
71
+ for insert_mask in inserts:
72
+ for wall_mask in walls:
73
+ if np.all((wall_mask >= insert_mask)[insert_mask > 0]):
74
+ exclusion_mask_np = np.maximum(exclusion_mask_np, insert_mask)
75
+ break
76
+ raw_mask_np = np.copy(inclusion_mask_np); raw_mask_np[exclusion_mask_np > 0] = 0
77
+ mask_filled_np = cv2.morphologyEx(raw_mask_np, cv2.MORPH_CLOSE, np.ones((10,10),np.uint8))
78
+ return Image.fromarray(mask_filled_np)
79
+
80
+ def generate_depth_map(image_pil: Image.Image) -> Image.Image:
81
+ predicted_depth = models['depth_estimator'](image_pil)['predicted_depth']
82
+ depth_map_np = predicted_depth.cpu().numpy()
83
+ depth_map_np = (depth_map_np - depth_map_np.min()) / (depth_map_np.max() - depth_map_np.min()) * 255.0
84
+ depth_map_np = depth_map_np.astype(np.uint8)
85
+ return Image.fromarray(np.concatenate([depth_map_np[..., None]] * 3, axis=-1))
86
+
87
+ # --- API Endpoints ---
88
+ @app.get("/")
89
+ def read_root():
90
+ return {"status": "Virtual Staging API is running."}
91
+
92
+ @app.post("/furnish-room/")
93
+ async def furnish_room(request: StagingRequest):
94
+ try:
95
+ image_bytes = base64.b64decode(request.image_base64)
96
+ init_image_pil = Image.open(io.BytesIO(image_bytes)).convert("RGB").resize((512, 512))
97
+
98
+ mask_image_pil = create_precise_mask(init_image_pil)
99
+ control_image_pil = generate_depth_map(init_image_pil)
100
+
101
+ generator = torch.Generator(device="cuda").manual_seed(request.seed)
102
+ final_image = models['inpainting_pipe'](
103
+ prompt=request.prompt, negative_prompt=request.negative_prompt, image=init_image_pil,
104
+ mask_image=mask_image_pil, control_image=control_image_pil,
105
+ num_inference_steps=30, guidance_scale=8.0, generator=generator,
106
+ ).images[0]
107
+
108
+ buffered = io.BytesIO()
109
+ final_image.save(buffered, format="PNG")
110
+ img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
111
+
112
+ return {"result_image_base64": img_str}
113
+ except Exception as e:
114
+ raise HTTPException(status_code=500, detail=str(e))
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # requirements.txt
2
+ diffusers
3
+ transformers
4
+ accelerate
5
+ torch
6
+ matplotlib
7
+ timm
8
+ scipy
9
+ opencv-python-headless
10
+ fastapi
11
+ uvicorn[standard]
12
+ python-multipart