fix: Sub-Session Zeilenerkennung nutzt Word-Grouping statt Gap-Detection
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 28s
CI / test-python-klausur (push) Failing after 2m0s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 23s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 28s
CI / test-python-klausur (push) Failing after 2m0s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 23s
Gap-basierte Erkennung findet bei kleinen Box-Bildern zu wenige Gaps
und mergt Zeilen (7 raw gaps -> 4 validated -> nur 3 rows statt 6).
Sub-Sessions nutzen jetzt direkt _build_rows_from_word_grouping(),
das Woerter nach Y-Position clustert — robuster fuer komplexe Box-Layouts.
Zusaetzlich: alle zones=None Crashes gefixt (replace_all .get("zones") or []).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -419,7 +419,7 @@ async def create_box_sessions(session_id: str):
|
|||||||
if not column_result:
|
if not column_result:
|
||||||
raise HTTPException(status_code=400, detail="Column detection must be completed first")
|
raise HTTPException(status_code=400, detail="Column detection must be completed first")
|
||||||
|
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
box_zones = [z for z in zones if z.get("zone_type") == "box" and z.get("box")]
|
box_zones = [z for z in zones if z.get("zone_type") == "box" and z.get("box")]
|
||||||
if not box_zones:
|
if not box_zones:
|
||||||
return {"session_id": session_id, "sub_sessions": [], "message": "No boxes detected"}
|
return {"session_id": session_id, "sub_sessions": [], "message": "No boxes detected"}
|
||||||
@@ -1532,7 +1532,7 @@ async def _get_columns_overlay(session_id: str) -> Response:
|
|||||||
cv2.addWeighted(overlay, 0.2, img, 0.8, 0, img)
|
cv2.addWeighted(overlay, 0.2, img, 0.8, 0, img)
|
||||||
|
|
||||||
# Draw detected box boundaries as dashed rectangles
|
# Draw detected box boundaries as dashed rectangles
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
for zone in zones:
|
for zone in zones:
|
||||||
if zone.get("zone_type") == "box" and zone.get("box"):
|
if zone.get("zone_type") == "box" and zone.get("box"):
|
||||||
box = zone["box"]
|
box = zone["box"]
|
||||||
@@ -1600,6 +1600,21 @@ async def detect_rows(session_id: str):
|
|||||||
# Read zones from column_result to exclude box regions
|
# Read zones from column_result to exclude box regions
|
||||||
session = await get_session_db(session_id)
|
session = await get_session_db(session_id)
|
||||||
column_result = (session or {}).get("column_result") or {}
|
column_result = (session or {}).get("column_result") or {}
|
||||||
|
is_sub_session = bool((session or {}).get("parent_session_id"))
|
||||||
|
|
||||||
|
# Sub-sessions (box crops): use word-grouping instead of gap-based
|
||||||
|
# row detection. Box images are small with complex internal layouts
|
||||||
|
# (headings, sub-columns) where the horizontal projection approach
|
||||||
|
# merges rows. Word-grouping directly clusters words by Y proximity,
|
||||||
|
# which is more robust for these cases.
|
||||||
|
if is_sub_session and word_dicts:
|
||||||
|
from cv_layout import _build_rows_from_word_grouping
|
||||||
|
rows = _build_rows_from_word_grouping(
|
||||||
|
word_dicts, left_x, right_x, top_y, bottom_y,
|
||||||
|
right_x - left_x, bottom_y - top_y,
|
||||||
|
)
|
||||||
|
logger.info(f"OCR Pipeline: sub-session {session_id}: word-grouping found {len(rows)} rows")
|
||||||
|
else:
|
||||||
zones = column_result.get("zones") or [] # zones can be None for sub-sessions
|
zones = column_result.get("zones") or [] # zones can be None for sub-sessions
|
||||||
|
|
||||||
# Collect box y-ranges for filtering
|
# Collect box y-ranges for filtering
|
||||||
@@ -1677,6 +1692,7 @@ async def detect_rows(session_id: str):
|
|||||||
|
|
||||||
# Assign zone_index based on which content zone each row falls in
|
# Assign zone_index based on which content zone each row falls in
|
||||||
# Build content zone list with indices
|
# Build content zone list with indices
|
||||||
|
zones = column_result.get("zones") or []
|
||||||
content_zones = [(i, z) for i, z in enumerate(zones) if z.get("zone_type") == "content"] if zones else []
|
content_zones = [(i, z) for i, z in enumerate(zones) if z.get("zone_type") == "content"] if zones else []
|
||||||
|
|
||||||
# Build serializable result (exclude words to keep payload small)
|
# Build serializable result (exclude words to keep payload small)
|
||||||
@@ -1909,7 +1925,7 @@ async def detect_words(
|
|||||||
row.word_count = len(row.words)
|
row.word_count = len(row.words)
|
||||||
|
|
||||||
# Exclude rows that fall within box zones
|
# Exclude rows that fall within box zones
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
box_ranges = []
|
box_ranges = []
|
||||||
for zone in zones:
|
for zone in zones:
|
||||||
if zone.get("zone_type") == "box" and zone.get("box"):
|
if zone.get("zone_type") == "box" and zone.get("box"):
|
||||||
@@ -2676,7 +2692,7 @@ async def get_fabric_json(session_id: str):
|
|||||||
subs = await get_sub_sessions(session_id)
|
subs = await get_sub_sessions(session_id)
|
||||||
if subs:
|
if subs:
|
||||||
column_result = session.get("column_result") or {}
|
column_result = session.get("column_result") or {}
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
box_zones = [z for z in zones if z.get("zone_type") == "box" and z.get("box")]
|
box_zones = [z for z in zones if z.get("zone_type") == "box" and z.get("box")]
|
||||||
|
|
||||||
for sub in subs:
|
for sub in subs:
|
||||||
@@ -2733,7 +2749,7 @@ async def get_merged_vocab_entries(session_id: str):
|
|||||||
subs = await get_sub_sessions(session_id)
|
subs = await get_sub_sessions(session_id)
|
||||||
if subs:
|
if subs:
|
||||||
column_result = session.get("column_result") or {}
|
column_result = session.get("column_result") or {}
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
box_zones = [z for z in zones if z.get("zone_type") == "box" and z.get("box")]
|
box_zones = [z for z in zones if z.get("zone_type") == "box" and z.get("box")]
|
||||||
|
|
||||||
for sub in subs:
|
for sub in subs:
|
||||||
@@ -3289,7 +3305,7 @@ async def _get_rows_overlay(session_id: str) -> Response:
|
|||||||
|
|
||||||
# Draw zone separator lines if zones exist
|
# Draw zone separator lines if zones exist
|
||||||
column_result = session.get("column_result") or {}
|
column_result = session.get("column_result") or {}
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
if zones:
|
if zones:
|
||||||
img_w_px = img.shape[1]
|
img_w_px = img.shape[1]
|
||||||
zone_color = (0, 200, 255) # Yellow (BGR)
|
zone_color = (0, 200, 255) # Yellow (BGR)
|
||||||
@@ -3445,7 +3461,7 @@ async def _get_words_overlay(session_id: str) -> Response:
|
|||||||
|
|
||||||
# Red semi-transparent overlay for box zones
|
# Red semi-transparent overlay for box zones
|
||||||
column_result = session.get("column_result") or {}
|
column_result = session.get("column_result") or {}
|
||||||
zones = column_result.get("zones", [])
|
zones = column_result.get("zones") or []
|
||||||
_draw_box_exclusion_overlay(img, zones)
|
_draw_box_exclusion_overlay(img, zones)
|
||||||
|
|
||||||
success, result_png = cv2.imencode(".png", img)
|
success, result_png = cv2.imencode(".png", img)
|
||||||
|
|||||||
Reference in New Issue
Block a user