"""G3C stereographic recapture, scheduled across daylight solar conditions.
Each session is an aligned stereo pair plus the surrounding photos from both
viewpoints. The server offers each participant a capture time whose solar
angle the dataset is still missing, one distinct time per person.
"""
from datetime import UTC, datetime, timedelta
import capturegraph as cg
import capturegraph.scheduling as cgsh
from server import authoring as cgserver
from server.notifications import hourly_bucket, notification_candidates
SELECTIONS = 3
BOOK = "g3c-schedule"
class StereoSession(cg.Struct):
user_id: cg.UserID
weather: cg.Weather
left: cg.Image
right: cg.Image
left_surrounding: cg.Array[cg.Image]
right_surrounding: cg.Array[cg.Image]
_metadata: cg.Metadata
class G3C(cg.Struct):
instructions: cg.Image
G3C: cg.Map[cg.Date, StereoSession]
_metadata: cg.Metadata
@cgserver.target
@cg.procedure(G3C)
def g3c(root: cg.Procedure[cg.Path[G3C]]) -> None:
root._metadata._location |= cg.UserInputLocation()
root._metadata._thumbnail |= cg.ConvertImageToThumbnail(image=root.instructions.load())
cg.do(
cg.ShowInstructions(
text=(
"Capture two aligned images from the locations numbered 1 and 2 "
"in the reference image. Then take panoramic photos from both."
),
image=root.instructions.load(),
)
)
cg.do(cg.ShowLocationGuide(target=root._metadata._location, threshold_meters=100.0))
session = root.G3C[cg.CaptureTime()]
session.user_id &= cg.GetUserID()
session.weather &= cg.CaptureWeather(label="Record current weather")
session.left &= cg.CaptureImage(label="Capture Left Photo (1)", encoding=cg.ImageEncoding.RAW)
session.left_surrounding &= cg.CaptureImageSequence(
label="Capture Panoramic Photos (1)", encoding=cg.ImageEncoding.RAW
)
session.right &= cg.CaptureImage(label="Capture Right Photo (2)", encoding=cg.ImageEncoding.RAW)
session.right_surrounding &= cg.CaptureImageSequence(
label="Capture Panoramic Photos (2)", encoding=cg.ImageEncoding.RAW
)
session._metadata._thumbnail &= cg.ConvertImageToThumbnail(image=session.left)
@cgserver.intercept(G3C, lambda s: s._metadata._next_notification)
def notification(profile: cgserver.RequestProfile, target: G3C) -> cg.Time:
now = datetime.now(UTC)
location = target._metadata._location
if cg.is_missing(location):
location = profile.location
if location is None:
return cg.Time(now + timedelta(hours=1))
candidates = notification_candidates(
target.G3C,
location,
hourly_bucket(now),
selections=SELECTIONS,
daylight_only=True,
)
chosen = cgsh.ReservationBook.open(BOOK).reserve(
profile.user_id,
[candidate.value for candidate in candidates],
captured=max(target.G3C.to_dict(), default=None),
grace=timedelta(minutes=30),
now=now,
)
return cg.Time(chosen if chosen is not None else now + timedelta(hours=1))