ammariii08 commited on
Commit
017b32e
·
verified ·
1 Parent(s): 62a0b65

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -25
app.py CHANGED
@@ -41,6 +41,14 @@ class ReferenceBoxNotDetectedError(Exception):
41
  """Raised when the reference box cannot be detected in the image"""
42
  pass
43
 
 
 
 
 
 
 
 
 
44
  # ---------------------
45
  # Global Model Initialization with caching and print statements
46
  # ---------------------
@@ -339,14 +347,21 @@ def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=
339
  print(f"Skipping contour: {e}")
340
  return doc, final_polygons_inch
341
 
342
- def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width, boundary_unit):
343
  msp = doc.modelspace()
344
- if boundary_unit == "mm":
 
 
 
 
 
345
  boundary_length_in = boundary_length / 25.4
346
  boundary_width_in = boundary_width / 25.4
347
  else:
348
  boundary_length_in = boundary_length
349
  boundary_width_in = boundary_width
 
 
350
  min_x = float("inf")
351
  min_y = float("inf")
352
  max_x = -float("inf")
@@ -360,6 +375,19 @@ def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width
360
  if min_x == float("inf"):
361
  print("No tool polygons found, skipping boundary.")
362
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  shape_cx = (min_x + max_x) / 2
364
  shape_cy = (min_y + max_y) / 2
365
  half_w = boundary_width_in / 2.0
@@ -369,6 +397,7 @@ def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width
369
  bottom = shape_cy - half_l
370
  top = shape_cy + half_l
371
  rect_coords = [(left, bottom), (right, bottom), (right, top), (left, top), (left, bottom)]
 
372
  from shapely.geometry import Polygon as ShapelyPolygon
373
  boundary_polygon = ShapelyPolygon(rect_coords)
374
  msp.add_lwpolyline(rect_coords, close=True, dxfattribs={"layer": "BOUNDARY"})
