Types and Nodes#
A procedure fills a target — a typed tree you declare up front. The
type system (capturegraph.types, surfaced on cg) describes that tree;
the node set produces the values that land in it.
The type system#
Every type is a value: a class you can instantiate, store, and load. There are four forms.
Scalars#
A scalar is one leaf. File scalars store as a path to a file; JSON
scalars store as a small .json document.
| File scalar | Stored as | JSON scalar | Stored as |
|---|---|---|---|
cg.Image |
image file | cg.Bool |
JSON |
cg.Video |
video file | cg.Number |
JSON |
cg.Thumbnail |
image file | cg.String |
JSON |
cg.Panorama |
image file | cg.Time (alias cg.Date) |
JSON |
cg.Depth |
OpenEXR depth map | cg.Duration |
JSON |
cg.PointCloud |
PLY point cloud | cg.Location |
JSON |
cg.Mesh |
glTF/USD/OBJ mesh | cg.Angle |
JSON |
cg.ARWorldMap |
opaque ARKit blob | cg.Weather |
JSON |
cg.UserID (alias cg.Participant) |
JSON | ||
cg.Pose |
JSON | ||
cg.Exposure |
JSON | ||
cg.VideoFormat (a cg.Enum) |
JSON |
Scalars are reverse-DNS namespaced on the wire: the platform-neutral
vocabulary under edu.cornell.*, and platform-proprietary blobs under the
owning vendor (cg.ARWorldMap is com.apple.arkit.world_map — other readers
store or skip its bytes from the self-describing schema alone).
cg.Enum is a JSON scalar with a declared, closed case set — subclass it with
a wire name and uppercase str class attributes, each naming one case; the
cases become instances of the type. On the wire an enum is an ordinary scalar;
its admitted cases are part of each platform's implementation, cross-checked
by the golden wire vectors like the rest of the vocabulary.
Composites#
Three forms compose scalars into a tree:
import capturegraph as cg
class Note(cg.Struct):
photo: cg.Image
location: cg.Location
class Session(cg.Struct):
primary: cg.Image
extras: cg.Array[cg.Image] # ordered, append-only
notes: cg.Array[Note]
class Survey(cg.Struct):
reference: cg.Image
sessions: cg.Map[cg.Date, Session] # keyed by capture instant
_metadata: cg.Metadata
cg.Array[T]— an ordered list ofT.cg.Map[Key, V]— entries ofVunder keyable scalar keys. The key must be keyable:cg.Time,cg.String,cg.UserID, orcg.Bool.cg.Struct— subclass it and annotate fields with types.
cg.Metadata is a ready-made struct of the metadata positions the iOS app
and server fill (_thumbnail, _location, _next_notification).
Ready-made composites cover the recurring capture bundles: cg.FlashPair,
cg.ImagePanorama (with cg.PanoramaFrame), the aliases
cg.ImageSequence/cg.LocationSequence/cg.PoseTrack, and the two
world-anchored posed RGB-D shapes:
cg.SceneCapture— a world map plus an array ofcg.SceneFramekeyframes (image + best-effort depth + pose + exposure; the depth leaf is simply absent on a device without scene depth). A sparse, curated set of posed keyframes.cg.PosedVideo— a world map plus onecg.Videoclip with dense per-frame poses (cg.Map[cg.Time, cg.Pose], one per frame) and sparse per-frame depth (cg.Map[cg.Time, cg.Depth], only the frames a sensor reached, keyed by the same frame instant so they line up). The dense, continuous counterpart, withPosedVideo.frames()to walk the clip with each frame's pose and depth in step.
Derived spatial products (meshes, point clouds, trajectories) are reconstructed
server-side from these captures and written back as
cg.Mesh/cg.PointCloud/cg.PoseTrack values, never captured as lesser
substitutes.
Metadata positions#
Add a hidden _metadata: cg.Metadata field to a struct (a target or a
session) to reserve its three positions:
_thumbnail— a preview for the app's session list. A procedure can set it from a captured photo withcg.ConvertImageToThumbnail(image=...)._location— the target's intended capture location, used to check a user is in the right place._next_notification— when to next remind a user to capture.
A procedure may cache any of these (e.g.
session._metadata._thumbnail &= cg.ConvertImageToThumbnail(image=session.photo)),
but the reminder and location-validation positions are normally left for the
server to fill: an interceptor computes the value per user and per
request without shipping an app update. That is where reminders, solar-aware
scheduling, and location checks live — see
Targets and Interceptors and
Scheduling Captures.
Non-storable types#
cg.Path[T] and cg.Void have no place on disk. A procedure navigates the
schema with cg.Path[T] cursors and produces side effects typed cg.Void;
neither is ever written.
On disk#
The schema is the layout. A value of the Survey above lands as:
MyTarget/
├── .metadata/ # _metadata: a "_" field hides as ".name"
│ ├── .thumbnail.jpeg
│ ├── .location.json
│ └── .next_notification.json
├── reference.heic
└── sessions/ # the Map
├── 00063B40E29D696A/ # a Time key: 16-hex microseconds = session id
│ ├── primary.heic
│ ├── extras/
│ │ ├── 00000.heic # Array entries: zero-padded indices
│ │ └── 00001.heic
│ └── notes/
│ └── 00000/
│ ├── photo.heic
│ └── location.json
└── 00063B41A2C8F1B0/
└── ...
- A struct field starting with
_hides as a leading.(_metadata→.metadata). - A
cg.Time/cg.Datemap key encodes as 16 uppercase hex digits of microseconds since the epoch — the same string used as a session id. cg.Arrayentries live under zero-padded indices (00000,00001).
Because every position is fixed by the schema, sessions captured on different devices merge by copying folders, and analysis loads the whole tree with one call (see Loading Captures).
File helpers#
A loaded file scalar is its path and carries the decode helpers as methods, so analysis reads them straight off the value — no separate library, no path threaded in:
import capturegraph as cg
photo = cg.load(path, Survey).reference # a cg.Image — a Path with methods
photo.exif() # typed EXIF — a cg.Exif (.iso, .datetime_original, .gps…)
photo.pil() # a PIL image
photo.to_jpeg(dst) # / to_heif / to_png
photo.tooltip() # a small preview
clip.metadata() # a loaded cg.Video
clip.frame_at(t=1.0)
clip.thumbnail()
The node set#
Procedures are built from nodes — the constructors aggregated onto the
capturegraph root (cg); their API reference lives under
capturegraph.procedures. Every node declares a
return type as a cg.Path[T] (a position) or a stored scalar.
Browse the live catalog instead of memorizing a table:
build_node_schema() is what the management panel's node editor is built on
— that editor is currently deferred, but the catalog it exposes always
reflects the installed library.
Categories#
Nodes group by execution behavior:
- Collection — block until the user acts.
Capture*(sensors/camera),UserInput*(forms),Show*(guidance), and the iOS-onlyARKit*family (ARKitCaptureScenefor sparse posed keyframes andARKitCaptureVideofor a dense posed RGB-D video, plus their sharedARKitNewScenefallback value). The types these produce are platform-neutral — another platform would contribute the same data through its own tracker's nodes. - Process — run immediately:
Convert*, control flow (IfThenElse,ProcedureWhile,AssertBool,DoNothing), structure (ProcedureSequence,ProcedureSet,OptionalProcedure), operators (BoolNot,NumberLessThan,TimeAddInterval). - Destination — the path family and cache:
GetRoot,PathField,PathKey,PathIndex,PathKeys,PathValues,PathAppend,LoadPath,CacheProcedure, plus the unifiedConstant.
The destination nodes and Constant are recorder internals: the
recorder emits them from root.field |= ... syntax, and
they are hidden from the editor palette. You rarely name them directly.
Type checking#
The recorder type-checks as it builds: a cg.Image slot only accepts a
node that produces an image, a cg.Map[cg.Date, ...] is keyed only by a
cg.Time-producing node, and a plain = assignment (no caching operator)
is rejected. Mistakes fail on your laptop, not on the device.
See Also#
- Recorder and Caching —
|=,&=, path navigation - Targets and Sessions — schema shapes for capture
- Control Flow —
cg.do,cg.while_repeat,cg.when,cg.optional - Loading Captures —
cg.loadover a schema