Files
business-card-scanner/tests/test_imaging.py
Lucas Orth ab993e98e1 Visitenkarten-Scanner: Stapelscan, Extraktion, Übersicht, vCard-Export
Selbst gehostete PWA, die Visitenkarten von einem Foto freistellt, ausliest
und als Kontakt bereitstellt.

- Stapelscan: OpenCV findet die Kartenrechtecke über mehrere Binärmasken,
  entzerrt sie perspektivisch und schneidet sie einzeln aus. Ohne Fund gilt
  das ganze Foto als eine Karte.
- Extraktion: ein Aufruf je Zuschnitt an das Vision-Modell mit
  JSON-Schema. Kein vorgeschaltetes OCR - das würde Layout und
  Schriftgrößen wegwerfen, aus denen die Feldzuordnung entsteht.
- Metadaten: Aufnahmezeit und GPS aus den EXIF-Daten des Fotos, Ortsname
  über Nominatim, Browserstandort nur als Rückfallebene.
- Übersicht mit Volltextsuche und Filtern, Detailansicht mit Korrekturmaske.
- Notizfeld je Karte, Erinnerungen per Mail inklusive Nachholen verpasster
  Termine nach einem Neustart.
- vCard 3.0 einzeln und als Sammeldatei, Karte gilt danach als exportiert.
- Anmeldung über ein Passwort, Sitzung als signiertes Cookie.
- Deployment per Tag-Push nach den Konventionen in DEPLOY.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 11:45:56 +02:00

73 lines
1.8 KiB
Python

import io
from PIL import Image
from app import imaging
def _jpeg_with_exif(tags: dict) -> bytes:
image = Image.new("RGB", (64, 40), "white")
exif = Image.Exif()
for tag, value in tags.items():
exif[tag] = value
buffer = io.BytesIO()
image.save(buffer, format="JPEG", exif=exif.tobytes())
return buffer.getvalue()
def test_aufnahmezeit_wird_gelesen():
raw = _jpeg_with_exif({0x9003: "2026:09:01 14:23:05"})
meta = imaging.read_metadata(raw)
assert meta["captured_at"].startswith("2026-09-01T14:23:05")
def test_gps_wird_in_dezimalgrad_umgerechnet():
raw = _jpeg_with_exif(
{0x8825: {1: "N", 2: (52.0, 19.0, 18.0), 3: "E", 4: (9.0, 48.0, 28.0)}}
)
meta = imaging.read_metadata(raw)
assert meta["lat"] == 52.321667
assert meta["lon"] == 9.807778
def test_suedliche_und_westliche_koordinaten_werden_negativ():
raw = _jpeg_with_exif(
{0x8825: {1: "S", 2: (33.0, 55.0, 0.0), 3: "W", 4: (18.0, 25.0, 0.0)}}
)
meta = imaging.read_metadata(raw)
assert meta["lat"] < 0
assert meta["lon"] < 0
def test_foto_ohne_exif_liefert_leere_metadaten():
buffer = io.BytesIO()
Image.new("RGB", (32, 32), "white").save(buffer, format="JPEG")
meta = imaging.read_metadata(buffer.getvalue())
assert meta == {"captured_at": None, "lat": None, "lon": None}
def test_kaputte_datei_wirft_nicht():
assert imaging.read_metadata(b"kein bild") == {
"captured_at": None,
"lat": None,
"lon": None,
}
def test_dekodieren_liefert_bgr_array():
buffer = io.BytesIO()
Image.new("RGB", (20, 10), (255, 0, 0)).save(buffer, format="PNG")
array = imaging.decode(buffer.getvalue())
assert array.shape == (10, 20, 3)
assert tuple(array[0, 0]) == (0, 0, 255) # Rot liegt in BGR hinten