matrix-shooter-train / matrix /code /matrix_report.py
peih's picture
backup matrix/code
f1caa47 verified
Raw History Blame Contribute Delete
5.35 kB
"""Summarise a matrix_capture.py run: mix, fight statistics, coverage maps and sample sheets.
usage: matrix_report.py DATA_DIR OUT_DIR
Writes OUT_DIR/report.json, coverage-<map>.png (top-down: grey = walkable
blocks from the plan, colour = player frames per 2 m cell, with eight yaw
sectors per 10 m block counted), and sheet-<map>.jpg (graybox | cartoon | war |
punk at mid-clip for a spread of team sizes).
"""
import collections
import json
import math
import random
import sys
from pathlib import Path
import cv2
import numpy as np
LOOKS = ('original', 'cartoon', 'war', 'punk')
def frame_at(video, index):
cap = cv2.VideoCapture(str(video))
cap.set(cv2.CAP_PROP_POS_FRAMES, index)
ok, image = cap.read()
cap.release()
return image if ok else np.zeros((544, 960, 3), np.uint8)
def main():
data, out = Path(sys.argv[1]), Path(sys.argv[2])
out.mkdir(parents=True, exist_ok=True)
jobs = [json.loads(l) for l in (data / 'jobs.jsonl').read_text().splitlines() if l.strip()]
report = {}
for map_id in ('paris', 'tsinghua'):
metas = [json.loads((c / 'meta.json').read_text()) for c in sorted((data / map_id).glob('clip_*')) if (c / 'meta.json').exists()]
fights = [m for m in metas if m['teams'][1]]
positions, sectors = [], collections.defaultdict(set)
for m in metas:
for line in (data / map_id / f"clip_{m['clip']:06d}" / 'frames.jsonl').read_text().splitlines()[::6]:
row = json.loads(line)
p, yaw = row['state']['player'], row['state']['yaw']
positions.append((p['x'], p['z']))
sectors[(math.floor(p['x'] / 10), math.floor(p['z'] / 10))].add(int(((yaw % (2 * math.pi)) / (math.pi / 4))) % 8)
planned = {(math.floor(j['setup']['player']['x'] / 10), math.floor(j['setup']['player']['z'] / 10)) for j in jobs if j['map'] == map_id}
report[map_id] = dict(
clips=len(metas), planned=sum(j['map'] == map_id for j in jobs),
teams=dict(sorted(collections.Counter(f"{m['teams'][0]}v{m['teams'][1]}" for m in metas).items())),
distance=dict(collections.Counter(m['distance'] for m in fights)), side=dict(collections.Counter(m['side'] for m in fights)),
landmark_clips=sum(m['anchor'] is not None for m in metas), max_drift=max((m['max_drift'] for m in metas), default=None),
fight_bullet_frame_share=round(float(np.mean([m['bullet_frame_share'] for m in fights])), 3) if fights else None,
fight_mean_alive_bots=round(float(np.mean([m['mean_alive_bots'] for m in fights])), 2) if fights else None,
fight_player_shots=round(float(np.mean([m['shots'] for m in fights])), 1) if fights else None,
fight_kills=round(float(np.mean([m['kills'] for m in fights])), 2) if fights else None,
blocks_visited=len(sectors), blocks_planned=len(planned),
yaw_sectors_per_block=round(float(np.mean([len(s) for s in sectors.values()])), 2) if sectors else None,
yaw_histogram=[sum(k in s for s in sectors.values()) for k in range(8)],
wall_s_mean=round(float(np.mean([m['wall_s'] for m in metas])), 1) if metas else None)
# Top-down coverage map: 2 m cells, log count colour.
xs, zs = [p[0] for p in positions], [p[1] for p in positions]
if positions:
x0, x1, z0, z1 = min(xs) - 4, max(xs) + 4, min(zs) - 4, max(zs) + 4
cell = 2.0
grid = np.zeros((int((z1 - z0) / cell) + 1, int((x1 - x0) / cell) + 1))
for x, z in positions:
grid[int((z - z0) / cell), int((x - x0) / cell)] += 1
norm = np.log1p(grid) / max(1e-6, np.log1p(grid).max())
image = cv2.applyColorMap((norm * 255).astype(np.uint8), cv2.COLORMAP_INFERNO)
image[grid == 0] = (40, 40, 40)
for bx, bz in planned:
x, z = int((bx * 10 - x0) / cell), int((bz * 10 - z0) / cell)
cv2.rectangle(image, (x, z), (x + 5, z + 5), (90, 90, 90), 1)
image = cv2.resize(image, None, fx=8, fy=8, interpolation=cv2.INTER_NEAREST)
cv2.imwrite(str(out / f'coverage-{map_id}.png'), image[::-1])
# Sample sheet: one fight per team size, mid-clip.
rng = random.Random(0)
by_team = collections.defaultdict(list)
for m in metas:
by_team[tuple(m['teams'])].append(m)
rows = []
for team in sorted(by_team)[:9]:
m = rng.choice(by_team[team])
clip = data / map_id / f"clip_{m['clip']:06d}"
tiles = [cv2.resize(frame_at(clip / f'{look}.mp4', 120), (384, 218), interpolation=cv2.INTER_AREA) for look in LOOKS]
row = np.concatenate(tiles, 1)
cv2.putText(row, f"{m['teams'][0]}v{m['teams'][1]} {m['distance']} {m['side']} clip {m['clip']}", (8, 22), 0, .6, (0, 0, 0), 4)
cv2.putText(row, f"{m['teams'][0]}v{m['teams'][1]} {m['distance']} {m['side']} clip {m['clip']}", (8, 22), 0, .6, (255, 255, 255), 2)
rows.append(row)
if rows:
cv2.imwrite(str(out / f'sheet-{map_id}.jpg'), np.concatenate(rows, 0), [cv2.IMWRITE_JPEG_QUALITY, 82])
(out / 'report.json').write_text(json.dumps(report, indent=1))
print(json.dumps(report, indent=1))
if __name__ == '__main__':
main()