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#
- The library installed in a virtual environment (Installation)
- The app on your device (The iOS App)
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 acg.Mapentry. The key nodecg.CaptureTime()produces the capture instant, which becomes the session id.session.photodescends into a field inside that entry.- The recorder type-checks as it builds: a
cg.Imagefield only accepts a node that produces an image, and acg.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.
(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:
- Connect your device to your Mac and open Finder (or use the Files app on the device itself).
- Select your device and open the Files tab.
- Drag
MySurvey.jsoninto the CaptureGraph folder. - 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.