mlbench123 commited on
Commit
2a0abec
·
verified ·
1 Parent(s): ebb091d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +407 -75
app.py CHANGED
@@ -14,7 +14,8 @@ 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
  import json
@@ -153,7 +154,77 @@ def get_birefnet():
153
  logger.info("BiRefNet model loaded successfully")
154
  return birefnet
155
 
156
- def detect_paper_contour(image: np.ndarray) -> Tuple[np.ndarray, float]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  """
158
  Detect paper in the image using contour detection as fallback
159
  Returns the paper contour and estimated scaling factor
@@ -209,7 +280,7 @@ def detect_paper_contour(image: np.ndarray) -> Tuple[np.ndarray, float]:
209
  rect_area = w * h
210
  if rect_area > 0:
211
  extent = area / rect_area
212
- if extent > 0.85: # At least 70% rectangular
213
  paper_contours.append((contour, area, aspect_ratio, extent))
214
 
215
  if not paper_contours:
@@ -222,9 +293,10 @@ def detect_paper_contour(image: np.ndarray) -> Tuple[np.ndarray, float]:
222
 
223
  logger.info(f"Paper detected using contours: area={paper_contours[0][1]}, aspect_ratio={paper_contours[0][2]:.2f}")
224
 
225
- return best_contour, 0.0 # Return 0.0 as placeholder scaling factor
226
-
227
- def detect_paper_bounds(image: np.ndarray, paper_size: str) -> Tuple[np.ndarray, float]:
 
228
  """
229
  Detect paper bounds in the image and calculate scaling factor
230
  """
@@ -233,23 +305,22 @@ def detect_paper_bounds(image: np.ndarray, paper_size: str) -> Tuple[np.ndarray,
233
 
234
  if paper_detector is not None:
235
  # Use trained model if available
236
- # FIXED: Add verbose=False to suppress prints, and use proper confidence threshold
237
- results = paper_detector.predict(image, conf=0.8, verbose=False) # Lower confidence threshold
238
 
239
  if not results or len(results) == 0:
240
  logger.warning("No results from paper detector")
241
- return detect_paper_contour(image)
242
 
243
  # Check if boxes exist and are not empty
244
  if not hasattr(results[0], 'boxes') or results[0].boxes is None or len(results[0].boxes) == 0:
245
  logger.warning("No boxes detected by model, using fallback contour detection")
246
- return detect_paper_contour(image)
247
 
248
  # Get the largest detected paper
249
- boxes = results[0].boxes.xyxy.cpu().numpy() # Convert to numpy
250
  if len(boxes) == 0:
251
  logger.warning("Empty boxes detected, using fallback")
252
- return detect_paper_contour(image)
253
 
254
  largest_box = None
255
  max_area = 0
@@ -263,7 +334,7 @@ def detect_paper_bounds(image: np.ndarray, paper_size: str) -> Tuple[np.ndarray,
263
 
264
  if largest_box is None:
265
  logger.warning("No valid paper box found, using fallback")
266
- return detect_paper_contour(image)
267
 
268
  # Convert box to contour-like format
269
  x_min, y_min, x_max, y_max = map(int, largest_box)
@@ -279,45 +350,120 @@ def detect_paper_bounds(image: np.ndarray, paper_size: str) -> Tuple[np.ndarray,
279
  else:
280
  # Use fallback contour detection
281
  logger.info("Using fallback contour detection for paper")
282
- paper_contour, _ = detect_paper_contour(image)
283
 
284
- # Calculate scaling factor based on paper size
285
- scaling_factor = calculate_paper_scaling_factor(paper_contour, paper_size)
286
 
287
  return paper_contour, scaling_factor
288
 
289
  except Exception as e:
290
  logger.error(f"Error in paper detection: {e}")
291
- # Instead of raising PaperNotDetectedError, raise ReferenceBoxNotDetectedError
292
  raise ReferenceBoxNotDetectedError(f"Failed to detect paper: {str(e)}")
293
 
294
- def calculate_paper_scaling_factor(paper_contour: np.ndarray, paper_size: str) -> float:
295
- """
296
- Calculate scaling factor based on detected paper dimensions
297
- """
298
- # Get paper dimensions
299
- paper_dims = PAPER_SIZES[paper_size]
300
- expected_width_mm = paper_dims["width"]
301
- expected_height_mm = paper_dims["height"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
- # Calculate bounding rectangle of paper contour
304
- rect = cv2.boundingRect(paper_contour)
305
- detected_width_px = rect[2]
306
- detected_height_px = rect[3]
307
 
308
- # Calculate scaling factors for both dimensions
309
- scale_x = expected_width_mm / detected_width_px
310
- scale_y = expected_height_mm / detected_height_px
311
 
312
- # Use average of both scales
313
- # scaling_factor = (scale_x + scale_y) / 2
314
- scaling_factor = min(scale_x, scale_y)
315
 
316
- logger.info(f"Paper detection: {detected_width_px}x{detected_height_px} px -> {expected_width_mm}x{expected_height_mm} mm")
317
- logger.info(f"Calculated scaling factor: {scaling_factor:.4f} mm/px")
318
 
 
 
 
 
 
 
 
 
319
  return scaling_factor
320
 
 
321
  def validate_single_object(mask: np.ndarray, paper_contour: np.ndarray) -> None:
322
  """
