Skip to content

Repository files navigation

Photogram

Turn a folder of images into a 3D model from the command line, using Apple's Object Capture (PhotogrammetrySession). A reusable Swift engine (PhotogrammetryKit) plus a photogram CLI. A SwiftUI drop-target app may follow — the engine is built to be shared.

photogram convert ./shoe-photos shoe.usdz --mode object --detail full

Requirements

  • macOS 15+ on Apple silicon with enough RAM/GPU (uses RealityKit's PhotogrammetrySession). Run photogram doctor to confirm.
  • Swift 6 / Xcode 26 toolchain to build.

Install

git clone https://github.com/saidutt46/photogram.git
cd photogram
make install          # release build → ~/.local/bin/photogram (+ man page)

~/.local/bin is already on PATH on this machine. Verify:

photogram doctor
man photogram

Or just run from the source tree without installing:

swift run photogram doctor

Quick start

# Inspect a folder before committing to a 2-minute run
photogram info ./shoe-photos

# Single object, high quality
photogram convert ./shoe-photos shoe.usdz --mode object --detail full --ordering sequential

# A room / scene
photogram convert ./room-photos room.usdz --mode area --detail medium

convert is the default subcommand, so the word is optional:

photogram ./shoe-photos shoe.usdz

If you omit the output name it defaults to <folder-name>.usdz. Use a .obj extension to emit OBJ instead of USDZ.

Commands

Command What it does
convert <input> [output] Reconstruct a model from a folder of images. (default)
info <input> Image count, format breakdown, and whether the folder is usable.
doctor Whether this Mac supports Object Capture, plus engine limits.

All three accept --json for machine-readable output.

convert options

Flag Maps to Notes
--mode masking + bounding box combo object (single item: masked + cropped) vs area (scene/room: no mask, full extent). Default object.
--detail Request.Detail preview | reduced | medium | full | raw | custom. Default medium.
--ordering Configuration.sampleOrdering unordered | sequential. sequential is faster when frames were captured in a path.
--sensitivity Configuration.featureSensitivity normal | high. high helps smooth / low-texture objects.
--mesh-primitive Configuration.meshPrimitive triangle | quad.
--object-masking / --no-object-masking isObjectMaskingEnabled Override the --mode default.
--ignore-bounding-box / --no-ignore-bounding-box ignoreBoundingBox Override the --mode default.
--checkpoint-dir <dir> checkpointDirectory Enables resume after interruption.
--max-polygons <n> custom maximumPolygonCount --detail custom only.
--texture-size <size> custom maximumTextureDimension oneK…sixteenK. Custom only.
--texture-maps <maps…> custom outputTextureMaps diffuse normal roughness displacement ambientOcclusion. Custom only.
--texture-format <fmt> / --jpeg-quality <q> custom textureFormat png | jpeg. Custom only.
--roi <6 floats> Request.Geometry.bounds Crop box minX minY minZ maxX maxY maxZ (meters).
--force / -f Overwrite the output if it exists.
--dry-run Validate inputs and print the resolved config without running.
--json Emit JSON progress on stdout.
--quiet / -q Print only the final path and errors.

Object vs area mode

PhotogrammetrySession has no literal mode switch — --mode resolves to a combination of two knobs that mirrors how iOS Object Capture treats each case:

--mode object masking bounding box
object on (segment from background) kept (crop to the object)
area off ignored (reconstruct the whole scene)

Use object for a single item shot from all sides; use area for rooms and scenes. Override either knob individually with --[no-]object-masking / --[no-]ignore-bounding-box.

What inputs it uses

Photos only — there is no mesh input. The input type (PhotogrammetrySample) carries an image plus an optional depth map, gravity vector, and object mask. Photos captured by Apple's iOS pipeline embed depth (LiDAR) + gravity in each HEIC, which PhotogrammetrySession reads automatically to improve real-world scale and accuracy. Plain photos work too; they just lack those scale hints.

Capture tips

  • 20–40 overlapping photos, going all the way around the subject.
  • Keep the subject sharp and evenly lit; avoid motion blur and harsh shadows.
  • For a single object, isolate it on a contrasting surface and use --mode object. For a room, use --mode area.
  • Smooth or shiny / low-texture subjects: add --sensitivity high.

JSON output

With --json, convert writes one JSON object per line to stdout (progress on stderr is suppressed), suitable for scripts or a UI:

{"event":"start","images":31,"mode":"object","detail":"full","output":"/…/shoe.usdz"}
{"event":"stage","stage":"imageAlignment"}
{"event":"progress","fraction":0.42,"stage":"imageAlignment"}
{"event":"eta","secondsRemaining":120}
{"event":"inputComplete"}
{"event":"finished","output":"/…/shoe.usdz","seconds":139}

event is one of: start, stage, progress, eta, inputComplete, invalidSample, skippedSample, automaticDownsampling, finished, error. Processing stages: preProcessing, imageAlignment, pointCloudGeneration, meshGeneration, textureMapping, optimization.

Using the engine directly

import PhotogrammetryKit

let engine = PhotogrammetryEngine()
let config = ReconstructionConfig(mode: .object, detail: .full, ordering: .sequential)

for try await event in engine.reconstruct(
    inputFolder: inputURL, outputFile: outputURL, config: config) {
    switch event {
    case .stage(let s):              print("stage:", s)
    case .progress(let f):           print("progress:", f)
    case .finished(let url):         print("done:", url.path)
    default:                         break
    }
}

Troubleshooting

  • Warning … Failed to resolve reference … baked_mesh_…png — cosmetic. It comes from Apple's USD texture-baking step on a background thread; the final .usdz is complete and self-contained. Not an error.
  • doctor reports not supported — needs an Apple-silicon Mac on macOS 15+ with adequate RAM/GPU.
  • Patchy / holey result — usually coverage: more overlapping photos, and match --mode to the subject. Rooms are inherently harder than single objects.

Exit codes

0 success · 1 reconstruction or runtime failure · 64 invalid arguments / validation error (standard ArgumentParser usage code).

Development

swift build          # debug build
swift test           # unit tests (Swift Testing)
swift run photogram …
make manpage         # regenerate man/photogram.1 after CLI changes

See CLAUDE.md for architecture notes and invariants.

Roadmap

  • Core engine (PhotogrammetryKit) with full knob coverage
  • CLI: convert / info / doctor, object/area modes, JSON output
  • Tests, man page, install, docs
  • SwiftUI Mac app: drag-drop folders, live progress, job queue/history

License

MIT © 2026 Sai Dutt G.V

About

Turn a folder of images into a 3D model from the CLI, using Apple's Object Capture (PhotogrammetrySession).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages