anktechsol commited on
Commit
8fee75c
·
verified ·
1 Parent(s): d9ddcf4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +154 -80
app.py CHANGED
@@ -1,32 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import io
2
  import json
3
- import math
4
  import os
5
  import tempfile
6
  import zipfile
7
- from pathlib import Path
8
  from dataclasses import dataclass
 
9
 
 
10
  import cv2
11
  import gradio as gr
12
  import numpy as np
13
- from PIL import Image, ImageDraw
14
- import cairosvg
 
 
 
 
15
 
16
  try:
17
- from shapely.geometry import Polygon, LineString, mapping
18
- from shapely.ops import unary_union
19
- SHAPELY_OK = True
20
- except Exception:
21
- SHAPELY_OK = False
22
 
23
 
24
  @dataclass
25
  class PlanConfig:
26
- min_room_area_ratio: float = 0.035
27
  wall_thickness: int = 6
28
  variation: str = "balanced"
29
  max_rooms: int = 6
 
30
 
31
 
32
  def pil_to_bgr(img: Image.Image) -> np.ndarray:
@@ -39,41 +72,84 @@ def bgr_to_pil(arr: np.ndarray) -> Image.Image:
39
  return Image.fromarray(rgb)
40
 
41
 
42
- def order_quad_points(pts):
43
- pts = np.array(pts, dtype=np.float32)
44
- s = pts.sum(axis=1)
45
- d = np.diff(pts, axis=1)
46
- return np.array([
47
- pts[np.argmin(s)],
48
- pts[np.argmin(d)],
49
- pts[np.argmax(s)],
50
- pts[np.argmax(d)],
51
- ], dtype=np.float32)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
 
54
  def preprocess_and_extract_boundary(img_bgr: np.ndarray):
55
  gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
56
  blur = cv2.GaussianBlur(gray, (5, 5), 0)
57
  _, th = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
58
-
59
  kernel = np.ones((5, 5), np.uint8)
60
  th = cv2.morphologyEx(th, cv2.MORPH_CLOSE, kernel, iterations=2)
61
  th = cv2.morphologyEx(th, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8), iterations=1)
62
-
63
  contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
64
  if not contours:
65
  raise ValueError("No closed shape detected. Use a darker outline on light background.")
66
-
67
  contour = max(contours, key=cv2.contourArea)
68
  peri = cv2.arcLength(contour, True)
69
  approx = cv2.approxPolyDP(contour, 0.01 * peri, True)
70
-
71
  mask = np.zeros_like(gray)
72
  cv2.drawContours(mask, [contour], -1, 255, thickness=cv2.FILLED)
73
-
74
  return th, contour[:, 0, :], approx[:, 0, :], mask
75
 
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def axis_aligned_split(bounds, orientation, pos, gap=8):
78
  x1, y1, x2, y2 = bounds
79
  if orientation == "v":
@@ -81,7 +157,7 @@ def axis_aligned_split(bounds, orientation, pos, gap=8):
81
  return [(x1, y1, x2, pos - gap), (x1, pos + gap, x2, y2)]
82
 
83
 
84
- def rect_inside_mask(rect, mask, fill_ratio=0.84):
85
  x1, y1, x2, y2 = [int(v) for v in rect]
86
  if x2 <= x1 or y2 <= y1:
87
  return False
@@ -94,12 +170,10 @@ def rect_inside_mask(rect, mask, fill_ratio=0.84):
94
  def generate_rooms(mask: np.ndarray, cfg: PlanConfig):
95
  ys, xs = np.where(mask > 0)
96
  x1, y1, x2, y2 = xs.min(), ys.min(), xs.max(), ys.max()
97
- root = (x1, y1, x2, y2)
98
- rooms = [root]
99
- target = {"compact": 4, "balanced": 5, "open": 3}.get(cfg.variation, 5)
100
  target = min(target, cfg.max_rooms)
101
-
102
- for _ in range(target * 2):
103
  if len(rooms) >= target:
104
  break
105
  areas = [max(0, (r[2] - r[0]) * (r[3] - r[1])) for r in rooms]
@@ -110,43 +184,51 @@ def generate_rooms(mask: np.ndarray, cfg: PlanConfig):
110
  if w * h < mask.shape[0] * mask.shape[1] * cfg.min_room_area_ratio:
111
  rooms.append(r)
112
  continue
113
-
114
  if cfg.variation == "open":
