Nighty3912 commited on
Commit
e10b0ce
·
verified ·
1 Parent(s): 5df4cbb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -45
app.py CHANGED
@@ -270,23 +270,95 @@ def extract_outlines(binary_image: np.ndarray) -> (np.ndarray, list):
270
  # ---------------------
271
  # Functions for Finger Cut Clearance
272
  # ---------------------
273
- def union_tool_and_circle(tool_polygon: Polygon, center_inch, circle_diameter=1.0):
 
 
 
 
 
 
 
 
 
 
 
274
  radius = circle_diameter / 2.0
275
- circle_poly = Point(center_inch).buffer(radius, resolution=64)
276
- union_poly = tool_polygon.union(circle_poly)
277
- return union_poly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
  def build_tool_polygon(points_inch):
280
  return Polygon(points_inch)
281
 
282
- def polygon_to_exterior_coords(poly: Polygon): # works fine
283
- if poly.geom_type == "MultiPolygon":
284
- biggest = max(poly.geoms, key=lambda g: g.area)
285
- poly = biggest
286
- if not poly.exterior:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  return []
288
- return list(poly.exterior.coords)
289
-
290
 
291
 
292
 
@@ -413,42 +485,215 @@ from shapely.geometry import Point
413
  # return None, None
414
 
415
 
416
- def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=100):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  import random
418
  needed_center_distance = circle_diameter + min_gap
419
  radius = circle_diameter / 2.0
420
- attempts = 0
421
- indices = list(range(len(points_inch)))
422
- random.shuffle(indices) # Shuffle indices for randomness
423
-
424
- for i in indices:
425
- if attempts >= max_attempts:
426
- break
427
- cx, cy = points_inch[i]
428
- # Try small adjustments around the chosen candidate
429
- for dx in np.linspace(-0.1, 0.1, 10):
430
- for dy in np.linspace(-0.1, 0.1, 10):
431
- candidate_center = (cx + dx, cy + dy)
432
- # Check distance from already placed centers
433
- if any(np.hypot(candidate_center[0] - ex, candidate_center[1] - ey) < needed_center_distance for ex, ey in existing_centers):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  continue
 
 
 
 
 
 
 
 
 
 
435
 
436
- union_poly= union_tool_and_circle(tool_polygon,candidate_center)
437
  overlap = False
438
- # Check against other tool polygons for overlap or proximity issues
439
  for poly in all_polygons:
440
  if poly == tool_polygon:
441
- continue
442
- if union_poly.intersects(poly) or union_poly.buffer(min_gap).intersects(poly):
443
  overlap = True
444
  break
445
- if overlap:
446
- continue
447
- # If candidate passes, accept it
448
- existing_centers.append(candidate_center)
449
- return union_poly, candidate_center
450
- attempts += 1
451
- print("Warning: Could not place a finger cut circle meeting all spacing requirements.")
 
452
  return None, None
453
  # ---------------------
454
  # DXF Spline and Boundary Functions
@@ -484,6 +729,59 @@ def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=
484
  print(f"Skipping contour: {e}")
485
  return doc, final_polygons_inch
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
 
489
  def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width, offset_unit, annotation_text="", image_height_in=None, image_width_in=None):
@@ -658,8 +956,8 @@ def predict(
658
  print(f"Error calculating scaling factor: {e}")
659
 
660
  if scaling_factor is None or scaling_factor == 0:
661
- scaling_factor = 0.7
662
- print("Using default scaling factor of 0.7 due to calculation error")
663
  gc.collect()
664
  print("Scaling factor determined: {}".format(scaling_factor))
665
 
@@ -891,10 +1189,10 @@ if __name__ == "__main__":
891
  gr.Image(label="Input Image"),
892
  gr.Number(label="Offset value for Mask", value=0.075),
893
  gr.Dropdown(label="Offset Unit", choices=["mm", "inches"], value="inches"),
894
- gr.Dropdown(label="Add Finger Clearance?", choices=["Yes", "No"], value="No"),
895
- gr.Dropdown(label="Add Rectangular Boundary?", choices=["Yes", "No"], value="No"),
896
- gr.Number(label="Boundary Length", value=300.0, precision=2),
897
- gr.Number(label="Boundary Width", value=200.0, precision=2),
898
  gr.Textbox(label="Annotation (max 20 chars)", max_length=20, placeholder="Type up to 20 characters")
899
  ],
900
  outputs=[
@@ -905,8 +1203,8 @@ if __name__ == "__main__":
905
  gr.Textbox(label="Scaling Factor (inches/pixel)")
906
  ],
