Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/mintpy/cli/load_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@
# Author: Antonio Valentino, Zhang Yunjun, Aug 2022 #
############################################################

# os 用来处理文件路径,例如取当前脚本文件名、判断模板文件名。
import os
# sys 用来读取命令行参数和退出程序;sys.exit(0) 表示正常退出,sys.exit(1) 表示出错退出。
import sys

# auto_path 保存不同处理软件(ISCE/GAMMA 等)的默认输入路径规则。
from mintpy.defaults import auto_path
# get_template_content() 会读取 MintPy 内置的模板说明文本,显示在命令行帮助中。
from mintpy.defaults.template import get_template_content
# create_argument_parser() 是 MintPy 封装的命令行解析器创建函数。
from mintpy.utils.arg_utils import create_argument_parser

#################################################################
# DEFAULT_TEMPLATE 是 `load_data.py -H` 时显示的示例模板。
# """...""" 是多行字符串;format(...) 把不同处理器的自动路径说明拼进去。
DEFAULT_TEMPLATE = """template:
########## 1. Load Data (--load to exit after this step)
{}\n
Expand All @@ -23,14 +30,17 @@
auto_path.AUTO_PATH_ISCE_TOPS,
)

# TEMPLATE 是 load_data 对应的完整模板配置说明。
TEMPLATE = get_template_content('load_data')

# NOTE 是补充说明,提醒用户哪些数据集是必需的、文件名里需要包含什么日期信息。
NOTE = """NOTE:
For interferogram, unwrapPhase is required, the other dataset are optional, including coherence, connectComponent, wrapPhase, etc.
The unwrapPhase metadata file requires DATE12 attribute in YYMMDD-YYMMDD format.
All path of data file must contain the reference and secondary date, either in file name or folder name.
"""

# EXAMPLE 是命令行示例,会显示在 `load_data.py -h` 的帮助信息里。
EXAMPLE = """example:
# MUST run in the mintpy working directory!

Expand All @@ -54,25 +64,31 @@

def create_parser(subparsers=None):
"""Create command line parser."""
# 创建命令行解析器:把终端输入的 `load_data.py -t xxx.cfg` 转换成 Python 变量。
synopsis = 'Load stacks of interferograms to HDF5 files'
epilog = TEMPLATE + '\n' + NOTE + '\n' + EXAMPLE
name = __name__.split('.')[-1]
parser = create_argument_parser(
name, synopsis=synopsis, description=synopsis, epilog=epilog, subparsers=subparsers)

# extra help
# action='store_true' 表示用户输入 -H 时,inps.print_example_template 变成 True。
parser.add_argument('-H', dest='print_example_template', action='store_true',
help='Print/Show the example template file for loading.')

# input files
# nargs='+' 表示 -t 后面可以跟一个或多个模板文件。
parser.add_argument('-t', '--template', dest='template_file', type=str, nargs='+',
help='template file(s) with path info.')
# --geom 只加载几何文件,不加载干涉图栈;常用于先检查几何输入是否正确。
parser.add_argument('--geom','--geometry', dest='only_load_geometry', action='store_true',
help='Load the geometry file(s) ONLY.')

# options from template file name & content
# PROJECT_NAME 会写入输出 HDF5 元数据,便于后续产品识别。
parser.add_argument('--project', type=str, dest='PROJECT_NAME',
help='project name of dataset for INSARMAPS Web Viewer')
# --enforce 会把 updateMode 设为 False,意思是强制重新加载,不用已有文件跳过。
parser.add_argument('--enforce', '-f', dest='updateMode', action='store_false',
help='Disable the update mode, or skip checking dataset already loaded.')

Expand All @@ -83,41 +99,49 @@ def cmd_line_parse(iargs=None):
"""Command line parser."""
# parse
parser = create_parser()
# parse_args() 解析参数;iargs 为 None 时读取真实命令行,传列表时方便其它代码或测试调用。
inps = parser.parse_args(args=iargs)

# check: -H option
if inps.print_example_template:
# 只打印示例模板并退出,不执行真正的数据加载。
print(DEFAULT_TEMPLATE)
sys.exit(0)

# check: -t/--template option
# -t option is required AND
# smallbaselineApp.cfg file is required
if not inps.template_file:
# 如果没有 -t/--template,就打印用法并退出。
parser.print_usage()
script_name = os.path.basename(__file__)
print(f'{script_name}: error: -t/--template option is required.')
print(f'run {script_name} -H to show the example template file.')
sys.exit(1)

