Apply current workspace changes

This commit is contained in:
2026-04-22 01:52:21 +08:00
parent cf34b9a8ed
commit cb518b431f
449 changed files with 92252 additions and 321 deletions
@@ -0,0 +1,394 @@
#! /usr/bin/env python
#################################################################
### This program is part of PyINT v2.1 ###
### Pixel Offset Tracking (POT) for a single pair ###
### Based on GAMMA offset_pwr_tracking / offset_pwr_tracking2###
### Author: ZYD / Cascade AI ###
#################################################################
import os
import sys
import time
import argparse
import numpy as np
from pyint import _utils as ut
INTRODUCTION = '''
-------------------------------------------------------------------
Pixel Offset Tracking (POT) for a single interferometric pair
using GAMMA software.
Two-round estimation approach (Greenland tracking demo):
Round 1: Large search window for initial offset field
Post-processing: Quality check, outlier removal, gap filling
Round 2: Smaller window with conditioned Round 1 as initial
Final: Convert pixel offsets to displacement in meters + Geocode
'''
EXAMPLE = '''
Usage:
POT_gamma.py projectName Mdate Sdate
POT_gamma.py shanghaiT171F128S1A 20241105 20241117
-------------------------------------------------------------------
'''
def run_cmd(cmd_str):
"""执行 GAMMA 命令并打印"""
print(f' >> {cmd_str}')
return os.system(cmd_str)
def sanitize_gamma_float(filepath, valid_max=1e6):
"""
清理 GAMMA 浮点数据文件中的无效值。
GAMMA 对无效像素写入特殊标记值(~3.4e38 / NaN / Inf),
这些值无法被 single_class_mapping 的范围过滤器正确捕获。
将 NaN / Inf / |val| > valid_max 的像素替换为 0.0。
"""
data = np.fromfile(filepath, dtype=np.float32)
bad_mask = ~np.isfinite(data) | (np.abs(data) > valid_max)
n_bad = int(np.sum(bad_mask))
if n_bad > 0:
data[bad_mask] = 0.0
data.tofile(filepath)
print(f' [sanitize] {os.path.basename(filepath)}: '
f'清理 {n_bad} 个无效像素 (NaN/Inf/|val|>{valid_max})')
def postprocess_offsets(offs_cpx, ccp, mli, width,
ccp_thresh, roff_min, roff_max,
azoff_min, azoff_max,
drange_thresh, dazimuth_thresh,
median_win, median_nmin,
prefix):
"""
偏移量场后处理流程(参考 GAMMA Greenland tracking demo:
1) 提取距离向/方位向分量 + 清理 GAMMA no-data 标记值
2) 初始掩膜: 互相关阈值 + 偏移量范围限制
3) 中值滤波 + 偏差计算
4) 偏差阈值精细掩膜
5) 空洞填充
6) 空间滤波 → 组合为 conditioned 复数偏移量
返回: (condi, real_interp, imag_interp)
- condi: fspf 平滑后的复数偏移量(供 Round 2 初始值)
- real_interp / imag_interp: 填充后的偏移量(供最终米制转换)
"""
real_file = prefix + '.real'
imag_file = prefix + '.imag'
# --- 1) 提取距离向 (real) 和方位向 (imag) ---
run_cmd(f'cpx_to_real {offs_cpx} {real_file} {width} 0')
run_cmd(f'cpx_to_real {offs_cpx} {imag_file} {width} 1')
# 清理 GAMMA no-data 标记值(~3.4e38),避免污染后续掩膜和插值
valid_max = max(abs(float(roff_max)), abs(float(roff_min)),
abs(float(azoff_max)), abs(float(azoff_min))) * 10
sanitize_gamma_float(real_file, valid_max)
sanitize_gamma_float(imag_file, valid_max)
sanitize_gamma_float(ccp, 1.0)
# --- 2) 初始掩膜: 互相关 + 偏移量范围 ---
mask1 = prefix + '.mask1.bmp'
real_m1 = prefix + '.real.masked1'
imag_m1 = prefix + '.imag.masked1'
run_cmd(f'single_class_mapping 3 '
f'{ccp} {ccp_thresh} 1.0 '
f'{real_file} {roff_min} {roff_max} '
f'{imag_file} {azoff_min} {azoff_max} '
f'{mask1} {width} 1 0 1 1')
run_cmd(f'mask_class {mask1} {real_file} {real_m1} 0 1 1 1 0 0.0')
run_cmd(f'mask_class {mask1} {imag_file} {imag_m1} 0 1 1 1 0 0.0')
# 初始掩膜后 BMP
run_cmd(f'rasdt_pwr {real_m1} {mli} {width} - - - - '
f'{roff_min} {roff_max} 0 rmg.cm {real_m1}.bmp - - 24')
run_cmd(f'rasdt_pwr {imag_m1} {mli} {width} - - - - '
f'{azoff_min} {azoff_max} 0 rmg.cm {imag_m1}.bmp - - 24')
# --- 3) 中值滤波 + 偏差 ---
real_med = prefix + '.real.median'
imag_med = prefix + '.imag.median'
dreal = prefix + '.dreal'
dimag = prefix + '.dimag'
run_cmd(f'median_filter {real_m1} {real_med} {width} '
f'{median_win} {median_win} {median_nmin}')
run_cmd(f'lin_comb 2 {real_m1} {real_med} 0. 1. -1. '
f'{dreal} {width} 1 0 1 1')
run_cmd(f'median_filter {imag_m1} {imag_med} {width} '
f'{median_win} {median_win} {median_nmin}')
run_cmd(f'lin_comb 2 {imag_m1} {imag_med} 0. 1. -1. '
f'{dimag} {width} 1 0 1 1')
# --- 4) 偏差阈值精细掩膜 ---
mask2 = prefix + '.mask2.bmp'
real_masked = prefix + '.real.masked'
imag_masked = prefix + '.imag.masked'
run_cmd(f'single_class_mapping 5 '
f'{dreal} -{drange_thresh} {drange_thresh} '
f'{dimag} -{dazimuth_thresh} {dazimuth_thresh} '
f'{ccp} {ccp_thresh} 1.0 '
f'{real_file} {roff_min} {roff_max} '
f'{imag_file} {azoff_min} {azoff_max} '
f'{mask2} {width} 1 0 1 1 5')
run_cmd(f'mask_class {mask2} {real_file} {real_masked} 0 1 1 1 0 0.0')
run_cmd(f'mask_class {mask2} {imag_file} {imag_masked} 0 1 1 1 0 0.0')
# 精细掩膜后 BMP
run_cmd(f'rasdt_pwr {real_masked} {mli} {width} - - - - '
f'{roff_min} {roff_max} 0 rmg.cm {real_masked}.bmp - - 24')
run_cmd(f'rasdt_pwr {imag_masked} {mli} {width} - - - - '
f'{azoff_min} {azoff_max} 0 rmg.cm {imag_masked}.bmp - - 24')
# --- 5) 空洞填充 ---
real_interp = prefix + '.real.interp'
imag_interp = prefix + '.imag.interp'
run_cmd(f'fill_gaps {real_masked} {width} {real_interp} 0 4 - 1')
run_cmd(f'fill_gaps {imag_masked} {width} {imag_interp} 0 4 - 1')
# 清理 fill_gaps 插值可能引入的 NaN/Inf/极端值
sanitize_gamma_float(real_interp, valid_max)
sanitize_gamma_float(imag_interp, valid_max)
# 填充后 BMP
run_cmd(f'rasdt_pwr {real_interp} {mli} {width} - - - - '
f'{roff_min} {roff_max} 0 rmg.cm {real_interp}.bmp - - 24')
run_cmd(f'rasdt_pwr {imag_interp} {mli} {width} - - - - '
f'{azoff_min} {azoff_max} 0 rmg.cm {imag_interp}.bmp - - 24')
# --- 6) 空间滤波 + 组合 conditioned ---
real_fspf = prefix + '.real.fspf'
imag_fspf = prefix + '.imag.fspf'
run_cmd(f'fspf {real_interp} {real_fspf} {width} 2 2 2')
run_cmd(f'fspf {imag_interp} {imag_fspf} {width} 2 2 2')
condi = prefix + '.condi'
run_cmd(f'real_to_cpx {real_fspf} {imag_fspf} {condi} {width} 0')
return condi, real_interp, imag_interp
def cmdLineParse():
parser = argparse.ArgumentParser(
description='Pixel Offset Tracking for a single pair using GAMMA.',
formatter_class=argparse.RawTextHelpFormatter,
epilog=INTRODUCTION + '\n' + EXAMPLE)
parser.add_argument('projectName', help='projectName for processing.')
parser.add_argument('Mdate', help='Master date.')
parser.add_argument('Sdate', help='Slave date.')
inps = parser.parse_args()
return inps
def main(argv):
start_time = time.time()
inps = cmdLineParse()
Mdate = inps.Mdate
Sdate = inps.Sdate
projectName = inps.projectName
scratchDir = os.getenv('SCRATCHDIR')
templateDir = os.getenv('TEMPLATEDIR')
templateFile = templateDir + '/' + projectName + '.template'
templateDict = ut.update_template(templateFile)
rlks = templateDict['range_looks']
azlks = templateDict['azimuth_looks']
masterDate = templateDict['masterDate']
# ========== POT 参数 ==========
pot_rstep = templateDict['pot_rstep']
pot_azstep = templateDict['pot_azstep']
pot_rwin = templateDict['pot_rwin']
pot_azwin = templateDict['pot_azwin']
pot_rwin2 = templateDict['pot_rwin2']
pot_azwin2 = templateDict['pot_azwin2']
pot_ovr = templateDict['pot_ovr']
pot_snr_thresh = templateDict['pot_snr_thresh']
pot_ccp_thresh = templateDict['pot_ccp_thresh']
pot_roff_min = templateDict['pot_roff_min']
pot_roff_max = templateDict['pot_roff_max']
pot_azoff_min = templateDict['pot_azoff_min']
pot_azoff_max = templateDict['pot_azoff_max']
pot_drange_thresh = templateDict['pot_drange_thresh']
pot_dazimuth_thresh = templateDict['pot_dazimuth_thresh']
pot_median_win = templateDict['pot_median_win']
pot_median_nmin = templateDict['pot_median_nmin']
pot_two_rounds = templateDict['pot_two_rounds']
pot_geocode = templateDict['pot_geocode']
pot_disp_max = templateDict['pot_disp_max']
# ========== 目录 ==========
projectDir = scratchDir + '/' + projectName
rslcDir = projectDir + '/RSLC'
demDir = projectDir + '/DEM'
potDir = projectDir + '/offsets'
if not os.path.isdir(potDir):
os.mkdir(potDir)
Pair = Mdate + '-' + Sdate
workDir = potDir + '/' + Pair
if not os.path.isdir(workDir):
os.mkdir(workDir)
# ========== 输入文件 ==========
Mrslc = rslcDir + '/' + Mdate + '/' + Mdate + '.rslc'
MrslcPar = rslcDir + '/' + Mdate + '/' + Mdate + '.rslc.par'
Srslc = rslcDir + '/' + Sdate + '/' + Sdate + '.rslc'
SrslcPar = rslcDir + '/' + Sdate + '/' + Sdate + '.rslc.par'
slc_width = ut.read_gamma_par(MrslcPar, 'read', 'range_samples')
off_width = str(int(int(slc_width) // int(pot_rstep)))
print('=' * 60)
print(f'Pixel Offset Tracking (POT): {Pair}')
print(f' SLC width : {slc_width}')
print(f' Offset width : {off_width}')
print(f' Step (r x az) : {pot_rstep} x {pot_azstep}')
print(f' R1 window : {pot_rwin} x {pot_azwin}')
if pot_two_rounds == '1':
print(f' R2 window : {pot_rwin2} x {pot_azwin2}')
print(f' Offset range : [{pot_roff_min}, {pot_roff_max}] r '
f'[{pot_azoff_min}, {pot_azoff_max}] az')
print('=' * 60)
#######################################################################
# Step 1: 生成偏移量几何下的 MLI(背景图 + 尺寸参考)
#######################################################################
print('\n[Step 1] 生成偏移量几何 MLI ...')
MLI_pot = workDir + '/' + Mdate + '.mli_pot'
MLI_pot_par = workDir + '/' + Mdate + '.mli_pot.par'
run_cmd(f'multi_look {Mrslc} {MrslcPar} {MLI_pot} {MLI_pot_par} '
f'{pot_rstep} {pot_azstep}')
run_cmd(f'raspwr {MLI_pot} {off_width} - - - - 1. .2 - {MLI_pot}.bmp')
#######################################################################
# Step 2: 创建偏移量参数文件
#######################################################################
print('\n[Step 2] 创建偏移量参数文件 ...')
OFF = workDir + '/' + Pair + '.off'
run_cmd(f'create_offset {MrslcPar} {SrslcPar} {OFF} 1 {rlks} {azlks} 0')
#######################################################################
# Step 3: Round 1 — 初始偏移量估计(大窗口)
#######################################################################
print(f'\n[Step 3] Round 1 偏移量估计 ({pot_rwin}x{pot_azwin}) ...')
r1_tag = f'{pot_rwin}x{pot_azwin}'
r1_prefix = workDir + '/' + Pair + '.offs' + r1_tag
offs_r1 = r1_prefix
ccp_r1 = workDir + '/' + Pair + '.ccp' + r1_tag
run_cmd(f'offset_pwr_tracking {Mrslc} {Srslc} {MrslcPar} {SrslcPar} '
f'{OFF} {offs_r1} {ccp_r1} '
f'{pot_rwin} {pot_azwin} - {pot_ovr} {pot_snr_thresh} '
f'{pot_rstep} {pot_azstep}')
#######################################################################
# Step 4: Round 1 后处理
#######################################################################
print(f'\n[Step 4] Round 1 后处理 ...')
r1_condi, _, _ = postprocess_offsets(
offs_cpx=offs_r1, ccp=ccp_r1, mli=MLI_pot, width=off_width,
ccp_thresh=pot_ccp_thresh,
roff_min=pot_roff_min, roff_max=pot_roff_max,
azoff_min=pot_azoff_min, azoff_max=pot_azoff_max,
drange_thresh=pot_drange_thresh, dazimuth_thresh=pot_dazimuth_thresh,
median_win=pot_median_win, median_nmin=pot_median_nmin,
prefix=r1_prefix)
# 默认使用 Round 1 结果
final_prefix = r1_prefix
final_ccp = ccp_r1
final_off = OFF
#######################################################################
# Step 5-6: Round 2 — 精细偏移量估计(小窗口,可选)
#######################################################################
if pot_two_rounds == '1':
print(f'\n[Step 5] Round 2 偏移量估计 ({pot_rwin2}x{pot_azwin2}) ...')
OFF2 = workDir + '/' + Pair + '.off2'
run_cmd(f'create_offset {MrslcPar} {SrslcPar} {OFF2} 1 {rlks} {azlks} 0')
r2_tag = f'{pot_rwin2}x{pot_azwin2}'
r2_prefix = workDir + '/' + Pair + '.offs' + r2_tag
offs_r2 = r2_prefix
ccp_r2 = workDir + '/' + Pair + '.ccp' + r2_tag
run_cmd(f'offset_pwr_tracking2 {Mrslc} {Srslc} {MrslcPar} {SrslcPar} '
f'{OFF2} {offs_r2} {ccp_r2} {OFF} {r1_condi} '
f'{pot_rwin2} {pot_azwin2} - {pot_ovr} {pot_snr_thresh} '
f'{pot_rstep} {pot_azstep}')
print(f'\n[Step 6] Round 2 后处理 ...')
_, _, _ = postprocess_offsets(
offs_cpx=offs_r2, ccp=ccp_r2, mli=MLI_pot, width=off_width,
ccp_thresh=pot_ccp_thresh,
roff_min=pot_roff_min, roff_max=pot_roff_max,
azoff_min=pot_azoff_min, azoff_max=pot_azoff_max,
drange_thresh=pot_drange_thresh, dazimuth_thresh=pot_dazimuth_thresh,
median_win=pot_median_win, median_nmin=pot_median_nmin,
prefix=r2_prefix)
final_prefix = r2_prefix
final_ccp = ccp_r2
final_off = OFF2
#######################################################################
# Step 7: 像素偏移量 → 米制位移量
#######################################################################
print('\n[Step 7] 像素偏移量转换为地面位移 (米) ...')
# 用填充后(非 fspf 平滑)的偏移量组合复数,供 offset_tracking 使用
final_real_interp = final_prefix + '.real.interp'
final_imag_interp = final_prefix + '.imag.interp'
final_offs_combined = final_prefix + '.offs_combined'
run_cmd(f'real_to_cpx {final_real_interp} {final_imag_interp} '
f'{final_offs_combined} {off_width} 0')
disp_map = workDir + '/' + Pair + '.disp_map'
run_cmd(f'offset_tracking {final_offs_combined} {final_ccp} '
f'{MrslcPar} {final_off} {disp_map} - 2 {pot_ccp_thresh} 0')
# 提取位移分量
disp_real = disp_map + '.real' # 地距向位移 (米)
disp_imag = disp_map + '.imag' # 方位向位移 (米)
disp_mag = disp_map + '.mag' # 位移幅值 (米)
run_cmd(f'cpx_to_real {disp_map} {disp_real} {off_width} 0')
run_cmd(f'cpx_to_real {disp_map} {disp_imag} {off_width} 1')
run_cmd(f'cpx_to_real {disp_map} {disp_mag} {off_width} 3')
# 清理 offset_tracking 输出中的 NaN/Inf/极端值
disp_max_m = float(pot_disp_max) * 10
sanitize_gamma_float(disp_real, disp_max_m)
sanitize_gamma_float(disp_imag, disp_max_m)
sanitize_gamma_float(disp_mag, disp_max_m)
# BMP 可视化
run_cmd(f'rasdt_pwr {disp_real} {MLI_pot} {off_width} - - - - '
f'-{pot_disp_max} {pot_disp_max} 1 rmg.cm {disp_real}.bmp - - 24')
run_cmd(f'rasdt_pwr {disp_imag} {MLI_pot} {off_width} - - - - '
f'-{pot_disp_max} {pot_disp_max} 1 rmg.cm {disp_imag}.bmp - - 24')
run_cmd(f'rasdt_pwr {disp_mag} {MLI_pot} {off_width} - - - - '
f'-{pot_disp_max} {pot_disp_max} 1 rmg.cm {disp_mag}.bmp - - 24')
# 注意: 地理编码已移至 geocode_gamma.py --type pot
# 用法: geocode_gamma.py projectName Pair --type pot
print(f"\nPixel Offset Tracking for {Pair} is done!")
ut.print_process_time(start_time, time.time())
sys.exit(0)
if __name__ == '__main__':
main(sys.argv[:])