iammraat commited on
Commit
8d8f4ec
·
verified ·
1 Parent(s): e665655

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -31
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
- Uses Horizontal Projection Profile.
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 (Sum of white pixels per row)
124
  h_proj = np.sum(thresh, axis=1)
125
 
126
- # 3. Normalize projection
127
  max_val = np.max(h_proj)
128
- if max_val == 0: return [crop_img] # Empty image
129
  h_proj = h_proj / max_val
130
 
131
- # 4. Find Peaks (Lines of text) and Valleys (Space between lines)
132
- # We look for peaks with a certain prominence
133
- peaks, _ = find_peaks(h_proj, height=0.2, distance=15)
134
 
135
  if len(peaks) < 2:
136
- return [crop_img] # Likely just one line
137
 
138
- # If we have 2+ clear peaks, we check the "valley" between them
139
- # Simple logic: Find the deepest point between the two major peaks
140
- if len(peaks) >= 2:
141
- # Get the first two major peaks
142
- p1, p2 = peaks[0], peaks[1]
143
-
144
- # Look at the region between peaks
145
- valley_region = h_proj[p1:p2]
146
- if len(valley_region) == 0: return [crop_img]
147
-
148
- min_val = np.min(valley_region)
149
- min_idx = np.argmin(valley_region) + p1
150
-
151
- # STRICT CHECK: Only split if the valley is truly empty (or noise)
152
- # If the valley still has > 30% ink density of the peak, it might just be a messy 'y' or 'g'
153
- if min_val < 0.3:
154
- logs.append(f" -> ✂️ Refinement: Split double line at Y={min_idx}")
155
- top_crop = crop_img[0:min_idx, :]
156
- bot_crop = crop_img[min_idx:, :]
157
- return [top_crop, bot_crop]
158
 
159
- return [crop_img]
 
 
 
 
 
 
 
 
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
  # ==========================================