Skip to content

Targets and Sessions#

A target is the subject of a capture project — a building, patient, specimen, or study. Its schema is a cg.Struct, and everything captured for it lives under one directory laid out by that schema.

A target is a schema#

import capturegraph as cg


class Session(cg.Struct):
    photo: cg.Image
    notes: cg.String


class PlantJournal(cg.Struct):
    reference: cg.Image                  # target-level: persists across sessions
    sessions: cg.Map[cg.Date, Session]   # one entry per capture
    _metadata: cg.Metadata

Fields on the target struct that are not the session map persist across every session — use them for reference imagery, calibration, or one-time configuration. The session map holds the per-run data.

Sessions#

A session is one execution of the procedure. Sessions are a cg.Map[cg.Date, ...]: each is keyed by its capture instant, which encodes as a 16-hex-digit microsecond timestamp — the session id.

@cg.procedure(PlantJournal)
def journal(root):
    root.reference |= cg.CaptureImage(label="Reference (once)")
    session = root.sessions[cg.CaptureTime()]      # a new session
    session.photo &= cg.CaptureImage(label="Photo")
    session.notes &= cg.UserInputString(label="Notes")

Keying sessions by the capture time gives you, for free:

  • Chronological sorting — ids sort by capture time.
  • Mergeability — captures from different devices never collide, so folders merge by copying.
  • No overwrites — each run is its own entry.

On disk#

The schema fixes the layout. A PlantJournal target lands as:

PlantJournal/
├── .metadata/
│   ├── .thumbnail.heic
│   ├── .location.json
│   └── .next_notification.json
├── reference.heic              # target-level field
└── sessions/
    ├── 00063B40E29D696A/       # one session (Time key = 16-hex id)
    │   ├── photo.heic
    │   └── notes.json
    └── 00063B41A2C8F1B0/
        └── ...

A _-prefixed field (_metadata) hides under a leading .. See Types and Nodes for the full encoding.

Per-session collections#

A session can hold an cg.Array you append to in a loop, or its own nested struct:

class Session(cg.Struct):
    primary: cg.Image
    extras: cg.Array[cg.Image]      # zero or more, appended in a loop


@cg.procedure(PlantJournal)
def journal(root):
    session = root.sessions[cg.CaptureTime()]
    session.primary &= cg.CaptureImage(label="Primary")
    with cg.while_repeat(cg.UserInputBool(label="Another?"), session.extras) as item:
        item &= cg.CaptureImage(label="Extra")

Array entries store under zero-padded indices (extras/00000.heic, extras/00001.heic).

See Also#