115
- orientation = "v" if w > h * 1.35 else "h"
 
116
  elif cfg.variation == "compact":
117
  orientation = "h" if h > w else "v"
 
118
  else:
119
  orientation = "v" if w >= h else "h"
120
-
121
- pos = int((rx1 + rx2) / 2) if orientation == "v" else int((ry1 + ry2) / 2)
122
- kids = axis_aligned_split(r, orientation, pos, gap=cfg.wall_thickness)
123
- valid = [k for k in kids if rect_inside_mask(k, mask)]
124
- if len(valid) == 2:
125
- rooms.extend(valid)
126
- else:
 
 
 
 
127
  rooms.append(r)
128
-
129
  return rooms
130
 
131
 
132
- def draw_floorplan(mask, rooms, contour_points, approx_points, cfg: PlanConfig):
133
- h, w = mask.shape
134
- canvas = np.full((h, w, 3), 255, np.uint8)
 
 
 
135
 
136
- cv2.drawContours(canvas, [contour_points.reshape(-1, 1, 2)], -1, (0, 0, 0), thickness=max(3, cfg.wall_thickness + 2))
137
 
 
 
 
138
  palette = [(239, 246, 255), (236, 253, 245), (255, 247, 237), (250, 245, 255), (254, 242, 242), (240, 253, 250)]
139
  room_json = []
140
 
141
  for i, r in enumerate(rooms):
142
  x1, y1, x2, y2 = [int(v) for v in r]
143
- room_mask = np.zeros_like(mask)
144
  cv2.rectangle(room_mask, (x1, y1), (x2, y2), 255, thickness=-1)
