增加坡度计算

This commit is contained in:
2026-04-22 09:27:59 +08:00
parent 4fd1b0a203
commit d563a56358
20 changed files with 4891 additions and 344 deletions

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -16,265 +16,57 @@ from Flexbrdf.hytools.masks import mask_create
warnings.filterwarnings("ignore")
np.seterr(divide='ignore', invalid='ignore')
# Default configuration values
DEFAULT_CONFIG = {
'file_type': 'envi',
'num_cpus': 16,
'bad_bands': [],
'corrections': [],
'resample': False,
'resampler': {'type': 'cubic', 'out_waves': []},
'export': {
'coeffs':True,
'image': True,
'masks': False,
'subset_waves': [],
'output_dir': './output/',
'suffix': 'BRDF'
},
'brdf': {
'type': 'flex',
'grouped': True,
'geometric': 'li_dense_r',
'volume': 'ross_thick',
'b/r': 2.5,
'h/b': 2.0,
'sample_perc': 0.1,
'interp_kind': 'linear',
'calc_mask': [
['water', {'band_1': 850, 'band_2': 660,"threshold": 290}],
['kernel_finite', {}],
['ancillary', {'name': 'sensor_zn', 'min': 0.03490658503988659, 'max': 'inf'}]
],
'apply_mask': [
['water', {'band_1': 850, 'band_2': 660,"threshold": 290}]
],
'bin_type': 'dynamic',
'num_bins': 18,
'ndvi_bin_min': 0.05,
'ndvi_bin_max': 1.0,
'ndvi_perc_min': 10,
'ndvi_perc_max': 95,
'solar_zn_type': 'scene'
},
# 'topo': {
# 'type': 'scs+c',
# 'calc_mask': [
# ['ndi', {'band_1': 850, 'band_2': 660, 'min': 0.05, 'max': 1.0}],
# ['kernel_finite', {}],
# ['ancillary', {'name': 'sensor_zn', 'min': 0.03490658503988659, 'max': 'inf'}]
# ],
# 'apply_mask': [
# ['ndi', {'band_1': 850, 'band_2': 660, 'min': 0.05, 'max': 1.0}]
# ],
# 'sample_perc': 0.1,
# 'subgrouped': False,
# 'subgroup': {}
# },
# 'glint': {
# 'type': 'hochberg',
# 'correction_band': 560,
# 'deep_water_sample': {},
# 'calc_mask': [
# ['ndi', {'band_1': 550, 'band_2': 2150, 'min': -1, 'max': 0}],
# ['kernel_finite', {}]
# ],
# 'apply_mask': [
# ['ndi', {'band_1': 550, 'band_2': 2150, 'min': -1, 'max': 0}]
# ]
# }
}
def build_config_from_args(args):
"""从命令行参数构建配置字典"""
# 自动发现输入文件
input_files = []
if os.path.isdir(args.input_dir):
# 支持的文件扩展名
extensions = ['.tif', '.tiff', '.envi', '.img', '.dat']
for file in os.listdir(args.input_dir):
if any(file.lower().endswith(ext) for ext in extensions):
input_files.append(os.path.join(args.input_dir, file))
else:
input_files = [args.input_dir] if os.path.isfile(args.input_dir) else []
if not input_files:
raise ValueError(f"No input files found in {args.input_dir}")
# 自动生成anc_files
anc_files = {}
for input_file in input_files:
base_name = os.path.splitext(os.path.basename(input_file))[0]
# 根据文件名模式生成对应的angles文件路径
# 例如: 2025_9_2_3_53_45_202592_35252_0_rad_geo_corrected_reflectance.dat
# 对应的angles文件: 2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo_registered_angles.bip
parts = base_name.split('_')
if len(parts) >= 9: # 确保有足够的parts
anc_base = f"{parts[0]}_{parts[1]}_{parts[2]}_{parts[3]}_{parts[4]}_{parts[5]}_{parts[6]}_{parts[7]}_{parts[8]}_rad_rgbxyz_geo_angles_registered"
# 确保路径分隔符统一使用反斜杠Windows
anc_dir_clean = args.anc_dir.replace('/', '\\')
anc_path = os.path.join(anc_dir_clean, anc_base + ".bip")
# 确保最终路径格式正确
anc_path = os.path.normpath(anc_path)
if os.path.exists(anc_path):
# 生成完整的anc_files字典结构
anc_files[input_file] = {
"path_length": [anc_path, 0],
"sensor_az": [anc_path, 9],
"sensor_zn": [anc_path, 8],
"solar_az": [anc_path, 7],
"solar_zn": [anc_path, 6],
"phase": [anc_path, 0],
"slope": [anc_path, 0],
"aspect": [anc_path, 0],
"cosine_i": [anc_path, 0],
"utc_time": [anc_path, 0]
}
else:
# 如果找不到对应的angles文件使用默认的obs文件
obs_path = os.path.join(args.anc_dir, base_name + "_obs")
if os.path.exists(obs_path):
anc_files[input_file] = obs_path
# 构建配置字典
config_dict = DEFAULT_CONFIG.copy()
config_dict.update({
'input_files': input_files,
'anc_files': anc_files,
'file_type': 'envi', # 假设都是ENVI格式
'num_cpus': args.num_cpus,
'bad_bands': args.bad_bands if args.bad_bands else [],
'corrections': args.corrections if args.corrections else [],
'export': {
'coeffs': args.export_coeffs,
'image': args.export_image,
'masks': args.export_masks,
'subset_waves': args.subset_waves if args.subset_waves else [],
'output_dir': args.output_dir,
'suffix': args.suffix
}
})
# 根据命令行参数更新BRDF配置
if 'brdf' in args.corrections:
brdf_config = config_dict['brdf'].copy()
if hasattr(args, 'brdf_type') and args.brdf_type:
brdf_config['type'] = args.brdf_type
if hasattr(args, 'grouped') and args.grouped is not None:
brdf_config['grouped'] = args.grouped
if hasattr(args, 'geometric') and args.geometric:
brdf_config['geometric'] = args.geometric
if hasattr(args, 'volume') and args.volume:
brdf_config['volume'] = args.volume
if hasattr(args, 'num_bins') and args.num_bins:
brdf_config['num_bins'] = args.num_bins
config_dict['brdf'] = brdf_config
# 根据命令行参数更新TOPO配置
if 'topo' in args.corrections:
topo_config = config_dict['topo'].copy()
if hasattr(args, 'topo_type') and args.topo_type:
topo_config['type'] = args.topo_type
config_dict['topo'] = topo_config
# 根据命令行参数更新Glint配置
if 'glint' in args.corrections:
glint_config = config_dict['glint'].copy()
if hasattr(args, 'glint_type') and args.glint_type:
glint_config['type'] = args.glint_type
config_dict['glint'] = glint_config
return config_dict
def main():
parser = argparse.ArgumentParser(description="High-resolution image correction with automatic configuration")
parser = argparse.ArgumentParser(description="FlexBRDF - 只接受JSON配置文件输入")
parser.add_argument('config', help='JSON配置文件路径 (例如: config.json)')
args = parser.parse_args()
# 检查配置文件是否存在
if not os.path.exists(args.config):
print(f"错误: 配置文件不存在: {args.config}")
sys.exit(1)
# 检查是否为JSON文件
if not args.config.endswith('.json'):
print("错误: 只接受JSON格式的配置文件")
sys.exit(1)
# 加载JSON配置
try:
with open(args.config, 'r', encoding='utf-8') as f:
config_dict = json.load(f)
except json.JSONDecodeError as e:
print(f"错误: JSON配置文件格式错误: {e}")
sys.exit(1)
except Exception as e:
print(f"错误: 无法读取配置文件: {e}")
sys.exit(1)
# 必需参数
parser.add_argument('input_dir', help='Input directory containing image files or single image file path')
parser.add_argument('anc_dir', help='Ancillary directory containing angle/obs files')
parser.add_argument('--output-dir', required=True, help='Output directory for corrected images')
# 验证必需的配置字段
required_fields = ['input_files', 'anc_files', 'corrections', 'export']
missing_fields = [f for f in required_fields if f not in config_dict]
if missing_fields:
print(f"错误: 配置文件缺少必需字段: {', '.join(missing_fields)}")
print("必需字段包括: input_files, anc_files, corrections, export")
sys.exit(1)
# 可选参数 - 基本设置
parser.add_argument('--num-cpus', type=int, default=16, help='Number of CPUs to use (default: 4)')
parser.add_argument('--bad-bands', nargs='*', type=int, default=[], help='Bad band indices to exclude')
# 校正类型
parser.add_argument('--corrections', nargs='*', choices=['topo', 'brdf', 'glint'],
default=['brdf'], help='Correction types to apply (default: brdf)')
# BRDF参数
parser.add_argument('--brdf-type', choices=['universal', 'flex'], default='flex',
help='BRDF correction type (default: flex)')
parser.add_argument('--grouped', action='store_true', default=True,
help='Group images for BRDF correction (default: True)')
parser.add_argument('--no-grouped', action='store_false', dest='grouped',
help='Do not group images for BRDF correction')
parser.add_argument('--geometric', default='li_dense_r',
choices=['li_sparse', 'li_dense', 'li_dense_r', 'roujean'],
help='Geometric kernel type (default: li_dense_r)')
parser.add_argument('--volume', default='ross_thick',
choices=['ross_thin', 'ross_thick', 'hotspot', 'roujean'],
help='Volume kernel type (default: ross_thick)')
parser.add_argument('--num-bins', type=int, default=18,
help='Number of NDVI bins for FlexBRDF (default: 18)')
# TOPO参数
parser.add_argument('--topo-type', default='scs+c',
choices=['scs', 'scs+c', 'c', 'cosine', 'mod_minneart'],
help='TOPO correction type (default: scs+c)')
# Glint参数
parser.add_argument('--glint-type', default='hochberg',
choices=['hochberg', 'gao', 'hedley'],
help='Glint correction type (default: hochberg)')
# 输出选项
parser.add_argument('--export-coeffs', action='store_true', default=True,
help='Export correction coefficients')
parser.add_argument('--export-image', action='store_true', default=True,
help='Export corrected images (default: True)')
parser.add_argument('--export-masks', action='store_true', default=True,
help='Export correction masks (default: True)')
parser.add_argument('--subset-waves', nargs='*', type=float, default=[],
help='Subset of wavelengths to export (empty for all)')
parser.add_argument('--suffix', default='corrected',
help='Suffix for output files (default: corrected)')
parser.add_argument('--output-config', type=str, default=None,
help='Output current configuration to JSON file')
# 向后兼容如果只有一个参数且是JSON文件则使用传统模式
if len(sys.argv) == 2 and sys.argv[1].endswith('.json'):
config_file = sys.argv[1]
with open(config_file, 'r') as outfile:
config_dict = json.load(outfile)
else:
args = parser.parse_args()
config_dict = build_config_from_args(args)
# 如果指定了输出配置选项则输出配置到JSON文件
if hasattr(args, 'output_config') and args.output_config:
print(f"Outputting configuration to {args.output_config}...")
# 重新排序配置字典以匹配期望的格式
ordered_config = {
"bad_bands": config_dict.get("bad_bands", []),
"file_type": config_dict.get("file_type", "envi"),
"input_files": config_dict.get("input_files", []),
"anc_files": config_dict.get("anc_files", {}),
"num_cpus": config_dict.get("num_cpus", 4),
"export": config_dict.get("export", {}),
"corrections": config_dict.get("corrections", []),
"brdf": config_dict.get("brdf", {}),
"topo": config_dict.get("topo", {}),
"glint": config_dict.get("glint", {}),
"resample": config_dict.get("resample", False),
"resampler": config_dict.get("resampler", {"type": "cubic", "out_waves": []})
}
with open(args.output_config, 'w', encoding='utf-8') as f:
json.dump(ordered_config, f, indent=2, ensure_ascii=False)
print("Configuration output complete.")
return # 如果只是输出配置,则退出程序
# 设置默认值
config_dict.setdefault('file_type', 'envi')
config_dict.setdefault('num_cpus', 16)
config_dict.setdefault('bad_bands', [])
config_dict.setdefault('resample', False)
config_dict.setdefault('resampler', {'type': 'cubic', 'out_waves': []})
print(f"=" * 60)
print(f"FlexBRDF 图像校正工具")
print(f"=" * 60)
print(f"配置文件: {args.config}")
print(f"输入文件数量: {len(config_dict['input_files'])}")
print(f"校正类型: {', '.join(config_dict['corrections'])}")
print(f"输出目录: {config_dict['export']['output_dir']}")
print(f"=" * 60)
print("Starting image correction process...")
total_start = time.time()