323
  Validate that only a single object is present on the paper
@@ -524,35 +670,83 @@ def resample_contour(contour, edge_radius_px: int = 0):
524
  logger.error(f"Error in resample_contour: {e}")
525
  raise
526
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=False):
528
- """Save contours as DXF splines with optional finger cuts"""
529
  doc = ezdxf.new(units=ezdxf.units.MM)
530
  doc.header["$INSUNITS"] = ezdxf.units.MM
531
  msp = doc.modelspace()
532
- final_polygons_inch = []
533
  finger_centers = []
534
  original_polygons = []
535
 
536
- # Scale correction factor
537
- scale_correction = 1.0
538
-
539
  for contour in inflated_contours:
540
  try:
541
  resampled_contour = resample_contour(contour)
542
 
543
- points_inch = [(x * scaling_factor, (height - y) * scaling_factor)
544
- for x, y in resampled_contour]
 
545
 
546
- if len(points_inch) < 3:
547
  continue
548
 
549
- tool_polygon = build_tool_polygon(points_inch)
550
  original_polygons.append(tool_polygon)
551
 
552
  if finger_clearance:
553
  try:
554
  tool_polygon, center = place_finger_cut_adjusted(
555
- tool_polygon, points_inch, finger_centers, final_polygons_inch
556
  )
557
  except FingerCutOverlapError:
558
  tool_polygon = original_polygons[-1]
@@ -561,18 +755,16 @@ def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=
561
  if len(exterior_coords) < 3:
562
  continue
563
 
564
- # Apply scale correction
565
- corrected_coords = [(x * scale_correction, y * scale_correction) for x, y in exterior_coords]
566
-
567
- msp.add_spline(corrected_coords, degree=3, dxfattribs={"layer": "TOOLS"})
568
- final_polygons_inch.append(tool_polygon)
569
 
570
  except ValueError as e:
571
  logger.warning(f"Skipping contour: {e}")
572
 
573
  dxf_filepath = os.path.join("./outputs", "out.dxf")
574
  doc.saveas(dxf_filepath)
575
- return dxf_filepath, final_polygons_inch, original_polygons
576
 
577
  def build_tool_polygon(points_inch):
578
  """Build a polygon from inch-converted points"""
@@ -866,14 +1058,152 @@ def make_square(img: np.ndarray):
866
 
867
  return padded
868
 
869
- def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  """Main prediction function using paper as reference"""
871
 
872
  logger.info(f"Starting prediction with image shape: {image.shape}")
873
- logger.info(f"Paper size: {paper_size}")
874
 
 
875
  if offset_unit == "inches":
876
- offset_mm = offset * 25.4 # Convert to mm for internal calculations
877
  else:
878
  offset_mm = offset
879
 
@@ -885,10 +1215,10 @@ def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=F
885
  raise gr.Error("Offset Value Can't be negative")
886
 
887
  try:
888
- # Detect paper bounds and calculate scaling factor
889
  logger.info("Starting paper detection...")
890
- paper_contour, scaling_factor = detect_paper_bounds(image, paper_size)
891
- logger.info(f"Paper detected successfully with scaling factor: {scaling_factor:.4f} mm/px")
892
 
893
  except ReferenceBoxNotDetectedError as e:
894
  logger.error(f"Paper detection failed: {e}")
@@ -900,11 +1230,10 @@ def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=F
900
  logger.error(f"Unexpected error in paper detection: {e}")
901
  raise gr.Error(f"Error processing image: {str(e)}")
902
 
 
 
 
903
  try:
904
- # Remove background from main objects
905
- # orig_size = image.shape[:2]
906
- # objects_mask = remove_bg(image)
907
-
908
  # Mask paper area in input image first
909
  masked_input_image = mask_paper_area_in_image(image, paper_contour)
910
 
@@ -938,14 +1267,16 @@ def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=F
938
  # Apply edge rounding if specified
939
  rounded_mask = objects_mask.copy()
940
 
941
- # Apply dilation for offset - more precise calculation
942
  if offset_mm > 0:
943
- offset_pixels = int(round(float(offset_mm) / scaling_factor))
944
  if offset_pixels > 0:
945
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (offset_pixels*2+1, offset_pixels*2+1))
946
  dilated_mask = cv2.dilate(rounded_mask, kernel, iterations=1)
947
  else:
948
  dilated_mask = rounded_mask.copy()
 
 
949
 
950
  # Save original dilated mask for output
951
  Image.fromarray(dilated_mask).save("./outputs/scaled_mask_original.jpg")
@@ -955,10 +1286,10 @@ def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=F
955
  outlines, contours = extract_outlines(dilated_mask)
956
 
957
  try:
958
- # Generate DXF
959
  dxf, finger_polygons, original_polygons = save_dxf_spline(
960
  contours,
961
- scaling_factor,
962
  processed_size[0],
963
  finger_clearance=(finger_clearance == "On")
964
  )
@@ -987,21 +1318,22 @@ def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=F
987
  cv2.drawContours(shrunked_img_contours, contours, -1, (0, 255, 0), thickness=2)
988
  cv2.drawContours(outlines, contours, -1, 0, thickness=2)
989
 
990
- # # Draw paper bounds on annotated image
991
- # cv2.drawContours(shrunked_img_contours, [paper_contour], -1, (255, 0, 0), thickness=3)
992
-
993
- # # Add paper size text
994
- # paper_text = f"Paper: {paper_size}"
995
- # cv2.putText(shrunked_img_contours, paper_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
996
-
997
  cleanup_models()
998
 
 
 
 
 
 
 
 
 
999
  return (
1000
  shrunked_img_contours,
1001
  outlines,
1002
  dxf,
1003
  dilated_mask_orig,
1004
- f"Scale: {scaling_factor:.4f} mm/px | Paper: {paper_size}"
1005
  )
1006
 
1007
  def predict_full_paper(image, paper_size, offset_value_mm,offset_unit, enable_finger_cut, selected_outputs):
 
14
  import ezdxf
15
  import gradio as gr
16
  import gc
17
+ # from scalingtestupdated import calculate_scaling_factor
18
+ from scalingtestupdated import calculate_scaling_factor_with_units, calculate_paper_scaling_factor, convert_units
19
  from scipy.interpolate import splprep, splev
20
  from scipy.ndimage import gaussian_filter1d
21
  import json
 
154
  logger.info("BiRefNet model loaded successfully")
155
  return birefnet
156
 
157
+ # def detect_paper_contour(image: np.ndarray) -> Tuple[np.ndarray, float]:
158
+ # """
159
+ # Detect paper in the image using contour detection as fallback
160
+ # Returns the paper contour and estimated scaling factor
161
+ # """
162
+ # logger.info("Using contour-based paper detection")
163
+
164
+ # # Convert to grayscale
165
+ # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) == 3 else image
166
+
167
+ # # Apply bilateral filter to reduce noise while preserving edges
168
+ # filtered = cv2.bilateralFilter(gray, 9, 75, 75)
169
+
170
+ # # Apply adaptive threshold
171
+ # thresh = cv2.adaptiveThreshold(filtered, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
172
+ # cv2.THRESH_BINARY, 11, 2)
173
+
174
+ # # Edge detection with multiple thresholds
175
+ # edges1 = cv2.Canny(filtered, 50, 150)
176
+ # edges2 = cv2.Canny(filtered, 30, 100)
177
+ # edges = cv2.bitwise_or(edges1, edges2)
178
+
179
+ # # Morphological operations to connect broken edges
180
+ # kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
181
+ # edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
182
+
183
+ # # Find contours
184
+ # contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
185
+
186
+ # # Filter contours by area and aspect ratio to find paper-like rectangles
187
+ # paper_contours = []
188
+ # image_area = image.shape[0] * image.shape[1]
189
+ # min_area = image_area * 0.20 # At least 15% of image
190
+ # max_area = image_area * 0.85 # At most 95% of image
191
+
192
+ # for contour in contours:
193
+ # area = cv2.contourArea(contour)
194
+ # if min_area < area < max_area:
195
+ # # Approximate contour to polygon
196
+ # epsilon = 0.015 * cv2.arcLength(contour, True)
197
+ # approx = cv2.approxPolyDP(contour, epsilon, True)
198
+
199
+ # # Check if it's roughly rectangular (4 corners) or close to it
200
+ # if len(approx) >= 4:
201
+ # # Calculate bounding rectangle
202
+ # rect = cv2.boundingRect(approx)
203
+ # w, h = rect[2], rect[3]
204
+ # aspect_ratio = w / h if h > 0 else 0
205
+
206
+ # # Check if aspect ratio matches common paper ratios
207
+ # # A4: 1.414, A3: 1.414, US Letter: 1.294
208
+ # if 1.3 < aspect_ratio < 1.5: # More lenient tolerance
209
+ # # Check if contour area is close to bounding rect area (rectangularity)
210
+ # rect_area = w * h
211
+ # if rect_area > 0:
212
+ # extent = area / rect_area
213
+ # if extent > 0.85: # At least 70% rectangular
214
+ # paper_contours.append((contour, area, aspect_ratio, extent))
215
+
216
+ # if not paper_contours:
217
+ # logger.error("No paper-like contours found")
218
+ # raise ReferenceBoxNotDetectedError("Could not detect paper in the image using contour detection")
219
+
220
+ # # Select the best paper contour based on area and rectangularity
221
+ # paper_contours.sort(key=lambda x: (x[1] * x[3]), reverse=True) # Sort by area * extent
222
+ # best_contour = paper_contours[0][0]
223
+
224
+ # logger.info(f"Paper detected using contours: area={paper_contours[0][1]}, aspect_ratio={paper_contours[0][2]:.2f}")
225
+
226
+ # return best_contour, 0.0 # Return 0.0 as placeholder scaling factor
227
+ def detect_paper_contour(image: np.ndarray, output_unit: str = "mm") -> Tuple[np.ndarray, float]:
228
  """
229
  Detect paper in the image using contour detection as fallback
230
  Returns the paper contour and estimated scaling factor
 
280
  rect_area = w * h
281
  if rect_area > 0:
282
  extent = area / rect_area
283
+ if extent > 0.85: # At least 85% rectangular
284
  paper_contours.append((contour, area, aspect_ratio, extent))
285
 
286
  if not paper_contours:
 
293
 
294
  logger.info(f"Paper detected using contours: area={paper_contours[0][1]}, aspect_ratio={paper_contours[0][2]:.2f}")
295
 
296
+ # Return 0.0 as placeholder - will be calculated later based on paper size
297
+ return best_contour, 0.0
298
+
299
+ def detect_paper_bounds(image: np.ndarray, paper_size: str, output_unit: str = "mm") -> Tuple[np.ndarray, float]:
300
  """