145
- room_mask = cv2.bitwise_and(room_mask, mask)
146
- contours, _ = cv2.findContours(room_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
147
- if not contours:
148
  continue
149
- c = max(contours, key=cv2.contourArea)
150
  cv2.drawContours(canvas, [c], -1, palette[i % len(palette)], thickness=cv2.FILLED)
151
  cv2.drawContours(canvas, [c], -1, (0, 0, 0), thickness=2)
152
  M = cv2.moments(c)
@@ -157,14 +239,14 @@ def draw_floorplan(mask, rooms, contour_points, approx_points, cfg: PlanConfig):
157
  room_json.append({"id": f"room_{i+1}", "bbox": [x1, y1, x2, y2], "area_px": int(cv2.contourArea(c)), "polygon": c[:, 0, :].tolist()})
158
 
159
  cv2.drawContours(canvas, [contour_points.reshape(-1, 1, 2)], -1, (0, 0, 0), thickness=max(3, cfg.wall_thickness + 2))
160
- cv2.polylines(canvas, [approx_points.reshape(-1, 1, 2)], True, (90, 90, 90), 1)
161
 
162
  result = {
163
  "boundary": contour_points.tolist(),
164
  "boundary_simplified": approx_points.tolist(),
165
  "rooms": room_json,
166
  "config": cfg.__dict__,
167
- "meta": {"shapely_available": SHAPELY_OK, "image_size": [int(w), int(h)]},
168
  }
169
  return canvas, result
170
 
@@ -176,7 +258,6 @@ def export_svg(result, width, height):
176
  start = f"M {pts[0][0]} {pts[0][1]}"
177
  rest = " ".join([f"L {p[0]} {p[1]}" for p in pts[1:]])
178
  return f"{start} {rest} Z"
179
-
180
  colors = ["#dbeafe", "#dcfce7", "#ffedd5", "#f3e8ff", "#fee2e2", "#ccfbf1"]
181
  parts = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">', '<rect width="100%" height="100%" fill="white"/>']
182
  for i, room in enumerate(result["rooms"]):
@@ -186,36 +267,28 @@ def export_svg(result, width, height):
186
  return "\n".join(parts)
187
 
188
 
189
- def load_any_image(image_file):
190
- if image_file is None:
191
- raise gr.Error("Upload a PNG, JPG, or SVG file first.")
192
- path = image_file if isinstance(image_file, str) else getattr(image_file, "name", None)
193
- if not path:
194
- raise gr.Error("Could not read uploaded file.")
195
- ext = os.path.splitext(path)[1].lower()
196
- if ext == ".svg":
197
- png_bytes = cairosvg.svg2png(url=path)
198
- return Image.open(io.BytesIO(png_bytes)).convert("RGB"), ext
199
- return Image.open(path).convert("RGB"), ext
200
-
201
-
202
- def process(image_file, variation, wall_thickness, max_rooms):
203
  image, source_ext = load_any_image(image_file)
204
-
205
- cfg = PlanConfig(variation=variation, wall_thickness=wall_thickness, max_rooms=max_rooms)
206
  img_bgr = pil_to_bgr(image)
207
- binary, contour, approx, mask = preprocess_and_extract_boundary(img_bgr)
208
- rooms = generate_rooms(mask, cfg)
209
- rendered, result = draw_floorplan(mask, rooms, contour, approx, cfg)
 
 
 
 
 
 
 
 
 
 
210
 
211
  svg = export_svg(result, rendered.shape[1], rendered.shape[0])
212
  json_str = json.dumps(result, indent=2)
213
-
214
  preview = bgr_to_pil(rendered)
215
- binary_preview = Image.fromarray(binary)
216
-
217
- result["meta"]["source_ext"] = source_ext
218
- json_str = json.dumps(result, indent=2)
219
 
220
  tmpdir = Path(tempfile.mkdtemp(prefix="floorplan_space_"))
221
  zip_path = tmpdir / "floorplan_bundle.zip"
@@ -229,7 +302,7 @@ def process(image_file, variation, wall_thickness, max_rooms):
229
  with gr.Blocks(title="Closed Shape to Floor Plan") as demo:
230
  gr.Markdown(
231
  "# Closed Shape to Floor Plan\n"
232
- "Upload a closed-outline PNG, JPG, or SVG. This Space rasterizes SVG input when needed, extracts the outer boundary, partitions the space into rooms, and exports structured JSON + SVG."
233
  )
234
  with gr.Row():
235
  with gr.Column(scale=1):
@@ -237,14 +310,15 @@ with gr.Blocks(title="Closed Shape to Floor Plan") as demo:
237
  variation = gr.Radio(["balanced", "compact", "open"], value="balanced", label="Variation")
238
  wall_thickness = gr.Slider(2, 16, value=6, step=1, label="Wall thickness")
239
  max_rooms = gr.Slider(2, 8, value=5, step=1, label="Max rooms")
 
240
  btn = gr.Button("Generate floor plan", variant="primary")
241
  with gr.Column(scale=1):
242
  out = gr.Image(label="Generated floor plan")
243
- binary = gr.Image(label="Detected binary mask")
244
  structured = gr.Code(label="Structured JSON", language="json")
245
  archive = gr.File(label="Download JSON + SVG")
246
 
247
- btn.click(process, inputs=[inp, variation, wall_thickness, max_rooms], outputs=[out, binary, structured, archive])
248
 
249
  if __name__ == "__main__":
250
  demo.launch(theme=gr.themes.Soft(), ssr_mode=False)
 
1
+ in Python with Transformers. I used a SegFormer-based segmentation pipeline as the first demoable model because Hugging Face supports semantic segmentation directly through pipeline(task="image-segmentation").
2
+
3
+ Why this helps
4
+ Your earlier version only split the detected outer mask geometrically, so if the outer region was clean and undivided, the engine naturally returned one room. A vision model introduces an extra prior by estimating interior regions from the uploaded image before the partition logic runs, which gives the downstream room generator more structure to work with.
5
+
6
+ Important limitation
7
+ This is still a demo-grade improvement, not a true architectural floor-plan model. Generic segmentation models like SegFormer or SAM are not trained specifically on floor-plan semantics, so they can improve region proposals but will not reliably infer architecturally correct rooms, walls, doors, and circulation from arbitrary closed shapes.
8
+
9
+ Better next model
10
+ If you want proper floor-plan generation, the stronger path is:
11
+
12
+ use SAM/SAM2 for boundary and mask proposals,
13
+
14
+ or plug in a floor-plan-specific segmentation model such as a U-Net/ResNet room-segmentation model trained on floor-plan datasets.
15
+
16
+ The files are updated so you can redeploy the Space directly.
17
+
18
+ Follow-ups
19
+
20
+ How to use SAM2 for video floor plan generation
21
+
22
+ Add door and window detection to multi-room floor plan
23
+
24
+ Fine-tune floor plan segmentation model on custom dataset
25
+
26
+ Convert SAM masks to SVG floor plan with measurements
27
+
28
+ Compare SAM vs U-Net ResNet for room segmentation accuracy
29
+ app
30
  import io
31
  import json
 
32
  import os
33
  import tempfile
34
  import zipfile
 
35
  from dataclasses import dataclass
36
+ from pathlib import Path
37
 
38
+ import cairosvg
39
  import cv2
40
  import gradio as gr
41
  import numpy as np
42
+ from PIL import Image
43
+
44
+ MODEL_AVAILABLE = False
45
+ MODEL_ERROR = None
46
+ processor = None
47
+ seg_pipeline = None
48
 
49
  try:
50
+ from transformers import pipeline
51
+ MODEL_AVAILABLE = True
52
+ except Exception as e:
53
+ MODEL_ERROR = str(e)
 
54
 
55
 
56
  @dataclass
57
  class PlanConfig:
58
+ min_room_area_ratio: float = 0.02
59
  wall_thickness: int = 6
60
  variation: str = "balanced"
61
  max_rooms: int = 6
62
+ use_vision_model: bool = False
63
 
64
 
65
  def pil_to_bgr(img: Image.Image) -> np.ndarray:
 
72
  return Image.fromarray(rgb)
73
 
74
 
75
+ def load_any_image(image_file):
76
+ if image_file is None:
77
+ raise gr.Error("Upload a PNG, JPG, or SVG file first.")
78
+ path = image_file if isinstance(image_file, str) else getattr(image_file, "name", None)
79
+ if not path:
80
+ raise gr.Error("Could not read uploaded file.")
81
+ ext = os.path.splitext(path)[1].lower()
82
+ if ext == ".svg":
83
+ png_bytes = cairosvg.svg2png(url=path)
84
+ return Image.open(io.BytesIO(png_bytes)).convert("RGB"), ext
85
+ return Image.open(path).convert("RGB"), ext
86
+
87
+
88
+ def init_segmentation_pipeline():
89
+ global seg_pipeline, MODEL_AVAILABLE, MODEL_ERROR
90
+ if seg_pipeline is not None:
91
+ return seg_pipeline
92
+ if not MODEL_AVAILABLE:
93
+ return None
94
+ try:
95
+ seg_pipeline = pipeline(
96
+ task="image-segmentation",
97
+ model="nvidia/segformer-b0-finetuned-ade-512-512",
98
+ device=-1,
99
+ )
100
+ return seg_pipeline
101
+ except Exception as e:
102
+ MODEL_ERROR = str(e)
103
+ return None
104
 
105
 
106
  def preprocess_and_extract_boundary(img_bgr: np.ndarray):
107
  gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
108
  blur = cv2.GaussianBlur(gray, (5, 5), 0)
109
  _, th = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
 
110
  kernel = np.ones((5, 5), np.uint8)
111
  th = cv2.morphologyEx(th, cv2.MORPH_CLOSE, kernel, iterations=2)
112
  th = cv2.morphologyEx(th, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8), iterations=1)
 
