1187 lines
43 KiB
Python
1187 lines
43 KiB
Python
#! /usr/bin/env python
|
||
#################################################################
|
||
### This program is part of PyINT v2.1 ###
|
||
### Copy Right (c): 2017-2019, Yunmeng Cao ###
|
||
### Author: Yunmeng Cao ###
|
||
### Contact : ymcmrs@gmail.com ###
|
||
#################################################################
|
||
"""
|
||
GACOS atmospheric correction for GAMMA interferograms.
|
||
This script applies GACOS-derived tropospheric corrections to unwrapped interferograms.
|
||
|
||
GACOS (Generic Atmospheric Correction Online Service) provides zenith total delay (ZTD)
|
||
maps that can be used to correct for atmospheric phase delays in InSAR data.
|
||
|
||
Usage:
|
||
gacos_gamma.py projectName Mdate-Sdate
|
||
gacos_gamma.py PacayaT163TsxHhA 20150102-20150601
|
||
"""
|
||
|
||
import numpy as np
|
||
import os
|
||
import sys
|
||
import argparse
|
||
import subprocess
|
||
import glob
|
||
import warnings
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from scipy.interpolate import RegularGridInterpolator
|
||
|
||
from pyint import _utils as ut
|
||
|
||
warnings.filterwarnings('ignore', category=RuntimeWarning)
|
||
|
||
# Try to import AutoGACOS modules
|
||
try:
|
||
from gacos import Downloader, Submitter, SarDataset
|
||
AUTOGACOS_AVAILABLE = True
|
||
except Exception as _e:
|
||
AUTOGACOS_AVAILABLE = False
|
||
print(f"[DEBUG] AutoGACOS import failed: {type(_e).__name__}: {_e}")
|
||
|
||
|
||
INTRODUCTION = '''
|
||
-------------------------------------------------------------------
|
||
Apply GACOS atmospheric correction to interferograms.
|
||
|
||
This script:
|
||
1. Generates elevation angle file from GAMMA products
|
||
2. Downloads GACOS ZTD data for master and slave dates
|
||
3. Applies tropospheric correction to the interferogram
|
||
|
||
Prerequisites:
|
||
- GACOS data for the acquisition dates (or valid email for auto-download)
|
||
- Geocoded interferogram from geocode_gamma.py
|
||
-------------------------------------------------------------------
|
||
'''
|
||
|
||
EXAMPLE = '''
|
||
Usage:
|
||
gacos_gamma.py projectName Mdate-Sdate
|
||
gacos_gamma.py projectName Mdate-Sdate --ztd-dir /path/to/gacos/data
|
||
gacos_gamma.py PacayaT163TsxHhA 20150102-20150601
|
||
-------------------------------------------------------------------
|
||
'''
|
||
|
||
|
||
class HEADER:
|
||
"""Header class for GACOS file format"""
|
||
width = 0
|
||
length = 0
|
||
xfirst = 0.0
|
||
yfirst = 0.0
|
||
xstep = 0.0
|
||
ystep = 0.0
|
||
|
||
|
||
def read_header(filename):
|
||
"""Read header information from GACOS .rsc file"""
|
||
if not os.path.isfile(filename):
|
||
print(filename + " file not exist")
|
||
return None
|
||
|
||
header = HEADER()
|
||
with open(filename) as f:
|
||
for line in f:
|
||
data = line.split()
|
||
if len(data) >= 2:
|
||
if data[0] == "WIDTH":
|
||
header.width = int(data[1])
|
||
if data[0] == "FILE_LENGTH":
|
||
header.length = int(data[1])
|
||
if data[0] == "X_FIRST":
|
||
header.xfirst = float(data[1])
|
||
if data[0] == "Y_FIRST":
|
||
header.yfirst = float(data[1])
|
||
if data[0] == "X_STEP":
|
||
header.xstep = float(data[1])
|
||
if data[0] == "Y_STEP":
|
||
header.ystep = float(data[1])
|
||
return header
|
||
|
||
|
||
def cut_image2(filename, headername, yfirst_new, length_new, xfirst_new, width_new):
|
||
"""Cut GACOS ZTD image to match interferogram extent"""
|
||
header = read_header(headername)
|
||
if header is None:
|
||
return None
|
||
|
||
with open(filename, 'rb') as f:
|
||
data0 = np.fromfile(f, dtype=np.float32)
|
||
data = np.reshape(data0, (header.length, header.width))
|
||
|
||
out = np.zeros((length_new, width_new), dtype=np.float32)
|
||
|
||
for i in range(header.length):
|
||
lat = header.yfirst + header.ystep * i
|
||
row = int(round((lat - yfirst_new) / header.ystep))
|
||
if row < 0 or row >= length_new:
|
||
continue
|
||
for j in range(header.width):
|
||
lon = header.xfirst + header.xstep * j
|
||
col = int(round((lon - xfirst_new) / header.xstep))
|
||
if col < 0 or col >= width_new:
|
||
continue
|
||
out[row, col] = data[i, j]
|
||
|
||
out = np.where(out == 0, np.nan, out)
|
||
out.tofile(filename + ".cut")
|
||
|
||
f = open(filename + ".cut.rsc", 'w')
|
||
f.write("WIDTH " + str(width_new) + "\n")
|
||
f.write("FILE_LENGTH " + str(length_new) + "\n")
|
||
f.write("X_FIRST " + str(xfirst_new) + "\n")
|
||
f.write("Y_FIRST " + str(yfirst_new) + "\n")
|
||
f.write("X_STEP " + str(header.xstep) + "\n")
|
||
f.write("Y_STEP " + str(header.ystep) + "\n")
|
||
f.close()
|
||
|
||
return filename + ".cut"
|
||
|
||
|
||
def make_correction(phsfilename, ztd1filename, ztd2filename, elevfilename, wavelength=None):
|
||
"""
|
||
Apply GACOS tropospheric correction to interferogram.
|
||
|
||
Parameters:
|
||
-----------
|
||
phsfilename : str
|
||
Path to unwrapped phase file
|
||
ztd1filename : str
|
||
Path to GACOS ZTD file for master date
|
||
ztd2filename : str
|
||
Path to GACOS ZTD file for slave date
|
||
elevfilename : str
|
||
Path to elevation angle file
|
||
wavelength : float, optional
|
||
Radar wavelength in meters. If None, read from template.
|
||
"""
|
||
header = read_header(phsfilename + ".rsc")
|
||
if header is None:
|
||
print("Error reading phase header file")
|
||
return None
|
||
|
||
# Cut ZTD files to match interferogram extent
|
||
if not os.path.isfile(ztd1filename + ".cut"):
|
||
cut_image2(ztd1filename, ztd1filename + ".rsc", header.yfirst, header.length, header.xfirst, header.width)
|
||
if not os.path.isfile(ztd2filename + ".cut"):
|
||
cut_image2(ztd2filename, ztd2filename + ".rsc", header.yfirst, header.length, header.xfirst, header.width)
|
||
|
||
# Read phase data
|
||
with open(phsfilename, 'rb') as f:
|
||
data = np.fromfile(f, dtype=np.float32)
|
||
phase = np.reshape(data, [header.length, header.width])
|
||
|
||
# Read ZTD data
|
||
with open(ztd1filename + ".cut", 'rb') as f:
|
||
data = np.fromfile(f, dtype=np.float32)
|
||
ztd1 = np.reshape(data, [header.length, header.width])
|
||
|
||
with open(ztd2filename + ".cut", 'rb') as f:
|
||
data = np.fromfile(f, dtype=np.float32)
|
||
ztd2 = np.reshape(data, [header.length, header.width])
|
||
|
||
# Read elevation angle data
|
||
with open(elevfilename, 'rb') as f:
|
||
data = np.fromfile(f, dtype=np.float32)
|
||
elev = np.reshape(data, [header.length, header.width])
|
||
|
||
# Calculate tropospheric phase delay
|
||
# ZTD difference to phase: phase = 4*pi*dZTD / (wavelength * sin(elevation))
|
||
# Default wavelength factor for Sentinel-1: 0.0044138251819503 = 4*pi/lambda
|
||
if wavelength:
|
||
ztd_factor = 4 * np.pi / wavelength
|
||
else:
|
||
ztd_factor = 1 / 0.0044138251819503 # Default factor
|
||
|
||
dztd = ztd2 - ztd1
|
||
dztd = dztd / 0.0044138251819503 # Convert ZTD to phase
|
||
dztd = dztd / np.sin(elev) # Incidence angle correction
|
||
|
||
# Apply correction
|
||
index = np.where(phase == 0)
|
||
phase[index] = np.nan
|
||
phasemean = np.nanmean(phase)
|
||
print("Phase std before correction: " + str(np.nanstd(phase)))
|
||
|
||
phase = phase - phasemean
|
||
phase = phase - dztd # Apply correction
|
||
phase[index] = np.nan
|
||
phasemean = np.nanmean(phase)
|
||
print("Phase std after correction: " + str(np.nanstd(phase)))
|
||
|
||
phase = phase - phasemean
|
||
phase[index] = 0
|
||
phase.tofile(phsfilename + ".gacos")
|
||
|
||
return phsfilename + ".gacos"
|
||
|
||
|
||
def generate_elevation_angle(workDir, Mdate, Sdate, MampPar, offpar, dempar, dem, rlks):
|
||
"""
|
||
Generate elevation angle file from GAMMA products.
|
||
|
||
The elevation angle (90 - incidence angle) is needed for
|
||
projecting ZTD to line-of-sight phase delay.
|
||
"""
|
||
os.chdir(workDir)
|
||
|
||
# Generate look vector (incidence angle)
|
||
call_str = "look_vector " + MampPar + " " + offpar + " " + dempar + " " + dem + " lv_theta lv_phi"
|
||
os.system(call_str)
|
||
|
||
# Get DEM parameters
|
||
call_str = "grep 'corner_lat:' " + dempar + " | awk '{print $2}' "
|
||
North = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'corner_lon:' " + dempar + " | awk '{print $2}' "
|
||
West = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'post_lat:' " + dempar + " | awk '{print $2}' "
|
||
posty = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'post_lon:' " + dempar + " | awk '{print $2}' "
|
||
postx = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'width:' " + dempar + " | awk '{print $2}' "
|
||
width = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'nlines:' " + dempar + " | awk '{print $2}' "
|
||
length = subprocess.getstatusoutput(call_str)[1]
|
||
|
||
South = str(round(float(North) + (float(length) - 1) * float(posty), 7))
|
||
East = str(round(float(West) + (float(width) - 1) * float(postx), 7))
|
||
|
||
# Convert to elevation angle
|
||
call_str = "swap_bytes lv_theta lv_theta.phase_swap 4 > dinsar.log "
|
||
os.system(call_str)
|
||
|
||
call_str = "gmt xyz2grd lv_theta.phase_swap -Glv_theta.grd -Ddegree/degree/cm/1/0/=/= -R" + West + "/" + East + "/" + South + "/" + North + " -I" + postx + " -ZTLf -N0"
|
||
os.system(call_str)
|
||
|
||
# Elevation angle = 90 - incidence angle
|
||
call_str = "gmt grdmath 90 lv_theta.grd 3.1415926 DIV 180 MUL SUB = lv_theta_final.grd"
|
||
os.system(call_str)
|
||
call_str = "gmt grdmath 90 lv_theta_final.grd SUB = lv_elev.grd"
|
||
os.system(call_str)
|
||
|
||
# Resample to 3 arc-seconds
|
||
call_str = "gmt grdsample lv_elev.grd -Glv_elev_3c.grd -I3c"
|
||
os.system(call_str)
|
||
|
||
# Convert to binary
|
||
call_str = "gmt grd2xyz lv_elev_3c.grd -ZTLf -N0 > " + Mdate + '-' + Sdate + '.gacos.elev'
|
||
os.system(call_str)
|
||
|
||
return workDir + '/' + Mdate + '-' + Sdate + '.gacos.elev'
|
||
|
||
|
||
def prepare_unw_for_gacos(workDir, unw, Mdate, Sdate, dempar, rlks):
|
||
"""
|
||
Prepare unwrapped interferogram for GACOS correction.
|
||
|
||
Converts GAMMA format to GACOS-compatible format with .rsc file.
|
||
"""
|
||
os.chdir(workDir)
|
||
|
||
# Get DEM parameters
|
||
call_str = "grep 'corner_lat:' " + dempar + " | awk '{print $2}' "
|
||
North = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'corner_lon:' " + dempar + " | awk '{print $2}' "
|
||
West = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'post_lat:' " + dempar + " | awk '{print $2}' "
|
||
posty = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'post_lon:' " + dempar + " | awk '{print $2}' "
|
||
postx = subprocess.getstatusoutput(call_str)[1]
|
||
|
||
# Convert unw to grd
|
||
call_str = "swap_bytes " + unw + " unw.phase_swap 4 > dinsar.log "
|
||
os.system(call_str)
|
||
|
||
# Get dimensions
|
||
call_str = "gmt grdinfo unw_f.grd -C"
|
||
result = subprocess.getstatusoutput(call_str)[1].split()
|
||
if len(result) >= 12:
|
||
Width = result[9]
|
||
line = result[10]
|
||
West = result[1]
|
||
North = result[4]
|
||
else:
|
||
# Fallback to DEM parameters
|
||
call_str = "grep 'width:' " + dempar + " | awk '{print $2}' "
|
||
Width = subprocess.getstatusoutput(call_str)[1]
|
||
call_str = "grep 'nlines:' " + dempar + " | awk '{print $2}' "
|
||
line = subprocess.getstatusoutput(call_str)[1]
|
||
|
||
ymax = str(int(round(float(line), 7)))
|
||
xmax = str(int(round(float(Width), 7)))
|
||
|
||
# Create .rsc file
|
||
output = workDir + '/' + Mdate + '-' + Sdate + '.gacos.unw.rsc'
|
||
if os.path.exists(output):
|
||
os.remove(output)
|
||
|
||
with open(output, 'a+') as f:
|
||
f.write('WIDTH ' + str(Width) + '\n')
|
||
f.write('FILE_LENGTH ' + str(line) + '\n')
|
||
f.write('XMIN 1' + '\n')
|
||
f.write('XMAX ' + xmax + '\n')
|
||
f.write('YMIN 1' + '\n')
|
||
f.write('YMAX ' + ymax + '\n')
|
||
f.write('X_FIRST ' + str(West) + '\n')
|
||
f.write('Y_FIRST ' + str(North) + '\n')
|
||
f.write('X_STEP 8.33333333E-04' + '\n')
|
||
f.write('Y_STEP -8.33333333E-04' + '\n')
|
||
f.write('X_UNIT degrees' + '\n')
|
||
f.write('Y_UNIT degrees' + '\n')
|
||
f.write('Z_OFFSET 0' + '\n')
|
||
f.write('Z_SCALE 1' + '\n')
|
||
f.write('PROJECTION LATLON' + '\n')
|
||
f.write('DATUM WGS84' + '\n')
|
||
|
||
return workDir + '/' + Mdate + '-' + Sdate + '.gacos.unw'
|
||
|
||
|
||
def parse_dem_par(dempar):
|
||
"""
|
||
从 GAMMA DEM par 文件读取网格参数(纯 Python,无需 grep/awk)
|
||
"""
|
||
info = {}
|
||
with open(dempar) as f:
|
||
for line in f:
|
||
parts = line.split(':')
|
||
if len(parts) >= 2:
|
||
key = parts[0].strip()
|
||
val = parts[1].strip().split()[0] if parts[1].strip() else ''
|
||
info[key] = val
|
||
return {
|
||
'width': int(info['width']),
|
||
'nlines': int(info['nlines']),
|
||
'corner_lat': float(info['corner_lat']),
|
||
'corner_lon': float(info['corner_lon']),
|
||
'post_lat': float(info['post_lat']),
|
||
'post_lon': float(info['post_lon']),
|
||
}
|
||
|
||
|
||
def resample_ztd_to_dem(ztd_file, rsc_file, dem_info):
|
||
"""
|
||
读取 ZTD 数据并双线性插值重采样到 DEM 网格
|
||
修复: RegularGridInterpolator 要求坐标严格递增,
|
||
ZTD 纬度为递减(北→南),需翻转
|
||
"""
|
||
# 读取 RSC 头文件
|
||
rsc = {}
|
||
with open(rsc_file) as f:
|
||
for line in f:
|
||
parts = line.split()
|
||
if len(parts) >= 2:
|
||
rsc[parts[0]] = parts[1]
|
||
|
||
ztd_w = int(rsc['WIDTH'])
|
||
ztd_h = int(rsc['FILE_LENGTH'])
|
||
ztd_x0 = float(rsc['X_FIRST'])
|
||
ztd_y0 = float(rsc['Y_FIRST'])
|
||
ztd_dx = float(rsc['X_STEP'])
|
||
ztd_dy = float(rsc['Y_STEP'])
|
||
|
||
raw = np.fromfile(ztd_file, dtype=np.float32).reshape(ztd_h, ztd_w)
|
||
|
||
# 构建 ZTD 网格坐标
|
||
ztd_lats = ztd_y0 + np.arange(ztd_h) * ztd_dy
|
||
ztd_lons = ztd_x0 + np.arange(ztd_w) * ztd_dx
|
||
|
||
# 翻转使坐标严格递增(RegularGridInterpolator 要求)
|
||
if ztd_lats[0] > ztd_lats[-1]:
|
||
ztd_lats = ztd_lats[::-1]
|
||
raw = raw[::-1, :]
|
||
if ztd_lons[0] > ztd_lons[-1]:
|
||
ztd_lons = ztd_lons[::-1]
|
||
raw = raw[:, ::-1]
|
||
|
||
interp = RegularGridInterpolator(
|
||
(ztd_lats, ztd_lons), raw,
|
||
method='linear', bounds_error=False, fill_value=np.nan
|
||
)
|
||
|
||
# 构建 DEM 网格查询点
|
||
dem_w = dem_info['width']
|
||
dem_h = dem_info['nlines']
|
||
dem_lats = dem_info['corner_lat'] + np.arange(dem_h) * dem_info['post_lat']
|
||
dem_lons = dem_info['corner_lon'] + np.arange(dem_w) * dem_info['post_lon']
|
||
|
||
# 逐行插值(节省内存)
|
||
result = np.empty((dem_h, dem_w), dtype=np.float32)
|
||
for i in range(dem_h):
|
||
pts = np.column_stack([np.full(dem_w, dem_lats[i]), dem_lons])
|
||
result[i] = interp(pts).astype(np.float32)
|
||
|
||
return result
|
||
|
||
|
||
def convert_ztd_tif_to_binary(tif_file):
|
||
"""
|
||
将 .ztd.tif (GeoTIFF) 转换为 .ztd (raw binary) + .ztd.rsc (header)
|
||
返回 .ztd 文件路径
|
||
"""
|
||
try:
|
||
import rasterio
|
||
except ImportError:
|
||
from osgeo import gdal
|
||
ds = gdal.Open(tif_file)
|
||
gt = ds.GetGeoTransform()
|
||
w, h = ds.RasterXSize, ds.RasterYSize
|
||
data = ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||
x0, y0, dx, dy = gt[0], gt[3], gt[1], gt[5]
|
||
ds = None
|
||
else:
|
||
with rasterio.open(tif_file) as src:
|
||
data = src.read(1).astype(np.float32)
|
||
w, h = src.width, src.height
|
||
x0 = src.transform[2]
|
||
y0 = src.transform[5]
|
||
dx = src.transform[0]
|
||
dy = src.transform[4]
|
||
|
||
ztd_file = tif_file.replace('.ztd.tif', '.ztd')
|
||
rsc_file = tif_file.replace('.ztd.tif', '.ztd.rsc')
|
||
|
||
data.tofile(ztd_file)
|
||
with open(rsc_file, 'w') as f:
|
||
f.write(f"WIDTH {w}\n")
|
||
f.write(f"FILE_LENGTH {h}\n")
|
||
f.write(f"X_FIRST {x0}\n")
|
||
f.write(f"Y_FIRST {y0}\n")
|
||
f.write(f"X_STEP {dx}\n")
|
||
f.write(f"Y_STEP {dy}\n")
|
||
f.write("X_UNIT degrees\n")
|
||
f.write("Y_UNIT degrees\n")
|
||
f.write("PROJECTION LATLON\n")
|
||
f.write("DATUM WGS84\n")
|
||
|
||
return ztd_file
|
||
|
||
|
||
def apply_gacos_correction_python(geo_unw_file, ztd1_file, ztd1_rsc, ztd2_file, ztd2_rsc,
|
||
dempar, slcpar=None, wavelength=None, out_file=None,
|
||
figdir=None, pair_str=None):
|
||
"""
|
||
纯 Python GACOS 大气校正(不依赖 GMT)
|
||
修复原 make_correction 的所有 Bug:
|
||
- 极端值掩码(GAMMA 无效标记)
|
||
- sin/cos 弧度转换
|
||
- cut_image2 慢速循环 → scipy 向量化重采样
|
||
- 增加校正前后对比图
|
||
|
||
参数:
|
||
-----
|
||
geo_unw_file : str 地理编码后的解缠相位文件
|
||
ztd1_file : str Master 日期 ZTD 二进制文件
|
||
ztd1_rsc : str Master 日期 ZTD RSC 头文件
|
||
ztd2_file : str Slave 日期 ZTD 二进制文件
|
||
ztd2_rsc : str Slave 日期 ZTD RSC 头文件
|
||
dempar : str DEM 参数文件
|
||
slcpar : str SLC 参数文件(读取入射角,可选)
|
||
wavelength : float 雷达波长(m),None则从slcpar读取
|
||
out_file : str 输出文件路径(默认 geo_unw_file + '.gacos')
|
||
figdir : str 对比图保存目录(None则不绘图)
|
||
pair_str : str 干涉对名称如 '20241105-20241117'
|
||
|
||
返回: 输出文件路径
|
||
"""
|
||
# 1. 读取 DEM 参数
|
||
dem = parse_dem_par(dempar)
|
||
dem_w, dem_h = dem['width'], dem['nlines']
|
||
|
||
# 2. 读取入射角和波长
|
||
inc_angle_deg = 39.0 # 默认值
|
||
if slcpar and os.path.isfile(slcpar):
|
||
with open(slcpar) as f:
|
||
for line in f:
|
||
if line.startswith('incidence_angle:'):
|
||
inc_angle_deg = float(line.split(':')[1].strip().split()[0])
|
||
if line.startswith('radar_frequency:') and wavelength is None:
|
||
freq = float(line.split(':')[1].strip().split()[0])
|
||
wavelength = 299792458.0 / freq
|
||
|
||
if wavelength is None:
|
||
wavelength = 0.0554657595 # Sentinel-1 C-band 默认值
|
||
|
||
cos_inc = np.cos(np.radians(inc_angle_deg))
|
||
ztd_to_phase = 4.0 * np.pi / wavelength
|
||
|
||
print(f" 入射角: {inc_angle_deg:.4f}°, cos={cos_inc:.6f}")
|
||
print(f" 波长: {wavelength:.10f} m")
|
||
|
||
# 3. 读取地理编码后的解缠相位
|
||
unw_data = np.fromfile(geo_unw_file, dtype=np.float32).reshape(dem_h, dem_w)
|
||
|
||
# 4. 重采样 ZTD 到 DEM 网格
|
||
print(" 重采样 ZTD Master...")
|
||
ztd_m = resample_ztd_to_dem(ztd1_file, ztd1_rsc, dem)
|
||
print(" 重采样 ZTD Slave...")
|
||
ztd_s = resample_ztd_to_dem(ztd2_file, ztd2_rsc, dem)
|
||
|
||
# 5. 计算 LOS 相位校正量
|
||
dztd = ztd_s - ztd_m
|
||
phase_correction = dztd * ztd_to_phase / cos_inc
|
||
|
||
# 6. 构建有效像素掩码
|
||
# 排除: 零值 | NaN | GAMMA 伪影/解缠错误(|x| >= 1000 rad ≈ 4.4m LOS)
|
||
PHASE_THRESH = 1000.0
|
||
valid = ((unw_data != 0)
|
||
& np.isfinite(unw_data)
|
||
& (np.abs(unw_data) < PHASE_THRESH)
|
||
& np.isfinite(phase_correction))
|
||
n_valid = np.sum(valid)
|
||
n_total = dem_w * dem_h
|
||
print(f" 有效像素: {n_valid}/{n_total} ({n_valid/n_total*100:.1f}%)")
|
||
|
||
if n_valid == 0:
|
||
print(" 错误: 无有效像素,跳过校正")
|
||
return None
|
||
|
||
# 7. 统计校正前
|
||
std_before = float(np.std(unw_data[valid]))
|
||
|
||
# 8. 应用校正(保留原始数据结构,仅修改有效像素)
|
||
corrected = unw_data.copy()
|
||
corrected[valid] = unw_data[valid] - phase_correction[valid]
|
||
|
||
# 去均值(仅对有效像素)
|
||
mean_val = np.mean(corrected[valid])
|
||
corrected[valid] -= mean_val
|
||
|
||
std_after = float(np.std(corrected[valid]))
|
||
reduction = (1 - std_after / std_before) * 100 if std_before > 0 else 0
|
||
|
||
print(f" 校正前 std: {std_before:.3f} rad")
|
||
print(f" 校正后 std: {std_after:.3f} rad")
|
||
print(f" std 降低: {reduction:.1f}%")
|
||
|
||
# 9. 保存
|
||
if out_file is None:
|
||
out_file = geo_unw_file + '.gacos'
|
||
corrected.astype(np.float32).tofile(out_file)
|
||
print(f" 输出: {out_file}")
|
||
|
||
# 10. 绘制对比图
|
||
if figdir:
|
||
plot_gacos_comparison(unw_data, phase_correction, corrected, valid,
|
||
dem, std_before, std_after, pair_str, figdir)
|
||
|
||
return out_file
|
||
|
||
|
||
def plot_gacos_comparison(unw_before, correction, unw_after, valid_mask,
|
||
dem_info, std_before, std_after, pair_str, figdir):
|
||
"""
|
||
绘制 GACOS 校正前后相位对比图(三子图)
|
||
版本控制: 自动追加 _v1, _v2...
|
||
"""
|
||
import matplotlib
|
||
matplotlib.use('Agg')
|
||
import matplotlib.pyplot as plt
|
||
import matplotlib.colors as mcolors
|
||
|
||
# 中文字体配置
|
||
plt.rcParams['font.sans-serif'] = ['Noto Serif CJK SC', 'Noto Sans CJK SC',
|
||
'AR PL UMing CN', 'SimHei', 'DejaVu Sans']
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
figdir = Path(figdir)
|
||
figdir.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 版本控制
|
||
base_name = f"GACOS_comparison_{pair_str}" if pair_str else "GACOS_comparison"
|
||
existing = sorted(figdir.glob(f"{base_name}_v*.png"))
|
||
ver = int(existing[-1].stem.split('_v')[-1]) + 1 if existing else 1
|
||
out_path = figdir / f"{base_name}_v{ver}.png"
|
||
|
||
# 准备显示数据(仅显示有效像素)
|
||
before_disp = np.full_like(unw_before, np.nan, dtype=np.float64)
|
||
before_disp[valid_mask] = unw_before[valid_mask]
|
||
corr_disp = np.full_like(correction, np.nan, dtype=np.float64)
|
||
corr_disp[valid_mask] = correction[valid_mask]
|
||
after_disp = np.full_like(unw_after, np.nan, dtype=np.float64)
|
||
after_disp[valid_mask] = unw_after[valid_mask]
|
||
|
||
# 色标范围:使用有效数据的 P2/P98
|
||
vals_b = unw_before[valid_mask]
|
||
vals_c = correction[valid_mask]
|
||
vals_a = unw_after[valid_mask]
|
||
vlim = max(abs(np.percentile(vals_b, 2)), abs(np.percentile(vals_b, 98)), 0.5)
|
||
vlim_c = max(abs(np.percentile(vals_c, 2)), abs(np.percentile(vals_c, 98)), 0.5)
|
||
vlim_a = max(abs(np.percentile(vals_a, 2)), abs(np.percentile(vals_a, 98)), 0.5)
|
||
|
||
# 地理坐标范围
|
||
w = dem_info['width']
|
||
h = dem_info['nlines']
|
||
extent = [dem_info['corner_lon'],
|
||
dem_info['corner_lon'] + w * dem_info['post_lon'],
|
||
dem_info['corner_lat'] + h * dem_info['post_lat'],
|
||
dem_info['corner_lat']]
|
||
|
||
# 降采样显示
|
||
step = max(1, h // 2000)
|
||
b_s = before_disp[::step, ::step]
|
||
c_s = corr_disp[::step, ::step]
|
||
a_s = after_disp[::step, ::step]
|
||
|
||
# 使用带 NaN 灰色背景的 colormap
|
||
cmap_phase = plt.cm.RdBu_r.copy()
|
||
cmap_phase.set_bad(color='#E0E0E0')
|
||
cmap_corr = plt.cm.coolwarm.copy()
|
||
cmap_corr.set_bad(color='#E0E0E0')
|
||
|
||
fig, axes = plt.subplots(1, 3, figsize=(18, 6), dpi=150)
|
||
for ax in axes:
|
||
ax.set_facecolor('#E0E0E0')
|
||
|
||
im1 = axes[0].imshow(b_s, cmap=cmap_phase, vmin=-vlim, vmax=vlim,
|
||
extent=extent, aspect='auto', interpolation='nearest')
|
||
axes[0].set_title(f'Unwrapped Phase (std={std_before:.2f} rad)', fontsize=11, fontweight='bold')
|
||
plt.colorbar(im1, ax=axes[0], label='rad', shrink=0.8)
|
||
|
||
im2 = axes[1].imshow(c_s, cmap=cmap_corr, vmin=-vlim_c, vmax=vlim_c,
|
||
extent=extent, aspect='auto', interpolation='nearest')
|
||
axes[1].set_title('GACOS Correction', fontsize=11, fontweight='bold')
|
||
plt.colorbar(im2, ax=axes[1], label='rad', shrink=0.8)
|
||
|
||
im3 = axes[2].imshow(a_s, cmap=cmap_phase, vmin=-vlim_a, vmax=vlim_a,
|
||
extent=extent, aspect='auto', interpolation='nearest')
|
||
axes[2].set_title(f'Corrected (std={std_after:.2f} rad)', fontsize=11, fontweight='bold')
|
||
plt.colorbar(im3, ax=axes[2], label='rad', shrink=0.8)
|
||
|
||
for ax in axes:
|
||
ax.set_xlabel('Longitude')
|
||
ax.set_ylabel('Latitude')
|
||
ax.tick_params(labelsize=9)
|
||
|
||
reduction = (1 - std_after / std_before) * 100 if std_before > 0 else 0
|
||
n_valid = np.sum(valid_mask)
|
||
if pair_str:
|
||
m, s = pair_str.split('-')
|
||
fig.suptitle(f'GACOS: {m}-{s} | valid={n_valid:,} px | std {reduction:+.1f}%',
|
||
fontsize=13, fontweight='bold', y=1.02)
|
||
plt.tight_layout()
|
||
fig.savefig(str(out_path), dpi=150, bbox_inches='tight',
|
||
facecolor='white', edgecolor='none')
|
||
plt.close(fig)
|
||
print(f" 对比图: {out_path}")
|
||
|
||
|
||
def find_existing_ztd(date, gacos_dir):
|
||
"""
|
||
Find existing GACOS ZTD file for a specific date.
|
||
|
||
Returns:
|
||
--------
|
||
ztd_file : str or None
|
||
Path to ZTD file (without extension), or None if not found
|
||
"""
|
||
# Check for .ztd + .ztd.rsc format (preferred)
|
||
ztd_pattern = os.path.join(gacos_dir, "**", date + "*.ztd")
|
||
ztd_files = glob.glob(ztd_pattern, recursive=True)
|
||
if ztd_files:
|
||
for ztd_file in ztd_files:
|
||
if ztd_file.endswith('.ztd.tif'):
|
||
continue
|
||
rsc_file = ztd_file + '.rsc'
|
||
if os.path.exists(rsc_file):
|
||
return ztd_file
|
||
|
||
# Check for .ztd.tif format, auto-convert if needed
|
||
ztd_pattern = os.path.join(gacos_dir, "**", date + "*.ztd.tif")
|
||
tif_files = glob.glob(ztd_pattern, recursive=True)
|
||
if tif_files:
|
||
tif_file = tif_files[0]
|
||
ztd_bin = tif_file.replace('.ztd.tif', '.ztd')
|
||
rsc_file = tif_file.replace('.ztd.tif', '.ztd.rsc')
|
||
if not os.path.exists(ztd_bin) or not os.path.exists(rsc_file):
|
||
print(f" Auto-converting {os.path.basename(tif_file)} -> .ztd + .rsc")
|
||
convert_ztd_tif_to_binary(tif_file)
|
||
if os.path.exists(ztd_bin) and os.path.exists(rsc_file):
|
||
return ztd_bin
|
||
|
||
return None
|
||
|
||
|
||
def submit_gacos_request(dates, bounds, email, gacos_dir, acquisition_time=None):
|
||
"""
|
||
Submit GACOS data request for multiple dates.
|
||
|
||
Parameters:
|
||
-----------
|
||
dates : list
|
||
List of acquisition dates in YYYYMMDD format
|
||
bounds : tuple
|
||
Bounding box (West, South, East, North)
|
||
email : str
|
||
Email address for GACOS submission
|
||
gacos_dir : str
|
||
Directory to save GACOS data
|
||
acquisition_time : str, optional
|
||
Acquisition time in HH:MM format
|
||
|
||
Returns:
|
||
--------
|
||
success : bool
|
||
True if submission successful
|
||
"""
|
||
if not AUTOGACOS_AVAILABLE:
|
||
print("AutoGACOS module not available. Please download GACOS data manually.")
|
||
return False
|
||
|
||
try:
|
||
from gacos import Submitter, SarDataset
|
||
import pandas as pd
|
||
|
||
# Create datetime index
|
||
if acquisition_time:
|
||
hour, minute = map(int, acquisition_time.split(':'))
|
||
else:
|
||
hour, minute = 10, 0 # Sentinel-1 升轨默认采集时间 ~10:00 UTC
|
||
|
||
date_strings = [f"{d} {hour:02d}:{minute:02d}:00" for d in dates]
|
||
date_times = pd.to_datetime(date_strings)
|
||
|
||
# Create dataset
|
||
dataset = SarDataset(bounds, date_times, gacos_dir)
|
||
|
||
print(f"\nSubmitting GACOS request for {len(dates)} dates...")
|
||
print(f"Bounds: W={bounds[0]:.4f}, S={bounds[1]:.4f}, E={bounds[2]:.4f}, N={bounds[3]:.4f}")
|
||
print(f"Acquisition time: {hour:02d}:{minute:02d} UTC")
|
||
print(f"Email: {email}")
|
||
|
||
# Submit request
|
||
submitter = Submitter(dataset, email)
|
||
submitter.post_requests()
|
||
|
||
if submitter.succeed:
|
||
print(f"\nSuccessfully submitted {len(submitter.succeed)} requests.")
|
||
if submitter.failed:
|
||
print(f"\nFailed to submit {len(submitter.failed)} requests.")
|
||
|
||
return len(submitter.succeed) > 0
|
||
|
||
except Exception as e:
|
||
print(f"Error in GACOS submission: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
|
||
def download_gacos_from_email(email_config, gacos_dir, bounds=None, times=None,
|
||
submit_time=None):
|
||
"""
|
||
从邮箱检索 gacos2017@foxmail.com 发来的 GACOS 下载链接并下载数据。
|
||
|
||
Parameters:
|
||
-----------
|
||
email_config : dict
|
||
邮箱配置: username, password, host, port, ssl
|
||
gacos_dir : str
|
||
GACOS 数据保存目录
|
||
bounds : tuple, optional
|
||
边界框过滤 (West, South, East, North)
|
||
times : list, optional
|
||
采集时间过滤
|
||
submit_time : str, optional
|
||
提交时间(YYYY-MM-DD HH:MM:SS),仅检索此时间之后的邮件
|
||
|
||
Returns:
|
||
--------
|
||
downloaded_files : list
|
||
已下载的 ZTD 文件路径列表
|
||
"""
|
||
if not AUTOGACOS_AVAILABLE:
|
||
print("AutoGACOS module not available.")
|
||
return []
|
||
|
||
try:
|
||
from gacos import Downloader, GACOSEmail
|
||
|
||
# Step 1: 从邮箱检索 GACOS 下载链接
|
||
print(" 检索 gacos2017@foxmail.com 邮件中的下载链接...")
|
||
|
||
email_retriever = GACOSEmail(
|
||
username=email_config.get('username'),
|
||
password=email_config.get('password'),
|
||
host=email_config.get('host', 'imap.163.com'),
|
||
port=email_config.get('port'),
|
||
email_protocol=email_config.get('protocol', 'imap'),
|
||
ssl=email_config.get('ssl', True),
|
||
gacos_email='gacos2017@foxmail.com',
|
||
start_date=submit_time
|
||
)
|
||
|
||
url_file = os.path.join(gacos_dir, 'gacos_urls.csv')
|
||
email_retriever.retrieve_gacos_urls(url_file)
|
||
|
||
if not os.path.exists(url_file):
|
||
print(" 未找到 GACOS 下载链接")
|
||
return []
|
||
|
||
# 检查 CSV 是否有内容
|
||
import pandas as pd
|
||
try:
|
||
df = pd.read_csv(url_file)
|
||
if len(df) == 0:
|
||
print(" 邮件中未发现新的 GACOS 数据链接")
|
||
return []
|
||
print(f" 找到 {len(df)} 个下载链接")
|
||
except Exception:
|
||
print(" URL 文件解析失败")
|
||
return []
|
||
|
||
# Step 2: 下载文件
|
||
print(" 下载 GACOS 数据...")
|
||
|
||
dl = Downloader(
|
||
url_file=url_file,
|
||
output_dir=gacos_dir,
|
||
bounds=bounds,
|
||
times=times,
|
||
keep_original=False
|
||
)
|
||
|
||
dl.download()
|
||
|
||
# Step 3: 将下载的 .ztd.tif 转换为 .ztd + .rsc
|
||
downloaded = []
|
||
for tif_file in glob.glob(os.path.join(gacos_dir, "*.ztd.tif")):
|
||
rsc_file = tif_file.replace('.ztd.tif', '.ztd.rsc')
|
||
if not os.path.exists(rsc_file):
|
||
ztd_file = convert_ztd_tif_to_binary(tif_file)
|
||
if ztd_file:
|
||
downloaded.append(ztd_file)
|
||
print(f" 转换: {os.path.basename(tif_file)} → .ztd + .rsc")
|
||
else:
|
||
downloaded.append(tif_file.replace('.ztd.tif', '.ztd'))
|
||
|
||
return downloaded
|
||
|
||
except Exception as e:
|
||
print(f" 邮箱检索/下载出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return []
|
||
|
||
|
||
def auto_gacos_workflow(dates, bounds, gacos_dir, email, email_config=None,
|
||
acquisition_time=None, wait_for_email=False, max_wait_hours=24,
|
||
check_interval=60):
|
||
"""
|
||
Complete automatic GACOS workflow: submit request, wait, and download.
|
||
|
||
Parameters:
|
||
-----------
|
||
dates : list
|
||
List of acquisition dates in YYYYMMDD format
|
||
bounds : tuple
|
||
Bounding box (West, South, East, North)
|
||
gacos_dir : str
|
||
Directory to save GACOS data
|
||
email : str
|
||
Email address for GACOS submission
|
||
email_config : dict, optional
|
||
Email configuration for downloading (username, password, host, etc.)
|
||
acquisition_time : str, optional
|
||
Acquisition time in HH:MM format
|
||
wait_for_email : bool, optional
|
||
Whether to wait for email notification before downloading
|
||
max_wait_hours : int, optional
|
||
Maximum hours to wait for email
|
||
|
||
Returns:
|
||
--------
|
||
ztd_files : dict
|
||
Dictionary mapping dates to ZTD file paths
|
||
"""
|
||
import time as time_module
|
||
from datetime import datetime, timedelta
|
||
|
||
ztd_files = {}
|
||
missing_dates = []
|
||
|
||
# Step 1: Check existing files
|
||
print("\n" + "="*60)
|
||
print("Step 1: Checking existing GACOS data...")
|
||
print("="*60)
|
||
|
||
for date in dates:
|
||
ztd_file = find_existing_ztd(date, gacos_dir)
|
||
if ztd_file:
|
||
ztd_files[date] = ztd_file
|
||
else:
|
||
missing_dates.append(date)
|
||
|
||
if not missing_dates:
|
||
print("\nAll required GACOS data already exists!")
|
||
return ztd_files
|
||
|
||
print(f"\nMissing GACOS data for {len(missing_dates)} dates:")
|
||
for d in missing_dates:
|
||
print(f" - {d}")
|
||
|
||
# Step 2: Submit request for missing dates
|
||
print("\n" + "="*60)
|
||
print("Step 2: Submitting GACOS requests...")
|
||
print("="*60)
|
||
|
||
submit_success = submit_gacos_request(
|
||
dates=missing_dates,
|
||
bounds=bounds,
|
||
email=email,
|
||
gacos_dir=gacos_dir,
|
||
acquisition_time=acquisition_time
|
||
)
|
||
|
||
if not submit_success:
|
||
print("\n提交失败(可能是重复提交或服务器繁忙),尝试从邮箱获取已有数据...")
|
||
else:
|
||
print("\nGACOS request submitted successfully!")
|
||
print("You will receive an email with download links when data is ready.")
|
||
|
||
# Step 3: Wait and download from email (if configured)
|
||
if email_config and wait_for_email:
|
||
print("\n" + "="*60)
|
||
print("Step 3: 检查 gacos2017@foxmail.com 邮件并下载...")
|
||
print("="*60)
|
||
|
||
# 记录时间基准,仅检索此时间之后的 GACOS 邮件,过滤掉旧邮件
|
||
if submit_success:
|
||
submit_time_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
else:
|
||
# 提交失败(重复提交),向前回溯 1 小时查找已有邮件
|
||
submit_time_str = (datetime.now() - timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')
|
||
print(f"邮件过滤起始时间: {submit_time_str}")
|
||
|
||
start_time = datetime.now()
|
||
# 提交成功则等 3 分钟再检查;提交失败则立即检查邮箱
|
||
first_wait = 180 if submit_success else 5
|
||
retry_wait = 120 # 之后每 2 分钟
|
||
attempt = 0
|
||
|
||
# 首次等待
|
||
print(f"\n提交完成,等待 {first_wait//60} 分钟后首次检查邮箱...")
|
||
time_module.sleep(first_wait)
|
||
|
||
while (datetime.now() - start_time) < timedelta(hours=max_wait_hours):
|
||
attempt += 1
|
||
elapsed = datetime.now() - start_time
|
||
elapsed_min = int(elapsed.total_seconds() // 60)
|
||
elapsed_sec = int(elapsed.total_seconds() % 60)
|
||
print(f"\n[第 {attempt} 次检查] 已等待 {elapsed_min}m{elapsed_sec}s")
|
||
|
||
downloaded = download_gacos_from_email(
|
||
email_config=email_config,
|
||
gacos_dir=gacos_dir,
|
||
bounds=bounds,
|
||
submit_time=submit_time_str
|
||
)
|
||
|
||
# 检查缺失日期是否已下载
|
||
for date in missing_dates[:]:
|
||
ztd_file = find_existing_ztd(date, gacos_dir)
|
||
if ztd_file:
|
||
ztd_files[date] = ztd_file
|
||
missing_dates.remove(date)
|
||
print(f" ✓ 已获取: {date}")
|
||
|
||
if not missing_dates:
|
||
print(f"\n所有 {len(dates)} 个日期的 GACOS 数据下载完成!")
|
||
break
|
||
|
||
print(f" 仍缺 {len(missing_dates)} 个日期,{retry_wait//60} 分钟后重试...")
|
||
time_module.sleep(retry_wait)
|
||
|
||
if missing_dates:
|
||
print(f"\n超时!仍缺 {len(missing_dates)} 个日期的 GACOS 数据。")
|
||
|
||
return ztd_files
|
||
|
||
|
||
def cmdLineParse():
|
||
parser = argparse.ArgumentParser(description='Apply GACOS atmospheric correction to GAMMA interferograms.',
|
||
formatter_class=argparse.RawTextHelpFormatter,
|
||
epilog=INTRODUCTION + '\n' + EXAMPLE)
|
||
|
||
parser.add_argument('projectName', help='projectName for processing.')
|
||
parser.add_argument('pair', help='Master-Slave, e.g., 20150101-20150106.')
|
||
parser.add_argument('--ztd-dir', dest='ztdDir', help='Directory containing GACOS ZTD files.')
|
||
parser.add_argument('--ztd1', dest='ztd1', help='GACOS ZTD file for master date.')
|
||
parser.add_argument('--ztd2', dest='ztd2', help='GACOS ZTD file for slave date.')
|
||
parser.add_argument('--email', dest='email', help='Email for GACOS auto-download.')
|
||
parser.add_argument('--wavelength', dest='wavelength', type=float, help='Radar wavelength in meters.')
|
||
|
||
# Email configuration for auto-download
|
||
parser.add_argument('--email-user', dest='emailUser', help='Email username for downloading.')
|
||
parser.add_argument('--email-pass', dest='emailPass', help='Email password for downloading.')
|
||
parser.add_argument('--email-host', dest='emailHost', default='imap.gmail.com', help='Email IMAP host.')
|
||
parser.add_argument('--email-port', dest='emailPort', type=int, default=993, help='Email IMAP port.')
|
||
parser.add_argument('--email-ssl', dest='emailSsl', action='store_true', default=True, help='Use SSL for email.')
|
||
|
||
# Auto-download options
|
||
parser.add_argument('--auto-download', dest='autoDownload', action='store_true',
|
||
help='Automatically submit request and download.')
|
||
parser.add_argument('--wait-hours', dest='waitHours', type=int, default=24,
|
||
help='Maximum hours to wait for email notification.')
|
||
|
||
inps = parser.parse_args()
|
||
return inps
|
||
|
||
|
||
def main(argv):
|
||
|
||
inps = cmdLineParse()
|
||
projectName = inps.projectName
|
||
Pair = inps.pair
|
||
|
||
# Parse dates
|
||
Mdate = ut.yyyymmdd(Pair.split('-')[0])
|
||
Sdate = ut.yyyymmdd(Pair.split('-')[1])
|
||
|
||
# Setup directories
|
||
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']
|
||
|
||
ifgDir = scratchDir + '/' + projectName + "/ifgrams"
|
||
demDir = scratchDir + '/' + projectName + "/DEM"
|
||
slcDir = scratchDir + '/' + projectName + "/SLC"
|
||
workDir = ifgDir + '/' + Pair
|
||
|
||
# GACOS data directory
|
||
if inps.ztdDir:
|
||
gacosDir = inps.ztdDir
|
||
else:
|
||
gacosDir = scratchDir + '/' + projectName + "/GACOS"
|
||
|
||
if not os.path.exists(gacosDir):
|
||
os.makedirs(gacosDir)
|
||
|
||
# Define input files
|
||
dempar = demDir + '/' + masterDate + '_' + rlks + 'rlks.utm.dem.par'
|
||
slcpar = slcDir + '/' + masterDate + '/' + masterDate + '.slc.par'
|
||
geo_unw = workDir + '/geo_' + Pair + '_' + rlks + 'rlks.diff_filt.unw'
|
||
out_file = workDir + '/geo_' + Pair + '_' + rlks + 'rlks.diff_filt.unw.gacos'
|
||
figdir = scratchDir + '/' + projectName + "/figure"
|
||
|
||
# Check if geocoded interferogram exists
|
||
if not os.path.isfile(geo_unw):
|
||
print(f"Error: Geocoded interferogram not found: {geo_unw}")
|
||
print("Please run geocode_gamma.py first.")
|
||
sys.exit(1)
|
||
|
||
print("="*60)
|
||
print("GACOS Atmospheric Correction (Pure Python)")
|
||
print(f"Project: {projectName}")
|
||
print(f"Pair: {Pair}")
|
||
print("="*60)
|
||
|
||
# Step 1: Find GACOS ZTD files
|
||
print("\nStep 1: Checking GACOS ZTD files...")
|
||
|
||
ztd1 = None
|
||
ztd2 = None
|
||
|
||
if inps.ztd1 and inps.ztd2:
|
||
ztd1 = inps.ztd1
|
||
ztd2 = inps.ztd2
|
||
else:
|
||
ztd1 = find_existing_ztd(Mdate, gacosDir)
|
||
ztd2 = find_existing_ztd(Sdate, gacosDir)
|
||
|
||
if ztd1 and ztd2:
|
||
print(f" Master ZTD: {ztd1}")
|
||
print(f" Slave ZTD: {ztd2}")
|
||
elif inps.email:
|
||
# Auto-download workflow (existing logic preserved)
|
||
dem_info = parse_dem_par(dempar)
|
||
North = dem_info['corner_lat']
|
||
West = dem_info['corner_lon']
|
||
South = North + (dem_info['nlines'] - 1) * dem_info['post_lat']
|
||
East = West + (dem_info['width'] - 1) * dem_info['post_lon']
|
||
bounds = (West, South, East, North)
|
||
|
||
missing_dates = []
|
||
if not ztd1: missing_dates.append(Mdate)
|
||
if not ztd2: missing_dates.append(Sdate)
|
||
|
||
if inps.autoDownload:
|
||
email_config = None
|
||
if inps.emailUser and inps.emailPass:
|
||
email_config = {
|
||
'username': inps.emailUser, 'password': inps.emailPass,
|
||
'host': inps.emailHost, 'port': inps.emailPort, 'ssl': inps.emailSsl
|
||
}
|
||
ztd_files = auto_gacos_workflow(
|
||
dates=missing_dates, bounds=bounds, gacos_dir=gacosDir,
|
||
email=inps.email, email_config=email_config,
|
||
wait_for_email=(email_config is not None), max_wait_hours=inps.waitHours
|
||
)
|
||
ztd1 = ztd1 or ztd_files.get(Mdate)
|
||
ztd2 = ztd2 or ztd_files.get(Sdate)
|
||
else:
|
||
submit_gacos_request(dates=missing_dates, bounds=bounds,
|
||
email=inps.email, gacos_dir=gacosDir)
|
||
print(f"\nGACOS request submitted for: {missing_dates}")
|
||
sys.exit(0)
|
||
|
||
if ztd1 is None or ztd2 is None:
|
||
print("\nError: GACOS ZTD files not found.")
|
||
print(f" Missing: {Mdate if not ztd1 else ''} {Sdate if not ztd2 else ''}")
|
||
print(f" Search dir: {gacosDir}")
|
||
sys.exit(1)
|
||
|
||
# Step 2: Apply GACOS correction (Pure Python, no GMT dependency)
|
||
print("\nStep 2: Applying GACOS correction...")
|
||
|
||
ztd1_rsc = ztd1 + '.rsc'
|
||
ztd2_rsc = ztd2 + '.rsc'
|
||
|
||
corrected_file = apply_gacos_correction_python(
|
||
geo_unw_file=geo_unw,
|
||
ztd1_file=ztd1, ztd1_rsc=ztd1_rsc,
|
||
ztd2_file=ztd2, ztd2_rsc=ztd2_rsc,
|
||
dempar=dempar,
|
||
slcpar=slcpar,
|
||
wavelength=inps.wavelength,
|
||
out_file=out_file,
|
||
figdir=figdir,
|
||
pair_str=Pair
|
||
)
|
||
|
||
if corrected_file:
|
||
# Step 3: Generate BMP preview using rasdt_pwr
|
||
print("\nStep 3: Generating BMP preview...")
|
||
geo_amp = workDir + '/geo_' + masterDate + '_' + rlks + 'rlks.amp'
|
||
nWidthUTMDEM = str(parse_dem_par(dempar)['width'])
|
||
|
||
if os.path.isfile(geo_amp):
|
||
call_str = ('rasdt_pwr ' + corrected_file + ' ' + geo_amp + ' '
|
||
+ nWidthUTMDEM + ' - - - - -3.14 3.14 1 rmg.cm')
|
||
print(f" {call_str}")
|
||
os.system(call_str)
|
||
bmp_file = corrected_file + '.bmp'
|
||
if os.path.isfile(bmp_file):
|
||
print(f" BMP: {bmp_file}")
|
||
else:
|
||
print(" Warning: BMP file not generated")
|
||
else:
|
||
print(f" Warning: geo_amp not found: {geo_amp}")
|
||
|
||
print(f"\nGACOS atmospheric correction completed successfully!")
|
||
else:
|
||
print("\nGACOS correction failed!")
|
||
sys.exit(1)
|
||
|
||
sys.exit(0)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main(sys.argv[:])
|