301
  Detect paper bounds in the image and calculate scaling factor
302
  """
 
305
 
306
  if paper_detector is not None:
307
  # Use trained model if available
308
+ results = paper_detector.predict(image, conf=0.8, verbose=False)
 
309
 
310
  if not results or len(results) == 0:
311
  logger.warning("No results from paper detector")
312
+ return detect_paper_contour(image, output_unit)
313
 
314
  # Check if boxes exist and are not empty
315
  if not hasattr(results[0], 'boxes') or results[0].boxes is None or len(results[0].boxes) == 0:
316
  logger.warning("No boxes detected by model, using fallback contour detection")
317
+ return detect_paper_contour(image, output_unit)
318
 
319
  # Get the largest detected paper
320
+ boxes = results[0].boxes.xyxy.cpu().numpy()
321
  if len(boxes) == 0:
322
  logger.warning("Empty boxes detected, using fallback")
323
+ return detect_paper_contour(image, output_unit)
324
 
325
  largest_box = None
326
  max_area = 0
 
334
 
335
  if largest_box is None:
336
  logger.warning("No valid paper box found, using fallback")
337
+ return detect_paper_contour(image, output_unit)
338
 
339
  # Convert box to contour-like format
340
  x_min, y_min, x_max, y_max = map(int, largest_box)
 
350
  else:
351
  # Use fallback contour detection
352
  logger.info("Using fallback contour detection for paper")
353
+ paper_contour, _ = detect_paper_contour(image, output_unit)
354
 
355
+ # Calculate scaling factor based on paper size with proper units
356
+ scaling_factor = calculate_paper_scaling_factor(paper_contour, paper_size, output_unit)
357
 
358
  return paper_contour, scaling_factor
359
 
360
  except Exception as e:
361
  logger.error(f"Error in paper detection: {e}")
 
362
  raise ReferenceBoxNotDetectedError(f"Failed to detect paper: {str(e)}")
363
 
364
+ # def detect_paper_bounds(image: np.ndarray, paper_size: str) -> Tuple[np.ndarray, float]:
365
+ # """
366
+ # Detect paper bounds in the image and calculate scaling factor
367
+ # """
368
+ # try:
369
+ # paper_detector = get_paper_detector()
370
+
371
+ # if paper_detector is not None:
372
+ # # Use trained model if available
373
+ # # FIXED: Add verbose=False to suppress prints, and use proper confidence threshold
374
+ # results = paper_detector.predict(image, conf=0.8, verbose=False) # Lower confidence threshold
375
+
376
+ # if not results or len(results) == 0:
377
+ # logger.warning("No results from paper detector")
378
+ # return detect_paper_contour(image)
379
+
380
+ # # Check if boxes exist and are not empty
381
+ # if not hasattr(results[0], 'boxes') or results[0].boxes is None or len(results[0].boxes) == 0:
382
+ # logger.warning("No boxes detected by model, using fallback contour detection")
383
+ # return detect_paper_contour(image)
384
+
385
+ # # Get the largest detected paper
386
+ # boxes = results[0].boxes.xyxy.cpu().numpy() # Convert to numpy
387
+ # if len(boxes) == 0:
388
+ # logger.warning("Empty boxes detected, using fallback")
389
+ # return detect_paper_contour(image)
390
+
391
+ # largest_box = None
392
+ # max_area = 0
393
+
394
+ # for box in boxes:
395
+ # x_min, y_min, x_max, y_max = box
396
+ # area = (x_max - x_min) * (y_max - y_min)
397
+ # if area > max_area:
398
+ # max_area = area
399
+ # largest_box = box
400
+
401
+ # if largest_box is None:
402
+ # logger.warning("No valid paper box found, using fallback")
403
+ # return detect_paper_contour(image)
404
+
405
+ # # Convert box to contour-like format
406
+ # x_min, y_min, x_max, y_max = map(int, largest_box)
407
+ # paper_contour = np.array([
408
+ # [[x_min, y_min]],
409
+ # [[x_max, y_min]],
410
+ # [[x_max, y_max]],
411
+ # [[x_min, y_max]]
412
+ # ])
413
+
414
+ # logger.info(f"Paper detected by model: {x_min},{y_min} to {x_max},{y_max}")
415
+
416
+ # else:
417
+ # # Use fallback contour detection
418
+ # logger.info("Using fallback contour detection for paper")
419
+ # paper_contour, _ = detect_paper_contour(image)
420
+
421
+ # # Calculate scaling factor based on paper size
422
+ # scaling_factor = calculate_paper_scaling_factor(paper_contour, paper_size)
423
+
424
+ # return paper_contour, scaling_factor
425
+
426
+ # except Exception as e:
427
+ # logger.error(f"Error in paper detection: {e}")
428
+ # # Instead of raising PaperNotDetectedError, raise ReferenceBoxNotDetectedError
429
+ # raise ReferenceBoxNotDetectedError(f"Failed to detect paper: {str(e)}")
430
+
431
+ # def calculate_paper_scaling_factor(paper_contour: np.ndarray, paper_size: str) -> float:
432
+ # """
433
+ # Calculate scaling factor based on detected paper dimensions
434
+ # """
435
+ # # Get paper dimensions
436
+ # paper_dims = PAPER_SIZES[paper_size]
437
+ # expected_width_mm = paper_dims["width"]
438
+ # expected_height_mm = paper_dims["height"]
439
 
