372 lines
13 KiB
Python
372 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
高光谱分析工具包统一入口
|
|
提供命令行接口调用所有功能模块
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
import os
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
import traceback
|
|
|
|
from registry import (
|
|
REGISTRY, create_module_parser, execute_task,
|
|
get_module_info, get_call_sequence
|
|
)
|
|
from validators import validate_task_inputs
|
|
from output_handler import create_unified_output
|
|
|
|
|
|
def create_parser() -> argparse.ArgumentParser:
|
|
"""创建主参数解析器"""
|
|
parser = argparse.ArgumentParser(
|
|
prog='hyperspectral-toolkit',
|
|
description='高光谱图像分析工具包 - 统一入口',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
使用示例:
|
|
# 降维分析
|
|
python main.py dim-reduction --input data.hdr --method pca --n-components 5
|
|
|
|
# 图像分割
|
|
python main.py segmentation --input data.hdr --method otsu --band-index 100
|
|
|
|
# 分类分析
|
|
python main.py classification --input data.hdr --roi-file roi.xml --method svm
|
|
|
|
# 光谱指数计算
|
|
python main.py spectral-index --input data.hdr --batch --png
|
|
|
|
# 色差计算
|
|
python main.py delta-e --mode image --input image.lab --standards colors.csv --method CIEDE2000
|
|
|
|
# 光谱到颜色转换
|
|
python main.py spectral-to-color --input spectrum.csv --color-space Lab --illuminant D65
|
|
|
|
# XYZ到RGB转换
|
|
python main.py xyz-to-rgb --input xyz_image.dat --rgb-space sRGB --gamma-method sRGB
|
|
|
|
# 查看特定任务帮助
|
|
python main.py <task_name> --help
|
|
"""
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--version', '-v', action='version',
|
|
version='高光谱分析工具包 v1.0.0'
|
|
)
|
|
|
|
# 创建子解析器
|
|
subparsers = parser.add_subparsers(
|
|
dest='task',
|
|
help='选择要执行的任务',
|
|
metavar='TASK'
|
|
)
|
|
|
|
# 为每个任务创建子解析器
|
|
for task_name in REGISTRY.keys():
|
|
create_module_parser(subparsers, task_name)
|
|
|
|
return parser
|
|
|
|
|
|
def validate_input_file(file_path: str, task_name: str) -> bool:
|
|
"""验证输入文件"""
|
|
if not os.path.exists(file_path):
|
|
print(f"错误: 输入文件不存在: {file_path}")
|
|
return False
|
|
|
|
# 检查文件扩展名
|
|
valid_extensions = ['.hdr', '.dat', '.csv', '.xml', '.bil', '.bsq', '.bip']
|
|
file_ext = Path(file_path).suffix.lower()
|
|
|
|
if file_ext not in valid_extensions:
|
|
print(f"警告: 文件扩展名 {file_ext} 可能不受支持")
|
|
print(f"支持的格式: {', '.join(valid_extensions)}")
|
|
|
|
return True
|
|
|
|
|
|
def validate_roi_file(roi_path: str) -> bool:
|
|
"""验证ROI文件"""
|
|
if not os.path.exists(roi_path):
|
|
print(f"错误: ROI文件不存在: {roi_path}")
|
|
return False
|
|
|
|
if not roi_path.endswith('.xml'):
|
|
print(f"错误: ROI文件必须是XML格式: {roi_path}")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def validate_output_dir(output_dir: str) -> bool:
|
|
"""验证输出目录"""
|
|
try:
|
|
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
return True
|
|
except Exception as e:
|
|
print(f"错误: 无法创建输出目录 {output_dir}: {e}")
|
|
return False
|
|
|
|
|
|
def validate_args(args) -> bool:
|
|
"""验证命令行参数"""
|
|
# 获取任务信息
|
|
task_info = REGISTRY.get(args.task)
|
|
if not task_info:
|
|
print(f"错误: 未知任务 '{args.task}'")
|
|
return False
|
|
|
|
info = task_info["info"]
|
|
|
|
# 验证输入文件
|
|
if info.requires_data_file:
|
|
# 根据任务类型确定输入文件参数名
|
|
input_param = None
|
|
if args.task == 'delta-e':
|
|
input_param = getattr(args, 'input', None)
|
|
elif args.task == 'spectral-to-color':
|
|
input_param = getattr(args, 'input', None)
|
|
elif args.task == 'xyz-to-rgb':
|
|
input_param = getattr(args, 'input', None)
|
|
else:
|
|
input_param = getattr(args, 'input', None)
|
|
|
|
if not input_param:
|
|
print(f"错误: 任务 '{args.task}' 需要指定输入文件")
|
|
return False
|
|
|
|
if not validate_input_file(input_param, args.task):
|
|
return False
|
|
|
|
# 验证ROI文件
|
|
if info.requires_roi_file:
|
|
roi_attr = getattr(args, 'roi_file', None)
|
|
if not roi_attr:
|
|
print(f"错误: 任务 '{args.task}' 需要指定ROI文件 (--roi-file)")
|
|
return False
|
|
if not validate_roi_file(roi_attr):
|
|
return False
|
|
|
|
# 验证输出目录或文件
|
|
if args.task in ['delta-e', 'spectral-to-color', 'xyz-to-rgb', 'prosail-gui']:
|
|
# 颜色分析任务和GUI应用使用自己的输出参数验证
|
|
if args.task == 'prosail-gui':
|
|
# PROSAIL GUI 不需要输出目录验证
|
|
pass
|
|
elif args.task == 'delta-e':
|
|
output_dir = getattr(args, 'output_dir', './results')
|
|
if not validate_output_dir(output_dir):
|
|
return False
|
|
elif args.task == 'spectral-to-color':
|
|
# spectral-to-color 使用 -o 参数作为输出路径,需要提取目录部分
|
|
output_param = getattr(args, 'output', None)
|
|
if output_param:
|
|
output_dir = os.path.dirname(output_param) or '.'
|
|
else:
|
|
output_dir = './results'
|
|
if not validate_output_dir(output_dir):
|
|
return False
|
|
elif args.task == 'xyz-to-rgb':
|
|
# xyz-to-rgb 使用 --output 参数作为输出路径,需要提取目录部分
|
|
output_param = getattr(args, 'output', None)
|
|
if output_param:
|
|
output_dir = os.path.dirname(output_param) or '.'
|
|
else:
|
|
output_dir = './results'
|
|
if not validate_output_dir(output_dir):
|
|
return False
|
|
else:
|
|
# 标准任务的输出验证
|
|
if hasattr(args, 'output_file') and args.output_file:
|
|
# 如果指定了输出文件,验证其目录
|
|
output_dir = os.path.dirname(args.output_file) or '.'
|
|
if not validate_output_dir(output_dir):
|
|
return False
|
|
else:
|
|
# 否则验证输出目录
|
|
if not validate_output_dir(args.output_dir):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def print_task_summary(task_name: str, args, success: bool = True):
|
|
"""打印任务执行摘要"""
|
|
status = "✓ 成功" if success else "✗ 失败"
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"任务执行摘要")
|
|
print(f"{'='*60}")
|
|
print(f"任务名称: {task_name}")
|
|
print(f"执行状态: {status}")
|
|
|
|
# 根据任务类型显示不同的参数信息
|
|
if task_name == 'prosail-gui':
|
|
print("PROSAIL植被光谱模拟器GUI")
|
|
print(f"GUI模式: {'否' if getattr(args, 'no_gui', False) else '是'}")
|
|
elif task_name == 'delta-e':
|
|
print(f"模式: {args.mode}")
|
|
print(f"输入文件: {getattr(args, 'input', 'N/A')}")
|
|
print(f"标准色文件: {getattr(args, 'standards', 'N/A')}")
|
|
print(f"色差方法: {getattr(args, 'method', 'N/A')}")
|
|
print(f"输出目录: {getattr(args, 'output_dir', './results')}")
|
|
elif task_name == 'spectral-to-color':
|
|
print(f"输入文件: {getattr(args, 'input_file', 'N/A')}")
|
|
print(f"颜色空间: {getattr(args, 'color_space', 'N/A')}")
|
|
print(f"光源: {getattr(args, 'illuminant', 'N/A')}")
|
|
print(f"观察者: {getattr(args, 'observer', 'N/A')}")
|
|
print(f"输出文件: {getattr(args, 'output', 'N/A')}")
|
|
elif task_name == 'xyz-to-rgb':
|
|
print(f"输入文件: {getattr(args, 'input', 'N/A')}")
|
|
print(f"RGB空间: {getattr(args, 'rgb_space', 'N/A')}")
|
|
print(f"Gamma方法: {getattr(args, 'gamma', 'N/A')}")
|
|
print(f"输出文件: {getattr(args, 'output', 'N/A')}")
|
|
elif task_name == 'regression-prediction':
|
|
print("使用训练好的回归模型进行高光谱图像预测")
|
|
print(f"输入图像: {getattr(args, 'input', 'N/A')}")
|
|
print(f"模型路径: {getattr(args, 'model_path', 'N/A')}")
|
|
if hasattr(args, 'mask_path') and getattr(args, 'mask_path', None):
|
|
print(f"遮罩文件: {args.mask_path}")
|
|
print(f"输出目录: {getattr(args, 'output_dir', './results')}")
|
|
print(f"使用遮罩: {'是' if getattr(args, 'use_mask', True) else '否'}")
|
|
elif task_name == 'glcm':
|
|
print("GLCM纹理特征提取")
|
|
print(f"输入图像: {getattr(args, 'input', 'N/A')}")
|
|
print(f"波段索引: {getattr(args, 'band_index', 25)}")
|
|
print(f"灰度级数: {getattr(args, 'nbit', 64)}")
|
|
print(f"滑动窗口: {getattr(args, 'slide_window', 7)}")
|
|
print(f"步长距离: {getattr(args, 'step', [2])}")
|
|
print(f"角度: {[f'{a:.2f}rad' for a in getattr(args, 'angle', [0])]}")
|
|
print(f"输出目录: {getattr(args, 'output_dir', './results')}")
|
|
print(f"保存DAT: {'是' if getattr(args, 'save_dat', True) else '否'}")
|
|
else:
|
|
# 标准任务的参数显示
|
|
print(f"输入文件: {getattr(args, 'input', 'N/A')}")
|
|
if hasattr(args, 'roi_file') and args.roi_file:
|
|
print(f"ROI文件: {args.roi_file}")
|
|
print(f"输出目录: {getattr(args, 'output_dir', './results')}")
|
|
print(f"输出前缀: {getattr(args, 'output_prefix', 'result')}")
|
|
|
|
print(f"{'='*60}\n")
|
|
|
|
|
|
def list_available_tasks():
|
|
"""列出所有可用任务"""
|
|
print("可用任务列表:")
|
|
print("-" * 60)
|
|
|
|
categories = {}
|
|
for task_name, task_info in REGISTRY.items():
|
|
category = task_info["info"].category
|
|
if category not in categories:
|
|
categories[category] = []
|
|
categories[category].append((task_name, task_info["info"].description))
|
|
|
|
for category, tasks in categories.items():
|
|
print(f"\n{category}:")
|
|
for task_name, description in tasks:
|
|
print(f" {task_name:20} - {description}")
|
|
|
|
print(f"\n{'='*60}")
|
|
print("使用方法: python main.py <task_name> --help 查看具体任务参数")
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
parser = create_parser()
|
|
args = parser.parse_args()
|
|
|
|
# 如果没有指定任务,显示帮助信息
|
|
if not args.task:
|
|
parser.print_help()
|
|
print("\n" + "="*60)
|
|
list_available_tasks()
|
|
return 0
|
|
|
|
# 如果是特殊任务,处理后退出
|
|
if args.task == 'list':
|
|
list_available_tasks()
|
|
return 0
|
|
|
|
try:
|
|
# 验证参数
|
|
if not validate_args(args):
|
|
return 1
|
|
|
|
# 高级验证
|
|
validation_result = validate_task_inputs(args.task, **vars(args))
|
|
if not validation_result['valid']:
|
|
print("验证失败:")
|
|
for error in validation_result['errors']:
|
|
print(f" ✗ {error}")
|
|
return 1
|
|
|
|
if validation_result['warnings']:
|
|
print("验证警告:")
|
|
for warning in validation_result['warnings']:
|
|
print(f" ⚠ {warning}")
|
|
|
|
print(f"开始执行任务: {args.task}")
|
|
if args.task != 'prosail-gui':
|
|
print(f"输入文件: {getattr(args, 'input', 'N/A')}")
|
|
print(f"输出目录: {getattr(args, 'output_dir', './results')}")
|
|
else:
|
|
print("PROSAIL植被光谱模拟器GUI")
|
|
print("-" * 40)
|
|
|
|
# 记录开始时间
|
|
start_time = time.time()
|
|
|
|
# 执行任务
|
|
result = execute_task(args.task, args)
|
|
|
|
# 计算执行时间
|
|
execution_time = time.time() - start_time
|
|
|
|
# 创建统一输出
|
|
if args.task != 'prosail-gui':
|
|
output_files = create_unified_output(
|
|
args.task, args, result, getattr(args, 'output_dir', './results'),
|
|
execution_time, success=True
|
|
)
|
|
else:
|
|
output_files = None
|
|
|
|
# 显示输出文件信息
|
|
if output_files:
|
|
print("\n生成的文件:")
|
|
for file_type, file_path in output_files.items():
|
|
print(f" ✓ {file_type}: {file_path}")
|
|
|
|
return 0
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\n用户中断执行")
|
|
return 1
|
|
except Exception as e:
|
|
print(f"\n执行失败: {e}")
|
|
if '--debug' in sys.argv:
|
|
traceback.print_exc()
|
|
|
|
# 即使失败也尝试创建输出摘要
|
|
try:
|
|
execution_time = time.time() - start_time if 'start_time' in locals() else None
|
|
if args.task != 'prosail-gui':
|
|
create_unified_output(
|
|
args.task, args, None, getattr(args, 'output_dir', './results'),
|
|
execution_time, success=False
|
|
)
|
|
except:
|
|
pass # 如果输出失败,忽略
|
|
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|