Skip to content

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.

import capturegraph as cg

loc = cg.Location(latitude=42.0, longitude=-76.0, altitude_meters=100.0)

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 of T.
  • cg.Map[Key, V] — entries of V under keyable scalar keys. The key must be keyable: cg.Time, cg.String, cg.UserID, or cg.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 of cg.SceneFrame keyframes (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 one cg.Video clip 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, with PosedVideo.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 with cg.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.Date map key encodes as 16 uppercase hex digits of microseconds since the epoch — the same string used as a session id.
  • cg.Array entries 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:

import capturegraph as cg

catalog = cg.build_node_schema()   # every kind, with its settings/inputs

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-only ARKit* family (ARKitCaptureScene for sparse posed keyframes and ARKitCaptureVideo for a dense posed RGB-D video, plus their shared ARKitNewScene fallback 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 unified Constant.

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#