feat: add D-InSAR source bundle distribution

This commit is contained in:
2026-05-11 11:08:53 +08:00
parent 6893041a9f
commit 508ca5641a
11 changed files with 1150 additions and 63 deletions
@@ -279,7 +279,10 @@ score =
- `dest_dir`
- `copy_statuses`,为空时默认 `["COMPLETED"]`
- `include_orbit_files`,默认 `false`;为 `true` 时把 master/slave 精轨复制到 Task 内的 `orbit/`
- `export_zip`默认 `false`;为 `true` 时每个 Task 输出为一个 `.zip`
- `package_mode`支持 `task_folder``task_zip``source_bundle`
- `export_zip`,兼容旧参数;为 `true``package_mode=task_folder` 时等价于 `task_zip`
- `skip_existing`,默认 `true`
- `max_items`,每次最多处理的新 Task 或新 pair 数量;为空或 0 表示不限制
后端动作:
@@ -288,7 +291,9 @@ score =
3. 创建 `SystemJob`job_type 也是 `COPY_DATA`
4. worker 领取 job 后进入 `job_handlers._handle_copy_data()`
5. `_handle_copy_data()` 根据 `batch_id` 查询 `dinsar_task_items`,只取 `copy_statuses` 命中的条目。
6. 调用 [backend/app/copier.py](../backend/app/copier.py) 的 `run_dinsar_copy_items()`
6. 根据 `package_mode` 调用 [backend/app/copier.py](../backend/app/copier.py) 的 `run_dinsar_copy_items()``run_dinsar_source_bundle_items()`
### 9.1 Task 文件夹 / ZIP 模式
`run_dinsar_copy_items()` 对每个 item 执行:
@@ -298,8 +303,10 @@ score =
- slave 目录:`<task_alias>/slave`
- 如果启用 `include_orbit_files`,从 `radar_data.orbit_file_path` 找 master/slave 精轨并复制到 `<task_alias>/orbit/`
- 直接复制配对时保存的原始产品目录;D-InSAR 分发不再优先使用 `envi_import/`
- 使用 `shutil.copytree(..., dirs_exist_ok=True)` 复制 master/slave
- 先复制到临时目录,完成后再替换为最终 Task 目录或 ZIP,避免留下半成品
- 写入 `<task_alias>/.dinsar_pair.json`
- `skip_existing=true` 时,文件夹模式检查 `<task_alias>/master``<task_alias>/slave` 非空即跳过;ZIP 模式只检查 `<task_alias>.zip` 存在且大小大于 0,不打开 ZIP 做深度校验
- `max_items` 限制本次新复制数量;已跳过的既有 Task 不消耗本次额度
`.dinsar_pair.json` 是后续生产追踪的关键 sidecar,包含:
@@ -319,7 +326,32 @@ score =
- `selection_strategy`
- `copied_at`
当前实现不会清空已有 Task 目录,而是合并复制;如果目标已有旧文件,需要人工确认目录状态
当前实现不再合并写入已有 Task 目录。目标 Task 已完整存在时跳过;目标同名目录存在但不完整时,为避免误覆盖,会报错并要求人工处理
### 9.2 去重源数据包模式
`package_mode=source_bundle` 时,分发不生成每个 `Task_*`,而是在同一个目标目录内维护:
```text
<dest_dir>/
data/
orbit/
pairs.json
manifest.json
```
规则:
- `data/` 只复制唯一源影像目录或文件,命名为 `scene_<source_path_hash>_<source_name>`
- `orbit/` 只复制唯一精密轨道文件,命名为 `orbit_<source_path_hash>_<orbit_name>`
- `pairs.json` 记录每个 pair 的 master/slave 数据相对路径、轨道相对路径、pair 元数据和 `identity_key`
- `manifest.json` 记录 package 统计信息、场景清单、轨道清单和本次追加统计。
- 同一目标目录再次分发时,系统先读取已有 `pairs.json/manifest.json`,根据 `identity_key``scene_pair_uid/pair_uid``pair_key``network_run_id + network_edge_id`、源路径或 bundle 相对路径识别已导出的 pair。
- `max_items` 在跳过已导出 pair 后生效。因此 500 个 pair 第一次限制 100,第二次同一目录限制 100,会追加下一批未导出的 100 个 pair。
- `data/` / `orbit/` 仍按文件存在性跳过重复复制;pair 级续跑以 `pairs.json` 为准。
- `pairs.json``manifest.json` 写入时先写临时文件,再原子替换。
这个模式面向外部分发和后续离线还原,不直接作为本系统生产输入。反向还原工具任务书见 [DINSAR_SOURCE_BUNDLE_REVERSE_TOOL_TASK_20260511.md](DINSAR_SOURCE_BUNDLE_REVERSE_TOOL_TASK_20260511.md)。
## 10. 生产提交与运行分发
@@ -0,0 +1,168 @@
# D-InSAR 去重源数据包反向还原工具任务书
日期:2026-05-11
## 背景
本系统新增“去重源数据包”分发模式。该模式不直接生成每个干涉对的 `Task_*` 目录,而是只分发唯一源影像、唯一精密轨道文件和配对关系文件,减少外部分发时的重复复制量。
反向还原工具由任务接收方本地运行,将去重源数据包还原为传统 D-InSAR `Task_*` 目录结构。
## 输入目录结构
```text
BundleRoot/
data/
scene_<hash>_<source_name>/
...
orbit/
orbit_<hash>_<orbit_name>.txt
...
pairs.json
manifest.json
```
`orbit/` 可能不存在,或 `pairs.json` 内某些配对的轨道字段为空。
## 输出目录结构
```text
OutputRoot/
Task_YYYYMMDD_YYYYMMDD/
master/
<master source product content>
slave/
<slave source product content>
orbit/
<master/slave orbit files, if present>
.dinsar_pair.json
```
输出目录名称优先使用 `pairs.json` 内的 `task_alias`,若为空则使用 `task_name`,再为空则使用 `pair_id`
## pairs.json 关键字段
```json
{
"schema": "dinsar_source_bundle_pairs.v1",
"exported_at": "2026-05-11T00:00:00Z",
"pairs": [
{
"pair_id": "pair_0001",
"identity_key": "uid:<scene_pair_uid>",
"task_name": "Task_20250101_20250113",
"task_alias": "Task_20250101_20250113",
"master_source_path": "D:/Source/master",
"slave_source_path": "D:/Source/slave",
"master_scene_id": "scene_0001",
"slave_scene_id": "scene_0002",
"master_data": "data/scene_xxx_master",
"slave_data": "data/scene_yyy_slave",
"master_orbit_id": "orbit_0001",
"slave_orbit_id": "orbit_0002",
"master_orbit_source_path": "D:/Orbit/master.EOF",
"slave_orbit_source_path": "D:/Orbit/slave.EOF",
"master_orbit": "orbit/orbit_xxx.txt",
"slave_orbit": "orbit/orbit_yyy.txt",
"master_imaging_date": "20250101",
"slave_imaging_date": "20250113",
"time_baseline_days": 12
}
]
}
```
## 本系统分发续跑规则
去重源数据包支持向同一个 `BundleRoot` 多次分发:
- 每次启动时先读取目标目录内已有的 `pairs.json``manifest.json`
- 已导出的 pair 通过 `identity_key``scene_pair_uid/pair_uid``pair_key``network_run_id + network_edge_id``master/slave_source_path``master_data + slave_data` 识别。
- 开启“每次最多追加新配对”时,系统会先跳过已导出的 pair,再从剩余 pair 中取下一批追加;例如 500 个 pair 第一次限制 100,第二次同一目录仍限制 100 时,会追加第 101-200 个未导出的 pair。
- `data/``orbit/` 按源路径哈希命名,已有文件或目录在 `skip_existing` 开启时不会重复复制。
- `pairs.json``manifest.json` 采用临时文件写入后原子替换,避免中途失败留下半写 JSON。
注意:如果用户手动删除了 `pairs.json`,系统无法再根据记录判断哪些 pair 已经分发,只能根据重新生成的 `data/` 路径做源数据级去重,pair 级续跑能力会丢失。
## 还原规则
1. 读取 `pairs.json`
2. 对每个 pair 创建目标 `Task` 目录。
3.`master_data` 指向的数据复制到 `Task/master/`
4.`slave_data` 指向的数据复制到 `Task/slave/`
5.`master_orbit` / `slave_orbit` 存在,将轨道文件复制到 `Task/orbit/`
6. 生成 `.dinsar_pair.json`,至少保留:
- `pair_id`
- `identity_key`
- `task_name`
- `task_alias`
- `master_scene_id`
- `slave_scene_id`
- `master_data`
- `slave_data`
- `master_orbit_id`
- `slave_orbit_id`
- `master_orbit`
- `slave_orbit`
- `master_imaging_date`
- `slave_imaging_date`
- `time_baseline_days`
- `restored_at`
7. 每个 Task 应采用临时目录还原,全部成功后再重命名为最终目录,避免半成品。
## 覆盖策略
工具应提供参数:
- `--skip-existing`:默认开启。若目标 `Task/master``Task/slave` 均存在且非空,则跳过。
- `--overwrite`:删除并重建已存在的目标 Task。
- `--limit N`:最多还原 N 个 pair,便于分批执行。
- `--dry-run`:只打印计划,不复制。
`--skip-existing``--overwrite` 同时出现时应报错。
## 校验要求
启动前:
- 检查 `pairs.json` 是否存在且可解析。
- 检查 `data/` 是否存在。
- 检查每个 pair 的 `master_data` / `slave_data` 是否存在。
- 轨道缺失不应阻断还原,但要记录 warning。
还原后:
- `Task/master/` 非空。
- `Task/slave/` 非空。
- `.dinsar_pair.json` 存在。
## 日志与报告
工具结束后输出 `restore_report.json`
```json
{
"started_at": "...",
"finished_at": "...",
"input_root": "...",
"output_root": "...",
"total_pairs": 20,
"restored": 18,
"skipped": 2,
"failed": 0,
"warnings": []
}
```
同时建议输出人类可读日志 `restore.log`
## 建议实现
建议使用 Python 3.10+
- `argparse` 处理命令行参数。
- `pathlib.Path` 处理路径。
- `shutil.copytree(..., dirs_exist_ok=True)` / `shutil.copy2()` 处理复制。
- Windows 下注意长路径和权限异常。
该工具不需要连接本系统数据库,也不需要调用本系统 API。
+2
View File
@@ -68,6 +68,8 @@
Sentinel-1 D-InSAR 在不使用 ENVI + SARscape 核心时,基于 Gamma/PyINT 与 ISCE2 的可行性、接入边界和推荐实施顺序。
- [DINSAR_PAIRING_DISTRIBUTION_LOGIC_20260508.md](DINSAR_PAIRING_DISTRIBUTION_LOGIC_20260508.md)
2026-05-08 源码走读记录,梳理 D-InSAR 配对缓存、策略筛选、批次保存、数据分发和生产 worker 执行链路。
- [DINSAR_SOURCE_BUNDLE_REVERSE_TOOL_TASK_20260511.md](DINSAR_SOURCE_BUNDLE_REVERSE_TOOL_TASK_20260511.md)
D-InSAR 去重源数据包的目录协议、续分发规则和外部反向还原工具任务书。
- [PAIRING_ENHANCEMENT_DESIGN.md](PAIRING_ENHANCEMENT_DESIGN.md)
- [FRONTEND_NAVIGATION_ARCHITECTURE.md](FRONTEND_NAVIGATION_ARCHITECTURE.md)