Fix D-InSAR pairing payload and center-distance controls
This commit is contained in:
@@ -21,6 +21,12 @@ MAX_SCAN_PATH_LENGTH = read_int_env(
|
||||
minimum=64,
|
||||
maximum=32767,
|
||||
)
|
||||
PAIRING_CENTER_DISTANCE_MAX_METERS = read_int_env(
|
||||
"PAIRING_CENTER_DISTANCE_MAX_METERS",
|
||||
20_000_000,
|
||||
minimum=1,
|
||||
maximum=50_000_000,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_directory_list(value: Any, field_name: str) -> List[str]:
|
||||
@@ -246,14 +252,15 @@ class PairingRequest(BaseModel):
|
||||
time_baseline_min: int = Field(default=1, ge=0, le=3650)
|
||||
time_baseline_max: int = Field(default=90, ge=1, le=3650)
|
||||
overlap_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
|
||||
spatial_baseline_max_meters: int = Field(default=3000, ge=0, le=100000)
|
||||
spatial_baseline_max_meters: int = Field(default=3000, ge=0, le=PAIRING_CENTER_DISTANCE_MAX_METERS)
|
||||
limit_footprint_center_distance: bool = False
|
||||
coverage_diversity_penalty: float = Field(default=0.3, ge=0.0, le=1.0)
|
||||
require_same_imaging_mode: bool = True
|
||||
require_same_polarization: bool = True
|
||||
aoi_overlap_threshold: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
||||
max_temporal_baseline_days: Optional[int] = Field(default=None, ge=1, le=3650)
|
||||
pair_footprint_overlap_min_ratio: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
||||
footprint_center_distance_max_meters: Optional[int] = Field(default=None, ge=0, le=100000)
|
||||
footprint_center_distance_max_meters: Optional[int] = Field(default=None, ge=0, le=PAIRING_CENTER_DISTANCE_MAX_METERS)
|
||||
|
||||
# === 双池日期(新增) ===
|
||||
master_date_from: Optional[str] = Field(default=None, pattern=r'^\d{8}$|^$')
|
||||
|
||||
@@ -4,6 +4,7 @@ import logging
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
|
||||
from pydantic import ValidationError
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -42,6 +43,7 @@ def get_pairing_request_from_form(
|
||||
time_baseline_max: int = Form(90),
|
||||
overlap_threshold: float = Form(0.5),
|
||||
spatial_baseline_max_meters: int = Form(3000),
|
||||
limit_footprint_center_distance: bool = Form(False),
|
||||
max_temporal_baseline_days: Optional[int] = Form(None),
|
||||
pair_footprint_overlap_min_ratio: Optional[float] = Form(None),
|
||||
footprint_center_distance_max_meters: Optional[int] = Form(None),
|
||||
@@ -70,29 +72,33 @@ def get_pairing_request_from_form(
|
||||
except Exception:
|
||||
satellites_list = None
|
||||
|
||||
return PairingRequest(
|
||||
time_baseline_min=time_baseline_min,
|
||||
time_baseline_max=time_baseline_max,
|
||||
overlap_threshold=overlap_threshold,
|
||||
spatial_baseline_max_meters=spatial_baseline_max_meters,
|
||||
max_temporal_baseline_days=max_temporal_baseline_days,
|
||||
pair_footprint_overlap_min_ratio=pair_footprint_overlap_min_ratio,
|
||||
footprint_center_distance_max_meters=footprint_center_distance_max_meters,
|
||||
coverage_diversity_penalty=coverage_diversity_penalty,
|
||||
require_same_imaging_mode=require_same_imaging_mode,
|
||||
require_same_polarization=require_same_polarization,
|
||||
aoi_overlap_threshold=aoi_overlap_threshold,
|
||||
start_date=start_date,
|
||||
master_date_from=master_date_from,
|
||||
master_date_to=master_date_to,
|
||||
slave_date_from=slave_date_from,
|
||||
slave_date_to=slave_date_to,
|
||||
strategy=strategy,
|
||||
num_connections=num_connections,
|
||||
reference_image_id=reference_image_id,
|
||||
allowed_satellites=satellites_list,
|
||||
cross_satellite_pairing=cross_satellite_pairing,
|
||||
)
|
||||
try:
|
||||
return PairingRequest(
|
||||
time_baseline_min=time_baseline_min,
|
||||
time_baseline_max=time_baseline_max,
|
||||
overlap_threshold=overlap_threshold,
|
||||
spatial_baseline_max_meters=spatial_baseline_max_meters,
|
||||
limit_footprint_center_distance=limit_footprint_center_distance,
|
||||
max_temporal_baseline_days=max_temporal_baseline_days,
|
||||
pair_footprint_overlap_min_ratio=pair_footprint_overlap_min_ratio,
|
||||
footprint_center_distance_max_meters=footprint_center_distance_max_meters,
|
||||
coverage_diversity_penalty=coverage_diversity_penalty,
|
||||
require_same_imaging_mode=require_same_imaging_mode,
|
||||
require_same_polarization=require_same_polarization,
|
||||
aoi_overlap_threshold=aoi_overlap_threshold,
|
||||
start_date=start_date,
|
||||
master_date_from=master_date_from,
|
||||
master_date_to=master_date_to,
|
||||
slave_date_from=slave_date_from,
|
||||
slave_date_to=slave_date_to,
|
||||
strategy=strategy,
|
||||
num_connections=num_connections,
|
||||
reference_image_id=reference_image_id,
|
||||
allowed_satellites=satellites_list,
|
||||
cross_satellite_pairing=cross_satellite_pairing,
|
||||
)
|
||||
except ValidationError as exc:
|
||||
raise HTTPException(status_code=422, detail=exc.errors()) from exc
|
||||
|
||||
|
||||
def get_ps_request_from_form(
|
||||
|
||||
@@ -7,7 +7,7 @@ from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
|
||||
@@ -24,7 +24,6 @@ from ..models import (
|
||||
PsTaskItem,
|
||||
PsTaskItemORM,
|
||||
RadarData,
|
||||
RadarPair,
|
||||
TimeseriesStackPlanEdgeORM,
|
||||
TimeseriesStackPlanItemORM,
|
||||
TimeseriesStackPlanORM,
|
||||
@@ -97,13 +96,41 @@ def _normalize_list_pagination(limit: int, offset: int) -> tuple[int, int]:
|
||||
return safe_limit, safe_offset
|
||||
|
||||
|
||||
class DinsarBatchSceneCreate(BaseModel):
|
||||
file_path: str = Field(min_length=1)
|
||||
satellite: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||
imaging_date: Optional[str] = Field(default=None, max_length=32)
|
||||
imaging_mode: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||
polarization: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
|
||||
class DinsarBatchPairCreate(BaseModel):
|
||||
master: DinsarBatchSceneCreate
|
||||
slave: DinsarBatchSceneCreate
|
||||
task_name: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||
task_alias: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||
pair_key: Optional[str] = Field(default=None, max_length=128)
|
||||
pair_uid: Optional[str] = Field(default=None, max_length=64)
|
||||
network_run_id: Optional[str] = Field(default=None, max_length=64)
|
||||
network_edge_id: Optional[int] = None
|
||||
policy_version: Optional[str] = Field(default=None, max_length=32)
|
||||
selection_strategy: Optional[str] = Field(default=None, max_length=32)
|
||||
time_baseline_days: Optional[int] = None
|
||||
spatial_baseline_meters: Optional[float] = None
|
||||
scene_center_distance_meters: Optional[float] = None
|
||||
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
|
||||
class DinsarBatchCreateRequest(BaseModel):
|
||||
name: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||
pairs: List[RadarPair]
|
||||
pairs: List[DinsarBatchPairCreate]
|
||||
|
||||
@field_validator("pairs")
|
||||
@classmethod
|
||||
def _validate_pairs_size(cls, value: List[RadarPair]) -> List[RadarPair]:
|
||||
def _validate_pairs_size(cls, value: List[DinsarBatchPairCreate]) -> List[DinsarBatchPairCreate]:
|
||||
if len(value) > TASK_BATCH_MAX_ITEMS:
|
||||
raise ValueError(
|
||||
f"pairs exceeds max item count ({TASK_BATCH_MAX_ITEMS})."
|
||||
@@ -244,10 +271,11 @@ async def create_dinsar_batch_endpoint(
|
||||
for pair in request.pairs:
|
||||
master = pair.master
|
||||
slave = pair.slave
|
||||
task_name = pair.task_name or pair.task_alias or f"DINSAR_{master.imaging_date or 'master'}_{slave.imaging_date or 'slave'}"
|
||||
item = DinsarTaskItemORM(
|
||||
batch_id=batch_id,
|
||||
task_name=pair.task_name,
|
||||
task_alias=pair.task_alias or pair.task_name,
|
||||
task_name=task_name,
|
||||
task_alias=pair.task_alias or task_name,
|
||||
pair_key=pair.pair_key,
|
||||
scene_pair_uid=pair.pair_uid,
|
||||
network_run_id=pair.network_run_id,
|
||||
@@ -264,7 +292,7 @@ async def create_dinsar_batch_endpoint(
|
||||
slave_imaging_date=slave.imaging_date,
|
||||
slave_imaging_mode=slave.imaging_mode,
|
||||
slave_polarization=slave.polarization,
|
||||
time_baseline_days=pair.time_baseline_days,
|
||||
time_baseline_days=int(pair.time_baseline_days) if pair.time_baseline_days is not None else None,
|
||||
spatial_baseline_meters=pair.spatial_baseline_meters,
|
||||
scene_center_distance_meters=(
|
||||
pair.scene_center_distance_meters
|
||||
|
||||
@@ -169,12 +169,14 @@ class SpatialService:
|
||||
PairingMetricCacheORM.status == "READY",
|
||||
PairingMetricCacheORM.time_baseline_days >= params.time_baseline_min,
|
||||
PairingMetricCacheORM.time_baseline_days <= params.time_baseline_max,
|
||||
center_distance_expr <= params.spatial_baseline_max_meters,
|
||||
PairingMetricCacheORM.scene_overlap_ratio >= params.overlap_threshold,
|
||||
PairingMetricCacheORM.same_look_direction.is_(True),
|
||||
)
|
||||
)
|
||||
|
||||
if params.limit_footprint_center_distance:
|
||||
stmt = stmt.where(center_distance_expr <= params.spatial_baseline_max_meters)
|
||||
|
||||
if require_orbit_data:
|
||||
stmt = stmt.where(
|
||||
master_alias.has_orbit_data.is_(True),
|
||||
@@ -765,9 +767,12 @@ class SpatialService:
|
||||
|
||||
def _score_pair_candidate(self, candidate: dict, params: PairingRequest) -> float:
|
||||
max_time = max(float(params.time_baseline_max or 1), 1.0)
|
||||
max_center = max(float(params.spatial_baseline_max_meters or 1), 1.0)
|
||||
time_score = 1.0 - min(float(candidate.get("days") or 0) / max_time, 1.0)
|
||||
center_score = 1.0 - min(float(candidate.get("dist") or 0) / max_center, 1.0)
|
||||
if params.limit_footprint_center_distance:
|
||||
max_center = max(float(params.spatial_baseline_max_meters or 1), 1.0)
|
||||
center_score = 1.0 - min(float(candidate.get("dist") or 0) / max_center, 1.0)
|
||||
else:
|
||||
center_score = 1.0
|
||||
overlap_score = min(max(float(candidate.get("overlap_ratio") or 0), 0.0), 1.0)
|
||||
source_score = 1.0 if (
|
||||
bool(getattr(candidate.get("master"), "insar_source_ready", False))
|
||||
@@ -1215,9 +1220,12 @@ class SpatialService:
|
||||
aoi_poly,
|
||||
) -> float:
|
||||
max_time = max(float(params.time_baseline_max or 1), 1.0)
|
||||
max_space = max(float(params.spatial_baseline_max_meters or 1), 1.0)
|
||||
time_score = 1.0 - min(float(candidate.get("days") or 0) / max_time, 1.0)
|
||||
spatial_score = 1.0 - min(float(candidate.get("dist") or 0) / max_space, 1.0)
|
||||
if params.limit_footprint_center_distance:
|
||||
max_space = max(float(params.spatial_baseline_max_meters or 1), 1.0)
|
||||
spatial_score = 1.0 - min(float(candidate.get("dist") or 0) / max_space, 1.0)
|
||||
else:
|
||||
spatial_score = 1.0
|
||||
overlap_score = min(max(float(candidate.get("overlap_ratio") or 0), 0.0), 1.0)
|
||||
source_score = 1.0 if (
|
||||
bool(getattr(candidate.get("master"), "insar_source_ready", False))
|
||||
|
||||
Reference in New Issue
Block a user