@@ -399,12 +428,12 @@ def draw_single_polygon(poly, image_rgb, scaling_factor, image_height, color=(0,
399
  # ---------------------
400
  def predict(
401
  image: Union[str, bytes, np.ndarray],
402
- offset_inches: float,
 
403
  finger_clearance: str, # "Yes" or "No"
404
  add_boundary: str, # "Yes" or "No"
405
  boundary_length: float,
406
  boundary_width: float,
407
- boundary_unit: str,
408
  annotation_text: str
409
  ):
410
  overall_start = time.time()
@@ -417,7 +446,7 @@ def predict(
417
  image = np.array(Image.open(io.BytesIO(base64.b64decode(image))).convert("RGB"))
418
  except Exception:
419
  raise ValueError("Invalid base64 image data")
420
- # Apply sharpness enhancement if image is a NumPy array.
421
  if isinstance(image, np.ndarray):
422
  pil_image = Image.fromarray(image)
423
  enhanced_image = ImageEnhance.Sharpness(pil_image).enhance(1.5)
@@ -462,6 +491,35 @@ def predict(
462
  print("Using default scaling factor of 1.0 due to calculation error")
463
  gc.collect()
464
  print("Scaling factor determined: {}".format(scaling_factor))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  t = time.time()
466
  orig_size = shrunked_img.shape[:2]
467
  objects_mask = remove_bg(shrunked_img)
@@ -477,17 +535,13 @@ def predict(
477
  del objects_mask
478
  gc.collect()
479
  print("Mask dilation completed in {:.2f} seconds".format(time.time() - t))
480
- # Save the dilated mask for debugging if needed.
481
  Image.fromarray(dilated_mask).save("./outputs/scaled_mask_new.jpg")
482
- # --- Extract outlines (only used for DXF generation) ---
483
  t = time.time()
484
  outlines, contours = extract_outlines(dilated_mask)
485
  print("Outline extraction completed in {:.2f} seconds".format(time.time() - t))
486
- # Instead of drawing the original contours, we now prepare a clean copy of the shrunk image for drawing new contours.
487
  output_img = shrunked_img.copy()
488
  del shrunked_img
489
  gc.collect()
490
- # --- Generate DXF using the extracted contours and apply finger clearance ---
491
  t = time.time()
492
  use_finger_clearance = True if finger_clearance.lower() == "yes" else False
493
  doc, final_polygons_inch = save_dxf_spline(contours, scaling_factor, processed_size[0], finger_clearance=use_finger_clearance)
@@ -496,10 +550,9 @@ def predict(
496
  print("DXF generation completed in {:.2f} seconds".format(time.time() - t))
497
  boundary_polygon = None
498
  if add_boundary.lower() == "yes":
499
- boundary_polygon = add_rectangular_boundary(doc, final_polygons_inch, boundary_length, boundary_width, boundary_unit)
500
  if boundary_polygon is not None:
501
  final_polygons_inch.append(boundary_polygon)
502
- # --- Annotation Text Placement (Centered horizontally) ---
503
  min_x = float("inf")
504
  min_y = float("inf")
505
  max_x = -float("inf")
@@ -514,31 +567,34 @@ def predict(
514
  max_x = b[2]
515
  if b[3] > max_y:
516
  max_y = b[3]
517
- margin = 0.5
518
  text_x = (min_x + max_x) / 2
519
- text_y = min_y - margin
 
 
 
520
  msp = doc.modelspace()
521
  if annotation_text.strip():
522
  text_entity = msp.add_text(
523
  annotation_text.strip(),
524
  dxfattribs={
525
- "height": 0.25,
526
- "layer": "ANNOTATION"
 
527
  }
528
  )
529
  text_entity.dxf.insert = (text_x, text_y)
530
  dxf_filepath = os.path.join("./outputs", "out.dxf")
531
  doc.saveas(dxf_filepath)
532
- # --- Draw only the new contours (final_polygons_inch) on the clean output image ---
533
  draw_polygons_inch(final_polygons_inch, output_img, scaling_factor, processed_size[0], color=(0,0,255), thickness=2)
534
- # Also prepare an "Outlines" image based on a blank canvas for clarity.
535
  new_outlines = np.ones_like(output_img) * 255
536
  draw_polygons_inch(final_polygons_inch, new_outlines, scaling_factor, processed_size[0], color=(0,0,255), thickness=2)
537
  if annotation_text.strip():
538
  text_px = int(text_x / scaling_factor)
539
  text_py = int(processed_size[0] - (text_y / scaling_factor))
540
- cv2.putText(output_img, annotation_text.strip(), (text_px, text_py), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2, cv2.LINE_AA)
541
- cv2.putText(new_outlines, annotation_text.strip(), (text_px, text_py), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2, cv2.LINE_AA)
 
542
  outlines_color = cv2.cvtColor(new_outlines, cv2.COLOR_BGR2RGB)
543
  print("Total prediction time: {:.2f} seconds".format(time.time() - overall_start))
544
  return (
@@ -554,18 +610,21 @@ def predict(
554
  # ---------------------
555
  if __name__ == "__main__":
556
  os.makedirs("./outputs", exist_ok=True)
557
- def gradio_predict(img, offset, finger_clearance, add_boundary, boundary_length, boundary_width, boundary_unit, annotation_text):
558
- return predict(img, offset, finger_clearance, add_boundary, boundary_length, boundary_width, boundary_unit, annotation_text)
 
 
 
559
  iface = gr.Interface(
560
  fn=gradio_predict,
561
  inputs=[
562
  gr.Image(label="Input Image"),
563
- gr.Number(label="Offset value for Mask (inches)", value=0.075),
 
564
  gr.Dropdown(label="Add Finger Clearance?", choices=["Yes", "No"], value="No"),
565
  gr.Dropdown(label="Add Rectangular Boundary?", choices=["Yes", "No"], value="No"),
566
  gr.Number(label="Boundary Length", value=300.0, precision=2),
567
  gr.Number(label="Boundary Width", value=200.0, precision=2),
568
- gr.Dropdown(label="Boundary Unit", choices=["mm", "inches"], value="mm"),
569
  gr.Textbox(label="Annotation (max 20 chars)", max_length=20, placeholder="Type up to 20 characters")
570
  ],
571
  outputs=[
@@ -576,8 +635,8 @@ if __name__ == "__main__":
576
  gr.Textbox(label="Scaling Factor (inches/pixel)")
577
  ],
578
  examples=[
579
- ["./Test20.jpg", 0.075, "No", "No", 300.0, 200.0, "mm", "MyTool"],
580
- ["./Test21.jpg", 0.075, "Yes", "Yes", 300.0, 200.0, "mm", "Tool2"]
581
  ]
582
  )
583
  iface.launch(share=True)
 
41
  """Raised when the reference box cannot be detected in the image"""
42
  pass
43
 
44
+ class BoundaryExceedsError(Exception):
45
+ """Raised when the optional boundary dimensions exceed allowed image dimensions."""
46
+ pass
47
+
48
+ class BoundaryOverlapError(Exception):
49
+ """Raised when the optional boundary dimensions are too small and overlap with the inner contours."""
50
+ pass
51
+
52
  # ---------------------
53
  # Global Model Initialization with caching and print statements
54
  # ---------------------
 
347
  print(f"Skipping contour: {e}")
348
  return doc, final_polygons_inch
349
 
350
+ def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width, offset_unit):
351
  msp = doc.modelspace()
352
+ # First, if unit is mm, check if values seem too low (accidental inches) and convert them.
353
+ if offset_unit.lower() == "mm":
354
+ if boundary_length < 50:
355
+ boundary_length = boundary_length * 25.4
356
+ if boundary_width < 50:
357
+ boundary_width = boundary_width * 25.4
358
  boundary_length_in = boundary_length / 25.4
359
  boundary_width_in = boundary_width / 25.4
360
  else:
361
  boundary_length_in = boundary_length
362
  boundary_width_in = boundary_width
363
+
364
+ # Compute bounding box from inner contours.
365
  min_x = float("inf")
366
  min_y = float("inf")
367
  max_x = -float("inf")
 
375
  if min_x == float("inf"):
376
  print("No tool polygons found, skipping boundary.")
377
  return None
378
+
379
+ # Calculate inner bounding box dimensions.
380
+ inner_width = max_x - min_x
381
+ inner_length = max_y - min_y
382
+
383
+ # Define a clearance margin (in inches) required between the inner contours and the boundary.
384
+ clearance_margin = 0.2 # Adjust this value as needed
385
+
386
+ # New check: if the provided boundary dimensions are too small relative to the inner contours, raise an error.
387
+ if boundary_width_in <= inner_width + clearance_margin or boundary_length_in <= inner_length + clearance_margin:
388
+ raise BoundaryOverlapError("Error: The specified boundary dimensions are too small and overlap with the inner contours. Please provide larger values.")
389
+
390
+ # Compute the boundary rectangle centered on the inner contours.
391
  shape_cx = (min_x + max_x) / 2
392
  shape_cy = (min_y + max_y) / 2
393
  half_w = boundary_width_in / 2.0
 
397
  bottom = shape_cy - half_l
398
  top = shape_cy + half_l
399
  rect_coords = [(left, bottom), (right, bottom), (right, top), (left, top), (left, bottom)]
400
+
401
  from shapely.geometry import Polygon as ShapelyPolygon
402
  boundary_polygon = ShapelyPolygon(rect_coords)
403
  msp.add_lwpolyline(rect_coords, close=True, dxfattribs={"layer": "BOUNDARY"})
 
428
  # ---------------------
429
  def predict(
430
  image: Union[str, bytes, np.ndarray],
431
+ offset_value: float,
432
+ offset_unit: str, # "mm" or "inches"
433
  finger_clearance: str, # "Yes" or "No"
434
  add_boundary: str, # "Yes" or "No"
435
  boundary_length: float,
436
  boundary_width: float,
 
437
  annotation_text: str
438
  ):
439
  overall_start = time.time()
 
446
  image = np.array(Image.open(io.BytesIO(base64.b64decode(image))).convert("RGB"))
447
  except Exception:
448
  raise ValueError("Invalid base64 image data")
449
+ # Apply sharpness enhancement.
450
  if isinstance(image, np.ndarray):
451
  pil_image = Image.fromarray(image)
452
  enhanced_image = ImageEnhance.Sharpness(pil_image).enhance(1.5)
 
491
  print("Using default scaling factor of 1.0 due to calculation error")
492
  gc.collect()
493
  print("Scaling factor determined: {}".format(scaling_factor))
494
+
495
+ # ---------------------
496
+ # Process boundary dimensions if boundary is enabled.
497
+ # ---------------------
498
+ if add_boundary.lower() == "yes":
499
+ image_height_px, image_width_px = shrunked_img.shape[:2]
500
+ image_height_in = image_height_px * scaling_factor
501
+ image_width_in = image_width_px * scaling_factor
502
+ # First, if units are mm, check if boundary dimensions seem too low and convert.
503
+ if offset_unit.lower() == "mm":
504
+ if boundary_length < 50:
505
+ boundary_length = boundary_length * 25.4
506
+ if boundary_width < 50:
507
+ boundary_width = boundary_width * 25.4
508
+ boundary_length_in = boundary_length / 25.4
509
+ boundary_width_in = boundary_width / 25.4
510
+ else:
511
+ boundary_length_in = boundary_length
512
+ boundary_width_in = boundary_width
513
+ if boundary_length_in > (image_height_in - 2) or boundary_width_in > (image_width_in - 2):
514
+ raise BoundaryExceedsError("Error: The specified boundary dimensions exceed the allowed image dimensions (image size minus 2 inches). Please enter smaller values.")
515
+
516
+ # Convert offset value.
517
+ if offset_unit.lower() == "mm":
518
+ if offset_value < 1:
519
+ offset_value = offset_value * 25.4
520
+ offset_inches = offset_value / 25.4
521
+ else:
522
+ offset_inches = offset_value
523
  t = time.time()
524
  orig_size = shrunked_img.shape[:2]
525
  objects_mask = remove_bg(shrunked_img)
 
535
  del objects_mask
536
  gc.collect()
537
  print("Mask dilation completed in {:.2f} seconds".format(time.time() - t))
 
538
  Image.fromarray(dilated_mask).save("./outputs/scaled_mask_new.jpg")
 
539
  t = time.time()
540
  outlines, contours = extract_outlines(dilated_mask)
541
  print("Outline extraction completed in {:.2f} seconds".format(time.time() - t))
 
542
  output_img = shrunked_img.copy()
543
  del shrunked_img
544
  gc.collect()
 
545
  t = time.time()
546
  use_finger_clearance = True if finger_clearance.lower() == "yes" else False
547
  doc, final_polygons_inch = save_dxf_spline(contours, scaling_factor, processed_size[0], finger_clearance=use_finger_clearance)
 
550
  print("DXF generation completed in {:.2f} seconds".format(time.time() - t))
551
  boundary_polygon = None
552
  if add_boundary.lower() == "yes":
553
+ boundary_polygon = add_rectangular_boundary(doc, final_polygons_inch, boundary_length, boundary_width, offset_unit)
554
  if boundary_polygon is not None:
555
  final_polygons_inch.append(boundary_polygon)
 
556
  min_x = float("inf")
557
  min_y = float("inf")
558
  max_x = -float("inf")
 
567
  max_x = b[2]
568
  if b[3] > max_y:
569
  max_y = b[3]
570
+ margin = 0.40
571
  text_x = (min_x + max_x) / 2
572
+ if add_boundary.lower() == "yes":
573
+ text_y = min_y + margin
574
+ else:
575
+ text_y = min_y - margin
576
  msp = doc.modelspace()
577
  if annotation_text.strip():
578
  text_entity = msp.add_text(
579
  annotation_text.strip(),
580
  dxfattribs={
581
+ "height": 0.50,
582
+ "layer": "ANNOTATION",
583
+ "style": "Bold"
584
  }
585
  )
586
  text_entity.dxf.insert = (text_x, text_y)
587
  dxf_filepath = os.path.join("./outputs", "out.dxf")
588
  doc.saveas(dxf_filepath)
 
589
  draw_polygons_inch(final_polygons_inch, output_img, scaling_factor, processed_size[0], color=(0,0,255), thickness=2)
 
590
  new_outlines = np.ones_like(output_img) * 255
591
  draw_polygons_inch(final_polygons_inch, new_outlines, scaling_factor, processed_size[0], color=(0,0,255), thickness=2)
592
  if annotation_text.strip():
593
  text_px = int(text_x / scaling_factor)
594
  text_py = int(processed_size[0] - (text_y / scaling_factor))
595
+ org = (int(text_px) - int(len(annotation_text.strip()) / 2), int(text_py))
596
+ cv2.putText(output_img, annotation_text.strip(), org, cv2.FONT_HERSHEY_SIMPLEX, 1.25, (0,0,255), 3, cv2.LINE_AA)
597
+ cv2.putText(new_outlines, annotation_text.strip(), org, cv2.FONT_HERSHEY_SIMPLEX, 1.25, (0,0,255), 3, cv2.LINE_AA)
598
  outlines_color = cv2.cvtColor(new_outlines, cv2.COLOR_BGR2RGB)
599
  print("Total prediction time: {:.2f} seconds".format(time.time() - overall_start))
600
  return (
 
610
  # ---------------------
611
  if __name__ == "__main__":
612
  os.makedirs("./outputs", exist_ok=True)
613
+ def gradio_predict(img, offset, offset_unit, finger_clearance, add_boundary, boundary_length, boundary_width, annotation_text):
614
+ try:
615
+ return predict(img, offset, offset_unit, finger_clearance, add_boundary, boundary_length, boundary_width, annotation_text)
616
+ except Exception as e:
617
+ return None, None, None, None, f"Error: {str(e)}"
618
  iface = gr.Interface(
619
  fn=gradio_predict,
620
  inputs=[
621
  gr.Image(label="Input Image"),
622
+ gr.Number(label="Offset value for Mask", value=0.075),
623
+ gr.Dropdown(label="Offset Unit", choices=["mm", "inches"], value="inches"),
624
  gr.Dropdown(label="Add Finger Clearance?", choices=["Yes", "No"], value="No"),
625
  gr.Dropdown(label="Add Rectangular Boundary?", choices=["Yes", "No"], value="No"),
626
  gr.Number(label="Boundary Length", value=300.0, precision=2),
627
  gr.Number(label="Boundary Width", value=200.0, precision=2),
 
628
  gr.Textbox(label="Annotation (max 20 chars)", max_length=20, placeholder="Type up to 20 characters")
629
  ],
630
  outputs=[
 
635
  gr.Textbox(label="Scaling Factor (inches/pixel)")
636
  ],
637
  examples=[
638
+ ["./Test20.jpg", 0.075, "inches", "No", "No", 300.0, 200.0, "MyTool"],
639
+ ["./Test21.jpg", 0.075, "inches", "Yes", "Yes", 300.0, 200.0, "Tool2"]
640
  ]
641
  )
642
  iface.launch(share=True)