muhammadhamza-stack commited on
Commit
2b7c25d
·
1 Parent(s): 5d6860d

refine the gradio app

Browse files
Files changed (5) hide show
  1. .gitattributes +1 -0
  2. .gitignore +4 -0
  3. Reference_ScalingBox.jpg +0 -0
  4. app.py +561 -53
  5. requirements.txt +4 -2
.gitattributes CHANGED
@@ -37,3 +37,4 @@ examples/Test20.jpg filter=lfs diff=lfs merge=lfs -text
37
  examples/Test21.jpg filter=lfs diff=lfs merge=lfs -text
38
  examples/Test22.jpg filter=lfs diff=lfs merge=lfs -text
39
  examples/Test23.jpg filter=lfs diff=lfs merge=lfs -text
 
 
37
  examples/Test21.jpg filter=lfs diff=lfs merge=lfs -text
38
  examples/Test22.jpg filter=lfs diff=lfs merge=lfs -text
39
  examples/Test23.jpg filter=lfs diff=lfs merge=lfs -text
40
+ *.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ venv
2
+ outputs
3
+ yolov8x-worldv2.pt
4
+ largest_contour.jpg
Reference_ScalingBox.jpg CHANGED

Git LFS Details

  • SHA256: 5b129cb438a9aaf808edf9616c74e78a9bcf0479d9ca50ec53d21ee782aff3ae
  • Pointer size: 129 Bytes
  • Size of remote file: 4.58 kB
app.py CHANGED
@@ -1,3 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  from pathlib import Path
3
  from typing import List, Union
@@ -18,6 +460,46 @@ from scalingtestupdated import calculate_scaling_factor
18
  from scipy.interpolate import splprep, splev
19
  from scipy.ndimage import gaussian_filter1d
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  birefnet = AutoModelForImageSegmentation.from_pretrained(
22
  "zhengpeng7/BiRefNet", trust_remote_code=True
23
  )
@@ -50,6 +532,7 @@ def yolo_detect(
50
  )
51
 
52
  del drawer_detector
 
53
 
54
  return boxes[0]
55
 
@@ -65,7 +548,6 @@ def remove_bg(image: np.ndarray) -> np.ndarray:
65
 
66
  # Show Results
67
  pred_pil: Image = transforms.ToPILImage()(pred)
68
- print(pred_pil)
69
  # Scale proportionally with max length to 1024 for faster showing
70
  scale_ratio = 1024 / max(image.size)
71
  scaled_size = (int(image.size[0] * scale_ratio), int(image.size[1] * scale_ratio))
@@ -220,9 +702,6 @@ def extract_outlines(binary_image: np.ndarray) -> np.ndarray:
220
  binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
221
  )
222
 
223
- # smooth_contours_list = []
224
- # for contour in contours:
225
- # smooth_contours_list.append(smooth_contours(contour))
226
  # Create a blank image to draw contours
227
  outline_image = np.zeros_like(binary_image)
228
 
@@ -236,16 +715,12 @@ def extract_outlines(binary_image: np.ndarray) -> np.ndarray:
236
 
237
  def shrink_bbox(image: np.ndarray, shrink_factor: float):
238
  """
239
- Crops the central 80% of the image, maintaining proportions for non-square images.
240
- Args:
241
- image: Input image as a NumPy array.
242
- Returns:
243
- Cropped image as a NumPy array.
244
  """
245
  height, width = image.shape[:2]
246
  center_x, center_y = width // 2, height // 2
247
 
248
- # Calculate 80% dimensions
249
  new_width = int(width * shrink_factor)
250
  new_height = int(height * shrink_factor)
251
 
@@ -280,13 +755,6 @@ def smooth_contours(contour):
280
  def scale_image(image: np.ndarray, scale_factor: float) -> np.ndarray:
281
  """
282
  Resize image by scaling both width and height by the same factor.
283
-
284
- Args:
285
- image: Input numpy image
286
- scale_factor: Factor to scale the image (e.g., 0.5 for half size, 2 for double size)
287
-
288
- Returns:
289
- np.ndarray: Resized image
290
  """
291
  if scale_factor <= 0:
292
  raise ValueError("Scale factor must be positive")
@@ -312,6 +780,7 @@ def detect_reference_square(img) -> np.ndarray:
312
  box_detector = YOLO("./last.pt")
313
  res = box_detector.predict(img, conf=0.05)
314
  del box_detector
 
315
  return save_one_box(res[0].cpu().boxes.xyxy, res[0].orig_img, save=False), res[
316
  0
317
  ].cpu().boxes.xyxy[0]
@@ -334,7 +803,6 @@ def predict(image, offset_inches):
334
  except:
335
  raise gr.Error("Unable to DETECT REFERENCE BOX, please take another picture with different magnification level!")
336
 
337
- # reference_obj_img_scaled = shrink_bbox(reference_obj_img, 1.2)
338
  # make the image sqaure so it does not effect the size of objects
339
  reference_obj_img = make_square(reference_obj_img)
340
  reference_square_mask = remove_bg(reference_obj_img)
@@ -344,6 +812,7 @@ def predict(image, offset_inches):
344
  reference_square_mask, (reference_obj_img.shape[1], reference_obj_img.shape[0])
345
  )
346
 
 
347
  try:
348
  scaling_factor = calculate_scaling_factor(
349
  reference_image_path="./Reference_ScalingBox.jpg",
@@ -351,14 +820,12 @@ def predict(image, offset_inches):
351
  feature_detector="ORB",
352
  )
353
  except ZeroDivisionError:
354
- scaling_factor = None
355
  print("Error calculating scaling factor: Division by zero")
356
  except Exception as e:
357
- scaling_factor = None
358
  print(f"Error calculating scaling factor: {e}")
359
 
360
- # Default to a scaling factor of 1.0 if calculation fails
361
- if scaling_factor is None or scaling_factor == 0:
362
  scaling_factor = 1.0
363
  print("Using default scaling factor of 1.0 due to calculation error")
364
 
@@ -381,17 +848,19 @@ def predict(image, offset_inches):
381
  )
382
 
383
  # Ensure offset_inches is valid
384
- if scaling_factor != 0:
385
- offset_pixels = (offset_inches / scaling_factor) * 2 + 1
 
 
 
386
  else:
387
- offset_pixels = 1 # Default value in case of invalid scaling factor
388
 
 
389
  dilated_mask = cv2.dilate(
390
- objects_mask, np.ones((int(offset_pixels), int(offset_pixels)), np.uint8)
391
  )
392
 
393
- # Scale the object mask according to scaling factor
394
- # objects_mask_scaled = scale_image(objects_mask, scaling_factor)
395
  Image.fromarray(dilated_mask).save("./outputs/scaled_mask_new.jpg")
396
  outlines, contours = extract_outlines(dilated_mask)
