Nodes/ComfyUI-SplatKit/SphereSfM Dataset (Dual-Res: low-res SfM + 8K faces)
ComfyUI Node

SphereSfM Dataset (Dual-Res: low-res SfM + 8K faces)

SfM on the cheap frames, train on the 8K ones — dual-res SphereSfM, explained

By mickmumpitz·Created about a month ago·Updated about 19 hours ago· 17
SphereSfM Dataset (Dual-Res: low-res SfM + 8K faces)
  • pano_frames_1
  • pano_frames_2
  • pano_frames_3
  • pano_frames_4
  • model_dir
  • num_images
  • num_points
hires_dir
output_namemy_scene
matcher_typeexhaustive
on_splitstop
face_size0
max_num_features8192
peak_threshold0.0066
edge_threshold10.00
max_num_matches32768
filter_max_reproj_error4.00
filter_min_tri_angle1.50
init_min_tri_angle4.00
init_min_num_inliers30
init_max_forward_motion1.00
image_ordercamera_major
hires_glob*.png
frame_stride1
max_frames0
hires_1
hires_2
hires_3
hires_4
cleanup_hires_sourcefalse

If you've built a splat dataset from a panorama, you know the tension: structure-from-motion is expensive, but the training images are only as sharp as the frames you fed SfM. The dual-res variant of SphereSfM Dataset is the author's answer to "why not do both". It runs SfM on low-res equirect frames and reprojects the trainable cube faces from 8K panoramas on disk. You get cheap exhaustive matching and full-resolution training images, which is exactly the pipeline the main 1_generate-dataset-hires.json workflow uses.

The reasoning is sound and worth internalizing: SPHERE camera poses are angular, so they're effectively resolution-independent. Feature extraction, matching, and bundle adjustment don't need 8K - they need enough pixels to find corners. So this node spends the cheap frames on posing the scene and the expensive ones only where they actually matter: the pinhole faces your trainer trains on.

How it works

pano_frames_1..4 are the low-res equirect trajectories (say the raw 1440×720 WAN panoramas - a few GB for a couple hundred frames, so they fit fine in a ComfyUI tensor). The hi-res set is where it gets clever: hires_dir points at a folder of matching 8K equirects (e.g. <dataset>/panoramas_upscaled), read frame-by-frame from disk, never tensored - which is what avoids the OOM you'd hit trying to cram 122 GB of 8K into VRAM. After the low-res solve, the SPHERE camera is rescaled to the 8K grid and sphere_cubic_reprojecer samples the sharp source.

Two ways to feed the hi-res side:

  • hires_dir + hires_glob - a folder of matching hi-res files. Sorted order and count must match the wired frames 1:1. Thin with this node's frame_stride, not the loader's, so both sides stay aligned.
  • hires_1..hires_4 - wire a HiRes Composite's hires_manifest per trajectory. That carries the exact 8K file list and overrides hires_dir/hires_glob entirely.

Leave hires_dir empty and it degrades to a plain single-res SphereSfM run - same node, no special setup.

Inputs that matter

  • pano_frames_1 (required) - the equirect trajectory SfM poses.
  • output_name - dataset folder under ComfyUI/output or an absolute path.
  • matcher_type - exhaustive is the default here (unlike the base node) because linking non-adjacent trajectories into one model is the whole point; sequential only matches temporally adjacent frames.
  • on_split - stop (default) raises with a per-model breakdown if SfM forms more than one disconnected model, so you see that your trajectories didn't fuse instead of silently training on the biggest one. largest reprojects the biggest model anyway (legacy behavior).
  • face_size - up to 8192 px; 0 = COLMAP default scaled from the rescaled 8K SPHERE camera (full detail).
  • frame_stride / max_frames - thin the low-res frames and the matching hi-res files together, keeping them in lockstep.
  • cleanup_hires_source - leave off unless you're done iterating. On, it deletes each consumed 8K file from its original folder only after a same-size copy is confirmed staged in the dataset's _spheresfm_work/equirect_hires. Worth knowing: if both are on the same volume they're hardlinks, so it frees a directory listing but ~no disk.

Outputs are the same trio as the base node: model_dir, num_images, num_points.

Install & gotchas