907
  examples=[
908
- ["./Test20.jpg", 0.075, "inches", "No", "No", 300.0, 200.0, "MyTool"],
909
- ["./Test21.jpg", 0.075, "inches", "Yes", "Yes", 300.0, 200.0, "Tool2"]
910
  ]
911
  )
912
  iface.launch(share=True)
 
270
  # ---------------------
271
  # Functions for Finger Cut Clearance
272
  # ---------------------
273
+ # def union_tool_and_circle(tool_polygon: Polygon, center_inch, circle_diameter=1.0): #original
274
+ # radius = circle_diameter / 2.0
275
+ # circle_poly = Point(center_inch).buffer(radius, resolution=64)
276
+ # union_poly = tool_polygon.union(circle_poly)
277
+ # return union_poly
278
+
279
+ def union_tool_and_circle(tool_polygon, center_inch, circle_diameter=1.0):
280
+ """Union a tool polygon with a circle with extensive validation."""
281
+ if tool_polygon is None or not isinstance(tool_polygon, Polygon):
282
+ print("Invalid tool polygon provided")
283
+ return None
284
+
285
  radius = circle_diameter / 2.0
286
+ try:
287
+ # Create the circle
288
+ circle_poly = Point(center_inch).buffer(radius, resolution=64)
289
+
290
+ # Make sure both geometries are valid
291
+ if not tool_polygon.is_valid:
292
+ tool_polygon = tool_polygon.buffer(0)
293
+ if not circle_poly.is_valid:
294
+ print("Invalid circle geometry")
295
+ return tool_polygon
296
+
297
+ # Perform union
298
+ result = tool_polygon.union(circle_poly)
299
+
300
+ # Validate result
301
+ if result.is_empty:
302
+ print(f"Union resulted in empty geometry at {center_inch}")
303
+ return tool_polygon
304
+
305
+ # Handle multi-polygon results
306
+ if result.geom_type == "MultiPolygon":
307
+ # Take the largest piece to avoid fragmentation
308
+ result = max(result.geoms, key=lambda g: g.area)
309
+
310
+ # Final validation
311
+ if not result.is_valid:
312
+ print("Union produced invalid geometry, returning original polygon")
313
+ return tool_polygon
314
+
315
+ # Check exterior points
316
+ if not result.exterior or len(list(result.exterior.coords)) < 4:
317
+ print(f"Union resulted in degenerate polygon with insufficient points")
318
+ return tool_polygon
319
+
320
+ return result
321
+ except Exception as e:
322
+ print(f"Exception during union operation: {e}")
323
+ return tool_polygon
324
 
325
  def build_tool_polygon(points_inch):
326
  return Polygon(points_inch)
327
 
328
+ # def polygon_to_exterior_coords(poly: Polygon): # works fine original
329
+ # if poly.geom_type == "MultiPolygon":
330
+ # biggest = max(poly.geoms, key=lambda g: g.area)
331
+ # poly = biggest
332
+ # if not poly.exterior:
333
+ # return []
334
+ # return list(poly.exterior.coords)
335
+
336
+ def polygon_to_exterior_coords(poly):
337
+ """Extract exterior coordinates with robust error handling."""
338
+ if poly is None:
339
+ print("Warning: Null polygon provided")
340
+ return []
341
+
342
+ try:
343
+ if poly.geom_type == "MultiPolygon":
344
+ if len(poly.geoms) == 0:
345
+ print("Warning: Empty MultiPolygon")
346
+ return []
347
+ biggest = max(poly.geoms, key=lambda g: g.area)
348
+ poly = biggest
349
+
350
+ if not poly.exterior:
351
+ print("Warning: Polygon has no exterior")
352
+ return []
353
+
354
+ coords = list(poly.exterior.coords)
355
+ if len(coords) < 4:
356
+ print(f"Warning: Polygon has insufficient coordinates: {len(coords)}")
357
+
358
+ return coords
359
+ except Exception as e:
360
+ print(f"Error extracting polygon coordinates: {e}")
361
  return []
 
 
362
 
363
 
364
 
 
485
  # return None, None
486
 
487
 