View File

@ -0,0 +1,167 @@
{
"input_files": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_0_rad_geo_corrected_reflectance.dat",
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_1_rad_geo_corrected_reflectance.dat"
],
"file_type": "envi",
"bad_bands": [],
"num_cpus": 2,
"anc_files": {
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_0_rad_geo_corrected_reflectance.dat": {
"path_length": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
0
],
"sensor_az": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
9
],
"sensor_zn": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
8
],
"solar_az": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
7
],
"solar_zn": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
6
],
"phase": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
0
],
"slope": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
0
],
"aspect": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
0
],
"cosine_i": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
0
],
"utc_time": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_0_rad_rgbxyz_geo.bip_with_angles.bip",
0
]
},
"D:/BaiduNetdiskDownload/20250902/_3_52_52/rad2geo/R/2025_9_2_3_53_45_202592_35252_1_rad_geo_corrected_reflectance.dat": {
"path_length": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
0
],
"sensor_az": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
9
],
"sensor_zn": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
8
],
"solar_az": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
7
],
"solar_zn": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
6
],
"phase": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
0
],
"slope": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
0
],
"aspect": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
0
],
"cosine_i": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
0
],
"utc_time": [
"D:/BaiduNetdiskDownload/20250902/_3_52_52/BRDF/angle/test/2025_9_2_3_53_45_202592_35252_1_rad_rgbxyz_geo.bip_angles.bip",
0
]
}
},
"corrections": [
"brdf"
],
"topo": {
"type": "scs+c",
"calc_mask": [],
"apply_mask": [],
"c_fit_type": "nnls"
},
"brdf": {
"type": "flex",
"grouped": true,
"geometric": "li_dense_r",
"volume": "ross_thick",
"b/r": 2.5,
"h/b": 2.0,
"sample_perc": 0.1,
"interp_kind": "linear",
"calc_mask": [
[
"water",
{
"band_1": 850,
"band_2": 660,
"threshold": 290.0
}
],
[
"kernel_finite",
{}
],
[
"ancillary",
{
"name": "sensor_zn",
"min": 0.034907,
"max": 1000000.0
}
]
],
"apply_mask": [
[
"water",
{
"band_1": 850,
"band_2": 660,
"threshold": 290.0
}
]
],
"bin_type": "dynamic",
"num_bins": 18,
"ndvi_bin_min": 0.05,
"ndvi_bin_max": 1.0,
"ndvi_perc_min": 10,
"ndvi_perc_max": 95,
"solar_zn_type": "scene"
},
"glint": {
"type": "hochberg",
"correction_wave": 2150,
"apply_mask": []
},
"export": {
"coeffs": true,
"image": true,
"masks": true,
"subset_waves": [],
"output_dir": "E:/code/hytools-master/hytools-master/data/output",
"suffix": "brdf_glint"
},
"resample": false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

View File

