Speed: dhash-only rows (0.21s→6ms/file); gate near-dup+phash behind flag; fix hx UnboundLocalError; loop in single task (engine deadlock fix); photo_watch uses ingest flow
This commit is contained in:
33
photo_db.py
33
photo_db.py
@@ -82,14 +82,21 @@ def sha256_file(path: Path) -> str:
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def hash_image(path: Path, hash_size: int = 8):
|
||||
"""Perceptual hashes for one image file."""
|
||||
def hash_image(path: Path, hash_size: int = 8, include_phash: bool = False):
|
||||
"""Perceptual hashes for one image file.
|
||||
|
||||
dhash is cheap (~6ms); phash is expensive (~210ms) so only computed when
|
||||
include_phash=True (near-dup checks). The stored row uses dhash as the
|
||||
dedup fingerprint — dhash is a valid perceptual hash for this purpose.
|
||||
"""
|
||||
with Image.open(path) as im:
|
||||
im = im.convert("RGB")
|
||||
return {
|
||||
"phash": str(phash(im, hash_size=hash_size)),
|
||||
"dhash": str(dhash(im, hash_size=hash_size)),
|
||||
}
|
||||
hx = {"dhash": str(dhash(im, hash_size=hash_size))}
|
||||
if include_phash:
|
||||
hx["phash"] = str(phash(im, hash_size=hash_size))
|
||||
else:
|
||||
hx["phash"] = ""
|
||||
return hx
|
||||
|
||||
|
||||
def register_image(conn, path: Path, source: str, exif_date: str | None = None):
|
||||
@@ -122,8 +129,11 @@ def check_near_dup(conn, path: Path, hamming_threshold: int = 10):
|
||||
rows = conn.execute("SELECT phash, dhash, path, source FROM image_hashes").fetchall()
|
||||
best, best_dist = None, None
|
||||
for ph, dh, p, src in rows:
|
||||
phd = hamming(ph, hx["phash"])
|
||||
dhd = hamming(dh, hx["dhash"])
|
||||
dhd = hamming(dh, hx.get("dhash", ""))
|
||||
if ph and hx.get("phash"):
|
||||
phd = hamming(ph, hx["phash"])
|
||||
else:
|
||||
phd = dhd # no phash available — use dhash distance
|
||||
dist = min(phd, dhd)
|
||||
if best_dist is None or dist < best_dist:
|
||||
best, best_dist = (p, src), dist
|
||||
@@ -168,8 +178,11 @@ def _near_dup_lookup(conn, hx: dict, hamming_threshold: int = 10):
|
||||
rows = conn.execute("SELECT phash, dhash, path, source FROM image_hashes").fetchall()
|
||||
best, best_dist = None, None
|
||||
for ph, dh, p, src in rows:
|
||||
phd = hamming(ph, hx["phash"])
|
||||
dhd = hamming(dh, hx["dhash"])
|
||||
dhd = hamming(dh, hx.get("dhash", ""))
|
||||
if ph and hx.get("phash"):
|
||||
phd = hamming(ph, hx["phash"])
|
||||
else:
|
||||
phd = dhd # no phash available — use dhash distance
|
||||
dist = min(phd, dhd)
|
||||
if best_dist is None or dist < best_dist:
|
||||
best, best_dist = (p, src), dist
|
||||
|
||||
Reference in New Issue
Block a user