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,
|
minimum=64,
|
||||||
maximum=32767,
|
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]:
|
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_min: int = Field(default=1, ge=0, le=3650)
|
||||||
time_baseline_max: int = Field(default=90, ge=1, 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)
|
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)
|
coverage_diversity_penalty: float = Field(default=0.3, ge=0.0, le=1.0)
|
||||||
require_same_imaging_mode: bool = True
|
require_same_imaging_mode: bool = True
|
||||||
require_same_polarization: bool = True
|
require_same_polarization: bool = True
|
||||||
aoi_overlap_threshold: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
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)
|
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)
|
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}$|^$')
|
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 typing import Dict, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
|
||||||
|
from pydantic import ValidationError
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ def get_pairing_request_from_form(
|
|||||||
time_baseline_max: int = Form(90),
|
time_baseline_max: int = Form(90),
|
||||||
overlap_threshold: float = Form(0.5),
|
overlap_threshold: float = Form(0.5),
|
||||||
spatial_baseline_max_meters: int = Form(3000),
|
spatial_baseline_max_meters: int = Form(3000),
|
||||||
|
limit_footprint_center_distance: bool = Form(False),
|
||||||
max_temporal_baseline_days: Optional[int] = Form(None),
|
max_temporal_baseline_days: Optional[int] = Form(None),
|
||||||
pair_footprint_overlap_min_ratio: Optional[float] = Form(None),
|
pair_footprint_overlap_min_ratio: Optional[float] = Form(None),
|
||||||
footprint_center_distance_max_meters: Optional[int] = Form(None),
|
footprint_center_distance_max_meters: Optional[int] = Form(None),
|
||||||
@@ -70,11 +72,13 @@ def get_pairing_request_from_form(
|
|||||||
except Exception:
|
except Exception:
|
||||||
satellites_list = None
|
satellites_list = None
|
||||||
|
|
||||||
|
try:
|
||||||
return PairingRequest(
|
return PairingRequest(
|
||||||
time_baseline_min=time_baseline_min,
|
time_baseline_min=time_baseline_min,
|
||||||
time_baseline_max=time_baseline_max,
|
time_baseline_max=time_baseline_max,
|
||||||
overlap_threshold=overlap_threshold,
|
overlap_threshold=overlap_threshold,
|
||||||
spatial_baseline_max_meters=spatial_baseline_max_meters,
|
spatial_baseline_max_meters=spatial_baseline_max_meters,
|
||||||
|
limit_footprint_center_distance=limit_footprint_center_distance,
|
||||||
max_temporal_baseline_days=max_temporal_baseline_days,
|
max_temporal_baseline_days=max_temporal_baseline_days,
|
||||||
pair_footprint_overlap_min_ratio=pair_footprint_overlap_min_ratio,
|
pair_footprint_overlap_min_ratio=pair_footprint_overlap_min_ratio,
|
||||||
footprint_center_distance_max_meters=footprint_center_distance_max_meters,
|
footprint_center_distance_max_meters=footprint_center_distance_max_meters,
|
||||||
@@ -93,6 +97,8 @@ def get_pairing_request_from_form(
|
|||||||
allowed_satellites=satellites_list,
|
allowed_satellites=satellites_list,
|
||||||
cross_satellite_pairing=cross_satellite_pairing,
|
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(
|
def get_ps_request_from_form(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from datetime import datetime
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
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.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.future import select
|
from sqlalchemy.future import select
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ from ..models import (
|
|||||||
PsTaskItem,
|
PsTaskItem,
|
||||||
PsTaskItemORM,
|
PsTaskItemORM,
|
||||||
RadarData,
|
RadarData,
|
||||||
RadarPair,
|
|
||||||
TimeseriesStackPlanEdgeORM,
|
TimeseriesStackPlanEdgeORM,
|
||||||
TimeseriesStackPlanItemORM,
|
TimeseriesStackPlanItemORM,
|
||||||
TimeseriesStackPlanORM,
|
TimeseriesStackPlanORM,
|
||||||
@@ -97,13 +96,41 @@ def _normalize_list_pagination(limit: int, offset: int) -> tuple[int, int]:
|
|||||||
return safe_limit, safe_offset
|
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):
|
class DinsarBatchCreateRequest(BaseModel):
|
||||||
name: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
name: Optional[str] = Field(default=None, max_length=BATCH_TEXT_MAX_LENGTH)
|
||||||
pairs: List[RadarPair]
|
pairs: List[DinsarBatchPairCreate]
|
||||||
|
|
||||||
@field_validator("pairs")
|
@field_validator("pairs")
|
||||||
@classmethod
|
@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:
|
if len(value) > TASK_BATCH_MAX_ITEMS:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"pairs exceeds max item count ({TASK_BATCH_MAX_ITEMS})."
|
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:
|
for pair in request.pairs:
|
||||||
master = pair.master
|
master = pair.master
|
||||||
slave = pair.slave
|
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(
|
item = DinsarTaskItemORM(
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
task_name=pair.task_name,
|
task_name=task_name,
|
||||||
task_alias=pair.task_alias or pair.task_name,
|
task_alias=pair.task_alias or task_name,
|
||||||
pair_key=pair.pair_key,
|
pair_key=pair.pair_key,
|
||||||
scene_pair_uid=pair.pair_uid,
|
scene_pair_uid=pair.pair_uid,
|
||||||
network_run_id=pair.network_run_id,
|
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_date=slave.imaging_date,
|
||||||
slave_imaging_mode=slave.imaging_mode,
|
slave_imaging_mode=slave.imaging_mode,
|
||||||
slave_polarization=slave.polarization,
|
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,
|
spatial_baseline_meters=pair.spatial_baseline_meters,
|
||||||
scene_center_distance_meters=(
|
scene_center_distance_meters=(
|
||||||
pair.scene_center_distance_meters
|
pair.scene_center_distance_meters
|
||||||
|
|||||||
@@ -169,12 +169,14 @@ class SpatialService:
|
|||||||
PairingMetricCacheORM.status == "READY",
|
PairingMetricCacheORM.status == "READY",
|
||||||
PairingMetricCacheORM.time_baseline_days >= params.time_baseline_min,
|
PairingMetricCacheORM.time_baseline_days >= params.time_baseline_min,
|
||||||
PairingMetricCacheORM.time_baseline_days <= params.time_baseline_max,
|
PairingMetricCacheORM.time_baseline_days <= params.time_baseline_max,
|
||||||
center_distance_expr <= params.spatial_baseline_max_meters,
|
|
||||||
PairingMetricCacheORM.scene_overlap_ratio >= params.overlap_threshold,
|
PairingMetricCacheORM.scene_overlap_ratio >= params.overlap_threshold,
|
||||||
PairingMetricCacheORM.same_look_direction.is_(True),
|
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:
|
if require_orbit_data:
|
||||||
stmt = stmt.where(
|
stmt = stmt.where(
|
||||||
master_alias.has_orbit_data.is_(True),
|
master_alias.has_orbit_data.is_(True),
|
||||||
@@ -765,9 +767,12 @@ class SpatialService:
|
|||||||
|
|
||||||
def _score_pair_candidate(self, candidate: dict, params: PairingRequest) -> float:
|
def _score_pair_candidate(self, candidate: dict, params: PairingRequest) -> float:
|
||||||
max_time = max(float(params.time_baseline_max or 1), 1.0)
|
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)
|
time_score = 1.0 - min(float(candidate.get("days") or 0) / max_time, 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)
|
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)
|
overlap_score = min(max(float(candidate.get("overlap_ratio") or 0), 0.0), 1.0)
|
||||||
source_score = 1.0 if (
|
source_score = 1.0 if (
|
||||||
bool(getattr(candidate.get("master"), "insar_source_ready", False))
|
bool(getattr(candidate.get("master"), "insar_source_ready", False))
|
||||||
@@ -1215,9 +1220,12 @@ class SpatialService:
|
|||||||
aoi_poly,
|
aoi_poly,
|
||||||
) -> float:
|
) -> float:
|
||||||
max_time = max(float(params.time_baseline_max or 1), 1.0)
|
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)
|
time_score = 1.0 - min(float(candidate.get("days") or 0) / max_time, 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)
|
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)
|
overlap_score = min(max(float(candidate.get("overlap_ratio") or 0), 0.0), 1.0)
|
||||||
source_score = 1.0 if (
|
source_score = 1.0 if (
|
||||||
bool(getattr(candidate.get("master"), "insar_source_ready", False))
|
bool(getattr(candidate.get("master"), "insar_source_ready", False))
|
||||||
|
|||||||
@@ -60,6 +60,12 @@
|
|||||||
|
|
||||||
## 4. 配对与前端导航
|
## 4. 配对与前端导航
|
||||||
|
|
||||||
|
- [SENTINEL1_SYSTEM_ENHANCEMENT_MASTER_PLAN_20260510.md](SENTINEL1_SYSTEM_ENHANCEMENT_MASTER_PLAN_20260510.md)
|
||||||
|
Sentinel-1 系统增强主维护文档,汇总数据管理、精轨、配对、任务分发、Gamma/PyINT 生产、结果管理和分阶段实施边界。
|
||||||
|
- [SENTINEL1_DATA_MANAGEMENT_ADAPTATION_PLAN_20260510.md](SENTINEL1_DATA_MANAGEMENT_ADAPTATION_PLAN_20260510.md)
|
||||||
|
Sentinel-1 源数据管理、精轨匹配、配对约束、分发和后续生产适配的分阶段改造规划。
|
||||||
|
- [SENTINEL1_DINSAR_GAMMA_ISCE2_FEASIBILITY_20260510.md](SENTINEL1_DINSAR_GAMMA_ISCE2_FEASIBILITY_20260510.md)
|
||||||
|
Sentinel-1 D-InSAR 在不使用 ENVI + SARscape 核心时,基于 Gamma/PyINT 与 ISCE2 的可行性、接入边界和推荐实施顺序。
|
||||||
- [DINSAR_PAIRING_DISTRIBUTION_LOGIC_20260508.md](DINSAR_PAIRING_DISTRIBUTION_LOGIC_20260508.md)
|
- [DINSAR_PAIRING_DISTRIBUTION_LOGIC_20260508.md](DINSAR_PAIRING_DISTRIBUTION_LOGIC_20260508.md)
|
||||||
2026-05-08 源码走读记录,梳理 D-InSAR 配对缓存、策略筛选、批次保存、数据分发和生产 worker 执行链路。
|
2026-05-08 源码走读记录,梳理 D-InSAR 配对缓存、策略筛选、批次保存、数据分发和生产 worker 执行链路。
|
||||||
- [PAIRING_ENHANCEMENT_DESIGN.md](PAIRING_ENHANCEMENT_DESIGN.md)
|
- [PAIRING_ENHANCEMENT_DESIGN.md](PAIRING_ENHANCEMENT_DESIGN.md)
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
# Sentinel-1 数据管理改造规划
|
||||||
|
|
||||||
|
日期:2026-05-10
|
||||||
|
|
||||||
|
本文只规划“让系统能管理哨兵一号 Sentinel-1 数据”的改造范围,不记录已完成实现。当前建议把目标分成两层:先支持 Sentinel-1 源数据入库、检索、预览、配对和分发;真正进入 ISCE2 / PyINT / SARscape 的 Sentinel-1 自动生产链路,作为后续独立阶段处理。
|
||||||
|
|
||||||
|
## 1. 目标边界
|
||||||
|
|
||||||
|
本轮建议做:
|
||||||
|
|
||||||
|
1. 扫描并登记 Sentinel-1 SLC `.SAFE` 解包目录。
|
||||||
|
2. 解析 Sentinel-1 基础元数据,包括卫星、成像模式、极化、起止时间、绝对轨道、相对轨道、升降轨、footprint。
|
||||||
|
3. 管理 Sentinel-1 精密轨道 `.EOF` 文件,并把影像按 acquisition time 匹配到覆盖该时间窗的轨道文件。
|
||||||
|
4. 在源数据检索、配对规划、任务批次和数据分发中正确显示和使用 Sentinel-1。
|
||||||
|
5. 对 Sentinel-1 配对增加必要的轻量约束,避免只靠 footprint 交叠产生不科学的候选对。
|
||||||
|
|
||||||
|
本轮不建议做:
|
||||||
|
|
||||||
|
1. 不把 Sentinel-1 自动送入现有 LT1 专用 ISCE2 / PyINT 流水线。
|
||||||
|
2. 不做 burst 级精确公共覆盖计算。
|
||||||
|
3. 不计算真实垂直基线,不把 footprint 中心距伪装成 SAR 空间基线。
|
||||||
|
4. 不强制支持 `.SAFE.zip` 直接入库;优先支持已解包的 `.SAFE` 目录,zip 可作为后续增强。
|
||||||
|
|
||||||
|
## 2. 当前代码现状
|
||||||
|
|
||||||
|
已有基础:
|
||||||
|
|
||||||
|
- [backend/app/utils.py](../backend/app/utils.py) 已经有 `parse_s1_radar_filename()` 和 `parse_s1_orbit_filename()` 的雏形。
|
||||||
|
- [backend/app/services/data_service.py](../backend/app/services/data_service.py) 的 `scan_radar_data()` 是通用源数据扫描入口,理论上可以扫描 LT1 / S1 / GF3。
|
||||||
|
- `radar_data` 已有 `satellite`、`satellite_family`、`imaging_date`、`imaging_mode`、`polarization`、`orbit_direction`、`product_type`、`source_product_token`、`has_orbit_data`、`orbit_file_path`、`geom`、`coverage_polygon` 等通用字段。
|
||||||
|
- [backend/app/services/pairing_cache_service.py](../backend/app/services/pairing_cache_service.py) 已经把 `S1A/S1B` 归为 `S1` family,配对层具备同卫星族筛选能力。
|
||||||
|
- [backend/app/copier.py](../backend/app/copier.py) 分发逻辑是复制原始产品目录,不再依赖 `envi_import`,这对 `.SAFE` 目录是有利的。
|
||||||
|
|
||||||
|
主要缺口:
|
||||||
|
|
||||||
|
- Sentinel-1 `.SAFE` 的关键元数据主要在 `manifest.safe` 和 `annotation/*.xml`,当前 `find_xml_file()` / `parse_xml_metadata()` 更偏 LT1 风格 XML,不能可靠解析 S1 footprint、升降轨、相对轨道。
|
||||||
|
- 当前精轨模块 [backend/app/services/orbit_converter.py](../backend/app/services/orbit_converter.py) 基本写死 LT1 `LT1A_GpsData_GAS_C_YYYYMMDD.txt`,并会同步/转换到 ENVI、ISCE2 池;Sentinel-1 `.EOF` 不能走这套转换逻辑。
|
||||||
|
- 当前配对缓存只要求同升降轨、同 look direction、footprint 相交、同卫星族、同模式/极化等。对 Sentinel-1 来说还缺少“同相对轨道/同轨道方向/同 beam mode/必要时同 slice 或同 burst 覆盖”的约束。
|
||||||
|
- 前端卫星组 [frontend/src/config/appConstants.js](../frontend/src/config/appConstants.js) 目前只有 LT-1、GF-3;配对默认 `allowed_satellites` 是 `LT1A/LT1B`,导入 S1 后如果不改,会默认把 S1 排除。
|
||||||
|
- 现有 ISCE2 / PyINT 生产脚本明显是 LT1 专用,不能因为管理了 S1 数据就默认允许一键生产。
|
||||||
|
|
||||||
|
## 3. Sentinel-1 文件名能提供的信息
|
||||||
|
|
||||||
|
标准 Sentinel-1 SLC SAFE 名称通常类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
S1A_IW_SLC__1SDV_20250101T104105_20250101T104132_XXXXXX_YYYYYY_ZZZZ.SAFE
|
||||||
|
```
|
||||||
|
|
||||||
|
可直接从文件名得到:
|
||||||
|
|
||||||
|
- `satellite`: `S1A` / `S1B`
|
||||||
|
- `satellite_family`: `S1`
|
||||||
|
- `imaging_mode`: `IW` / `EW` / `SM`
|
||||||
|
- `product_type`: `SLC`
|
||||||
|
- `product_level`: 可从 `1SDV` 中解析出 level 1
|
||||||
|
- `polarization`: `DV`、`DH`、`SV`、`SH`
|
||||||
|
- `acquisition_start_time_utc`
|
||||||
|
- `acquisition_stop_time_utc`
|
||||||
|
- `imaging_date`
|
||||||
|
- `absolute_orbit`,即文件名中的 6 位轨道号
|
||||||
|
- datatake id / product unique id
|
||||||
|
|
||||||
|
文件名通常不能可靠提供:
|
||||||
|
|
||||||
|
- footprint
|
||||||
|
- orbit direction,升轨/降轨
|
||||||
|
- relative orbit / track
|
||||||
|
- look direction
|
||||||
|
- burst 范围
|
||||||
|
- 精轨覆盖关系
|
||||||
|
|
||||||
|
因此只靠文件名可以完成“粗入库”,但要做可靠配对,至少还要读取 `manifest.safe` 或 annotation XML。
|
||||||
|
|
||||||
|
## 4. 建议新增的元数据模型
|
||||||
|
|
||||||
|
为了控制工作量,不建议把 Sentinel-1 所有元数据拆成大量列。建议采用“少量关键列 + JSON 扩展”的方式。
|
||||||
|
|
||||||
|
建议新增字段:
|
||||||
|
|
||||||
|
- `acquisition_start_time_utc`
|
||||||
|
- `acquisition_stop_time_utc`
|
||||||
|
- `relative_orbit`
|
||||||
|
- `absolute_orbit`
|
||||||
|
- `slice_number`
|
||||||
|
- `sensor_operational_mode`,可与现有 `imaging_mode` 保持一致或作为补充
|
||||||
|
- `swath_list`,可用 JSON 或逗号文本保存 `IW1/IW2/IW3`
|
||||||
|
- `source_format`,例如 `SAFE_DIR`、`SAFE_ZIP`、`LT1_DIR`
|
||||||
|
- `metadata_json`,保存 Sentinel-1 专用扩展信息
|
||||||
|
|
||||||
|
如果想更轻量,也可以第一阶段只增加:
|
||||||
|
|
||||||
|
- `relative_orbit`
|
||||||
|
- `acquisition_start_time_utc`
|
||||||
|
- `acquisition_stop_time_utc`
|
||||||
|
- `metadata_json`
|
||||||
|
|
||||||
|
这四个字段已经足够支撑 Sentinel-1 基础管理和更科学的配对过滤。
|
||||||
|
|
||||||
|
数据库处理要求:
|
||||||
|
|
||||||
|
- 新增 ORM 字段和 Pydantic schema 字段。
|
||||||
|
- 新增幂等迁移 `010_sentinel1_source_fields.sql`。
|
||||||
|
- 把该迁移加入 [backend/app/db_maintenance.py](../backend/app/db_maintenance.py) 的 `MIGRATION_FILES`。
|
||||||
|
- 健康检查里至少确认新增列存在;如果后续扩展精轨库存表,再把库存一致性纳入健康检查。
|
||||||
|
|
||||||
|
## 5. 入库扫描设计
|
||||||
|
|
||||||
|
建议把现有解析逻辑拆成“产品类型适配器”:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SourceProductParser
|
||||||
|
- LT1 parser
|
||||||
|
- Sentinel1 SAFE parser
|
||||||
|
- GF3 parser
|
||||||
|
```
|
||||||
|
|
||||||
|
Sentinel-1 parser 负责:
|
||||||
|
|
||||||
|
1. 识别 `.SAFE` 目录。
|
||||||
|
2. 从目录名解析粗元数据。
|
||||||
|
3. 读取 `manifest.safe`。
|
||||||
|
4. 读取 annotation XML 获取或校正:
|
||||||
|
- pass / orbit direction
|
||||||
|
- relative orbit
|
||||||
|
- absolute orbit
|
||||||
|
- footprint 坐标
|
||||||
|
- start / stop time
|
||||||
|
- polarization / swath
|
||||||
|
5. 生成 `coverage_polygon`、`geom`、`min/max lon/lat`。
|
||||||
|
6. 判断 `insar_source_ready`:
|
||||||
|
- 必须是 `SLC`
|
||||||
|
- 必须有 footprint
|
||||||
|
- 必须有 imaging date / acquisition time
|
||||||
|
- 必须有 orbit direction
|
||||||
|
- Sentinel-1 推荐必须有 relative orbit
|
||||||
|
|
||||||
|
兼容策略:
|
||||||
|
|
||||||
|
- 已解包 `.SAFE` 目录优先。
|
||||||
|
- `.SAFE.zip` 不在第一阶段直接解析,除非用户明确需要。后续可以在扫描器里只读 zip 内 `manifest.safe`,但这会明显增加实现和测试量。
|
||||||
|
- 预览图优先复用 SAFE 内 `preview/quick-look.png` 或其他 quicklook 文件;当前 `find_radar_preview_source()` 已有关键词扫描,可少量适配。
|
||||||
|
|
||||||
|
## 6. Sentinel-1 精轨管理设计
|
||||||
|
|
||||||
|
Sentinel-1 精轨文件是 `.EOF`,匹配逻辑不是“卫星 + 日期等于影像日期”,而是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
orbit.satellite == scene.satellite
|
||||||
|
and orbit.validity_start <= scene.acquisition_time <= orbit.validity_stop
|
||||||
|
```
|
||||||
|
|
||||||
|
建议新增一个 Sentinel-1 orbit inventory 解析路径:
|
||||||
|
|
||||||
|
- 解析 `S1A/S1B`
|
||||||
|
- 解析轨道类型:`AUX_POEORB` / `AUX_RESORB`
|
||||||
|
- 解析 validity start / stop
|
||||||
|
- 记录原始 `.EOF` 路径
|
||||||
|
|
||||||
|
第一阶段可以不建独立轨道表,仍然把匹配结果写回 `radar_data.has_orbit_data` 和 `radar_data.orbit_file_path`。但代码上要避免把 `.EOF` 送进 LT1 的 txt->xml 转换流程。
|
||||||
|
|
||||||
|
更稳的设计是后续新增 `orbit_files` 表:
|
||||||
|
|
||||||
|
- `satellite`
|
||||||
|
- `satellite_family`
|
||||||
|
- `orbit_type`
|
||||||
|
- `validity_start_utc`
|
||||||
|
- `validity_stop_utc`
|
||||||
|
- `file_path`
|
||||||
|
- `metadata_json`
|
||||||
|
|
||||||
|
考虑到用户希望初级任务不要太复杂,第一阶段建议先不建表,只做扫描时内存索引和 `radar_data` 回填。
|
||||||
|
|
||||||
|
## 7. 配对逻辑改造
|
||||||
|
|
||||||
|
当前配对缓存可继续使用,但 Sentinel-1 应增加轻量硬约束。
|
||||||
|
|
||||||
|
建议硬约束:
|
||||||
|
|
||||||
|
- 同 `satellite_family = S1`
|
||||||
|
- 默认允许 `S1A/S1B` 跨星,但必须同 Sentinel-1 family
|
||||||
|
- 同 `orbit_direction`
|
||||||
|
- 同 `relative_orbit`
|
||||||
|
- 同 `imaging_mode`
|
||||||
|
- 同 `polarization` 或至少主极化兼容
|
||||||
|
- footprint 有交叠
|
||||||
|
- `insar_source_ready = true`
|
||||||
|
|
||||||
|
建议筛选指标:
|
||||||
|
|
||||||
|
- `time_baseline_days`
|
||||||
|
- `scene_overlap_ratio`
|
||||||
|
- `scene_center_distance_meters`,只作为 footprint 中心距,不叫真实空间基线
|
||||||
|
- `has_orbit_data`
|
||||||
|
|
||||||
|
不建议第一阶段做:
|
||||||
|
|
||||||
|
- 真实垂直基线计算
|
||||||
|
- TOPS burst 级 overlap
|
||||||
|
- 自动下载 ASF / Copernicus metadata
|
||||||
|
- Sentinel-1 burst id 图层化管理
|
||||||
|
|
||||||
|
配对缓存字段建议:
|
||||||
|
|
||||||
|
- 给 `pairing_metric_cache` 增加 `same_relative_orbit` 或通用 `same_track`。
|
||||||
|
- 或者在 SQL 硬约束里按 `radar_data.relative_orbit` 直接过滤。
|
||||||
|
- 在 `selection_meta_json` 中记录 `relative_orbit`、`orbit_direction`、`source_family`,便于追溯。
|
||||||
|
|
||||||
|
## 8. 数据分发设计
|
||||||
|
|
||||||
|
分发层整体可以复用:
|
||||||
|
|
||||||
|
- master 复制完整 `.SAFE` 目录到 `Task_*/master/`
|
||||||
|
- slave 复制完整 `.SAFE` 目录到 `Task_*/slave/`
|
||||||
|
- 如果启用精轨分发,则把 `.EOF` 复制到 `Task_*/orbit/`
|
||||||
|
- `.dinsar_pair.json` 中增加 Sentinel-1 专用元数据,如 relative orbit、acquisition start/stop、orbit EOF 路径
|
||||||
|
|
||||||
|
需要注意:
|
||||||
|
|
||||||
|
- `.SAFE` 目录可能很大,zip 分发开关已经存在的话可以复用。
|
||||||
|
- 不建议在分发阶段裁剪 SAFE 或抽取 measurement 文件,这会把“数据管理”变成“预处理”。
|
||||||
|
- 如果源数据是 `.SAFE.zip`,第一阶段可以要求用户先解包;否则分发逻辑要支持复制 zip 并在生产侧再解包。
|
||||||
|
|
||||||
|
## 9. 前端改造
|
||||||
|
|
||||||
|
需要处理的点:
|
||||||
|
|
||||||
|
- `SATELLITE_GROUPS` 增加 `S1` / `Sentinel-1`。
|
||||||
|
- 源数据检索高级字段增加相对轨道、起止时间、源格式等字段。
|
||||||
|
- 配对弹窗默认不要固定 `LT1A/LT1B`;建议默认“不限定卫星”,或者根据当前检索结果自动选择可见卫星。
|
||||||
|
- 配对参数文案继续使用“footprint 中心距上限”,不要改回“空间基线”。
|
||||||
|
- 对 Sentinel-1 配对显示 `relative_orbit` 和 `orbit_direction`,让用户一眼能看出是不是同轨道。
|
||||||
|
- 生产提交页如果任务包是 Sentinel-1,但选择了 LT1 专用 PyINT/ISCE2 profile,应给出阻止或强提示。
|
||||||
|
|
||||||
|
## 10. 生产链路边界
|
||||||
|
|
||||||
|
现有生产引擎不应直接宣布支持 Sentinel-1:
|
||||||
|
|
||||||
|
- ISCE2 当前 pipeline 和 profile 明显是 LT1 定制,包含 LT1 wavelength、LT1 输入 resolver、LT1 轨道转换。
|
||||||
|
- PyINT / Gamma 当前输入搜索是 `LT1*.tar.gz` / `LT1*.tiff`,也属于 LT1 专用。
|
||||||
|
- SARscape 理论上能处理 Sentinel-1,但当前系统侧是否能用 raw SAFE 驱动 SARscape D-InSAR,需要单独验证模板、参数和 task runner。
|
||||||
|
|
||||||
|
所以建议第一阶段只做到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Sentinel-1 数据管理 -> 配对规划 -> 批次保存 -> 数据分发
|
||||||
|
```
|
||||||
|
|
||||||
|
生产执行留到第二阶段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Sentinel-1 Task_* -> 指定 Sentinel-1 engine/profile -> 预检 -> 生产
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. 实施阶段建议
|
||||||
|
|
||||||
|
### 阶段 1:管理与检索
|
||||||
|
|
||||||
|
工作内容:
|
||||||
|
|
||||||
|
- 完成 Sentinel-1 SAFE parser。
|
||||||
|
- 从 `manifest.safe` / annotation XML 提取 footprint、relative orbit、pass、start/stop time。
|
||||||
|
- 支持 `.EOF` 精轨扫描和时间窗匹配。
|
||||||
|
- 增加必要数据库字段和迁移。
|
||||||
|
- 前端增加 Sentinel-1 过滤和显示。
|
||||||
|
|
||||||
|
预估工作量:2-4 天。
|
||||||
|
|
||||||
|
风险:主要在不同 SAFE 版本 XML 结构差异,需要准备 3-5 个真实样本覆盖 S1A/S1B、IW、单双极化。
|
||||||
|
|
||||||
|
### 阶段 2:配对与分发
|
||||||
|
|
||||||
|
工作内容:
|
||||||
|
|
||||||
|
- 配对缓存加入 `relative_orbit` 约束。
|
||||||
|
- 配对结果、批次 item、`.dinsar_pair.json` 保留 Sentinel-1 扩展元数据。
|
||||||
|
- 分发 `.SAFE` + `.EOF`。
|
||||||
|
- 前端配对弹窗默认卫星选择调整。
|
||||||
|
|
||||||
|
预估工作量:1-2 天。
|
||||||
|
|
||||||
|
风险:如果数据库新增字段后没有正确标 dirty,需要强制重建 pairing cache。
|
||||||
|
|
||||||
|
### 阶段 3:生产预检保护
|
||||||
|
|
||||||
|
工作内容:
|
||||||
|
|
||||||
|
- 识别 task source family。
|
||||||
|
- Sentinel-1 批次提交到 LT1 专用 engine/profile 时阻止。
|
||||||
|
- 在生产面板显示“当前仅支持管理和分发,生产适配待完成”。
|
||||||
|
|
||||||
|
预估工作量:0.5-1 天。
|
||||||
|
|
||||||
|
风险:低。
|
||||||
|
|
||||||
|
### 阶段 4:Sentinel-1 生产适配
|
||||||
|
|
||||||
|
工作内容:
|
||||||
|
|
||||||
|
- 选择 SARscape、ISCE2 TOPS、GMTSAR 或其他处理链。
|
||||||
|
- 设计 Sentinel-1 专用 engine/profile。
|
||||||
|
- 做 TOPS 配准、轨道、DEM、burst overlap、输出发布包和 catalog 适配。
|
||||||
|
|
||||||
|
预估工作量:按引擎不同约 1-3 周,不建议和第一阶段混在一起。
|
||||||
|
|
||||||
|
## 12. 推荐最小方案
|
||||||
|
|
||||||
|
如果目标是“先把系统能管理哨兵数据”,推荐最小闭环如下:
|
||||||
|
|
||||||
|
1. 只支持已解包 `.SAFE` 目录入库。
|
||||||
|
2. 只解析 SLC 产品。
|
||||||
|
3. 只新增 `relative_orbit`、`acquisition_start_time_utc`、`acquisition_stop_time_utc`、`metadata_json` 四类关键字段。
|
||||||
|
4. `.EOF` 精轨只做匹配和路径保存,不做转换。
|
||||||
|
5. 配对只增加同 relative orbit 约束,不做真实空间基线。
|
||||||
|
6. 分发复制完整 SAFE 和 EOF。
|
||||||
|
7. 生产页阻止 Sentinel-1 进入 LT1 专用 profile。
|
||||||
|
|
||||||
|
这个方案对现有 LT1 链路侵入最小,能让 Sentinel-1 数据先进入“可检索、可配对、可分发、可追踪”的状态。
|
||||||
|
|
||||||
|
## 13. 验收标准
|
||||||
|
|
||||||
|
最小验收:
|
||||||
|
|
||||||
|
- 扫描一个包含 S1A/S1B `.SAFE` 的目录后,`radar_data` 能看到 S1 数据。
|
||||||
|
- 源数据检索可以按 Sentinel-1、IW、SLC、极化、升降轨、相对轨道过滤。
|
||||||
|
- 每个 S1 scene 有 footprint,可在地图上显示。
|
||||||
|
- 有匹配 `.EOF` 时 `has_orbit_data = true`,`orbit_file_path` 指向 EOF。
|
||||||
|
- 配对结果不会跨 relative orbit。
|
||||||
|
- 批次保存和数据分发能生成 `Task_*`,其中包含 master/slave SAFE 和可选 orbit EOF。
|
||||||
|
- Sentinel-1 批次不会误提交到 LT1 专用生产 profile。
|
||||||
|
|
||||||
|
## 14. 需要用户确认的输入
|
||||||
|
|
||||||
|
开工前最好确认:
|
||||||
|
|
||||||
|
1. Sentinel-1 数据池是 `.SAFE` 解包目录,还是 `.SAFE.zip` 为主。
|
||||||
|
2. 精轨 `.EOF` 是否已经本地保存,还是希望后续系统自动下载。
|
||||||
|
3. 第一阶段是否只做管理/配对/分发,不接生产。
|
||||||
|
4. 是否接受新增少量数据库字段和一份幂等迁移。
|
||||||
|
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
# Sentinel-1 D-InSAR Gamma / ISCE2 可行性评估
|
||||||
|
|
||||||
|
日期:2026-05-10
|
||||||
|
|
||||||
|
本文只评估设计,不涉及代码修改。重点回答:在不使用 ENVI + SARscape 核心的前提下,现有 D-InSAR 生产链路是否适合接入 Sentinel-1,Gamma/PyINT 与 ISCE2 两条路线的实际工作量和风险分别是什么。
|
||||||
|
|
||||||
|
## 1. 结论
|
||||||
|
|
||||||
|
Gamma/PyINT 路线适合优先做 Sentinel-1 D-InSAR。
|
||||||
|
|
||||||
|
`D:\Code\PyINT` 中的专家代码对 Sentinel-1 支持比较完整,尤其是 Sentinel-1 TOPS/IW 的 ZIP 导入、精轨下载、burst 选择、TOPS burst 裁剪、ScanSAR/TOPS 配准、差分干涉、解缠、地理编码等流程。它不是只停留在文件名解析层面,而是已经围绕 GAMMA 命令组织了 Sentinel-1 的实际处理脚本。
|
||||||
|
|
||||||
|
ISCE2 本身也能处理 Sentinel-1 TOPS 数据,但本项目当前接入的 ISCE2 托管链路是 `lt1_stripmap`,核心是 LT-1 stripmap 的 `stripmapApp.py` 流程,不是 Sentinel-1 TOPS 的 `topsApp.py` 流程。因此 ISCE2 的 Sentinel-1 能力不能直接等价为“当前系统里马上可用”。如果要做,属于新增一个 `s1_tops` 生产 profile,工作量明显大于复用 PyINT/Gamma 的 Sentinel-1 脚本。
|
||||||
|
|
||||||
|
推荐顺序:
|
||||||
|
|
||||||
|
1. 先做 `s1_gamma_dinsar` profile,复用 `D:\Code\PyINT` 的 Sentinel-1 Gamma 处理路线。
|
||||||
|
2. 配对阶段保持轻量,只做同轨、同模式、同方向、同极化、覆盖重叠、时间基线等前置筛选。
|
||||||
|
3. 真正的垂直基线、可处理性和失败原因交给 Gamma/PyINT 在 SLC/RSLC 生成后校验和回填。
|
||||||
|
4. ISCE2 的 `s1_tops` profile 放到第二阶段,等 Gamma 路线跑通后再做。
|
||||||
|
|
||||||
|
## 2. 本项目当前 D-InSAR 引擎状态
|
||||||
|
|
||||||
|
### 2.1 PyINT/Gamma 当前是 LT-1 专用接入
|
||||||
|
|
||||||
|
本项目现有 PyINT 托管入口主要围绕 `lt1_gamma_dinsar`:
|
||||||
|
|
||||||
|
- `.env` 中当前 PyINT/Gamma D-InSAR 配置说明面向 LT-1。
|
||||||
|
- `backend/app/services/pyint_engine.py` 当前只接受 `lt1_gamma_dinsar`。
|
||||||
|
- `backend/app/pyint_pipeline/run_lt1_pyint_pipeline.py` 是 LT-1 专用 runner。
|
||||||
|
- `backend/app/services/pyint_input_assets_service.py` 负责 LT-1 归档、LT-1 精轨和输入资产准备。
|
||||||
|
|
||||||
|
这说明当前系统“接入 PyINT”不等于“已经接入 Sentinel-1 PyINT”。要支持 Sentinel-1,合理做法是新增一个 profile,而不是把 LT-1 profile 混改成多卫星逻辑。
|
||||||
|
|
||||||
|
### 2.2 ISCE2 当前是 LT-1 stripmap 接入
|
||||||
|
|
||||||
|
本项目现有 ISCE2 托管入口主要围绕 `lt1_stripmap`:
|
||||||
|
|
||||||
|
- `.env` 中配置的是 `ISCE2_PROFILE=lt1_stripmap`。
|
||||||
|
- 当前 pipeline 脚本是 `backend/app/isce2_pipeline/run_lt1_dinsar_pipeline.py`。
|
||||||
|
- 当前使用的是 `stripmapApp.py`,不是 Sentinel-1 TOPS 常用的 `topsApp.py`。
|
||||||
|
- WSL runtime registry 允许的 ISCE2 profile 目前也是 LT-1 stripmap 方向。
|
||||||
|
|
||||||
|
因此,ISCE2 对 Sentinel-1 的支持在算法生态上成立,但在本项目现有托管实现里还没有落地。
|
||||||
|
|
||||||
|
## 3. `D:\Code\PyINT` 对 Sentinel-1 的支持证据
|
||||||
|
|
||||||
|
`D:\Code\PyINT` 的 README 和用户指南明确把 Sentinel-1 作为支持对象。代码中也存在成套 Sentinel-1 处理脚本:
|
||||||
|
|
||||||
|
- `pyint/down2slc_sen.py`
|
||||||
|
Sentinel-1 ZIP 下载数据转 SLC。它会调用 `eof` 获取精轨,并通过 GAMMA 相关脚本读取 TOPS SLC。
|
||||||
|
- `pyint/down2slc_sen_all.py`
|
||||||
|
批量发现 `DOWNLOAD/S1*.zip`,按日期调用 Sentinel-1 SLC 生成逻辑。
|
||||||
|
- `pyint/slc_sen_cat.py`
|
||||||
|
老版本 Sentinel-1 SLC 导入与拼接逻辑,说明 PyINT 对多 swath / 多 burst / 多文件拼接有历史支持。
|
||||||
|
- `pyint/extract_s1_bursts.py`
|
||||||
|
基于 master/slave 的 burst 参数,裁剪共同 burst 区间。
|
||||||
|
- `pyint/coreg_s1_gamma.py`
|
||||||
|
使用 GAMMA `ScanSAR_coreg.py` 做 Sentinel-1 TOPS/ScanSAR 配准。
|
||||||
|
- `pyint/raw2ifg_s1.py`
|
||||||
|
Sentinel-1 单对 D-InSAR 的一站式流程:下载数据转 SLC、burst 提取、DEM、配准、差分干涉、解缠、地理编码。
|
||||||
|
- `pyint/select_pairs.py`
|
||||||
|
基于 GAMMA `base_calc` 计算基线并按 `max_tb`、`max_sb` 等约束选网。
|
||||||
|
|
||||||
|
这批脚本说明 PyINT 的 Sentinel-1 支持不是临时拼出来的文件名适配,而是围绕 GAMMA Sentinel-1 TOPS 处理能力组织出的完整流程。
|
||||||
|
|
||||||
|
## 4. Gamma/PyINT 接入方案
|
||||||
|
|
||||||
|
### 4.1 新增 profile
|
||||||
|
|
||||||
|
建议新增独立 profile:
|
||||||
|
|
||||||
|
```text
|
||||||
|
s1_gamma_dinsar
|
||||||
|
```
|
||||||
|
|
||||||
|
不要复用或扩展 `lt1_gamma_dinsar` 的内部假设。两者输入资产、轨道文件、SLC 生成方式、配准方式和模板字段都不同。
|
||||||
|
|
||||||
|
最小改造对象:
|
||||||
|
|
||||||
|
- 新增 Sentinel-1 输入资产准备逻辑。
|
||||||
|
- 新增 Sentinel-1 PyINT runner,例如 `run_s1_pyint_pipeline.py`。
|
||||||
|
- 生产引擎 registry 增加 `s1_gamma_dinsar`。
|
||||||
|
- 任务 preflight 增加 Sentinel-1 ZIP、EOF、GAMMA 命令、模板字段检查。
|
||||||
|
- 输出 catalog 增加 Sentinel-1 结果归档映射。
|
||||||
|
|
||||||
|
### 4.2 输入数据约定
|
||||||
|
|
||||||
|
PyINT 现有 Sentinel-1 脚本默认从:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$SCRATCHDIR/<project>/DOWNLOAD/S1*.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
发现数据。
|
||||||
|
|
||||||
|
因此最省事、风险最低的约定是:系统管理 Sentinel-1 时尽量保留原始 `.zip` 产品,并在生产分发时把 master/slave 的 ZIP 放入 PyINT 项目的 `DOWNLOAD` 目录。
|
||||||
|
|
||||||
|
如果数据池里只有解压后的 `.SAFE` 目录,当前 PyINT 脚本不能直接等价复用。可选方案有三个:
|
||||||
|
|
||||||
|
1. 要求 Sentinel-1 生产任务必须绑定原始 ZIP。
|
||||||
|
2. 在分发阶段把 `.SAFE` 目录重新打包成 ZIP。
|
||||||
|
3. 改造 PyINT 的 Sentinel-1 导入脚本,让它直接接受 `.SAFE` 路径。
|
||||||
|
|
||||||
|
推荐第 1 种。第 2 种会增加磁盘和时间成本,第 3 种会扩大对专家代码的修改面。
|
||||||
|
|
||||||
|
### 4.3 单对 D-InSAR 最小流程
|
||||||
|
|
||||||
|
对于本系统的 D-InSAR 配对任务,建议先做“系统选出一对,PyINT 处理这一对”,而不是马上做完整时序网。
|
||||||
|
|
||||||
|
可控 runner 可以按以下顺序执行:
|
||||||
|
|
||||||
|
1. 准备 `$SCRATCHDIR/<project>/DOWNLOAD`,放入 master/slave Sentinel-1 ZIP。
|
||||||
|
2. 写入 `$TEMPLATEDIR/<project>.template`,包含 masterDate、slaveDate、swath、burst、look、轨道、DEM、网络参数等。
|
||||||
|
3. 调用 `down2slc_sen.py <project> <masterDate>`。
|
||||||
|
4. 调用 `down2slc_sen.py <project> <slaveDate>`。
|
||||||
|
5. 调用 `extract_s1_bursts.py <project> <masterDate> <slaveDate>`。
|
||||||
|
6. 调用 `generate_rdc_dem.py <project>` 或等价 DEM 生成步骤。
|
||||||
|
7. 调用 `coreg_s1_gamma.py <project> <masterDate> <slaveDate>`。
|
||||||
|
8. 调用 `diff_gamma.py <project> <masterDate> <slaveDate>`。
|
||||||
|
9. 调用 `unwrap_gamma.py <project> <masterDate> <slaveDate>`。
|
||||||
|
10. 调用 `geocode_gamma.py <project> <masterDate>-<slaveDate>`。
|
||||||
|
|
||||||
|
也可以参考 `raw2ifg_s1.py` 作为端到端样板,但不建议生产系统直接无控制地调用全流程脚本。托管系统应该显式控制每一步、日志、失败原因、输出发布和清理策略。
|
||||||
|
|
||||||
|
### 4.4 精轨处理
|
||||||
|
|
||||||
|
PyINT 的 Sentinel-1 脚本会使用 `eof` / OPOD 目录获取精轨。系统前面已经设计了 Sentinel-1 精轨管理和分发逻辑,因此两者可以分工:
|
||||||
|
|
||||||
|
- 数据管理层负责发现、缓存、匹配和可选分发 EOF。
|
||||||
|
- PyINT runner 负责把 EOF 目录暴露给 PyINT/GAMMA。
|
||||||
|
- preflight 负责确认 master/slave 覆盖时段都有可用 EOF。
|
||||||
|
|
||||||
|
这样可以避免每次生产任务都临时联网下载精轨,也能保证任务可复现。
|
||||||
|
|
||||||
|
## 5. ISCE2 接入方案
|
||||||
|
|
||||||
|
ISCE2 Sentinel-1 TOPS 路线理论上可行,但本项目当前没有现成托管实现。
|
||||||
|
|
||||||
|
如果做 ISCE2,需要新增:
|
||||||
|
|
||||||
|
- `s1_tops` profile。
|
||||||
|
- `topsApp.py` 路径配置,例如 `ISCE2_TOPS_APP`。
|
||||||
|
- Sentinel-1 SAFE/ZIP 输入准备逻辑。
|
||||||
|
- EOF 轨道文件挂载逻辑。
|
||||||
|
- `topsApp.py` XML 生成器。
|
||||||
|
- topsStack 或 topsApp 的执行脚本。
|
||||||
|
- Sentinel-1 TOPS 输出目录识别、catalog 发布和日志解析。
|
||||||
|
- 与 LT-1 `stripmapApp.py` profile 隔离的健康检查。
|
||||||
|
|
||||||
|
这条路线的主要风险不是 ISCE2 不支持 Sentinel-1,而是当前系统的 ISCE2 封装抽象是为 LT-1 stripmap 写的。直接把 Sentinel-1 塞进现有 `lt1_stripmap` runner 会形成大量条件分支,后期维护会很差。
|
||||||
|
|
||||||
|
如果要做,建议完全独立成 `s1_tops`,不要污染 LT-1 runner。
|
||||||
|
|
||||||
|
## 6. 配对与基线策略
|
||||||
|
|
||||||
|
Sentinel-1 的配对建议分为两层。
|
||||||
|
|
||||||
|
第一层是系统内的轻量预筛选:
|
||||||
|
|
||||||
|
- 同一 relative orbit。
|
||||||
|
- 同一升降轨方向。
|
||||||
|
- 同一 beam mode,例如 IW。
|
||||||
|
- 极化兼容,例如 VV 对 VV、VH 对 VH。
|
||||||
|
- 覆盖范围有足够重叠。
|
||||||
|
- 时间基线在阈值内。
|
||||||
|
- 产品级别满足生产要求,优先 SLC。
|
||||||
|
- EOF 精轨可获得。
|
||||||
|
|
||||||
|
第二层是处理引擎内的精确校验:
|
||||||
|
|
||||||
|
- SLC/TOPS 导入是否成功。
|
||||||
|
- master/slave 是否有共同 burst。
|
||||||
|
- DEM 覆盖是否足够。
|
||||||
|
- GAMMA `base_calc` 计算出的垂直基线是否超过阈值。
|
||||||
|
- TOPS 配准质量是否达标。
|
||||||
|
- 干涉、解缠、地理编码是否生成有效产物。
|
||||||
|
|
||||||
|
这符合“配对是初级任务,不要太复杂”的目标。系统配对不应该冒充严密的 SAR 处理器;它只需要筛出大概率能跑的候选对。真正的物理基线和可处理性,由 GAMMA/ISCE2 在生成 SLC/RSLC 后确认。
|
||||||
|
|
||||||
|
注意命名上应避免再把 footprint 质心距离叫作 `spatial_baseline_meters`。对 Sentinel-1 更合理的字段区分是:
|
||||||
|
|
||||||
|
- `centroid_distance_meters`:覆盖 footprint 质心距离,只是几何覆盖近似指标。
|
||||||
|
- `temporal_baseline_days`:时间基线。
|
||||||
|
- `perpendicular_baseline_meters`:由处理引擎计算或轨道模型计算出的垂直基线。
|
||||||
|
|
||||||
|
## 7. 工作量评估
|
||||||
|
|
||||||
|
### Gamma/PyINT 优先路线
|
||||||
|
|
||||||
|
前提:Sentinel-1 数据管理、ZIP 保留、EOF 匹配和分发机制已经具备。
|
||||||
|
|
||||||
|
预计工作量:
|
||||||
|
|
||||||
|
- 最小可用单对 D-InSAR:约 4 到 7 个工作日。
|
||||||
|
- 加上稳定的前端配置、健康检查、日志归档、失败原因归类和输出发布:约 1 到 2 周。
|
||||||
|
- 扩展到时序网、自动 burst 推荐、多 pair 网络:另算,不建议第一阶段做。
|
||||||
|
|
||||||
|
主要风险:
|
||||||
|
|
||||||
|
- 原始数据是否保留 ZIP。
|
||||||
|
- WSL/GAMMA 环境中的 Sentinel-1 命令是否完整。
|
||||||
|
- `eof` 工具和 OPOD 目录是否稳定。
|
||||||
|
- PyINT 模板字段默认值是否适配当前项目。
|
||||||
|
- 大量 ZIP、SLC、RSLC 中间产物带来的磁盘压力。
|
||||||
|
|
||||||
|
### ISCE2 Sentinel-1 路线
|
||||||
|
|
||||||
|
预计工作量:
|
||||||
|
|
||||||
|
- 可用 demo:约 1 周。
|
||||||
|
- 托管生产级接入:约 2 周或更长。
|
||||||
|
|
||||||
|
主要风险:
|
||||||
|
|
||||||
|
- 当前 `isce2_engine.py` 和 WSL registry 偏 LT-1 stripmap。
|
||||||
|
- 需要新增 topsApp/topsStack 方向的 XML、输入资产和产物识别。
|
||||||
|
- 输出结构和错误日志与当前 LT-1 ISCE2 产物不一致。
|
||||||
|
- 对系统健康检查和 profile registry 的影响更大。
|
||||||
|
|
||||||
|
## 8. 推荐实施路线
|
||||||
|
|
||||||
|
第一阶段只做 Gamma/PyINT Sentinel-1 单对 D-InSAR:
|
||||||
|
|
||||||
|
1. 完成 Sentinel-1 数据管理文档中的数据入库、元数据、EOF 匹配和分发约定。
|
||||||
|
2. 新增 `s1_gamma_dinsar` profile。
|
||||||
|
3. 新增 Sentinel-1 PyINT runner,显式调用 `D:\Code\PyINT` 中已存在的 Sentinel-1 脚本链。
|
||||||
|
4. 生产任务只接受 master/slave 两景 SLC ZIP。
|
||||||
|
5. 配对表只保存轻量预筛选指标和处理后回填的真实 `perpendicular_baseline_meters`。
|
||||||
|
6. 跑通后再考虑自动 burst 推荐、多 pair 网络和 ISCE2 `s1_tops`。
|
||||||
|
|
||||||
|
第二阶段再做 ISCE2 Sentinel-1:
|
||||||
|
|
||||||
|
1. 新增 `s1_tops` profile。
|
||||||
|
2. 使用 `topsApp.py` 或 topsStack,不复用 LT-1 stripmap runner。
|
||||||
|
3. 与 Gamma 路线共享数据管理和 EOF 匹配能力。
|
||||||
|
4. 输出 catalog 与生产日志保持同一前端体验。
|
||||||
|
|
||||||
|
## 9. 验收标准
|
||||||
|
|
||||||
|
Gamma/PyINT Sentinel-1 接入的第一阶段验收建议如下:
|
||||||
|
|
||||||
|
- 同一对 Sentinel-1 ZIP 可以从任务分发目录进入 PyINT `DOWNLOAD`。
|
||||||
|
- preflight 能明确报告 ZIP、EOF、DEM、GAMMA 命令和模板字段是否齐全。
|
||||||
|
- master/slave 能成功生成 SLC。
|
||||||
|
- 能识别共同 burst 并完成裁剪。
|
||||||
|
- 能完成 TOPS 配准并生成 RSLC。
|
||||||
|
- 能生成差分干涉图、解缠结果和地理编码结果。
|
||||||
|
- 生产日志能展示每一步命令、耗时、退出码和失败原因。
|
||||||
|
- 结果 catalog 能发布核心产物。
|
||||||
|
- 配对记录能回填真实 `perpendicular_baseline_meters`,同时保留原始 `centroid_distance_meters`。
|
||||||
|
|
||||||
|
## 10. 总体判断
|
||||||
|
|
||||||
|
不使用 ENVI + SARscape 核心没有问题。对 Sentinel-1 D-InSAR 来说,本项目更应该优先利用 Gamma/PyINT 的专家代码路线。
|
||||||
|
|
||||||
|
Gamma/PyINT 是“已有专家脚本,需要做系统托管适配”;ISCE2 是“算法生态支持,但本项目需要新增 tops profile”。所以第一阶段选择 Gamma/PyINT 更稳、更快,也更符合“配对不要做得过度复杂,但生产尽可能有精度”的目标。
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
# Sentinel-1 系统增强总纲
|
||||||
|
|
||||||
|
日期:2026-05-10
|
||||||
|
|
||||||
|
本文作为 Sentinel-1 增强工作的主维护文档。后续如果继续推进 Sentinel-1 数据管理、D-InSAR 配对、Gamma/PyINT 生产和结果归档,优先维护本文;更细的调研材料保留为参考:
|
||||||
|
|
||||||
|
- [SENTINEL1_DATA_MANAGEMENT_ADAPTATION_PLAN_20260510.md](SENTINEL1_DATA_MANAGEMENT_ADAPTATION_PLAN_20260510.md)
|
||||||
|
- [SENTINEL1_DINSAR_GAMMA_ISCE2_FEASIBILITY_20260510.md](SENTINEL1_DINSAR_GAMMA_ISCE2_FEASIBILITY_20260510.md)
|
||||||
|
|
||||||
|
## 1. 总体目标
|
||||||
|
|
||||||
|
让当前系统能够管理 Sentinel-1 数据,并把 Sentinel-1 D-InSAR 生产纳入现有生产管理和结果管理体系。
|
||||||
|
|
||||||
|
推荐边界是:
|
||||||
|
|
||||||
|
- 系统负责数据管理、元数据解析、精轨匹配、配对、任务分发、生产调度、日志、结果归档和前端展示。
|
||||||
|
- Gamma + PyINT 负责 Sentinel-1 D-InSAR 的处理内核,包括 SLC 导入、burst 裁剪、TOPS 配准、差分干涉、解缠和地理编码。
|
||||||
|
- ISCE2 Sentinel-1 TOPS 作为后续可选 profile,不作为第一阶段主线。
|
||||||
|
|
||||||
|
第一阶段不要求系统自己实现 SAR 核心算法,也不要求配对阶段计算真实垂直基线。
|
||||||
|
|
||||||
|
## 2. 总体架构
|
||||||
|
|
||||||
|
建议新增一条独立 Sentinel-1 Gamma 生产链:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Sentinel-1 数据池
|
||||||
|
-> 数据扫描与元数据入库
|
||||||
|
-> EOF 精轨管理与匹配
|
||||||
|
-> D-InSAR 轻量配对
|
||||||
|
-> 任务批次保存
|
||||||
|
-> Task 目录分发
|
||||||
|
-> s1_gamma_dinsar profile
|
||||||
|
-> PyINT + Gamma 处理
|
||||||
|
-> 结果扫描与 catalog 发布
|
||||||
|
-> 生产管理 / 结果管理前端展示
|
||||||
|
```
|
||||||
|
|
||||||
|
关键原则:
|
||||||
|
|
||||||
|
- Sentinel-1 不混进 LT-1 专用的 `lt1_gamma_dinsar` 或 `lt1_stripmap`。
|
||||||
|
- 新增 `s1_gamma_dinsar` profile 作为第一阶段生产入口。
|
||||||
|
- 真实 `perpendicular_baseline_meters` 由 Gamma/PyINT 在生成 SLC/RSLC 后计算或回填。
|
||||||
|
- footprint 质心距离只能叫 `centroid_distance_meters`,不能再叫空间基线。
|
||||||
|
|
||||||
|
## 3. 数据管理
|
||||||
|
|
||||||
|
### 3.1 支持的数据形态
|
||||||
|
|
||||||
|
第一阶段建议以 Sentinel-1 SLC ZIP 为生产标准输入。
|
||||||
|
|
||||||
|
系统可以管理两类形态:
|
||||||
|
|
||||||
|
- `.SAFE` 目录:适合浏览、入库、解析元数据。
|
||||||
|
- `.zip` 原始产品:适合 PyINT/Gamma 生产分发。
|
||||||
|
|
||||||
|
如果数据池只有 `.SAFE` 目录,也可以入库和配对;但进入 PyINT/Gamma 生产时,建议要求关联原始 ZIP。原因是 `D:\Code\PyINT` 的 Sentinel-1 脚本默认从 `DOWNLOAD/S1*.zip` 发现和导入数据。
|
||||||
|
|
||||||
|
### 3.2 元数据解析
|
||||||
|
|
||||||
|
文件名可以提供粗信息:
|
||||||
|
|
||||||
|
- `S1A` / `S1B`
|
||||||
|
- `IW` / `EW` / `SM`
|
||||||
|
- `SLC`
|
||||||
|
- 极化组合,例如 `DV`、`DH`、`SV`、`SH`
|
||||||
|
- 起止时间
|
||||||
|
- absolute orbit
|
||||||
|
- datatake id / product id
|
||||||
|
|
||||||
|
可靠配对还需要从 `manifest.safe` 或 annotation XML 中解析:
|
||||||
|
|
||||||
|
- relative orbit
|
||||||
|
- 升降轨方向
|
||||||
|
- footprint
|
||||||
|
- swath / burst 相关信息
|
||||||
|
- 更完整的极化和产品结构
|
||||||
|
|
||||||
|
建议采用“关键列 + JSON 扩展”的模型。关键列服务检索、筛选和配对;JSON 保存 Sentinel-1 专有细节,避免一开始改出大量窄字段。
|
||||||
|
|
||||||
|
### 3.3 精轨管理
|
||||||
|
|
||||||
|
Sentinel-1 EOF 不走 LT-1 的精轨转换链路。
|
||||||
|
|
||||||
|
建议新增 Sentinel-1 EOF 管理逻辑:
|
||||||
|
|
||||||
|
- 扫描 `.EOF` 文件。
|
||||||
|
- 解析 mission、validity start、validity stop、generation time。
|
||||||
|
- 按影像 acquisition time 匹配覆盖该时段的 EOF。
|
||||||
|
- 允许把匹配到的 EOF 复制到 Task 目录。
|
||||||
|
- PyINT runner 将 EOF 目录作为 `OPOD_DIR` 暴露给 PyINT/GAMMA。
|
||||||
|
|
||||||
|
这样可以避免生产时临时联网下载精轨,也便于任务复现。
|
||||||
|
|
||||||
|
## 4. D-InSAR 配对
|
||||||
|
|
||||||
|
Sentinel-1 配对适合嵌入现有 D-InSAR 配对体系,但应使用独立策略:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sentinel1_dinsar_pairing
|
||||||
|
```
|
||||||
|
|
||||||
|
第一阶段配对只做轻量预筛选:
|
||||||
|
|
||||||
|
- 同一卫星族:`S1A` / `S1B` 可以互配,不按字面同卫星硬卡死。
|
||||||
|
- 同一成像模式:优先只支持 `IW`。
|
||||||
|
- 同一产品类型:优先只支持 `SLC`。
|
||||||
|
- 同一升降轨方向。
|
||||||
|
- 同一 relative orbit。
|
||||||
|
- 极化兼容,例如 `VV` 对 `VV`。
|
||||||
|
- footprint 重叠率达到阈值。
|
||||||
|
- 时间基线不超过阈值。
|
||||||
|
- master/slave 都能匹配 EOF。
|
||||||
|
|
||||||
|
不在配对阶段强制完成:
|
||||||
|
|
||||||
|
- 真实垂直基线计算。
|
||||||
|
- burst 级精确公共覆盖计算。
|
||||||
|
- TOPS 配准质量判断。
|
||||||
|
- 解缠可行性判断。
|
||||||
|
|
||||||
|
这些应交给 Gamma/PyINT 的处理链路完成。配对阶段只负责筛出大概率可跑的候选对。
|
||||||
|
|
||||||
|
## 5. 任务分发
|
||||||
|
|
||||||
|
Sentinel-1 D-InSAR 任务分发需要输出一个可复现的 Task 目录。
|
||||||
|
|
||||||
|
建议包含:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Task/
|
||||||
|
manifest.json
|
||||||
|
pair.json
|
||||||
|
input/
|
||||||
|
master/
|
||||||
|
<S1 master zip or SAFE>
|
||||||
|
slave/
|
||||||
|
<S1 slave zip or SAFE>
|
||||||
|
orbit/
|
||||||
|
<matched EOF files>
|
||||||
|
dem/
|
||||||
|
<optional dem reference or config>
|
||||||
|
pyint/
|
||||||
|
<project>.template
|
||||||
|
```
|
||||||
|
|
||||||
|
分发开关建议:
|
||||||
|
|
||||||
|
- 是否复制 EOF 到 Task 目录。
|
||||||
|
- 是否输出 ZIP 压缩包。
|
||||||
|
- 是否保留中间 SLC/RSLC 产物。
|
||||||
|
- 是否只发布核心结果。
|
||||||
|
|
||||||
|
如果生产目标是 PyINT/Gamma,Task 到 PyINT scratch 目录之间可以由 runner 再做一次结构化投放,把 master/slave ZIP 放入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$SCRATCHDIR/<project>/DOWNLOAD/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Gamma/PyINT 生产
|
||||||
|
|
||||||
|
第一阶段新增 profile:
|
||||||
|
|
||||||
|
```text
|
||||||
|
s1_gamma_dinsar
|
||||||
|
```
|
||||||
|
|
||||||
|
它应独立于当前 `lt1_gamma_dinsar`。
|
||||||
|
|
||||||
|
推荐 runner 行为:
|
||||||
|
|
||||||
|
1. 读取 Task manifest 和 pair 信息。
|
||||||
|
2. 创建 PyINT project 目录。
|
||||||
|
3. 复制或链接 master/slave Sentinel-1 ZIP 到 `DOWNLOAD/`。
|
||||||
|
4. 准备 EOF / OPOD 目录。
|
||||||
|
5. 写入 PyINT template。
|
||||||
|
6. 调用 Sentinel-1 PyINT 脚本链。
|
||||||
|
7. 收集每一步日志和退出码。
|
||||||
|
8. 扫描输出产物并发布到系统结果 catalog。
|
||||||
|
|
||||||
|
可复用的 `D:\Code\PyINT` Sentinel-1 脚本包括:
|
||||||
|
|
||||||
|
- `pyint/down2slc_sen.py`
|
||||||
|
- `pyint/down2slc_sen_all.py`
|
||||||
|
- `pyint/extract_s1_bursts.py`
|
||||||
|
- `pyint/coreg_s1_gamma.py`
|
||||||
|
- `pyint/raw2ifg_s1.py`
|
||||||
|
- `pyint/select_pairs.py`
|
||||||
|
|
||||||
|
生产系统不建议直接无控制地调用完整 app,而应显式控制步骤、日志、失败原因和产物发布。
|
||||||
|
|
||||||
|
## 7. 结果管理
|
||||||
|
|
||||||
|
PyINT/Gamma 生产结束后,系统负责结果归档。
|
||||||
|
|
||||||
|
第一阶段建议发布:
|
||||||
|
|
||||||
|
- pair manifest
|
||||||
|
- PyINT template
|
||||||
|
- 全量日志
|
||||||
|
- preflight 报告
|
||||||
|
- SLC/RSLC 生成状态
|
||||||
|
- 共同 burst 检查结果
|
||||||
|
- interferogram
|
||||||
|
- coherence
|
||||||
|
- unwrapped phase
|
||||||
|
- geocoded result
|
||||||
|
- quicklook
|
||||||
|
- 真实 `perpendicular_baseline_meters`
|
||||||
|
|
||||||
|
结果管理应复用现有生产结果 catalog,不新建一套孤立页面。前端仍然从生产管理和结果管理入口查看,只是引擎 profile 显示为 `s1_gamma_dinsar`。
|
||||||
|
|
||||||
|
## 8. 前端入口
|
||||||
|
|
||||||
|
前端需要在现有页面中增强,而不是新增一个割裂的 Sentinel-1 子系统。
|
||||||
|
|
||||||
|
建议改动范围:
|
||||||
|
|
||||||
|
- 数据管理页面支持 Sentinel-1 过滤、详情和 EOF 匹配状态。
|
||||||
|
- 配对页面新增 Sentinel-1 策略和参数。
|
||||||
|
- 配对结果中显示 relative orbit、升降轨、极化、时间基线、重叠率和 EOF 状态。
|
||||||
|
- 生产管理中支持 `s1_gamma_dinsar` profile。
|
||||||
|
- 任务分发 UI 中增加 EOF 复制和 ZIP 导出开关。
|
||||||
|
- 结果详情中显示 PyINT/Gamma 日志、核心产物和真实垂直基线。
|
||||||
|
|
||||||
|
第一阶段不需要让用户配置所有 PyINT template 字段。可以只暴露少量参数,其余使用系统默认值。
|
||||||
|
|
||||||
|
## 9. 数据库和自检
|
||||||
|
|
||||||
|
本系统有数据库自维护和系统自检机制,Sentinel-1 增强必须纳入这两部分。
|
||||||
|
|
||||||
|
需要评估的数据库变更:
|
||||||
|
|
||||||
|
- Sentinel-1 关键元数据字段是否已有通用列可复用。
|
||||||
|
- relative orbit、acquisition start/stop、source archive path、orbit match status 是否需要新增列。
|
||||||
|
- Sentinel-1 专有元数据是否放入 JSON 扩展字段。
|
||||||
|
- pairing cache 是否需要保存 `centroid_distance_meters` 与 `perpendicular_baseline_meters` 的区分。
|
||||||
|
- 生产结果 catalog 是否需要新增 engine profile 或产品类型枚举。
|
||||||
|
|
||||||
|
自检需要增加:
|
||||||
|
|
||||||
|
- Sentinel-1 数据根目录是否存在。
|
||||||
|
- EOF 根目录是否存在。
|
||||||
|
- Sentinel-1 解析器是否可用。
|
||||||
|
- `s1_gamma_dinsar` profile 是否启用。
|
||||||
|
- PyINT 路径是否存在。
|
||||||
|
- GAMMA 环境是否可用。
|
||||||
|
- `eof` 工具是否可用。
|
||||||
|
- 关键 GAMMA/PyINT 命令是否可调用。
|
||||||
|
- DEM 配置是否完整。
|
||||||
|
|
||||||
|
任何 schema 改动都必须同步数据库自维护逻辑,避免启动时 schema check 报错。
|
||||||
|
|
||||||
|
## 10. 分阶段实施
|
||||||
|
|
||||||
|
### 阶段 1:数据管理和配对
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- Sentinel-1 SAFE / ZIP 可扫描入库。
|
||||||
|
- 基础元数据和 footprint 可解析。
|
||||||
|
- EOF 可扫描、匹配和显示。
|
||||||
|
- Sentinel-1 配对可生成候选对。
|
||||||
|
- 配对不进入生产也能保存批次。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 同一 relative orbit 的 S1 数据能被正确筛出。
|
||||||
|
- 不同方向、不同 relative orbit、不同模式的数据不会误配。
|
||||||
|
- footprint 重叠率和时间基线显示正确。
|
||||||
|
- EOF 状态清晰可见。
|
||||||
|
|
||||||
|
### 阶段 2:任务分发
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- Sentinel-1 pair 可分发为 Task 目录。
|
||||||
|
- 可选复制 EOF。
|
||||||
|
- 可选导出 ZIP。
|
||||||
|
- manifest 可复现输入数据和参数。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- Task 目录包含 master/slave、EOF、pair manifest、profile 参数。
|
||||||
|
- ZIP 导出可直接交给生产或转移归档。
|
||||||
|
|
||||||
|
### 阶段 3:Gamma/PyINT 生产
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 新增 `s1_gamma_dinsar` profile。
|
||||||
|
- 托管调用 PyINT/Gamma 完成单对 Sentinel-1 D-InSAR。
|
||||||
|
- 生产日志进入系统。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 至少一对 Sentinel-1 SLC ZIP 能跑通到 geocoded 结果。
|
||||||
|
- 失败时能看到明确步骤和原因。
|
||||||
|
- 真实垂直基线可以回填或记录。
|
||||||
|
|
||||||
|
### 阶段 4:结果管理
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- PyINT/Gamma 产物进入现有结果 catalog。
|
||||||
|
- 前端可以查看日志、manifest、quicklook 和核心产物。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 用户不需要进入 PyINT 工作目录即可查看结果。
|
||||||
|
- 结果与 pair、task、engine profile 可追踪。
|
||||||
|
|
||||||
|
### 阶段 5:ISCE2 Sentinel-1 可选增强
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 新增 `s1_tops` profile。
|
||||||
|
- 使用 ISCE2 `topsApp.py` 或 topsStack。
|
||||||
|
- 与 Gamma 路线共享数据管理和 EOF 管理。
|
||||||
|
|
||||||
|
验收:
|
||||||
|
|
||||||
|
- 不污染现有 LT-1 `lt1_stripmap` runner。
|
||||||
|
- Sentinel-1 TOPS 生产有独立健康检查、日志和结果发布。
|
||||||
|
|
||||||
|
## 11. 主要风险
|
||||||
|
|
||||||
|
- 数据池只保留 `.SAFE`,没有原始 ZIP,会增加 PyINT/Gamma 接入复杂度。
|
||||||
|
- EOF 匹配不稳定会导致生产不可复现。
|
||||||
|
- Sentinel-1 annotation XML 解析不完整会影响 relative orbit、方向和 footprint。
|
||||||
|
- 中间产物体积大,SLC/RSLC 保留策略要可配置。
|
||||||
|
- PyINT 专家代码可用,但生产系统仍要做日志、失败分类和输出扫描。
|
||||||
|
- ISCE2 Sentinel-1 虽然可行,但当前系统没有 tops profile,不能当作低成本改动。
|
||||||
|
|
||||||
|
## 12. 当前推荐决策
|
||||||
|
|
||||||
|
建议确认以下产品决策后再进入代码实现:
|
||||||
|
|
||||||
|
1. Sentinel-1 生产第一阶段要求保留原始 SLC ZIP。
|
||||||
|
2. 配对策略命名为 `sentinel1_dinsar_pairing`。
|
||||||
|
3. 生产 profile 命名为 `s1_gamma_dinsar`。
|
||||||
|
4. 第一阶段只支持 `IW + SLC + 同 relative orbit + 同方向 + 兼容极化`。
|
||||||
|
5. `centroid_distance_meters` 与 `perpendicular_baseline_meters` 严格区分。
|
||||||
|
6. 真实垂直基线由 Gamma/PyINT 处理后回填。
|
||||||
|
7. ISCE2 Sentinel-1 TOPS 放到第二阶段。
|
||||||
|
|
||||||
|
这套边界可以把系统改造控制在数据管理、配对、分发、生产托管和结果管理范围内,把 SAR 处理精度交给 Gamma/PyINT,避免在配对阶段过度复杂化。
|
||||||
@@ -11,23 +11,23 @@ const STRATEGY_DESCRIPTIONS = {
|
|||||||
title: '全部配对(默认)',
|
title: '全部配对(默认)',
|
||||||
description: '列出所有满足约束条件的候选干涉对,由用户自行筛选。',
|
description: '列出所有满足约束条件的候选干涉对,由用户自行筛选。',
|
||||||
details: [
|
details: [
|
||||||
'• 系统遍历所有影像组合,保留满足时间基线、footprint 中心距和两景 footprint 最小重叠率的配对',
|
'• 系统遍历所有影像组合,保留满足时间基线和两景 footprint 最小重叠率的配对',
|
||||||
'• 结果按时间排序,用户可在配对列表中逐一勾选或取消',
|
'• 结果按时间排序,用户可在配对列表中逐一勾选或取消',
|
||||||
'• 适用于研究型场景,需要精确控制每一对干涉组合',
|
'• 适用于研究型场景,需要精确控制每一对干涉组合',
|
||||||
'• 配对数量可能较多,建议配合 AOI 和日期范围缩小结果'
|
'• 配对数量可能较多,建议配合 AOI 和日期范围缩小结果'
|
||||||
],
|
],
|
||||||
params: '参数:时间基线范围、中心距上限、两景 footprint 最小重叠率'
|
params: '参数:时间基线范围、两景 footprint 最小重叠率、可选 footprint 中心距上限'
|
||||||
},
|
},
|
||||||
sbas: {
|
sbas: {
|
||||||
title: 'SBAS (短基线子集)',
|
title: 'SBAS (短基线子集)',
|
||||||
description: '基于短基线原则的配对策略,通过覆盖优化算法自动筛选配对。',
|
description: '基于短基线原则的配对策略,通过覆盖优化算法自动筛选配对。',
|
||||||
details: [
|
details: [
|
||||||
'• 优先选择时间间隔和 footprint 中心距都较短的配对',
|
'• 优先选择时间间隔较短、覆盖质量较好的配对;可按需启用 footprint 中心距限制',
|
||||||
'• 通过覆盖优化算法,去除冗余配对,确保时间序列连续性',
|
'• 通过覆盖优化算法,去除冗余配对,确保时间序列连续性',
|
||||||
'• 适用于大范围、长时间序列的形变监测',
|
'• 适用于大范围、长时间序列的形变监测',
|
||||||
'• 配对数量会比"全部配对"少,但覆盖更均匀'
|
'• 配对数量会比"全部配对"少,但覆盖更均匀'
|
||||||
],
|
],
|
||||||
params: '参数:时间基线、中心距、两景 footprint 最小重叠率、覆盖多样性惩罚'
|
params: '参数:时间基线、两景 footprint 最小重叠率、覆盖多样性惩罚、可选 footprint 中心距上限'
|
||||||
},
|
},
|
||||||
sequential: {
|
sequential: {
|
||||||
title: 'Sequential (顺序配对)',
|
title: 'Sequential (顺序配对)',
|
||||||
@@ -337,10 +337,30 @@ function PairingModal({
|
|||||||
<input type="number" step="0.1" min="0" max="1" value={pairingParams.overlap_threshold}
|
<input type="number" step="0.1" min="0" max="1" value={pairingParams.overlap_threshold}
|
||||||
onChange={e => setPairingParams({...pairingParams, overlap_threshold: parseFloat(e.target.value) || 0})} />
|
onChange={e => setPairingParams({...pairingParams, overlap_threshold: parseFloat(e.target.value) || 0})} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-group checkbox-group">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="limit-footprint-center-distance"
|
||||||
|
checked={Boolean(pairingParams.limit_footprint_center_distance)}
|
||||||
|
onChange={e => setPairingParams({
|
||||||
|
...pairingParams,
|
||||||
|
limit_footprint_center_distance: e.target.checked
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<label htmlFor="limit-footprint-center-distance">限制 footprint 中心距</label>
|
||||||
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>footprint 中心距上限 (米):</label>
|
<label>footprint 中心距上限 (米):</label>
|
||||||
<input type="number" min="0" value={pairingParams.spatial_baseline_max_meters}
|
<input
|
||||||
onChange={e => setPairingParams({...pairingParams, spatial_baseline_max_meters: parseInt(e.target.value) || 3000})} />
|
type="number"
|
||||||
|
min="0"
|
||||||
|
value={pairingParams.spatial_baseline_max_meters}
|
||||||
|
disabled={!pairingParams.limit_footprint_center_distance}
|
||||||
|
onChange={e => setPairingParams({...pairingParams, spatial_baseline_max_meters: parseInt(e.target.value) || 3000})}
|
||||||
|
/>
|
||||||
|
{!pairingParams.limit_footprint_center_distance && (
|
||||||
|
<div style={{ fontSize: '12px', color: '#6b7280', marginTop: '4px' }}>默认不按中心距过滤;勾选后使用上方数值。</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>覆盖多样性惩罚 (0-1):</label>
|
<label>覆盖多样性惩罚 (0-1):</label>
|
||||||
|
|||||||
@@ -10,6 +10,30 @@ import {
|
|||||||
} from '../store';
|
} from '../store';
|
||||||
import { getSelectedRegionTreeId } from '../utils/appUiHelpers';
|
import { getSelectedRegionTreeId } from '../utils/appUiHelpers';
|
||||||
|
|
||||||
|
const compactDinsarBatchScene = (scene = {}) => ({
|
||||||
|
file_path: scene.file_path || '',
|
||||||
|
satellite: scene.satellite || null,
|
||||||
|
imaging_date: scene.imaging_date || null,
|
||||||
|
imaging_mode: scene.imaging_mode || null,
|
||||||
|
polarization: scene.polarization || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const compactDinsarBatchPair = (pair = {}) => ({
|
||||||
|
task_name: pair.task_name || null,
|
||||||
|
task_alias: pair.task_alias || pair.task_name || null,
|
||||||
|
pair_key: pair.pair_key || null,
|
||||||
|
pair_uid: pair.pair_uid || null,
|
||||||
|
network_run_id: pair.network_run_id || null,
|
||||||
|
network_edge_id: pair.network_edge_id ?? null,
|
||||||
|
policy_version: pair.policy_version || null,
|
||||||
|
selection_strategy: pair.selection_strategy || null,
|
||||||
|
time_baseline_days: pair.time_baseline_days ?? null,
|
||||||
|
spatial_baseline_meters: pair.spatial_baseline_meters ?? null,
|
||||||
|
scene_center_distance_meters: pair.scene_center_distance_meters ?? pair.spatial_baseline_meters ?? null,
|
||||||
|
master: compactDinsarBatchScene(pair.master),
|
||||||
|
slave: compactDinsarBatchScene(pair.slave),
|
||||||
|
});
|
||||||
|
|
||||||
export default function usePairingLogic({
|
export default function usePairingLogic({
|
||||||
fetchRegionGeometry,
|
fetchRegionGeometry,
|
||||||
refreshBatchList,
|
refreshBatchList,
|
||||||
@@ -116,9 +140,10 @@ export default function usePairingLogic({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const batchPairs = selectedPairs.map(compactDinsarBatchPair);
|
||||||
const response = await apiClient.post('/task-batches/dinsar', {
|
const response = await apiClient.post('/task-batches/dinsar', {
|
||||||
name: `DINSAR_${new Date().toISOString().slice(0, 10)}`,
|
name: `DINSAR_${new Date().toISOString().slice(0, 10)}`,
|
||||||
pairs: selectedPairs
|
pairs: batchPairs
|
||||||
});
|
});
|
||||||
const batchId = response.data?.batch_id || '';
|
const batchId = response.data?.batch_id || '';
|
||||||
addLog('success', `已创建 D-InSAR 批次: ${batchId || 'OK'}`);
|
addLog('success', `已创建 D-InSAR 批次: ${batchId || 'OK'}`);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const usePairingStore = create((set) => ({
|
|||||||
time_baseline_max: 90,
|
time_baseline_max: 90,
|
||||||
overlap_threshold: 0.5,
|
overlap_threshold: 0.5,
|
||||||
spatial_baseline_max_meters: 3000,
|
spatial_baseline_max_meters: 3000,
|
||||||
|
limit_footprint_center_distance: false,
|
||||||
coverage_diversity_penalty: 0.3,
|
coverage_diversity_penalty: 0.3,
|
||||||
require_same_imaging_mode: false,
|
require_same_imaging_mode: false,
|
||||||
require_same_polarization: false,
|
require_same_polarization: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user