@ -0,0 +1,186 @@
基于 Flexbrdf/hytools/topo 目录下的代码,我来详细解释地形校正的原理。
地形校正原理详解
地形校正Topographic Correction的目的是消除地形起伏坡度、坡向对高光谱图像反射率的影响。由于太阳光入射角度在不同坡度和坡向上的变化同一地物在不同地形位置会呈现不同的辐射亮度地形校正就是要将这种差异归一化到水平地表的条件。
一、核心概念入射角余弦cosine i
地形校正的核心是计算入射角余弦cosine of incidence angle即太阳光线与地表法线之间夹角的余弦值。
计算公式(在 topo.py:53
cosine_i = np.cos(solar_zn)*np.cos(slope) + np.sin(solar_zn)*np.sin(slope)*np.cos(relative_az)
其中:
solar_zn太阳天顶角太阳光线与垂直方向的夹角
solar_az太阳方位角
slope地面坡度
aspect地面坡向
relative_az = aspect - solar_az相对方位角
物理意义:
当 cosine_i = 1 时,太阳光垂直照射地面(正午水平地表)
当 cosine_i < 1 太阳光斜射照度降低
背阴坡 cosine_i 较小向阳坡 cosine_i 较大
支持的5种地形校正方法
1. Cosine 校正余弦校正
原理最简单的地形校正方法假设地表为朗伯体各向同性反射)。
校正公式cosine.py:50
c_factor = cos_solar_zn / cos_i
cos_solar_zn水平地表的太阳天顶角余弦
cos_i实际地表的入射角余弦
校正效果
corrected_reflectance = original_reflectance × (cos_solar_zn / cos_i)
特点
简单快速
假设地表是理想朗伯体对非朗伯体如植被校正过度
2. SCS 校正Sun-Canopy-Sensor
原理考虑森林冠层对地形影响的简化模型基于 "cosine i" 但增加坡度因子
校正公式scs.py:51
c_factor = (cos_slope × cos_solar_zn) / cos_i
Cosine 方法的区别
增加了 cos_slope 坡度余弦
考虑了坡度对观测路径的影响
适用场景森林覆盖区域
3. C 校正C-Correction
原理 Cosine 校正基础上增加一个经验系数 C用于调整非朗伯体效应
校正公式c.py:177
correction_factor = (cos_sz + C) / (cosine_i + C)
其中 C 系数的计算过程c.py:42-76
对每个波段建立反射率与 cosine_i 的线性关系
ρ = slope × cos_i + intercept
通过最小二乘拟合OLS或非负最小二乘NNLS求解
计算 C 系数Soenen et al. 2005, Eq. 8
C = intercept / slope
物理意义C 值越大表示地表越接近朗伯体校正强度越接近 Cosine 方法C 值越小校正越保守
拟合方法选择c_fit_type
ols普通最小二乘Ordinary Least Squares
nnls非负最小二乘Non-Negative Least Squares
wls加权最小二乘Weighted Least Squares
4. SCS+C 校正推荐用于森林
原理结合 SCS C 校正的优势是目前森林地形校正的主流方法Soenen et al. 2005, IEEE TGRS)。
计算步骤scsc.py
计算 c1 因子
c1 = cos(solar_zn) × cos(slope)
计算 C 系数 C 校正相同通过线性回归
综合校正因子scsc.py:115-125
correction_factor = (c1 + C × cos_slope) / (cos_i + C)
为什么有效
SCS 部分处理冠层-地形几何关系
C 部分调整非朗伯体效应
相比单独使用 SCS C 校正能更好地保持植被反射率的一致性
5. Modified Minnaert 校正改进的 Minnaert
原理基于 Minnaert 指数的半经验方法区分植被和非植被区域进行不同强度的校正Richter et al. 2009)。
算法流程modminn.py:46-83
计算调整后的太阳天顶角
if solar_zn < 45°: solar_zn_t = solar_zn + 20°
if 45° solar_zn 55°: solar_zn_t = solar_zn + 15°
if solar_zn > 55°: solar_zn_t = solar_zn + 10°
NDVI 掩膜分类:
植被区NDVI > 0.2
非植被区NDVI ≤ 0.2
计算基础校正因子:
factor = cos_i / cos(solar_zn_t)
分区应用不同指数:
区域 短波(<720nm 长波(≥720nm
非植被
factor^(1/2)
factor^(1/2)
植被
factor^(3/4)
factor^(1/3)
限幅处理
factor = np.clip(factor, 0.25, 1.0)
阈值控制当入射角小于调整后的太阳天顶角时不进行校正
特点
自适应不同地物类型
区分短波和长波有不同的散射特性
通过指数控制校正强度避免过度校正
地形校正的工作流程
topo.py:104-154 可以看出地形校正的完整流程
1. 设置地形参数set_topo
2. 生成计算掩膜calc_mask
3. 计算校正系数calc_*_coeffs
- 遍历每个波段跳过坏波段
- 根据选定的算法类型计算校正因子
4. 应用校正apply_*
- 支持多种数据切分方式line/column/band/chunk/pixels
关键配置参数
参数 类型 说明 适用算法
type
str
校正算法类型
所有
calc_mask
list
计算校正系数的掩膜条件
所有
apply_mask
list
应用校正的掩膜条件
所有
c_fit_type
str
C系数拟合方法ols/nnls/wls
c, scs+c
sample_perc
float
采样比例用于分组计算
分组计算时
算法选择建议
场景 推荐算法 理由
裸土/岩石
Cosine C
地表接近朗伯体
森林植被
SCS+C
考虑冠层几何校正效果最好
草地/灌木
SCS
简单有效
混合地物
Modified Minnaert
自适应不同地物
需要精确结果
C SCS+C + 分组计算
按NDVI分组计算C系数
参考文献
SCS+C 方法Soenen, S. A., Peddle, D. R., & Coburn, C. A. (2005). SCS+C: A Modified Sun-Canopy-Sensor Topographic Correction in Forested Terrain. IEEE TGRS, 43(9), 2148-2159.
方法比较Richter, R., Kellenberger, T., & Kaufmann, H. (2009). Comparison of topographic correction methods. Remote Sensing, 1(3), 184-196.
C 校正Teillet, P. M., Guindon, B., & Goodenough, D. G. (1982). On the slope-aspect correction of multispectral scanner data. Canadian Journal of Remote Sensing, 8(2), 84-106.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,133 @@
{
"input_files": [
"E:/is2/yaopu/output/before.dat"
],
"file_type": "envi",
"bad_bands": [],
"num_cpus": 10,
"anc_files": {
"E:/is2/yaopu/output/before.dat": {
"path_length": [
"E:/is2/yaopu/output/angel.dat",
0
],
"sensor_az": [
"E:/is2/yaopu/output/angel.dat",
9
],
"sensor_zn": [
"E:/is2/yaopu/output/angel.dat",
8
],
"solar_az": [
"E:/is2/yaopu/output/angel.dat",
7
],
"solar_zn": [
"E:/is2/yaopu/output/angel.dat",
6
],
"phase": [
"E:/is2/yaopu/output/angel.dat",
0
],
"slope": [
"E:/is2/yaopu/output/angel.dat",
0
],
"aspect": [
"E:/is2/yaopu/output/angel.dat",
0
],
"cosine_i": [
"E:/is2/yaopu/output/angel.dat",
0
],
"utc_time": [
"E:/is2/yaopu/output/angel.dat",
0
]
}
},
"corrections": [
"brdf"
],
"topo": {
"type": "mod_minneart",
"calc_mask": [],
"apply_mask": [],
"c_fit_type": "nnls"
},
"brdf": {
"type": "flex",
"grouped": true,
"geometric": "li_dense_r",
"volume": "ross_thick",
"b/r": 2.5,
"h/b": 2.0,
"sample_perc": 0.1,
"interp_kind": "linear",
"calc_mask": [
[
"water",
{
"band_1": 850,
"band_2": 660,
"threshold": 290.0
}
],
[
"kernel_finite",
{}
],
[
"ancillary",
{
"name": "sensor_zn",
"min": 0.0349,
"max": 1000000.0
}
],
[
"ndi",
{
"band_1": 550,
"band_2": 2150,
"min": -1.0,
"max": 0.0
}
]
],
"apply_mask": [
[
"water",
{
"band_1": 850,
"band_2": 660,
"threshold": 290.0
}
]
],
"bin_type": "dynamic",
"num_bins": 18,
"ndvi_bin_min": 0.05,
"ndvi_bin_max": 1.0,
"ndvi_perc_min": 10,
"ndvi_perc_max": 95,
"solar_zn_type": "scene"
},
"glint": {
"type": "hochberg",
"correction_wave": 2150,
"apply_mask": []
},
"export": {
"coeffs": false,
"image": true,
"masks": true,
"subset_waves": [],
"output_dir": "E:/is2/yaopu/Flex",
"suffix": "topo_brdf_glint"
},
"resample": false
}

451
GUI/软件使用说明.md Normal file
View File

@ -0,0 +1,451 @@
# BRDF校正工具 - 使用说明文档
## 一、软件概述
BRDF校正工具是一款用于高光谱图像双向反射分布函数BRDF校正的统一GUI应用程序支持两种场景
1. **陆地植被BRDF校正**FlexBRDF算法
- 适用于陆地植被和地表的BRDF、地形和镜面反射校正
- 支持AVIRIS、AVIRIS-NG等机载高光谱数据
2. **水体BRDF校正**Ocean BRDF Correction
- 专用于水体和海洋的BRDF校正
- 支持多种BRDF模型和不确定性估算
---
## 二、快速开始
### 2.1 启动软件
```bash
python brdf_gui.py
```
启动后将显示主界面,选择需要使用的模块:
- 点击"进入陆地模块"打开陆地植被BRDF校正
- 点击"进入水体模块"打开水体BRDF校正
---
## 三、陆地植被BRDF校正模块FlexBRDF
### 3.1 界面布局
模块界面分为左右两部分:
- **左侧**:配置选项卡(文件与波段映射、校正参数、输出设置)
- **右侧**:执行日志和进度显示
### 3.2 文件与波段映射页
#### 3.2.1 文件夹设置
| 参数 | 说明 | 默认值 |
|------|------|--------|
| 输入文件夹 | 反射率文件所在目录ENVI格式 | 空 |
| 辅助文件夹 | 观测几何数据目录 | 空 |
| CPU核心数 | 并行处理使用的CPU核心数 | 1 |
**操作步骤**
1. 点击"浏览..."按钮选择输入文件夹和辅助文件夹
2. 点击"自动匹配文件"按钮,软件会自动匹配反射率文件(`_rfl`后缀)和观测几何文件(`_obs`后缀)
3. 匹配成功的文件对将显示在列表中
#### 3.2.2 文件匹配规则
- 反射率文件命名格式:`{prefix}_rfl{ext}`
- 观测几何文件命名格式:`{prefix}_obs{ext}`
- 软件根据`{prefix}`部分自动匹配对应文件对
#### 3.2.3 手动添加文件对
如需手动指定文件对:
1. 在"手动添加文件对"区域,分别选择反射率文件和观测几何文件
2. 点击"添加文件对"按钮
#### 3.2.4 波段映射设置
每个输入文件需要映射以下观测几何参数到辅助文件的波段:
| 参数名称 | 说明 | 默认波段 |
|----------|------|----------|
| path_length | 路径长度 | 0 |
| sensor_zn | 传感器天顶角 | 1 |
| sensor_az | 传感器方位角 | 2 |
| solar_zn | 太阳天顶角 | 3 |
| solar_az | 太阳方位角 | 4 |
| phase | 相位角 | 5 |
| slope | 坡度 | 6 |
| aspect | 坡向 | 7 |
| cosine_i | 入射角余弦 | 8 |
**设置方法**
1. 在文件列表中选择一个文件
2. 在右侧波段映射表格中设置各参数对应的波段索引从0开始
3. 点击"应用当前映射"保存设置
### 3.3 校正参数页
#### 3.3.1 选择校正类型
勾选需要执行的校正类型(可单选或多选):
| 校正类型 | 说明 |
|----------|------|
| Topo (地形校正) | 消除地形起伏对反射率的影响 |
| BRDF (双向反射分布函数) | 校正各向异性反射效应 |
| Glint (耀斑校正) | 消除水体或湿润表面的镜面反射 |
#### 3.3.2 地形校正参数Topo Correction
| 参数 | 选项 | 默认值 | 说明 |
|------|------|--------|------|
| 类型 (type) | mod_minneart, scs+c, cosine, c, scs | mod_minneart | 地形校正算法类型 |
| 拟合类型 (c_fit_type) | nnls, ols, wls | nnls | 系数拟合方法 |
**掩膜条件设置**
- **计算掩膜 (calc_mask)**:定义参与计算校正因子的像素条件
- **应用掩膜 (apply_mask)**:定义应用校正的像素条件
掩膜条件类型详见[第5章 掩膜条件说明](#五掩膜条件说明)。
#### 3.3.3 BRDF校正参数BRDF Correction
| 参数 | 选项/范围 | 默认值 | 说明 |
|------|-----------|--------|------|
| 算法类型 (type) | flex, universal | flex | BRDF算法类型 |
| 启用分组 (grouped) | 勾选/取消 | 勾选 | 是否按NDVI分组计算 |
| 几何核 (geometric) | li_sparse, li_dense, li_sparse_r, li_dense_r, roujean | li_sparse | 几何光学核函数 |
| 体积核 (volume) | ross_thin, ross_thick, hotspot, roujean | ross_thick | 体积散射核函数 |
| b/r 比率 | 0.1 - 10.0 | 2.5 | 树冠宽度/高度比 |
| h/b 比率 | 0.1 - 5.0 | 2.0 | 树高/树冠宽度比 |
| 采样比例 (sample_perc) | 0.01 - 1.0 | 0.1 | 用于拟合的采样比例 |
| 插值方法 (interp_kind) | linear, nearest, cubic | linear | 插值方法 |
| 分箱类型 (bin_type) | dynamic, fixed | dynamic | NDVI分箱类型 |
| 分箱数量 (num_bins) | 1 - 100 | 18 | NDVI分箱数量 |
| NDVI最小值 (ndvi_bin_min) | 0.0 - 1.0 | 0.05 | 分箱NDVI下限 |
| NDVI最大值 (ndvi_bin_max) | 0.0 - 1.0 | 1.0 | 分箱NDVI上限 |
| NDVI百分比最小值 (ndvi_perc_min) | 0 - 100 | 10 | 有效样本百分比下限 |
| NDVI百分比最大值 (ndvi_perc_max) | 0 - 100 | 95 | 有效样本百分比上限 |
| 太阳天顶角类型 (solar_zn_type) | scene, fixed, mean, custom | scene | 太阳天顶角计算方法 |
**核函数组合推荐**
- 植被覆盖区:`li_sparse` + `ross_thick`
- 稀疏植被区:`roujean` + `roujean`
- 浓密植被区:`li_dense` + `ross_thick`
#### 3.3.4 耀斑校正参数Glint Correction
| 参数 | 选项 | 默认值 | 说明 |
|------|------|--------|------|
| 类型 (type) | mod_minneart, scs+c, cosine, c, scs, nir, mask | nir | 耀斑校正方法 |
| 拟合类型 (c_fit_type) | nnls, ols, wls | nnls | 系数拟合方法 |
**掩膜条件设置**
- **计算掩膜 (calc_mask)**:定义参与计算耀斑因子的像素
- **应用掩膜 (apply_mask)**:定义应用耀斑校正的像素
### 3.4 输出设置页
| 参数 | 说明 | 默认值 |
|------|------|--------|
| 输出目录 | 校正结果保存路径 | 空 |
| 后缀 (Suffix) | 输出文件名后缀 | topo_brdf_glint |
| 输出系数 (coeffs) | 输出校正系数文件 | 不勾选 |
| 输出图像 (image) | 输出校正后图像 | 勾选 |
| 输出掩膜 (masks) | 输出使用的掩膜 | 不勾选 |
**输出文件名格式**
```
{原文件名}_{后缀}.dat
```
### 3.5 操作按钮
| 按钮 | 功能 |
|------|------|
| 加载 JSON 配置 | 从JSON文件加载之前保存的配置 |
| 保存 JSON 配置 | 将当前配置保存为JSON文件 |
| 生成配置并运行 | 生成配置并启动BRDF校正处理 |
---
## 四、水体BRDF校正模块Ocean BRDF
### 4.1 输入文件设置
| 参数 | 文件格式 | 说明 |
|------|----------|------|
| 高光谱文件 | ENVI (.dat, .bsq, .bip, .bil, .hdr) | 高光谱遥感影像 |
| 角度文件 | ENVI (.dat, .bsq, .bip, .bil, .hdr) | 观测几何角度数据 |
| 水体掩膜 | GeoTIFF (.tif, .tiff) | 水体区域掩膜 |
### 4.2 BRDF模型参数
| 参数 | 选项 | 默认值 | 说明 |
|------|------|--------|------|
| BRDF模型 | L11, M02, M02SeaDAS, O25 | L11 | BRDF校正模型 |
| 处理块大小 | 1024 - 16384 | 4096 | 光谱维分块大小 |
| 空间块大小 | 256 - 2048 | 512 | 空间维分块大小 |
| 计算不确定性 | 勾选/取消 | 取消 | 是否计算BRDF不确定度 |
**BRDF模型说明**
- **L11**Lee et al. (2011) 模型,适用于开阔大洋
- **M02**Morel et al. (2002) 模型经典水体BRDF模型
- **M02SeaDAS**SeaDAS实现的M02模型
- **O25**:最新优化模型
### 4.3 输出变量选择
支持多选按住Ctrl键选择多个
| 变量 | 说明 |
|------|------|
| Rw_brdf | BRDF校正后的水体反射率 |
| rho_ex_w | 水体反射率 (nrrs * π) |
| nrrs | 归一化遥感反射率 |
| C_brdf | BRDF校正因子 |
| brdf_unc | BRDF不确定度需勾选计算不确定性 |
| nrrs_unc | nrrs不确定度需勾选计算不确定性 |
**默认选中**Rw_brdf, nrrs, C_brdf
### 4.4 输出设置
| 参数 | 选项 | 默认值 |
|------|------|--------|
| 输出路径 | 文件前缀路径 | 空 |
| 输出格式 | ENVI | ENVI |
### 4.5 运行
点击"运行水体BRDF校正"按钮开始处理。
---
## 五、掩膜条件说明
掩膜用于定义参与计算或应用校正的像素范围。所有掩膜条件取交集(同时满足)。
### 5.1 支持的掩膜类型
| 类型 | 说明 | 参数 |
|------|------|------|
| **ndi** | 归一化差值指数 | band_1, band_2, min, max |
| **ancillary** | 辅助数据掩膜 | name, min, max |
| **neon_edge** | NEON数据边缘掩膜 | radius |
| **kernel_finite** | 核函数有限性掩膜 | 无参数 |
| **water** | 水体掩膜 | band_1, band_2, threshold |
| **external** | 外部掩膜文件 | file_path, class |
| **band** | 单波段阈值 | band, min, max |
### 5.2 各类型详细参数
#### ndi归一化差值指数
```
ndi = (band_1 - band_2) / (band_1 + band_2)
保留范围: [min, max] 内的像素
```
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| band_1 | 整数 | 550 | 第一个波段波长(nm) |
| band_2 | 整数 | 2150 | 第二个波段波长(nm) |
| min | 浮点数 | -1.0 | NDI最小值 |
| max | 浮点数 | 0.0 | NDI最大值 |
**典型应用**
- NDVI植被掩膜band_1=850, band_2=680, min=0.2, max=1.0
- NDWI水体掩膜band_1=860, band_2=1240, min=-1.0, max=0.0
#### ancillary辅助数据掩膜
基于观测几何参数创建掩膜。
| 参数 | 类型 | 默认值 | 可选值 |
|------|------|--------|--------|
| name | 字符串 | slope | slope, aspect, cosine_i, sensor_zn, sensor_az, solar_zn, solar_az, phase, path_length, utc_time |
| min | 浮点数 | 0.0 | - |
| max | 浮点数 | 1.0 | - |
**典型应用**
- 坡度掩膜只处理坡度小于30度的区域
- name=slope, min=0, max=30
- 太阳天顶角掩膜:排除大太阳天顶角
- name=solar_zn, min=0, max=60
#### neon_edgeNEON边缘掩膜
用于消除NEON高光谱数据图像边缘的噪声区域。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| radius | 整数 | 1 | 边缘裁剪像素数 |
#### kernel_finite核函数有限性掩膜
自动排除会导致核函数计算溢出或不稳定的像素。无需参数。
#### water水体掩膜
使用特定波段组合识别水体。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| band_1 | 整数 | 550 | 第一个波段波长(nm) |
| band_2 | 整数 | 850 | 第二个波段波长(nm) |
| threshold | 浮点数 | 0.0 | 水体识别阈值 |
#### external外部掩膜文件
使用外部分类或掩膜文件。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| file_path | 字符串 | 空 | 外部掩膜文件路径 |
| class | 整数 | 1 | 要保留的类别值 |
#### band单波段阈值
基于单个波段的值创建掩膜。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| band | 整数 | 650 | 波段波长(nm) |
| min | 浮点数 | 0.0 | 最小值 |
| max | 浮点数 | 1.0 | 最大值 |
### 5.3 掩膜条件设置步骤
1. 在校正参数页找到掩膜设置区域
2. 点击"+ 添加条件"按钮
3. 选择掩膜类型
4. 设置对应参数值
5. 可添加多个条件(取交集)
6. 点击"删除"按钮移除不需要的条件
---
## 六、配置文件的保存与加载
### 6.1 保存配置
1. 完成所有参数设置
2. 点击"保存 JSON 配置"按钮
3. 选择保存路径和文件名
4. 配置将以JSON格式保存包含
- 输入文件列表
- 波段映射关系
- 校正参数设置
- 掩膜条件
- 输出设置
### 6.2 加载配置
1. 点击"加载 JSON 配置"按钮
2. 选择之前保存的JSON配置文件
3. 软件将自动恢复所有设置
**注意**
- 加载配置后会自动匹配文件对,请确保输入文件和辅助文件路径仍然有效
- 如果文件路径已改变,需要重新手动设置文件夹路径
---
## 七、典型工作流程
### 7.1 陆地植被BRDF校正流程
1. **启动软件** → 选择"陆地植被BRDF校正"
2. **设置文件夹** → 选择输入文件夹和辅助文件夹
3. **匹配文件** → 点击"自动匹配文件"
4. **检查映射** → 确认波段映射设置正确
5. **选择校正** → 勾选需要的校正类型Topo/BRDF/Glint
6. **设置参数** → 根据数据特点调整校正参数
7. **设置掩膜** → 添加合适的掩膜条件如NDVI>0.2
8. **设置输出** → 指定输出目录和后缀
9. **保存配置** → (可选)保存当前配置
10. **运行** → 点击"生成配置并运行"
### 7.2 水体BRDF校正流程
1. **启动软件** → 选择"水体BRDF校正"
2. **选择文件** → 依次选择高光谱文件、角度文件、水体掩膜
3. **选择模型** → 选择适合的BRDF模型通常用L11
4. **选择变量** → 勾选需要的输出变量
5. **调整参数** → (可选)调整块大小以优化性能
6. **设置输出** → 指定输出文件前缀路径
7. **运行** → 点击"运行水体BRDF校正"
---
## 八、常见问题与解决方案
### 8.1 文件匹配问题
**问题**:自动匹配找不到文件对
**解决**
- 检查文件命名是否符合 `{prefix}_rfl``{prefix}_obs` 格式
- 确保输入文件夹和辅助文件夹路径正确
- 使用手动添加功能指定文件对
### 8.2 波段映射问题
**问题**:提示波段索引超出范围
**解决**
- 检查辅助文件的实际波段数
- 确认波段索引从0开始计数
- 验证辅助文件格式正确
### 8.3 内存不足问题
**问题**:处理大文件时内存溢出
**解决**
- 减少CPU核心数设置
- 对于水体模块减小处理块大小chunk_size
- 分块处理FlexBRDF会自动分块
### 8.4 掩膜问题
**问题**:校正后结果为空或过少
**解决**
- 检查掩膜条件是否过于严格
- 验证NDVI或波段阈值设置是否合理
- 查看掩膜类型是否与数据匹配
---
## 九、技术参数参考
### 9.1 支持的文件格式
| 类型 | 格式 |
|------|------|
| 输入反射率 | ENVI标准格式 (.dat + .hdr) |
| 观测几何 | ENVI标准格式 (.dat + .hdr) |
| 水体掩膜 | GeoTIFF (.tif) |
| 输出结果 | ENVI标准格式 |
| 配置文件 | JSON (.json) |
### 9.2 系统要求
- **操作系统**Windows 10/11, Linux, macOS
- **Python版本**3.8+
- **内存**建议8GB以上处理大图像需要更多
- **依赖库**PyQt5, numpy, scipy等详见requirements.txt
---
## 十、联系与支持
如有问题或建议,请查看项目文档或联系开发团队。
---
*文档版本v1.0*
*最后更新2026年4月*

1417
angle/angle_compute.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -177,11 +177,11 @@ def _block_windows(src, block_size=512):
def _resolve_output_envi_path(output_file):
"""Return the ENVI image path used for rasterio writes."""
if output_file.lower().endswith(".img"):
if output_file.lower().endswith(".dat"):
return output_file
if output_file.lower().endswith(".hdr"):
return output_file[:-4] + ".img"
return output_file + ".img"
return output_file[:-4] + ".dat"
return output_file + ".dat"
def _read_wavelengths_from_header(hyperspectral_file):
@ -259,7 +259,7 @@ def _get_output_filename(base_output_file: str, var_name: str) -> str:
input.hdr + brdf_unc -> input_brdf_unc.hdr
"""
base, ext = os.path.splitext(base_output_file)
if ext.lower() in ('.hdr', '.img'):
if ext.lower() in ('.hdr', '.dat'):
base = base # keep base without extension
suffix = f"_{var_name}" if var_name.lower() != "rw_brdf" else ""
return f"{base}{suffix}{ext}"
@ -486,6 +486,29 @@ def run_brdf_correction_block(
for dst in output_dsts.values():
dst.close()
# Write ENVI header files with wavelength information from input
for var_name, out_path in output_paths.items():
hdr_path = out_path[:-4] + ".hdr" if out_path.lower().endswith(".dat") else out_path + ".hdr"
# Prepare ENVI header content with wavelengths from input file
header_lines = [
"ENVI",
f"description = {{BRDF corrected result: {var_name}}}",
f"samples = {n_cols}",
f"lines = {n_rows}",
f"bands = {n_bands}",
"header offset = 0",
"file type = ENVI Standard",
"data type = 4", # float32
"interleave = bsq",
"byte order = 0", # little-endian
f"wavelength = {{{', '.join(str(v) for v in wavelengths.tolist())}}}",
"wavelength units = nm",
]
# Write header file
with open(hdr_path, 'w') as f:
f.write('\n'.join(header_lines))
print(f" Written header: {hdr_path}")
# Print performance statistics
total_blocks = blocks_processed + blocks_skipped
if total_blocks > 0:
@ -864,7 +887,7 @@ def main():
parser.add_argument("hyperspectral_file", help="Input ENVI BSQ hyperspectral file or .hdr path.")
parser.add_argument("angle_file", help="Input ENVI BIP angle file or .hdr path.")
parser.add_argument("mask_file", help="Input water mask GeoTIFF; pixels == 1 are corrected.")
parser.add_argument("output_file", help="Output path prefix. ENVI writes .hdr/.img, NetCDF writes .nc.")
parser.add_argument("output_file", help="Output path prefix. ENVI writes .hdr/.dat, NetCDF writes .nc.")
parser.add_argument("--brdf-model", default="L11", choices=["L11", "M02", "M02SeaDAS", "O25"])
parser.add_argument("--output-var", nargs='+', default=["Rw_brdf"],
help="Output variables to save (one or more). "

View File

@ -1,14 +1,42 @@
# BRDF_GUI 项目依赖
# 安装命令: pip install -r requirements.txt
#
# 本项目包含两个主要模块:
# 1. 陆地植被BRDF校正 (FlexBRDF) - 使用 HyTools 库
# 2. 水体BRDF校正 (Ocean BRDF) - 使用 ocbrdf 模块
# ========== 核心科学计算 ==========
numpy>=1.21.0
ray>=2.0.0
h5py>=3.0.0
h5netcdf>=0.13.0
matplotlib>=3.5.0
pandas>=1.3.0
scikit-learn>=1.0.0
scipy>=1.7.0
pandas>=1.3.0
xarray>=0.20.0
rasterio>=1.2.0
spectral>=0.22.0
# ========== GUI 界面 ==========
PyQt5>=5.15.0
# ========== 高光谱数据处理 ==========
spectral>=0.22.0 # ENVI文件读写
rasterio>=1.3.0 # 栅格数据处理 (GeoTIFF/ENVI)
geopandas>=0.10.0 # 地理空间数据处理
shapely>=2.0.0 # 几何形状处理
# ========== HDF5/NetCDF 支持 ==========
h5py>=3.0.0 # HDF5文件支持
h5netcdf>=0.13.0 # NetCDF via HDF5
netCDF4>=1.6.0 # NetCDF文件支持 (可选)
# ========== 并行处理 ==========
ray>=2.0.0
# ========== 机器学习 ==========
scikit-learn>=1.0.0
# ========== 可视化 ==========
matplotlib>=3.5.0
# ========== 观测几何计算 ==========
pvlib>=0.10.0 # 太阳位置计算
pyproj>=3.4.0 # 坐标投影转换
# ========== 进度条/工具 ==========
tqdm>=4.60.0
geopandas>=0.10.0
shapely>=2.0.0

103
slope/caijian.py Normal file
View File

@ -0,0 +1,103 @@
import rasterio
from rasterio.warp import reproject, Resampling
import numpy as np
def merge_angles_with_slope_aspect(angle_file, slope_aspect_file, output_file,
resampling=Resampling.bilinear):
"""
将坡度坡向波段添加到角度文件波段之后输出合并的ENVI DAT文件。
输出文件的空间范围、分辨率、投影与角度文件完全一致。
掩膜基于角度文件的忽略值生成:只有所有角度波段均为忽略值(0.0)的像素才被设为忽略值。
输出文件的忽略值统一设为0.0。
Parameters
----------
angle_file : str
包含角度信息的ENVI DAT文件如传感器角度、太阳角度等至少1个波段
该文件定义了输出的目标空间参数和掩膜基准。
slope_aspect_file : str
包含坡度坡向的ENVI DAT文件通常2个波段
output_file : str
输出合并后的ENVI DAT文件路径将自动生成.hdr头文件
resampling : rasterio.warp.Resampling, optional
重采样方法,默认为双线性(适用于连续参数)。
"""
# 1. 从角度文件获取目标空间参数、数据、掩膜
with rasterio.open(angle_file) as src_angle:
# 目标空间参数
dst_transform = src_angle.transform
dst_width = src_angle.width
dst_height = src_angle.height
dst_crs = src_angle.crs
# 读取角度文件所有波段
angle_data = src_angle.read() # shape: (bands, rows, cols)
angle_count = src_angle.count
# 获取角度文件的忽略值如果未定义默认为0.0,但应给出警告)
angle_nodata = src_angle.nodata
if angle_nodata is None:
print("警告角度文件未定义忽略值将使用0.0作为默认忽略值。")
angle_nodata = 0.0
# 构建掩膜:所有波段均不等于忽略值视为有效
mask = np.all(angle_data != angle_nodata, axis=0)
# 2. 处理坡度坡向文件:重采样到目标空间
with rasterio.open(slope_aspect_file) as src_sa:
src_sa_data = src_sa.read() # shape: (bands, src_sa_height, src_sa_width)
src_sa_count = src_sa.count
src_sa_crs = src_sa.crs
src_sa_transform = src_sa.transform
sa_resampled = np.empty((src_sa_count, dst_height, dst_width),
dtype=src_sa_data.dtype)
reproject(
source=src_sa_data,
destination=sa_resampled,
src_transform=src_sa_transform,
src_crs=src_sa_crs,
dst_transform=dst_transform,
dst_crs=dst_crs,
resampling=resampling
)
# 3. 合并波段:角度在前,坡度坡向在后
merged_data = np.concatenate([angle_data, sa_resampled], axis=0)
merged_count = merged_data.shape[0]
# 4. 准备输出元数据基于角度文件的profile
with rasterio.open(angle_file) as src_angle:
out_profile = src_angle.profile.copy()
output_nodata = 0.0 # 强制输出忽略值为0.0(用户要求)
out_profile.update({
'driver': 'ENVI',
'height': dst_height,
'width': dst_width,
'transform': dst_transform,
'crs': dst_crs,
'count': merged_count,
'dtype': merged_data.dtype,
'nodata': output_nodata
})
# 5. 应用掩膜将无效区域的像素设为忽略值0.0
for b in range(merged_count):
merged_data[b, ~mask] = output_nodata
# 6. 写入输出文件
with rasterio.open(output_file, 'w', **out_profile) as dst:
dst.write(merged_data)
print(f"处理完成!合并后的文件已保存至:{output_file}")
print(f"总波段数:{merged_count}(角度 {angle_count} 波段 + 坡度坡向 {src_sa_count} 波段)")
print(f"有效像素区域基于角度文件所有波段判断,忽略值统一设为 0.0。")
# 示例用法
if __name__ == "__main__":
angle_file = r"E:\is2\yaopu\output\angel.dat" # 角度DAT文件
slope_aspect_file = r"E:\is2\yaopu\DEM\slope_aspect_cosine.dat" # 原始坡度坡向DAT
out_file = r"E:\is2\yaopu\DEM\angles_with_slope_aspect.dat" # 合并输出
merge_angles_with_slope_aspect(angle_file, slope_aspect_file, out_file)

197
slope/readme.txt Normal file
View File

@ -0,0 +1,197 @@
================================================================================
地形校正模块 - 已知问题与改进方向
================================================================================
【问题描述】
-----------
当前版本的 slope_aspectV2.py 在处理推扫式无人机高光谱图像时,存在一个关键假设:
整幅图像使用单一的太阳天顶角和方位角来计算 cosine_i入射角余弦
然而,实际数据采集情况是:
- 高光谱图像由3个架次的无人机飞行拼接而成
- 每个架次的飞行时间不同(通常间隔数分钟至数十分钟)
- 因此,不同扫描线(或不同图像区域)对应的太阳角度是不同的
- 但目前代码仅基于DEM中心点坐标计算一组太阳角度并应用于整幅图像
【技术影响】
-----------
这个简化会导致以下问题:
1. 时间跨度误差
- 假设3个架次间隔30分钟太阳方位角可能变化7-8度取决于纬度
- 在日出/日落时段太阳高度角变化可达10-15度
- 这会导致不同架次交界处的 cosine_i 出现系统性偏差
2. 地形校正不准确
- 东向坡面:过早架次会低估照明,过晚架次会高估照明
- 西向坡面:与东向坡面相反
- 交界区域会出现明显的"拼接缝"现象
3. BRDF校正累积误差
- cosine_i 是许多BRDF模型的关键输入参数
- 角度误差会传播到后续的BRDF地形校正结果
【当前代码实现】
---------------
文件: slope/slope_aspectV2.py
当前流程:
1. 读取DEM -> 2. 计算坡度/坡向 -> 3. 获取DEM中心点坐标(lat, lon)
4. 根据单一时间计算一组太阳角度(solar_zn, solar_az)
5. 将这组角度应用于整幅图像的所有像素
关键代码段约110-130行
```python
# 获取DEM中心点坐标
latitude, longitude = get_dem_center_coords(dem) # 仅一个中心点!
# 计算太阳角度(仅一组)
solar_zn_deg, solar_az_deg = calc_solar_angles(latitude, longitude, observation_time)
# 应用于整幅图像
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
```
【需要的改进方向】
-----------------
接手人员需要进行以下修改:
方案A: 分架次处理(推荐)
----------------------------
如果已知3个架次的地理分界线或时间范围
1. 输入格式修改
- 接收3组时间参数或从飞行日志自动提取
- 例如: -t1 "2024-06-15 08:30:00" -t2 "2024-06-15 09:00:00" -t3 "2024-06-15 09:30:00"
2. 空间分区处理
- 根据每架次的覆盖范围可从高光谱图像的GLT或地理坐标确定
- 将DEM分割为3个区域
- 每个区域使用对应架次的时间计算太阳角度
3. 修改后的伪代码示例:
```python
# 定义3个架次的时间和地理范围
flight_segments = [
{'time': '2024-06-15 08:30:00', 'row_range': (0, 1200)}, # 架次1: 行0-1199
{'time': '2024-06-15 09:00:00', 'row_range': (1200, 2800)}, # 架次2: 行1200-2799
{'time': '2024-06-15 09:30:00', 'row_range': (2800, 4000)}, # 架次3: 行2800-3999
]
# 初始化cosine_i数组
cosine_i = np.zeros_like(slope_data)
# 分段计算
for segment in flight_segments:
dt = parse_datetime(segment['time'])
solar_zn, solar_az = calc_solar_angles(lat, lon, dt)
y1, y2 = segment['row_range']
cosine_i[y1:y2, :] = calc_cosine_i(
np.deg2rad(solar_zn), np.deg2rad(solar_az),
aspect_rad[y1:y2, :], slope_rad[y1:y2, :]
)
```
方案B: 逐行/逐像素太阳角度(更精确)
-------------------------------------
如果需要更高精度(考虑太阳角度在单架次内的缓慢变化):
1. 输入要求
- 高光谱图像的每一行对应的时间戳可从IMU/GPS数据提取
- 或者每个像素的精确UTC时间
2. 逐行计算
```python
# 假设times是一个与图像行数相同长度的一维数组
# times[i] = 第i行的观测时间datetime对象
for row in range(n_rows):
dt = observation_times[row] # 该行的具体时间
solar_zn, solar_az = calc_solar_angles(lat, lon, dt)
cosine_i[row, :] = calc_cosine_i(
np.deg2rad(solar_zn), np.deg2rad(solar_az),
aspect_rad[row, :], slope_rad[row, :]
)
```
3. 性能优化
- 由于pvlib计算较慢可对时间相近的行批量计算
- 例如每10行计算一次太阳角度中间行插值
方案C: 从飞行日志自动提取(最自动化)
--------------------------------------
如果飞行平台记录了GPS/IMU数据
1. 输入数据
- 读取POS数据位置、姿态、时间
- 或从高光谱图像的GLT地理位置查找表提取
2. 建立时间-位置映射
- 根据无人机位置和时间建立每行图像的观测时间
3. 分区域计算cosine_i
【数据结构参考】
---------------
典型的推扫式高光谱数据结构:
高光谱图像 (lines, samples, bands)
- lines: 扫描线数(飞行方向的像素数)
- samples: 每行的像素数(垂直飞行方向)
- 第i行对应无人机的第i个位置
关键信息来源:
1. 图像头文件 (.hdr) - 通常包含起始时间和积分时间
2. GLT文件 (Geographic Lookup Table) - 每个像素的地理坐标
3. 飞行日志/POS数据 - 每行对应的时间戳
4. 帧辅助数据 (Frame Auxiliary Data) - 部分传感器自带每帧时间
【建议的输入接口修改】
----------------------
当前命令行接口:
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:00:00"
建议扩展为:
# 方式1: 手动指定3个架次的时间和行范围
python slope_aspectV2.py -i dem.tif -o output.dat \
--segments "0,1200,2024-06-15T08:30:00" \
--segments "1200,2800,2024-06-15T09:00:00" \
--segments "2800,4000,2024-06-15T09:30:00"
# 方式2: 从飞行日志文件自动读取
python slope_aspectV2.py -i dem.tif -o output.dat --time-log flight_log.csv
# 方式3: 从高光谱图像头文件提取时间信息
python slope_aspectV2.py -i dem.tif -o output.dat --hyperspectral-ref image.hdr
【验证方法】
-----------
修改后应验证以下指标:
1. 连续性检查
- 目视检查3个架次交界处的cosine_i是否连续
- 统计交界行前后10行的cosine_i差异
2. 物理合理性
- 东向坡的cosine_i应随时间增加而增加上午飞行
- 西向坡的cosine_i应随时间增加而减小上午飞行
3. 与GLT对比
- 如果高光谱有GLT可根据每像素的太阳角度公式验证
【相关文件】
-----------
- slope_aspectV2.py - 当前主程序(需要修改)
- angle_compute.py - 角度计算工具(可能包含相关逻辑)
- 高光谱图像的.hdr文件 - 可能包含时间元数据
- 飞行日志/POS数据 - 外部时间参考
【优先级建议】
--------------
1. 【高】首先实现方案A分架次处理- 工作量适中,效果显著
2. 【中】评估是否需要方案B逐行- 取决于太阳角度变化幅度
3. 【低】长期可考虑方案C全自动- 需要整合飞行数据解析
简单来说在计算的时候需要加入角度文件进行逐像素的cosi计算

83
slope/slope_aspect.py Normal file
View File

@ -0,0 +1,83 @@
import xdem
import rasterio
import numpy as np
def calc_cosine_i(solar_zn, solar_az, aspect, slope):
"""Generate cosine i image. The cosine of the incidence angle (i) is
defined as the angle between the normal to the pixel surface
and the solar zenith direction.
All input geometry units must be in radians.
Args:
solar_az (numpy.ndarray): Solar azimuth angle.
solar_zn (numpy.ndarray): Solar zenith angle.
aspect (numpy.ndarray): Ground aspect.
slope (numpy.ndarray): Ground slope.
Returns:
numpy.ndarray: Cosine i image.
"""
relative_az = aspect - solar_az
cosine_i = (np.cos(solar_zn) * np.cos(slope) +
np.sin(solar_zn) * np.sin(slope) * np.cos(relative_az))
return cosine_i
# ========== 1. 读取 DEM ==========
dem = xdem.DEM("E:/is2/yaopu/DEM/dsm.tif")
# ========== 2. 计算坡度、坡向 ==========
slope = xdem.terrain.slope(dem, method='ZevenbergThorne')
aspect = xdem.terrain.aspect(dem)
# ========== 3. 获取空间参考信息 ==========
transform = dem.transform
crs = dem.crs
# ========== 4. 转换为 numpy 数组 ==========
slope_data = slope.data
aspect_data = aspect.data
# ========== 5. 太阳角度(单位:度,根据实际飞行时间和地理位置设置) ==========
solar_zn_deg = 30.0 # 太阳天顶角(从天顶到太阳的夹角),例如 30°
solar_az_deg = 180.0 # 太阳方位角(从北顺时针),例如 180°正南
# 转换为弧度(因为三角函数需要弧度)
solar_zn_rad = np.deg2rad(solar_zn_deg)
solar_az_rad = np.deg2rad(solar_az_deg)
# 坡度和坡向也转换为弧度
slope_rad = np.deg2rad(slope_data)
aspect_rad = np.deg2rad(aspect_data)
# ========== 6. 计算 cosine_i ==========
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
# ========== 7. 处理无效值NaN ==========
# 注意:将 NaN 替换为 0 可能会与真实值为 0 的像元混淆如水平面坡度为0阴影边缘cosine_i=0
# 如需严格区分,建议使用特殊值(如 -9999作为 NoData。
slope_data = np.nan_to_num(slope_data, nan=0.0)
aspect_data = np.nan_to_num(aspect_data, nan=0.0)
cosine_i = np.nan_to_num(cosine_i, nan=0.0)
# ========== 8. 堆叠为多波段数组坡度、坡向、cosine_i ==========
stacked = np.stack([slope_data, aspect_data, cosine_i], axis=0)
# ========== 9. 输出 ENVI 格式文件 ==========
output_path = "E:/is2/yaopu/DEM/slope_aspect_cosine.dat"
with rasterio.open(
output_path,
'w',
driver='ENVI',
height=stacked.shape[1],
width=stacked.shape[2],
count=stacked.shape[0],
dtype=stacked.dtype,
crs=crs,
transform=transform,
nodata=0.0, # 与上面替换的 NaN 值一致
) as dst:
dst.write(stacked)
print(f"已保存多波段 ENVI 文件:{output_path}")
print(f"波段顺序1-坡度(°), 2-坡向(°), 3-cosine_i")

381
slope/slope_aspectV2.py Normal file
View File

@ -0,0 +1,381 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DEM地形处理工具 - 计算坡度、坡向和太阳入射角余弦
该工具从DEM文件中提取坐标信息根据给定时间计算太阳位置
并生成坡度、坡向和cosine_i的多波段ENVI文件。
作者: BRDF_GUI Team
版本: 2.0.0
"""
import xdem
import rasterio
import numpy as np
import pandas as pd
import argparse
import sys
from datetime import datetime
from pathlib import Path
import pvlib.solarposition as solarposition
from rasterio.crs import CRS
import pyproj
def calc_cosine_i(solar_zn, solar_az, aspect, slope):
"""
计算入射角余弦cosine i
入射角余弦定义为地表法线与太阳天顶方向之间夹角的余弦值。
所有角度输入必须为弧度。
Args:
solar_az (numpy.ndarray): 太阳方位角(弧度)
solar_zn (numpy.ndarray): 太阳天顶角(弧度)
aspect (numpy.ndarray): 地面坡向(弧度)
slope (numpy.ndarray): 地面坡度(弧度)
Returns:
numpy.ndarray: 入射角余弦图像
"""
relative_az = aspect - solar_az
cosine_i = (np.cos(solar_zn) * np.cos(slope) +
np.sin(solar_zn) * np.sin(slope) * np.cos(relative_az))
return cosine_i
def get_dem_center_coords(dem):
"""
获取DEM图像中心点的经纬度坐标。
Args:
dem: xdem.DEM对象
Returns:
tuple: (纬度, 经度) - 单位:度
"""
# 获取DEM的范围边界
bounds = dem.bounds # (left, bottom, right, top)
center_x = (bounds.left + bounds.right) / 2
center_y = (bounds.bottom + bounds.top) / 2
# 如果DEM是投影坐标系需要转换为WGS84经纬度
if dem.crs is not None and not dem.crs.is_geographic:
# 创建从投影坐标系到WGS84的转换
transformer = pyproj.Transformer.from_crs(dem.crs, CRS.from_epsg(4326), always_xy=True)
lon, lat = transformer.transform(center_x, center_y)
else:
# 已经是地理坐标系(经纬度)
lon, lat = center_x, center_y
return lat, lon
def parse_datetime(datetime_str):
"""
解析日期时间字符串。
支持的格式:
- YYYY-MM-DD HH:MM:SS
- YYYY-MM-DDTHH:MM:SS
- YYYYMMDD_HHMMSS
Args:
datetime_str (str): 日期时间字符串
Returns:
datetime: datetime对象UTC时间
"""
formats = [
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%dT%H:%M:%S",
"%Y%m%d_%H%M%S",
"%Y-%m-%d",
]
for fmt in formats:
try:
dt = datetime.strptime(datetime_str, fmt)
# 如果只输入了日期默认时间为正午12:00
if fmt == "%Y-%m-%d":
dt = dt.replace(hour=12, minute=0, second=0)
return dt
except ValueError:
continue
raise ValueError(f"无法解析日期时间: {datetime_str}. 支持的格式: YYYY-MM-DD HH:MM:SS, YYYY-MM-DDTHH:MM:SS, YYYYMMDD_HHMMSS")
def get_user_input_datetime():
"""
交互式获取用户输入的日期和时间。
Returns:
datetime: datetime对象UTC时间
"""
print("请输入观测日期和时间UTC时间")
try:
year = int(input(" 年份 (YYYY): "))
month = int(input(" 月份 (MM): "))
day = int(input(" 日期 (DD): "))
hour = int(input(" 小时 (HH, 24小时制): "))
minute = int(input(" 分钟 (MM): "))
second = int(input(" 秒 (SS): "))
dt = datetime(year, month, day, hour, minute, second)
print(f"\n使用的时间: {dt.strftime('%Y-%m-%d %H:%M:%S')} UTC")
return dt
except ValueError as e:
print(f"输入错误: {e}")
print("将使用默认时间 2024-06-15 10:00:00 UTC")
return datetime(2024, 6, 15, 10, 0, 0)
def calc_solar_angles(lat, lon, dt):
"""
使用pvlib计算太阳天顶角和方位角。
Args:
lat (float): 纬度(度)
lon (float): 经度(度)
dt (datetime): datetime对象UTC时间
Returns:
tuple: (太阳天顶角, 太阳方位角) - 单位:度
天顶角: 0=天顶, 90=地平线
方位角: 从北顺时针0=北90=东180=南270=西
"""
# 创建时间索引
times = pd.DatetimeIndex([dt])
# 使用pvlib计算太阳位置
solpos = solarposition.get_solarposition(times, lat, lon)
solar_zn_deg = solpos['zenith'].values[0] # 天顶角
solar_az_deg = solpos['azimuth'].values[0] # 方位角
return solar_zn_deg, solar_az_deg
def write_envi_output(output_path, data, transform, crs, metadata=None):
"""
将数据写入ENVI格式文件。
Args:
output_path (str): 输出文件路径
data (numpy.ndarray): 数据数组 (bands, rows, cols)
transform: 地理变换参数
crs: 坐标参考系
metadata (dict, optional): 额外的元数据
"""
with rasterio.open(
output_path,
'w',
driver='ENVI',
height=data.shape[1],
width=data.shape[2],
count=data.shape[0],
dtype=data.dtype,
crs=crs,
transform=transform,
nodata=0.0,
) as dst:
dst.write(data)
# 写入ENVI头文件元数据
if metadata:
dst.update_tags(**metadata)
def process_dem(dem_path, output_path, datetime_input=None, interactive=False):
"""
主处理函数读取DEM计算坡度坡向计算太阳角度输出结果。
Args:
dem_path (str): DEM输入文件路径
output_path (str): 输出文件路径
datetime_input (str, optional): 日期时间字符串如果为None则使用交互模式
interactive (bool): 是否使用交互式时间输入
Returns:
dict: 处理结果信息
"""
# ========== 1. 读取 DEM ==========
print(f"读取DEM文件: {dem_path}")
dem = xdem.DEM(dem_path)
# ========== 2. 计算坡度、坡向 ==========
print("计算坡度和坡向...")
slope = xdem.terrain.slope(dem, method='ZevenbergThorne')
aspect = xdem.terrain.aspect(dem)
# ========== 3. 获取空间参考信息 ==========
transform = dem.transform
crs = dem.crs
# ========== 4. 转换为 numpy 数组 ==========
slope_data = slope.data
aspect_data = aspect.data
# ========== 5. 获取DEM中心点坐标并计算太阳角度 ==========
print("\n获取DEM坐标信息...")
latitude, longitude = get_dem_center_coords(dem)
print(f" 中心点纬度: {latitude:.6f}°")
print(f" 中心点经度: {longitude:.6f}°")
print(f" 坐标系: WGS84")
# 获取观测时间
if datetime_input:
observation_time = parse_datetime(datetime_input)
print(f"\n使用命令行提供的时间: {observation_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
elif interactive or not sys.stdin.isatty():
observation_time = get_user_input_datetime()
else:
# 默认时间(如果非交互式且未提供时间)
observation_time = datetime(2024, 6, 15, 10, 0, 0)
print(f"\n使用默认时间: {observation_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
# 计算太阳角度
print("\n计算太阳位置...")
solar_zn_deg, solar_az_deg = calc_solar_angles(latitude, longitude, observation_time)
print(f" 太阳天顶角: {solar_zn_deg:.2f}°")
print(f" 太阳高度角: {90 - solar_zn_deg:.2f}°")
print(f" 太阳方位角: {solar_az_deg:.2f}°")
# 转换为弧度
solar_zn_rad = np.deg2rad(solar_zn_deg)
solar_az_rad = np.deg2rad(solar_az_deg)
# 坡度和坡向也转换为弧度
slope_rad = np.deg2rad(slope_data)
aspect_rad = np.deg2rad(aspect_data)
# ========== 6. 计算 cosine_i ==========
print("\n计算入射角余弦 (cosine_i)...")
cosine_i = calc_cosine_i(solar_zn_rad, solar_az_rad, aspect_rad, slope_rad)
# ========== 7. 处理无效值NaN ==========
slope_data = np.nan_to_num(slope_data, nan=0.0)
aspect_data = np.nan_to_num(aspect_data, nan=0.0)
cosine_i = np.nan_to_num(cosine_i, nan=0.0)
# ========== 8. 堆叠为多波段数组 ==========
stacked = np.stack([slope_data, aspect_data, cosine_i], axis=0)
# ========== 9. 准备元数据 ==========
metadata = {
'description': 'Slope, Aspect, and Cosine_i derived from DEM',
'band_names': 'slope(deg),aspect(deg),cosine_i',
'solar_zenith_angle': str(solar_zn_deg),
'solar_azimuth_angle': str(solar_az_deg),
'solar_elevation_angle': str(90 - solar_zn_deg),
'observation_time_utc': observation_time.strftime('%Y-%m-%d %H:%M:%S'),
'dem_center_lat': str(latitude),
'dem_center_lon': str(longitude),
}
# ========== 10. 输出 ENVI 格式文件 ==========
print(f"\n保存结果到: {output_path}")
write_envi_output(output_path, stacked, transform, crs, metadata)
print("\n处理完成!")
print(f" 输出文件: {output_path}")
print(f" 波段顺序: 1-坡度(°), 2-坡向(°), 3-cosine_i")
return {
'output_path': output_path,
'solar_zenith': solar_zn_deg,
'solar_azimuth': solar_az_deg,
'center_lat': latitude,
'center_lon': longitude,
'observation_time': observation_time,
}
def main():
"""主函数 - 命令行入口"""
parser = argparse.ArgumentParser(
description='DEM地形处理工具 - 计算坡度、坡向和太阳入射角余弦',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
使用示例:
# 基本用法(交互式输入时间)
python slope_aspectV2.py -i dem.tif -o output.dat
# 通过命令行指定时间
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:30:00"
# 仅指定日期默认使用正午12:00
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15"
# 显示详细处理信息
python slope_aspectV2.py -i dem.tif -o output.dat -t "2024-06-15 10:30:00" -v
支持的日期时间格式:
- "YYYY-MM-DD HH:MM:SS" (推荐)
- "YYYY-MM-DDTHH:MM:SS" (ISO格式)
- "YYYYMMDD_HHMMSS"
- "YYYY-MM-DD" (默认使用12:00:00)
'''
)
parser.add_argument('-i', '--input', required=True,
help='输入DEM文件路径 (支持GeoTIFF等格式)')
parser.add_argument('-o', '--output', required=True,
help='输出ENVI文件路径 (.dat)')
parser.add_argument('-t', '--time',
help='观测日期时间 (UTC),格式: "YYYY-MM-DD HH:MM:SS"'
'如果不提供,将进入交互式输入模式')
parser.add_argument('--interactive', action='store_true',
help='强制使用交互式时间输入模式')
parser.add_argument('-v', '--verbose', action='store_true',
help='显示详细处理信息')
parser.add_argument('--version', action='version', version='%(prog)s 2.0.0')
args = parser.parse_args()
# 验证输入文件存在
if not Path(args.input).exists():
print(f"错误: 输入文件不存在: {args.input}", file=sys.stderr)
sys.exit(1)
# 创建输出目录(如果不存在)
output_dir = Path(args.output).parent
if not output_dir.exists():
output_dir.mkdir(parents=True, exist_ok=True)
print(f"创建输出目录: {output_dir}")
try:
# 执行处理
result = process_dem(
dem_path=args.input,
output_path=args.output,
datetime_input=args.time,
interactive=args.interactive
)
if args.verbose:
print("\n详细结果:")
for key, value in result.items():
print(f" {key}: {value}")
sys.exit(0)
except Exception as e:
print(f"\n处理失败: {e}", file=sys.stderr)
if args.verbose:
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main()

BIN
说明文档.docx Normal file

Binary file not shown.