Spaces:
Running on Zero
Running on Zero
johnlockejrr Cursor commited on
Commit ·
398eee2
1
Parent(s): 5cd4b66
Mirror hardened BLLA polygonizer from main repo
Browse filesCLAHE + Sauvola fallback + median seam + convex-hull recovery + int32 fix.
Co-authored-by: Cursor <cursoragent@cursor.com>
regnetx_infer/serialization/blla_polygonal_environment.py
CHANGED
|
@@ -23,7 +23,8 @@ from scipy.ndimage import affine_transform, binary_erosion, distance_transform_c
|
|
| 23 |
from shapely.ops import nearest_points, unary_union
|
| 24 |
from shapely.validation import explain_validity
|
| 25 |
from skimage import draw
|
| 26 |
-
from skimage.
|
|
|
|
| 27 |
from skimage.transform import AffineTransform, warp
|
| 28 |
|
| 29 |
logger = logging.getLogger(__name__)
|
|
@@ -286,7 +287,9 @@ def _extract_patch(
|
|
| 286 |
polygon = np.concatenate(([end_points[-1]], upper_seam, [end_points[0]], bottom_seam))
|
| 287 |
polygon = geom.Polygon(polygon)
|
| 288 |
if not polygon.is_valid:
|
| 289 |
-
|
|
|
|
|
|
|
| 290 |
inter = roi_polygon.intersection(polygon)
|
| 291 |
if inter.is_empty:
|
| 292 |
raise Exception("ROI ∩ polygon is empty")
|
|
@@ -366,8 +369,8 @@ def _calc_roi(line, bounds, baselines, suppl_obj, p_dir):
|
|
| 366 |
)
|
| 367 |
env_up.append(_coords_array(upper_limit)[0])
|
| 368 |
env_bottom.append(_coords_array(bottom_limit)[0])
|
| 369 |
-
env_up = np.array(env_up, dtype="
|
| 370 |
-
env_bottom = np.array(env_bottom, dtype="
|
| 371 |
return env_up, env_bottom
|
| 372 |
|
| 373 |
|
|
@@ -397,6 +400,7 @@ def calculate_polygonal_environment(
|
|
| 397 |
if suppl_obj is not None:
|
| 398 |
suppl_obj = [(np.array(bl) * scale_arr).astype("int").tolist() for bl in suppl_obj]
|
| 399 |
|
|
|
|
| 400 |
if im_feats is None:
|
| 401 |
assert im is not None
|
| 402 |
bounds = np.array(im.size, dtype=float) - 1
|
|
@@ -405,6 +409,26 @@ def calculate_polygonal_environment(
|
|
| 405 |
else:
|
| 406 |
bounds = np.array(im_feats.shape[::-1], dtype=float) - 1
|
| 407 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
polygons: list = []
|
| 409 |
if suppl_obj is None:
|
| 410 |
suppl_obj = []
|
|
@@ -445,6 +469,30 @@ def calculate_polygonal_environment(
|
|
| 445 |
)
|
| 446 |
)
|
| 447 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
if raise_on_error:
|
| 449 |
raise
|
| 450 |
logger.warning("Polygonizer failed on line %s: %s", idx, e)
|
|
|
|
| 23 |
from shapely.ops import nearest_points, unary_union
|
| 24 |
from shapely.validation import explain_validity
|
| 25 |
from skimage import draw
|
| 26 |
+
from skimage.exposure import equalize_adapthist
|
| 27 |
+
from skimage.filters import sobel, threshold_sauvola
|
| 28 |
from skimage.transform import AffineTransform, warp
|
| 29 |
|
| 30 |
logger = logging.getLogger(__name__)
|
|
|
|
| 287 |
polygon = np.concatenate(([end_points[-1]], upper_seam, [end_points[0]], bottom_seam))
|
| 288 |
polygon = geom.Polygon(polygon)
|
| 289 |
if not polygon.is_valid:
|
| 290 |
+
polygon = geom.MultiPoint(np.concatenate([upper_seam, bottom_seam])).convex_hull
|
| 291 |
+
if not polygon.is_valid or polygon.is_empty or polygon.geom_type != "Polygon":
|
| 292 |
+
raise Exception(f"Invalid bounding polygon computed: {explain_validity(polygon)}")
|
| 293 |
inter = roi_polygon.intersection(polygon)
|
| 294 |
if inter.is_empty:
|
| 295 |
raise Exception("ROI ∩ polygon is empty")
|
|
|
|
| 369 |
)
|
| 370 |
env_up.append(_coords_array(upper_limit)[0])
|
| 371 |
env_bottom.append(_coords_array(bottom_limit)[0])
|
| 372 |
+
env_up = np.array(env_up, dtype="int32")
|
| 373 |
+
env_bottom = np.array(env_bottom, dtype="int32")
|
| 374 |
return env_up, env_bottom
|
| 375 |
|
| 376 |
|
|
|
|
| 400 |
if suppl_obj is not None:
|
| 401 |
suppl_obj = [(np.array(bl) * scale_arr).astype("int").tolist() for bl in suppl_obj]
|
| 402 |
|
| 403 |
+
im_arr: np.ndarray | None = None
|
| 404 |
if im_feats is None:
|
| 405 |
assert im is not None
|
| 406 |
bounds = np.array(im.size, dtype=float) - 1
|
|
|
|
| 409 |
else:
|
| 410 |
bounds = np.array(im_feats.shape[::-1], dtype=float) - 1
|
| 411 |
|
| 412 |
+
# Fallback edge maps (computed lazily on first failure):
|
| 413 |
+
# 1) CLAHE-enhanced Sobel — helps faded ink with even background
|
| 414 |
+
# 2) Sauvola-binarized Sobel — helps extreme low-contrast / foxed pages
|
| 415 |
+
_fallback_cache: dict[str, np.ndarray] = {}
|
| 416 |
+
|
| 417 |
+
def _get_fallback_feats() -> list[np.ndarray]:
|
| 418 |
+
if im_arr is None:
|
| 419 |
+
return []
|
| 420 |
+
out: list[np.ndarray] = []
|
| 421 |
+
if "clahe" not in _fallback_cache:
|
| 422 |
+
clahe = (equalize_adapthist(im_arr, clip_limit=0.03) * 255).astype(np.uint8)
|
| 423 |
+
_fallback_cache["clahe"] = gaussian_filter(sobel(clahe), 0.5)
|
| 424 |
+
out.append(_fallback_cache["clahe"])
|
| 425 |
+
if "sauvola" not in _fallback_cache:
|
| 426 |
+
thresh = threshold_sauvola(im_arr, window_size=25)
|
| 427 |
+
binary = (im_arr < thresh).astype(float)
|
| 428 |
+
_fallback_cache["sauvola"] = gaussian_filter(sobel(binary), 0.5)
|
| 429 |
+
out.append(_fallback_cache["sauvola"])
|
| 430 |
+
return out
|
| 431 |
+
|
| 432 |
polygons: list = []
|
| 433 |
if suppl_obj is None:
|
| 434 |
suppl_obj = []
|
|
|
|
| 469 |
)
|
| 470 |
)
|
| 471 |
except Exception as e:
|
| 472 |
+
# Retry with fallback edge maps (CLAHE, then Sauvola) before giving up
|
| 473 |
+
recovered = False
|
| 474 |
+
for fb_feats in _get_fallback_feats():
|
| 475 |
+
try:
|
| 476 |
+
polygons.append(
|
| 477 |
+
_extract_patch(
|
| 478 |
+
env_up,
|
| 479 |
+
env_bottom,
|
| 480 |
+
line_arr.astype("int"),
|
| 481 |
+
offset_line.astype("int"),
|
| 482 |
+
end_points,
|
| 483 |
+
p_dir,
|
| 484 |
+
topline,
|
| 485 |
+
offset,
|
| 486 |
+
fb_feats,
|
| 487 |
+
bounds,
|
| 488 |
+
)
|
| 489 |
+
)
|
| 490 |
+
recovered = True
|
| 491 |
+
break
|
| 492 |
+
except Exception:
|
| 493 |
+
continue
|
| 494 |
+
if recovered:
|
| 495 |
+
continue
|
| 496 |
if raise_on_error:
|
| 497 |
raise
|
| 498 |
logger.warning("Polygonizer failed on line %s: %s", idx, e)
|
regnetx_infer/serialization/polygon.py
CHANGED
|
@@ -97,7 +97,7 @@ def _polygon_scale_tuple(polygon_scale: int, page_w: int, page_h: int) -> tuple[
|
|
| 97 |
page already fits within ``polygon_scale`` (we never upscale small pages).
|
| 98 |
"""
|
| 99 |
s = int(polygon_scale)
|
| 100 |
-
if s <= 0 or max(page_w, page_h) <= s:
|
| 101 |
return None
|
| 102 |
return (s, 0)
|
| 103 |
|
|
|
|
| 97 |
page already fits within ``polygon_scale`` (we never upscale small pages).
|
| 98 |
"""
|
| 99 |
s = int(polygon_scale)
|
| 100 |
+
if s <= 0 or max(page_w, page_h) <= int(s * 1.3):
|
| 101 |
return None
|
| 102 |
return (s, 0)
|
| 103 |
|