Update app.py
Browse files
app.py
CHANGED
|
@@ -112,52 +112,62 @@ def filter_nested_boxes(boxes, containment_thresh=0.9):
|
|
| 112 |
def split_double_lines(crop_img, logs):
|
| 113 |
"""
|
| 114 |
Analyzes a crop to see if it accidentally contains TWO lines of text.
|
| 115 |
-
|
| 116 |
-
Returns: List of crops (either [original] or [top_half, bottom_half])
|
| 117 |
"""
|
| 118 |
# 1. Binarize
|
| 119 |
gray = cv2.cvtColor(crop_img, cv2.COLOR_RGB2GRAY)
|
| 120 |
-
# Otsu's thresholding for dynamic contrast
|
| 121 |
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
| 122 |
|
| 123 |
-
# 2. Horizontal Projection
|
| 124 |
h_proj = np.sum(thresh, axis=1)
|
| 125 |
|
| 126 |
-
#
|
| 127 |
max_val = np.max(h_proj)
|
| 128 |
-
if max_val == 0: return [crop_img]
|
| 129 |
h_proj = h_proj / max_val
|
| 130 |
|
| 131 |
-
#
|
| 132 |
-
#
|
| 133 |
-
peaks, _ = find_peaks(h_proj, height=0.2, distance=
|
| 134 |
|
| 135 |
if len(peaks) < 2:
|
| 136 |
-
return [crop_img]
|
| 137 |
|
| 138 |
-
#
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
bot_crop = crop_img[min_idx:, :]
|
| 157 |
-
return [top_crop, bot_crop]
|
| 158 |
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
# ==========================================
|
| 162 |
# ⛓️ PIPELINE STEP: MERGING & ORDERING
|
| 163 |
# ==========================================
|
|
|
|
| 112 |
def split_double_lines(crop_img, logs):
|
| 113 |
"""
|
| 114 |
Analyzes a crop to see if it accidentally contains TWO lines of text.
|
| 115 |
+
Includes 'Descender Protection' to prevent cutting off tails (y, g, p).
|
|
|
|
| 116 |
"""
|
| 117 |
# 1. Binarize
|
| 118 |
gray = cv2.cvtColor(crop_img, cv2.COLOR_RGB2GRAY)
|
|
|
|
| 119 |
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
|
| 120 |
|
| 121 |
+
# 2. Horizontal Projection
|
| 122 |
h_proj = np.sum(thresh, axis=1)
|
| 123 |
|
| 124 |
+
# Normalize
|
| 125 |
max_val = np.max(h_proj)
|
| 126 |
+
if max_val == 0: return [crop_img]
|
| 127 |
h_proj = h_proj / max_val
|
| 128 |
|
| 129 |
+
# 3. Find Peaks
|
| 130 |
+
# Increased distance to 25 to prevent finding peaks within the same letter height
|
| 131 |
+
peaks, _ = find_peaks(h_proj, height=0.2, distance=25)
|
| 132 |
|
| 133 |
if len(peaks) < 2:
|
| 134 |
+
return [crop_img]
|
| 135 |
|
| 136 |
+
# 4. Analyze the Valley
|
| 137 |
+
p1, p2 = peaks[0], peaks[1]
|
| 138 |
+
valley_region = h_proj[p1:p2]
|
| 139 |
+
|
| 140 |
+
if len(valley_region) == 0: return [crop_img]
|
| 141 |
+
|
| 142 |
+
min_val = np.min(valley_region)
|
| 143 |
+
min_idx = np.argmin(valley_region) + p1
|
| 144 |
+
|
| 145 |
+
# ==========================
|
| 146 |
+
# 🛡️ SAFETY GUARDRAILS
|
| 147 |
+
# ==========================
|
| 148 |
+
|
| 149 |
+
# CHECK 1: Valley Depth
|
| 150 |
+
# Handwriting lines often touch. If the valley isn't DEEP (very low ink), don't split.
|
| 151 |
+
# We lowered the threshold to 0.15 (15% ink density)
|
| 152 |
+
if min_val > 0.15:
|
| 153 |
+
return [crop_img]
|
|
|
|
|
|
|
| 154 |
|
| 155 |
+
# CHECK 2: Edge Protection (The "Descender" Check)
|
| 156 |
+
# If the split point is too close to the bottom (e.g., > 75% down the image),
|
| 157 |
+
# it's almost certainly chopping off tails (y, g, p, q), not separating a new line.
|
| 158 |
+
total_height = crop_img.shape[0]
|
| 159 |
+
split_ratio = min_idx / total_height
|
| 160 |
+
|
| 161 |
+
if split_ratio < 0.20 or split_ratio > 0.75:
|
| 162 |
+
logs.append(f" -> ⚠️ Refinement: Prevented split at {int(split_ratio*100)}% (Likely descenders)")
|
| 163 |
+
return [crop_img]
|
| 164 |
|
| 165 |
+
# If we pass checks, perform the split
|
| 166 |
+
logs.append(f" -> ✂️ Refinement: Split double line at Y={min_idx}")
|
| 167 |
+
top_crop = crop_img[0:min_idx, :]
|
| 168 |
+
bot_crop = crop_img[min_idx:, :]
|
| 169 |
+
|
| 170 |
+
return [top_crop, bot_crop]
|
| 171 |
# ==========================================
|
| 172 |
# ⛓️ PIPELINE STEP: MERGING & ORDERING
|
| 173 |
# ==========================================
|