Spaces:
Paused
Paused
File size: 4,085 Bytes
a358495 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | """Metadata-based sensor and pair semantics. Appearance never establishes time."""
from __future__ import annotations
from datetime import datetime, UTC
from satquery_engine.schemas import RasterMetadata
def acquisition_time(value):
date=datetime.fromisoformat(value.replace("Z","+00:00"))
return date.replace(tzinfo=UTC) if date.tzinfo is None else date.astimezone(UTC)
def classify_configuration(assets: list[RasterMetadata], pair_type: str = "auto") -> tuple[str, str | None]:
if len(assets) not in (1, 2):
return "INVALID", "Please upload one or two supported images."
if len(assets) == 1:
if assets[0].modality == "optical" and assets[0].bands == 3:
return "SINGLE_RGB", None
return {"optical": "SINGLE_OPTICAL", "multispectral": "SINGLE_MULTISPECTRAL", "sar": "SINGLE_SAR"}.get(assets[0].modality, "UNKNOWN"), None
a, b = assets
cross = (a.modality == "sar") != (b.modality == "sar") and "unknown" not in (a.modality, b.modality)
dated = False
if a.acquisition_date and b.acquisition_date:
try:
dated = acquisition_time(a.acquisition_date) != acquisition_time(b.acquisition_date)
except ValueError:
pass
if pair_type == "optical_sar" or cross:
if not cross:
return "PAIR_AMBIGUOUS", "I cannot verify which image is radar imagery from its metadata. Please supply imagery with sensor or polarization metadata."
return "PAIR_MULTISENSOR_BITEMPORAL" if dated else "PAIR_OPTICAL_SAR", None
if pair_type == "bi_temporal" or dated:
return "PAIR_BITEMPORAL_SAR" if a.modality == b.modality == "sar" else "PAIR_BITEMPORAL_OPTICAL", None
return "PAIR_AMBIGUOUS", "Are these images of the same place at different dates, in before-then-after order?"
def policy_blockers(plan, assets, configuration):
blockers = []
if plan.requires_pair and len(assets) != 2:
blockers.append("Please upload two images to answer this comparison question.")
if plan.requires_temporal_relationship and configuration not in {"PAIR_BITEMPORAL_OPTICAL", "PAIR_BITEMPORAL_SAR"}:
blockers.append("A verified pair of images from different dates is required for change analysis.")
if any(n.tool == "fusion" for n in plan.nodes) and configuration not in {"PAIR_OPTICAL_SAR", "PAIR_MULTISENSOR_BITEMPORAL"}:
blockers.append("This request needs one optical image and one SAR image with identifiable sensor metadata.")
if any(n.tool == "buildings" for n in plan.nodes) and any(a.modality not in {"optical", "multispectral"} for a in assets):
blockers.append("The installed building specialist accepts optical RGB imagery; SAR building counting is unavailable.")
return blockers
def node_policy_error(node,assets):
"""Run before the node invokes any model; failed independent branches can abstain."""
if node.tool=="fusion":
from satquery_engine.services.sar import croma_compatibility
errors=croma_compatibility(assets)
return " ".join(errors) if errors else None
if node.tool in {"spectral","spectral_change"}:
if node.tool == "spectral" and node.parameters["target"] in {"water", "built-up"} and not node.parameters.get("strict"):
return None # Specialist performs its own sensor/radiometry preflight.
from satquery_engine.services.spectral import TARGET_INDEX
index=TARGET_INDEX[node.parameters["target"]]
selected=assets if node.tool=="spectral_change" else assets[:1]
for asset in selected:
if asset.modality=="sar": return "Optical spectral indices cannot be calculated from SAR backscatter."
if index not in asset.available_indices and (node.parameters.get("strict") or node.tool=="spectral_change" or asset.bands!=3):
return f"I cannot calculate {index.upper()} because the required named spectral bands are missing."
if node.tool=="buildings" and node.parameters["asset"]>=len(assets):
return "Building comparison requires two images."
return None
|