𦴠SOMA NPZ β OpenPose (ControlNet)
SOMA NPZ β OpenPose, straight from 3D joints
- openpose_image
Normal OpenPose workflow: find or generate a photo of a person in the pose you want, run it through a pose estimator, hope the estimator's guess matches the pose you had in mind, feed the heatmap to a ControlNet. Three chances to lose the pose, and you need a reference image to start with.
This node deletes the middle steps. If you have exact 3D joint positions - which is what motion-generation models like SOMA-77 output - you can project them to a 2D skeleton directly. No estimator, no source photo, no pixel drift. The skeleton is mathematically what the motion actually was.
It's a niche bridge, but the logic is airtight, and it's the cleanest way to get a generated motion into the 2D stack.
How it works
It loads the NPZ - which must contain a posed_joints array shaped (T, 77, 3) - pulls the frame you asked for, and projects it orthographically from the front: x = -X, y = -Y, because SOMA's axes are X-left, Y-up, Z-forward and image coordinates go right and down. It scales that frame's joint bounding box to fill 92% of the canvas and centres it.
Then it maps the 77 SOMA joints onto COCO-18 - the standard OpenPose format - using a fixed table. The arm entries deliberately point at the actual arm joints rather than the clavicles (a classic source of skeletons that look subtly wrong), and the ear points are skipped because SOMA has no ears. Limbs and joints are drawn with the exact colour palette and limb connections from controlnet_aux, which is what OpenPose ControlNets were trained on. Get the palette wrong and the model reads the skeleton worse; get it right and it reads it natively.
If OpenCV is installed it draws with cv2 using the same filled-ellipse limb style as controlnet_aux. Without it, there's a PIL fallback - usable, slightly softer lines.
The PNG also gets written to output/openpose/soma_openpose_frame_XXXX.png, which is handy: you can eyeball exactly what the ControlNet is being handed.
Inputs and output
npz_path- required STRING, wired. This isKimodoTextToPose'snpz_pathoutput from the author's separatemelite-kimodo-nodespack.frame_index- which frame to render. The widget's minimum is 0, so it's the frame you actually mean from the front of the clip.img_size- square output size, default 768, in steps of 64. Match it to whatever your ControlNet is working at; feeding a 2048 skeleton into a 1024 generation just wastes memory.
One output: openpose_image, an IMAGE tensor. Wire it into a ControlNet Apply node with an OpenPose-family control model - or into any image node, since it's just a picture of a skeleton.
Install
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/JayDataEngineer/melite-autorig-nodes.git
Manager β "melite", restart. The pack's requirements.txt is trimesh, scipy, requests; this node needs NumPy, PIL and - optionally - pip install opencv-python for the closer visual match to controlnet_aux. No Blender, no GPU.
Where people get burned
The tooltip lies about -1. It says "-1 = the middle frame (peak motion)". The code treats a negative index as wrapping from the end, so -1 is the last frame, not the middle. In practice this is harmless, because the widget's minimum is 0 and you can't type a negative value in the UI - but if you drive this node from an API call or a primitive that bypasses the widget, don't trust the tooltip. Compute the frame you want and pass it.
Per-frame auto-scaling means the framing jumps. The scale factor comes from that frame's own joint bounding box, so a crouched frame is zoomed differently than a standing one. Render a sequence and a pose that compresses vertically will appear to grow. That's inherent to auto-fit, not a bug.
It's a hard front view. No camera angle, no perspective, no depth. A profile motion still renders as a front-facing skeleton, and a limb pointed straight at the camera collapses to a stub with no length cue. Fine for archetypal poses; poor where orientation is the point.
The NPZ has to be the right one. It needs the posed_joints key - if it's missing, the error lists the keys your file does have. Motion files from other sources won't have it.
This isn't the only way to pose a character anymore. The KB's read on 2026 tooling is that posing has largely migrated to pose LoRAs on instruction-edit models, with skeleton ControlNets as the fallback. That's a real trade: a LoRA is faster and understands "back view, arm raised", while a skeleton gives you exact, reproducible joint placement. When you need this specific frame of this specific motion, exact wins, and that's the job this node does.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| npz_path | STRING | SOMA-77 motion NPZ path (from KimodoTextToPose). Connect the STRING output. | |
| frame_index | INT | 00β100000 | Which frame to render. 0 = first frame. Use -1 for the middle frame (peak motion). |
| img_size | INT | 768256β2048 | Output image size (square). Match your ControlNet's expected resolution. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| openpose_image | IMAGE | β |