@@ -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 0–32")
|
||||
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")
|
||||
Reference in New Issue
Block a user