elif all(not x.endswith('smallbaselineApp.cfg') for x in inps.template_file):
# load_data 至少需要默认模板 smallbaselineApp.cfg,因为很多默认路径和选项都来自它。
script_name = os.path.basename(__file__)
print(f'{script_name}: error: at least smallbaselineApp.cfg file is required for -t/--template option.')
sys.exit(1)

# 返回解析后的参数对象,后面的 main() 会把它交给核心 load_data() 函数。
return inps



#################################################################
def main(iargs=None):
# parse
# main() 是 CLI 入口函数:先解析命令行参数。
inps = cmd_line_parse(iargs)

# import
# 延迟导入核心函数,只有真正要运行时才加载 src/mintpy/load_data.py。
from mintpy.load_data import load_data

# run
# 调用核心模块的 load_data(inps),把外部数据读入 MintPy 标准 HDF5 文件。
load_data(inps)


Expand Down
73 changes: 73 additions & 0 deletions src/mintpy/cli/smallbaselineApp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@
############################################################


# datetime 是 Python 标准库,专门用来处理日期和时间;这里用它打印程序开始运行的时间。
import datetime
# os 是 Python 标准库,专门用来和操作系统交互,比如判断文件是否存在、拼接路径、获取当前目录。
import os
# sys 是 Python 标准库,专门用来访问 Python 解释器相关信息;这里用 sys.argv 读取命令行参数,用 sys.exit 退出程序。
import sys

# mintpy 是本项目的主包;这里用它读取软件版本、默认配置文件所在位置等信息。
import mintpy
# STEP_LIST 是 MintPy 默认模板中定义好的处理步骤顺序,例如 load_data、modify_network、velocity 等。
from mintpy.defaults.template import STEP_LIST
# create_argument_parser 是 MintPy 自己封装的 argparse 工具,用来创建风格统一的命令行参数解析器。
from mintpy.utils.arg_utils import create_argument_parser

##########################################################################
# 命令行帮助文本:说明可通过 --start/--end/--dostep 选择运行的处理步骤。
# """...""" 是 Python 的多行字符串,适合保存较长的帮助说明。
# .format(...) 会把 STEP_LIST 分成几段填入上面的 {},这样 help 页面里能显示所有步骤名称。
STEP_HELP = """Command line options for steps processing with names chosen from the following list:

{}
Expand All @@ -33,6 +42,7 @@
doi:10.1016/j.cageo.2019.104331.
"""

