"""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))