397
  shrunked_img_contours = cv2.drawContours(
@@ -411,28 +880,67 @@ def predict(image, offset_inches):
411
  if __name__ == "__main__":
412
  os.makedirs("./outputs", exist_ok=True)
413
 
414
- ifer = gr.Interface(
415
- fn=predict,
416
- inputs=[
417
- gr.Image(label="Input Image"),
418
- gr.Number(label="Offset value for Mask(inches)", value=0.075),
419
- ],
420
- outputs=[
421
- gr.Image(label="Ouput Image"),
422
- gr.Image(label="Outlines of Objects"),
423
- gr.File(label="DXF file"),
424
- gr.Image(label="Mask"),
425
- gr.Textbox(
426
- label="Scaling Factor(mm)",
427
- placeholder="Every pixel is equal to mentioned number in inches",
428
- ),
429
- ],
430
- examples=[
431
- ["./examples/Test20.jpg", 0.075],
432
- ["./examples/Test21.jpg", 0.075],
433
- ["./examples/Test22.jpg", 0.075],
434
- ["./examples/Test23.jpg", 0.075],
435
- ],
436
- )
437
- ifer.launch(share=True)
438
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import os
2
+ # from pathlib import Path
3
+ # from typing import List, Union
4
+ # from PIL import Image
5
+ # import ezdxf.units
6
+ # import numpy as np
7
+ # import torch
8
+ # from torchvision import transforms
9
+ # from ultralytics import YOLOWorld, YOLO
10
+ # from ultralytics.engine.results import Results
11
+ # from ultralytics.utils.plotting import save_one_box
12
+ # from transformers import AutoModelForImageSegmentation
13
+ # import cv2
14
+ # import ezdxf
15
+ # import gradio as gr
16
+ # import gc
17
+ # from scalingtestupdated import calculate_scaling_factor
18
+ # from scipy.interpolate import splprep, splev
19
+ # from scipy.ndimage import gaussian_filter1d
20
+
21
+ # birefnet = AutoModelForImageSegmentation.from_pretrained(
22
+ # "zhengpeng7/BiRefNet", trust_remote_code=True
23
+ # )
24
+
25
+ # device = "cpu"
26
+ # torch.set_float32_matmul_precision(["high", "highest"][0])
27
+
28
+ # birefnet.to(device)
29
+ # birefnet.eval()
30
+ # transform_image = transforms.Compose(
31
+ # [
32
+ # transforms.Resize((1024, 1024)),
33
+ # transforms.ToTensor(),
34
+ # transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
35
+ # ]
36
+ # )
37
+
38
+
39
+ # def yolo_detect(
40
+ # image: Union[str, Path, int, Image.Image, list, tuple, np.ndarray, torch.Tensor],
41
+ # classes: List[str],
42
+ # ) -> np.ndarray:
43
+ # drawer_detector = YOLOWorld("yolov8x-worldv2.pt")
44
+ # drawer_detector.set_classes(classes)
45
+ # results: List[Results] = drawer_detector.predict(image)
46
+ # boxes = []
47
+ # for result in results:
48
+ # boxes.append(
49
+ # save_one_box(result.cpu().boxes.xyxy, im=result.orig_img, save=False)
50
+ # )
51
+
52
+ # del drawer_detector
53
+
54
+ # return boxes[0]
55
+
56
+
57
+ # def remove_bg(image: np.ndarray) -> np.ndarray:
58
+ # image = Image.fromarray(image)
59
+ # input_images = transform_image(image).unsqueeze(0).to("cpu")
60
+
61
+ # # Prediction
62
+ # with torch.no_grad():
63
+ # preds = birefnet(input_images)[-1].sigmoid().cpu()
64
+ # pred = preds[0].squeeze()
65
+
66
+ # # Show Results
67
+ # pred_pil: Image = transforms.ToPILImage()(pred)
68
+ # print(pred_pil)
69
+ # # Scale proportionally with max length to 1024 for faster showing
70
+ # scale_ratio = 1024 / max(image.size)
71
+ # scaled_size = (int(image.size[0] * scale_ratio), int(image.size[1] * scale_ratio))
72
+
73
+ # return np.array(pred_pil.resize(scaled_size))
74
+
75
+
76
+ # def make_square(img: np.ndarray):
77
+ # # Get dimensions
78
+ # height, width = img.shape[:2]
79
+
80
+ # # Find the larger dimension
81
+ # max_dim = max(height, width)
82
+
83
+ # # Calculate padding
84
+ # pad_height = (max_dim - height) // 2
85
+ # pad_width = (max_dim - width) // 2
86
+
87
+ # # Handle odd dimensions
88
+ # pad_height_extra = max_dim - height - 2 * pad_height
89
+ # pad_width_extra = max_dim - width - 2 * pad_width
90
+
91
+ # # Create padding with edge colors
92
+ # if len(img.shape) == 3: # Color image
93
+ # # Pad the image
94
+ # padded = np.pad(
95
+ # img,
96
+ # (
97
+ # (pad_height, pad_height + pad_height_extra),
98
+ # (pad_width, pad_width + pad_width_extra),
99
+ # (0, 0),
100
+ # ),
101
+ # mode="edge",
102
+ # )
103
+ # else: # Grayscale image
104
+ # padded = np.pad(
105
+ # img,
106
+ # (
107
+ # (pad_height, pad_height + pad_height_extra),
108
+ # (pad_width, pad_width + pad_width_extra),
109
+ # ),
110
+ # mode="edge",
111
+ # )
112
+
113
+ # return padded
114
+
115
+
116
+ # def exclude_scaling_box(
117
+ # image: np.ndarray,
118
+ # bbox: np.ndarray,
119
+ # orig_size: tuple,
120
+ # processed_size: tuple,
121
+ # expansion_factor: float = 1.2,
122
+ # ) -> np.ndarray:
123
+ # # Unpack the bounding box
124
+ # x_min, y_min, x_max, y_max = map(int, bbox)
125
+
126
+ # # Calculate scaling factors
127
+ # scale_x = processed_size[1] / orig_size[1] # Width scale
128
+ # scale_y = processed_size[0] / orig_size[0] # Height scale
129
+
130
+ # # Adjust bounding box coordinates
131
+ # x_min = int(x_min * scale_x)
132
+ # x_max = int(x_max * scale_x)
133
+ # y_min = int(y_min * scale_y)
134
+ # y_max = int(y_max * scale_y)
135
+
136
+ # # Calculate expanded box coordinates
137
+ # box_width = x_max - x_min
138
+ # box_height = y_max - y_min
139
+ # expanded_x_min = max(0, int(x_min - (expansion_factor - 1) * box_width / 2))
140
+ # expanded_x_max = min(
141
+ # image.shape[1], int(x_max + (expansion_factor - 1) * box_width / 2)
142
+ # )
143
+ # expanded_y_min = max(0, int(y_min - (expansion_factor - 1) * box_height / 2))
144
+ # expanded_y_max = min(
145
+ # image.shape[0], int(y_max + (expansion_factor - 1) * box_height / 2)
146
+ # )
147
+
148
+ # # Black out the expanded region
149
+ # image[expanded_y_min:expanded_y_max, expanded_x_min:expanded_x_max] = 0
150
+
151
+ # return image
152
+
153
+
154
+ # def resample_contour(contour):
155
+ # # Get all the parameters at the start:
156
+ # num_points = 1000
157
+ # smoothing_factor = 5
158
+ # spline_degree = 3 # Typically k=3 for cubic spline
159
+
160
+ # smoothed_x_sigma = 1
161
+ # smoothed_y_sigma = 1
162
+
163
+ # # Ensure contour has enough points
164
+ # if len(contour) < spline_degree + 1:
165
+ # raise ValueError(f"Contour must have at least {spline_degree + 1} points, but has {len(contour)} points.")
166
+
167
+ # contour = contour[:, 0, :]
168
+
169
+ # tck, _ = splprep([contour[:, 0], contour[:, 1]], s=smoothing_factor)
170
+ # u = np.linspace(0, 1, num_points)
171
+ # resampled_points = splev(u, tck)
172
+
173
+ # smoothed_x = gaussian_filter1d(resampled_points[0], sigma=smoothed_x_sigma)
174
+ # smoothed_y = gaussian_filter1d(resampled_points[1], sigma=smoothed_y_sigma)
175
+
176
+ # return np.array([smoothed_x, smoothed_y]).T
177
+
178
+
179
+ # def save_dxf_spline(inflated_contours, scaling_factor, height):
180
+ # degree = 3
181
+ # closed = True
182
+
183
+ # doc = ezdxf.new(units=0)
184
+ # doc.units = ezdxf.units.IN
185
+ # doc.header["$INSUNITS"] = ezdxf.units.IN
186
+
187
+ # msp = doc.modelspace()
188
+
189
+ # for contour in inflated_contours:
190
+ # try:
191
+ # resampled_contour = resample_contour(contour)
192
+ # points = [
193
+ # (x * scaling_factor, (height - y) * scaling_factor)
194
+ # for x, y in resampled_contour
195
+ # ]
196
+ # if len(points) >= 3:
197
+ # if np.linalg.norm(np.array(points[0]) - np.array(points[-1])) > 1e-2:
198
+ # points.append(points[0])
199
+
200
+ # spline = msp.add_spline(points, degree=degree)
201
+ # spline.closed = closed
202
+ # except ValueError as e:
203
+ # print(f"Skipping contour: {e}")
204
+
205
+ # dxf_filepath = os.path.join("./outputs", "out.dxf")
206
+ # doc.saveas(dxf_filepath)
207
+ # return dxf_filepath
208
+
209
+
210
+ # def extract_outlines(binary_image: np.ndarray) -> np.ndarray:
211
+ # """
212
+ # Extracts and draws the outlines of masks from a binary image.
213
+ # Args:
214
+ # binary_image: Grayscale binary image where white represents masks and black is the background.
215
+ # Returns:
216
+ # Image with outlines drawn.
217
+ # """
218
+ # # Detect contours from the binary image
219
+ # contours, _ = cv2.findContours(
220
+ # binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
221
+ # )
222
+
223
+ # # smooth_contours_list = []
224
+ # # for contour in contours:
225
+ # # smooth_contours_list.append(smooth_contours(contour))
226
+ # # Create a blank image to draw contours
227
+ # outline_image = np.zeros_like(binary_image)
228
+
229
+ # # Draw the contours on the blank image
230
+ # cv2.drawContours(
231
+ # outline_image, contours, -1, (255), thickness=1
232
+ # ) # White color for outlines
233
+
234
+ # return cv2.bitwise_not(outline_image), contours
235
+
236
+
237
+ # def shrink_bbox(image: np.ndarray, shrink_factor: float):
238
+ # """
239
+ # Crops the central 80% of the image, maintaining proportions for non-square images.
240
+ # Args:
241
+ # image: Input image as a NumPy array.
242
+ # Returns:
243
+ # Cropped image as a NumPy array.
244
+ # """
245
+ # height, width = image.shape[:2]
246
+ # center_x, center_y = width // 2, height // 2
247
+
248
+ # # Calculate 80% dimensions
249
+ # new_width = int(width * shrink_factor)
250
+ # new_height = int(height * shrink_factor)
251
+
252
+ # # Determine the top-left and bottom-right points for cropping
253
+ # x1 = max(center_x - new_width // 2, 0)
254
+ # y1 = max(center_y - new_height // 2, 0)
255
+ # x2 = min(center_x + new_width // 2, width)
256
+ # y2 = min(center_y + new_height // 2, height)
257
+
258
+ # # Crop the image
259
+ # cropped_image = image[y1:y2, x1:x2]
260
+ # return cropped_image
261
+
262
+
263
+ # def to_dxf(contours):
264
+ # doc = ezdxf.new()
265
+ # msp = doc.modelspace()
266
+
267
+ # for contour in contours:
268
+ # points = [(point[0][0], point[0][1]) for point in contour]
269
+ # msp.add_lwpolyline(points, close=True) # Add a polyline for each contour
270
+
271
+ # doc.saveas("./outputs/out.dxf")
272
+ # return "./outputs/out.dxf"
273
+
274
+
275
+ # def smooth_contours(contour):
276
+ # epsilon = 0.01 * cv2.arcLength(contour, True) # Adjust factor (e.g., 0.01)
277
+ # return cv2.approxPolyDP(contour, epsilon, True)
278
+
279
+
280
+ # def scale_image(image: np.ndarray, scale_factor: float) -> np.ndarray:
281
+ # """
282
+ # Resize image by scaling both width and height by the same factor.
283
+
284
+ # Args:
285
+ # image: Input numpy image
286
+ # scale_factor: Factor to scale the image (e.g., 0.5 for half size, 2 for double size)
287
+
288
+ # Returns:
289
+ # np.ndarray: Resized image
290
+ # """
291
+ # if scale_factor <= 0:
292
+ # raise ValueError("Scale factor must be positive")
293
+
294
+ # current_height, current_width = image.shape[:2]
295
+
296
+ # # Calculate new dimensions
297
+ # new_width = int(current_width * scale_factor)
298
+ # new_height = int(current_height * scale_factor)
299
+
300
+ # # Choose interpolation method based on whether we're scaling up or down
301
+ # interpolation = cv2.INTER_AREA if scale_factor < 1 else cv2.INTER_CUBIC
302
+
303
+ # # Resize image
304
+ # resized_image = cv2.resize(
305
+ # image, (new_width, new_height), interpolation=interpolation
306
+ # )
307
+
308
+ # return resized_image
309
+
310
+
311
+ # def detect_reference_square(img) -> np.ndarray:
312
+ # box_detector = YOLO("./last.pt")
313
+ # res = box_detector.predict(img, conf=0.05)
314
+ # del box_detector
315
+ # return save_one_box(res[0].cpu().boxes.xyxy, res[0].orig_img, save=False), res[
316
+ # 0
317
+ # ].cpu().boxes.xyxy[0]
318
+
319
+
320
+ # def resize_img(img: np.ndarray, resize_dim):
321
+ # return np.array(Image.fromarray(img).resize(resize_dim))
322
+
323
+
324
+ # def predict(image, offset_inches):
325
+ # try:
326
+ # drawer_img = yolo_detect(image, ["box"])
327
+ # shrunked_img = make_square(shrink_bbox(drawer_img, 0.90))
328
+ # except:
329
+ # raise gr.Error("Unable to DETECT DRAWER, please take another picture with different magnification level!")
330
+
331
+ # # Detect the scaling reference square
332
+ # try:
333
+ # reference_obj_img, scaling_box_coords = detect_reference_square(shrunked_img)
334
+ # except:
335
+ # raise gr.Error("Unable to DETECT REFERENCE BOX, please take another picture with different magnification level!")
336
+
337
+ # # reference_obj_img_scaled = shrink_bbox(reference_obj_img, 1.2)
338
+ # # make the image sqaure so it does not effect the size of objects
339
+ # reference_obj_img = make_square(reference_obj_img)
340
+ # reference_square_mask = remove_bg(reference_obj_img)
341
+
342
+ # # make the mask same size as org image
343
+ # reference_square_mask = resize_img(
344
+ # reference_square_mask, (reference_obj_img.shape[1], reference_obj_img.shape[0])
345
+ # )
346
+
347
+ # try:
348
+ # scaling_factor = calculate_scaling_factor(
349
+ # reference_image_path="./Reference_ScalingBox.jpg",
350
+ # target_image=reference_square_mask,
351
+ # feature_detector="ORB",
352
+ # )
353
+ # except ZeroDivisionError:
354
+ # scaling_factor = None
355
+ # print("Error calculating scaling factor: Division by zero")
356
+ # except Exception as e:
357
+ # scaling_factor = None
358
+ # print(f"Error calculating scaling factor: {e}")
359
+
360
+ # # Default to a scaling factor of 1.0 if calculation fails
361
+ # if scaling_factor is None or scaling_factor == 0:
362
+ # scaling_factor = 1.0
363
+ # print("Using default scaling factor of 1.0 due to calculation error")
364
+
365
+ # # Save original size before `remove_bg` processing
366
+ # orig_size = shrunked_img.shape[:2]
367
+ # # Generate foreground mask and save its size
368
+ # objects_mask = remove_bg(shrunked_img)
369
+
370
+ # processed_size = objects_mask.shape[:2]
371
+ # # Exclude scaling box region from objects mask
372
+ # objects_mask = exclude_scaling_box(
373
+ # objects_mask,
374
+ # scaling_box_coords,
375
+ # orig_size,
376
+ # processed_size,
377
+ # expansion_factor=1.2,
378
+ # )
379
+ # objects_mask = resize_img(
380
+ # objects_mask, (shrunked_img.shape[1], shrunked_img.shape[0])
381
+ # )
382
+
383
+ # # Ensure offset_inches is valid
384
+ # if scaling_factor != 0:
385
+ # offset_pixels = (offset_inches / scaling_factor) * 2 + 1
386
+ # else:
387
+ # offset_pixels = 1 # Default value in case of invalid scaling factor
388
+
389
+ # dilated_mask = cv2.dilate(
390
+ # objects_mask, np.ones((int(offset_pixels), int(offset_pixels)), np.uint8)
391
+ # )
392
+
393
+ # # Scale the object mask according to scaling factor
394
+ # # objects_mask_scaled = scale_image(objects_mask, scaling_factor)
395
+ # Image.fromarray(dilated_mask).save("./outputs/scaled_mask_new.jpg")
396
+ # outlines, contours = extract_outlines(dilated_mask)
397
+ # shrunked_img_contours = cv2.drawContours(
398
+ # shrunked_img, contours, -1, (0, 0, 255), thickness=2
399
+ # )
400
+ # dxf = save_dxf_spline(contours, scaling_factor, processed_size[0])
401
+
402
+ # return (
403
+ # cv2.cvtColor(shrunked_img_contours, cv2.COLOR_BGR2RGB),
404
+ # outlines,
405
+ # dxf,
406
+ # dilated_mask,
407
+ # scaling_factor,
408
+ # )
409
+
410
+
411
+ # if __name__ == "__main__":
412
+ # os.makedirs("./outputs", exist_ok=True)
413
+
414
+ # ifer = gr.Interface(
415
+ # fn=predict,
416
+ # inputs=[
417
+ # gr.Image(label="Input Image"),
418
+ # gr.Number(label="Offset value for Mask(inches)", value=0.075),
419
+ # ],
420
+ # outputs=[
421
+ # gr.Image(label="Ouput Image"),
422
+ # gr.Image(label="Outlines of Objects"),
423
+ # gr.File(label="DXF file"),
424
+ # gr.Image(label="Mask"),
425
+ # gr.Textbox(
426
+ # label="Scaling Factor(mm)",
427
+ # placeholder="Every pixel is equal to mentioned number in inches",
428
+ # ),
429
+ # ],
430
+ # examples=[
431
+ # ["./examples/Test20.jpg", 0.075],
432
+ # ["./examples/Test21.jpg", 0.075],
433
+ # ["./examples/Test22.jpg", 0.075],
434
+ # ["./examples/Test23.jpg", 0.075],
435
+ # ],
436
+ # )
437
+ # ifer.launch(share=True)
438
+
439
+
440
+
441
+
442
+
443
  import os
444
  from pathlib import Path
445
  from typing import List, Union
 
460
  from scipy.interpolate import splprep, splev
461
  from scipy.ndimage import gaussian_filter1d
462
 
463
+ # --- DOCUMENTATION STRINGS (Drawer Detection App) ---
464
+
465
+ GUIDELINE_SETUP = """
466
+ ## 1. Quick Start Guide: Setup and DXF Generation
467
+
468
+ This application analyzes an image of items inside a drawer, calculates scaling, and outputs a manufacturing-ready DXF file with offsets applied.
469
+
470
+ 1. **Upload Image:** Upload a clear image of the drawer area, ensuring the items and the scaling reference box are visible.
471
+ 2. **Set Offset:** Enter the desired offset value in **inches**. This determines the clearance around the contour (e.g., 0.075 inches is the default).
472
+ 3. **Run:** Click the **"Submit"** button (or run using an example).
473
+ 4. **Review & Download:** Review the resulting images (Contoured Output, Outlines, Mask) and download the generated **DXF file**.
474
+ """
475
+
476
+ GUIDELINE_INPUT = """
477
+ ## 2. Expected Inputs and Preprocessing
478
+
479
+ | Input Field | Purpose | Requirement |
480
+ | :--- | :--- | :--- |
481
+ | **Input Image** | A high-resolution image of the drawer containing the objects to be contoured. | Must show the items and the reference scaling box clearly. |
482
+ | **Offset value (inches)** | The physical distance (clearance) to be added around the detected contours for manufacturing tolerance. | Input must be a positive number (float). Default is 0.075 inches. |
483
+
484
+
485
+ """
486
+
487
+ GUIDELINE_OUTPUT = """
488
+ ## 3. Expected Outputs (Manufacturing Results)
489
+
490
+ The application provides five key outputs:
491
+
492
+ 1. **Ouput Image:** The original cropped drawer image overlaid with the final, offset contours (blue lines).
493
+ 2. **Outlines of Objects:** A grayscale image showing only the final, smoothed contour lines.
494
+ 3. **DXF file (Downloadable):** The primary output. This file contains scaled 2D spline geometry (in inches) based on the calculated contours, ready for CAD or CNC machines.
495
+ 4. **Mask:** The raw, dilated binary mask used to generate the contours.
496
+ 5. **Scaling Factor (Textbox):** The calculated ratio (in pixels per inch) used to accurately convert pixel dimensions into real-world units for the DXF file.
497
+ """
498
+ # ----------------------------------------------------
499
+ # END GUIDELINE DEFINITIONS
500
+ # ----------------------------------------------------
501
+
502
+
503
  birefnet = AutoModelForImageSegmentation.from_pretrained(
504
  "zhengpeng7/BiRefNet", trust_remote_code=True
505
  )
 
532
  )
533
 
534
  del drawer_detector
535
+ gc.collect() # Ensure memory is cleared
536
 
537
  return boxes[0]
538
 
 
548
 
549
  # Show Results
550
  pred_pil: Image = transforms.ToPILImage()(pred)
 
551
  # Scale proportionally with max length to 1024 for faster showing
552
  scale_ratio = 1024 / max(image.size)
553
  scaled_size = (int(image.size[0] * scale_ratio), int(image.size[1] * scale_ratio))
 
702
  binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
703
  )
704
 
 
 
 
705
  # Create a blank image to draw contours
706
  outline_image = np.zeros_like(binary_image)
707
 
 
715
 
716
  def shrink_bbox(image: np.ndarray, shrink_factor: float):
717
  """
718
+ Crops the central portion of the image.
 
 
 
 
719
  """
720
  height, width = image.shape[:2]
721
  center_x, center_y = width // 2, height // 2
722
 
723
+ # Calculate dimensions
724
  new_width = int(width * shrink_factor)
725
  new_height = int(height * shrink_factor)
726
 
 
755
  def scale_image(image: np.ndarray, scale_factor: float) -> np.ndarray:
756
  """
757
  Resize image by scaling both width and height by the same factor.
 
 
 
 
 
 
 
758
  """
759
  if scale_factor <= 0:
760
  raise ValueError("Scale factor must be positive")
 
780
  box_detector = YOLO("./last.pt")
781
  res = box_detector.predict(img, conf=0.05)
782
  del box_detector
783
+ gc.collect()
784
  return save_one_box(res[0].cpu().boxes.xyxy, res[0].orig_img, save=False), res[
785
  0
786
  ].cpu().boxes.xyxy[0]
 
803
  except:
804
  raise gr.Error("Unable to DETECT REFERENCE BOX, please take another picture with different magnification level!")
805
 
 
806
  # make the image sqaure so it does not effect the size of objects
807
  reference_obj_img = make_square(reference_obj_img)
808
  reference_square_mask = remove_bg(reference_obj_img)
 
812
  reference_square_mask, (reference_obj_img.shape[1], reference_obj_img.shape[0])
813
  )
814
 
815
+ scaling_factor = 1.0
816
  try:
817
  scaling_factor = calculate_scaling_factor(
818
  reference_image_path="./Reference_ScalingBox.jpg",
 
820
  feature_detector="ORB",
821
  )
822
  except ZeroDivisionError:
 
823
  print("Error calculating scaling factor: Division by zero")
824
  except Exception as e:
 
825
  print(f"Error calculating scaling factor: {e}")
826
 
827
+ # Default to a scaling factor of 1.0 if calculation fails or is 0
828
+ if scaling_factor is None or scaling_factor <= 0:
829
  scaling_factor = 1.0
