pose-to-video
Version:
A library to convert pose estimation data from a custom binary format to a video.
145 lines (129 loc) • 3.49 kB
JavaScript
// @ts-check
const {
/* types only */
} = require("./types");
/**
* Base class for pose rendering
*/
class PoseRenderer {
/**
* @param {import("./types").Viewer} viewer
*/
constructor(viewer) {
this.viewer = viewer;
}
/**
* @param {number} v
*/
x(v) {
const n = v * (this.viewer.width - 2);
return n / this.viewer.pose.header.width;
}
/**
* @param {number} v
*/
y(v) {
const n = v * this.viewer.height;
return n / this.viewer.pose.header.height;
}
/**
* @param {import("./types").PosePointModel} joint
* @returns {boolean}
*/
isJointValid(joint) {
return joint.C !== undefined && joint.C > 0;
}
/**
* @param {number} i
* @param {import("./types").PosePointModel} joint
* @param {import("./types").RGBColor} color
* @param {number} [avgZ]
*/
renderJoint(i, joint, color, avgZ) {
throw new Error("renderJoint() must be implemented in subclass");
}
/**
* @param {import("./types").PosePointModel[]} joints
* @param {import("./types").RGBColor[]} colors
* @param {string} name
*/
renderJoints(joints, colors, name) {
joints = joints.filter(this.isJointValid.bind(this));
return joints.map((joint, i) =>
this.renderJoint(
i,
joint,
colors[i % colors.length],
name === "LEFT_HAND_LANDMARKS" || name === "RIGHT_HAND_LANDMARKS"
? 20
: undefined
)
);
}
/**
* @param {import("./types").PosePointModel} from
* @param {import("./types").PosePointModel} to
* @param {import("./types").RGBColor} color
*/
renderLimb(from, to, color) {
throw new Error("renderLimb() must be implemented in subclass");
}
/**
* @param {import("./types").PoseLimb[]} limbs
* @param {import("./types").PosePointModel[]} joints
* @param {import("./types").RGBColor[]} colors
* @param {string} name
*/
renderLimbs(limbs, joints, colors, name) {
const lines = limbs
.map(({ from, to }) => {
let a = joints[from];
let b = joints[to];
if (!this.isJointValid(a) || !this.isJointValid(b)) return null;
const c1 = colors[from % colors.length];
const c2 = colors[to % colors.length];
const color = {
R: (c1.R + c2.R) / 2,
G: (c1.G + c2.G) / 2,
B: (c1.B + c2.B) / 2,
};
b.Z =
b.Z &&
(name === "LEFT_HAND_LANDMARKS" || name === "RIGHT_HAND_LANDMARKS")
? b.Z + 5
: b.Z;
return { from: a, to: b, color, z: ((a.Z || 0) + (b.Z || 0)) / 2 };
})
.filter((line) => line !== null);
return lines
.filter(Boolean)
.sort((a, b) => b.z - a.z)
.map(({ from, to, color }) => this.renderLimb(from, to, color));
}
/**
* @param {import("./types").PoseBodyFrameModel} frame
*/
renderFrame(frame) {
return frame.people.map((person) =>
this.viewer.pose.header.components.map((component) => {
const joints = person[component.name];
return [
this.renderJoints(joints, component.colors, component.name),
this.renderLimbs(
component.limbs,
joints,
component.colors,
component.name
),
];
})
);
}
/**
* @param {import("./types").PoseBodyFrameModel} frame
*/
render(frame) {
throw new Error("render() must be implemented in subclass");
}
}
module.exports = PoseRenderer;