113
  contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
114
  if not contours:
115
  raise ValueError("No closed shape detected. Use a darker outline on light background.")
 
116
  contour = max(contours, key=cv2.contourArea)
117
  peri = cv2.arcLength(contour, True)
118
  approx = cv2.approxPolyDP(contour, 0.01 * peri, True)
 
119
  mask = np.zeros_like(gray)
120
  cv2.drawContours(mask, [contour], -1, 255, thickness=cv2.FILLED)
 
121
  return th, contour[:, 0, :], approx[:, 0, :], mask
122
 
123
 
124
+ def mask_from_vision_model(image: Image.Image, boundary_mask: np.ndarray):
125
+ pipe = init_segmentation_pipeline()
126
+ if pipe is None:
127
+ return None, f"Vision model unavailable: {MODEL_ERROR}" if MODEL_ERROR else "Vision model unavailable"
128
+ try:
129
+ preds = pipe(image)
130
+ if not preds:
131
+ return None, "Vision model returned no masks"
132
+ room_mask = np.zeros(boundary_mask.shape, dtype=np.uint8)
133
+ for pred in preds:
134
+ label = str(pred.get("label", "")).lower()
135
+ if any(k in label for k in ["floor", "rug", "carpet", "room", "interior"]):
136
+ seg = pred.get("mask")
137
+ if isinstance(seg, Image.Image):
138
+ seg = np.array(seg.convert("L"))
139
+ else:
140
+ seg = np.array(seg)
141
+ if seg.ndim == 3:
142
+ seg = seg[..., 0]
143
+ seg = cv2.resize(seg.astype(np.uint8), (boundary_mask.shape[1], boundary_mask.shape[0]))
144
+ room_mask = np.maximum(room_mask, (seg > 0).astype(np.uint8) * 255)
145
+ room_mask = cv2.bitwise_and(room_mask, boundary_mask)
146
+ if np.count_nonzero(room_mask) == 0:
147
+ return None, "Vision model found no interior-like regions"
148
+ return room_mask, "Vision model used for interior proposal"
149
+ except Exception as e:
150
+ return None, f"Vision inference failed: {e}"
151
+
152
+
153
  def axis_aligned_split(bounds, orientation, pos, gap=8):
