Code/FlowMotion.cs
using System;
using System.Linq;
namespace HumanoidMocapX;
/// <summary>Numeric SMPL-H motion in metres and Y-up coordinates.</summary>
public sealed class FlowMotion
{
public const int JointCount = 52;
public const int MaximumFrames = 108000;
public double Fps { get; set; } = 30;
public double StartTime { get; set; }
public string Video { get; set; } = "";
public string Name { get; set; } = "capture";
public FlowModel Model { get; set; } = FlowModel.Latest;
public int[] Parents { get; set; } = Array.Empty<int>();
public float[][] Rest { get; set; } = Array.Empty<float[]>();
public float[][] Poses { get; set; } = Array.Empty<float[]>();
public float[][] Translations { get; set; } = Array.Empty<float[]>();
public float[][] Contacts { get; set; } = Array.Empty<float[]>();
const int ContactCount = 4;
static readonly int[] SmplParents = {
-1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 12, 13, 14, 16, 17, 18, 19,
20, 22, 23, 20, 25, 26, 20, 28, 29, 20, 31, 32, 20, 34, 35,
21, 37, 38, 21, 40, 41, 21, 43, 44, 21, 46, 47, 21, 49, 50
};
/// <summary>The supported SMPL-H parent indices, with -1 for the pelvis.</summary>
public static ReadOnlySpan<int> JointParents => SmplParents;
/// <summary>Rejects malformed shapes, non-finite data and unsupported SMPL-H skeletons.</summary>
public void Validate()
{
if (Model is not (FlowModel.Base or FlowModel.Latest)) throw new FormatException("Unknown FlowHMR model.");
if (!double.IsFinite(Fps) || Fps != 30 || !double.IsFinite(StartTime) || StartTime < 0)
throw new FormatException("FlowHMR requires 30 fps and a finite, non-negative start time.");
if (Parents is null || !Parents.SequenceEqual(SmplParents))
throw new FormatException("Expected the 52-joint SMPL-H hierarchy.");
CheckRows(Rest, JointCount, 3, nameof(Rest));
if (Poses is null || Poses.Length is < 1 or > MaximumFrames)
throw new FormatException("FlowHMR motion is empty or exceeds the frame budget.");
CheckRows(Poses, Poses.Length, JointCount * 3, nameof(Poses));
CheckRows(Translations, Poses.Length, 3, nameof(Translations));
if (Contacts is null) throw new FormatException("Foot-contact predictions cannot be null.");
if (Contacts.Length == 0) return;
CheckRows(Contacts, Poses.Length, ContactCount, nameof(Contacts));
if (Contacts.Any(row => row.Any(p => p < 0 || p > 1)))
throw new FormatException("Foot-contact probabilities must lie between zero and one.");
}
static void CheckRows(float[][] values, int rows, int columns, string name)
{
if (values is null || values.Length != rows || values.Any(row => row is null
|| row.Length != columns || row.Any(v => !float.IsFinite(v))))
throw new FormatException($"Invalid {name}: expected {rows} rows of {columns} finite numbers.");
}
}