Skip to content

Your First Procedure#

This tutorial builds a simple capture procedure with the Procedure DSL: you declare a typed schema, record the procedure against it, serialize it to JSON, and deploy it to your phone.

Prerequisites#

Step 1: Declare the Schema#

A procedure fills a schema — the typed tree your target will hold. You declare it up front with cg.Struct. Sessions are a cg.Map keyed by capture time, and _metadata reserves the positions the app and server fill.

# my_procedure.py
import capturegraph as cg


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


class Survey(cg.Struct):
    sessions: cg.Map[cg.Date, Session]
    _metadata: cg.Metadata

Because the layout is fixed before the first capture, the dataset's shape is known in advance — which is exactly what lets analysis load the whole target in one call. See Types and Nodes for every type and how it lands on disk.

Step 2: Record the Procedure#

Decorate a function with @cg.procedure(Survey). Its root argument is a cursor into the schema (cg.Procedure[cg.Path[Survey]]). Navigating the cursor and assigning nodes records the graph — the function never runs your capture logic.

@cg.procedure(Survey)
def survey(root):
    session = root.sessions[cg.CaptureTime()]          # a new session, keyed by capture time
    session.photo  &= cg.CaptureImage(label="Today's photo")
    session.notes  &= cg.UserInputString(label="Notes")
    session.rating &= cg.UserInputRating(label="Rating")
  • root.sessions[cg.CaptureTime()] adds a cg.Map entry. The key node cg.CaptureTime() produces the capture instant, which becomes the session id.
  • session.photo descends into a field inside that entry.
  • The recorder type-checks as it builds: a cg.Image field only accepts a node that produces an image, and a cg.Map[cg.Date, ...] is keyed only by a time-producing node. Mistakes fail on your laptop, not on the device.

Step 3: |= vs &=#

A captured value lands on disk by assigning it to a path with a caching operator. A plain = with a node is rejected — caching must be explicit. The two operators differ only in what happens when the file already exists:

Operator Meaning Use it for
path \|= node Cache, skip if it already exists Config and reference data — ask once, remember
path &= node Cache, overwrite Per-session data — fresh every run

A reference image or a one-time setting uses |=; the per-session photo, notes, and rating above use &=. See Recorder and Caching for the full story.

@cg.procedure(Survey)
def survey(root):
    # Ask once and remember (skipped on later sessions)
    root._metadata._location |= cg.CaptureLocation(label="Target location")

    session = root.sessions[cg.CaptureTime()]
    session.photo  &= cg.CaptureImage(label="Today's photo")
    session.notes  &= cg.UserInputString(label="Notes")
    session.rating &= cg.UserInputRating(label="Rating")

Step 4: Serialize to JSON#

A recorded procedure serializes itself with to_json() — a {schema, root, nodes} string the app executes. The schema travels inside it, so the JSON is the complete a-priori data layout.

with open("MySurvey.json", "w") as f:
    f.write(survey.to_json())

(Use survey.to_dict() for the same thing as a dict.) See The JSON Format for the wire shape.

Step 5: Deploy to Your Phone#

The app's Documents folder is shared with the Files app, so deployment is a drag-and-drop:

  1. Connect your device to your Mac and open Finder (or use the Files app on the device itself).
  2. Select your device and open the Files tab.
  3. Drag MySurvey.json into the CaptureGraph folder.
  4. Quit and relaunch the app — imports are processed at launch, when valid procedure files are moved into CaptureProcedures/.

After relaunching, the procedure appears in the target-creation procedure list as "Local - MySurvey".

Only drop valid procedure JSON files at the top level

At launch, the app sweeps its Documents root: valid procedure JSONs are imported, but any other unrecognized file at the top level is deleted — including a procedure JSON that fails to parse. Don't park CSVs, exports, or other documents there.

Broken procedures are quarantined

You can also drop your file directly into the CaptureProcedures/ folder. Files there that fail to parse (for example, referencing a node kind the app doesn't know) are not deleted — they are moved to BrokenCaptureProcedures/ alongside a <name>.json.error.txt file explaining what went wrong. Check there if your procedure doesn't show up.

Step 6: Run It#

As in Your First Capture: create a new target in the Explorer tab, select Local - MySurvey, and capture a few sessions. Each run lands in CaptureData/<your target>/sessions/<16-hex-id>/ with a photo, your notes, and a rating.

Next#

Pull that data back onto your computer in Analyzing Your Data — using the same Survey schema you wrote here.