InferenceWorker/BodyCapture.cs
using HumanoidMocap.Mapping;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using HumanoidMocap.Editor;
using HumanoidMocap.Inference;

namespace HumanoidMocap.Worker;

/// <param name="HorizontalFov">The recording lens in degrees when the camera wrote it; null assumes GVHMR's default (focal = image diagonal).</param>
public sealed record BodyCaptureRequest(string Video,string Models,string Output,double Start,double End,GvhmrDecoder.Box? PersonCrop=null,float? HorizontalFov=null);
public static class BodyCapture
{
    sealed class FrameState
    {
        public double Time { get; set; }
        public float[]? Observations { get; set; }
        public float[]? ImageFeatures { get; set; }
        public PersonDetector.Detection[]? Detections { get; set; }
        public PersonCropTrack.Sample? Person { get; set; }
        /// <summary>Left then right finger sample; null entries are hands that were not reconstructed.</summary>
        public BodyHandTracks.Sample?[]? Hands { get; set; }
    }
    sealed class State
    {
        public string Key { get; set; }="";
        public string Status { get; set; }="preparing";
        public string? Error { get; set; }
        public List<FrameState> Frames { get; set; }=new();
        public Dictionary<string,double> Seconds { get; set; }=new();
        public long PeakRamBytes { get; set; }
        public CameraMotionCheck.Result? CameraMotion { get; set; }
        public string? CameraMotionVersion { get; set; }
        public CameraRotationTrack.Result? CameraRotation { get; set; }
        public string? CameraRotationVersion { get; set; }
    }
    /// <summary>The automatic track has no subject at <see cref="Time"/>. <see cref="LastSeen"/> is null when none was found yet.</summary>
    sealed class SubjectLost(string message,double rangeStart,double time,double? lastSeen,List<FrameState> frames):IOException(message)
    {
        public double RangeStart=>rangeStart;public double Time=>time;public double? LastSeen=>lastSeen;public List<FrameState> Frames=>frames;
    }
    public const string PartlyVisiblePrefix="Performer not in view for the whole video";
    /// <summary>The shortest stretch with the performer in view that is still worth capturing.</summary>
    public const double MinimumVisibleSeconds=1;
    /// <summary>Captures the range; when the performer enters late or leaves the picture, captures the part
    /// where they are in view and says so, rather than refusing the whole video.</summary>
    public static string Run(BodyCaptureRequest request,CancellationToken cancellation,Action<string>? progress=null)
    {
        string? note=null;List<FrameState>? seed=null;
        for(var attempt=0;;attempt++)
        {
            try{return RunRange(request,cancellation,progress,seed,note);}
            catch(SubjectLost lost) when(request.PersonCrop is null&&attempt<3)
            {
                if(lost.LastSeen is double seen)
                {
                    if(seen-lost.RangeStart<MinimumVisibleSeconds)throw;
                    note=FormattableString.Invariant($"{PartlyVisiblePrefix}: they could not be followed from {lost.Time:F2} s, so the capture ends at {seen:F2} s. {note}").TrimEnd();
                    progress?.Invoke(FormattableString.Invariant($"Performer left the picture at {lost.Time:F1} s; capturing up to there"));
                    // Keep the 2D poses already found; the shorter range is a job of its own.
                    seed=lost.Frames.Where(f=>f.Time<=seen+.0001&&f.Observations is not null&&f.Person is not null).ToList();
                    request=request with{Start=lost.RangeStart,End=seen+.0001};
                }
                else
                {
                    progress?.Invoke("Looking for where the performer enters the picture");
                    if(FirstSubjectTime(request,lost.Time,cancellation) is not double entered||request.End-entered<MinimumVisibleSeconds)throw;
                    note=FormattableString.Invariant($"{PartlyVisiblePrefix}: nobody could be followed before {entered:F2} s, so the capture starts there.");
                    seed=null;request=request with{Start=entered};
                }
            }
        }
    }
    /// <summary>First time after <paramref name="after"/>, checked about four times a second, at which the detector finds
    /// somebody and the pose model sees enough of their body to follow, which is what the tracker itself requires.</summary>
    static double? FirstSubjectTime(BodyCaptureRequest request,double after,CancellationToken cancellation)
    {
        using var detector=new PersonDetector(Path.Combine(request.Models,"person/person_detection_mediapipe_2023mar.onnx"));
        using var pose=new VisionModel(Path.Combine(request.Models,"vitpose/vitpose-h-multi-coco.pth"),VisionModel.Kind.VitPoseHeatmaps,cancellation,gpuCache:Path.Combine(request.Models,"gpu"));
        using var decoder=new WindowsVideoDecoder(request.Video);var next=after+.25;DecodedVideoFrame? frame;
        while((frame=decoder.Read(cancellation)) is not null&&frame.Time<request.End)
        {
            if(frame.Time<next)continue;next=frame.Time+.25;
            if(PersonCropTrack.Select(null,detector.DetectFollowing(frame,null,cancellation)) is not { } subject)continue;
            var crop=VideoCrop.Prepare(frame,subject.Crop);
            var joints=VideoCrop.DecodeHeatmaps(VideoCrop.AverageFlippedHeatmaps(pose.Run(crop,cancellation),pose.Run(VideoCrop.FlipImage(crop),cancellation)),subject.Crop);
            if(PersonCropTrack.Followable(joints,null,frame.Width,frame.Height))return frame.Time;
        }
        return null;
    }
    static string RunRange(BodyCaptureRequest request,CancellationToken cancellation,Action<string>? progress,List<FrameState>? seed,string? visibilityNote)
    {
        if(!double.IsFinite(request.Start+request.End)||request.Start<0||request.End<=request.Start)throw new ArgumentException("Select a finite non-empty video range.");
        var metadata=Mp4Metadata.Read(request.Video);var captureTimes=metadata.CaptureTimes;
        if(captureTimes.Count(t=>t>=request.Start&&t<request.End) is <1 or >1800)throw new ArgumentException("Select between one and 1,800 frames.");
        // Edited footage: capture the first shot of at least half a second, not a subject followed across a cut.
        string? shotNote=null;
        if(request.PersonCrop is null)
        {
            progress?.Invoke("Checking the footage for cuts");
            for(var guard=0;guard<64;guard++)
            {
                var selected=captureTimes.Where(t=>t>=request.Start&&t<request.End).ToArray();
                if(ShotCutDetector.FirstCut(request.Video,selected,cancellation) is not int cut)break;
                var shotSeconds=selected[cut]-selected[0];
                if(shotSeconds>=.5){shotNote=FormattableString.Invariant($"{ShotCutDetector.Prefix} at {selected[cut]:F2} s. Only the shot from {selected[0]:F2} s up to the cut was captured; trim the video to capture another shot.");request=request with{End=selected[cut]};break;}
                request=request with{Start=selected[cut]};
            }
        }
        // Without a lens from the camera, estimate it from the picture (see MogeLens); the image-diagonal
        // assumption is the last resort. Done before the cache key so the key records the lens used.
        string? lensNote=null;
        if(request.HorizontalFov is null)
        {
            var mogePath=Path.Combine(request.Models,"moge/moge-2-vits-normal.pt");
            if(File.Exists(mogePath))
            {
                try
                {
                    progress?.Invoke("Estimating the camera lens");
                    var range=captureTimes.Where(t=>t>=request.Start&&t<request.End).ToArray();
                    if(range.Length>0&&MogeLens.EstimateHorizontalFov(mogePath,request.Video,range,metadata.Width,metadata.Height,5,cancellation) is float estimated)
                    {
                        estimated=MathF.Round(estimated,1);request=request with{HorizontalFov=estimated};
                        lensNote=FormattableString.Invariant($"Lens: estimated from the picture as a {estimated:F0} degree horizontal field of view (MoGe-2), used for depth and travel.");
                    }
                }
                catch(OperationCanceledException){throw;}
                catch(Exception error){progress?.Invoke("Lens estimate unavailable ("+error.Message.Split('\n')[0]+"); assuming one from the picture size");}
            }
        }
        var count=captureTimes.Count(t=>t>=request.Start&&t<request.End);
        if(count<1)throw new ArgumentException("No shot of at least half a second was found in the selected range.");
        using var video=File.OpenRead(request.Video);var sourceSha=Convert.ToHexString(SHA256.HashData(video));
        var key=Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new{
            version="gvhmr-csharp-person-crop-v2"+(metadata.CaptureStride>1?"-every"+metadata.CaptureStride:""),detector=request.PersonCrop is null?PersonDetector.Version+PersonDetector.CheckpointSha256:"manual",decoder=WindowsVideoDecoder.ImplementationVersion,sourceSha,request.Start,request.End,request.PersonCrop,request.HorizontalFov,
            temporal=GvhmrTemporalNetwork.CheckpointSha256,hmr="2dcf79638109781d1ae5f5c44fee5f55bc83291c210653feead9b7f04fa6f20e",pose="50e33f4077ef2a6bcfd7110c58742b24c5859b7798fb0eedd6d2215e0a8980bc",
            // Reduced precision changes image features slightly, so it keeps its own cache.
            // The graphics-card path agrees to about four digits, not bit for bit; it keeps its own cache too.
            visionPrecision=GpuBackbone.KeySuffix??WilorModel.ChoosePrecision(),
            // Fingers need the WiLoR checkpoint; a capture made without it is a different result.
            fingers=File.Exists(Path.Combine(request.Models,"wilor/wilor_final.ckpt"))?BodyHandTracks.Version:"none"
        }))));
        var folder=Path.Combine(request.Output,key);Directory.CreateDirectory(folder);var statePath=Path.Combine(folder,"reconstruction.json");
        using var jobLock=new FileStream(Path.Combine(folder,"job.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
        // A checkpoint that is unreadable (interrupted write) or does not follow this video's sample
        // table is started again rather than failing every later attempt.
        var expected=captureTimes.Where(t=>t>=request.Start&&t<request.End).ToArray();
        State? state=null;
        try{if(File.Exists(statePath))state=JsonSerializer.Deserialize<State>(File.ReadAllText(statePath));}
        catch(JsonException){}
        if(state is null||state.Key!=key||state.Frames.Count>count||state.Frames.Where((f,i)=>Math.Abs(f.Time-expected[i])>.0005).Any())
        {
            if(state is not null)progress?.Invoke("Earlier progress for this video could not be reused; starting again");
            state=new State{Key=key};
        }
        if(state.Frames.Count==0&&seed is not null&&seed.Count<=count&&!seed.Where((f,i)=>Math.Abs(f.Time-expected[i])>.0005).Any())
            state.Frames.AddRange(seed.Select(f=>new FrameState{Time=f.Time,Observations=f.Observations,Detections=f.Detections,Person=f.Person}));
        var lastWrite=Stopwatch.StartNew();
        void Save(string status)
        {
            state.Status=status;state.PeakRamBytes=Math.Max(state.PeakRamBytes,Process.GetCurrentProcess().PeakWorkingSet64);
            File.WriteAllText(statePath+".partial",JsonSerializer.Serialize(state));File.Move(statePath+".partial",statePath,true);lastWrite.Restart();progress?.Invoke(status);
        }
        // Per-frame progress. The checkpoint holds every frame so far (megabytes by the end of a long clip),
        // and rewriting it for each frame made saving grow with the square of the clip length: on a
        // 708-frame clip it cost more time than the graphics card's inference. It is written every few
        // seconds instead; a stage end, cancellation or failure always writes it.
        void Progress(string status)
        {
            if(lastWrite.Elapsed.TotalSeconds>=5)Save(status);else{state.Status=status;progress?.Invoke(status);}
        }
        void Visit(Action<DecodedVideoFrame,int> process)
        {
            var index=0;var wanted=captureTimes.Where(t=>t>=request.Start&&t<request.End).ToArray();
            // Decoding runs a few frames ahead on its own thread, overlapping the inference below.
            foreach(var frame in PrefetchedFrames.Read(request.Video,cancellation,wanted.Length>0?wanted[0]:0))
            {
                // Follow the sample table rather than comparing decoded times with the range: the decoder
                // rounds to 100 ns, so a range that starts exactly on a frame (after a cut) would lose it.
                // Frames between the sampled ones (footage faster than the capture rate) are passed over.
                if(index>=wanted.Length)break;
                if(frame.Time<wanted[index]-.00001)continue;
                if(index>=1800)throw new InvalidDataException("Decoded range exceeds frame limit.");
                if(index>=state.Frames.Count)state.Frames.Add(new(){Time=frame.Time});
                if(Math.Abs(state.Frames[index].Time-frame.Time)>.0005)throw new InvalidDataException("Decoded timestamps differ from reconstruction checkpoint.");
                process(frame,index++);
            }
            if(index!=count)throw new InvalidDataException($"Expected {count} selected frames but decoded {index}.");
        }
        try
        {
            state.Error=null;Save("preparing");var watch=Stopwatch.StartNew();
            if(request.PersonCrop is { } manual)
            {
                if(!float.IsFinite(manual.CenterX+manual.CenterY+manual.Size)||manual.Size<=0)throw new ArgumentException("Invalid manual person crop.");
                Visit((frame,index)=>state.Frames[index].Person=new(manual,"manual crop",null));
            }
            else
            {
                // The detector finds the subject; after that each crop follows the previous
                // frame's 2D body joints, as pose trackers do. Detector-only association lost
                // a distant performer and was taken over by a nearer bystander on the kata sample.
                if(state.Frames.Count<count||state.Frames.Any(f=>f.Observations is null||f.Person is null))
                {
                    using var detector=new PersonDetector(Path.Combine(request.Models,"person/person_detection_mediapipe_2023mar.onnx"));
                    using var pose=new VisionModel(Path.Combine(request.Models,"vitpose/vitpose-h-multi-coco.pth"),VisionModel.Kind.VitPoseHeatmaps,cancellation,gpuCache:Path.Combine(request.Models,"gpu"),report:progress);
                    GvhmrDecoder.Box? followed=null;var lastSeen=double.NaN;float? span=null;
                    float[] Observe(DecodedVideoFrame frame,GvhmrDecoder.Box box)
                    {
                        var crop=VideoCrop.Prepare(frame,box);
                        return VideoCrop.DecodeHeatmaps(VideoCrop.AverageFlippedHeatmaps(pose.Run(crop,cancellation),pose.Run(VideoCrop.FlipImage(crop),cancellation)),box);
                    }
                    Visit((frame,index)=>
                    {
                        var current=state.Frames[index];
                        if(current.Observations is null||current.Person is null)
                        {
                            float[]? joints=null;var evidence="followed body joints";float? score=null;
                            if(followed is { } expected)
                            {
                                joints=Observe(frame,expected);
                                if(!PersonCropTrack.Followable(joints,span,frame.Width,frame.Height))
                                {
                                    // Fast or blurred movement: look again in a wider window before asking the detector.
                                    var wider=expected with{Size=expected.Size*1.35f};joints=Observe(frame,wider);
                                    if(!PersonCropTrack.Followable(joints,span,frame.Width,frame.Height))joints=null;else{followed=wider;evidence="widened crop";}
                                }
                            }
                            if(joints is null)
                            {
                                current.Detections=detector.DetectFollowing(frame,followed,cancellation);
                                if(PersonCropTrack.Select(followed,current.Detections) is { } subject)
                                {
                                    // The detector's crop comes from the face and hips and can be far tighter
                                    // than the body; while a subject is followed, keep a comparable size.
                                    var crop=followed is { } known?subject.Crop with{Size=Math.Clamp(subject.Crop.Size,known.Size*.85f,known.Size*1.35f)}:subject.Crop;
                                    var found=Observe(frame,crop);
                                    if(PersonCropTrack.Followable(found,span,frame.Width,frame.Height)){joints=found;followed=crop;evidence="detected";score=subject.Score;}
                                }
                            }
                            if(joints is null)
                            {
                                if(followed is not { } held||!(current.Time-lastSeen<=.5))
                                    throw new SubjectLost($"Cannot follow one person at {current.Time:F2}s. Film one clearly visible performer, whole body in frame, or select the part of the video where they are in view under Advanced.",
                                        request.Start,current.Time,double.IsNaN(lastSeen)?null:lastSeen,state.Frames);
                                // Keep the last crop briefly; the joints are still this frame's own prediction.
                                joints=Observe(frame,held);evidence="held crop";
                            }
                            current.Observations=joints;current.Person=new(followed!.Value,evidence,score);
                            if(index==0||index==count-1)VideoCrop.SaveOverlay(frame,joints,Path.Combine(folder,$"observations-{index}.png"));
                        }
                        // A held crop is not a sighting: its joints did not pass as a followable body, so they
                        // must neither move the crop nor extend how long the subject may stay unseen.
                        if(current.Person!.Evidence!="held crop"&&PersonCropTrack.FromJoints(current.Observations) is { } next)
                        {followed=PersonCropTrack.Continue(followed,next,PersonCropTrack.ConfidentJoints(current.Observations));lastSeen=current.Time;span=PersonCropTrack.Span(current.Observations,frame.Width,frame.Height)??span;}
                        Progress($"Followed person and reconstructed 2D pose {index+1}/{count}");
                    });
                    if(state.Frames[^1].Person!.Evidence=="held crop")
                    {
                        var lastFollowed=state.Frames.FindLastIndex(f=>f.Person!.Evidence!="held crop");
                        throw new SubjectLost("The performer could not be followed at the end of the video. Select the part where they are in view under Advanced.",
                            request.Start,state.Frames[lastFollowed+1].Time,lastFollowed<0?null:state.Frames[lastFollowed].Time,state.Frames);
                    }
                }
                // Final model crops come from each frame's own joints, then GVHMR's crop smoothing.
                GvhmrDecoder.Box? steady=null;
                var own=state.Frames.Select(f=>
                {
                    var crop=PersonCropTrack.FromJoints(f.Observations!) is { } box?PersonCropTrack.Continue(steady,box,PersonCropTrack.ConfidentJoints(f.Observations!)):f.Person!.Crop;
                    steady=crop;return f.Person! with{Crop=crop};
                }).ToArray();
                var track=PersonCropTrack.Stabilize(own);
                for(var i=0;i<track.Length;i++)state.Frames[i].Person=track[i];
            }
            state.Seconds["personDetectionThisRun"]=watch.Elapsed.TotalSeconds;Save("person-crops-ready");watch.Restart();
            if(state.CameraMotion is null||state.CameraMotionVersion!=CameraMotionCheck.Version)
            {
                progress?.Invoke("Checking whether the recording camera stayed still");
                using var check=new CameraMotionCheck();
                Visit((frame,index)=>check.Add(frame,state.Frames[index].Person!.Crop));
                state.CameraMotion=check.Finish();state.CameraMotionVersion=CameraMotionCheck.Version;
                state.Seconds["cameraMotionThisRun"]=watch.Elapsed.TotalSeconds;Save("camera-motion-checked");watch.Restart();
            }
            if(state.Frames.Count<count||state.Frames.Any(f=>f.Observations is null))
            {
                using var pose=new VisionModel(Path.Combine(request.Models,"vitpose/vitpose-h-multi-coco.pth"),VisionModel.Kind.VitPoseHeatmaps,cancellation,gpuCache:Path.Combine(request.Models,"gpu"),report:progress);
                Visit((frame,index)=>
                {
                    if(state.Frames[index].Observations is not null)return;
                    var box=state.Frames[index].Person!.Crop;
                    var crop=VideoCrop.Prepare(frame,box);var heatmap=VideoCrop.AverageFlippedHeatmaps(pose.Run(crop,cancellation),pose.Run(VideoCrop.FlipImage(crop),cancellation));
                    state.Frames[index].Observations=VideoCrop.DecodeHeatmaps(heatmap,box);
                    if(index==0||index==count-1)VideoCrop.SaveOverlay(frame,state.Frames[index].Observations!,Path.Combine(folder,$"observations-{index}.png"));
                    Progress($"Reconstructed 2D pose {index+1}/{count}");
                });
            }
            state.Seconds["poseThisRun"]=watch.Elapsed.TotalSeconds;Save("pose-ready");GC.Collect();GC.WaitForPendingFinalizers();watch.Restart();
            var wilorPath=Path.Combine(request.Models,"wilor/wilor_final.ckpt");
            if(File.Exists(wilorPath)&&state.Frames.Any(f=>f.Hands is null))
            {
                progress?.Invoke("Loading WiLoR for finger capture");
                using var wilor=new WilorModel(wilorPath,cancellation,gpuCache:Path.Combine(request.Models,"gpu"),report:progress);
                Visit((frame,index)=>
                {
                    var current=state.Frames[index];if(current.Hands is not null)return;
                    var hands=new BodyHandTracks.Sample?[2];
                    for(var side=0;side<2;side++)
                        if(BodyHandTracks.Region(current.Observations!,side==0,frame.Width,frame.Height) is { } box)
                            hands[side]=BodyHandTracks.Reconstruct(wilor,frame,box,side==0,cancellation);
                    current.Hands=hands;Progress($"Reconstructed fingers {index+1}/{count}");
                });
                state.Seconds["fingersThisRun"]=watch.Elapsed.TotalSeconds;Save("fingers-ready");GC.Collect();GC.WaitForPendingFinalizers();watch.Restart();
            }
            // A wrong lens scales depth-wise travel: a 20% narrower crop of the kata clip lost 10% of its 8.5 m.
            var focalLength=request.HorizontalFov is float fov&&fov is >=20 and <=150?metadata.Width/2f/MathF.Tan(fov*MathF.PI/360):MathF.Sqrt(metadata.Width*metadata.Width+metadata.Height*metadata.Height);
            if(!state.CameraMotion!.Stationary&&(state.CameraRotation is null||state.CameraRotationVersion!=CameraRotationTrack.Version))
            {
                progress?.Invoke("Following the moving camera's rotation from the background");
                using var rotation=new CameraRotationTrack(focalLength);
                Visit((frame,index)=>rotation.Add(frame,state.Frames[index].Person!.Crop,index==count-1));
                state.CameraRotation=rotation.Finish();state.CameraRotationVersion=CameraRotationTrack.Version;
                state.Seconds["cameraRotationThisRun"]=watch.Elapsed.TotalSeconds;Save("camera-rotation-followed");watch.Restart();
            }
            if(state.Frames.Any(f=>f.ImageFeatures is null))
            {
                using var hmr=new VisionModel(Path.Combine(request.Models,"hmr2/hmr2.ckpt"),VisionModel.Kind.Hmr2Features,cancellation,gpuCache:Path.Combine(request.Models,"gpu"),report:progress);
                Visit((frame,index)=>
                {
                    if(state.Frames[index].ImageFeatures is not null)return;
                    state.Frames[index].ImageFeatures=hmr.Run(VideoCrop.Prepare(frame,state.Frames[index].Person!.Crop),cancellation);Progress($"Reconstructed image features {index+1}/{count}");
                });
            }
            state.Seconds["imageFeaturesThisRun"]=watch.Elapsed.TotalSeconds;Save("image-features-ready");GC.Collect();GC.WaitForPendingFinalizers();watch.Restart();
            Save("temporal-inference");var camera=new GvhmrDecoder.Camera(focalLength,metadata.Width*.5f,metadata.Height*.5f);
            var boxes=state.Frames.Select(f=>f.Person!.Crop).ToArray();var cameras=Enumerable.Repeat(camera,count).ToArray();
            var identityCondition=Enumerable.Range(0,count).SelectMany(_=>new[]{1f,0,0,0,1,0}).ToArray();
            // A still camera, or one whose rotation could not be followed, is conditioned as not rotating.
            var followedRotation=!state.CameraMotion.Stationary&&state.CameraRotation is {Usable:true} solvedRotation&&solvedRotation.AngularVelocity6d.Length==count*6?solvedRotation.AngularVelocity6d:null;
            var conditions=GvhmrDecoder.Prepare(state.Frames.SelectMany(f=>f.Observations!).ToArray(),boxes,cameras,followedRotation??identityCondition);
            var network=new GvhmrTemporalNetwork(Path.Combine(request.Models,"gvhmr/gvhmr_siga24_release.ckpt"),cancellation);
            var prediction=network.Run(count,conditions.Observations,conditions.CliffCamera,conditions.NormalizedAngularVelocity,state.Frames.SelectMany(f=>f.ImageFeatures!).ToArray(),cancellation);
            File.WriteAllText(Path.Combine(folder,"raw-predictions.json"),JsonSerializer.Serialize(prediction));
            var rotationPath=Path.Combine(folder,BodyRefinement.CameraRotationFile);
            if(followedRotation is null)File.Delete(rotationPath);
            else File.WriteAllText(rotationPath,JsonSerializer.Serialize(new BodyRefinement.CameraRotation(followedRotation,state.CameraRotation!.RotationOnly)));
            var decoded=GvhmrDecoder.Decode(prediction.PredX,count);var translation=GvhmrDecoder.CameraTranslation(prediction.PredCam,boxes,cameras);
            var skeleton=new SmplxSkeleton(Path.Combine(request.Models,"smplx/SMPLX_NEUTRAL.npz"),cancellation);
            var motion=BodyMotionBuilder.CameraRelative(skeleton,decoded,translation,state.Frames.Select(f=>f.Time).ToArray(),Path.GetFileNameWithoutExtension(request.Video),request.Video,sourceSha,metadata.CaptureFrameRate,camera);
            if(state.Frames.All(f=>f.Hands is not null))
            {
                BodyHandTracks.Append(motion,state.Frames.Select(f=>f.Hands!).ToArray());
                motion.ModelVersion+="; "+BodyHandTracks.Version+" "+WilorModel.CheckpointSha256;
            }
            // Floor sits read as crouches by the network: mark them for the retargeter to seat the hips.
            if(SeatedDetection.Detect(motion,state.Frames.Select(f=>f.Observations!).ToArray(),prediction.StaticConfidenceLogits,focalLength,metadata.Width*.5f,metadata.Height*.5f) is float[] seatedWeights
                &&seatedWeights.Count(w=>w>=1) is var seatedFrames&&seatedFrames>0)
            {
                motion.StationaryJoints.Add(new(){Bone=motion.Bones.First(b=>b.Role==BoneRole.Hips).Name,Source=SeatedDetection.Source,Probability=seatedWeights});
                motion.Diagnostics.Add(FormattableString.Invariant($"Sitting on the floor: seated in {seatedFrames} of {count} frames. Retargeting lowers the hips onto the floor there and keeps the feet and resting hands in place."));
            }
            motion.ModelVersion+="; "+WindowsVideoDecoder.ImplementationVersion;
            motion.ModelVersion+="; "+(request.PersonCrop is null?PersonDetector.Version:"manual-person-crop");
            motion.Diagnostics.Add(request.PersonCrop is null
                ?$"Automatic single-person image crops ({PersonDetector.Version}): the detector located the subject in {state.Frames.Count(f=>f.Person!.Evidence=="detected")} frame(s), crops then followed the previous frame's 2D body joints, and {state.Frames.Count(f=>f.Person!.Evidence=="held crop")} frame(s) briefly held the last crop. Two centered five-frame crop averages follow. Crop evidence is saved separately in reconstruction.json; it is not joint confidence or camera calibration."
                :"Explicit fixed manual person crop. Automatic subject tracking was not used.");
            if(shotNote is not null)motion.Diagnostics.Add(shotNote);
            if(visibilityNote is not null)motion.Diagnostics.Add(visibilityNote);
            motion.Diagnostics.Add(lensNote??(request.HorizontalFov is float lens?FormattableString.Invariant($"Lens: the camera recorded a {lens:F0} degree horizontal field of view, used for depth and travel."):
                "Lens: not recorded by the camera; assumed from the picture size (about 53 degrees across the diagonal). Distances toward and away from the camera scale with this assumption."));
            motion.Diagnostics.Add(GpuBackbone.DeviceNote);
            if(metadata.SamplingNote is { } sampling)motion.Diagnostics.Add(sampling);
            motion.Diagnostics.Add(state.CameraMotion.Diagnostic);
            if(!state.CameraMotion.Stationary&&state.CameraRotation is not null)motion.Diagnostics.Add(state.CameraRotation.Diagnostic);
            if(followedRotation is not null)
            {
                // The builder's default note describes a still-camera conditioning that was not used here.
                motion.Diagnostics.RemoveAll(d=>d.StartsWith("Camera-relative reconstruction with identity camera-angular-velocity",StringComparison.Ordinal));
                motion.Diagnostics.Add("Camera-relative reconstruction conditioned on the followed camera rotation; camera translation has not been recovered.");
            }
            var result=Path.Combine(folder,"raw-body.hmotion");File.WriteAllText(result+".partial",motion.ToJson());File.Move(result+".partial",result,true);
            state.Seconds["temporalAndDecodeThisRun"]=watch.Elapsed.TotalSeconds;Save("complete");return result;
        }
        catch(OperationCanceledException){Save("cancelled-resumable");throw;}
        catch(Exception e){state.Error=e.Message;Save("failed-resumable");throw;}
    }
}