830
  print("Using default scaling factor of 1.0 due to calculation error")
831
 
 
848
  )
849
 
850
  # Ensure offset_inches is valid
851
+ # Calculate pixel dilation amount: (offset_inches / scaling_factor) * 2 + 1
852
+ # We use 1.0 / scaling_factor because scaling_factor is px/inch.
853
+ if scaling_factor > 0:
854
+ # Convert inches to pixels
855
+ offset_pixels = int(offset_inches / scaling_factor * 2) + 1
856
  else:
857
+ offset_pixels = 1
858
 
859
+ # Dilate mask for offset
860
  dilated_mask = cv2.dilate(
861
+ objects_mask, np.ones((offset_pixels, offset_pixels), np.uint8)
862
  )
863
 
 
 
864
  Image.fromarray(dilated_mask).save("./outputs/scaled_mask_new.jpg")
865
  outlines, contours = extract_outlines(dilated_mask)
866
  shrunked_img_contours = cv2.drawContours(
 
880
  if __name__ == "__main__":
881
  os.makedirs("./outputs", exist_ok=True)
882
 
883
+ # Use gr.Blocks to allow for the structured guideline accordion
884
+ with gr.Blocks(title="Drawer Contouring and DXF Generator") as demo:
885
+ gr.Markdown("<h1 style='text-align: center;'>Drawer Contouring and DXF Generator (YOLO + BiRefNet)</h1>")
886
+ gr.Markdown("Tool for generating scaled manufacturing contours from an input image.")
887
+
888
+ # 1. Guidelines Section
889
+ with gr.Accordion(" Tips & User Guidelines", open=False):
890
+ gr.Markdown(GUIDELINE_SETUP)
891
+ gr.Markdown("---")
892
+ gr.Markdown(GUIDELINE_INPUT)
893
+ gr.Markdown("---")
894
+ gr.Markdown(GUIDELINE_OUTPUT)
895
+
896
+ # 2. Main Interface
897
+ with gr.Row():
898
+ with gr.Column(scale=1):
899
+ gr.Markdown("## Step 1: Upload an Image")
900
+ input_image = gr.Image(label="1. Input Image", type="numpy")
901
+ gr.Markdown("## Step 2: Set the offset value (Optional) ")
902
+ offset_input = gr.Number(label="2. Offset value for Mask (inches)", value=0.075)
903
+ gr.Markdown("## Step 3: Click the button ")
904
+ submit_button = gr.Button(" Process and Generate DXF", variant="primary")
905
+
906
+
907
+ with gr.Column(scale=2):
908
+ gr.Markdown("## Results")
909
+ scaling_output = gr.Textbox(
910
+ label="Scaling Factor (pixels/inch)",
911
+ placeholder="Calculated conversion rate",
912
+ )
913
+ output_image = gr.Image(label="Output Image (Contours Drawn)")
914
+
915
+ with gr.Row():
916
+ output_outlines = gr.Image(label="Outlines of Objects")
917
+ output_mask = gr.Image(label="Final Dilated Mask")
918
+
919
+ dxf_file = gr.File(label="DXF file (Download)")
920
+
921
+
922
+ # 3. Examples Section
923
+ gr.Markdown("## Examples ")
924
+ gr.Examples(
925
+ examples=[
926
+ ["./examples/Test20.jpg", 0.075],
927
+ ["./examples/Test21.jpg", 0.075],
928
+ ["./examples/Test22.jpg", 0.075],
929
+ ["./examples/Test23.jpg", 0.075],
930
+ ],
931
+ inputs=[input_image, offset_input],
932
+ outputs=[output_image, output_outlines, dxf_file, output_mask, scaling_output],
933
+ fn=predict,
934
+ cache_examples=False,
935
+ label="Example Images (Click to load and run)",
936
+ )
937
+
938
+ # Event Handler
939
+ submit_button.click(
940
+ fn=predict,
941
+ inputs=[input_image, offset_input],
942
+ outputs=[output_image, output_outlines, dxf_file, output_mask, scaling_output],
943
+ )
944
+
945
+ demo.queue()
946
+ demo.launch(share=True)
requirements.txt CHANGED
@@ -1,7 +1,9 @@
1
  transformers
2
  ultralytics==8.3.9
3
  ezdxf
4
- gradio
5
  kornia
6
  timm
7
- einops
 
 
 
 
1
  transformers
2
  ultralytics==8.3.9
3
  ezdxf
 
4
  kornia
5
  timm
6
+ einops
7
+ numpy<2
8
+ gradio==3.50.2
9
+ gradio-client==0.6.1