154
  x1, y1, x2, y2 = bounds
155
  if orientation == "v":
 
157
  return [(x1, y1, x2, pos - gap), (x1, pos + gap, x2, y2)]
158
 
159
 
160
+ def rect_inside_mask(rect, mask, fill_ratio=0.72):
161
  x1, y1, x2, y2 = [int(v) for v in rect]
162
  if x2 <= x1 or y2 <= y1:
163
  return False
 
170
  def generate_rooms(mask: np.ndarray, cfg: PlanConfig):
171
  ys, xs = np.where(mask > 0)
172
  x1, y1, x2, y2 = xs.min(), ys.min(), xs.max(), ys.max()
173
+ rooms = [(x1, y1, x2, y2)]
174
+ target = {"compact": 6, "balanced": 5, "open": 3}.get(cfg.variation, 5)
 
175
  target = min(target, cfg.max_rooms)
176
+ for _ in range(target * 4):
 
177
  if len(rooms) >= target:
178
  break
179
  areas = [max(0, (r[2] - r[0]) * (r[3] - r[1])) for r in rooms]
 
184
  if w * h < mask.shape[0] * mask.shape[1] * cfg.min_room_area_ratio:
185
  rooms.append(r)
186
  continue
 
187
  if cfg.variation == "open":
188
+ orientation = "v" if w > h * 1.25 else "h"
189
+ cuts = [0.5]
190
  elif cfg.variation == "compact":
191
  orientation = "h" if h > w else "v"
192
+ cuts = [0.45, 0.55]
193
  else:
194
  orientation = "v" if w >= h else "h"
195
+ cuts = [0.5, 0.4, 0.6]
196
+ split_done = False
197
+ for c in cuts:
198
+ pos = int(rx1 + w * c) if orientation == "v" else int(ry1 + h * c)
199
+ kids = axis_aligned_split(r, orientation, pos, gap=cfg.wall_thickness)
200
+ valid = [k for k in kids if rect_inside_mask(k, mask)]
201
+ if len(valid) == 2:
202
+ rooms.extend(valid)
203
+ split_done = True
204
+ break
205
+ if not split_done:
206
  rooms.append(r)
 
207
  return rooms
208
 
209
 
