phash updates!
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-07-04 01:10:33 +02:00
parent 44763c3408
commit 729071817e
21 changed files with 2080 additions and 677 deletions
+120
View File
@@ -0,0 +1,120 @@
"""Recompute PR curves from an image_similarity .store file (messagepack).
Replicates DescriptorStore::get_stats semantics:
- micro-averaged over base-image queries
- TP_m(t): mutant m of query found within Hamming distance t
- FP: other base images count for every mutator; other images' mutants
count only for their own mutator
"""
import sys
import msgpack
import numpy as np
TMAX = 33 # thresholds 0..32
def load(path):
with open(path, "rb") as f:
m = msgpack.unpack(f, strict_map_key=False)
if isinstance(m, (list, tuple)):
# versioned format: [format, descriptor, descriptor_version, map]
print(f"{path}: {m[1]} v{m[2]} (store format {m[0]})")
return m[3]
return m # legacy format: bare map
def detect_mutators(buckets):
tags = set()
for bucket in buckets:
for name in bucket:
if name.startswith("mut."):
tags.add(name.split(".")[1])
return sorted(tags)
def main(path, label):
m = load(path)
global MUTATORS
MUTATORS = detect_mutators(m.values())
print(f"mutators: {MUTATORS}")
keys = np.array(list(m.keys()), dtype=np.uint64)
buckets = list(m.values())
K = len(keys)
name2hash = {}
n_base = np.zeros(K, dtype=np.float64)
n_mut = {mut: np.zeros(K, dtype=np.float64) for mut in MUTATORS}
for ki, bucket in enumerate(buckets):
for name in bucket:
name2hash[name] = keys[ki]
if name.startswith("mut."):
for mut in MUTATORS:
if name.startswith(f"mut.{mut}."):
n_mut[mut][ki] += 1
break
else:
n_base[ki] += 1
bases = [n for n in name2hash if not n.startswith("mut.")]
N = len(bases)
q = np.array([name2hash[b] for b in bases], dtype=np.uint64)
# TP_m(t): distance from each base to its own mutant, cumulative over t
tp = {}
for mut in MUTATORS:
d = np.array(
[bin(int(name2hash[b]) ^ int(name2hash[f"mut.{mut}.{b}"])).count("1")
for b in bases])
tp[mut] = np.cumsum(np.bincount(d, minlength=TMAX)[:TMAX])
# Histogram of (query, key) distances weighted by bucket composition
hist_base = np.zeros(TMAX)
hist_mut = {mut: np.zeros(TMAX) for mut in MUTATORS}
CHUNK = 512
for i in range(0, N, CHUNK):
d = np.bitwise_count(q[i:i + CHUNK, None] ^ keys[None, :]).astype(np.uint8)
flat = d.ravel()
sel = flat < TMAX
flat = flat[sel]
rows = d.shape[0]
hist_base += np.bincount(flat, weights=np.broadcast_to(n_base, (rows, K)).ravel()[sel], minlength=TMAX)[:TMAX]
for mut in MUTATORS:
hist_mut[mut] += np.bincount(flat, weights=np.broadcast_to(n_mut[mut], (rows, K)).ravel()[sel], minlength=TMAX)[:TMAX]
cum_base = np.cumsum(hist_base) - N # exclude self (d=0 always)
print(f"\n=== {label} ===")
print(f"{'t':>2} | " + " | ".join(f"{mut:>22}" for mut in MUTATORS))
print(f"{'':>2} | " + " | ".join(f"{'recall':>10} {'precis':>11}" for _ in MUTATORS))
curves = {}
for mut in MUTATORS:
fp = (np.cumsum(hist_mut[mut]) - tp[mut]) + cum_base
rec = tp[mut] / N
prec = np.divide(tp[mut], tp[mut] + fp,
out=np.zeros(TMAX), where=(tp[mut] + fp) > 0)
curves[mut] = (rec, prec)
for t in range(TMAX):
row = " | ".join(f"{curves[mut][0][t]:>10.4f} {curves[mut][1][t]:>11.6f}" for mut in MUTATORS)
print(f"{t:>2} | {row}")
return curves
if __name__ == "__main__":
curves_by_store = {}
for path, label in [("dct.store", "DCT"), ("median.store", "Median")]:
full = f"/home/mark/workspace/repos/image-similarity/{path}"
curves_by_store[label] = main(full, label)
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(13, 5.5), sharey=True)
for ax, (label, curves) in zip(axes, curves_by_store.items()):
for mut, (rec, prec) in curves.items():
ax.plot(rec, prec, marker=".", label=mut)
ax.set_title(f"{label} hash — 24,988 Flickr images, thresholds 032")
ax.set_xlabel("Recall")
ax.set_ylabel("Precision")
ax.grid(alpha=.3)
ax.legend()
fig.tight_layout()
fig.savefig("/tmp/imgsim/pr-full.png", dpi=110)
print("\nplot: /tmp/imgsim/pr-full.png")
except ImportError:
print("\nmatplotlib not available; table output only")