fix: merge nearby spine gaps + handle multi-page crop in frontend
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 31s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 1m53s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 18s
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 31s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 1m53s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 18s
Backend: merge gaps within 5% of image width — the spine area may have thin ink strips splitting one physical gap into multiple detected gaps. Only use gaps >= 2% width as split points. Frontend: StepCrop now handles multi_page crop responses without crashing on missing original_size/cropped_size fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,10 +108,21 @@ export function StepCrop({ sessionId, onNext }: StepCropProps) {
|
|||||||
{cropResult && (
|
{cropResult && (
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||||
<div className="flex flex-wrap items-center gap-3 text-sm">
|
<div className="flex flex-wrap items-center gap-3 text-sm">
|
||||||
{cropResult.crop_applied ? (
|
{(cropResult as Record<string, unknown>).multi_page ? (
|
||||||
|
<>
|
||||||
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 text-xs font-medium">
|
||||||
|
Mehrseitig: {(cropResult as Record<string, unknown>).page_count as number} Seiten erkannt
|
||||||
|
</span>
|
||||||
|
{((cropResult as Record<string, unknown>).sub_sessions as Array<{id: string; name: string; page_index: number}> | undefined)?.map((sub) => (
|
||||||
|
<span key={sub.id} className="text-gray-400 text-xs">
|
||||||
|
Seite {sub.page_index + 1}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : cropResult.crop_applied ? (
|
||||||
<>
|
<>
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 text-xs font-medium">
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 text-xs font-medium">
|
||||||
✂️ Zugeschnitten
|
Zugeschnitten
|
||||||
</span>
|
</span>
|
||||||
{cropResult.detected_format && (
|
{cropResult.detected_format && (
|
||||||
<>
|
<>
|
||||||
@@ -126,10 +137,14 @@ export function StepCrop({ sessionId, onNext }: StepCropProps) {
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
{cropResult.original_size && cropResult.cropped_size && (
|
||||||
<span className="text-gray-400 text-xs">
|
<>
|
||||||
{cropResult.original_size.width}x{cropResult.original_size.height} → {cropResult.cropped_size.width}x{cropResult.cropped_size.height}
|
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
||||||
</span>
|
<span className="text-gray-400 text-xs">
|
||||||
|
{cropResult.original_size.width}x{cropResult.original_size.height} → {cropResult.cropped_size.width}x{cropResult.cropped_size.height}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{cropResult.border_fractions && (
|
{cropResult.border_fractions && (
|
||||||
<>
|
<>
|
||||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
||||||
@@ -141,7 +156,7 @@ export function StepCrop({ sessionId, onNext }: StepCropProps) {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 text-xs font-medium">
|
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 text-xs font-medium">
|
||||||
✓ Kein Zuschnitt noetig
|
Kein Zuschnitt noetig
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{cropResult.duration_seconds != null && (
|
{cropResult.duration_seconds != null && (
|
||||||
|
|||||||
@@ -100,12 +100,33 @@ def detect_page_splits(
|
|||||||
if not gaps:
|
if not gaps:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
# Merge nearby gaps (< 5% of width apart) — the spine area may have
|
||||||
|
# thin ink strips between multiple gap segments
|
||||||
|
merge_dist = max(20, int(w * 0.05))
|
||||||
|
merged: list = [gaps[0]]
|
||||||
|
for g in gaps[1:]:
|
||||||
|
prev = merged[-1]
|
||||||
|
prev_end = prev["x"] + prev["width"]
|
||||||
|
if g["x"] - prev_end < merge_dist:
|
||||||
|
# Merge: extend previous gap to cover both
|
||||||
|
new_end = g["x"] + g["width"]
|
||||||
|
prev["width"] = new_end - prev["x"]
|
||||||
|
prev["center"] = prev["x"] + prev["width"] // 2
|
||||||
|
else:
|
||||||
|
merged.append(g)
|
||||||
|
gaps = merged
|
||||||
|
|
||||||
# Sort gaps by width (largest = most likely spine)
|
# Sort gaps by width (largest = most likely spine)
|
||||||
gaps.sort(key=lambda g: g["width"], reverse=True)
|
gaps.sort(key=lambda g: g["width"], reverse=True)
|
||||||
|
|
||||||
# Use the widest gap(s) as split points
|
# Use only gaps that are significant (>= 2% of image width)
|
||||||
# For now: support up to N-1 gaps → N pages
|
significant_gaps = [g for g in gaps if g["width"] >= w * 0.02]
|
||||||
split_points = sorted(g["center"] for g in gaps[:3]) # max 4 pages
|
if not significant_gaps:
|
||||||
|
# Fall back to widest gap
|
||||||
|
significant_gaps = [gaps[0]]
|
||||||
|
|
||||||
|
# Use the significant gap(s) as split points
|
||||||
|
split_points = sorted(g["center"] for g in significant_gaps[:3])
|
||||||
|
|
||||||
# Build page rectangles
|
# Build page rectangles
|
||||||
pages: list = []
|
pages: list = []
|
||||||
|
|||||||
Reference in New Issue
Block a user