SphereSfM Dataset (Dual-Res: low-res SfM + 8K faces)
SfM on the cheap frames, train on the 8K ones — dual-res SphereSfM, explained
- pano_frames_1
- pano_frames_2
- pano_frames_3
- pano_frames_4
- model_dir
- num_images
- num_points
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'sframe_stride, not the loader's, so both sides stay aligned.hires_1..hires_4- wire a HiRes Composite'shires_manifestper trajectory. That carries the exact 8K file list and overrideshires_dir/hires_globentirely.
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 underComfyUI/outputor an absolute path.matcher_type-exhaustiveis the default here (unlike the base node) because linking non-adjacent trajectories into one model is the whole point;sequentialonly 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.largestreprojects 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_splitfires. 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 lowerinit_min_tri_angle- notlargest, 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.
Inputs (27)
| Name | Type | Default | Description |
|---|---|---|---|
| pano_frames_1 | IMAGE | Equirect trajectory 1 (the frames SfM poses). | |
| hires_dir | STRING | OPTIONAL 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_name | STRING | my_scene | Dataset folder under ComfyUI/output (or an absolute path). |
| pano_frames_2opt | IMAGE | Optional low-res trajectory 2. | |
| pano_frames_3opt | IMAGE | Optional low-res trajectory 3. | |
| pano_frames_4opt | IMAGE | Optional low-res trajectory 4. | |
| matcher_typeopt | COMBO | exhaustive | exhaustive matches ALL pairs (links non-adjacent trajectories); sequential only matches temporally adjacent frames. |
| on_splitopt | COMBO | stop | stop = raise with the per-model breakdown if >1 model forms; largest = reproject the biggest model anyway (legacy behaviour). |
| face_sizeopt | INT | 00–8192 | Output cube-face size in px; 0 = COLMAP default (scaled from the rescaled 8K SPHERE camera -> full detail). |
| max_num_featuresopt | INT | 8192512–65536 | — |
| peak_thresholdopt | FLOAT | 0.00660–1 | — |
| edge_thresholdopt | FLOAT | 10.001–100 | — |
| max_num_matchesopt | INT | 327681024–262144 | — |
| filter_max_reproj_erroropt | FLOAT | 4.000.5–32 | — |
| filter_min_tri_angleopt | FLOAT | 1.500.1–30 | — |
| init_min_tri_angleopt | FLOAT | 4.000.5–30 | — |
| init_min_num_inliersopt | INT | 3010–500 | — |
| init_max_forward_motionopt | FLOAT | 1.000.1–1 | — |
| image_orderopt | COMBO | camera_major | 2 options: camera_major, frame_major |
| hires_globopt | STRING | *.png | Glob for the hi-res files inside hires_dir. |
| frame_strideopt | INT | 11–100 | Use 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_framesopt | INT | 00–2000 | Cap the frame count after striding (0 = no cap). Frames are picked evenly across the strided clip. |
| hires_1opt | STRING | hires_manifest from the HiRes Composite feeding pano_frames_1. Wire it and hires_dir/hires_glob are ignored (dual-res on). | |
| hires_2opt | STRING | hires_manifest for pano_frames_2. | |
| hires_3opt | STRING | hires_manifest for pano_frames_3. | |
| hires_4opt | STRING | hires_manifest for pano_frames_4. | |
| cleanup_hires_sourceopt | BOOLEAN | false | After 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)
| Name | Type | Description |
|---|---|---|
| model_dir | STRING | — |
| num_images | INT | — |
| num_points | INT | — |