488
+ # def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=100): #working best
489
+ # import random
490
+ # needed_center_distance = circle_diameter + min_gap
491
+ # radius = circle_diameter / 2.0
492
+ # attempts = 0
493
+ # indices = list(range(len(points_inch)))
494
+ # random.shuffle(indices) # Shuffle indices for randomness
495
+
496
+ # for i in indices:
497
+ # if attempts >= max_attempts:
498
+ # break
499
+ # cx, cy = points_inch[i]
500
+ # # Try small adjustments around the chosen candidate
501
+ # for dx in np.linspace(-0.1, 0.1, 10):
502
+ # for dy in np.linspace(-0.1, 0.1, 10):
503
+ # candidate_center = (cx + dx, cy + dy)
504
+ # # Check distance from already placed centers
505
+ # if any(np.hypot(candidate_center[0] - ex, candidate_center[1] - ey) < needed_center_distance for ex, ey in existing_centers):
506
+ # continue
507
+
508
+ # union_poly= union_tool_and_circle(tool_polygon,candidate_center)
509
+ # overlap = False
510
+ # # Check against other tool polygons for overlap or proximity issues
511
+ # for poly in all_polygons:
512
+ # if poly == tool_polygon:
513
+ # continue
514
+ # if union_poly.intersects(poly) or union_poly.buffer(min_gap).intersects(poly):
515
+ # overlap = True
516
+ # break
517
+ # if overlap:
518
+ # continue
519
+ # # If candidate passes, accept it
520
+ # existing_centers.append(candidate_center)
521
+ # return union_poly, candidate_center
522
+ # attempts += 1
523
+ # print("Warning: Could not place a finger cut circle meeting all spacing requirements.")
524
+ # return None, None
525
+
526
+
527
+ # def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=20):
528
+ # """Place a finger cut with strategic positioning based on tool shape."""
529
+ # if tool_polygon is None or len(points_inch) < 4:
530
+ # return None, None
531
+
532
+ # import random
533
+ # needed_center_distance = circle_diameter + min_gap
534
+ # radius = circle_diameter / 2.0
535
+
536
+ # # Calculate the tool's bounding box and find its center
537
+ # minx, miny, maxx, maxy = tool_polygon.bounds
538
+ # bbox_center = ((minx + maxx) / 2, (miny + maxy) / 2)
539
+
540
+ # # Strategy 1: Try handle areas first (typically thinner parts)
541
+ # # Find narrow regions by looking at distance to boundary
542
+ # narrow_candidates = []
543
+ # for i, pt in enumerate(points_inch):
544
+ # # Calculate distance to the opposite side of the polygon
545
+ # point = Point(pt)
546
+ # if tool_polygon.boundary.distance(point) < radius * 1.5: # Points near boundary
547
+ # narrow_candidates.append(i)
548
+
549
+ # # Strategy 2: Try areas far from existing cuts
550
+ # far_candidates = []
551
+ # if existing_centers:
552
+ # for i, pt in enumerate(points_inch):
553
+ # min_dist = min(np.hypot(pt[0] - cx, pt[1] - cy) for cx, cy in existing_centers)
554
+ # if min_dist > needed_center_distance * 1.5: # Points far from existing cuts
555
+ # far_candidates.append(i)
556
+
557
+ # # Strategy 3: Try concave regions (often good for finger grips)
558
+ # # This is more complex but could identify good grip points
559
+
560
+ # # Combine strategies, prioritizing certain candidates
561
+ # candidate_indices = (narrow_candidates + far_candidates +
562
+ # list(set(range(len(points_inch))) -
563
+ # set(narrow_candidates) -
564
+ # set(far_candidates)))
565
+ # random.shuffle(candidate_indices) # Add randomness
566
+
567
+ # # Try candidates
568
+ # for idx in candidate_indices:
569
+ # base_pt = points_inch[idx]
570
+
571
+ # # Try at different distances from the point
572
+ # for dist_factor in [0.0, 0.1, 0.2, 0.3, 0.4]:
573
+ # # Try different angles
574
+ # for angle in np.linspace(0, 2*np.pi, 12):
575
+ # dx = dist_factor * radius * np.cos(angle)
576
+ # dy = dist_factor * radius * np.sin(angle)
577
+ # candidate = (base_pt[0] + dx, base_pt[1] + dy)
578
+
579
+ # # Skip if point isn't inside the polygon
580
+ # if not tool_polygon.contains(Point(candidate)):
581
+ # continue
582
+
583
+ # # Skip if too close to existing centers
584
+ # if any(np.hypot(candidate[0] - cx, candidate[1] - cy) < needed_center_distance
585
+ # for cx, cy in existing_centers):
586
+ # continue
587
+
588
+ # # Try creating the union
589
+ # new_polygon = union_tool_and_circle(tool_polygon, candidate, circle_diameter)
590
+ # if new_polygon is None or new_polygon == tool_polygon:
591
+ # continue # Union failed
592
+
593
+ # # Check for overlaps with other tools
594
+ # overlap = False
595
+ # for poly in all_polygons:
596
+
597
+ # if poly == tool_polygon:
598
+ # continue
599
+ # # if new_polygon.intersects(poly):
600
+ # # overlap = True
601
+ # # break
602
+ # if new_polygon.buffer(0.25).intersects(poly):
603
+ # overlap = True
604
+ # break
605
+
606
+ # if not overlap:
607
+ # # Success! This is a good spot for a finger cut
608
+ # existing_centers.append(candidate)
609
+ # return new_polygon, candidate
610
+
611
+ # # If we get here, we couldn't find a good spot
612
+ # print("Could not find suitable location for finger cut")
613
+ # return None, None
614
+
615
+ def place_finger_cut_adjusted(tool_polygon, points_inch, existing_centers, all_polygons, circle_diameter=1.0, min_gap=0.25, max_attempts=50):
616
+ """Place a finger cut with strategic positioning based on tool shape."""
617
+ if tool_polygon is None or len(points_inch) < 4:
618
+ return None, None
619
+
620
  import random
621
  needed_center_distance = circle_diameter + min_gap
622
  radius = circle_diameter / 2.0
623
+
624
+ # Calculate the tool's bounding box and find its center
625
+ minx, miny, maxx, maxy = tool_polygon.bounds
626
+ bbox_center = ((minx + maxx) / 2, (miny + maxy) / 2)
627
+
628
+ # Strategy 1: Try handle areas first (typically thinner parts)
629
+ # Find narrow regions by looking at distance to boundary
630
+ narrow_candidates = []
631
+ for i, pt in enumerate(points_inch):
632
+ # Calculate distance to the opposite side of the polygon
633
+ point = Point(pt)
634
+ if tool_polygon.boundary.distance(point) < radius * 1.5: # Points near boundary
635
+ narrow_candidates.append(i)
636
+
637
+ # Strategy 2: Try areas far from existing cuts
638
+ far_candidates = []
639
+ if existing_centers:
640
+ for i, pt in enumerate(points_inch):
641
+ min_dist = min(np.hypot(pt[0] - cx, pt[1] - cy) for cx, cy in existing_centers)
642
+ if min_dist > needed_center_distance * 1.5: # Points far from existing cuts
643
+ far_candidates.append(i)
644
+
645
+ # Strategy 3: Try concave regions (often good for finger grips)
646
+ # This is more complex but could identify good grip points
647
+
648
+ # Combine strategies, prioritizing certain candidates
649
+ candidate_indices = (narrow_candidates + far_candidates +
650
+ list(set(range(len(points_inch))) -
651
+ set(narrow_candidates) -
652
+ set(far_candidates)))
653
+ random.shuffle(candidate_indices) # Add randomness
654
+
655
+ # Try candidates
656
+ for idx in candidate_indices:
657
+ base_pt = points_inch[idx]
658
+
659
+ # Try at different distances from the point
660
+ for dist_factor in [0.0, 0.1, 0.2, 0.3, 0.4]:
661
+ # Try different angles
662
+ for angle in np.linspace(0, 2*np.pi, 12):
663
+ dx = dist_factor * radius * np.cos(angle)
664
+ dy = dist_factor * radius * np.sin(angle)
665
+ candidate = (base_pt[0] + dx, base_pt[1] + dy)
666
+
667
+ # Skip if point isn't inside the polygon
668
+ if not tool_polygon.contains(Point(candidate)):
669
  continue
670
+
671
+ # Skip if too close to existing centers
672
+ if any(np.hypot(candidate[0] - cx, candidate[1] - cy) < needed_center_distance
673
+ for cx, cy in existing_centers):
674
+ continue
675
+
676
+ # Try creating the union
677
+ new_polygon = union_tool_and_circle(tool_polygon, candidate, circle_diameter)
678
+ if new_polygon is None or new_polygon == tool_polygon:
679
+ continue # Union failed
680
 
681
+ # Check for overlaps with other tools
682
  overlap = False
 
683
  for poly in all_polygons:
684
  if poly == tool_polygon:
685
+ continue
686
+ if new_polygon.buffer(0.1).intersects(poly):
687
  overlap = True
688
  break
689
+
690
+ if not overlap:
691
+ # Success! This is a good spot for a finger cut
692
+ existing_centers.append(candidate)
693
+ return new_polygon, candidate
694
+
695
+ # If we get here, we couldn't find a good spot
696
+ print("Could not find suitable location for finger cut")
697
  return None, None
698
  # ---------------------
