compare.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. #!/usr/bin/env python3
  2. """Compares our renders against the real-WE reference frames.
  3. compare.py <ours dir> <review out dir> [ids...]
  4. Scores each item against the closest of its WE frames (they animate), writes <review>/<id>.png
  5. (top: WE | ours, bottom: diff heat | heat over ours) and small side-by-side sheets for skimming,
  6. and prints/stores the scores in <review>/scores.json. WE window crop = [30:1080, 4:1920] of the
  7. :98 screen, ours rendered at 1920x1058 -> [0:1050, 0:1916].
  8. """
  9. import glob, json, os, re, sys
  10. import numpy as np
  11. from PIL import Image
  12. REFS = os.environ.get('REFS', os.path.expanduser('~/.local/share/we_live/refs'))
  13. def log_problems(path):
  14. if not os.path.exists(path):
  15. return []
  16. out = []
  17. for line in open(path, errors='replace'):
  18. if re.search(r'glslang|shader.*(fail|error)|exception|Segmentation|terminate called|could not|cannot|failed', line, re.I) \
  19. and 'NoReply' not in line and 'dbus' not in line.lower():
  20. out.append(line.strip()[:200])
  21. return out
  22. def shift(a, b):
  23. """global (dx, dy) in pixels of b relative to a, phase correlation on grey images"""
  24. ga, gb = a.mean(axis=2), b.mean(axis=2)
  25. fa, fb = np.fft.fft2(ga - ga.mean()), np.fft.fft2(gb - gb.mean())
  26. r = fa.conj() * fb
  27. c = np.abs(np.fft.ifft2(r / (np.abs(r) + 1e-6)))
  28. y, x = np.unravel_index(np.argmax(c), c.shape)
  29. h, w = ga.shape
  30. return int(x if x < w // 2 else x - w), int(y if y < h // 2 else y - h), round(float(c.max()), 3)
  31. def main():
  32. ours_dir, rev = sys.argv[1], sys.argv[2]
  33. os.makedirs(rev, exist_ok=True)
  34. ids = sys.argv[3:] or sorted(os.path.basename(p)[:-4] for p in glob.glob(ours_dir + '/*.png'))
  35. spath = os.path.join(rev, 'scores.json')
  36. scores = json.load(open(spath)) if os.path.exists(spath) else {}
  37. thumbs = []
  38. for id_ in ids:
  39. we = [np.asarray(Image.open(f).convert('RGB'))[30:1080, 4:1920].astype(np.float32)
  40. for f in sorted(glob.glob(f'{REFS}/{id_}/f*.png'))]
  41. p = f'{ours_dir}/{id_}.png'
  42. rec = {'we_frames': len(we), 'ours': os.path.exists(p), 'our_log': log_problems(f'{ours_dir}/{id_}.log')[:8]}
  43. if not we or not rec['ours']:
  44. scores[id_] = rec
  45. print(id_, 'missing', rec)
  46. continue
  47. ours = np.asarray(Image.open(p).convert('RGB'))[0:1050, 0:1916].astype(np.float32)
  48. diffs = [np.abs(w - ours).mean() for w in we]
  49. best = we[int(np.argmin(diffs))]
  50. motion = float(np.mean([np.abs(we[i] - we[i + 1]).mean() for i in range(len(we) - 1)])) if len(we) > 1 else 0.0
  51. # blank WE frames (load failure) show up as near-uniform images
  52. rec.update(diff=round(float(min(diffs)), 2), we_motion=round(motion, 2),
  53. we_std=round(float(best.std()), 1), ours_std=round(float(ours.std()), 1),
  54. big_diff_area=round(float((np.abs(best - ours).mean(axis=2) > 48).mean()), 3),
  55. shift=shift(best, ours))
  56. scores[id_] = rec
  57. heat = np.clip(np.abs(best - ours).mean(axis=2) * 3, 0, 255)
  58. top = np.concatenate([best, ours], axis=1)
  59. grey = np.asarray(Image.fromarray(ours.astype(np.uint8)).convert('L').convert('RGB')).astype(np.float32) / 3
  60. bottom = np.concatenate([np.stack([heat] * 3, axis=2), grey + np.stack([heat, heat / 4, heat / 4], axis=2)], axis=1)
  61. Image.fromarray(np.clip(np.concatenate([top, bottom], axis=0), 0, 255).astype(np.uint8)).resize((1916, 1050)).save(f'{rev}/{id_}.png')
  62. thumbs.append((id_, Image.fromarray(top.astype(np.uint8)).resize((1280, 350))))
  63. print(f"{id_} diff={rec['diff']} area={rec['big_diff_area']} we-motion={rec['we_motion']} stds={rec['we_std']}/{rec['ours_std']} shift={rec['shift']}")
  64. json.dump(scores, open(spath, 'w'), indent=1, sort_keys=True)
  65. # sheets of 4 (WE left, ours right) for skimming
  66. for n in range(0, len(thumbs), 4):
  67. chunk = thumbs[n:n + 4]
  68. sheet = Image.new('RGB', (1280, 360 * len(chunk)), (255, 0, 255))
  69. for i, (id_, t) in enumerate(chunk):
  70. sheet.paste(t, (0, i * 360))
  71. sheet.save(f'{rev}/sheet_{n // 4:02d}_{"_".join(c[0] for c in chunk)}.png')
  72. if __name__ == '__main__':
  73. main()