Editor/Inference/SamPoseHead.cs
// C# adaptation of SAM 3D Body's MHR pose head. See LICENSE-SAM and THIRD_PARTY_NOTICES.md.
// Copyright (c) Meta Platforms, Inc. and affiliates.
using System;
using System.Threading;

namespace HumanoidMocapX.Inference;

/// <summary>Runs a body/hand head through the complete MHR body and keypoint model.
/// Owns reusable output storage. A shared body may be used sequentially by both heads.</summary>
internal sealed class SamPoseHead
{
    readonly SamPredictionHead prediction;
    readonly SamMhrParameters parameters;
    readonly SamKeypoints keypoints;
    readonly MhrBody body;
    readonly float[] raw = new float[SamMhrParameters.Predictions], model = new float[SamMhrParameters.ModelParameters];
    readonly float[] vertices = new float[SamKeypoints.Vertices * 3], states = new float[SamKeypoints.Joints * MhrState.Elements];
    readonly float[] points = new float[SamKeypoints.Count * 3], expressions = new float[SamMhrParameters.Expressions];
    internal ReadOnlySpan<float> Prediction => raw;
    internal ReadOnlySpan<float> Model => model;
    internal ReadOnlySpan<float> Keypoints => points;
    /// <summary>Posed vertices in MHR units. When the shared body is restricted (as
    /// <see cref="HumanoidMocapX.EditorTools.SamFeatures"/> does), only <see cref="MappedVertices"/>
    /// carry pose corrections; read other vertices only from an unrestricted body.</summary>
    internal ReadOnlySpan<float> Vertices => vertices;
    internal ReadOnlySpan<float> JointStates => states;

    internal SamPoseHead(SamPredictionHead prediction, SamMhrParameters parameters, SamKeypoints keypoints, MhrBody body)
    {
        ArgumentNullException.ThrowIfNull(prediction); ArgumentNullException.ThrowIfNull(parameters);
        ArgumentNullException.ThrowIfNull(keypoints); ArgumentNullException.ThrowIfNull(body);
        if (prediction.Parameters != raw.Length || body.Vertices != SamKeypoints.Vertices || body.Joints != SamKeypoints.Joints
            || body.ModelParameters != model.Length || body.Identities != SamMhrParameters.Shapes || body.Expressions != expressions.Length)
            throw new ArgumentException("SAM pose head requires the released MHR body dimensions.");
        this.prediction = prediction; this.parameters = parameters; this.keypoints = keypoints; this.body = body;
    }

    /// <summary>Vertices that can affect this head's keypoints; see <see cref="SamKeypoints.MappedVertices"/>.</summary>
    internal int[] MappedVertices() => keypoints.MappedVertices();

    /// <summary>Updates predictions, model parameters and camera-space keypoints.
    /// Vertices/joint states retain MHR units for subsequent hand merging. Discard
    /// outputs after an exception; returned spans expire on the next invocation.</summary>
    internal void Run(ReadOnlySpan<float> token, CancellationToken cancellation = default)
    {
        prediction.Run(token, raw, cancellation); parameters.Run(raw, model, cancellation);
        // The released head suppresses facial predictions before evaluating MHR.
        body.Run(raw.AsSpan(SamMhrParameters.ShapeOffset, SamMhrParameters.Shapes), model, expressions, vertices, states, cancellation);
        keypoints.Run(vertices, states, points, cancellation);
    }
}