"""Preregistered 30-epoch 640px diagnostic; execute only through the sandbox launcher."""
import os
os.chdir('/work')
os.environ.update(YOLO_AUTOINSTALL='False', YOLO_OFFLINE='True', YOLO_CONFIG_DIR='/work/config', MPLCONFIGDIR='/work/mpl')
import base64, copy, hashlib, io, json, platform, resource, sys, time
from pathlib import Path
import cv2, numpy as np, torch, ultralytics
from ultralytics import YOLO
from ultralytics.data.augment import LetterBox

epochs = 30
train_size = 640
eval_sizes = (320, 640)
start = time.monotonic()
torch.set_num_threads(2)
torch.set_num_interop_threads(2)
cv2.setNumThreads(2)
def sha(p): return hashlib.sha256(Path(p).read_bytes()).hexdigest()
manifest = json.loads(Path('/manifest.json').read_text())
frozen, audit, truth, truth_original = {}, [], {}, {}
for row in manifest['images']:
    name, split = row['file_name'], row['split']
    assert Path(name).name == name and split in ('train', 'val')
    p = Path('/input/images') / split / name
    label = Path('/input/labels') / split / (p.stem + '.txt')
    assert sha(p) == row['sha256'] and sha(label) == row['label_sha256']
    frozen[str(p)], frozen[str(label)] = sha(p), sha(label)
    im = cv2.imread(str(p))
    assert im.shape[:2] == (row['height'], row['width'])
    labels = np.loadtxt(label, ndmin=2)
    assert labels.shape == (row['box_count'], 5) and np.isfinite(labels).all()
    assert (labels[:, 0] == 0).all()
    xywh = labels[:, 1:]
    assert (xywh >= 0).all() and (xywh <= 1).all() and (xywh[:, 2:] > 0).all()
    assert (xywh[:, :2] - xywh[:, 2:]/2 >= -1e-6).all()
    assert (xywh[:, :2] + xywh[:, 2:]/2 <= 1+1e-6).all()
    original_dims = np.array([im.shape[1], im.shape[0]])
    original_centers, original_sizes = xywh[:, :2] * original_dims, xywh[:, 2:] * original_dims
    truth_original[name] = np.concatenate([original_centers-original_sizes/2, original_centers+original_sizes/2], axis=1)
    truth[name], pixel_sizes = {}, {}
    for size in eval_sizes:
        gain = size / max(im.shape[:2])
        dims = original_dims * gain
        centers, sizes = xywh[:, :2] * dims, xywh[:, 2:] * dims
        centers += (size - dims) / 2
        truth[name][size] = np.concatenate([centers-sizes/2, centers+sizes/2], axis=1)
        pixel_sizes[str(size)] = sizes.tolist()
    audit.append(dict(image=name, split=split, boxes=len(labels), pixel_sizes_wh=pixel_sizes, classes=[0]))
assert len(audit) == 12
assert not ({r['sha256'] for r in manifest['images'] if r['split']=='train'} & {r['sha256'] for r in manifest['images'] if r['split']=='val'})
data = Path('/work/data.yaml')
data.write_text('path: /input\ntrain: images/train\nval: images/val\nnames:\n  0: rubbish\n')
settings = dict(data=str(data), epochs=epochs, imgsz=train_size, batch=4, workers=0, device='cpu', seed=42,
    deterministic=True, cache=False, amp=False, pretrained=True, freeze=None, optimizer='AdamW', lr0=.001,
    nbs=4, warmup_epochs=0, close_mosaic=0, mosaic=0, mixup=0, copy_paste=0, plots=False,
    project='/work/runs', name='diagnostic', exist_ok=False, save=True, val=True, verbose=False)
print('AUDIT_JSON_BEGIN\n'+json.dumps(audit)+'\nAUDIT_JSON_END', flush=True)
model = YOLO('/pretrained.pt')
model.train(**settings)
checkpoint = Path('/work/runs/diagnostic/weights/last.pt')
loaded = YOLO(str(checkpoint))
assert loaded.names == {0:'rubbish'}
metrics = {}
for size in eval_sizes:
    metrics[str(size)] = {}
    for split in ('train', 'val'):
        m = loaded.val(data=str(data), split=split, imgsz=size, batch=4, workers=0, device='cpu', conf=.001,
                       plots=False, verbose=False, project='/work/runs', name=f'eval-{size}-{split}')
        metrics[str(size)][split] = {k:float(v) for k,v in m.results_dict.items()}
pt = copy.deepcopy(loaded.model).cpu().float().eval().fuse()
def ious(a, b):
    lt = np.maximum(a[:,None,:2], b[None,:,:2])
    rb = np.minimum(a[:,None,2:], b[None,:,2:])
    inter = np.maximum(rb-lt,0).prod(axis=2)
    aa = np.maximum(a[:,2:]-a[:,:2],0).prod(axis=1)
    bb = np.maximum(b[:,2:]-b[:,:2],0).prod(axis=1)
    return inter / np.maximum(aa[:,None]+bb[None,:]-inter,1e-9)
