37 lines
1.9 KiB
Python
37 lines
1.9 KiB
Python
from dataclasses import dataclass,asdict
|
|||
|
|
from pathlib import Path
|
||
|
|
import threading
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class Observation:
|
||
|
|
observation_id:str
|
||
|
|
stamp_ns:int
|
||
|
|
frame_id:str
|
||
|
|
image_path:str
|
||
|
|
station_id:str
|
||
|
|
registry_version:int
|
||
|
|
shelf_id:str
|
||
|
|
calibration_id:str=''
|
||
|
|
geometry_epoch:int=0
|
||
|
|
def validate(self,goal,now_ns,max_age_ns):
|
||
|
|
if type(self.stamp_ns) is not int or not goal['capture_after']<=self.stamp_ns<=now_ns or now_ns-self.stamp_ns>max_age_ns:raise ValueError('stale/future observation')
|
||
|
|
if not self.observation_id or not self.frame_id or not self.image_path:raise ValueError('observation identity incomplete')
|
||
|
|
if goal.get('observation_station_id',self.station_id)!=self.station_id or goal.get('station_registry_version',self.registry_version)!=self.registry_version:raise ValueError('station/version mismatch')
|
||
|
|
if goal.get('source_region_ref',self.shelf_id)!=self.shelf_id:raise ValueError('wrong shelf')
|
||
|
|
if goal.get('expected_geometry_epoch',self.geometry_epoch)!=self.geometry_epoch:raise ValueError('geometry changed')
|
||
|
|
return asdict(self)
|
||
|
|
|
||
|
|
class ObservationCache:
|
||
|
|
def __init__(self,media_root):self.root=Path(media_root).resolve(strict=True);self.lock=threading.Lock();self.observation=None
|
||
|
|
def put(self,observation):
|
||
|
|
path=Path(observation.image_path).resolve(strict=True)
|
||
|
|
if not path.is_relative_to(self.root) or not path.is_file() or path.stat().st_size>32*1024*1024:raise ValueError('image must be a bounded local trusted media file')
|
||
|
|
with self.lock:
|
||
|
|
# Accept a new clock epoch only after consumer freshness check; never
|
||
|
|
# attach a current timestamp to old image bytes.
|
||
|
|
self.observation=observation
|
||
|
|
def get(self):
|
||
|
|
with self.lock:
|
||
|
|
if self.observation is None:raise ValueError('no observation')
|
||
|
|
return self.observation
|