Spaces:
Sleeping
Sleeping
File size: 6,924 Bytes
8eb0b3e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
import cv2
import numpy as np
EPS = 1e-6
def get_circle_overlap(contour):
"""Checks if a contour is roughly circular based on the ratio of its area to its minimum enclosing circle."""
(_, _), radius = cv2.minEnclosingCircle(contour)
enclosing_area = np.pi * (radius ** 2) + EPS
contour_area = cv2.contourArea(contour)
return contour_area / enclosing_area
def get_rectangle_overlap(contour):
"""Checks if a contour is roughly rectangular based on the ratio of its area to its minimum area bounding box."""
rect = cv2.minAreaRect(contour)
box_area = rect[1][0] * rect[1][1] + EPS
contour_area = cv2.contourArea(contour)
return contour_area / box_area
def detect_shapes(preprocessed_img, circle_threshold, rect_threshold): ### TODO: critical bug here
contours_list, _ = cv2.findContours(preprocessed_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
circles = []
rectangles = []
for contour in contours_list:
circle_overlap_percentage = get_circle_overlap(contour)
rectangle_overlap_percentage = get_rectangle_overlap(contour)
if circle_overlap_percentage > rectangle_overlap_percentage and circle_overlap_percentage > circle_threshold:
circles.append(contour)
elif rectangle_overlap_percentage > circle_overlap_percentage and rectangle_overlap_percentage > rect_threshold:
# print(f"Rectangle detected: rectangle_overlap_percentage: {rectangle_overlap_percentage} circle_overlap_percentage: {circle_overlap_percentage}")
rectangles.append(contour)
return circles, rectangles
def get_nodes_mask(img_empty_nodes_filled, config):
"""
Isolates node structures using an iterative erosion/dilation heuristic based on contour count stability.
"""
# Default values, will be overridden by config if available
erosion_kernel_size = tuple(config.get('shape_detection', {}).get('erosion_kernel_size', [3, 3]))
min_stable_length = config.get('shape_detection', {}).get('min_stable_length', 3)
max_erosion_iterations = config.get('shape_detection', {}).get('max_erosion_iterations', 30)
erosion_kernel = np.ones(erosion_kernel_size, np.uint8)
contour_counts_history = []
optimal_erosion_iterations = 0 # Default if loop doesn't run or no erosions found necessary
optimal_condition_found = False # Flag to indicate if stability or zero contours was met
# Debug information collection
debug_info = {
'stability_detected': False,
'stable_count': None,
'zero_contours_at': None,
'max_iterations_reached': False,
'erosions_applied': 0,
'dilations_applied': 0,
'reason': 'No processing needed'
}
# This image is progressively eroded to find the optimal number of iterations
image_for_iterative_erosion = img_empty_nodes_filled.copy()
# Loop to determine the optimal number of erosion iterations
# If max_erosion_iterations is 0, this loop won't execute, and optimal_erosion_iterations will remain 0.
for current_iteration in range(1, max_erosion_iterations + 1):
# Perform one erosion step
eroded_this_step = cv2.erode(image_for_iterative_erosion, erosion_kernel, iterations=1)
contours, _ = cv2.findContours(eroded_this_step, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
num_contours_at_step = len(contours)
contour_counts_history.append(num_contours_at_step)
image_for_iterative_erosion = eroded_this_step # Update for the next iteration
# Check for stability in contour count
if len(contour_counts_history) >= min_stable_length:
last_n_counts = contour_counts_history[-min_stable_length:]
if all(c == last_n_counts[0] for c in last_n_counts):
# Optimal iterations = iteration count at the start of the stable sequence
optimal_erosion_iterations = current_iteration - min_stable_length + 1
debug_info['stability_detected'] = True
debug_info['stable_count'] = last_n_counts[0]
debug_info['reason'] = f'Stability detected: count {last_n_counts[0]} stable for {min_stable_length} iterations'
optimal_condition_found = True
break
# Check if all contours have disappeared
if num_contours_at_step == 0:
if not optimal_condition_found: # Only set if stability wasn't the primary reason
optimal_erosion_iterations = current_iteration # All contours gone after this many erosions
debug_info['zero_contours_at'] = current_iteration
debug_info['reason'] = f'All contours disappeared after {current_iteration} erosions'
optimal_condition_found = True # This is a definitive condition to stop
break
# Loop ends
# If the loop completed fully (max_erosion_iterations reached) without finding stability or zero contours
if not optimal_condition_found and max_erosion_iterations > 0:
optimal_erosion_iterations = max_erosion_iterations
debug_info['max_iterations_reached'] = True
debug_info['reason'] = f'Max erosions ({max_erosion_iterations}) reached without stability/zero-contour condition'
# Obtain the node mask by applying the optimal number of erosions to the original filled image
if optimal_erosion_iterations > 0:
node_mask_eroded = cv2.erode(img_empty_nodes_filled, erosion_kernel, iterations=optimal_erosion_iterations)
debug_info['erosions_applied'] = optimal_erosion_iterations
else:
# If no erosions are optimal, return a copy of the input to maintain consistency (always a new image object)
node_mask_eroded = img_empty_nodes_filled.copy()
debug_info['reason'] = 'No erosions needed (0 optimal erosions)'
# Dilate the eroded node mask to recover node sizes
if optimal_erosion_iterations > 0:
# Dilate by the same number of steps and with the same kernel
dilated_node_mask = cv2.dilate(node_mask_eroded, erosion_kernel, iterations=optimal_erosion_iterations)
debug_info['dilations_applied'] = optimal_erosion_iterations
else:
# If no erosions were done, no dilations are needed either.
# node_mask_eroded is already a copy of the original (or the optimally eroded one if erosions > 0).
dilated_node_mask = node_mask_eroded
# Print organized debug information
print("=== Node Mask Generation Debug Info ===")
print(f"Reason: {debug_info['reason']}")
print(f"Optimal erosions determined: {optimal_erosion_iterations}")
print(f"Erosions applied: {debug_info['erosions_applied']}")
print(f"Dilations applied: {debug_info['dilations_applied']}")
print(f"Contour count history: {contour_counts_history}")
print("=" * 40)
return dilated_node_mask |