predictions = []
for size in eval_sizes:
    for row in manifest['images']:
        p = Path('/input/images') / row['split'] / row['file_name']
        im = LetterBox(new_shape=(size,size), auto=False)(image=cv2.imread(str(p)))
        x = np.ascontiguousarray(im[:,:,::-1].transpose(2,0,1)[None], dtype=np.float32)/255
        with torch.inference_mode():
            raw = pt(torch.from_numpy(x))
            raw = raw[0] if isinstance(raw, tuple) else raw
            a = raw.numpy()[0]
        expected_proposals = sum((size // stride) ** 2 for stride in (8, 16, 32))
        assert a.shape == (5, expected_proposals) and np.isfinite(a).all()
        boxes = np.concatenate([a[:2].T-a[2:4].T/2,a[:2].T+a[2:4].T/2],axis=1)
        overlaps = ious(truth[row['file_name']][size], boxes)
        best = overlaps.argmax(axis=1)
        pred = loaded.predict(str(p), imgsz=size, rect=False, device='cpu', conf=.25, verbose=False)[0]
        detected_boxes = pred.boxes.xyxy.cpu().numpy()
        detected_scores = pred.boxes.conf.cpu().numpy()
        matched, used_gt = 0, set()
        detected_ious = ious(detected_boxes, truth_original[row['file_name']])
        for index in np.argsort(-detected_scores):
            candidates = np.argsort(-detected_ious[index])
            for gt_index in candidates:
                if detected_ious[index, gt_index] < .5: break
                if int(gt_index) not in used_gt:
                    used_gt.add(int(gt_index)); matched += 1; break
        predictions.append(dict(image=row['file_name'], split=row['split'], imgsz=size, raw_shape=list(a.shape),
            raw_max_class_score=float(a[4].max()), raw_score_quantiles=np.quantile(a[4],[0,.5,.9,.99,1]).tolist(),
            raw_best_iou_per_gt=overlaps.max(axis=1).tolist(), raw_score_at_best_iou=a[4,best].tolist(),
            raw_proposals_at_025=int((a[4]>=.25).sum()), detections_at_conf_025=len(pred.boxes),
            matched_gt_iou_05_at_conf_025=matched, false_positives_at_conf_025=len(pred.boxes)-matched,
            detection_classes=pred.boxes.cls.tolist(), detection_scores=detected_scores.tolist()))
assert frozen == {p:sha(p) for p in frozen}
report = dict(date='2026-09-30', scope='computer-learning resolution diagnostic; no untouched test or physical-readiness claim',
    hypothesis='640px training preserves tiny object pixels enough for correct detections at fixed confidence 0.25 on train and development validation',
    seconds=round(time.monotonic()-start,3), versions=dict(python=platform.python_version(), torch=torch.__version__, ultralytics=ultralytics.__version__, numpy=np.__version__, opencv=cv2.__version__),
    settings=settings, audit=audit, metrics=metrics, predictions=predictions, input_sha256=frozen,
    manifest_sha256=sha('/manifest.json'), pretrained_sha256=sha('/pretrained.pt'), checkpoint_sha256=sha(checkpoint),
    classes=loaded.names, sandbox=dict(cpu_affinity=sorted(os.sched_getaffinity(0)), address_space=resource.getrlimit(resource.RLIMIT_AS),
        cpu_limit=resource.getrlimit(resource.RLIMIT_CPU), processes=resource.getrlimit(resource.RLIMIT_NPROC),
        file_size=resource.getrlimit(resource.RLIMIT_FSIZE), open_files=resource.getrlimit(resource.RLIMIT_NOFILE),
        network_interfaces=Path('/proc/net/dev').read_text(), process_status=[s for s in Path('/proc/self/status').read_text().splitlines() if s.startswith(('Cap','NoNewPrivs'))],
        wall_seconds=900, scratch_tmpfs_bytes=2147483648, mountinfo=Path('/proc/self/mountinfo').read_text()))
print('EVIDENCE_JSON_BEGIN\n'+json.dumps(report,indent=2)+'\nEVIDENCE_JSON_END')
csv = Path('/work/runs/diagnostic/results.csv').read_text()
print('EPOCH_CSV_BEGIN\n'+csv+'EPOCH_CSV_END')
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
arr = np.genfromtxt(io.StringIO(csv),delimiter=',',names=True)
arr = np.atleast_1d(arr)
fig, axes = plt.subplots(1,2,figsize=(10,4))
for key,label in [('trainbox_loss','box'),('traincls_loss','class'),('traindfl_loss','DFL')]:
    axes[0].plot(arr['epoch'],arr[key],label=label)
axes[0].set(title=f'{epochs} epochs at {train_size}px: training loss',xlabel='Epoch',ylabel='Loss')
axes[0].legend()
axes[1].plot(arr['epoch'],arr['metricsmAP50B'],label='val mAP50')
axes[1].plot(arr['epoch'],arr['metricsmAP5095B'],label='val mAP50-95')
axes[1].set(title='Development validation (4 images)',xlabel='Epoch',ylabel='mAP',ylim=(0,1))
axes[1].legend()
fig.tight_layout()
buf=io.BytesIO()
fig.savefig(buf,format='png',dpi=130)
assert len(buf.getvalue()) < 1000000
print('PLOT_BASE64_BEGIN\n'+base64.b64encode(buf.getvalue()).decode()+'\nPLOT_BASE64_END')