# EXAMPLE 保存命令行使用示例,会显示在 -h/--help 帮助信息的末尾。
EXAMPLE = """example:
smallbaselineApp.py # run with default template 'smallbaselineApp.cfg'
smallbaselineApp.py <custom_template> # run with default and custom templates
Expand All @@ -49,18 +59,29 @@


def create_parser(subparsers=None):
# 创建 smallbaselineApp.py 的命令行参数解析器。
# “解析器”可以理解为:把用户在终端输入的文字参数,转换成 Python 里可访问的变量。
synopsis = 'Routine Time Series Analysis for Small Baseline InSAR Stack'
# epilog 是帮助信息最后显示的内容,这里把参考文献和示例命令拼在一起。
epilog = REFERENCE + '\n' + EXAMPLE
# __name__ 是 Python 自动提供的模块名;split('.')[-1] 取最后一段,作为命令名称。
name = __name__.split('.')[-1]
# 调用 MintPy 的 create_argument_parser() 方法,得到一个 parser 对象。
# 后面多次调用 parser.add_argument(),就是不断告诉它“这个命令支持哪些参数”。
parser = create_argument_parser(
name, synopsis=synopsis, description=synopsis, epilog=epilog, subparsers=subparsers)

# 位置参数:用户自定义模板文件;可选参数:工作目录。
# nargs='?' 表示这个位置参数可以不填;如果用户不填,就使用当前目录里的 smallbaselineApp.cfg。
parser.add_argument('customTemplateFile', nargs='?',
help='custom template with option settings.\n' +
"ignored if the default smallbaselineApp.cfg is input.")
# --dir/--work-dir 是同一个参数的两个名字;dest='workDir' 表示解析后保存到 inps.workDir。
parser.add_argument('--dir', '--work-dir', dest='workDir', default='./',
help='work directory, (default: %(default)s).')

# 辅助选项:生成模板、打印模板、查看版本或仅绘图。
# action='store_true' 表示用户写了这个选项时,对应变量就是 True;没写时就是 False。
parser.add_argument('-g', dest='generate_template', action='store_true',
help='generate default template (if it does not exist) and exit.')
parser.add_argument('-H', dest='print_template', action='store_true',
Expand All @@ -70,110 +91,152 @@ def create_parser(subparsers=None):
parser.add_argument('--plot', dest='plot', action='store_true',
help='plot results [only] without running smallbaselineApp.')

# 步骤控制选项:指定起止步骤,或只运行某一个步骤。
# add_argument_group() 会在 help 信息中单独分组显示这几个步骤相关参数。
step = parser.add_argument_group('steps processing (start/end/dostep)', STEP_HELP)
# default=STEP_LIST[0] 表示默认从第一个步骤开始运行。
step.add_argument('--start', dest='startStep', metavar='STEP', default=STEP_LIST[0],
help='start processing at the named step (default: %(default)s).')
# default=STEP_LIST[-1] 表示默认运行到最后一个步骤;[-1] 是 Python 中“最后一个元素”的写法。
step.add_argument('--end','--stop', dest='endStep', metavar='STEP', default=STEP_LIST[-1],
help='end processing at the named step (default: %(default)s)')
# --dostep 用来只运行一个步骤,后面会把 startStep 和 endStep 都改成这个步骤。
step.add_argument('--dostep', dest='doStep', metavar='STEP',
help='run processing at the named step only')

# 返回 parser,供 cmd_line_parse() 使用。
return parser


def cmd_line_parse(iargs=None):
"""Command line parser."""
# 解析命令行输入,返回后续工作流需要的参数对象。
# parse
parser = create_parser()
# parser.parse_args() 会真正解析命令行参数。
# 如果 iargs 是 None,表示从真实终端命令读取;如果传入列表,常用于测试或其它 Python 代码调用。
inps = parser.parse_args(args=iargs)

# 保存原始参数列表,用于判断用户是否只指定了特殊选项。
# save argv (to check the manually specified arguments)
# use iargs for python call
# use sys.argv[1:] for command line call
# sys.argv 是完整命令行参数列表,第 0 个通常是脚本名,所以用 [1:] 去掉脚本名。
inps.argv = iargs or sys.argv[1:]

# check
# os.path.dirname(mintpy.__file__) 得到 mintpy 包所在目录。
# os.path.join(...) 用正确的路径分隔符拼接路径,避免手写 '/' 在不同系统上出错。
template_file = os.path.join(os.path.dirname(mintpy.__file__), 'defaults/smallbaselineApp.cfg')

# -H 只打印默认模板内容并退出,不进入处理流程。
# check: -H option (print default template)
if inps.print_template:
# with open(...) as f 是安全打开文件的写法;代码块结束后文件会自动关闭。
with open(template_file) as f:
lines = f.read()
try:
# 如果安装了 rich,则用语法高亮显示 cfg 文件。
# syntax highlight via rich
from rich.console import Console
from rich.syntax import Syntax
# Console 是 rich 的输出对象;Syntax 负责按 cfg 语法给文本上色。
console = Console()
console.print(Syntax(lines, "cfg", background_color='default'))
except ImportError:
# 如果没有安装 rich,就退回普通 print,保证功能不依赖额外包。
print(lines)
# sys.exit(0) 表示正常退出程序,不再继续执行后面的处理流程。
sys.exit(0)

# -v 只打印 MintPy 版本信息并退出。
# check: -v option (print software version)
if inps.version:
# mintpy.version.version_description 是 MintPy 的版本说明字符串。
print(mintpy.version.version_description)
sys.exit(0)

# check: existence of input template files
if (not inps.customTemplateFile
and not os.path.isfile(os.path.basename(template_file))
and not inps.generate_template):
# 没有用户模板、当前目录也没有默认模板,并且不是生成模板模式时,无法继续运行。
# os.path.basename(template_file) 只取文件名 smallbaselineApp.cfg,不带前面的目录。
# os.path.isfile(...) 用来判断这个文件在当前目录下是否真实存在。
parser.print_usage()
print(EXAMPLE)

msg = "no template file found! It requires:"
msg += "\n 1) input a custom template file, OR"
msg += "\n 2) there is a default template 'smallbaselineApp.cfg' in current directory."
# raise SystemExit 会终止程序,并把错误信息显示给用户。
raise SystemExit(f'ERROR: {msg}')

# check: custom input template file
if inps.customTemplateFile:
# 自定义模板必须存在,并转换为绝对路径,避免后续切换工作目录后路径失效。
# check the existence
if not os.path.isfile(inps.customTemplateFile):
# FileNotFoundError 是 Python 内置异常,用来表示文件不存在。
raise FileNotFoundError(inps.customTemplateFile)

# os.path.abspath(...) 把相对路径转换成绝对路径,例如 a.txt 变成 /home/.../a.txt。
inps.customTemplateFile = os.path.abspath(inps.customTemplateFile)

# 如果用户传入的其实是默认模板,则按无自定义模板处理。
# ignore if it is the default template file (smallbaselineApp.cfg)
# os.path.basename(...) 只比较文件名,不比较目录。
if os.path.basename(inps.customTemplateFile) == os.path.basename(template_file):
inps.customTemplateFile = None

# 单独使用 --plot 时只绘图,不运行任何处理步骤。
# check: --plot option
if inps.argv == ['--plot']:
plot_only = True
print('plot smallbaselineApp results without run.')
else:
plot_only = False

# 根据 start/end/dostep 选项计算本次需要执行的步骤列表。
# check: --start/end/dostep options
# read_inps2run_steps() 是本文件下面定义的函数,负责把用户输入转换成真正要运行的步骤列表。
inps.runSteps = read_inps2run_steps(inps, step_list=STEP_LIST, plot_only=plot_only)

# 返回 inps;后面的 main() 会把它交给真正的处理工作流。
return inps


def read_inps2run_steps(inps, step_list, plot_only=False):
"""read/get run_steps from input arguments."""
# 将命令行指定的步骤范围转换成有序步骤列表。
# check: if start/end/do step input is valid
for key in ['startStep', 'endStep', 'doStep']:
# vars(inps) 会把 argparse 解析出来的对象转换成字典,便于用字符串 key 取值。
value = vars(inps)[key]
if value and value not in step_list:
# 用户输入的步骤名必须来自默认模板定义的 STEP_LIST。
msg = f'Input step not found: {value}'
msg += f'\nAvailable steps: {step_list}'
raise ValueError(msg)

# --dostep 优先级最高:忽略 --start/--end,只执行指定步骤。
# check: ignore --start/end input if --dostep is specified
if inps.doStep:
inps.startStep = inps.doStep
inps.endStep = inps.doStep

# get list of steps to run
# list.index(x) 返回 x 在列表中的位置编号,用它确定起始步骤和结束步骤的位置。
idx0 = step_list.index(inps.startStep)
idx1 = step_list.index(inps.endStep)
if idx0 > idx1:
# 起始步骤不能位于结束步骤之后。
msg = f'start step "{inps.startStep}" is CAN NOT be after the end step "{inps.endStep}"'
raise ValueError(msg)
# Python 切片 step_list[idx0:idx1+1] 表示从 idx0 到 idx1,包括 idx1。
run_steps = step_list[idx0:idx1+1]

# 生成模板或仅绘图时,不执行处理步骤。
# empty the step list if:
# a) -g OR
# b) iargs == ['--plot']
Expand All @@ -182,30 +245,40 @@ def read_inps2run_steps(inps, step_list, plot_only=False):

# print mssage - processing steps
if len(run_steps) > 0:
# 多步骤运行时打印 logo,单步骤运行时打印紧凑版本信息。
# for single step - compact version info
if len(run_steps) == 1:
print(mintpy.version.version_description)
else:
print(mintpy.version.logo)

# datetime.datetime.now() 返回当前日期时间;os.getcwd() 返回当前工作目录。
print(f'--RUN-at-{datetime.datetime.now()}--')
print(f'Current directory: {os.getcwd()}')
# os.path.basename(__file__) 只取当前脚本文件名,用于日志输出。
print(f'Run routine processing with {os.path.basename(__file__)} on steps: {run_steps}')
print(f'Remaining steps: {step_list[idx0+1:]}')
print('-'*50)

# 返回最终步骤列表,主工作流会逐个执行这些步骤。
return run_steps


##########################################################################
def main(iargs=None):
# CLI 入口:解析参数后调用主工作流实现。
# parse
inps = cmd_line_parse(iargs)

# 延迟导入主工作流,减少只看帮助/版本时的启动开销。
# import
# from ... import ... 表示从另一个 Python 文件/模块里拿到一个函数。
# 这里导入的是 src/mintpy/smallbaselineApp.py 里的 run_smallbaselineApp()。
from mintpy.smallbaselineApp import run_smallbaselineApp

# 将命令行参数对象交给 src/mintpy/smallbaselineApp.py 中的主流程执行。
# run
# 这里是 CLI 文件和主工作流文件的连接点:CLI 只负责解析参数,真正处理数据在 run_smallbaselineApp() 中。
run_smallbaselineApp(inps)


Expand Down
Loading