🌱 Plant Growth

A sunflower and a tulip photographed at server-scheduled slots, aligned to the first session. Drag the slider to scrub time.

Plant timelapse frame
1 / -- --

📄 Capture Graph Code

Sunflower.py

"""Sunflower 4D reconstruction, with reminders spread across the day.

Each session is an aligned reference photo plus surrounding photos from
multiple angles. The server hands each participant a distinct reminder slot
out of the day's fixed hours.
"""

from datetime import UTC, datetime, timedelta

import capturegraph as cg
import capturegraph.scheduling as cgsh
from server import authoring as cgserver

SLOT_HOURS = [10, 12, 14, 16, 18, 20, 22]
BOOK = "sunflower-schedule"


class PlantSession(cg.Struct):
    user_id: cg.UserID
    reference: cg.Image
    location: cg.Location
    time: cg.Time
    surrounding: cg.Array[cg.Image]
    _metadata: cg.Metadata


class Sunflower(cg.Struct):
    instructions: cg.Image
    Sunflower: cg.Map[cg.Date, PlantSession]
    _metadata: cg.Metadata


@cgserver.target
@cg.procedure(Sunflower)
def sunflower(root: cg.Procedure[cg.Path[Sunflower]]) -> None:
    root._metadata._location |= cg.UserInputLocation()
    root._metadata._thumbnail |= cg.ConvertImageToThumbnail(image=root.instructions.load())

    cg.do(
        cg.ShowInstructions(
            text=(
                "Capture one aligned photo of the sunflower, then photos from "
                "multiple angles for 3D reconstruction over time."
            ),
            image=root.instructions.load(),
        )
    )
    cg.do(cg.ShowLocationGuide(target=root._metadata._location, threshold_meters=25.0))

    session = root.Sunflower[cg.CaptureTime()]
    session.user_id &= cg.GetUserID()
    session.time &= cg.CaptureTime()
    session.location &= cg.CaptureLocation()
    session.reference &= cg.CaptureImage(label="Capture aligned sunflower photo")
    session.surrounding &= cg.CaptureImageSequence(
        label="Capture surrounding photos from multiple angles"
    )
    session._metadata._thumbnail &= cg.ConvertImageToThumbnail(image=session.reference)


@cgserver.intercept(Sunflower, lambda s: s._metadata._next_notification)
def notification(profile: cgserver.RequestProfile, target: Sunflower) -> cg.Time:
    now = datetime.now(UTC)
    slots = [slot.astimezone() for slot in cgsh.forecast.times_of_day(hours=SLOT_HOURS)]
    chosen = cgsh.ReservationBook.open(BOOK).reserve(
        profile.user_id,
        slots,
        captured=max(target.Sunflower.to_dict(), default=None),
        grace=timedelta(minutes=30),
        now=now,
    )
    return cg.Time(chosen if chosen is not None else now + timedelta(hours=2))

🎬 Capture Video