440
+ # # Calculate bounding rectangle of paper contour
441
+ # rect = cv2.boundingRect(paper_contour)
442
+ # detected_width_px = rect[2]
443
+ # detected_height_px = rect[3]
444
 
445
+ # # Calculate scaling factors for both dimensions
446
+ # scale_x = expected_width_mm / detected_width_px
447
+ # scale_y = expected_height_mm / detected_height_px
448
 
449
+ # # Use average of both scales
450
+ # # scaling_factor = (scale_x + scale_y) / 2
451
+ # scaling_factor = min(scale_x, scale_y)
452
 
453
+ # logger.info(f"Paper detection: {detected_width_px}x{detected_height_px} px -> {expected_width_mm}x{expected_height_mm} mm")
454
+ # logger.info(f"Calculated scaling factor: {scaling_factor:.4f} mm/px")
455
 
456
+ # return scaling_factor
457
+
458
+ def calculate_paper_scaling_factor(paper_contour: np.ndarray, paper_size: str, output_unit: str = "mm") -> float:
459
+ """
460
+ Calculate scaling factor based on detected paper dimensions with proper unit handling.
461
+ """
462
+ from scalingtestupdated import calculate_paper_scaling_factor as calc_paper_scale
463
+ scaling_factor, unit_string = calc_paper_scale(paper_contour, paper_size, output_unit)
464
  return scaling_factor
465
 
466
+
467
  def validate_single_object(mask: np.ndarray, paper_contour: np.ndarray) -> None:
468
  """
469
  Validate that only a single object is present on the paper
 
670
  logger.error(f"Error in resample_contour: {e}")
671
  raise
672
 
673
+ # def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=False):
674
+ # """Save contours as DXF splines with optional finger cuts"""
675
+ # doc = ezdxf.new(units=ezdxf.units.MM)
676
+ # doc.header["$INSUNITS"] = ezdxf.units.MM
677
+ # msp = doc.modelspace()
678
+ # final_polygons_inch = []
679
+ # finger_centers = []
680
+ # original_polygons = []
681
+
682
+ # # Scale correction factor
683
+ # scale_correction = 1.0
684
+
685
+ # for contour in inflated_contours:
686
+ # try:
687
+ # resampled_contour = resample_contour(contour)
688
+
689
+ # points_inch = [(x * scaling_factor, (height - y) * scaling_factor)
690
+ # for x, y in resampled_contour]
691
+
692
+ # if len(points_inch) < 3:
693
+ # continue
694
+
695
+ # tool_polygon = build_tool_polygon(points_inch)
696
+ # original_polygons.append(tool_polygon)
697
+
698
+ # if finger_clearance:
699
+ # try:
700
+ # tool_polygon, center = place_finger_cut_adjusted(
701
+ # tool_polygon, points_inch, finger_centers, final_polygons_inch
702
+ # )
703
+ # except FingerCutOverlapError:
704
+ # tool_polygon = original_polygons[-1]
705
+
706
+ # exterior_coords = polygon_to_exterior_coords(tool_polygon)
707
+ # if len(exterior_coords) < 3:
708
+ # continue
709
+
710
+ # # Apply scale correction
711
+ # corrected_coords = [(x * scale_correction, y * scale_correction) for x, y in exterior_coords]
712
+
713
+ # msp.add_spline(corrected_coords, degree=3, dxfattribs={"layer": "TOOLS"})
714
+ # final_polygons_inch.append(tool_polygon)
715
+
716
+ # except ValueError as e:
717
+ # logger.warning(f"Skipping contour: {e}")
718
+
719
+ # dxf_filepath = os.path.join("./outputs", "out.dxf")
720
+ # doc.saveas(dxf_filepath)
721
+ # return dxf_filepath, final_polygons_inch, original_polygons
722
+
723
  def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=False):
724
+ """Save contours as DXF splines with optional finger cuts - scaling_factor should be in mm/px"""
725
  doc = ezdxf.new(units=ezdxf.units.MM)
726
  doc.header["$INSUNITS"] = ezdxf.units.MM
727
  msp = doc.modelspace()
728
+ final_polygons_mm = [] # Use mm instead of inch for clarity
729
  finger_centers = []
730
  original_polygons = []
731
 
 
 
 
732
  for contour in inflated_contours:
733
  try:
734
  resampled_contour = resample_contour(contour)
735
 
736
+ # Convert pixel coordinates to mm using the scaling factor
737
+ points_mm = [(x * scaling_factor, (height - y) * scaling_factor)
738
+ for x, y in resampled_contour]
739
 
740
+ if len(points_mm) < 3:
741
  continue
742
 
743
+ tool_polygon = build_tool_polygon(points_mm)
744
  original_polygons.append(tool_polygon)
745
 
746
  if finger_clearance:
747
  try:
748
  tool_polygon, center = place_finger_cut_adjusted(
749
+ tool_polygon, points_mm, finger_centers, final_polygons_mm
750
  )
751
  except FingerCutOverlapError:
752
  tool_polygon = original_polygons[-1]
 
755
  if len(exterior_coords) < 3:
756
  continue
757
 
758
+ # Coordinates are already in mm, so add directly to DXF
759
+ msp.add_spline(exterior_coords, degree=3, dxfattribs={"layer": "TOOLS"})
760
+ final_polygons_mm.append(tool_polygon)
 
 
761
 
762
  except ValueError as e:
763
  logger.warning(f"Skipping contour: {e}")
764
 
765
  dxf_filepath = os.path.join("./outputs", "out.dxf")
766
  doc.saveas(dxf_filepath)
767
+ return dxf_filepath, final_polygons_mm, original_polygons
768
 
769
  def build_tool_polygon(points_inch):
770
  """Build a polygon from inch-converted points"""
 
1058
 
1059
  return padded
1060
 
1061
+ # def predict_with_paper(image, paper_size, offset,offset_unit, finger_clearance=False):
1062
+ # """Main prediction function using paper as reference"""
1063
+
1064
+ # logger.info(f"Starting prediction with image shape: {image.shape}")
1065
+ # logger.info(f"Paper size: {paper_size}")
1066
+
1067
+ # if offset_unit == "inches":
1068
+ # offset_mm = offset * 25.4 # Convert to mm for internal calculations
1069
+ # else:
1070
+ # offset_mm = offset
1071
+
1072
+ # edge_radius = None
1073
+ # if edge_radius is None or edge_radius == 0:
1074
+ # edge_radius = 0.0001
1075
+
1076
+ # if offset < 0:
1077
+ # raise gr.Error("Offset Value Can't be negative")
1078
+
1079
+ # try:
1080
+ # # Detect paper bounds and calculate scaling factor
1081
+ # logger.info("Starting paper detection...")
1082
+ # paper_contour, scaling_factor = detect_paper_bounds(image, paper_size)
1083
+ # logger.info(f"Paper detected successfully with scaling factor: {scaling_factor:.4f} mm/px")
1084
+
1085
+ # except ReferenceBoxNotDetectedError as e:
1086
+ # logger.error(f"Paper detection failed: {e}")
1087
+ # return (
1088
+ # None, None, None, None,
1089
+ # f"Error: {str(e)}"
1090
+ # )
1091
+ # except Exception as e:
1092
+ # logger.error(f"Unexpected error in paper detection: {e}")
1093
+ # raise gr.Error(f"Error processing image: {str(e)}")
1094
+
1095
+ # try:
1096
+ # # Remove background from main objects
1097
+ # # orig_size = image.shape[:2]
1098
+ # # objects_mask = remove_bg(image)
1099
+
1100
+ # # Mask paper area in input image first
1101
+ # masked_input_image = mask_paper_area_in_image(image, paper_contour)
1102
+
1103
+ # # Remove background from main objects
1104
+ # orig_size = image.shape[:2]
1105
+ # objects_mask = remove_bg(image)
1106
+ # processed_size = objects_mask.shape[:2]
1107
+
1108
+ # # Resize mask to match original image
1109
+ # objects_mask = cv2.resize(objects_mask, (image.shape[1], image.shape[0]))
1110
+
1111
+ # # Remove paper area from mask to focus only on objects
1112
+ # objects_mask = exclude_paper_area(objects_mask, paper_contour)
1113
+
1114
+ # # Check if we actually have object pixels after paper exclusion
1115
+ # object_pixels = np.count_nonzero(objects_mask)
1116
+ # if object_pixels < 1000: # Minimum threshold
1117
+ # raise NoObjectDetectedError("No significant object detected after excluding paper area")
1118
+
1119
+ # # Validate single object
1120
+ # validate_single_object(objects_mask, paper_contour)
1121
+
1122
+ # except (MultipleObjectsError, NoObjectDetectedError) as e:
1123
+ # return (
1124
+ # None, None, None, None,
1125
+ # f"Error: {str(e)}"
1126
+ # )
1127
+ # except Exception as e:
1128
+ # raise gr.Error(f"Error in object detection: {str(e)}")
1129
+
1130
+ # # Apply edge rounding if specified
1131
+ # rounded_mask = objects_mask.copy()
1132
+
1133
+ # # Apply dilation for offset - more precise calculation
1134
+ # if offset_mm > 0:
1135
+ # offset_pixels = int(round(float(offset_mm) / scaling_factor))
1136
+ # if offset_pixels > 0:
1137
+ # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (offset_pixels*2+1, offset_pixels*2+1))
1138
+ # dilated_mask = cv2.dilate(rounded_mask, kernel, iterations=1)
1139
+ # else:
1140
+ # dilated_mask = rounded_mask.copy()
1141
+
1142
+ # # Save original dilated mask for output
1143
+ # Image.fromarray(dilated_mask).save("./outputs/scaled_mask_original.jpg")
1144
+ # dilated_mask_orig = dilated_mask.copy()
1145
+
1146
+ # # Extract contours
1147
+ # outlines, contours = extract_outlines(dilated_mask)
1148
+
1149
+ # try:
1150
+ # # Generate DXF
1151
+ # dxf, finger_polygons, original_polygons = save_dxf_spline(
1152
+ # contours,
1153
+ # scaling_factor,
1154
+ # processed_size[0],
1155
+ # finger_clearance=(finger_clearance == "On")
1156
+ # )
1157
+ # except FingerCutOverlapError as e:
1158
+ # raise gr.Error(str(e))
1159
+
1160
+ # # Create annotated image
1161
+ # shrunked_img_contours = image.copy()
1162
+
1163
+ # if finger_clearance == "On":
1164
+ # outlines = np.full_like(dilated_mask, 255)
1165
+ # for poly in finger_polygons:
1166
+ # try:
1167
+ # coords = np.array([
1168
+ # (int(x / scaling_factor), int(processed_size[0] - y / scaling_factor))
1169
+ # for x, y in poly.exterior.coords
1170
+ # ], np.int32).reshape((-1, 1, 2))
1171
+
1172
+ # cv2.drawContours(shrunked_img_contours, [coords], -1, (0, 255, 0), thickness=2)
1173
+ # cv2.drawContours(outlines, [coords], -1, 0, thickness=2)
1174
+ # except Exception as e:
1175
+ # logger.warning(f"Failed to draw finger cut: {e}")
1176
+ # continue
1177
+ # else:
1178
+ # outlines = np.full_like(dilated_mask, 255)
1179
+ # cv2.drawContours(shrunked_img_contours, contours, -1, (0, 255, 0), thickness=2)
1180
+ # cv2.drawContours(outlines, contours, -1, 0, thickness=2)
1181
+
1182
+ # # # Draw paper bounds on annotated image
1183
+ # # cv2.drawContours(shrunked_img_contours, [paper_contour], -1, (255, 0, 0), thickness=3)
1184
+
1185
+ # # # Add paper size text
1186
+ # # paper_text = f"Paper: {paper_size}"
1187
+ # # cv2.putText(shrunked_img_contours, paper_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
1188
+
1189
+ # cleanup_models()
1190
+
1191
+ # return (
1192
+ # shrunked_img_contours,
1193
+ # outlines,
1194
+ # dxf,
1195
+ # dilated_mask_orig,
1196
+ # f"Scale: {scaling_factor:.4f} mm/px | Paper: {paper_size}"
1197
+ # )
1198
+ def predict_with_paper(image, paper_size, offset, offset_unit, finger_clearance=False):
1199
  """Main prediction function using paper as reference"""
1200
 
1201
  logger.info(f"Starting prediction with image shape: {image.shape}")
1202
+ logger.info(f"Paper size: {paper_size}, Offset: {offset} {offset_unit}")
1203
 
1204
+ # Convert offset to mm for internal calculations (DXF generation expects mm)
1205
  if offset_unit == "inches":
1206
+ offset_mm = convert_units(offset, "inches", "mm")
1207
  else:
1208
  offset_mm = offset
1209
 
 
1215
  raise gr.Error("Offset Value Can't be negative")
1216
 
1217
  try:
1218
+ # Detect paper bounds and calculate scaling factor (always in mm for DXF)
1219
  logger.info("Starting paper detection...")
1220
+ paper_contour, scaling_factor = detect_paper_bounds(image, paper_size, output_unit="mm")
1221
+ logger.info(f"Paper detected successfully with scaling factor: {scaling_factor:.6f} mm/px")
1222
 
1223
  except ReferenceBoxNotDetectedError as e:
1224
  logger.error(f"Paper detection failed: {e}")
 
1230
  logger.error(f"Unexpected error in paper detection: {e}")
1231
  raise gr.Error(f"Error processing image: {str(e)}")
1232
 
1233
+ # Rest of the function remains the same...
1234
+ # [Keep all the existing object detection and DXF generation code]
1235
+
1236
  try:
 
 
 
 
1237
  # Mask paper area in input image first
1238
  masked_input_image = mask_paper_area_in_image(image, paper_contour)
1239
 
 
1267
  # Apply edge rounding if specified
1268
  rounded_mask = objects_mask.copy()
1269
 
1270
+ # Apply dilation for offset - more precise calculation using mm values
1271
  if offset_mm > 0:
1272
+ offset_pixels = max(1, int(round(float(offset_mm) / scaling_factor)))
1273
  if offset_pixels > 0:
1274
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (offset_pixels*2+1, offset_pixels*2+1))
1275
  dilated_mask = cv2.dilate(rounded_mask, kernel, iterations=1)
1276
  else:
1277
  dilated_mask = rounded_mask.copy()
1278
+ else:
1279
+ dilated_mask = rounded_mask.copy()
1280
 
1281
  # Save original dilated mask for output
1282
  Image.fromarray(dilated_mask).save("./outputs/scaled_mask_original.jpg")
 
1286
  outlines, contours = extract_outlines(dilated_mask)
1287
 
1288
  try:
1289
+ # Generate DXF - scaling_factor should be in mm/px for proper DXF units
1290
  dxf, finger_polygons, original_polygons = save_dxf_spline(
1291
  contours,
1292
+ scaling_factor, # This should be mm/px
1293
  processed_size[0],
1294
  finger_clearance=(finger_clearance == "On")
1295
  )
 
1318
  cv2.drawContours(shrunked_img_contours, contours, -1, (0, 255, 0), thickness=2)
1319
  cv2.drawContours(outlines, contours, -1, 0, thickness=2)
1320
 
 
 
 
 
 
 
 
1321
  cleanup_models()
1322
 
1323
+ # Format scaling info with proper unit display
1324
+ if offset_unit == "inches":
1325
+ offset_display = f"{offset} inches ({offset_mm:.3f} mm)"
1326
+ else:
1327
+ offset_display = f"{offset} mm"
1328
+
1329
+ scale_info = f"Scale: {scaling_factor:.6f} mm/px | Paper: {paper_size} | Offset: {offset_display}"
1330
+
1331
  return (
1332
  shrunked_img_contours,
1333
  outlines,
1334
  dxf,
1335
  dilated_mask_orig,
1336
+ scale_info
1337
  )
1338
 
1339
  def predict_full_paper(image, paper_size, offset_value_mm,offset_unit, enable_finger_cut, selected_outputs):