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 && (
|
||||
<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">
|
||||
{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">
|
||||
✂️ Zugeschnitten
|
||||
Zugeschnitten
|
||||
</span>
|
||||
{cropResult.detected_format && (
|
||||
<>
|
||||
@@ -126,10 +137,14 @@ export function StepCrop({ sessionId, onNext }: StepCropProps) {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
||||
<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.original_size && cropResult.cropped_size && (
|
||||
<>
|
||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
||||
<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 && (
|
||||
<>
|
||||
<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">
|
||||
✓ Kein Zuschnitt noetig
|
||||
Kein Zuschnitt noetig
|
||||
</span>
|
||||
)}
|
||||
{cropResult.duration_seconds != null && (
|
||||
|
||||
@@ -100,12 +100,33 @@ def detect_page_splits(
|
||||
if not gaps:
|
||||
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)
|
||||
gaps.sort(key=lambda g: g["width"], reverse=True)
|
||||
|
||||
# Use the widest gap(s) as split points
|
||||
# For now: support up to N-1 gaps → N pages
|
||||
split_points = sorted(g["center"] for g in gaps[:3]) # max 4 pages
|
||||
# Use only gaps that are significant (>= 2% of image width)
|
||||
significant_gaps = [g for g in gaps if g["width"] >= w * 0.02]
|
||||
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
|
||||
pages: list = []
|
||||
|
||||
Reference in New Issue
Block a user