Installation is the pack-wide one - clone, pip install requirements.txt, restart, let colmap_sphere.exe auto-download on first run. You supply the WAN 2.1 i2v checkpoint and the converted Matrix-3D pano LoRA.

The two failure modes that bite people:

  • Count mismatch. If the hi-res folder doesn't line up 1:1 with the low-res frames, the node errors. That's why striding happens here, not in an image loader - the loader can't thin both sides together.
  • on_split fires. It means your trajectories didn't share enough view to fuse into one model. The fix is more overlap / more shared content between trajectories, or a lower init_min_tri_angle - not largest, which silently throws away everything outside the biggest model.

The underlying SfM rules still apply: real parallax, textured scene, and "No good initial image pair found" means lower init_min_tri_angle (the default here is already 4°, well below COLMAP's 16°). If your splats look soft, check the trainer's point cap - dual-res easily produces a few million points, and a low default cap (1M) will bottleneck the reconstruction and hide the resolution gain.

CategorySplatKit

Inputs (27)

NameTypeDefaultDescription
pano_frames_1IMAGEEquirect trajectory 1 (the frames SfM poses).
hires_dirSTRINGOPTIONAL folder of matching hi-res equirects (e.g. <dataset>/panoramas_upscaled). Sorted order AND COUNT must match the frames wired in above, 1:1 (thin with this node's frame_stride, not the loader's). LEAVE EMPTY for a plain single-res SphereSfM run -- the cube faces are then reprojected from the posed frames.
output_nameSTRINGmy_sceneDataset folder under ComfyUI/output (or an absolute path).
pano_frames_2optIMAGEOptional low-res trajectory 2.
pano_frames_3optIMAGEOptional low-res trajectory 3.
pano_frames_4optIMAGEOptional low-res trajectory 4.
matcher_typeoptCOMBOexhaustiveexhaustive matches ALL pairs (links non-adjacent trajectories); sequential only matches temporally adjacent frames.
on_splitoptCOMBOstopstop = raise with the per-model breakdown if >1 model forms; largest = reproject the biggest model anyway (legacy behaviour).
face_sizeoptINT00–8192Output cube-face size in px; 0 = COLMAP default (scaled from the rescaled 8K SPHERE camera -> full detail).
max_num_featuresoptINT8192512–65536
peak_thresholdoptFLOAT0.00660–1
edge_thresholdoptFLOAT10.001–100
max_num_matchesoptINT327681024–262144
filter_max_reproj_erroroptFLOAT4.000.5–32
filter_min_tri_angleoptFLOAT1.500.1–30
init_min_tri_angleoptFLOAT4.000.5–30
init_min_num_inliersoptINT3010–500
init_max_forward_motionoptFLOAT1.000.1–1
image_orderoptCOMBOcamera_major2 options: camera_major, frame_major
hires_globoptSTRING*.pngGlob for the hi-res files inside hires_dir.
frame_strideoptINT11–100Use every Nth frame for SfM. Stride HERE, not in the image loader: this thins the low-res frames and the matching hi-res files together, so the two sets stay aligned 1:1.
max_framesoptINT00–2000Cap the frame count after striding (0 = no cap). Frames are picked evenly across the strided clip.
hires_1optSTRINGhires_manifest from the HiRes Composite feeding pano_frames_1. Wire it and hires_dir/hires_glob are ignored (dual-res on).
hires_2optSTRINGhires_manifest for pano_frames_2.
hires_3optSTRINGhires_manifest for pano_frames_3.
hires_4optSTRINGhires_manifest for pano_frames_4.
cleanup_hires_sourceoptBOOLEANfalseAfter SfM succeeds, DELETE each consumed hi-res equirect (the HiRes Composite's frames/*.png, several GB per trajectory) from its ORIGINAL folder -- only once a byte-identical-size copy has been confirmed staged in this dataset's _spheresfm_work/equirect_hires (what repair/re-reprojection actually read from). OFF (default): keep everything, nothing is deleted -- safest, and required if you plan to re-run this node with a different frame_stride/ max_frames (a wider selection needs files this pass didn't keep). ON: frees the frames/ folder's disk once you're done iterating on THIS dataset. Only files this run actually used are touched; nothing outside hires_dir is ever removed.

Outputs (3)

NameTypeDescription
model_dirSTRING
num_imagesINT
num_pointsINT