699
  # DXF Spline and Boundary Functions
 
729
  print(f"Skipping contour: {e}")
730
  return doc, final_polygons_inch
731
 
732
+ # def save_dxf_spline(inflated_contours, scaling_factor, height, finger_clearance=True):
733
+ # degree = 3
734
+ # closed = True
735
+ # doc = ezdxf.new(units=0)
736
+ # doc.units = ezdxf.units.IN
737
+ # doc.header["$INSUNITS"] = ezdxf.units.IN
738
+ # msp = doc.modelspace()
739
+ # finger_cut_centers = []
740
+ # final_polygons_inch = []
741
+
742
+ # for contour in inflated_contours:
743
+ # try:
744
+ # resampled_contour = resample_contour(contour)
745
+ # points_inch = [(x * scaling_factor, (height - y) * scaling_factor) for x, y in resampled_contour]
746
+
747
+ # # Ensure minimum number of points
748
+ # if len(points_inch) < 4: # Need at least 4 for a valid polygon and spline
749
+ # print(f"Skipping contour with only {len(points_inch)} points")
750
+ # continue
751
+
752
+ # # Ensure first and last points match for closed polygon
753
+ # if np.linalg.norm(np.array(points_inch[0]) - np.array(points_inch[-1])) > 1e-2:
754
+ # points_inch.append(points_inch[0])
755
+
756
+ # # Create tool polygon with validation
757
+ # tool_polygon = build_tool_polygon(points_inch)
758
+ # if not tool_polygon.is_valid:
759
+ # tool_polygon = tool_polygon.buffer(0) # Fix invalid geometries
760
+
761
+ # # Add finger clearance if requested
762
+ # if finger_clearance:
763
+ # for attempt in range(3): # Try multiple finger cuts per tool
764
+ # union_poly, center = place_finger_cut_adjusted(
765
+ # tool_polygon, points_inch, finger_cut_centers, final_polygons_inch)
766
+ # if union_poly is not None:
767
+ # tool_polygon = union_poly
768
+ # else:
769
+ # break # Stop if we can't place more cuts
770
+
771
+ # # Get exterior coordinates with validation
772
+ # exterior_coords = polygon_to_exterior_coords(tool_polygon)
773
+ # if len(exterior_coords) < 4:
774
+ # print(f"Warning: Insufficient exterior points ({len(exterior_coords)})")
775
+ # continue
776
+
777
+ # # Add to DXF
778
+ # msp.add_spline(exterior_coords, degree=degree, dxfattribs={"layer": "TOOLS"})
779
+ # final_polygons_inch.append(tool_polygon)
780
+
781
+ # except Exception as e:
782
+ # print(f"Error processing contour: {e}")
783
+
784
+ # return doc, final_polygons_inch
785
 
786
 
787
  def add_rectangular_boundary(doc, polygons_inch, boundary_length, boundary_width, offset_unit, annotation_text="", image_height_in=None, image_width_in=None):
 
956
  print(f"Error calculating scaling factor: {e}")
957
 
958
  if scaling_factor is None or scaling_factor == 0:
959
+ scaling_factor = 0.05
960
+ print("Using default scaling factor of 0.05 due to calculation error")
961
  gc.collect()
962
  print("Scaling factor determined: {}".format(scaling_factor))
963
 
 
1189
  gr.Image(label="Input Image"),
1190
  gr.Number(label="Offset value for Mask", value=0.075),
1191
  gr.Dropdown(label="Offset Unit", choices=["mm", "inches"], value="inches"),
1192
+ gr.Dropdown(label="Add Finger Clearance?", choices=["Yes", "No"], value="Yes"),
1193
+ gr.Dropdown(label="Add Rectangular Boundary?", choices=["Yes", "No"], value="Yes"),
1194
+ gr.Number(label="Boundary Length", value=30.0, precision=2),
1195
+ gr.Number(label="Boundary Width", value=30.0, precision=2),
1196
  gr.Textbox(label="Annotation (max 20 chars)", max_length=20, placeholder="Type up to 20 characters")
1197
  ],
1198
  outputs=[
 
1203
  gr.Textbox(label="Scaling Factor (inches/pixel)")
1204
  ],
1205
  examples=[
1206
+ ["./Test20.jpg", 0.075, "inches", "Yes", "No", 30.0, 30.0, "MyTool"],
1207
+ ["./Test21.jpg", 0.075, "inches", "Yes", "Yes", 30.0, 30.0, "Tool2"]
1208
  ]
1209
  )
1210
  iface.launch(share=True)