Editor/YoloCheckpoint.cs
using System;
using System.IO;
using System.Threading;
using HumanoidMocapX.Inference;

namespace HumanoidMocapX.EditorTools;

/// <summary>Loads YOLOX-L tensors with the shared non-executing checkpoint reader.
/// Call off the editor thread; verify the downloaded file before loading it.</summary>
internal static class YoloCheckpoint
{
    internal static YoloFilter[] Load(string path, YoloNetwork network, CancellationToken cancellation = default)
    {
        cancellation.ThrowIfCancellationRequested();
        using var checkpoint = new TorchCheckpoint(path, "model");
        var result = new YoloFilter[network.Nodes.Length];
        for (var index = 0; index < network.Nodes.Length; index++)
        {
            cancellation.ThrowIfCancellationRequested();
            var node = network.Nodes[index];
            if (node.Operation != YoloOperation.Convolution) continue;
            result[index] = LoadFilter(checkpoint, node, network.Nodes[node.First].Channels, cancellation);
        }
        return result;
    }

    internal static YoloFilter LoadFilter(TorchCheckpoint checkpoint, YoloNode node, int inputs, CancellationToken cancellation)
    {
        var prefix = node.Weight;
        var weights = Read(prefix + (node.Normalize ? ".conv.weight" : ".weight"), node.Channels, inputs, node.Kernel, node.Kernel);
        var bias = node.Normalize ? YoloWeights.Fold(weights,
            Read(prefix + ".bn.weight", node.Channels), Read(prefix + ".bn.bias", node.Channels),
            Read(prefix + ".bn.running_mean", node.Channels), Read(prefix + ".bn.running_var", node.Channels), cancellation)
            : Read(prefix + ".bias", node.Channels);
        return new(weights, bias);

        float[] Read(string name, params int[] shape)
        {
            if (!checkpoint.Tensors.TryGetValue(name, out var tensor) || !tensor.Shape.AsSpan().SequenceEqual(shape))
                throw new InvalidDataException("Missing or incorrectly shaped YOLOX-L tensor: " + name);
            var values = checkpoint.ReadFloat(name, cancellation);
            foreach (var value in values)
                if (!float.IsFinite(value)) throw new InvalidDataException("Non-finite YOLOX-L tensor: " + name);
            return values;
        }
    }
}