210
+ def polygon_from_mask_component(component_mask):
211
+ contours, _ = cv2.findContours(component_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
212
+ if not contours:
213
+ return None
214
+ c = max(contours, key=cv2.contourArea)
215
+ return c
216
 
 
217
 
218
+ def draw_floorplan(boundary_mask, room_seed_mask, rooms, contour_points, approx_points, cfg: PlanConfig, vision_note: str):
219
+ h, w = boundary_mask.shape
220
+ canvas = np.full((h, w, 3), 255, np.uint8)
221
  palette = [(239, 246, 255), (236, 253, 245), (255, 247, 237), (250, 245, 255), (254, 242, 242), (240, 253, 250)]
222
  room_json = []
223
 
224
  for i, r in enumerate(rooms):
225
  x1, y1, x2, y2 = [int(v) for v in r]
226
+ room_mask = np.zeros_like(boundary_mask)
227
  cv2.rectangle(room_mask, (x1, y1), (x2, y2), 255, thickness=-1)
228
+ room_mask = cv2.bitwise_and(room_mask, room_seed_mask)
229
+ c = polygon_from_mask_component(room_mask)
230
+ if c is None:
231
  continue
 
232
  cv2.drawContours(canvas, [c], -1, palette[i % len(palette)], thickness=cv2.FILLED)
233
  cv2.drawContours(canvas, [c], -1, (0, 0, 0), thickness=2)
234
  M = cv2.moments(c)
 
239
  room_json.append({"id": f"room_{i+1}", "bbox": [x1, y1, x2, y2], "area_px": int(cv2.contourArea(c)), "polygon": c[:, 0, :].tolist()})
240
 
241
  cv2.drawContours(canvas, [contour_points.reshape(-1, 1, 2)], -1, (0, 0, 0), thickness=max(3, cfg.wall_thickness + 2))
242
+ cv2.putText(canvas, vision_note, (16, 24), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (60, 60, 60), 1, cv2.LINE_AA)
243
 
244
  result = {
245
  "boundary": contour_points.tolist(),
246
  "boundary_simplified": approx_points.tolist(),
247
  "rooms": room_json,
248
  "config": cfg.__dict__,
249
+ "meta": {"image_size": [int(w), int(h)], "vision_note": vision_note},
250
  }
251
  return canvas, result
252
 
 
258
  start = f"M {pts[0][0]} {pts[0][1]}"
259
  rest = " ".join([f"L {p[0]} {p[1]}" for p in pts[1:]])
260
  return f"{start} {rest} Z"
 
261
  colors = ["#dbeafe", "#dcfce7", "#ffedd5", "#f3e8ff", "#fee2e2", "#ccfbf1"]
262
  parts = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">', '<rect width="100%" height="100%" fill="white"/>']
263
  for i, room in enumerate(result["rooms"]):
 
267
  return "\n".join(parts)
268
 
269
 
270
+ def process(image_file, variation, wall_thickness, max_rooms, use_vision_model):
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  image, source_ext = load_any_image(image_file)
272
+ cfg = PlanConfig(variation=variation, wall_thickness=wall_thickness, max_rooms=max_rooms, use_vision_model=use_vision_model)
 
273
  img_bgr = pil_to_bgr(image)
274
+ binary, contour, approx, boundary_mask = preprocess_and_extract_boundary(img_bgr)
275
+
276
+ room_seed_mask = boundary_mask.copy()
277
+ vision_note = "Heuristic partition mode"
278
+ if use_vision_model:
279
+ proposed_mask, note = mask_from_vision_model(image, boundary_mask)
280
+ if proposed_mask is not None:
281
+ room_seed_mask = proposed_mask
282
+ vision_note = note
283
+
284
+ rooms = generate_rooms(room_seed_mask, cfg)
285
+ rendered, result = draw_floorplan(boundary_mask, room_seed_mask, rooms, contour, approx, cfg, vision_note)
286
+ result["meta"]["source_ext"] = source_ext
287
 
288
  svg = export_svg(result, rendered.shape[1], rendered.shape[0])
289
  json_str = json.dumps(result, indent=2)
 
290
  preview = bgr_to_pil(rendered)
291
+ binary_preview = Image.fromarray(room_seed_mask)
 
 
 
292
 
293
  tmpdir = Path(tempfile.mkdtemp(prefix="floorplan_space_"))
294
  zip_path = tmpdir / "floorplan_bundle.zip"
 
302
  with gr.Blocks(title="Closed Shape to Floor Plan") as demo:
303
  gr.Markdown(
304
  "# Closed Shape to Floor Plan\n"
305
+ "Upload a closed-outline PNG, JPG, or SVG. Enable the vision-model option to let a Hugging Face segmentation model propose interior regions before room partitioning."
306
  )
307
  with gr.Row():
308
  with gr.Column(scale=1):
 
310
  variation = gr.Radio(["balanced", "compact", "open"], value="balanced", label="Variation")
311
  wall_thickness = gr.Slider(2, 16, value=6, step=1, label="Wall thickness")
312
  max_rooms = gr.Slider(2, 8, value=5, step=1, label="Max rooms")
313
+ use_vision_model = gr.Checkbox(value=True, label="Use vision model for interior proposal")
314
  btn = gr.Button("Generate floor plan", variant="primary")
315
  with gr.Column(scale=1):
316
  out = gr.Image(label="Generated floor plan")
317
+ binary = gr.Image(label="Interior / room seed mask")
318
  structured = gr.Code(label="Structured JSON", language="json")
319
  archive = gr.File(label="Download JSON + SVG")
320
 
321
+ btn.click(process, inputs=[inp, variation, wall_thickness, max_rooms, use_vision_model], outputs=[out, binary, structured, archive])
322
 
323
  if __name__ == "__main__":
324
  demo.launch(theme=gr.themes.Soft(), ssr_mode=False)