From 3bb2c623188a9af61c0312fc39dad0ba62fc3abc Mon Sep 17 00:00:00 2001 From: Meissam Mehdizadeh <53583197+mehdizadehm@users.noreply.github.com> Date: Sat, 14 Nov 2020 17:23:08 +0100 Subject: [PATCH] update master branch (#2) * More complex models for velocity estimation are added: - polynomial functions with customized polynomial degree - periodic function(s) with customized period(s) for seasonal fitting - step function(s) with customized date(s) for coseismic fitting These models are not callable using smallbaseline.py But they have been made available using timeseries2velocity.py with arguments Check timeseries2velocity.py -h for more information under "velocity models of interest" * generic STD estimation for time func params + stack.timeseries(): - rename get_design_matrix4average_velocity() to get_design_matrix4time_func() for more meaningful name, and inside sub-functions. - set model as dict type to support future time functions. - update usage of get_design_matrix4*() in objects.gps, simulation.simulation. + timeseris2velocity: - set model as dict type, consistent with objects.stack() - add standard error estimation for all time function parameters, based on error propagation law, as a generic extension to the linear velocity STD from equation (10) in Fattahi and Amelung (2015, JGR). This is currently support via non-bootstrap approach only. * add writefil.write_isce_xml/file() + add utils.writefile.write_isce_xml() to support xml/vrt file writing + add writefile.write_isce_file() to write binary data file and metadata files in isce format. + ts2vel: add Yuan-Kai to the author list * rename file for the num of triplets with non-zero int ambiguity from `numNonzeroIntClosure.h5` to `numTriNonzeroIntAmbiguity.h5`. * add docs/FAQs page * indexing readfile.read() call in view.py Support fancy indexing / slicing in utils.readfile.read() with x/ystep, to merge the reading and multilooking process into one. This significantly reduce the memory load of view.py for large 3D dataset, from 9 GB to less than 500 MB in the case of GalapagosSenDT128 dataset. * isce_utils.py: bug fix for spacecraftname in metadata (#430) * tropo_pyaps3: add --custom-height for testing purpose (#431) + view: auto update vlim after masking for multi-subplots for better display when the non-zero values are largely off from zero, which is the case of the absolute tropo delay in ERA5.h5 file. + tropo_pyaps3.py: add --custom-height for testing purpose + test_smallbaselineApp.py: take codacy suggestion + docs/README: one line for the badges + docs/dask.md: fix inproper display of dask performance figure on readthedocs * utils: add med_abs_dev() separate median_abs_deviation() from median_abs_deviation_threshold() to support 2D matrix calculation for MAD. * isce_utils: update calc for CENTER_LINE_UTC and HEADING + use sensingMid for CENTER_LINE_TUC calculation, instead of the previous startUTC. Difference is in seconds, with no expected impact on tropo_pyaps3.py with GAM resolution of hours. + calc HEADING from orbit instead of reading los file to be more generic, precise and simple + use meta instead of metadata to shorter code * save_hdfeos5.py and smallbaselineApp.py: add avgSpatialCoh.h5 to S1*.he5 file (#439) + smallbaselineApp: add avgSpatialCoh.h5 to the default geocode file list + save_hdfeos5: add `quality/avgSpatialCoh` - add `--asc / --avg-spatial-coh` for the average spatial coherence - rename `-t` to `--tc / --temp-coh` for the temporal coherence to avoid confusion with the newly added avgSpatialCoh.h5 + update docs/hdfeos5.md for the updated HDF-EOS5 data structure * bugfix for view.py --coastline option (#440) * bugfix for view.py --coastline option * bugfix in view.py --multilook-num (#441) * bugfix in view.py --multilook-num, which causes the initiation shape mismatch due to the recently introduced multilooking via indexing. * view: set coastline default to None instead of 'no' * Improve memory efficiency in the steps of calculating ramp, deramp, residual_RMS (#436) * To enhance the memory efficiency in calculating ramp, deramp, and time-series RMS, by reading/estimating(/writing) one date at a time. * Fixed a syntax error in readfile.py at line 799 Before: is not "none" Now: != "none" * writefile.py: add `print_msg=True` to `layout_hdf5()` and `write_hdf5_block()`. * isce_utils.py: fix typo burst to frame * timeseries: add precise UTC time info to `self.times` from CENTER_LINE_UTC * view: tie --coastline with --lalo-label If --coastline option is turned ON, enable --lalo-label as well, to show the lat/lon label; otherwise, it's blank by default by cartopy. * add writefile.layout_hdf5(ref_file) utils.writefile.layout_hdf5(): + support ref_file as an alternative of ds_name_dict and metadata, for easy use + support auxliary data writing, for less call of write_hdf5_block() + use snake_case to replace the camelCase for consistency Simplify the code using the new `writefile.layout_hdf5()` in the following functions: + utils.utils1.run_deramp() + ifgram_inversion.ifgram_inversion() + prep_fringe.prepare_timeseries() pyaps: force era5 correctio in the 1st pass * skip pixels with zero avgSpatialCoh/tempCoh to speedup + ifg_inv: - skip pixel with zero avgSpatialCoh, which is caused by data missing in the processing (usually if not all) - msg bugfix in calc_temp_coh() when the patch is very small - change default --cluster value to 'no', to be consistent with default template value. + unwrap_phase_phase_closure.calc_T_int(): mask out pixels with zero average spatial coherence, which is caused by missing data + dem_err: skip pixel with zero temporal coherence, due to water body, shadow, zero average spatial coherence, etc. + decorrelation: update msg + multilook: ignore runtime warnning msg * ifgramStack_coherence_spatialCoh.txt to coherenceSpatialAvg.txt + rename ifgramStack_coherence_spatialCoh.txt to coherenceSpatialAvg.txt for shorter filename + use camelCase for output figure name in plot_network.py + msg update for load_data.py when checking inconsistent file size * improved auto skip for inv_net and quick_overview + ifg_inv: run if TS is partly written only, due to the incomplete running of ifgram_inversion. + unwrap_error_phase_closure: add checking of modification time, and ref_y/x metadata for numTriNonzeroIntAmbiguity calculation * readfile.read_hdf5_file(): speedup x/ystep indexing + utils.readfile.read_hdf5_file(): speedup x/ystep indexing by using numpy indexing instead of h5py indexing. The latter has significant slow down for large index. For a time-series with 20*2400*5500, it's 1 sec vs 44 secs. + .gitignore: add *.dSYM* + view: - update ref_y/x for multilooking - support date1_date2 for TS file to be able to reconstruct interferogram from time-series on the fly * decor: use coh_step to replace epsilon decor: use coh_step to replace epsilon to avoid dividing a number close to zero * decor.cal_coh4phase_pdf_bias: numerically robust + use poly_deg of 10 for L < 10; and 40 for 10 <= L < 20, for more suitable redundancy + add poly_rcond for easy modification + limit output coh_cal to [0, 1] + attach coh_sim/est = 1 to the end of the bias data for a more robust calibration for values close to 1 * tropo_pyaps3: check date_list in existing tropo h5 file * new ASCII logo + version: add a new ASCII logo to replace the previous one + docs/README: add mintpy prounciation in International Phonetic Alphabet (IPA) style. + docs/resources/colormaps/README: update link to sci colormap png + delete the unused mintpy/.travis.yml + delete the impractical docs/examples/dev, which is meant for early stage develop only + .gitignore: add mintpy/objects/solid_f.*.so* * ifg_inv: bugfix for custom output filenames ifg_inv: bugfix for custom output filenames and update example usage for offset sbApp: print plot_netowrk cmd always * time function fitting: k! in the denominator of the polynomial function (#450) * time function fitting: k! in the denominator of the polynomial function * dem_err: rm duplicated design matrix func for time func + remove the duplicated design matrix func for time func and use the one from stack.timeseries objects instead, because it's more generic and more clear coding. + use G for the design matrix symbol instead of A, to be consistent with the convension in Yunjun et al. (2019) * bugfix on the prep_fringe.py for the updated isce_utils.extract_geometry_metadata() args (#455) There is a bug on line 140 of the prep_fringe.py causing the code to stop working during the extraction of the metadata on the reference xml files. * asc_desc2horz_vert: add --dset option add --dset option to be able to customize the dataset to read/use to asc/desc 2 horz/vert conversion, for input file with more than one dataset. * net.select_pairs_*(): use date_format instead of date12_format for simplicity * move resources from docs to mintpy + move resources folder from docs to mintpy as they are used in the code, to make mintpy folder independent from docs folder + rename docs/resources/colormaps/README.md to docs/api/colormaps.md and update its usage in mkdocs.yml + update the location of resources files in save_kmz_timeseries.py, objects/colors.py + remove unused test/unwrap_error_bridging in .gitignore * prep_aria: support subset / update mode / compression + writefile.layout_hdf(): add compression argument + smallbaselineApp: do not forcely pass --update to prep_aria.py + prep_aria: - add read_subset_box() to support loading part of ARIA products in 'load_data' step using `mintpy.subset.yx/lalo` options - add --compression option - more robust run_or_skip() considering the corrupted h5 files * prep_aria: bugfix when no template file input + bugfix when there is no template file input + update example usage * add pip to docs/conda.txt + add pip to conda.txt since it's used in the installation of pykml + remove ${CONDA_PREFIX} while calling conda since it's now removed in the custom environment from the latest version of conda distribution * prep_gamma: support *-* folder style while searching for *.par files to grab LAT/LONO_REF1/2/3/4 metadata * prep_aria: update metadata after subseting * view: break date1_date2 into date1\ndate2 for number subplots in (20, 50] for clearer display * ifg_inv: NaN as no-data value for offset/phase + ifgram_inversion; - use np.nan value as the no-data value for observations (phase or offset) to replace the previous version of using both (zero and nan), by: - setting masked out pixel values as np.nan instead of 0 and - setting zero phase (not offset) value to nan. - The above changes results in the following improvements: - simplified checking of masks and pixels with no-data values in some (not all) interferograms. - remove `skip_zero_value` from estimate_timeseries() due to the above change - skip pixels with zero avgSpatialSnr values for offset - more comments + objects/stack.ifgramStack.temporal_average(): - split the big 3D matrix in row direction instead of ifgram/time dimension. - the above change allows to use nanmean to replace manual mean calculation, to better handle the nan value, for improved avgSpatialSnr.h5 calculation. + adjust printout msg in writefile.layout_hdf5() and view.py * support for alosStack in ISCE (#464) Adds support for ALOS-2 (and ALOS-4 in the future) stack processed with contrib/stack/alosStack in ISCE-2. The data loading part is similar to the product from topsStack and stripmapStack, whose sub-type is identified automatically based on the input metadata file via mintpy.load.metaFile. Detailed changes are as follows: + utils/isce_utils.py: - modify get_processor() to detect alosStack based on f1_*/*.frame.xml file. - add the following new functions to extract metadata for alosStack: - extract_alosStack_metadata() - alos2_acquisition_mode() - extract_image_size_alosStack() - load_track() - read_alosStack_baseline() + prep_isce.py: - adjust prepare_stack() and prepare_geometry() for alosStack. - cmd_line_parse(): 1) use argparse required argument to replace the checking in cmd_line_parse(); 2) translate wildcard in input metaFile + add docs/examples/input_files/NCalAlos2Scan.txt as the example template file for loading alosStack products + objects/stackDict.geometry.write2hdf5(): convert waterBody to waterMask while loading data + utils/readfile.read_binary_file(): use band2 by default for *.cor file with 2 bands to support reading complex coherence files generated by isce-2. * dir structure of ISCE/alosStack (#465) * docs/dir_structure: directory structure of ISCE/alosStack added * docs/examples/input_files: Rename template file of ISCE/alosStack * ifg_inv for offset: consider A/RLOOKS for az/rg_pixel_size + ifgram_inversion: update az/rg_pixel_size to the resolution before multilooking for meaningful value + add utils.utils0.vtec2range_delay() to convert the zenith TEC to the predicted range delay in SAR, based on Chen and Zebker (2012) + add utils.isce_utils.get_IPF() for Sentinel-1 data + view: show lat/lon in the status bar for file in radar coord, if the geometryRadar.h5 file is in the default location. + tsview: make REF_Y/X optional to support off time-series + update HDF-EOS5 usage in save_hdfeos5.py and docs/hdfeos5.md + remove ECMWF from smallbaselineApp.cfg since it's not been updated anymore and is replaced by ERA-5; and update comments in tropo_pyaps(3).py * ifg_recon: bugfix + stack.ifgramStack.get_design_matrix4timeseries(): add refDate='no' to disable the column redunction due to temporal referencing. + ifgram_reconstruction.py: - fix the bug of design matrix due to the reference date - update example usage * fix bool array index bug for h5 dataset + readfile & stack: fix a bug of fancy indexing with numpy boolean array in h5py dataset (https://docs.h5py.org/en/stable/high/dataset.html#fancy-indexing), which is used in utils/readfile.py and objects/stack.py. This seems to be a bug in h5py side because it was working before. + prep_aria: fix a bug in gdal*.ReadAsArray() from numpy array int64 type to native python int16 type * bugfix for loading SNAP DEM with subset enabled + load_data: fix a bug of subsetting geometry file in geo-coord if input dataset is in geo-coord as well. + stackDict: acknowledge the -32768 no data for DEM * example dataset & template file for SNAP + add an example input dataset processed with SNAP provided by Andre Theron + add mintpy/data/input_files/WCapSenAT29.txt + add SNAP dataset to test_smallbaselineApp.py and test/configs/WCapSenAT29.txt + view: do not update disp_min/max if --wrap is enabled for multiple subplots, to be consistent with the one for single subplot. * add setup.py for pip install and pypi distribution + add pyproject.toml to use setuptools and wheel for build system + add MANIFEST.in + add setup.py with the following updates: - auto grabbed version, (long_)description - dependencies info - data files + move /sh to /mintpy/sh for easy registration with setup.py + move /docs/examples/input_files into /mintpy/data/input_files + move /mintpy/resources into /mintpy/data + version: add release_description to replace the previous description; and add new description. + bump python min version in doc/setup from 3.5 to 3.6 to be consistent with the docs/conda.txt * example dataset for ARIA on San Francisco + update mintpy/data/input_files/SanFranSenDT42.txt: enable unwrapping error correction with bridging and disable network modification via interferogram index. + docs/demo_dataset: add ARIA section with zenodo link using the San Francisco dataset prepared by Heresh Fattahi using ARIA-tools + view.prep_slice(): update example comments regarding the cartopy projection * aria support in load_data with autoPath + load_data: - move the call of prep_aria.main() from smallbaselineApp.py into load_data.py, so that prep_aria could appears to be the same as other prep_*.py in the user side, a.k.a. "load_data.py -t template file" works for ARIA as well. - support 'mintpy.load.autoPath' for aria - ignore failed prep_aria and continue load_data, becuase 1) the ARIA product could have been loaded into mintpy and deleted already and 2) the potential missing loaded HDF5 files will be reported laterward in smallbaselineApp.py, so it's okay to ignore it here. + utils1.update_template_file(): fix a bug when the option name contains the option value, i.e. mintpy.load.autoPath = auto. + add SanFranSenDT42.txt to the test_smallbaselinApp.py + mintpy/sh/plot_smallbaselineApp: plot ts_demErr.h5 * add setup.py for pip install and pypi distribution + add pyproject.toml to use setuptools and wheel for build system + add MANIFEST.in + add setup.py with the following updates: - auto grabbed version, (long_)description - dependencies info - data files + move /sh to /mintpy/sh for easy registration with setup.py + move /examples from /docs to /mintpy for easy management via pip/setup.py + version: add release_description to replace the previous description; and add new description. * more comments on extract_alosStack_metadata() + more comments on isce_utils.extract_alosStack_metadata() + update docs/api/attribute.md * run_isce_stack: support topsStack + add run_isce_stack.py to drive the isce-2 topsStack and stripmapStack processors. Compared with process_isce_stack_v1.py: - remove the job submission code since I can not test and use it - add topsStack - simplify the code - add dem prep based on dem(_gsi).py and auto opt values - replace cmd call of stackSentinel/StripMap.py with python call - add --text_cmd to sh file execution to avoid running load_tops/stripmap_stack + add fill_and_translate_template_auto_value() to replace mintpy.utils.check_template_auto_value() - support input template option value of auto, which will be translated into default value + add isce.numThread option for topsStack.topo.py + delete obsolete process_isce_stack*.py Co-authored-by: Yuankailiu Co-authored-by: Zhang Yunjun Co-authored-by: Sara Mirzaee <37273875+mirzaees@users.noreply.github.com> Co-authored-by: Zhang Yunjun Co-authored-by: ranneylxr <35222932+ranneylxr@users.noreply.github.com> Co-authored-by: Yuan-Kai Liu <55262505+liuykai@users.noreply.github.com> Co-authored-by: Sara Mirzaee Co-authored-by: Bryan Marfito Co-authored-by: CunrenLiang <56097947+CunrenLiang@users.noreply.github.com> --- .gitignore | 7 +- MANIFEST.in | 6 + docs/FAQs.md | 13 + docs/README.md | 12 +- docs/api/attributes.md | 21 +- .../colormaps/README.md => api/colormaps.md} | 4 +- docs/api/module_hierarchy.md | 2 +- docs/conda.txt | 1 + docs/dask.md | 2 +- docs/demo_dataset.md | 40 +- docs/dir_structure.md | 295 ++++++++- docs/examples/dev/read_snap_img.ipynb | 127 ---- docs/hdfeos5.md | 6 +- docs/installation.md | 12 +- mintpy/.travis.yml | 8 - mintpy/asc_desc2horz_vert.py | 31 +- .../data}/colormaps/BlueWhiteOrangeRed.cpt | 0 .../data}/colormaps/DEM_print.cpt | 0 .../data}/colormaps/GMT_haxby.cpt | 0 .../data}/colormaps/GMT_no_green.cpt | 0 .../data}/colormaps/batlow.cpt | 0 .../data}/colormaps/hawaii.cpt | 0 .../data}/colormaps/oleron.cpt | 0 .../data}/colormaps/roma.cpt | 0 .../data}/colormaps/seminf-haxby.cpt | 0 .../data}/colormaps/temp-c.cpt | 0 .../data}/colormaps/temperature.cpt | 0 .../data}/colormaps/vik.cpt | 0 .../data}/colormaps/vikO.cpt | 0 .../data}/colormaps/wiki-2.0.cpt | 0 .../data}/colormaps/wiki-schwarzwald-d050.cpt | 0 .../data}/colormaps/wiki-scotland.cpt | 0 .../data}/dygraph-combined.js | 0 .../data}/input_files/FernandinaSenDT128.txt | 0 .../input_files/GalapagosAlosAT133.template | 0 .../data}/input_files/GalapagosEnvA2T061.txt | 0 .../input_files/GalapagosSenDT128.template | 0 .../KirishimaAlos2DT23F2970.template | 0 .../data}/input_files/KujuAlosAT422F650.txt | 0 .../input_files/NCalAlos2ScanSARDT169.txt | 26 + .../data}/input_files/README.md | 6 + mintpy/data/input_files/SanFranSenDT42.txt | 21 + mintpy/data/input_files/WCapeSenAT29.txt | 15 + .../data}/input_files/WellsEnvD2T399.txt | 0 .../resources => mintpy/data}/shaded_dot.png | Bin {docs/resources => mintpy/data}/star.png | Bin mintpy/defaults/auto_path.py | 19 +- mintpy/defaults/smallbaselineApp.cfg | 16 +- mintpy/dem_error.py | 79 +-- mintpy/dev/process_isce_stack_v1.py | 315 ---------- mintpy/dev/run_isce_stack.py | 577 ++++++++++++++++++ .../input_files/FernandinaSenDT128.txt | 38 ++ .../input_files/GalapagosAlosAT133.template | 78 +++ .../input_files/GalapagosEnvA2T061.txt | 18 + .../input_files/GalapagosSenDT128.template | 42 ++ .../KirishimaAlos2DT23F2970.template | 48 ++ .../input_files/KujuAlosAT422F650.txt | 30 + .../input_files/NCalAlos2ScanSARDT169.txt | 26 + mintpy/examples/input_files/README.md | 25 + .../examples/input_files/SanFranSenDT42.txt | 0 mintpy/examples/input_files/WCapeSenAT29.txt | 15 + .../examples/input_files/WellsEnvD2T399.txt | 32 + mintpy/ifgram_inversion.py | 214 ++++--- mintpy/ifgram_reconstruction.py | 18 +- mintpy/load_data.py | 101 ++- mintpy/modify_network.py | 9 +- mintpy/multilook.py | 10 +- mintpy/objects/colors.py | 4 +- mintpy/objects/gps.py | 2 +- mintpy/objects/ramp.py | 2 +- mintpy/objects/stack.py | 242 ++++++-- mintpy/objects/stackDict.py | 18 + mintpy/plot_network.py | 7 +- mintpy/prep_aria.py | 217 +++++-- mintpy/prep_fringe.py | 25 +- mintpy/prep_gamma.py | 17 +- mintpy/prep_isce.py | 102 +++- mintpy/prep_snap.py | 2 +- mintpy/save_hdfeos5.py | 38 +- mintpy/save_kmz_timeseries.py | 2 +- mintpy/select_network.py | 6 +- .../sh}/compare_velocity_with_diff_tropo.sh | 0 {sh => mintpy/sh}/load_data_aoi.sh | 0 {sh => mintpy/sh}/plot_smallbaselineApp.sh | 3 + {sh => mintpy/sh}/post_aoi.sh | 0 {sh => mintpy/sh}/post_process_Alos.sh | 0 {sh => mintpy/sh}/run_stripmap_stack.sh | 0 mintpy/simulation/decorrelation.py | 81 ++- mintpy/simulation/simulation.py | 2 +- mintpy/smallbaselineApp.py | 74 +-- mintpy/timeseries2velocity.py | 246 ++++++-- mintpy/tropo_pyaps.py | 2 +- mintpy/tropo_pyaps3.py | 25 +- mintpy/tsview.py | 18 +- mintpy/unwrap_error_phase_closure.py | 74 ++- mintpy/utils/isce_utils.py | 545 ++++++++++++----- mintpy/utils/network.py | 25 +- mintpy/utils/plot.py | 23 +- mintpy/utils/ptime.py | 2 +- mintpy/utils/readfile.py | 89 ++- mintpy/utils/utils0.py | 73 ++- mintpy/utils/utils1.py | 63 +- mintpy/utils/writefile.py | 237 +++++-- mintpy/version.py | 38 +- mintpy/view.py | 131 ++-- mkdocs.yml | 5 +- pyproject.toml | 4 + setup.py | 141 ++++- test/configs/FernandinaSenDT128.txt | 22 +- test/configs/SanFranSenDT42.txt | 14 + test/configs/WCapeSenAT29.txt | 16 + test/test_smallbaselineApp.py | 8 +- 112 files changed, 3635 insertions(+), 1375 deletions(-) create mode 100644 MANIFEST.in create mode 100644 docs/FAQs.md rename docs/{resources/colormaps/README.md => api/colormaps.md} (93%) delete mode 100644 docs/examples/dev/read_snap_img.ipynb delete mode 100644 mintpy/.travis.yml rename {docs/resources => mintpy/data}/colormaps/BlueWhiteOrangeRed.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/DEM_print.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/GMT_haxby.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/GMT_no_green.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/batlow.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/hawaii.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/oleron.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/roma.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/seminf-haxby.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/temp-c.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/temperature.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/vik.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/vikO.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/wiki-2.0.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/wiki-schwarzwald-d050.cpt (100%) rename {docs/resources => mintpy/data}/colormaps/wiki-scotland.cpt (100%) rename {docs/resources => mintpy/data}/dygraph-combined.js (100%) rename {docs/examples => mintpy/data}/input_files/FernandinaSenDT128.txt (100%) rename {docs/examples => mintpy/data}/input_files/GalapagosAlosAT133.template (100%) rename {docs/examples => mintpy/data}/input_files/GalapagosEnvA2T061.txt (100%) rename {docs/examples => mintpy/data}/input_files/GalapagosSenDT128.template (100%) rename {docs/examples => mintpy/data}/input_files/KirishimaAlos2DT23F2970.template (100%) rename {docs/examples => mintpy/data}/input_files/KujuAlosAT422F650.txt (100%) create mode 100644 mintpy/data/input_files/NCalAlos2ScanSARDT169.txt rename {docs/examples => mintpy/data}/input_files/README.md (81%) create mode 100644 mintpy/data/input_files/SanFranSenDT42.txt create mode 100644 mintpy/data/input_files/WCapeSenAT29.txt rename {docs/examples => mintpy/data}/input_files/WellsEnvD2T399.txt (100%) rename {docs/resources => mintpy/data}/shaded_dot.png (100%) rename {docs/resources => mintpy/data}/star.png (100%) delete mode 100755 mintpy/dev/process_isce_stack_v1.py create mode 100755 mintpy/dev/run_isce_stack.py create mode 100644 mintpy/examples/input_files/FernandinaSenDT128.txt create mode 100644 mintpy/examples/input_files/GalapagosAlosAT133.template create mode 100644 mintpy/examples/input_files/GalapagosEnvA2T061.txt create mode 100644 mintpy/examples/input_files/GalapagosSenDT128.template create mode 100644 mintpy/examples/input_files/KirishimaAlos2DT23F2970.template create mode 100644 mintpy/examples/input_files/KujuAlosAT422F650.txt create mode 100644 mintpy/examples/input_files/NCalAlos2ScanSARDT169.txt create mode 100644 mintpy/examples/input_files/README.md rename {docs => mintpy}/examples/input_files/SanFranSenDT42.txt (100%) create mode 100644 mintpy/examples/input_files/WCapeSenAT29.txt create mode 100644 mintpy/examples/input_files/WellsEnvD2T399.txt rename {sh => mintpy/sh}/compare_velocity_with_diff_tropo.sh (100%) rename {sh => mintpy/sh}/load_data_aoi.sh (100%) rename {sh => mintpy/sh}/plot_smallbaselineApp.sh (97%) rename {sh => mintpy/sh}/post_aoi.sh (100%) rename {sh => mintpy/sh}/post_process_Alos.sh (100%) rename {sh => mintpy/sh}/run_stripmap_stack.sh (100%) mode change 100644 => 100755 mintpy/utils/isce_utils.py create mode 100644 pyproject.toml create mode 100644 test/configs/SanFranSenDT42.txt create mode 100644 test/configs/WCapeSenAT29.txt diff --git a/.gitignore b/.gitignore index e2400ef84..6dadf144f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,15 @@ *.pyc *.idea/* *ipynb_checkpoints* +*.egg* build/ +dist/ docs/api_docs/ -docs/examples/dev/* docs/resources/*logo* docs/resources/*.ai docs/resources/*.pdf +docs/deps.svg isce.log -mintpy/dev/* mintpy/modis* +mintpy/objects/solid/solid_f.*.so* site/ -test/unwrap_error_bridging/ diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..13819bf0a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +# Include the README +include docs/README.md + +# Include the license file +include LICENSE + diff --git a/docs/FAQs.md b/docs/FAQs.md new file mode 100644 index 000000000..ab72b9708 --- /dev/null +++ b/docs/FAQs.md @@ -0,0 +1,13 @@ +## Frequently Asked Questions + +#### 1. What's the sign convention of the line-of-sight data? + +For line-of-sight (LOS) phase in the unit of radians, i.e. 'unwrapPhase' dataset in `ifgramStack.h5` file, positive value represents motion away from the satellite. We assume the "date1_date2" format for the interferogram with "date1" being the earlier acquisition. + +For LOS displacement in the unit of meters, i.e. 'timeseries' dataset in `timeseries.h5` file positive value represents motion toward the satellite (uplift for pure vertical motion). + +#### 2. How to prepare the input for MintPy if I am using InSAR software rather than ISCE stack processors and ARIA-tools? + + + + diff --git a/docs/README.md b/docs/README.md index 2e0eae688..889cfcba9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,14 +1,14 @@ -[![Language](https://img.shields.io/badge/python-3.5%2B-blue.svg)](https://www.python.org/) +[![Language](https://img.shields.io/badge/python-3.6%2B-blue.svg)](https://www.python.org/) [![Docs Status](https://readthedocs.org/projects/mintpy/badge/?version=latest)](https://mintpy.readthedocs.io/?badge=latest) [![CircleCI](https://img.shields.io/circleci/build/github/insarlab/MintPy.svg?color=green&logo=circleci)](https://circleci.com/gh/insarlab/MintPy) -[![Latest version](https://img.shields.io/badge/latest%20version-v1.2.3-yellowgreen.svg)](https://github.com/insarlab/MintPy/releases) +[![Version](https://img.shields.io/badge/version-v1.2.3-yellowgreen.svg)](https://github.com/insarlab/MintPy/releases) [![License](https://img.shields.io/badge/license-GPLv3-yellow.svg)](https://github.com/insarlab/MintPy/blob/main/LICENSE) [![Forum](https://img.shields.io/badge/forum-Google%20Group-orange.svg)](https://groups.google.com/forum/#!forum/mintpy) [![Citation](https://img.shields.io/badge/doi-10.1016%2Fj.cageo.2019.104331-blue)](https://doi.org/10.1016/j.cageo.2019.104331) ## MintPy ## -The Miami INsar Time-series software in PYthon (MintPy) is an open-source package for Interferometric Synthetic Aperture Radar time series analysis. It reads the stack of interferograms (coregistered and unwrapped) in [ISCE](https://github.com/isce-framework/isce2), [ARIA](https://github.com/aria-tools/ARIA-tools), [FRInGE](https://github.com/isce-framework/fringe), [SNAP](http://step.esa.int/), [GAMMA](https://www.gamma-rs.ch/no_cache/software.html) or ROI_PAC format, and produces three dimensional (2D in space and 1D in time) ground surface displacement in line-of-sight direction. It includes a routine time series analysis (`smallbaselineApp.py`) and some independent toolbox. +The Miami INsar Time-series software in PYthon (MintPy as /mɪnt paɪ/) is an open-source package for Interferometric Synthetic Aperture Radar (InSAR) time series analysis. It reads the stack of interferograms (coregistered and unwrapped) in [ISCE](https://github.com/isce-framework/isce2), [ARIA](https://github.com/aria-tools/ARIA-tools), [FRInGE](https://github.com/isce-framework/fringe), [SNAP](http://step.esa.int/), [GAMMA](https://www.gamma-rs.ch/no_cache/software.html) or ROI_PAC format, and produces three dimensional (2D in space and 1D in time) ground surface displacement in line-of-sight direction. It includes a routine time series analysis (`smallbaselineApp.py`) and some independent toolbox. This package was called PySAR before version 1.1.1. For version 1.1.2 and onward, we use MintPy instead. @@ -45,7 +45,7 @@ Configuration parameters for each step are initiated with default values in a cu wget https://zenodo.org/record/3952953/files/FernandinaSenDT128.tar.xz tar -xvJf FernandinaSenDT128.tar.xz cd FernandinaSenDT128/mintpy -smallbaselineApp.py ${MINTPY_HOME}/docs/examples/input_files/FernandinaSenDT128.txt +smallbaselineApp.py ${MINTPY_HOME}/mintpy/data/input_files/FernandinaSenDT128.txt ```

@@ -67,7 +67,7 @@ save_kmz_timeseries.py #generate Goodle Earth KMZ file in points for time-se #### 2.2 Customized processing recipe #### -MintPy is a toolbox with individual utility scripts. Simply run the script with `-h / --help` to see its usage, you could build your own customized processing recipe! [Here](../sh/compare_velocity_with_diff_tropo.sh) is an example to compare the velocities estimated from displacement time-series with different tropospheric delay corrections. +MintPy is a toolbox with individual utility scripts. Simply run the script with `-h / --help` to see its usage, you could build your own customized processing recipe! [Here](../mintpy/sh/compare_velocity_with_diff_tropo.sh) is an example to compare the velocities estimated from displacement time-series with different tropospheric delay corrections. #### 2.3 Build on top of `mintpy` module #### @@ -84,7 +84,7 @@ Algorithms implemented in the software are described in details at [Yunjun et al + [Quick start with example datasets](./demo_dataset.md) + [Example data directory](./dir_structure.md) -+ [Example template files for InSAR processors](./examples/input_files/README.md) ++ [Example template files for InSAR processors](./../mintpy/data/input_files/README.md) + [Tutorials in Jupyter Notebook](https://github.com/insarlab/MintPy-tutorial) ### 4. Contact us ### diff --git a/docs/api/attributes.md b/docs/api/attributes.md index fdb7f1ed2..b7cc4a394 100644 --- a/docs/api/attributes.md +++ b/docs/api/attributes.md @@ -1,8 +1,8 @@ -MintPy mainly uses attribute names from [ROI_PAC](http://www.geo.cornell.edu/eas/PeoplePlaces/Faculty/matt/pub/winsar/InSAR_textbook_for_web_2014.pdf), with some additional attributes generated by MintPy itself. +MintPy mainly uses attribute names from [ROI_PAC](http://www.geo.cornell.edu/eas/PeoplePlaces/Faculty/matt/pub/winsar/InSAR_textbook_for_web_2014.pdf), with some additional self-generated attributes. ### Required attributes ### -If using ROI_PAC as InSAR processor, both **baseline parameter RSC** file (i.e. *100416-100901_baseline.rsc*) and **basic metadata file** (i.e. *filt_100416-100901-sim_HDR_4rlks_c10.unw.rsc*) will be imported into MintPy. The following attributes for each interferogram are required in order to run MintPy: +If using ROI_PAC as the InSAR processor, both **baseline parameter RSC** file (i.e. *100416-100901_baseline.rsc*) and **basic metadata file** (i.e. *filt_100416-100901-sim_HDR_4rlks_c10.unw.rsc*) will be imported into MintPy. The following attributes for each interferogram are required in order to run MintPy: + FILE_LENGTH = number of rows. + WIDTH = number of columns. @@ -15,13 +15,16 @@ If using ROI_PAC as InSAR processor, both **baseline parameter RSC** file (i.e. + CENTER_LINE_UTC = Time at middle of interferogram in seconds, used in tropo correction using PyAPS. + HEIGHT = Height of satellite in meters, used in dem_error, incidence_angle, convert2mat. + STARTING_RANGE = Distance from satellite to first ground pixel in meters, used in incidence_angle calculation -+ DATE12 = (date1)-(date2), reference - secondary date of interferogram in 6 digit number. + PLATFORM = satellite/sensor name, used in Local Oscillator Drift correction for Envisat. + ORBIT_DIRECTION = ascending, or descending. ++ ALOOKS/RLOOKS = multilook number in azimuth/range direction, used in weighted network inversion. + +The following attributes vary for each interferogram: + ++ DATE12 = (date1)-(date2), reference - secondary date of interferogram in 6 digit number. + P_BASELINE_TOP_HDR = Perpendicular baseline at top (first line) of interferogram in meters. + P_BASELINE_BOTTOM_HDR = Perpendicular baseline at bottom (last line) of interferogram in meters. -+ ALOOKS/RLOOKS = multilook number in azimuth/range direction, used in weighted network inversion. - + ### Optional attributes ### + ANTENNA_SIDE = -1 for right looking radar, used in save_unavco @@ -29,16 +32,16 @@ If using ROI_PAC as InSAR processor, both **baseline parameter RSC** file (i.e. + HEADING = Spacecraft heading at peg point (degree), used in asc_desc, los2enu + PRF = Pulse repetition frequency (Hz), used in save_unavco -### Attributes generated by MintPy automatically ### +### Self-generated attributes ### + FILE_TYPE = file type. - for HDF5 files, it's the root level dataset name, such as `velocity, timeseries, ifgramStack, temporalCoherence, mask, HDFEOS, dem, coherence, etc.`;` - for binary files, it's the file extension name, such as `.unw, .cor, .int, .amp, .mli, .dem, .hgt, .unw.conncomp, .UTM_TO_RDC, .trans, etc.`, except for ISCE geometry files, which is the file base name such as `hgt, lat, lon, los, shadowMask, incLocal`. + FILE_PATH = absolute file path + LENGTH = row number, equivalent to FILE_LENGTH -+ PROCESSOR = processing software, i.e. isce, roipac, gamma ++ PROCESSOR = processing software, i.e. isce, aria, snap, gamma, roipac etc. + DATA_TYPE = data type, i.e. float32, int16, etc., for isce product read using GDAL -+ UNIT = data unit, i.e. m, m/yr, radian, and 1 for file without unit, such as coherence ++ UNIT = data unit, i.e. m, m/yr, radian, and 1 for file without unit, such as coherence [[source]](https://github.com/insarlab/MintPy/blob/main/mintpy/objects/stack.py#L75) + REF_DATE = reference date + REF_X/Y/LAT/LON = column/row/latitude/longitude of reference point + SUBSET_XMIN/XMAX/YMIN/YMAX = start/end column/row number of subset in the original coverage @@ -47,4 +50,4 @@ If using ROI_PAC as InSAR processor, both **baseline parameter RSC** file (i.e. ### Reference ### -+ Pritchard et al., (2014), Open-source software for geodetic imaging: ROI_PAC for InSAR and pixel tracking, pp 44-48. [PDF](http://www.geo.cornell.edu/eas/PeoplePlaces/Faculty/matt/pub/winsar/InSAR_textbook_for_web_2014.pdf) \ No newline at end of file ++ Pritchard et al., (2014), Open-source software for geodetic imaging: ROI_PAC for InSAR and pixel tracking, pp 44-48. [PDF](http://www.geo.cornell.edu/eas/PeoplePlaces/Faculty/matt/pub/winsar/InSAR_textbook_for_web_2014.pdf) diff --git a/docs/resources/colormaps/README.md b/docs/api/colormaps.md similarity index 93% rename from docs/resources/colormaps/README.md rename to docs/api/colormaps.md index 4d7540553..b98fa4644 100644 --- a/docs/resources/colormaps/README.md +++ b/docs/api/colormaps.md @@ -4,7 +4,7 @@ MintPy support the following colormaps: + [Matplotlib colormaps](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html) + Custom colormaps: `cmy` and `dismph` -+ Custom colormaps in **.cpt** (color palette tables) format. To add your own colormap, drop the corresponding .cpt file in `$MINTPY/docs/resources/colormaps`. ++ Custom colormaps in **.cpt** (color palette tables) format. To add your own colormap, drop the corresponding .cpt file in `$MINTPY/mintpy/data/colormaps`. We recommend to use cyclic colormap `cmy` for wrapped phase/displacement measurement. @@ -63,5 +63,5 @@ The following colormaps is included by default: + More at [Scientific Color-Maps](http://www.fabiocrameri.ch/colourmaps.php) ([Crameri, 2018](https://doi.org/10.5194/gmd-11-2541-2018))

- +

diff --git a/docs/api/module_hierarchy.md b/docs/api/module_hierarchy.md index 63cb4a009..ad70c5f57 100644 --- a/docs/api/module_hierarchy.md +++ b/docs/api/module_hierarchy.md @@ -51,7 +51,7 @@ Hierarchy of sub-modules within MintPy. Level N modules depends on level N-1, N- /utils plot (objects/{stack, coord, colors}, utils/{ptime, utils0, utils1, readfile, network}) utils (objects/{stack, coord}, utils/{ptime, utils0, utils1, readfile}) - isce_utils (utils/{readfile, writefile, utils1}) + isce_utils (utils/{ptime, readfile, writefile, utils1}) ------------------ level 6 -------------------- /objects insar_vs_gps (objects/{stack, giant}, utils/{readfile, gps, plot, utils}) diff --git a/docs/conda.txt b/docs/conda.txt index eefc1f99d..b286c6d7c 100644 --- a/docs/conda.txt +++ b/docs/conda.txt @@ -12,6 +12,7 @@ matplotlib netcdf4 numpy openmp +pip pygrib pyhdf pykdtree diff --git a/docs/dask.md b/docs/dask.md index d86ef8984..b76f82e39 100644 --- a/docs/dask.md +++ b/docs/dask.md @@ -55,7 +55,7 @@ A typical run time without local cluster is 30 secs and with 8 workers 11.4 secs To show the run time improvement, we test three datasets (South Isabela, Fernandina, and Kilauea) with different number of cores and same amount of allocated memory (4 GB) on a compute node in the [Stampede2 cluster's skx-normal queue](https://portal.tacc.utexas.edu/user-guides/stampede2#overview-skxcomputenodes). Results are as below: -![Dask LocalCluster Performance](https://github.com/insarlab/MintPy-tutorial/blob/main/docs/dask_local_cluster_performance.png) +![Dask LocalCluster Performance](https://yunjunzhang.files.wordpress.com/2020/08/dask_local_cluster_performance.png) #### 1.5 Known problems #### diff --git a/docs/demo_dataset.md b/docs/demo_dataset.md index 91faa85e0..50cf2abd6 100644 --- a/docs/demo_dataset.md +++ b/docs/demo_dataset.md @@ -10,7 +10,7 @@ Size: ~750 MB wget https://zenodo.org/record/3952953/files/FernandinaSenDT128.tar.xz tar -xvJf FernandinaSenDT128.tar.xz cd FernandinaSenDT128/mintpy -smallbaselineApp.py ${MINTPY_HOME}/docs/examples/input_files/FernandinaSenDT128.txt +smallbaselineApp.py ${MINTPY_HOME}/mintpy/data/input_files/FernandinaSenDT128.txt ```

@@ -21,6 +21,27 @@ Relevant literature: + Yunjun, Z., H. Fattahi, and F. Amelung (2019), Small baseline InSAR time series analysis: Unwrapping error correction and noise reduction, _Computers & Geosciences, 133,_ 104331, doi:10.1016/j.cageo.2019.104331. +#### Sentinel-1 on San Francisco Bay with ARIA #### + +Area: San Francisco Bay, California, USA +Data: Sentinel-1 A/B descending track 42 during May 2015 - March 2020 (114 acquisitoins; [Zenodo](https://zenodo.org/record/4265413)) +Size: ~2.7 GB + +```bash +wget https://zenodo.org/record/4265413/files/SanFranSenDT42.tar.xz +tar -xvJf SanFranSenDT42.tar.xz +cd SanFranSenDT42/mintpy +smallbaselineApp.py ${MINTPY_HOME}/mintpy/data/input_files/SanFranSenDT42.txt +``` + +

+ +

+ +Relevant literature: + ++ Chaussard, E., R. Bürgmann, H. Fattahi, R. M. Nadeau, T. Taira, C. W. Johnson, and I. Johanson (2015), Potential for larger earthquakes in the East San Francisco Bay Area due to the direct connection between the Hayward and Calaveras Faults, Geophysical Research Letters, 42(8), 2734-2741, doi:10.1002/2015GL063575. + #### Envisat of the 2008 Wells earthquake with Gamma #### Area: Wells, Nevada, USA @@ -31,7 +52,7 @@ Size: ~280 MB wget https://zenodo.org/record/3952950/files/WellsEnvD2T399.tar.xz tar -xvJf WellsEnvD2T399.tar.xz cd WellsEnvD2T399/mintpy -smallbaselineApp.py ${MINTPY_HOME}/docs/examples/input_files/WellsEnvD2T399.txt +smallbaselineApp.py ${MINTPY_HOME}/mintpy/data/input_files/WellsEnvD2T399.txt ```

@@ -42,6 +63,19 @@ Relevant literature: + Nealy, J. L., H. M. Benz, G. P. Hayes, E. A. Bergman, and W. D. Barnhart (2017), The 2008 Wells, Nevada, Earthquake Sequence: Source Constraints Using Calibrated Multiple‐Event Relocation and InSARThe 2008 Wells, Nevada, Earthquake Sequence: Source Constraints Using Calibrated Multiple‐Event Relocation, _Bulletin of the Seismological Society of America_, 107(3), 1107-1117, doi:10.1785/0120160298. +#### Sentinel-1 on Western Cape, South Africa with SNAP #### + +Area: West coast of Western Cape province, South Africa +Data: Sentinel-1 ascending track 29 during March - June 2019 (10 acquisitions; [Zenodo](https://zenodo.org/record/4127335)) +Size: ~560 MB + +```bash +wget https://zenodo.org/record/4127335/files/WCapeSenAT29.tar.xz +tar -xvJf WCapeSenAT29.tar.xz +cd WCapeSenAT29 +smallbaselineApp.py ${MINTPY_HOME}/mintpy/data/input_files/WCapeSenAT29.txt +``` + #### ALOS-1 on Kuju with ROI_PAC #### Area: Kuju volcano at Kyushu island, SW Japan @@ -52,7 +86,7 @@ Size: ~240 MB wget https://zenodo.org/record/3952917/files/KujuAlosAT422F650.tar.xz tar -xvJf KujuAlosAT422F650.tar.xz cd KujuAlosAT422F650/mintpy -smallbaselineApp.py ${MINTPY_HOME}/docs/examples/input_files/KujuAlosAT422F650.txt +smallbaselineApp.py ${MINTPY_HOME}/mintpy/data/input_files/KujuAlosAT422F650.txt ```

diff --git a/docs/dir_structure.md b/docs/dir_structure.md index 3ffd508aa..fb01e3d49 100644 --- a/docs/dir_structure.md +++ b/docs/dir_structure.md @@ -195,6 +195,291 @@ mintpy.load.azAngleFile = $DATA_DIR/KirishimaAlosAT424F620_630/geom_referen mintpy.load.shadowMaskFile = $DATA_DIR/KirishimaAlosAT424F620_630/geom_reference/shadowMask.rdr ``` +### ISCE / [alosStack](https://github.com/isce-framework/isce2/blob/main/contrib/stack/alosStack/alosStack_tutorial.txt) ### + +``` +$DATA_DIR/NCalAlos2ScanSARDT169 +├── alosStack.xml +├── baseline +│   ├── 150225-150408.rmg +│   ├── 150225-150408.rmg.vrt +│   ├── 150225-150408.rmg.xml +│   ├── ... +│   └── baseline_center.txt +├── burst_synchronization.txt +├── dates +│   ├── 150225 +│   │ ├── 150225.track.xml +│ │ ├── f1_2800 +│   │ │ ├── 150225.frame.xml +│   │ │ ├── mosaic #only in reference date folder +│   │ │ │ └── swath_offset.txt +│   │ │ ├── s1 +│   │ │ │ ├── 150225.slc.vrt +│   │ │ │ └── 150225.slc.xml +│   │ │ └── ... +│ │ ├── ... +│ │ └── insar #only in reference date folder +│   │ └── frame_offset.txt +│   └── ... +├── dates_ion +│   ├── filt_ion_150225_5rlks_28alks.ion +│   ├── filt_ion_150225_5rlks_28alks.ion.vrt +│   ├── filt_ion_150225_5rlks_28alks.ion.xml +│ └── ... +├── dates_resampled +│   ├── 150225 +│   │ ├── 150225.track.xml #only in reference date folder +│ │ ├── f1_2800 +│   │ │ ├── 150225.frame.xml #only in reference date folder +│   │ │ ├── s1 +│   │ │ │ ├── 150225_lower.slc +│   │ │ │ ├── 150225_lower.slc.vrt +│   │ │ │ ├── 150225_lower.slc.xml +│   │ │ │ ├── 150225.slc +│   │ │ │ ├── 150225.slc.vrt +│   │ │ │ ├── 150225.slc.xml +│   │ │ │ ├── 150225_upper.slc +│   │ │ │ ├── 150225_upper.slc.vrt +│   │ │ │ └── 150225_upper.slc.xml +│   │ │ └── ... +│ │ ├── ... +│ │ └── insar +│ │ ├── 150225_1rlks_14alks.hgt #the following files only in reference date folder +│   │   ├── 150225_1rlks_14alks.hgt.vrt +│   │   ├── 150225_1rlks_14alks.hgt.xml +│ │ ├── 150225_1rlks_14alks.lat +│   │   ├── 150225_1rlks_14alks.lat.vrt +│   │   ├── 150225_1rlks_14alks.lat.xml +│ │ ├── 150225_1rlks_14alks.lon +│   │   ├── 150225_1rlks_14alks.lon.vrt +│   │   ├── 150225_1rlks_14alks.lon.xml +│ │ ├── 150225_1rlks_14alks.los +│   │   ├── 150225_1rlks_14alks.los.vrt +│   │   ├── 150225_1rlks_14alks.los.xml +│ │ ├── 150225_1rlks_14alks.wbd +│   │   ├── 150225_1rlks_14alks.wbd.vrt +│   │   ├── 150225_1rlks_14alks.wbd.xml +│ │ ├── 150225_5rlks_28alks.hgt +│   │   ├── 150225_5rlks_28alks.hgt.vrt +│   │   ├── 150225_5rlks_28alks.hgt.xml +│ │ ├── 150225_5rlks_28alks.lat +│   │   ├── 150225_5rlks_28alks.lat.vrt +│   │   ├── 150225_5rlks_28alks.lat.xml +│ │ ├── 150225_5rlks_28alks.lon +│   │   ├── 150225_5rlks_28alks.lon.vrt +│   │   ├── 150225_5rlks_28alks.lon.xml +│ │ ├── 150225_5rlks_28alks.los +│   │   ├── 150225_5rlks_28alks.los.vrt +│   │   ├── 150225_5rlks_28alks.los.xml +│ │ ├── 150225_5rlks_28alks.los.geo +│   │   ├── 150225_5rlks_28alks.los.geo.vrt +│   │   ├── 150225_5rlks_28alks.los.geo.xml +│ │ ├── 150225_5rlks_28alks.wbd +│   │   ├── 150225_5rlks_28alks.wbd.vrt +│   │   ├── 150225_5rlks_28alks.wbd.xml +│   │   ├── affine_transform.txt +│   │   ├── crop.dem +│   │   ├── crop.dem.vrt +│   │   ├── crop.dem.xml +│   │   ├── rdr_dem_offset +│   │   ├── 150408_1rlks_14alks_az.off #the following files only in secondary date folders +│   │   ├── 150408_1rlks_14alks_az.off.vrt +│   │   ├── 150408_1rlks_14alks_az.off.xml +│   │   ├── 150408_1rlks_14alks_rg.off +│   │   ├── 150408_1rlks_14alks_rg.off.vrt +│   │   ├── 150408_1rlks_14alks_rg.off.xml +│   │   ├── 150408_1rlks_14alks_rg_rect.off +│   │   ├── 150408_1rlks_14alks_rg_rect.off.vrt +│   │   └── 150408_1rlks_14alks_rg_rect.off.xml +│   └── ... +├── fig_ion +│   ├── 150225-150408.tif +│   └── ... +├── pairs +│   ├── 150225-150408 +│   │ ├── 150225.track.xml +│   │ ├── 150408.track.xml +│   │ ├── f1_2800 +│   │ │ ├── 150225.frame.xml +│   │ │ ├── 150408.frame.xml +│   │ │ ├── mosaic +│   │ │ ├── s1 +│   │ │ │ ├── 150225-150408_1rlks_14alks.amp +│   │ │ │ ├── 150225-150408_1rlks_14alks.amp.vrt +│   │ │ │ ├── 150225-150408_1rlks_14alks.amp.xml +│   │ │ │ ├── 150225-150408_1rlks_14alks.int +│   │ │ │ ├── 150225-150408_1rlks_14alks.int.vrt +│   │ │ │ ├── 150225-150408_1rlks_14alks.int.xml +│   │ │ │ ├── 150225.slc +│   │ │ │ ├── 150225.slc.vrt +│   │ │ │ ├── 150225.slc.xml +│   │ │ │ ├── 150408.slc +│   │ │ │ ├── 150408.slc.vrt +│   │ │ │ └── 150408.slc.xml +│   │ │ └── ... +│   │ ├── ... +│   │ └── insar +│   │ ├── 150225-150408_1rlks_14alks.amp +│   │ ├── 150225-150408_1rlks_14alks.amp.vrt +│   │ ├── 150225-150408_1rlks_14alks.amp.xml +│   │ ├── 150225-150408_1rlks_14alks.int +│   │ ├── 150225-150408_1rlks_14alks.int.vrt +│   │ ├── 150225-150408_1rlks_14alks.int.xml +│   │ ├── 150225-150408_5rlks_28alks.amp +│   │ ├── 150225-150408_5rlks_28alks.amp.vrt +│   │ ├── 150225-150408_5rlks_28alks.amp.xml +│   │ ├── 150225-150408_5rlks_28alks.cor +│   │ ├── 150225-150408_5rlks_28alks.cor.vrt +│   │ ├── 150225-150408_5rlks_28alks.cor.xml +│   │ ├── 150225-150408_5rlks_28alks.cor.geo +│   │ ├── 150225-150408_5rlks_28alks.cor.geo.vrt +│   │ ├── 150225-150408_5rlks_28alks.cor.geo.xml +│   │ ├── 150225-150408_5rlks_28alks.phsig +│   │ ├── 150225-150408_5rlks_28alks.phsig.vrt +│   │ ├── 150225-150408_5rlks_28alks.phsig.xml +│   │ ├── crop.dem +│   │ ├── crop.dem.vrt +│   │ ├── crop.dem.xml +│   │ ├── diff_150225-150408_1rlks_14alks.int +│   │ ├── diff_150225-150408_1rlks_14alks.int.vrt +│   │ ├── diff_150225-150408_1rlks_14alks.int.xml +│   │ ├── diff_150225-150408_5rlks_28alks.int +│   │ ├── diff_150225-150408_5rlks_28alks.int.vrt +│   │ ├── diff_150225-150408_5rlks_28alks.int.xml +│   │ ├── diff_150225-150408_5rlks_28alks_ori.int +│   │ ├── diff_150225-150408_5rlks_28alks_ori.int.vrt +│   │ ├── diff_150225-150408_5rlks_28alks_ori.int.xml +│   │ ├── filt_150225-150408_5rlks_28alks.int +│   │ ├── filt_150225-150408_5rlks_28alks.int.vrt +│   │ ├── filt_150225-150408_5rlks_28alks.int.xml +│   │ ├── filt_150225-150408_5rlks_28alks_msk.unw +│   │ ├── filt_150225-150408_5rlks_28alks_msk.unw.geo +│   │ ├── filt_150225-150408_5rlks_28alks_msk.unw.geo.vrt +│   │ ├── filt_150225-150408_5rlks_28alks_msk.unw.geo.xml +│   │ ├── filt_150225-150408_5rlks_28alks_msk.unw.vrt +│   │ ├── filt_150225-150408_5rlks_28alks_msk.unw.xml +│   │ ├── filt_150225-150408_5rlks_28alks.unw +│   │ ├── filt_150225-150408_5rlks_28alks.unw.conncomp +│   │ ├── filt_150225-150408_5rlks_28alks.unw.conncomp.vrt +│   │ ├── filt_150225-150408_5rlks_28alks.unw.conncomp.xml +│   │ ├── filt_150225-150408_5rlks_28alks.unw.geo +│   │ ├── filt_150225-150408_5rlks_28alks.unw.geo.vrt +│   │ ├── filt_150225-150408_5rlks_28alks.unw.geo.xml +│   │ ├── filt_150225-150408_5rlks_28alks.unw.vrt +│   │ └── filt_150225-150408_5rlks_28alks.unw.xml +│   └── ... +└── pairs_ion +    ├── 150225-150408 +    │ ├── 150225.track.xml +    │ ├── 150408.track.xml +    │ ├── f1_2800 +    │ │ ├── 150225.frame.xml +    │ │ ├── 150408.frame.xml +    │ │ ├── s1 +    │ │ │ ├── 150225.slc +    │ │ │ ├── 150225.slc.vrt +    │ │ │ ├── 150225.slc.xml +    │ │ │ ├── 150408.slc +    │ │ │ ├── 150408.slc.vrt +    │ │ │ └── 150408.slc.xml +    │ │ └── ... +    │ ├── ... +    │ └── ion +    │ ├── ion_cal +    │ │ ├── diff_150225-150408_80rlks_448alks.int +    │ │ ├── diff_150225-150408_80rlks_448alks.int.vrt +    │ │ ├── diff_150225-150408_80rlks_448alks.int.xml +    │ │ ├── diff_150225-150408_80rlks_448alks_ori.int +    │ │ ├── diff_150225-150408_80rlks_448alks_ori.int.vrt +    │ │ ├── diff_150225-150408_80rlks_448alks_ori.int.xml +    │ │ ├── diff_80rlks_448alks.cor +    │ │ ├── diff_80rlks_448alks.cor.vrt +    │ │ ├── diff_80rlks_448alks.cor.xml +    │ │ ├── filt_ion_80rlks_448alks.ion +    │ │ ├── filt_ion_80rlks_448alks.ion.vrt +    │ │ ├── filt_ion_80rlks_448alks.ion.xml +    │ │ ├── filt_ion_80rlks_448alks.std +    │ │ ├── filt_ion_80rlks_448alks.std.vrt +    │ │ ├── filt_ion_80rlks_448alks.std.xml +    │ │ ├── filt_ion_80rlks_448alks.win +    │ │ ├── filt_ion_80rlks_448alks.win.vrt +    │ │ ├── filt_ion_80rlks_448alks.win.xml +    │ │ ├── ion_80rlks_448alks.ion +    │ │ ├── ion_80rlks_448alks.ion.vrt +    │ │ ├── ion_80rlks_448alks.ion.xml +    │ │ ├── lat_80rlks_448alks.lat +    │ │ ├── lat_80rlks_448alks.lat.vrt +    │ │ ├── lat_80rlks_448alks.lat.xml +    │ │ ├── lon_80rlks_448alks.lon +    │ │ ├── lon_80rlks_448alks.lon.vrt +    │ │ ├── lon_80rlks_448alks.lon.xml +    │ │ ├── lower_80rlks_448alks.amp +    │ │ ├── lower_80rlks_448alks.amp.vrt +    │ │ ├── lower_80rlks_448alks.amp.xml +    │ │ ├── lower_80rlks_448alks.cor +    │ │ ├── lower_80rlks_448alks.cor.vrt +    │ │ ├── lower_80rlks_448alks.cor.xml +    │ │ ├── lower_80rlks_448alks.int +    │ │ ├── lower_80rlks_448alks.int.vrt +    │ │ ├── lower_80rlks_448alks.int.xml +    │ │ ├── lower_80rlks_448alks.phsig +    │ │ ├── lower_80rlks_448alks.phsig.vrt +    │ │ ├── lower_80rlks_448alks.phsig.xml +    │ │ ├── lower_80rlks_448alks.unw +    │ │ ├── lower_80rlks_448alks.unw.conncomp +    │ │ ├── lower_80rlks_448alks.unw.conncomp.vrt +    │ │ ├── lower_80rlks_448alks.unw.conncomp.xml +    │ │ ├── lower_80rlks_448alks.unw.vrt +    │ │ ├── lower_80rlks_448alks.unw.xml +    │ │ ├── upper_80rlks_448alks.amp +    │ │ ├── upper_80rlks_448alks.amp.vrt +    │ │ ├── upper_80rlks_448alks.amp.xml +    │ │ ├── upper_80rlks_448alks.cor +    │ │ ├── upper_80rlks_448alks.cor.vrt +    │ │ ├── upper_80rlks_448alks.cor.xml +    │ │ ├── upper_80rlks_448alks.int +    │ │ ├── upper_80rlks_448alks.int.vrt +    │ │ ├── upper_80rlks_448alks.int.xml +    │ │ ├── upper_80rlks_448alks.phsig +    │ │ ├── upper_80rlks_448alks.phsig.vrt +    │ │ ├── upper_80rlks_448alks.phsig.xml +    │ │ ├── upper_80rlks_448alks.unw +    │ │ ├── upper_80rlks_448alks.unw.conncomp +    │ │ ├── upper_80rlks_448alks.unw.conncomp.vrt +    │ │ ├── upper_80rlks_448alks.unw.conncomp.xml +    │ │ ├── upper_80rlks_448alks.unw.vrt +    │ │ ├── upper_80rlks_448alks.unw.xml +    │ │ ├── wbd_80rlks_448alks.wbd +    │ │ ├── wbd_80rlks_448alks.wbd.vrt +    │ │ └── wbd_80rlks_448alks.wbd.xml +    │ ├── lower +    │ └── upper +    └── ... +``` + +The corresponding template options for `load_data`: + +```cfg +mintpy.load.processor = isce +##NOTE: 150408 is the reference date of alosStack processing. +## (parameter "reference date of the stack" of alosStack input xml file) +##---------for ISCE only: +mintpy.load.metaFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/150408.track.xml +mintpy.load.baselineDir = $DATA_DIR/NCalAlos2ScanSARDT169/baseline +##---------interferogram datasets: +mintpy.load.unwFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/filt_*-*_5rlks_28alks.unw +mintpy.load.corFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/*-*_5rlks_28alks.cor +mintpy.load.connCompFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/filt_*-*_5rlks_28alks.unw.conncomp +##---------geometry datasets: +mintpy.load.demFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.hgt +mintpy.load.lookupYFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.lat +mintpy.load.lookupXFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.lon +mintpy.load.incAngleFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.los +mintpy.load.azAngleFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.los +mintpy.load.waterMaskFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.wbd +``` + ### ARIA from [ARIA-tools](https://github.com/aria-tools/ARIA-tools) ### 1. Download GUNW products using [ariaDownload.py](https://nbviewer.jupyter.org/github/aria-tools/ARIA-tools-docs/blob/master/JupyterDocs/ariaDownload/ariaDownload_tutorial.ipynb). @@ -323,7 +608,7 @@ mintpy.load.lookupXFile = $DATA_DIR/GalapagosEnvA2T061/geometry/sim*rlks.UT ### [SNAP](https://github.com/insarlab/MintPy/wiki/SNAP-input-data) ### ``` -$DATA_DIR/SAfricaSenAT29 +$DATA_DIR/WCapeSenAT29 ├── interferograms │ ├── 20190408_20190420 │ │ ├── 20190408_20190420_coh_tc.dim @@ -345,7 +630,7 @@ $DATA_DIR/SAfricaSenAT29 │ ├── dem*.img │ ├── dem*.hdr └── mintpy - └── SAfricaSenAT29.txt + └── WCapeSenAT29.txt ``` The corresponding template options for `load_data`: @@ -353,10 +638,10 @@ The corresponding template options for `load_data`: ```cfg mintpy.load.processor = snap ##---------interferogram datasets: -mintpy.load.unwFile = $DATA_DIR/SAfricaSenAT29/interferograms/*/*/Unw_*.img -mintpy.load.corFile = $DATA_DIR/SAfricaSenAT29/interferograms/*/*/coh_*.img +mintpy.load.unwFile = $DATA_DIR/WCapeSenAT29/interferograms/*/*/Unw_*.img +mintpy.load.corFile = $DATA_DIR/WCapeSenAT29/interferograms/*/*/coh_*.img ##---------geometry datasets: -mintpy.load.demFile = $DATA_DIR/SAfricaSenAT29/dem_tc.data/dem*.img +mintpy.load.demFile = $DATA_DIR/WCapeSenAT29/dem_tc.data/dem*.img ``` ### ROI_PAC (rsmas version) ### diff --git a/docs/examples/dev/read_snap_img.ipynb b/docs/examples/dev/read_snap_img.ipynb deleted file mode 100644 index 1ea060bcb..000000000 --- a/docs/examples/dev/read_snap_img.ipynb +++ /dev/null @@ -1,127 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Read and plot file in SNAP BEAM-DIMAP format" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Go to directory: /Users/yunjunz/insarlab/test/SNAP_SAfricaS1/\n" - ] - } - ], - "source": [ - "%matplotlib inline\n", - "\n", - "import os\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from mintpy.utils import readfile\n", - "\n", - "proj_dir = os.path.expanduser('~/insarlab/test/SNAP_SAfricaS1/')\n", - "os.chdir(proj_dir)\n", - "print('Go to directory:', proj_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "

" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# unwrapped interferogram\n", - "fname = os.path.join(proj_dir, '20190415_20190427/20190415_20190427_unw_tc.data/Unw_Phase_ifg_15Apr2019_27Apr2019_VV.img')\n", - "data, atr = readfile.read(fname)\n", - "\n", - "# plot\n", - "data[data == 0] = np.nan # get rid of zero value, which is filled during phase unwrapping\n", - "plt.figure()\n", - "plt.imshow(data, cmap='jet')\n", - "plt.colorbar()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAT8AAAD8CAYAAAABraMFAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzsvW2MZNl93vc7U7e7q2uqZovTzene3d7VLDgkl2vSIa2xJEMyoERULAVJFCewIwWQlUQQDcOOX+APpAwEtmMIEQK/wEESIRRsSEL8QjmyYEbQSywhsk3Dps2V1iS1XIpDs6ntXfYMu2drpmu7q7pvzc2Hc586zz1dMzuzOzs7PV1/oFBV95577rm36jz3+b+eUFUVc5nLXOZy2uTMOz2AucxlLnN5J2QOfnOZy1xOpczBby5zmcuplDn4zWUuczmVMge/ucxlLqdS5uA3l7nM5VTKAwe/EML3hRC+HEK4EkL4xIM+/1zmMpe5AIQHGecXQmgBvwt8L7AF/Fvgh6qqevGBDWIuc5nLXHjwzO/bgCtVVf37qqoOgX8I/MADHsNc5jKXuVA84PM9Cbxs37eAb88bhRA+BnwM4OzZs9/67LPPPpjRzWUumTz/EnzrKfj7Pf/88ztVVb37rfRxKYRq/y7bfgN+raqq73sr53ur8qDBL8zYdkzvrqrqk8AnAS5fvlx97nOfe7vHNZe5EP4QaUbo/SPwuX/xDg3oAUoI4etvtY994E/eZdu/Aqtv9XxvVR40+G0BT9n3DeDVBzyGucwFMLAr63fNhhIYAu34OfwhqP7VOzPGkySBBw8ob0Ue9Fj/LfDeEMIzwCvADwL/zQMew1xOqYQPkYAOmmDn28r68yjbN5c7yhlg+Z0exD3IA/1Zq6oqQwh/Bvg1oAX83aqqfudBjmEup0PCU0Tw6tfv7azBkMT2+kSQy4GuZMr+5vLGEoCFd3oQ9yAP/JlWVdUvA7/8oM87l0dbgnSuggR0BRH49K5tA5r//KF9174C6AI7RNX3I1D99tt5BSdf5mrvXObyACSEzxOVLClaa1DWvEOAB8l+J9DrksBQIOngp2O0nXrf1v2+gkdP5sxvLnO5zxLCp2lak5aBc8AB6S98FN/KgmlQgUDN1daBPtys+yCpvV2a6u/Ijp/PlDeUOfOby1zeooTwm0RgO6pfnfo7RMDaI3GMI2AN2CQGDwBlrwa8vRoMqY8XipXAbt3XeRh2EujpJdHhuc1wLsdkzvzmMpd7kBA+VX86IILYEYnlHTFlZ0CMJDuy9vr7Xq2PuWltBW4H9XFlfaxAdA+4Ho8taxAcrRwfoOFlCDBf9eH2Mvf2zmUud5AQ/i8iAC2QpsoyEZh26+83621qc73e3qnfBYIOkhCRKgfBA3u/TgTEq8Al4Ev1Oa4BF2ws1+vtF4BwnA3OZabcL+YXQmgD/xxYImLU/11V1V8OIZwHPgVcJFL9P15V1Wv1MT8O/CgwAf5sVVW/9kbnmYPfXN5WCeFFIvAsA/+mfhedug6cJ4KXq7GQ1FRtP1+3L4gguF/vO7D3AxJwQmR3PSK4advVuv1v1dsEjteIoacCvbI+9iqRkeoczkTnkst9ApQx8B9VVTUMISwAnwkh/ArwXwK/UVXVT9YVoT4BfDyE8BwxZvj3AU8Avx5CeF9VVZMHMNa5zAVC+Er9Saxsj/gXWyaCTWH7jojgIlVU2xwEj7K+xAYXSH/dZSJASWUtSEC4TAK+Let7t/4sVVjqtT5fq8f2z0g2wuX6Gm9SVRtv+h49ynK/mF8VS03J164fvCIWQfnuevvPAr8JfLze/g+rqhoDXwshXCEWUbljXs4c/ObypiWE50kMSXqhAEqq4wGJUR3N6AUSKELTlrdQHyuG9mS9fY/I/CCxQ9n4BKACP/W9Vo/piAiUYo4CS1edITHEm/X+S3Z9c5kl99PbW5e/e5544//3qqo+G0JYq6rqGwBVVX0jhCBbxZPAv7bDt0h/ltvKHPzmctcSmZ1YVY8IDudJ4CH2BgmwnNUdkVRdgWGZHScgdUYmPnHdPosQ7JOATeNYoQmCAlTtl0ot0TkV3XyTJkuFxCAPgGfu4a6dHrlHh8dqCMErlnyyLmgCQK2yfjiE0Ad+MYTwwTv0dVcFU3KZg99cZkoIV0leVGdly0SwOCCptdC0hR3V+wRyV0kMbY/khfVjSpL9TyIGCSm2T6AmG2BZv1+v26zV5xPYaTzYWAWGAl53iihw2lVwtysuE8JnqKrvOn7TTrnco9q7U1XV5TdqVFXVIMTYp+8DroYQHq9Z3+PEJxK8yYIp8zU85gJACLuEcLN+XSU5Dg6s1TkSiJ0jahZiUEc0n6VyaAigbtYvqY9ifgIiMS5nZM68bhJV2yMiuIkFCvDW6j52aYKcMz/13cuuXixSwCdmKJVXY7lZn/uAuRwXzzB8o9cd+wnh3TXjI4SwDHwUeAn4NPAjdbMfAf5J/fnTwA+GEJbqoinvJXrX7ihz5ndKJQTFvknOEaMHZCqROisbnoePQAIJsUDsXft0jJwekEDSnRY6TsCaq8qy3SnmxPuXKqvjyhn9LhNth1Jv3Xkyy8myRgI/fb8JfAD4Sn1v5pLLfQxyfhz42drudwb4+aqqfimE8K+Anw8h/Cjwe8AfA6iq6ndCCD8PvEj84f70G3l6YQ5+p0aaYJerfG7f2rOjpJ7qGL2LpcljmjszxPZKUoAxdf8dEoO7RARcrL0A1cNcdKwDm9ou2LFicLO8xzpe4KvxOghDYqMKuta2sh7rCnCOEHapqhlB0adY7pfDo6qqzwMfmbF9F/ie2xzzE8BP3Mt55mrvIyghQAhV/ToihIpmQDEkABMoLBBZn/Y7KGjyq11JstepH2iCoVTIc7ZPfWnfJk2Ac9uif3dA2rNtuXld7XokdqmxSzxcxs/tLFDguUsTdKXqr80491zE/O7m9TDInPk9AhL+IDEqakAswRS31u/+V5PDQNsVLCzngJwZrmYq4NdVW/fIduyzq8ECDKmOshPKgQEJoCAyKndSaIyzQA7rX15nsUDtkyzP2Kc+OyTg1hizQgnT8TiDnMssCZysuzMHvxMo4XFiJZJVYiWSS8ALxF9zlVSTzrVchYy2F2DkgCgA9Fg9AeFNkgdW4CBngYDU1ear9b5la+PTQUxTfR0RwfV6dqyATeNQeAzZRbnKqz6dwTnQq2+/FqXJuUqve7JLyjWWI+W7SWDYmef6ZhKAhbtFlIcgXXAOfg+5hGVSNeIBCfT8lxsB60wLb9KlsQZFo5iJRG26xKomU3FmdI70PPd4PAEHJJDZI6qEB3bsEXCFJnDt0gSovRl9Q1Jvc9YmdunxhDetrTsy3HMLTTC8SWSbnrjrN1Wgq2v5AyRAnMssCQGKOfjN5c1KELEaEYFpRAQplWSfVV9O5dbX6+8DmiWYRtZuRATPsn5v1LxzFRSacaLuHPFtAqmLREB0G+JVUjiMWOI5O1be2h7NAGaBGCTmtUcCHo8vdDXZVVYVLFCsofb7rHOVXGDXq6+jY/shAaHa+gNjLhDBb6H1To/i7mUOfu+whGUiUKlwplTUNgnEnJy0aWppJREUu7Ztpz5WC/C0rW3uMO0C26TinUACvUACNFcJK5oovEICNnl4N0n2QzE72f0EWOdpOkRUhEDhK9on0N2lyQQlGocDL6RKLbk6j/XvAKfwHkhZHiUxhlYqfPNawjJU87A/4B6Z30Mgb3qoIYSngJ8j8o1bxPSUvx1C+CvAjwHfrJv+pXrdjjdVduZRkvAUzZLpJfHuCXQcrNoktXRAWuX0dtGiXZJ6XJDUWgGel3IXwOp8Xds30sAKIsgJ7KT+VrZfdjeBijsHzts2Z43KBZZq6t5YOVygGWCsPj1uTzejpBm0nIfCSATOAk+3+TkgigGqHwG/xyJq20Uo5wxQEgIsLL3To7h7eSs4XQJ/saqq3woh9IDnQwj/tN73t6qq+uve+M2WnTnJEp5iNnMb2XbZ6WTHE+vbIamlGzTVU4EjROBqk8BPuNSuv+t8Ajp5hQWUW1lfBVAuZCxQKWluq5PzomZ36wuRQU5r8glQztN0dAhcIIHKApGleaaFO1P0vUcTRBWLp4IFZGOUOONz76/HCR7ZZzFAjwfU8fKOvxK/ls8xl1pOWB37Nz3UurqCKizshRC+xJ0rKbypsjMnRcIHiGB1hQRsArNc5RRYuSNiSAI+gWNBBCmxNdkBsePE8rrWXm0ElPq8U5/jCk0nivrWOeUZjldGnPiyg0EERHcsFLAtkJC9cLdu76qunCBPEu2B50npmYoddAeGQMhVWVebl2/zGZoe33O2zcNr9EQ6R9O77AHbuslH9vmmHfcVYjbVXE4N+LmEEC4SI7I/C3wn8GdCCH8C+ByRHb7GPZSdCSF8DPgYwNNPP30/hnjfJXyEJjsrSMkKDmAjO0iqbJ8m44L0SwjgHLT0yrd3bZvCXNZJGqnGIBV4q37pHJtEP4XiA0d2DGQg6KomNKurSBw0OiRPqTy8Chz2vGEFD3vQtS7QWZ8HSeuYWZ4fOA5izjD1rr69PfZ9hQRyHVJMo2R/un+e7WFymsAvhNAFfgH481VV3Qwh/BTw14j04K8BfwP477mHsjN1aZtPAly+fPmhiKQK308EBoHELBELc7uaMzxoAoxUUamnbscr7Xh919we0nRi9OvXBnQ//E2Wzx5w8HqczN2z0Rs7uNFnpJCTZ0ns7wU7h9jnsetzgNPPWNH8Sd3hoGMKUt08MUBIpa3UTgzPWV4v68vDVpatvZ6f7qDw9pAAz+17Ov48x0FTYCx262E9Ei+19QqJ8Z5iCcBp8fbWJaZ/Afh7VVX9Y4Cqqq7a/p8Gfqn+usWbKDvzTkj4Z8QasQPSeq1yBqwSbVtSKd1x4B7aNk2Pq2x3Ahq3Be4QGVsOgEXWVqAksFQbnbsL/bMDLrLJ4tkxB3SY0KLFhJ3HVvjKRgfa9YHqzwFd/chp0mB+DnY58EECGBUN0EkkAkLP9tDgVc1FYS89Ijp7lRWJGJ3U5FdIZeYV1rJJYmkKi3FWCgmQdV4Bn0pxyenhgdM6P6Tiqbr2uZwatTeEEIC/A3ypqqq/adsfV7VV4I8CX6w/fxr4+yGEv0l0eLyXuyg78yDkT/K32eQiV7jE7ngFuuspFs7Z1YAEbu6hhRSTBxFMciCTc0FOCY+rlYqqRbWhqTpDU0WGJquUE2MEg9f77J5d4QLXGLPIIUv02GOJQ9affBWehOHrPcYbSxxtn4vnvmJj9LFJpR9BE+xy4KuIANAhAkce31eS4uY8XGS5vimd+iZ4rN8lUoEED5IWGLpKq373bJun3gnY3FEi1dlzja/TBFuF7ZSkyi6+rkcqAhHCr1BV38+plkBccuiEyFvB6e8Efhj4QgjhhXrbXwJ+KITwYeKM2AT+JLz5sjP3W/4qHwegpEWHA/boscv7GNBniTGTshXvikJQusT5KRbUJ4JbQWSAAqB+/R2S+isG5R5dSCDparJUWexdNr1ta0s9HjFQscJhPM/wi+/myxc7vNp/AoDlpX326dCi5AlepcMBvbN7DM72+UL7QwyLd1PfkPjaJDJUZ4O3C6oeigEGUvXkI46XfOqRnCRiWRdItjVnXpIVEqCpfJX6h6bNUeppmR37lfq7UvQOrL3q8+V2P3d8YMeo7Su23bNI8hqBp1BOC/OrquozzLbj/fIdjvkJ7rHszFuVH+anWWLMgD4tJixzkad4mR57TCgY0GePHmOW+NqNi4yunE9gBMlhIBXWPaSrJIB0lti143doOiYEKnnqmdifPg9tGxyPDYQIuEMiCPaJwDWCW8Oz3GifhVXYX73JZGXAEmOWOKRgUoPhhAtnr1GutxiV55O9T312aY7JZarN+s9f0XRMqMbH7Wr1uSwTwfAaiZE5UD1ZX9wBzarQrhbrc76eyB6p/FYeBiMW6kUNPIjaM0kEnhKvb1gwT3vj9IDfwyr/Gf+IPXpc4wJ9BuzzIfZZ5l0M6DNghV0KJvTYY5Nn2GeZRQ55efIUo83zySMK6e40UsBoqpvuGc1VWey4frY/VzG9X23zd2eTA/ssUJXtUGNZjd+PLp1jt2zR6e6zV/RYXDpksNtnbeUqV3fXOBr0ElALuPv1dzHc9fqcPv6hfS4hgZ0uXH4qLz2vGDnZBs+Rgqal3qpDgdpN0nq7YpR7dZ9av0NB05KClA63UW/b5Hgw8ywvr67BnS25SG1WCt854Eq9stu3zmh/iuS0ODzeafl3vI9dVtnkIi/yHJtc5BpPcZU1FhnzNS4CsMouLaKGPaE1ZYEASxwyocWl1hVevLTIcOvdiU05SDmYiQ05ALqHVMe2bZ+AxIEmt+t5Pq+AUNtLmg4IB02dU7m/rqJvw63yLMNuh4X+Hjd2+jBss7V5LjFZ2fV0TqnXyizJ7ZyODQ0piCAo4MsbCWjkFKnss2x0SqcLpCrKErE3ZX/oh9kgAuCmnTNXvS/SDIb2J4uzQY8D9NJdkBikt4HjwdunVObM7+2Tx/n3fIQXuMgmT/Aq7+E5dllllxUGU7oCYxandq41rtFnQIuSkha7rNBiwi4rvEZ/evyXx+9nuPnupq1ri8S45Kxw4INmNobARIABx0NW3AGiueugIhDKAUbg4xVdRJgcgPO8YEkZOKIHRRkdHEM7Vg4TP58KKeg6BOYDmtLAt2AbBRC5Kixg9KBlAZ/S6SRSZd2m0CM5H8QQlaN7kbTkpETtvo1or7tOtAVq4AIuqdKuqstZI3DOy2ppjFgfp1jm4Hd/5fltCL8APAvtjT4vP/bUVK29wnt4FwPGLLFHj5IWixwyoWCJMU/wKhe4Rq/2yk1oscw+k5qbv4sBB3S4wDV+b+kpbrjNDRKwQXJ0QLIBuoNinRjSvUEKa4Fk81Noifr3OD2Jz/E81s4BbjXeD14igajaCLQEVK7GjgJs1WloUp9L+4yNT+PIPda3ZX23E3le3Z4mBwg0V37ITcgGSMfU6xWaITeyyT1DKoDg6u2X6uMu1mP5OikVTyyyR2J3eWC1HCrKTimt/3PI8xvCZ6mqb7+XG/ToyCny9j4YCUzVxlF5ns1L0VAvucYar/IELUo2J3E91SdarzJmkRaTKQi2mLBXe+T266f1ImMucJUDOjzHi+xeXOWIcwmwpPbJWysVdVoHz8b5EhHwnM15YQF3HGhbbjt0kMH6EaBKBF6ejqa28lJ70QLJFdtXe4en5/OyV85qNW7t17Xn1z8FoooIFAZuxQKUAgqBlUJI5KnNpWpuL3VDZrWrU/CKTn2e8ySvbF54YcvO6w6PZWvvwCdWea4e8yv1OJ4kqdHLpPCXU+z1nTO/+yxniJO0D1w8YuXsLpe4wm/zET7z1Y8C0F59jaKYsHz2gPfzZXrs0WfAl3kfJS2ussYhiwD02GNIjyGwT4cVdmlRUjDhiZVX+fqVcxFsxOoU6CwnggONg1geFuKZEnJIwGzQy4sd5GElbsOTKur2QAF0lxQw7aDr5e2V3+vjcbAssn0NgLMxNpipMzCxNWNxJTQBztXbLIGnwfBc8hCXus3UFtnJzrNG8ly5aiqbodb5eMWOURpbQbI/rpEcL5CcHVes302axRhOqczB7/7Kt74bnpcK+cICXx88y9eHz8J6xbe858uscZVuDWiLHLLGVS5wjSXGtJgwoE+HfZ7iZQ5YZo8ee/R4mafYYZUWJUscsjfpcX17JQKIQE6xe56tAQlY/OUq7YimipgfJw+t7G1SgwWKqroip0hh7bdJoS46l4BP2/ycOlbXolAWbL+rtlJ/BXJq7+PUNUz7caBaaI5jKvLqZsCYg9xMlVr1A2dEVo20f9Y4Nmg6OQRoC9lrixRGs0wK1F4mqstu73PvsOyavoDTKQe/ubf3PssH4Uz/dW6NFmn39+g/NuASX506PiCqsnv06LHHIUsscshFvsZSDYiLHLLPMrusTtngN//d0wmk5KHVhBaACTTc0O/szJmfvuf7JQ4wAiQHIoGYxwsWJOYLTfXYvbACR/dCC7gh4sA6kaTovBt27UrZ07V4O09383Peyf53bHtOC1xNvlPaHHfevw5szzoGIhitEQHPy9lTj0Xs7wPAF0iqsTI9XD32WoUeuwjNEJpXTm+2x5z5vT1ya7TI+re8zNO8PAW9FXZZ4+rUnvcFPgTARTbp8xo9hvTYm9r8rnGBAe/id3l/9A53K/hiSMwmL/jpea8eg5eHvrizANvv6iM0QS0PocntdzrOFyOSt9nVaYk8zg5MA6J2tkla4EhqtoDVxy2gd9YLx1V6v5Y7fT4mDnbBtjHj8yxwzL9rjLNA048viQ4SOSq8UkyHFNIilnid42Anu945kuTVoA+IT5R8gfdTInOHx/2X6v0QXpnwLgY8zqtc4Cp9BnTY51WemNr09uhNAXGFXS7yNQomPM6rTCj4LN/Gv+APc5U1Xt19Aq6EpiFfYOexeJ7wD00gcG+ob/N2HsfnarHuvIBmx/aTtVEfUlvdqQIp/CUHXDFAAZ3H6vVJtkEfV+7w8GrSCvsZZGDjYH8M+HKVNAOp24Jl1j63BTaOsz69XR8YBFitnVhcIDJBxRf26mN3ibnEqslxhWbBVYGlr0anMBgHOqnAeRGFUyJz5vf2yEL7kBV26XDAEocxHY2LTCj4Ku+hxYTneLG2+V3lCb7Bah3K8Gv8EZ7nMl/m/WxykZd3n+JIQb6uRsop0CbVvfM0N7fleUyfT/q8BJU7O7B9aitgFKuT88JtdwpezpmjOx/cjqhzONCWpFhEtdP1KiRGXm1tk+zY5yFED252LXdke5l4ap/LHdXomqnNAtkc9D1MB0xVV+B0AcU5A/mV+qGg/GSVybodAxQYund3s35/EvgCIfwCVfVf3eZiHlGZg9/bI0efOcdnnv1ezvRf56m1l1lkTMGEl19/igtnr029vGOWOKDDl3kf/yv/AwUTrrLGLiu8yhPsvr4Sq5lAjJUrKs5097k1OBsf+FvEu7JKLL3q5amgObHEiJwZejyeT9IcIF3ddduc+hPAefkq9eMZGQ6Afu5ZHmMBqGdueEiOe32h6dHVcbNU+fxf9EaA6P2Wt/l8TBZSG7ex5gDvD4l8e5sYduP9YPvLuspN6QHLN0me6TVSYVYFZ68RnSJ7RGapNLxTyv5ODKKcpKG+AGzBrfWzfH312WlVk/bl61O736s8wcs8VZeneg8v8lzNFMfRyzvusT/sQLuC/pj1J19liUNaTDhcW+Tqap3r+lLNVj5MnPCbJEbmlZiddWm7OyB88jkTkXh8X676ql8BzqqNYZsUU5gDnRdSdRbUtv6xMWocDnwDa6c+nBVCE+D12YO5gWO2uFkpcgVQ3s7RkYmDlfpo2zZ/SOS/g9oX2Wd3ck3byi6oRZhS6arjNr0rUKdRpjxkH+Qpkrm39+2R6i9D+AtE0FknToRLcDha4tXHnmCnrqR7yBITWuyNe9zY6dPt7zEeLbHUHrM/7NAqJiyuvkb/sQGr7HKhtvNMKFhe2Wew8i6+OXw63pkrJBbozghnanB87Q1XeT1ERpOta8d4ILTn80ICFIGM6gRu1PtzEPAx3c4G6OBHNr6Rvfw61b+Odw+w9y12rFjEdmheh0uDpYXZKq8DN7f5nAeI+z86LxPmdlv/nRS0rWOG50jLaG6SmJwCoAWOBanyi7JHlone4986fdke91HtfRCrQ54Y8ANS+IVlPdwadni5LhB9a/tsaluD0XDYZmE1PqkvrEWgW+KQDvv164AVdvgGsf7dZNJq2s7WSY6BHPA8xk4TUNkXapNPOqWbKbTGWZnYnJe9WifZ50rSGh3a7yuxuVrtQOdAnNvC3MbnLAg7j/9LGsHNNAGrINrmBguJper6vQqMH6vxqD4hM9q1b7PNt8+yIcoem99//VZ+H/OxDAOMLpCyOyA6RBTkrAv4FpLqu0tzrZNTpvreX29vydu8OuTJAj/9QT2b4UrgVnk2qWb6U0sF7R/R6e7TXxrwBDHtrcMBhyxygWvT7I4eexRMuL51ITGY7yDFuqlvz5boEh/2Ah6pvu4oydmZV4xxh4jb/kqa2Rx9a68+10nFVJ1ZCcBmVYXxl7ZjY92248QIdc0agzt8xJrcnlgspKwSv/48v1lgp+ty4PN7Ak1wa2ffc/XVzQ0ObIUdq3ul/fqNczPBKJBygVWw9DoREK8Rwe0aMUtEcYQdIgiewjU97iPzexCrQ54o8Kv+HwjfS5pwL9Gsciyw0PcutPt7XFiS93eHcf1oUqUXoA6A7lDSgmIC/QXowpnV1+P+9pjD0RK3fulssv+5vc+zInJWoQmZMxxnG7mq26epzuUqpqrM+ET2fe7A0JhchXYbnpwv29YO69vHk6umAjSvYqP+c8DK7ZN6UKg/VYV277c/OHJpz9jmWTF6gEDz/jvw+1j8HPrcB0YL9XWo6ozCXc6RSmh9gGQH1HuMHQzh81TV758x2EdQ3iZv7/1eHVJyosAPSJNoi+aku0xyBPTrff0jltqxysuEFn0GLLPPAR2ucIkee3TYZ5dVBvQ5ZInz67u0WhMmkxZPtF5lUleKebF8jlvPEm+3gFYZD17qqrDtuR1PYDC1idF0POSqqtiXVF93hLSzPnXsIPvesK3RBEQBYJ/jIOp2QGdDDmq5yiyg9HuROxkgsVawh4fF72l8OVv1e5kDmrcha+fjcEAVuDszzlVsf4gVAUoVYD0gMr49Um6wwmJUg9CXzzwFcm/gtxpC+Jx9/2S9amOzy7dhdUjJyQM/xaRB00a1SpxUfeDSEd3VAcPtVW5srcEGLC2Nucoaa1zlKmvs0WWXFXrs1eXtY/7vpNWKJbBa0GLCVS4A0F8Z8M3huQRYmrya8A4A7j3MbWgeMuMAgx2jvlxVzg35kJiN2/jysJhZTMdZmnuZIZKZdRI4ulqtcTqgOPBorLofajuyY7s0gW9E1Cy92ALZ51lAmqu8zvj0PVe13UPt91MAvE7z99H/zMOZhkAZrFGPZn3AZSL7U0GEUyZ37+3dqarq8p0avN2rQ74l8AshbBIffROgrKrqcgjhPPAp4l96E/jjNS29Z2/MTPHcW33eoGkH3FiIFZm3Yrsb5TqTjRb7Zzu8yuPs0WPAu+rdamrfAAAgAElEQVQSB3t13b+rU+fHHj0O6PDbfJiXeZoWJd98/ul4NZskp4YmrGxlzgJnie52Hj8HaUKO7LvbEKGpVrsRPwc+V5XFIPOJP4tRFrbN7WFbHHccOIt0RuUqNsxmWtAEzsGM49TGvbVuw/PrcXWeGe85Sy6sH//d3ESh4we2zQEZaJbBUojLWv35FVQMIYTnT0d5+/vr7X3bV4e8H0P9D6uq8hyATwC/UVXVT4YQPlF///ib8cbMkuorEN5LYhf603eJt0EOhcymNCxWGRZ9vs57OL9xjcXWmD6vsVKHu6hQwlO8zD7LPM9lXuUJfpcxW//yvbHvzXoQAia9Q7OCci4FUFa1AT2TBtvZZxpQ60DpKquznFkT2ENVcrYk5jKwY/J2OaNSW/UhD66Pv7D+8n25h1Wg5c4cNwfkarqrsrNUUs92ycHOVXH/r+SOFDFoAbGu18fnD6YhpBXnIAGf8n9Vmn/f2pwS8Lt/3t63fXXIt0Pt/QHgu+vPP0tc/vvjvAlvzB1FE0ss5XMkVU2eSdmy2kRVpViAdbherPCBJ1+kx3C6nOMmF3mOF3mVx5lQsMnFWClm3EuTQwCnyZTb7UZeBJOmre8Nk/ZVKPO98XPRScdq8uXZHpqcWzRVX6lvOQjos9o6e1J/7qQRkDr7+iDp4eJ954DmDx8HMgdAaLI5Xa+bNWaBoO/zB4I7KsqsXZ/j43Bm7UB5O/OBA+3ofL1xt37XGsSQUt68TuApkPvr7f0Mb/PqkG91qBXw/4YQKuD/rA2Wa6KlVVV9I4RwoW57196YEMLHgI8BPP3008cbbNL88+c2sj5NlQ5SCacB0F/iNfq8jy/TYkKHffZZ5tf5KB32pyXxB7yLlaVdbhTrCWQV9qIJ42o4RVMFvJ0UgeNZDR0i8NVVSHKWp+v0gFx3qmi/gEeMy39h3a8GKNNU6XIG6GEhik/0McFx1uXqo+ccqw9J7p3t0gQujSc/F9l+ebgdHHMVPbcFCvigWaTWVVwPW3IP8oD4MC0vkFTfXSLofcFOpFXqIIRPUVX/NY+0nLLc3u+squrVGuD+aQjhpTu0vWtvTA2inwS4fPnysTbVEQT1pj+5l6PSH1YguElTNdoOHGx02F1aZULBC3wYiFWeFznkCaKXt0UZ1/sY1v1coeno8Ak5AjgghkSYTI3uxvbaIQbROlAW1Gqv1rigBkiiupwb+zWJBfIqWuCgN8spAM1zuqouAGnbZ+/T4/IchNz2pmt2IHFnRw5ErnrmwOsODLFed77koJs/eNSf2wTb9nJ11vvKzQb+H9OxKvAwDYA+ID7APkoqdb9BSnnL1yp+BOU0pbdVVfVq/X4thPCLRDX2qoySIYTHiVGg8Ca8MW9wdsBKUkH9x68isEgt9cnXrkdxGW5srvPCRovF9iGDnX4MhD474Dle5CoX6kWS1tib9NIE0iTYwby4FYz0lF9O24T1uaeWkGLabufdFGto2/Vp8uuaxEoUrqJJCU1gyg34/u7sUuMU6OWMMffaOnDo/Llt0o938PYgbPdY5+fKnSB+jR7DlzsqHNic7eUOGlf/XXQetfdYzoJmJZ+p+q7wll3gQ6RnvQodnALV97QwvxDCWeBMHX19FviPgf+J6HX5EeAn6/d/Uh9yz96YNxYnhVoZDCgLIsiQ1DRX4V6K24Zb755O9GFxlmG/z/K37NNjjwM6vEafSdmC1RHstNPiQB6+4ucuMkA7tmyjthXMLsDZIRrMLwD7NajWNefkzd6gCahuOxNwaHJ6bqvnEJc085N1f9zAP6KpguZqoQOOe6FXbV/OlHKAdiY2snZdmkt0uufYj89VdVePfay5hzyXWYx6RDMLReYUfxhB82EwAEYrTaAvgFEEvrAM1aMc/RK4/T1+COWt4PQa8IvRI00B/P2qqn41hPBvgZ8PIfwo8HvAH4M35425s+ifL4Dx6hv6h12IKuOQ+LpC0+YkQJFjZLjAtfU1rtVBqpOyRauY8NjqgBvr6ynExVVYIKmp6ligttDcXgClVjZz4BMg1mOezsKsfPpwIU0sTynzc8jm50xHYOUscd22Czj7dgw0Q2NK6xeadjZ3JMxSZXPnS85M1Z/b/rTPP88KZ1F7sVBnc87uZqnZDowykzj4+8MgH8cGTe+v7s+qtfeHlP9Wj6qcFrW3qqp/D/wHM7bvAt9zm2PuyRtz5/MvEMIWCSC03qpkAfgSjJ5LqqJYk4BCwCeVsgs3WM9UsIr26mvRbjg99g4L8KQR2j6pudqmAajyr5Z0VDaAfpbrNJZCdPuU56JeJFV5FpPaprn2ByS1/yJpcruX1FVFD4XRsTlTdBXSnRvORuF4RotAp0sTvByU8/OJiQoA1Z+rtrmzROPW+Fwd9n0aY5G9nLG6ecWBLVfvcxuns9pHXU6L2vtwiBedPEczor5gur7C6Fxz0mnCbZNUrFz9uli/DwOjK+djKM0mFscnxumrkklmLcrjaU6qDKJ3rxyitSB6sfy6xrx9FJteIQLERv3aJC1XuU5S1dyb6UHLcpC4l9pj7bBjfHLn8YPY/RJY5tks/tkBdYPjwJc7I3LW5OcfcRwc9e5A7mxzVqaKq9U5o/b/iQc6694IvG8XLqMx+P0dQngcpiG6j6KcIEQ5QUOdJVeJ1TN0GUekUuQlCXDqXMxBJ7EQ2QIHpPSuSyRwgVjp+QrRRvgCif01FuYO2WdoAp8DY22LpCCq6AdEY3hJs1RSL37fqY3kG/Vmd9xsksLKLpEm4Udtn9idGJ5UMzfW691VRj+PA5xeuZ2R+jh5nHPwcseGq5Ye/Ox5uzlTcnXVAc0dF86E29nnXK3Pc4QdZPP+IQHmer1tx453QJcZxcFYdmKda5VHV06L2vswSFU9RwhfIa2zukDMs1wjLTgDUX2sZ/yg3naFpN7qDymboJiRwma2aIIIEIOSl2ku0OMA6M4OhbA4IKsYpqvrKotUt1vtxPNvAx9eiAAs8Ni0GyGQXgd+lSa4uSrmE97VvR07xtu4MT9XKz28xcEJ+y4wGdCsrVdk75LiDv3koDZLNc3V2bw91m6Wepx7g32bn8edHTnA+vldPXev9aMqc7X3QYvUXc1QLSWopQrFrjr1PgMohaxs113JJujG84IIflvYQj5u59MsqmdMe6Ge8GKD2H5PhxLgyWbp7LVuL8bhzoY+EQTZh24nqbkXiSHk312PMw941lDVn2xcWyRboQOGq3rO+HIw8cuH2c4Mz5oQO1ynCX45YJbZe26r1Tlzlti3tnnMY84MJbPA3R1Gt7vO/KHi4OZs19lmCeEPQ/UvePRkvnTlgxYVjvTFpReIbKskxd7tM621NjxntpojGNXqqP6gsqFJTbpCzbQq61/R+5pZvbhtHWNlUomhmQu6TAyEvWjjl+NDC+PQtC1tAuVRBNcNYL0T840dlLqkklsOVND8pbtZ384Qc/XSHQazQI/smIImC3KgEnjfDuRud17/7kwLEnDLueNMze+NxuJsza/F4/hydun3zD3ebiv1ECgHTY+ZdLvzoyhz5vdgJaq+L9bfDmiusXCeBDorRKA5F0GEAnYCqfjkucjWClJIi9K55BgZlta3gG2fqS1vag9zJ4fUX7FT6vZKgl+r38+TgLJut7ML3RXLSz2CnYXk2PhBkj1QntkvktRYMdUtmuErmtR9a6P9Hr+Xv3K73+3CT/KJLzCa5VF19VvHexqfg5g7EnIboYf4+HhmgZhfi/pYpwnoDo4SV10FeM4i/YEj27If68D6KMoc/N4JKUlAp0tS+Iv+bUfWViXJt2h4XIfPpKe37H1SiYeQQlPKdFzRadqotqXqCvg6JLveMmkRHDHSqxwP1zHP8fAmDA+I8X+1jXE7pPxmTdoNkhPHVUQPdC7tmE2aHl1N9h2aE98BRWDnzMYDfx3bBV6QGF/ufXUW5MAjIBQ7zwOqS5qFbB3Q3Jub2+0Uh5dLDpC519rVYmdxfqwzTg/DcaZJ2hY+AtVvzxjLSZY5+L0TIvbm4SRSS5etjdROrcUA8RYo93KX6cLUA9kNpZaqbwdA1Wxw1VVOFkiFLcX+rtOM7RPQOUOEyCYhqcAXmKrlmphbdpoPklicA5d7cTUpL5KYbJdmLKCKP2zTVB0FLHm4kI7JHQoOgmJ8DlBkbXy8PlZli3gQs7OtNseByz2reneglog9Km/bw4E8dMXHq+2380b7NeahOa7CO1t+1GTu7X2wUlXfVXt9xeik/urfKxCUc2Sz/izQExCpFtuTRHW0JDE3sTjqYz17Q0sb6nYKzJgxhgOSU0ZqM/X+NZq2SoF1OP5LaeJukticT1aF7+Ss7AopO0GhGQLNkuZylbmNzY34UrN9XB4b58Hjbs9z9bqb7XPV0zMq4JjToMGmXKX0sed2Sd0HsUkB1iDrS+f3bboHbvNzoPfr9OwXZ4hi5G6zfJRkzvzeKVF1a4W1KPxFa7CKcUGytwn4IAFnCRxFT+rwJvTXYLBFAs46lvBZYvxfYz1XMb0eTQCUQ+Q6zawNxSVuEYHPGaHU8XNxf7nRVMcERlL9ZoVw+MR0sBLzk5rrxznAaCLnQcHqM2deUlOVl+tqrzPGWewqByxnbm7vg2YFG2/rYOZMzMcwnLHPbaD5PfT9noI369466ytoxldqrBZnGXpQ+XroJ13m3t53SgRi10lMTIDiAcVihsoOkTosx0MNfsOaGQ7E+iCxtiN4SYAnBieWpgwNB9tzNFf1ukis+yaA1LnVjxa+WSZStSeJACnvBk1j+iZxcilTxdPSfDLOAsZ1mnF+PtE9rtG9rcOsD/WtbBkHqtym6EA6yz6Xq9caY86UCpp1+DwNLbcpzvLe5mq6s9bcmeM2XQdqAVxBExRHpLhQv56B9eOB0o+KzJnfOy25Z9XBTpcrFndA8rQKvIr68xUSCHlfCpmRbW+flGUiQL1e75fNzsNcSlKIjMTtj9B0fFyyY2vvcBticYVabR92mtVHGsHYmeS2sZzVuXqn7w6Mq6S0Op/ss8DFHQRuF8u33a6Ngw7ZNjkwJJ4u56qr2wgLO05hPnn/fv2rtt/tdB64DMfjKd3rK2Y9pAmCI0gFMh4ROWHgd+adHsD9kuMLxPgjWiCkxaVv0vTAliTAvIYWnkkMrle311KFEtnnbtqxEJmc7IIdO6dAbJfkkBEQrtT7pbZ7/84GK2NBYrtHKQZti+OgoXcxMgfA3K7ljCtnSSPSAk65ytgmgYXb41xyJuWq7Sz7Yb5NdsTcHqhz5mzPr8X7k7PH1V31704PAVZuN3VW6OE3OSDLDODq7zZWD/IRo34Cv7t5PQTykAzjfskyETwKmmonNJlXj5QB4ja/wr6LAb5Sf++RgFLqrkDpFaJHdo9kz3umnuj7JLXZvb7nrU+IqPIkx50dmiDXaYbI+MphtfNliyZg+We3U7mHNlcZPStB786cfJu2D4mavBwdzhpzBwF2HDRtb1hbL8zgTgJ/d3XaM1LE6oqsrYsDmAOnPzPzECHsnLPUcDFPB7stEoD6QwOABULYp6oenUKn1dzb+07JHk1rPSTm5CEl1+u2nlcrsJOaDMn5AE1VWu9ux1MfMPXilnscD5FZJuqNV+v2Yn7Ltt8/a1wlaS1Y5Qlrv/KMw3G7FTQ9j+s0VzLzGnTQVO8EJkqVE5g4MF7keM6qwCdnkQ4mztpyL66AT989gFkA7eyutD761r8XV8jj7gR4Go/ayGwATfDWWPK2DvRwHCh1v47Z+LLFrh4Bqc7A4SzG/5DKI6P2AlTVt5NYk2L2xJbcaeGqqgDxnB0rdRTbL1bp8l6Sw0JOE+p3Bz6p21KF10h2Ro9NlB1Ri157uh40GaTY5x5RtTbbkauXjte5oV3bNMEFip6m5cwNmoAhoJAaKVByzyoc90Tn7NTZGsy2FTpw5deVM0pnfM4CPeDbz+9jKu04BzJniroPHss3i2nr+j3ovHHyo1k7TqxUAcrWmbt6PQzyiDE/SKqs4vigUQ15qqrOUj3P2WfZ7/zP2atfAsatuv2X6mPFzhZISxpCYnJicfJGu5cYUhiMbH9S36k/eyiM1Pd6vya5F2Zwe5za5B5RMSkPdp5ls8uBTufZqPfLI+sg4GwImuCQM0xnUX5+B0d3hnjdPWeHrsLq3GKGbufTeUe2HRJoeq5wrj7rQQHJ4+xeZ+/bnUMz5YAQdqmqZ27X4MRIFQKT4m4h5fBtHcvdyCMIfnJm+HeIIPEkUU+DCGAXgGfiH3gHIiBtEsHnW0gAKOYo0BGICcgUqiL1VWqoGOYCCYzVVh7hXP2R7VGzRfvXSBkiUuHr6+rSDHKe5cjIswoENvJkerCvA93tVEd3cChQ2r/nDhFt9zAWAW+uuub2N53bCxNI8s/O8Bwcva36d0Bzb3BJ03mT2/l0T7wcmNr4fYUYC+o/J5D+H043Hw2ZtE6O0e+RA7+q+jFC+OkZewRs14kgWKurbiRvL8DgvcDXgGdICwq5unmTpLJ+gPgnlkd3jemCQ1wkgqfn7FrFlkYYDDTVanmbNSkEuqpTWH/uLsTTKL4PYoSOsxVneM5MZB+TcV5qKxxXM50F6vh1kvfV1Ve9O0jlqqjATgCTq5i5M0TnH2T9eBuBkc6tfQJbffZ3iWNPDnQOXHkMYR7vKIDephluNHWO6EEmT/8BEQivEk0oJ1sqQlzq9YTIm1a+QwjvDyG8YK+bIYQ/H0L4KyGEV2z7f2LH/HgI4UoI4cshhD9yfy7hXsVDTv4NjG7CYDeFUKxC/HO+SKq64jUDpfKeB5ah34nZHvqO+gikhayl3srZctXG4Q4VsUjPSXZ7pVL4atX3EvBhYg2/S/V5V2mWkRJ4OBPzWDYHnDzgWJ9nMTBXFbXdmVPb+mhznGE5o3LnQ+4g8bF7W7ctapyyWWLbXFX2+wLH1fRZLFJ2PQFovk3jkvorYHYVe9qvRyDIjvzopHhUBEpad/V6GORNM7+qqr5MnHqEEFpEq/4vAv8d8Leqqvrr3j6E8ByxCNPvIy5d+eshhPe9tRXc3oyobPwy8HkiQG3AtrysIX6fVnyBZGe7SWSNFqT8XXWTnY3EBMRABgqnUbqb8oZfIamubseTKixHh+cH79r28wmsvSiB2IZY0BapNL+rg3JiyLbl6io01UF5gvM4uRwQHbQcTBy8nPnl4OmqYx6c7ExPwO0sMwfa3Mao7R6DJ+br32dpn/n5/Lv3uWX7Na5u3k7lz/QAk1PsOiEcUVUn2/tbETg8Qflt98vt8j3AV6uq+vod2vwA8A+rqhpXVfU14pT8tvt0/nsQBTbv1t9fAT7L1NbXplbpNoiqiFiaPMJyXOzFY35pP06Gy0SgEQvYgCbo6bvn/soDLPuPALEgMYPzJOYpBnnQDKH4Yj38TY7b8TZJk9Fzbt3Yr8mZq4seyJuzPQccz6pwRqR23RnvzvwE4vIWO0irD0hAnB+b5x/fjs217eXAlLNOB2t91r3L86vbNIE6Z5uuzgPxNxQA3iSaRp6E4mQDHyS1925eD4PcL/D7QeAf2Pc/E0L4fAjh74YQ3lVvexJ42dps1duOSQjhYyGEz4UQPvfNb37zngdTVT92h71HRO9s/pj/l8CvpLzMqZr0IRIIOUuTV/ZL8OtVbC/V0wN9GyqsxL3OmiH+2eMVVQwhi0ccVbGc/b8mGtW3SeXr89Q17Lurwe45lUh9hOaEdntX7pDI6wLOUnNnscVZzHDEcfAQS3OAxo5zFncMbGgWLMhByZllbnf08B2da922FaR77n8nNyk4uE7tfPp9ZePdgDaEk+/wvW/gF0J4KoTw/4UQvhRC+J0Qwp+rt58PIfzTEMJX6vd32TH3ZFZ7y+AXQlgE/nPgH9Wbfgp4D1El/gbwN9R0xuHVjG1UVfXJqqouV1V1+d3vfvdbHaLJBimExAOKvSjCz0Q74Dr18pWB6PzYIDklzO4mAPylKgX9DqiLoL7C8Vg9hb2cJ04CbRc7lDPGbX1imwrOridQWcVzbRIfJTK0Y+9ur7KKIlPQ0vdZ6mLOmpxd5Wpubmcke3ew9MKmcNxp4WErsmEKoFdpntPPkavmuWrtgdPeNmeKOrZtx7nDaIfmgyS3FULz/ugapw/MgqRRKNyKJoCeQLnPNr8S+ItVVX0A+A7gT9ems08Av1FV1XuB36i/52a17wP+j9ocd1u5H8zv+4HfqqrqKkBVVVerqppUVXUL+GmSarsFPGXHbQCv3ofz36VskGrn3UmOgE/BFz8fb/8G9R/3HMm7WxDVFY8j/FJcP8PDNziiWb5KT3uBqGeTaJaoNBekmeolriBOmroCzaBqMiKBj0BE9jwvCiqjvSa4sx5XZ6HJ0Nz2tpod4ypkzizVp1ix9082FmiCigcrax80Pdi5rW31qLl9Cuo1Q29XzYDpIvvcPUrgltsXdU9yNVefd2yc3gdwPHunJIZbLTSdTCdUotpb3NXrDfuqqm9UVfVb9ec9orr2JNF89rN1s58F/ov68z2b1e4H+P0QpvKGEB63fX+UaJEC+DTwgyGEpRDCM0SD2r+5D+efKcdVXzGnu5XPwkufTtWNN4DuCvAHSHYbSR26sPO1CIC/SmRl02BlMTkBWWnHQVq/Y1ZJKzFVzzlWmIxUYusyzzpoEwFng+Yk9YkmliiwURsPB2kTubzY2O08pbPCP7TPvcl5kLO3nWVjzNXRnOFhn4cLGZOrmscPQg2QVRP0+kRgHCw0s03Uj98/NwfkNkLdRx2n36Ch/DxJyjFPgerhD3JiJTo8Fu/qdS8SQrgIfIRonF+rqrjse/2ucup3bVaTvCXwCyF0gO8F/rFt/l9CCF8IIXwe+A+Bv1AP9HeAnyfGkPwq8KcfnKf3Is3A57uVq7D904lJrQPdDskGKFHfr8BoNx7HJsm5InGHBiS11lnggrWFlPJWkgBSlV88NY7ZKqcAxQNzfRLrOAemLilzo020ZaqP3NvprAt79/g/H48Dq7M93+dsKVe9NfZZdsVcfW9X0D6C9rhuU8Zt/czaUlIDYQVlSGA59XgfHWfNDsL+wIDmw2Sn7mcA0cqjmM8v1AfU/50RkaucYNW3gntRe1dl169fH5vVZwihC/wC8OerqrrTJL5rs5rkjfnnHaSqKhWz820/fIf2PwH8xFs555uTm9wb68vkyk9B8aeSQ2OoKtFygkAKYH2FZkFTMbiSxOqgGfAM0ZZ3nviTKARCM8HrCuonkzpta4bkBnb3mCqLxSex57qKnXhgswz5uTrqxRCcSRYcZ51q7wUHZtkPc9VTxzgo52PBvvePIuMr689lEcFO0q3vdXsMpZmCiooz6/u0iglHgx50RzBoR5AsQw1s9YMozwjx+6fvaoftn2bLlBxfrOomMah+DYpOk22fOAl3pdLWslNV1eU79hbCAhH4/l5VVSJYV0MIj1dV9Y1ay1Qa1j2b1R6ODOO3SZLqe3DHdm8sBZQ/Hb2q0wrK76UZiOzFB9wzC4mlncu+ixkWpKBnvWQPKkgguWztr9MI2FaA8wbJI+kA5EzF31dJITqQnAujup91mjbEaSA4TfudPJwODrpUt/2Nsn0FxwFN55b9zFmeOzoa/S5EgCtIwFdMONM+jGBXTOJ2iNuKCbTHLPT3IvANl+Mxw3Zt8wuRCXarmkHSfFA4wDnw6eGTe9oH0Ax29wfZOaAzde6E29KHh1vuZ6hLCCEAfwf4UlVVf9N2fRr4kfrzjwD/xLbfk1ntLTG/kyNvFfwkvwmD7wZW0iTjSZJdTrNXQOdA6Pm4/l0xfF5NWuxAzhGfKAJGK6PlsXLOyKRyzbIBehn8AWlVN7cRulNgloOgYVezz/7KPaJl1ocDCrZPzqYBCVQdaKfAV0XQGsludsSZ9iG3ds5Cf0KrmNDqHjApW9wyJrjQPmSpPWZ/2OGWM8H2UQTGYqEGw4Wm2UBjFKMVyOXjz+2DUzB8kmgScQ1gJQbLb1jfJ1TuYwzfdwI/DHwhhPBCve0vAT8J/HwI4UeB3wP+GESzWghBZrWSuzCrnQLw26C5zuNbka8QPb7nob0AwzXgt+p97gQpSeCX32KxPrE4V4XVh0DPnSNeT9AZQ715QLzUZ0nBz22aq7h1iSzvJZphH7M8pw5aztD0XezL82lzB4X37V5bZ6Su/noMXt/O4zY0t+lBzdAWEmgJyNrJ3HM0WqTd3WdSTKbfF9tjAFrFhFtli4VufPAcDZdZaB9yNLJMBc8J9sBmB7bc+6vr2MHiKCvoBhg+A3y9brAcHzzK0JFp5QTK/cztrarqM9y+xv/33OaYezKrPdJqL0BVff997vHTNMHUnRQ+K1W7T2ps7vjwuoJq41kfKnsFTL3EUoEFigfptI6LfeKEkvqr7xB9726zEqtxxuFpX67aurMiD+PoWhsnwdrn3lMBpHuGxaTcW6oXNJ8hU/CspiB3pn3IQvsQyoJbZYsz3X3O1GC30D5kUhYs1YDX7ceYyv1hp/H9aLQIxYSj0SJnuvuRTTrgesktt0Fq7Dn4a9/UjhdguEtMqxTLvxmJoBhufb3hHbCMv1WpCIxZuqvXwyCngPm9HfJ1GB6QAp9zG05Byg2W2qp8YNUD9JXdHBw1Ka7RLGdfkIBR32EaHD3spDU8NoiTboPmL5xPXtn38oBjSIuGe9UX7ffv0FStcxXXh+pqcM40ByT7ojMs9e9t5ZUtytq+V3FrcJZb7SMW+nscjRbpdFNM5/6wQ6+/x6RssdQeU5YtltqxntykLNgfdlhsj2l1S0bDDmeKCbdGi/E8wxAdKAp/8fxpbFz+QMizU6YqrwrsvlI3uET8jSvYCZG1iy2eQPZ30qq6zMHvTclXSGv/QpoN52jSHtn1vACqgK6XfZc6XNJUoXNQpN4nYLSfUJklBZHpFaQio1J/BTxdIlBezLpVd7K3eSqXX2pJqmQCTYDyGn0uOSPUNmh6dp1p5nm3RdV8l/e2gIXatrfQPmQ8WqJV2/ik4i62DwcbVAcAACAASURBVGm1Jhy8vkyrmLDUPmQ8gkmtKhfFJKrHZcEt9c1SBFgHMd3HLul+u+xY22ltvzzI/VuIv3MdLLGR9e0phydEThr4PfJqL7xRru+bFS+a6g4Od3oI2FTXj2yfVFf35nrZfIkqw3j+ryZRfQ4HFqWEXSSBm4DM1VYxLmeCEtnd3H4lIHR7nXtcfeIWM75j7/lxHvSsdl58oK2A5JBY37RtVG8nZYtOd59WUU5V3KJINu+JOTb2h8uMR4t0ugf0VwdMak/wpCyi+lsWyX44DMfZqx4CXlVHjDVnfkCq3aeUtmegWIug92FSTUbrO/yPnDg5FSWt5qK4wUukfFtI/3ZXd33lN0jpTbm6W5BKbkGy73kZLg+rMTVYYJFXK1Ge7BaRpQjA3NsrcdByFgizVVsHxsLa+TZ3Xvgxmug5YLhX18ck9ROmYAdM7XqL3X2KYjJVafeHnQh27TFL7UMWl6Kae/D6MovtQ5ZbEw7Hi+wPl+l0D2gVJRfOXuPV8nEmRYtb7XF0fAx6zev0QG9oApx7dwtrM32YaS2X70oPJ1ftV62vPFTmBEh1b3F+77icCub39shN4CIUz8R3eqQgZY//g+aMyNXXvJSRt/M0N2d8HhJzDtoh2fmGpAIHegkI5f2VBzL33jqo5epsHpKSe211OboEbwNNJ4qzxnyfROtiTOMPjwfrC/hulS0Oa+9sUUzYG/To9fdYbI/pPzZgeWmfVj2o7tk9Wq0Jk0lSdfU+mPQpigkX1q5ypnZ80B43PMcNkHcV3u+RzA/Ta7xJWh7hD8S2G6QHkedgi217POUJkdNa0uqhl/uv+taVm1chMTKP25M662s1aNEkpcZ5rJfW/RUayVvs6q/sisoFJr5/kFTOXgbzK0TP7mdI9j7h6jZNPFbX/tB2e57b8vJ20OzDgc8/i8F5WI07NbxCi4KKG6A7rlXRgjO1bW6xPaZVTDhTTOj199gfdihr1XexFRnfwbjDEoccTpaYTFocTpKncXHpkFYxYX+4TFm22B/Gez18vcfK2m7tPbaJesyBkd1DsVj3nA/zWM/l42aDdbsHfr+Lk+X1jd7exbt6PQxycjjqQyf1mh0lNFdhk/2uR3NmuF1Q9j4VMl2pMxqegXKLpr1Qk0dsUGyxnkgFUaUVqAhkrpCM8drnK45BE5wEVgK9ksQmscvIvbw5eOYODTfee8WXSyO40k5VcNzZsR1q5nMUw03aR9EDWwYWVm9OHRkQ7XitYsJ4tEivv8feoFc7KmIMX6e1zz4xpKXViiyvRTllH9FOGH+7w1GalIPdPkvtMZ3uPje2V1Kws0DLF3tyhupAOIT4O201d+oh0s8+51kk/n4C5KSpvSdnpA+dHACfgp1lYqUX2d+OSMxORQh6JNZ2RESVg7i924kG78tEwPqlDaKDo0M0kAv4FNvnlV96iY28xPEJKMbnAOWMw9s7aImJ9G27QJRsn1TkY17Z7FwNGxgR+HbqczkTalewWi++Xqa/563RInRHHI0WaXVLDkdLtIoJi+0xh6OlqYOj092ne3aPggmd1oC92qveak2mqu+EgknZmgLn/nCZpfYhy2cPGOz2p/3c2OnDaCk6V7pVDKhWsdiC5CyCZgXtRqEDpb/XJchYSMCo6jju3YamQ+qE2f4eFpX2bmQOfm9JpN6651Ugp7ptT9JMYYO0khtRXf0gyS63CuyINUJz5TgvvFofn69DIfVRqpcmqLIHXOX0yiqagOskL7FshwK3fKKqDxUYFRhq0uaZD9rm9kbPlugSPbntmvEVZQ2uC9AfTYFIsXgOYOPREp3uPv2zA/bGPZaX9tmf9FlsjSmYsD/pQIupra9VTFhsjRm+3qPTPWB/uDyNAxSTRFkeO5H1tS9eZ7RzPgG+s1XFTHqu70g3SpV+ak/+cCHeZy+EoHxm93rXBRHC34DqL/LQy0kLdTlV4Hf7ZS3fqmg93rrUVXulDhA+Z46Dc83iAJosXaItXCWNdqCZzysvr2d/2M/mHlhIFUQENusktXib5AF2NqjQGJ/E8j5u02Rz7tSYpT7n6Wq6DKmJkFjjRnYfpm0XItMaLEQPb38U1d46h3dhNaqpCl2ZlC0W2+OpR7e3tEdJi05rn5IWY5amTg5/H77eo3t2j8PJEkvtaP/bG/SihxdAHt9R/B1HnzvfzIvWQ2OTJnNusDX9blrqtG67XV/3RVJAuUwWzgz9/j7kMge/UylWVorzkcn9p8Q/8AukVdTk1ROb0h9+hzgZZKMrA81AZ0+hK4CQGFUebpJ7IQVeeXEAfV4lAuRGffzFerxS7zZIjGaW+uz/oFw19v397Lhu1saxXp7dbhVV39ECt4q6gIHJrbJFuw5xWT57wOF4kUnRotfaY5l9dierLLbGtY2vmNr8FltjOhxwwHLDAXI4Woz2xDpL5LHVATc219P9EuNzlXW7PtjvkYLKp3F9vla0VW1WYLP/lhJ/CJ2QMldKbzspcmq8vW+veGDyXgSPnyEC3/cRgbAxuUl2MJWiEnB4WEoR4osOEfw6Maylbe09XCT3sGL96TzqW+rthr1/0I4Xe5FneIPEJvO4PndkYNvhuM1RaqHuA/W2dl1MtK7Scqa7H8GuPYbuKObqtg+hlJOjhLJFUcTKLYfjRVq1KjxmkQHTdW04nCxNw2vl8d2jZn2jRTqtlArXe2zIpGxxpphER4czVz2oILF1fzjkIUYskNIUP8D0geYPG2kCud3PvetDCH+Vh15OWqjLqWN+b4/qK+9r7dEdkkrfa7JI7bxIAgTlskrtyR0Kstl5DTypXGIPCiKGJuB5IVJXjd2ruM4UBNsfvM7qY7vs3FhhdOV8muhSUaU6u71O4swt3+6fPazFbX9dYBQ4s/o6twZnoSCVmarfb+lzP6LypCw4v3FtavOT9Jb2aDFhv3ZqHNYqb489DuhAaxwdHpMWrVbJYvuQwev9aXD04TjmBZdli9HO2SkYw0KqlCNTRr++L/p9+qTFpKiIy04cEcOYrgEbUQPQ76yHnj/MZnl3c1b4EMvDAmx3Iyfklj7sottYq78fJk6C76C5EtmAyAYVQiI7lyaBbF/9+tgRiWFIxfJKKHlcmcBnZH1LHLB0jrpu35lLr/PcYy/yFC+z91iP3/3W97P11UsprWtEiiFcz/qEpuqaq9h5ILDsXbmNa7Xi1rBT91Ww0D2o08xiIdJYmaXFrcFZWqs3p2Etvf5era5OpiA4ocUSY1pLybu7R48eexyyxBigBQfjGAKz2D5kfyc+QUaDXowl7O7HoqY79YPNvdY7pAeCwl6GRI+7VOLtPaKT4/cDa8nEoN/9Is2YRwc+PbCGNMOAHnLR6m0nRd4Q/EIIf5eouF2rquqD9bbzwKeIP+Em8Merqnqt3vfjwI8CE+DPVlX1a/X2byUqg8vALwN/rqqqO9bYPxkifbAOQRHI/LdENdKN45Ami57+6zSLkHaJ7OC7qqjyfa6dihB45oC8iTt2DoFfl6SObdt2AZKAtpZbww4vrz1NwYQ+g/gHbo+hXQ9aaqrO6bZD93gK7Bx4/bw6Vtfapw5ojrm6Z9qHkfmVIaq17brMFHC0dY72xesc9mGpPZ4Cn5weiukD2J90pl7eMUtTlrc7WaXT2o9qcGvC4lJkeq3WhJW13RjmMlqA7iiG1pBdh+6DAFv2PmiWB9t2z/xavfYLTYYuB9CzNNl8aZ+hsSZw+HGo/mceWnkU4/x+BvjfgJ+zbZ8grp35kyGET9TfP56tnfkE8OshhPfVFVV/CvgYcZntXyZaw37lfl3Ivcj9VX23iGpNvS7DaB9e6qQ/uOxBSikTYyuIACU24AD1Ydh4zxU+xBcYP7nIE3yDa1zgy7yfr3/22egVFhANiN8FhB66ImCV08NV08KO3wzsdldorZW8phm63W6WbxKrg+YkFdPrWzusrba5w8QZYlGXiy8Lbm0tTMdZFJPIwhRcvBpR9lbZoqxtfvvDZYpiQvdsrJjTYkKHfRZbhwwmfQ7KFotLhyy2xhxOllhsjafAOP0+7DMeLU4LG5zpvx7rAbYPubV9NjE9jdtNEAVNNj619anARR2W5L+LHnQjO8ZNGLL1+b4cEB9ieaTU3qqq/nm9dJzLDwDfXX/+WeA3gY9ja2cCXwshXAG+LYSwCZyrqupfAYQQfo643uY7An73V7zmXv3v3N6HFzoRFzXxBRAXSevXOpNwZ8TqiD4DeuzxYb7KM3yND/Aiv8FH+blv/xO8fOkpjrbPRc6tQOaXSDY5aIKfQNdVK30exnad7j4dDtijF5/eAmuxVIFlu3nc1C6oqjGzPMASXbNPdFVgBlhtweYC9KEsW9HTK/AcLTGq4+5GxAKlrSLa7ACWOGRv0mO/1ZmGssjWt88yvdYeg3FElUkren4LJvRXBhyOF4FD9ga9aGssW9HGuDqCnXYCQF2b1F5tG9prClBb6cu6bRfoXSapzh4XKfNIniaX38uHULR05UmRN3s7G2tnhhB87cx/be22SD7+rRnbHwH5/aS6fueIlTuWYXs5pmlBsu/I2C22J9XJ1aBVYNhmQH/6GrPEAR3ew1d5jhdhBV7mKY4G5xJICXhyUJUH90o9RNnvuiTg2okFP3eLWFvuxhfXm3Ym2aZKe8mBg50Ljnt3XRX3yS3Vt5jw2OqA/WGHo5fOTWPbDkdLMeRnyiTrun3DBWCJo7LF0mrUFzscxPVgR4ssnz2g1ZqwxJiy9iwuccg+naTmUnIw7jApEkuR4+TWaDGyvmEHtkLz2iUCwPzaxLIpiQ/EC4DshjTtrg6GnuYmB1geSpQ/VB5CeeRsfvcot1s7857W1KzX8PwYwNNPP31/Rpaf/L6ovp5q5oVLj0hFCjrNgFWFNriKuEnTsL0BV3fX6K3EYN2UltWaLvp8NOjFCbhJUq+lNgugxP6kSkn9koosANqEW8Oz3Oiejec30tKoXqwJWWbnguTAcBVeIOme3WM2rTbjbvSw3uiem17Hrc2zsDGCYTuyw2EbuiPaGyoKG+18rdaEa69fYPnsAYu1t7ZVTChbrSkbVKwfQH9pn71Jj0nZ4sZOn3Z3f5rbe1QXNrhVthLwydYqVr1NUoMFfLpOfR/W1XaAqbMEktNEZpB1krNL4rZUf9jUbDv8GFRvR5z+fZBH0eY3S+517cwtUkinb58pVVV9EvgkwOXLlx9ip4hS2HzZSlVuqcNeFFqimLx1EkCs0vQYwlStPGqf42vFRXgMCiYc1sGjV1nj6u4abIbkCRZb0AQU8FwhMUNNPA+aHWbHF7bPmZ+DmLMXAdo0qJfkvcbaOospgfU6T7YOI5kWHJU6XQPKmWIC/dej7a0fixuMhp1pYPPi0iGD3T79lQGTSWuaoztNaaNDr7XHhBY7N1ZiDB91oHP7kI0nX2Zv3JumyZ1pH0bgG7YT6Ot+6T4O7TrcCaRxF/ofqJBFxdQhJlOBAFNeb91ztyXCcbachxg9hHKSbH5vNsj5ntbOrFXkvRDCd9Trcf4JO+YEi57w8uzVZa6msh//tPLIymC+QYr3k5oqW51YwRaMts7z8utPcYVLvMhz/Dof5auvv4ejrXMRNGWHE+CU2bmGJLugq6bulBjZK3dkQFOtc3G2415eZ56ufjt4DuoQmmGAOkTlxtZa08bVjl7oW6PFpt2rbDEadlg5u8vheJGVlR0Ox4vR+/tYvIkTCvbGPZYYTyu69B4b0qJkzNLUK7w37tWFDTppCcthu8lu9fvpXjj46L55+BGQ4j73U0di4epHrE9i3vfpvR7O+O7tHjJ55IKcQwj/gOjcWA0hbAF/mTe3duafIoW6/AoPgbMjqr4/Q3M93bsVAV1ecl5geI7ICjtplwOShzLIfiT7n2QHhuW7Ga72eLn9VFR1t0NaPF0Twdmee3TznFloOkA8rkxArMnsfbh656qr2KSrcx4Go1CWMkQv6ubZ1Md0caCadXX3uTU6mxYygtrmt8DC6k2OhnHdDdqHXFi7ymDcp1VMGNzo0+ke0KtXYOsvDRizyPJSDXTsM2Yxxv0xYUz0CkuULeIl7qcPldzmpt/QQ43U3ln0VEqmy5eK3fn9ccePmy10vlnjKB5e1feRs/lVVfVDt9l1T2tnVlX1OaL5/SGTNwN8KmJQkkrMF0TAq1+rwE69OE2bFA64SbOEfEFzAjioyYu63eaoMK+js0lNOM8OEaODxLZ0jlkTWt93rE1e/EDA6hNcYKn+PY6wC9NlJfuvR/vcpZscbZ9jYf1mtPG1+7DVZr/diaqtq8wlMci4LKItbrgwdXAMbkSvbad7wKSMntu9G116jw0Zs4iquIjhLXHYALxx7Rxp1arzeLQYWd9oKV2fx+25ausseWDbt63tdL1mFbwIzXuue7ZD1ABki/X762ArcZB8CCV6e09Obu9DehsfZvH1M7QWx4V6+wZTP45YF8Q/ulidA1AeLCyb1xVSAQT/hcSuBH7O8rY5nn2RMwd3suR2OHmcPazFj3F7oNv6/FyeyTJlN+N6pbS4ZGRr4/q05Hy3v8dwu51U2y3g2VGMMYTo2V2PoTBnVuOKbJOyRa+/N83m6K8MOHh9mf5jg1itmRZFK1ZyHrNEn9diWhuwTwqDWT4bnRx7N7rTGD9GIdlf/UEjr7yuEZphLgK+AcTfvy5QOy1k+gHimr0k84Tu4TbNoHC3q/pvL7buv+FDJvOqLidMour7K8Sn9dU3ak5aclLLDvZIpaZuxu/648qY7Qwrt6VNQz5IQbI5S3PbnhiHvIUOQG4wd4BdpbmcYtf6ctY4NdhbP95mlSZYQpMlyaEzDe4OsL5Eq15Dd/WxXfYnnaltrr/0Gl/pvpt2f4/RzrvgUn0B/Vol7oIKmnZqtVYA2ComDHb603U6DsadaZZHiwmLHLLPMgPeVRc1iOEv41YE3sPxYqMkFmWr+WBwk4KcHpu2Xde9bfejTVTVRxD/Dz0i89uE7RLW35vAze+x2xjL7BxqI6lBMHw/VO+44ei4zMHvxMkWxxcSupOobVZYlIJpuSnZ0OTlHZJyfp2VecqYx3rpSa9sAGcZHiQNx9Vet9m163N2szYSZxJuG1Q/00lNYkWuus0Ke9HY+8AoMCkLDkdLrJzdpd8asM8yw9d7TNotKKrIBAcB+vXJByHFAvZLuqsDhjt9FroHLLXH9Zq8MSWtxx674xUuLF1ljx4lLRY55JDFOr4v9inWt9gas8QhLMGkaFHW4S2URfMaoKmCOuDrN5Pjwu2AYMHmoW60AWyluEh5fHX/3QzijNodVK7u5gD6kMgjZ/M7PTLL9rdGCl+ROPCdJ9n/ekBIDElgdrlu3rZt7jhQ0LFURQ+lcJB0+94WTU9rkfXpIrD0cJocrDzEBGujd9kAd+xYV5t1TVIV3ftbxuyR8WiRvUlvWkx0PFpiPFr6/9t7+yBJ7vO+7/O77p23ndkb7A5uF4c9YEEcBBA8kAh5IeAiLcqWIkuq2JIiW6IrCalEJi2WVCVX5JQoy1VRKXFKiaUoZavCCmUpFFOyXlK0JMZFMiJVpiWxBFIgDREwXogFsdQtFruH2cPczdzszGzPdv54fk//np7be9+7273rp2pqZnp6eqZ7pr/9vHyf7wM9x05cgg4cPrEhXRZNfxGJZRA5hIHkQFbciEjYHM4RxWO2qDEmpsmb/ovLoJwGQnUZAlEkun6dYY1mucNG74i00WXzebmwkAEByDU1YI+7HlM9zvo8y42qNuM88DLED4U0hQ137f/Bfq6twOt3tOH3PrI7hed3B9gsko2uItJEGhLrRDWVpfeFDm1de8TfNOwdIJ0dkG8Nm2wfsxwyXUf5c+ppac+n/vltCK02SUmxnt3ktu3runw3z8Pm8TQEtDlA3QfdD/1+q8ACMgsDaB7uEDOGSPblzHO+yWdV+nfPtpvQqYTqsbd+r5bN66hOb2UdGiPKVMt9ESpFQHUrMtV1YESJKv0wsIiYqic66zzf4UA6RmjGUHEhlIV8DtTSeSbpQRruQ/4CkP0eng61SvDw9YKmx9KmJZh8v/ms3Tz4fWB72d52EUGVnwc+BLzhV/snaZp+xr+2q6DKpawAPy5GeTkDfJ1AV1DTQ/YIWd7vBPJnX0I8vSXgOBx+ROKcRrnLETbYosarZ5ekYb89ZZSbyXtytvKn4ZTm+iCE05O5Iv16WiVOzHr2BJ7cFVvU0M+0nD3Ie6HWK7VFkXXzWeoFtoAXK7AAp+N5jsxv0GKTbuJzp7q9JrBagZYfW1kZQq+Sqbo0ml2Zq+FNuzvKjBh7QrNWd0uIsEEzkgNmK5ARCTFjxpEMLiqVR5QrQy+XZdrQBuS9Wz3ukxepyX5mLWpoiJt5cQnZqFOl82j1X6MF+7tcrKhhQmz310C65feH7XHY+wkuFFQB+JU0TX/JLriMoMpFrQC/zH4Y+BLSm6tgtzWxzjwCeO8TD0HTOXpbIJwQrQGl8oijrDEiKLM8evh5xodjuvc3+OZrD8oJr+GkDT31JLHFBA11VskXTyYJyhaA7HqYdawHYTmHtvVut+S83ltPVIFZv6etQq+QtYntxNNsNWtE5dNCalZgtwC97jh0/Lz01gIMykz5mbzNuU7G19tKakTR2HP4RozHEd2kQaPcZUQpA76IhBGlTM1F5AxiygwZVUr5aq8tNui+TOZA7XJbLdf1bLub9Q41XdJ+HiqPTlSHgWU/eH6AXFd1G957znnouk0F2H1kexX2XkRQ5WK2q6AKcMlLQwF+3tK0hnO+MndRm0X0GJz8QQfIH/Y4eVUWkE4B4NT4GMeiU3RoUmWLeTbo0BRhzXtLrCZLMJgSbxEuzCnFZrnl09mTToHTeny7VQohhFwWeBT0bO5uIvTMeXwKfBrqrhOUHRf9a8+YY6HfZQWGCyU2y3NhmwoS2vrVQjT9Bsjwot4U5cqQUmXE5sYcc/ObtGgzLJe8GGmJUVymEXUZRiUJf30PdIkRY2ISomx+hxY9hpQzjh/4ft76NlRiKbjo95u8ACjQT4aetkKrx8++pwfZwPrBttx4za/gB9GrpJfmdCHoMloP2/7u+8huEtXlJ51zHwCeBn7a64heTFDlklbM8MjZly/z+gtIG/O5MJioTfDcFglX7XXY3JgjisZsMuebehL61DK5qgZd7r7/dWbf+5rw25SWot5jk6AEYwsNejJaz8SGavrapBc2mYeaDLn1xNJ2Lgj0Gxs6Ww9wlQCMx4GlbaingdQNIVTswWB9ltXXjgmnbmk7X/VMgDg1leQYmgN6nQajQYm5eWlp61Ojc7ZJqTyiWe7kBpOracW3T5Vy0G8O9JZxxNb5qp8BkkgfsfYYT3rN+lvoBa5ullcm1lsw69q+3Rik+KEFkBrS/fkQIdLo58npkzlf/R30d+mBu4d9Y1fZ3tZyzj1tbh++go/4GPAgwpt4Hfhlv/yqhFPU9tm149balSm9vAzcC82ZIFU16SF5AvJOc5o3/DCcTl3asY6W1zLvJCFiNCxJCLju8sWPJmEGiAUyyCfH1duz1UcFOQW+3sT7bIgGeS9FCzX6futl2lygpcBU/Pc8jldjngogriG65sKWgbgCj2yLdp/1Ohc8CNRjAcdKIqMj12dI6v0gYuC9C9XqU2Xm2IuZ6gQx5fuNKJF4aau4PM7lpYZ+4PnYy1mJdJa/gljPVT1kW7G3ec32xLotgme9SFDh1nUw2+gtIgW1BtIPXgv/Ac0Bq1l61GQleB/YVeT82mmanrz8asHSNM2IuE5O1H/rn66yu6DKJa0Av6s2X+U9jlx/Fgnk3h7ijA+A78KDYIWdDvQG01L5XBAZpcHqbAAiPTH0BFIemL3S2yT4bpVaNfvYnrDqTSlQTbbDtdm9kDIJlPodFghipgP/eDEN6sf1FJoJLMTwogf2VYK2z/JUnjdXh0y1OfMYpxhXRhxqnWfQvotK603GYxlNyWEymguQeXZv0sz0+8oMGXveX0KVMRH9seQK1QOs1fvZ40qzK8Wo+kAAcDIs340HqBQgvbfeuF7IdD0FK13WwqhcHkGuDFWZV0wMbaMnaDmHmalzs5vjc/Nth0M3tL1NlaT80x8EnvOPPw38a+fc/4YUPB4CvnK57RXgd4G9HanyXsp+C57+kIDfAiEv8xTwZ+ST3loE0TzOYoXBoJInz9qQs04AFM0v6To2pB2YZQps1nO0oRvkQcV+nj5XT61nlk9WPesT62pl2Ve6p5pdtmkwu7RGLerTPjtHHI/p12siWqAeoM1RWrDWfGfLn9RtBy0oVYYMkohxElOKhrRpMRqWKJVHWbvakHKm2aeWECFyYNLvGzHO5Kt04NHYyOKPBmUZnNRpyJS4XuXCY6nH2R4/mwe1Hrly+TDLOoSq8cA8ThySpuoinUIJMH8RJReVzJIv4FyDNN0fALhXOb+LCKp8h3PucQT1V4B/CJcVVLmoFeA3YWn6BM5dDvwAPgb/6iPieH8U+VP/DuGk1nBIW8L0SK8STij982sRomWWT1JStPo7WQxRwLNgqO/JPCq/TIHMAqQNb1cIFAw9ydXb0c/TPNYiVBbOMGBWtl1JmZtr06sMORqt0WaOY4dPZfNze/F0yI3qd1EO5CS/beAEfCoVdpKI0aCcEx1V2zpfzQ0u0ozS2AvAjokD6BHRp5bxAvX92i5Xqgyp1fv0ezVRmOnVJP9oO3YsEEIArjp5gMQfM+V55vbNbEdNLzpMIR6g0qtWCbzS2Cyf888dIpt1xi+7tbaXBY+LCKr8+iXW31VQ5VJWgN81Wwz8GqzPwz9aQmgwm8AidHzosk4+P6TUBT1ZlsgTmG1OzYZJ+nH2oxXsFsgXOvSx9eD0PU8SEufr5HOBCnQWTC3IWs90CWaPv0YpGrJevwsqQw63OpJTm5ZiztCTXR/leV6ZfpDeyQY8U8nTMyY9QA0bm170oFOBJGIniRjHwsujTjZ5bSsR8FOvTzp4w4HSnt4+Nf+dysSMs7m9pcooDshBSwAAIABJREFUU3ZRICxVhgx6NWRWL0J8Vjl9C2R6HBXUrEdovT0LhvbilJD/fROk6p/L08+aFyGTSqvgPUUg0YH2t95EtrVob7uDbMPfpgjyVgDzMGjAuq9CTvLnNCFuq6I2fLKel+XUZZVQ8tQafa1DCJttNXIRCdMrKTztAofQ5pJ0ewpCNk9oqC2HFs5TioYyCHxxg6PlNZmRgSyr0ucun2uLSGjSgXthdf2hEAqqFwshtNPCR5zSnO4wXCiz3Z6B+oCaF0cYDUoMB2Vq9b7M2+1VaRzuESOFDPX4VMdPZ/iOiRiPI4ik0luL+pL/i8eMhqUs9AVRkM6GpEPeO7ZesM3p2WX6u6nZC0cysdxyOxcQQQedFaxgqRcp69XnPNAp3N2Qat/DLbOive0ONVWF2UaQ5gXgnfKSejh6Ai0iXpjNnUGQvVJSq3p4i4TKouYQMx6YuCOH4pDi2HlxWgRPdbuL8r773/Yi38dn+Fjnv5PXJ/N5Fpitp6LreBpHqSLFhS1qzJU3AQk55zlNjb4HwlEGhmMffLIwgFYlgLKlz+jnrwPHhYe3vToDzW0/qS0UKcqVYTaMqHG4JwotcZQNLgIRMqjRz0JegFI0zDh/Q6RCrOou5Ypw/uJ4zAio1PuMVUuwOZa2Oya+t01B6PHqmH2xnq1259iLoOb/9AKk4I95v2UT6P/HEp2VHqXvu4VWSFrd8XaGoN68CrwV6i4PIEv+/jg+dPUzLVaQarHmxvREWkBAQSvL/gQ61DxPrd6nOR14g2/SZLPV4kx8b8gtNWHxHS/zBF9mjXtkW+p56Ulk85CQz81Zr6YpIqIqEKp0kzJDmnQYUeIYp1jjKLEXEJ1ngzERh1sdzi4thLwn5nsoFaYCDMq88Zf3ScXYawFCmLAGIknV79UYJxHV6S1GwxK1qJ+FvgqCqvhyejjPXHlTRnN6ovOYONteFI+J47H3AGVWCANTuawL4Rq4sLquv5clgdtCk/USbQeJvcho3rdnltvWOd2+vqYXDPsb3WJLcVm64yDYPjhk+8+uf7LbOX/vqQu9B8KfuUfohDgBLGzz0P3Pc5Kv8rutH2Hn6WkBhxVCD+gq4U9/AikGeCtVRjTocoTTjCjRYpNWtMlLT8KZlaPSreBP1j41vnD2O4UgoOGazUHF5vOs92K4fYda54W4fX6OI9PhM5XEvcQam8xRQ8LUGltsMkeJIcfKpzgbLwQvWD9Dw8nMa9IOCwdxRG+9RaX1ZqYDmI2e9EBVZkhUTnJqzVX/+c1yhy0/tlKBrxFJTjIiYWtQzWb/lioj8B0fUT1hXBlJz2+vJvy/OkK8jhNIpkJIas8i3S9byVWajHpnCpCQz6fasNkC3G5enRamIOsLdn8d0j/dZd2bZIWqS2EE8PNKz0kNerOS0FZPZwX4A+DxKTr338XzPMpO20u5K/i1yff3Zi1UETr4p9tpEM2N6dKgSp95P0jv0eh5nm5VGXAXJNJw/qfDv87g6dlwckE+96bLFYx6iHdqQuCdJKKz2czyYxri1ugTI722JUYZ367ke3Hn2OSU8lBtzk+9Jq2MWrpLZSgk50FJJOvHQlMplckqtADts3O0DofwG6BLg9iTnCPGdIfS99unRp9alhusTm/JoCOVva8APq+o+7gTJ/kwuGeAbxLI7HJb/NCwV0NgWyFWzy13ATDHxIbVGTXGvF9ft4WWW2S3Vdh7EWmZfw78bWAEvAL8N2madnwj8gvAS/7tT6Vp+uP+Pe8iDDD6DPBTaZru27GUafoh3PcCn9tGOJRdrm3exwbCG5yHZFbY/G0k5+ZPhDeW7+ONx+8TwFtBwlKtxuofXikSnkJzKB6zPShxuNWR6WWDOvOHx8yzQZ0up5mncbjHYF0qhuuvPCAelZKpJ+k06n3YEEorzlrsqABJxHavytH7X2FzOMc4iZif3siqqVW26HjgU0DUXtstamG/dH/US1IQVC+n56BTITreJ6onjAZSlCj72bxAJkkVxTKzoxF1M7rL0CtuR9oNUpbvp2MtdZiRzvbNen99GA0SCg8HUG51RPZKB5rHPgdowccWovS4WZBSsFNv3lZ7NeS1nE/btYP5HP1dduNy5gjQN98OWs7vSnp7PwF8z8SyzwMn0jR9O/AN4GfNa6+kafq4v/24Wf4xZBC5NjRObnP/2a+m8OIU/OIPQetHgfdcw0YSBABf8LdVAbjnkPzeMwgx+t/656tItKxeniaz9eTpAZ0pdpKIiq+AjpOIwcosfWpsMM9p5unQpHu2LiC7igBffRB0B3cDPsgTrzGPteiSxEzVt9g8L7yyUkXCyQ3mGVFihSU2mWPoW8rWOEqVPm1atM/OXejJ2pDQejXIPg86DQa9WgZ8AMNBKaOpRCRCfYnEC5T+aVFn1rykthKqnFVClKnBRKj0vVBlbA6wVB753l/JO9qikswcJpcL3fW4WfDy+5Td6/sqZju2um6r7LpOxdwszca/5r6XW2YpjvFOdEW3/WCX9fx2k5ZJ0/SPzNOngL97qW34weYzaSrqY865TwI/wD4YX3kpSx90/GP+J77rZ77A+//R73D2f38UPvqlq9yKsvGnkGLIF4EnoH0ccMET0JBTwYFzZPL4GloZHhwtGLw4y8CAxRvt+3ijdV/wRJYRoPUAWl/s0lushJxem3yOatLLmPx3JCADiZIsXAQJfRMiNpiXORm+xUmBZpM5NphnsDybz4XpyWy5bhCKA/UUkphDHvSieMzIV2S1U2McR8yVNzMSsxZhRpQz4NP2ttjkBEUHMJpof4szDzCKRRxVwXDoix+lylDCX4CkkgfyFkGj0eZMbeXcFjSs9z157NsTz5UPapdN9vxWuKWhb7rjsuN0EGwvcn7/LfC75vkDzrn/gJy9/zRN0z9F+nZWzTqrXEJyxis8fBjgvvvu24OveO32Gb5PTpIkgo9+9Tq2pHnAJcQDPAfMSjFknfDHXgV4FUigPhNOnlVCcUJJwAB/UAncPq0M67Z0u55C0mu2OLRwnp3KdPAW7Ek1MO/Xk3My9PJDw2v1LZIoygBlixo1tujSyPJqCoRb56v0Vu/2HijBq7WJ/A6heyTrPnFQkVTDaFBm0KtxuNXJZKjq00Kj2RzO0Sx3POiVSLySS1BvjnJeYJV+Bo6RaX9TG41910d5xMh7mdr9MU5iEVgdlOW7xbGE6JrLmzyjlIqi5G57LPWig1lmPW+bT7RcUM3Lqtes/5FbHfamLj//eJ/bdYGfc+7nkMP+W37R68B9aZpu+hzfHzjn3sZVSs6kafpx4OMAJ0+evKV5wRf++3fywt99Jzy5jVQortdWzGPv2Sm4sYl4hzFwb04KKpPG7wEd6HUaQh2JF8TDW0VwVU8yPSn0kjMAVhzR42N2lhB/XZPwWZGBfOhWMfd++aHKSLTvkEruyBOJ22fnKB2WEPiNV4zAxrqfZvaif65hnO1XtRxDQ+WRYxMTxaGtbTgoMU7ibBJcKRpSLSe5XFMZ7ykajp8sH9Knxha1DABVdl2BUBWho0jeO65EdDuNjGQdxYnkIFUFZuAuBCtbBVaw17AYs57mPK2HaHmXlYnntpCi62Le74+Z+xuQ/jtuvqXcGeDnnPsgUgj5Ti1ceCXVoX/8VefcK8C3kdfzgCuUnNkX9ktfhl86x4WqztdrG4j3twGD9xBiVG1peiCvsLKKAKAWPQZlzq4sCHCt+OXqHS4QZNGX/OtPASdh+5mZkAfUkBvyHL+K2Y7eK2h5iyLJpLXP3iOFlV6N0/ERen92dz5Et4WbJf9mJVJDPtRVq5jrna/oqml/b3fYkJyc99gixnTHjYzGUmMrkJsZBlFTL3ulIfLYL9O8YBh4FPhqzblO9lgr7LV6n2E8ZjuJAzVHj5XlTNrwNDavQX6WhwKm7dqZ9Aq1+mtBz4KsLrM8xJtoaepItm9z8HPOfQ/wM8D70jTtm+V3A2fSNB07596CFDa+mabpGedc1zn3JKIY+gHgX17/178ZdgbxyPYa/PDb1EIIiCeofZpfht5boTKTJyRrX+5zvnK7QuYNskTg6iWE1rkBIeSEUPFdxEhMETyuit/WEqHg0gQq29TqfXrrraySa6uvSRIFMFZgtV6PdjRYKo092TMPyINJfZArMtTqMoy8d75BFI/Z3Jjj2PwpQELbRiRT3VS81Cq9aLFDrcQwe655PzzgjTHhfCKE6K1hLesqieKExOccM/2/2BPVbZW2Q757xXp3ECg/GjJXJtazuVibitBjaotDkx7iLTHHzngvMmk3x66E6rKbtMzPAmXg8845CJSWbwd+wTmXIFOUfjxNU537+BEC1eWz7PNiR7AG+XTljbRz/jaPeIZnoP0eSGaCF6UegtJV1IPTE06VZPTkWSJQa5YJyfTdvBFNxi8hXuYCsChe2FSzm3lAPchax4aDEoP2XRyq9xk8NStVbPV2rJejqjR6AquCjXZIKGDrSdzclrxavS/cOl9x7Z1vUPLk41q9nwtrFcSCeKm0s8luSvFFuIh5+asSQ5kE50PhEhK+RyQ0yl0SJP8XkRDFoeo8GpQ9D9En+SupdPPYVkY9xpOthHog9bjbda0Xp89taLtIvlNE/xNJ+G1vCeE5BW6nsPdqpGXSNP0U8KmLvPY00p9woCxN34tzL1x+xT01FayNgU3ozAQazHHkz76KXP1XCKBVJ3hqWvlb9+9ZIYzL1DD2OPm2qoTQeueBr77QpufDvBp9mnTotuoMKdOiLcN/4oSdz00Hzpp6qjH5XBfkvRmbD4MQCvvcYq3eEQ/LV3dLlRHdToMkiWge7lAuCwjZ0FUBUDsNFPQUBK34gRY9IACgFkBku3HmAWYy+JHxHrXtrt6XPGgSQcV77pPiBurVLU4cH+vhWfC3VV4Ljrb3V71pXb5q7le4+bbjYHAbeX6FAc0PQed62t2u1bqIIO2zwAysz8L6cVioBU9Kw1k9kbQ/VukzNgeo6yj4KVCuI4ovStNYknUOL27QLHek2hkJ+I2JaB3eZESJDeal8vnU1IVqLbH5LgqC6hEuEk5UG8YZb2mnV6OXROJZ9aocXpAOjuackLrLjLxqiwwqV88NBLQsp8+agpkNgXUdBU+ly+hzrRwnXhkmisZ0z9azXuBMBiuJ8zlMG/JCnk8ZT9xXJl7XY2FpRwqKvV3W09/uGS4igHqTLLn8KvvFCvC7Umt+yF9lv87lBx3tpdkxmueA12D9fug9mg+ZbIeGFhwghL3qMWquSHNt64TBO0p1aQH1AdWyeHraFQEwpESDLl3fQcFKRbwM9UJsh4Ml7NpOhnXz+cr70/s4hThhqr5FuTKU7g2Q+bqax4uljc4CWOR1+uSxnIGTIa8dWxmR30bMmCp9tnwhRK3kga/k/cE48q1yh3sikAB02k0BaQDKvieZ/O8z2fKmbWyTvcGTIgW28muPsQVVvZAoKN4qyktKAX63m6VvgmsgczlW3w4vxsiM3xttVq68QQDAc9A7DbwPcHlJpB5hlOQC4SR5nHx4ZYm2lhzrT9pKvS98tyjiLjokRLkwMSFibeNo3vM4jpyci4QEh+W3KQA2CTlLywHwwIcPc1W3T1VdFMxqUT/L7YFwDFU78FKN9TbPp8CnFBe1kqfJ6OhLyIfVQ8pUy322hqIK3TvfyLQFz6zPhQKI9dz0t4G8Coy9TfL4bJ4PducQqunxtG2LFXBVSG9Ene5idsDArxhdeaXWWw39uMcfBf4BMu9jytyqN+CD/TCbC3qLV4DPAa8Gj2CZUAxRq6RZ4aP+5Bt+vCTB09L8ni1M1BEic9T3pGAhL9fo06BLE1FtLlWGgUuoJ2sLOA4Pve0vuf9tL8Lx7RDuWlKz5vfUU62kUuXtSM6spJ0k5T7VaRE1GI1DoaJPlf64lnlsCnz6unp5IIAWM6aMKOBE/nHVC57K+lHWmaIFE+1JTohMy5x4waXyKJPEyrQGB2U/AjP17W+esmOruLtxKJl4TU1TAjYXaMFFQVVBz1KVboUHmBKu15e77QMrPL8rttd8eLHo+W8OnnsCESxdRcDpZb9uwt78wgp8Wwiw6hm07Z/3ga8h+cA58aJWEIDTCmrPh2B1HQs5IBvNCHKCPEIIRVfwYzMrGRiUPB04IeIe1kQaCql6Dir+8xb8Nlpw97v+igdYkeLC/WPWmvcweG428NQsUNp/oO+Y0J7lciUULmpRnyr9TL1mRJlatJUDPRvOKggqERsE4Lo0TL4wUFpAPEPb+aGmnMHAGxzRHTay/t/u2TrjJGaqKVSbnTjx+T8XcnUtQ4WZVGaxZkNkqxqj21FA00LHutnOEiGHq3Smm2kpnuV7MKwAvys0GWz0RVhehME2PD7lT/gp5F/nEED6MgGg9sqq5t5ut+vvn4Xe+8KoQ3vSaBjqk/Mgmnw77Wl56+MiirozmA5hsVddeeNb9zC7eJpSNMrAZMt3SESIx5PJXvniyaFHzvMwL/FtvITM0d0iOpzwyvEyOyvT+fAXBPB6U1CRcLfekgKLcuuUrqKqMSBenVZnIZ/j09zeJK8P8KFxyAPKe0M4qxqEQncZZiApnyltcapKrRSYraEI15Yrw0z5JQM+G752DBEaLiyCaH5Qc4CWFmRDZTX9bVUh5yT5+SiL8h7n4KZpJxVh7+1smzD4OjAFz5iQJuveU+9sr/4BCnTJxHM1neq1AryQV4RZRzh3mgvqVKQi2a5IW5bnh1UWzmTN+9lXX8Z7GVN02k1Onz+SAd4aRxlSEspLpwFtOHTiPG99x9dgAebmN6l5D22OTY6zLHARj0OXSCv1J7V6o9tM1beo1PsMB2VGKijqv5f2DAOZJ6gAqGRrvQk097MWNy1WKIXFVnT1deX9aR5QCdBaTbZ5xL5JbWj4G8djrzKTSNhbGfr8pfn59DYpIrFbhTc0lORb5exzTSOc8Dfbq207c25mt4eC35Xc9oEVnt9V2Xcg3Rhen299PlQ09cpNjISre+H5VQkhr5puWwshmix6QfiALAoW1s1NvYi2T8R3prLqbhyPZYD6CuGEMRXjnVj8oVJlRCPqsjGeZy5qMyZme30GHk9ptjqsDY9CnFJmyEs87MFGPLM+NaI4YVu7IGIyxRYVLNWiRtkTmEPlduxl8sX7lJxdkgtFE0NH0dY0K2RgwU63aUNYIAPAvqn2xn4fNAzeopaB6uSkuH6vms3/uMAs7UUpKLbA0SF0g1hhAwhAaLmRVtzAWqaFSMizdrh5dsA8vwL8rsLSdA7n3ksQIJiVEJia/yPOIzlAz8vLlFyuxRT4IP+PmhxTqGdKF1iBzmKY96Hhr9IqtDsA/DqpSBC1yVcTvWCqbN6xs1RiNCjRnxbp+FFUZm3zKPWlNzgyfVqmh3Qa8KJjozlPuTJkc3qOTeZ4k6YA4aAsldw6Jh+WcigeZ6IB2kVRLku42YzepMNdNHkzq8xqSqnjtwvSbaL9uxCEDSBQVexM33CE+1kOMOQIZQ6IhsXqBdrKr74HRnSGzUz+vt+rCeev0wg9yZVI9hd3YfGCXe6th2g7QNTsbxqbdbSApd07Ws1fuYlV3wMGfkXYe9W2igDTLFLg2AI2/ZVW83/fhSh2Xc081SqhYjzlt6vP9Z87mUusTtxvAM9LVfqLyInwDAJ0q4RwGDLqxfbKTAiVNaRSFRZ9vDxFkohe3nBQ4lvfehCAe6bXhGDsJ6CRwPbTM/R7NV4aPkybFh3uokuDRrMrunzxmEP1Poea56EypOGLBNVyICir5zYmlvGYXi9Qw9IG3azyrHm6LT80ygKd+oUhlxcUXLQLRIFSw2aABpLPs95inyqbzGWgm+kH+kqvDj4aJzEkMYfiMVO+DS7rVVYQM3nVHM1Fe7TtYwVMvcYpcC5MvJeJ7bQJXTw308Upwt7b2b4GPIZ4eUtkM3t7ACnUa9CrAu9FAPA1gnDBpWyL4C3O+udbCL8vMesooOpPZwFQE3YzsLooJ4pWgJfIJ9MrSIFkmSBqoAosNrnuE+6DTkNEDAZlKfIclwluHe6SIT/PTWcn887qNP14nOnuNQ73iOIxR+fX6A4bDAelLLwF8fiArLiheTclJDd8YadPNQunFdggFCOsSa4viK2GtjdbtR3mPEENn0eUKTPK3hf7bSuXUKrdUlSJooRxFGdqLyB90Nu9qp/9G5OJHrTIV3kVCGxHRjzxHLNOxSy3oKie/SQQ7lZNvpG2wy3XFLwaK8Dvqm0DAaF3IwCoSixerbm3Cc056GwT9OKvtDdYwa1LyPWdIV9IUc9QTT1BDX1BCNjvhuUH5MQ4SaCwLBEoLQNCz6/2C0PeKwFJqA+mGA5K7KwKyEWx5MdGlOi1m/nZHC3Ybs8QLZxh/vBpKUxEWzTp0Cq3+Xr5MSlLxLUcYVmVVaq+iAFKbC7lQFEt9l0doXihYW8A0cSAoA4z17yevD+EwuUs/6cEaEufCRzDGls5qfzOUDxiFTzNWZxINXs3j8eGuuqp2byeJUPrT6yeoc3lqlCEhr2aPzQ0mJsS+hZh7+1tafohxDv7AgHUJgCp0/fPHQKQ/8UVbn2b8E+fJV/oiEFbyjKv0L62Rf5a9hXg1aDookn1HgJ4q365StlrVVhDY02+a1WxRyiMIIomnXFT6CftqeAx+jwTHWgd3qRBl6O8zhxtTvI0j/Esj/I8AI2oS38sYKGCAzIJboshZRp0s1yfkpO3qDGibAoiyQVcPytioOGsLb5Yj3A3UxC0RRPbHWIrvt1xg2q5T62+xXBQYrs941MA/rdI4lDE0OOv9+rlKfXHkpgnCyLasqjgaCWuFCAV+Drmt7CCCTfaDli1twC/67KvIV7WOYIHpt7ahseiGWAOuNLJMlvkqfC7dY3ov11DZfUSZ8hT6L8EvBzATomvA4L6swU/DbcWCUn0xL/vRfP+ZREeOLN8L2986b4wkEm3vQ60UhIijnGK4yxTZsQaR3mVJU4zn427nIvaWRVXws0hI0qUGWaKywpWtmiheTwFt7JZR7s5AJ8rLJsqcd/QZULIO1kNVhPC9DijvCiI2hyhcv3AUIc0zxcneeCxoag95rrMPlfvTy9E6l0rwOn21OO2z3Xdmwk2ewh+zrnfcM6dds49Z5bNOuc+75x72d/fZV77WefcsnPuJefc37qSr1uA33XbBuJlqQxVFcn1NSA5ByjfaxEphGiofDnbIu/hTboEGmpv+WVds67y/yBDOfUaVvyiFwmguOJXfcRv6gvINDk9edTzWEGktZaBZ5xMnXuKcKLpydoEBo4aEuo+yvMsscIKSzzLY75aOsyASsGqZnJrJd9+Zr08kGqstqVZD88qs2hrWjlraxtmnL2hB9gyw6yCmy+QBPIzkPM8w0wQ2U5ClA08Ak8b6jSE6AzInOapAFgKTOpl23yfbWVTjw0CrQXyw+R1m9arnKS1TIijuse48bZ3nt8nuHDC40eBP07T9CHgj/1znHOPAu8H3ubf83845y4rLFiA3zWYhL7WVhHEeFWexg6oISDkIFEy7wPQ/DtIvvBqTL1BDW0VFNXTi816U+b1KuKVnpGTbYWg/qwtUJov0nBVT5b3EjxDBchnCCfZ5xDw0xCrTl4t2pstNAz97N4h+bkZ+li8vFD8AHynxTBbZmkoY2KqJvyvTahtq9dmOXk6x2NS0FRa20pZGK3fXYG0TzXzQAW4E7aGNbbOVxknEf1elV6nAfFYRFjjNICUmgKcbT/TY9Yz97bDQ38PpbJYPuCkioulM2kYbTtKbjTnbw89vzRN/wQJoax9P/Cb/vFvIhMgdfnvpGk6TNP0VeTyfNmTrAC/PbNtBACfDWAXTwGr/n5T/oiPA/EDiMJ/lQsLGJcyK6BgQdCGypZbqGDg54Wox6FgpmHXil9NTyKlUagw5osET9Hk9GiZZZq3MiFYlwZdGnRo0qHJyHtLsQG9Bl3fOzzMAZS2sgHeW1OencziqCGjMzs0s3B0ZLxBXTcQnpNsuwpi4lWqgEE5E0CwbXG7dXpk373clf7eyshTXPxnx4n0VFuvTEHKci71WNnQ1ubptA9Yc3l6MVKvEfJVed2eAqpNAVsP8kbZDvm/5KVu12bzaZq+DuDvj/jl9wKnzHqrXGI6pFpR7d1z+zICQG+HxHdcJH4AUjuVk2IJWL4f+e2qwL/nyrpCbGX3Uv/kLiFXqCF2vDuFQgnRbaSqu0zwGjRM1sqiVYieBE3tclE6Rwc2N+Z4af5hujRY4yhAzquCUHTQgod2aqiXJ89DRVbfP/I+oQqsWgKzgpe+ZgUM8t7eOPMEQT26cfZ80juc7BQZUqZUHtHZ9K0V2ibolWlyIax6yCr/NUl4tr9NQriw6LIFsy0rEDGp9mLC3AvO7gG4/xTSv+DGWArm8FzOWs65p83zj/upjddiVzUdUq0Av2u0NP0Qzl1M3fkF5PL2HiT8nUHAqAsrM77LYtGs/8NIceI1Lg6AWtiwhZCLXUK1IDJFmAmC2bbpStEQSz3CJQJDRwnRu7Vk2SqlLlfNOn9y7rSnWavcwxr3ZP2vjahL2efPlKunczXKhndnw1t9bkdSAjngU/DT91S9mov1NDUPqPw9ICuGaCisIa2CnhU2iBhnsz2ATM15nETSLw2S59PjY3N4egxtJV0rtPqT6bFW2pFedCoEINQQWH8P/Y0G5nX7e9m833Gz/o2yK/cu22manrzKrW845+5J0/R159w9wGm/fBUw81KvbDrkZcPei1Rdft4595pz7hl/+z7z2q5VF+fcu5xzz/rX/oXzk49uX1tBCiF9Mg4gVWiv+mrolA+HtyGuAU8glJgfRULiSVPC85R5PkUA1kmzhRF1BVaRENi/Zk9CPWFbBPXlEwgQav+y9TC87h+Q722eqFIO1meFGI2OuxznPCgb6oZvHvkgdpTl7awKi8pRTQoUgBKRxz5/F95nQayWjVXPE6Nta5uGzLa/V0jWQqvpnRfSd79XY0cl7AeGz6c3SzGy+TkvnJk8AAAgAElEQVTIS07purt1bSiIWWK0mnrjzxGKILG56boL/t5ec/fabjzV5dPAB/3jDwJ/aJa/3zlXds5pTukrl9vYlXh+nwB+FfjkxPJfSdP0l+yCiarLUeALzrlvS9N0DHwM+DBSH/wMUpU5IBPcrtVeRoDvnQhvbwpYhPVNODEn+b+nakYQQSkTDS7sDbYFD+vB7eYpTnaBJH5bthCyLSerCppqFVET5gp4jyChsOadWgRvRL0+PakXCCMrl6D+yBvMTW/SpEONftYaZrszJqXopfXMz+GlnnHt7Dq2d9eaFlbU8o/DX90SoLUSnOfx1bLPGVK6oG+4Oy5Tnd4K4W5lKPm+ZCpQUiBcXCapKTYPaClF6glaxW0bEluZMgU39fJUx2+Sz6epCAW/5AZOdttDkvNFpkb+IvB7zrkfA/4K+HsAaZr+R+fc7wHP+2/wEx5zLmmXBb80Tf/EObd0hd85q7oArzrnloF3O+dWgJk0Tf/c79gnkUrNgQa/S4e+aisI2LxT7mMHSSJX6gHy8z4FDGrQSwmUld1EEa4kU6y5w0VEgEErwq/519Ud2IamCdEqhJB3yS87QTh5XiTfGqfJ9SYBEFuEQsniNs3pDvNs8Faez2SpRpRY5sEstzdp6rWpoICtBkueL6/YYu91nbrvB7bbVKKykqE1XyjV4Gr2/YRnGIYZ2cpyzJjN83MyRe6suL07gxKHKiN2BuULq7uTCssW0GyHhlpCXvmlZ163XqNVarbdH5PbsblYK4/f4sbYHra3XWRqJMB3XmT9fwb8s6v5jOvJ+f2kc+4DCPPrp9M0fROpsDxl1tGqyzb54beXrMY45z6MeIncd9991/EV94tpJ8hbIZlDcnDPw/Kj8ic9joALDgGsLuIpTlb6L2dWHGED8SDPkBdIuBcJxWflj/o9hN5f9eZ8T/DhE+scKW/wcvNR8Wp6BO9uhcDpUymsxN8vwuziaRp0WWLFE51fAeAUx2jQZZnju4KZHT2Zb3vLi5HaAsfk457vhFGai7w3LzFcziq+pWxZzfMHrcCpXac7bFCf7tI522ScxERxwrgyEmFYS08Z7HKz4KWenxaPVsw6tsVNCyS2iGE9beuFqxdow2MIv5mGzhZQb4Td6IryHtq1Ul0+BjyIBG6vA7/sl1+s6nJV1Zg0TT+epunJNE1P3n333df4FfebvYAUNZQM/Vbgq+IBJphczBziJV4N8Nn5IZMFkSr5jpGt8J7jyMmwRBh2riFwKyWKfc4sHoch5icIaiE/gCxfJAiVVoCWnOVHOM0SKzzACo/yPD84/n3+Np/mXTxNi3Ymj69gU9olnLViBAqENgxWgVK4kKysnRm2za3sFZqrXg9QX+tTzSgvKmyqxQ/tEimVR2xuits0TiIGnQY7Ha+IbXl6NqxV78+qtfTIz0tWb1krwbawZHNkNo+nnqPSZiYr7rYybxWffa7W/dcXHOrrtzuhvS1N0400Tcdpmu4Av0YgFF6s6rJKPtV6RdWY2882ECrMpn/+Tki+HkLK40BrhqAYcyVmuYK2AKJFFq0Qa84PWUdPvhPIr6HgB9mfs9Nu0qbFwr1rEKccOnGe+tIbsv53pHK/RDgBfdhbb3Z9VVeqtw26PDp8nplPb/OO//NlnuArLLFCa2LIxMXydQqAddNlAUGaSrtBAiV57CXnI+ZoZ+GzlalSsLWSVuolblHLZK1GlDNO4WhYolwZEsdjqe4mcejYUIDpIf92m+ezVVoFLOX3aZFCgU6LSJBXcbbAYXN9FvA0vK2Y39O+T9e9HFPqWu2ADTC6JvDzZWa1H0T8F7hI1cUTErvOuSd9lfcDhErNgbYLuz0uZxtIqvM08g98K/RWgwd4HH8CvAcJfS9lk1VezRXa3OAsUkixRRDDQ9PQVXN34IVQHTuDElvnq3TONmHgaDS7PDr9PLNPvsbs0hqzJ17j8Il1OJ6Gk28B+j3p4hh6EnGXBs+XH2Xw3cAj0ORNT3vucJQ15tnIiiKT7WXWq7Oqyl3qEz6e+nayvvbgym7mVVtsmGzzhfqabavT798fi3JLv1eTTg6AdZfvuFDAq5APdy2h2RKcFazq5IEL895J9Wd9n77Hvm/RbM+alc7fo5zcrqY8vyu57QO7bM7vIlWX73DOPY7s7grwD+GyVZePIJXjKnL2H+hix/XZFnKd+F4kB7cIyauw8oCA3wlguQbtH0IO0ypB1krf73mDFzBafTEjA0Zb4Y0R0J2HwUzoBtCcnW5qHX/STdGLG1TqfVFtLm+QELEUvYpq7SVRxOaDHb7VfsSHvgN2ejW2mjW65TobzMvgc0aUpoeU3zfiJR5mjjZN7xptcIQOTTZpCdD4764cQJGlUqBK2KKWm8pmZ/ZasLTSVboMAqB2aXhBhVJW3e16wLYagAkR3Z5BlEFZBhJNdmRYwFOzhQlb5FDAsuGxBUnr5dlKvA1x8es/Qqj0TnZy6HpKPtfdqIP7CKQfY+/sgElaXUm1d7eqy69fYv1dqy5pmj5NGGVdGCA5wHcShum+DF98SHJpTUSNefAYEiYnBMUYS+SyIKiVXqvuMjn/Iwa2gqei1IynEQBU+kpbnk9VRjQPdxiNy35uRswxTjGilImMAnxrQaawHYrH7FSGRLHIT3VoZgD4Csczft1jPJuJGbxJkxd4lDWO0qHJKY5lA4vU7PQ4EFkp7eUN3p7k/zSfZ7mDJUZsUaVJZ0LWSoF2nHEBlfoiecCYrfNVGf6UmOq0LWZYz0zzfWo2zLVFB1sVtp6alaJXMNTtakHDXuu00KLr6mdpq6GGvkpFsmHvXgNVyvW0rt10Kzo89sCujPKym51DEG4GqczeC8nL8MxDcjV/BHhmHolnVES1QQC8KQIoQvhn45crKXoKqfDq+hvyWe1aqMHrybJEznvR+RqNqJt1VJzmSFac0DkYMowopeRzYt1Og8Zc1/f0+mqpn+g2zwYNusydP0PlVWAaGg90eYb/hBWWaNPyZQklJss+id4faKeGmlJc1CYLINrtoV6cFjaArOMD39c7Mp+n90kSMVUZsd1pQOLyElIKgDa0xLyuQGP5d1rgUKECWyixYbANk/U1LaAoANqiiPHqssq7fifdnhZCIA/Ae2FX1952y60Av31hGgL7QsfyJgzmBPwqUzB4J1IoAeHrKX1FM8eaz9NLuZW0spdiWxX2HSKDqdDPq+C3jlBgBtDtNKjO95nz1dkVljjKWs7rKzPi8KJUsavlPqNxmSSJ6AybzJXbdGmwwTw1+pnHePTMGdwzwDeBw/CO8y9TO7GFbSE7zbwXngp0FwjgpjlCISLL803msjxekKwS0GzQ9UII+XyfErCBHOhuna9KB4edxWuLFHrILb9PQ1IFPz2uClyWo2dJ4rqeFi8mFXcgD6i2gKLb1LyrenyWBgMX0lzq4D4E6bVct3ez2y3sLexm2WcRvb854DVY3YJ4Ua7eKw0kNFbAeY18p4ft87VFkhoh9FWxU13PA+hgSja1SlBxqSMFmMdlzTFxpqC8xAoNujK3lzqbtKSaW34+EwQYRxEbh+ez99q5GVWvx+fWEI7+l5FGmPfBQ2dX2XjPkSxU1jycdlpYVZVowsVQeoqlwmh+sOrPyElStQKnFlKGlDLvtk+NJIn8NLZpiNPgdcGFnDlLb1Fw0nBU19dlWsSws1IsaVypMG3zXgU0zRuqWSDtIBcv/Xz18uxZbsNi3fZe2QEDv0LSao/s6qu+u9kXkH/qY8A5WDnn+4AdEvq+lZAfnAQ0FVF9zN+e8M91nXsRFRlVkjGcP9X4S/Bka79sWToY3njtCF0alLyElOregeTZmnQyMrNWbh/gVZq8SZ8qVmYqZkw8Hkvd5VXgLHzx38MnfgH4DLx9/Gw2lU2pMmHeRvD4VIzU9t/qjA4gC22t96d5RKsBqOGzviYFF9HpGw3KDNp3eWByAWg0Z2cLHQpIkyGphqwKhDYktXk7Xd8WTWwnja3uNs06yhCwdJoFAtfPbt9Whm9E5feAUV0Kz2/f2e8B/yXCEvoSDI7DYBFi76ENHkC8w1UCEToheHxLfvkcApir5DUANWT2nmMbwHs1q56LrqzMAbAyBYuiXHya+UwBRbhwPZp+HkeZEfNsZGFjmzmqbGXhpFVRSaIIzm7DeWAavuMEfOo54I+g9E+2JQdIlw2fWwxV3SG2F3jkgU/0/fr+SGiXSH7QUTLh9VlRBO3f7SCq6Plw1wVOnnptls5iCweTdJJ4YpmGuRYgbTXYVnchP5w8IQ++CqAdQi+xpdUohSkchACs+t392e8+wt5ZkfMr7NptG8kB/mcIw2hDbs35QElZn4HVtyKeWw2pBs/59/pCSEalWCRw/2LExZslXxH2OUOrPKL5KYC6o7PZpFwZ0pzuZKGhlZQ6yhpH2GCLGm1aGYFYOypUdn6LKh2azJ5chzIS9j4NP/SjwEPQmT5Mn2o2GLxr+Hp2upp2hqjHptQXqSCLZ6cUFnlPOVchnqS9KPCNhiUZ5A6SErCCAjYvZ4sFA7PcgpqtvlqvbjLUtB0ctnhiX9ft2eWTYbiG1YvkiyEWhC1YT9JmrteK0ZV3rl171XfSzgCfR4ogs8CXod2AR2qBvd9xkNTkRGmrYKmXyVLvZBEPaBrmqrhBFwFLUxjRE0NpFspB895DFCckSZT1vtbpehpzKESIPyhFCztQXIGww130Oc1p5uEYzB85zfTcTiagsP034cs8wTd4mA5NPx83VG4hL4Kqszhkj7SfN68PqLnCmu8A2fLjNlU2S9WmywzpDhvZ7N2dXi2cyLbCOtlxMemFQQDGlnmsF5XdenUtN09DWjXdpj1TPZk8+63scg3BFaj1fwB5ULXb26uqr4a9B8QK8Nu3dgaRJHs3EgI/Cy8+Af854QRZR06wtpHBX+BCtWZiMin7bPbHhBhqkkLdheqiJtt94/2geZdINx0OlBXRvINjnPLg1mSOTebY9JDSzboppII75iUPbKc4xoPlV2g80aX/RJWYMa+yxCs8SJsWHZqm0yLKwG5SBzBiTIcmTTqo5h7APBu+aJKg80PKjOjR8JSXauaNandIv1ejVBkyTmJ2ei7k6mzV1IasNs/XM/c2vFWQm+zBtSIEFiB1ewv+99WwVhV2LE9PqSz2ZnN7qrRjw2HrBVry9N3vehfXawXVpbC9s5cRkHosPP/CQzKEfJFwVV9HToRV8uHPim5nGakwnCNfKNkmaE64/MmrJzx+2aqDZoXOvXdRYyujlBxljS1qxN7bGyFDxxu+WCH5vxabzGXdGxp+Kmh1PSA9y2O8woO86d1N5eIlEyH2mIgmbwISrjboZoWP2Lenybbf9J6e5P2040OryHZub/dsne1Og207ZNzSSGxIqjk2K+8/mU8bmOW6bIF8vtB6eBbUYv+TqcrOwkW2qWH1JI9Qq8gWXJvkvUwDgumvgftXX/0qe2EHqNpbgN8e296FvmpfR8jKTwCfgsEWPPN2YcWcQP787yV/QmShmRKbl8nHIzP+BJwLJ5h6Gardt+BXtdXMihQDRtMl+tQ4yhpDSl7fOMr6Yu1woSYd2rSyGR5rHOUoazTpcJojWRsawCZz2XpKni4x5C7v1bWZowQ06aCKLNYbhBAeDyeAUz1PkFxg34e/He5iPI5kAFFlKK1r1muz3RN6swUI9bAgX0jQ93aQaiwEGlGbwMOz7XAThQgpOJHv2bUVZBvy2tyfDcchD3T/FzfODhjVpQC/A2FfQkDs7cAG9DbguXkBPRDwWiEA1zKIl7dC4ANalZdGvg+07m8L5Am7ECqcvgLc79VoT7eIGNPkTc+rFY+r47lyPRrcwxpVtljjqOcDzmXhZ5c6MeMsVO3QZOiBqEafJh3uopMVKXQur8zOjXMem4bGOgRdenJjmryZ8xy1Sq3tbzqzA4TIvT0oQa+S79xIzM0WLyx4TYaievLrugsT6+n2FUgVQO2ZaEF1ySyz77fvs8UYG+ImkP47bp4VBY/Cbox9EZnx8RrwMizPwuJUGDbUInD1sv5egG8R5vcCBjjoEbyKRwhEWg2jlYyrALkKO80a3cqQ5uE32fQyMMK3K9OhyRFO06LNiHIW6q7wAAkRz33rcSrNLs3DHcqM2GCezrBJFAfPrRb1s1C5zNB7kSMfstqZuvLX9dk6QHKRYVaHFjnGGXiqUIEqwowoSbg7KCEy9OS9vkmvTD05q6Vnuzf0bLJgaQGxOfH+SYqLvfAon896n+rp9SZ+Iw2Fe5Cqbu6tsMLzK2zvQ1+1f4NwAL8GrAQRBPXMMrKtTm07joClDjsyw4xs7kkJsZqUfz/Bk+wQ+n9bwMAxGpTZrLeIo1Bg6NLI2sR0VKXO633l/IMCcO0pBoNZ1ns1piojojghjsdE8ZgoGmd5vSHlHC9PeXsqQW9n65YYZQIEEOgwJbqZ56f0Fn1Nwl25CMTxWIBvfSqAmXZXKDgp8GleTe+12rrOhYTlVUIaQdMR+rrmCW1eUV+3/b4QPDyb8+sACzdoDsf1WgF+hd04+ywS754DUviCEzpgh3DC1oFejHh5DXLqzcwCGxB7z9Dmr2LEk3zKL18hHx77kG8nnqYDlOZltu4mLbZ8GDnvlapVgHSNo/TWW0LNUW+oV2E7idhOYg4vbjBOImqReG8a0lZJMg/tGKcy2oxy+0RyPvTh6pxfJTRrlVhb8bTTo0+VTeZo0GWTOaJ4LDM44qkgC7VOvgKrYa6+Dvnc3xJ5wNJwVx/bIoNut222oevZ4oVurxnu0/0uAldQXQq7sXYGkcS/HzgNvXkBK60IZidhzXsJs4j3p2Gw1/dL/HhMG5IpL7BFntJhE/4+BNsZTLOeHCW+9xQrLGWaepvM0fdKyJKDq4roJ2YbLTlDDqliTLmbVWFFhCCoK8ue9EUFhs0cbWZEyYe6cxllBcjygnY0pVaKy4xo0qFDUwA6iYTTZz283fJvq+Tzf3pRsHk2uLBgoftt+Xu6TotQobeyV8kNHCx+I62guhR24+1lwoCjPrQfkMU2R6XPmUFoLkcQ1RhVhTkHlZn8+toWpZ6Oyi7ZbSvA9gAqrMVHKVWG1OpbjJOI4aDEYGUWKnCoeV5axNrkw7f2FDRTdpJxBnxKmFbBUn2snqQqP6uaTIkhr3NUBAh8iNzzfblayS0zzGgzWiHu0uAYp7ICSHO6Qy9uIiIPfl9toUNNzxSr2KJcSL1g6Ho2T2gBc7JSvB6OS/r/cPCtyPkVBjcy76f2ZSTePQ1MQXveL98mK2rE+vwcIpo6Q8gBvgC9J8JJa0/O70I8kiXybVJKrDYh3U4yzSCeFjzQEHFV1tlhOpCuFTQfQSbDLW4wV5ZZJi3aF3DwqvSzQUNV+lk192Fe8kWLRkas7lPjNEdywAdk3l9CxCmOcY+X4iox4tv4BkPKrHEP/cUaZzhCNnRcvedJ8LPgaAsQlji8mweoZpRabguwm7QdCjHTwm6WfRH4ESQMPoeovpwjA78eiJd3nBD2Lvl1GkAKK07Abcm/rHJWS8B/5T8C8iF1TKb0DOQ9JeWmqbf3CCHJrzmt5jaNcpejrGV6elXPDQSy9jiQkPcor1NmmOXujo+X6UaNLG+3wTybzGVHRfONCoLa6vY6R7O5Ibq9PlWORadgEc60780XHGwVVi2ZuFdOpS1aTPTRpv+SO8eKsLewm2e/BXwQQZwzyBm3ikhYOYL0/SZU5mCgrW3zZGdwMhUqlquEkPcP/Eeoh6MVx2VCHnCVAIYKblohVlKuvndRtnuoMqI7bLBRPkKLzYzCot6dtsaVGGWP52hzjFMc4xQza9vMnD1D/0SNUxzLWthKDLMeXyVBR375kDJd6rSZY0iJeU4TkTDyFJ1SNAwUFC1uTHIeldKij21nhen5TX/lqn7A28suOpB2/9llwc859xtIR+npNE1P+GW/CzzsV2kCnTRNH3fOLSFuyEv+tafSNP1x/553EQYYfQb4qTRND9Chunq78aEvyE/4LNID/AISdywBCTSnoLNEpvQysLPjzyDe4DlIqhB7b1ET+cuE8YdWQNOGc8pV02VaEEkQ7/G4f/8Jsl7Uw0+uc3Z5gbPtabqtBt158eDKHuggjKScY5MafZZ4lRabHGGD2rAvbcoxHrxEuEDzfkqk1hxhHwFIFVU9yhorPCCdHUTCRRw3OLNyNN/Lq7w93S/bD6vhr3+c/sy1/naF3Uq7Es/vE8CvAp/UBWma/og+ds79MnDWrP9KmqaP77KdjwEfRmqTn0GE0vd78f6A2NcRT28OCWnPAbOeWlKDXgMpkoAAnsZsyv2rhXBPK5OW09Yj8P4UDJYwpGoubK9rme0lCAB24OyfLWRAspNMs955C6eb52k0u0TxmGPRKcoMeZBXstC0xhZH2OD+V98QDcDDcP6+Q4wo06XhBVbLmRBplwZb1LiHNTaZ4/XzR+n3BNyH80qCjmgzR+dsk0GnAW0XpOMhcP70DPHV2vSnr+0XKmz/2WXBL03TP/Ee3QXmZ/D+MPA3L7UNP+d3Jk3TP/fPP4nQcwvw2zP7LPAPCEqkZyCZgZ6OrlRB03uRn91XfOMZSFJIXPg39Mgk7LNeVM3hafV3heARrZv1tOixgADgMvniwcBsx5N2d5jm7Mo0LA6I7h3Tok2Hpq/IVv2IyZ6876/k/dOHd+gfqWWyV3Yuh4a8KzxAn2rWQbLTq7G+/haop/QWGvRW7g5iDlaOyvDs0p+6jp+ksH1t15vz++vARpqmL5tlDzjn/gPifvzTNE3/FDnjVs06mpTa1ZxzH0a8RO67777r/Iq31m5O6Kv2W0gB5Gv+eYO8fNUWIo+lLW5VMikrkDD1aUJot0rwAjUnaCua6+TBMAFWU2i6/Dxa22QPgdemiiXaWdKu0K3XqR3u+x7ghu/ybXKaI9x9uCfO7cuyPensiDLJKyFay/jKDncRkdA732A4KLOTRNDzROvECfDZfJ2nnqQ/cY2HvjD2utzrnFtBcjZjIEnT9KRzbhb4XULs8cNpmr55Ldu/XvD7+8Bvm+evA/elabrpc3x/4Jx7G0E3ydpF831pmn4c+DjAyZMnb+u84N7aFhICP4bMAzmHeHwzyH/oCGTEX5W0OgNL8yGndZyQ6Nfc13FCtXcRyedpEWQZAbBl/Q4u5M2WCIrQWkBQ8QSlh6jnuCjLRoMy3XqDzajl5wSLR7fGUepHutx//g15zzmoHd3iQV7hVV+qVgDsj2uMkwgokeisXVVn1uIMkL7nWo5xYRe3G9Li8TfSNG2b5x8F/jhN0190zn3UP7+mrOs1g59zLkY67TMRxDRNhyB8hTRNv+qcewX4NkIsprYIrF3rZxd2KfsacnjvRTh9ywghOiYMOjpj7hEwUw/uvQQvTsNXFUBQQFQytO1sQB97Ga3KlGxHc2crCIhW/LZUjGFiLu1OEjFOIsZRlA0dWsl4ODLfd3ZzABuw9MirrLCU6QR2aWTApzm+TLSg40ivX66zsEvaTWE5fz9CcAX4TYSMdU3gdz3T274LeDFN0yycdc7d7ZyL/OO3IDHWN9M0fR3oOuee9HnCDwB/eB2ffaBsbya7XY39G8K1xg9DZ9YvUwCckefxItAPnL0vEDy/kwS1lxUC1WMFWEkDzUWLHNn1+ZyvLG/nG/JbhM/R7pEFQhgMoOBHxBa1TChBQe4bPMwbJ+ucf/IQr3OUZR6kR4NTHKMzFgQfDkqMk4jR3AzpvRXS+6cK4Lsptufj21Lgj5xzX/WpMIB5jyf4+yPX+m2vhOry2wjStpxzq8D/kKbpryPaH789sfq3A7/gnEuQOP3H0zTVEWMfIVBdPktR7LiBFiO0l1kEyV5AxFC3EM/Mt8Nxzl+oG6IDqCHviwSu3km/qgLVi/gQ2eVbt5Jtgnd5zr9wJNBHID9+sUXG+xMPUrIbU/UtqtNbmURWyUtfVenzEg+zyZxw+8oicHCKYx4U30EmBHPY3wq7yXZVYW/LOfe0ef5xn+6y9p40Tdecc0eAzzvnXtyLb6l2JdXev3+R5T+6y7JPAZ+6yPpPI4SHwm6KvQz8HX+v9JcZcj95cwY6XwfeLotXgMSvp10LlvKhMk34549gwE3zi+fC9jkNVKWHGPJdE7p9XQ4QJ0J5IfHDh0QTsMyQb/Awc2xymiPU6fI/8j8DQu8ubL9YylUUPNppmp681Appmq75+9POud9HyKwbzrl70jR93bNITl/rty06PG6S3dyqr9qnkQlwXfACAQJQm3B8Dpb1Sr0BySbSHuf/Ela9RAnLqhmoGnWqRdfWN1TJxmCC39ZMvvKrgNcE6inECZWFLuMkltGYUScTLFWJ+i4N/j++fy8PTGE3xPYu5+ecmwYOpWna9Y+/G/gF5E/9QeAX/f01p88K8Lvt7VkEuZYRz89XeZsgf9SHEJevQSiEAPRhqZZv89Lcna3UtvzrzJKfDqch8PMweFQ+XuXylSyNtLqNBmUazS5HyhvMc5omHf5f/t4NOh6F3Tjb02rvPPD7UiIgBv51mqafc879BfB7zrkfQ1if1/xHKcDvtrdVhPpSRUDOt789BwJWy/6115D/m3IAEyOt7gUQtD9XN5vIpoRxtUXw/iC0z/muk85MkMpS7l/HsROXSO+fAqYJ1ZDCDqbtneeXpuk3gXfssnwT+M69+IwC/O4I+ywS0kJWkBjoXA+9gQCgr3rEMyaftyVyTxrSNvHV3CkzfGfbbEcresopjKG3DStTEE9OELNhcmEH2w6WlPP1UF0Ku0q7+ZQXawkCfBuEpHSXUIXYQsCvC6yaKi5QqZFr0KkjPD47I5hzZPlDQEAtIU0fIE3nSNMp0tf36dyJwvbI1PO7ktutt8Lzu2PsZSSsnUIASkPdBgJ4mqvb9M+Rf8cCEqauJ2EMoxU4WMUUPKZI0/fepP0pbP/ZwVIzLcDvjjINMRMk/6d2hDDeMiEXumQV2nnJ7b1ITuMudHU/tPdft7ADZgcr7C3A7yvKgrEAAAQlSURBVCbbraG8qK0i3l9MED24FwE8RbFziOeXisxTxsebQTR5CivsUrY/QtorsQL87jizxY0l87zqn2vO7gzpG3MUVtiVW+H5FbavbQvx/s6QCRuwRZo+ceu+UmG3iRXgV9hl7NaGvgArt7jyXNjtaQdrdmUBfneIFWBX2I23otpb2C22AugKuzVWhL2FXYHtZehbgF1h+8OKsLewG2wF2BW2P63w/ArbQyuArrCDY4XnV9gV2m6hbwF2hR1cO1gFD5em+3s4mnOuC7x0q7/HDTIVeLpdrdi/g2P3p2l69/VswDn3OeSYXIm10zT9nuv5vOu1gwB+T19O7vqg2u28b1DsX2H72wpJq8IKK+yOtAL8CiussDvSDgL4TY6zu53sdt43KPavsH1s+z7nV1hhhRV2I+wgeH6FFVZYYXtuBfgVVlhhd6TtW/Bzzn2Pc+4l59yyc+6jt/r7XKs551acc886555xzj3tl8065z7vnHvZ399l1v9Zv88vOef+1q375heac+43nHOnnXPPmWVXvS/OuXf5Y7LsnPsXzg9nvdV2kf37eefca/73e8Y5933mtQO1f4VNWJqm++4GRMArwFuAEvCXwKO3+ntd476sAK2JZf8r8FH/+KPA/+IfP+r3tQw84I9BdKv3wXzvbwfeCTx3PfsCfAX4a4BD5mp+763et0vs388D/3iXdQ/c/hW3/G2/en7vBpbTNP1mmqYj4HeA77/F32kv7fuB3/SPfxP4AbP8d9I0HaZp+ioyUfzdt+D77Wppmv4JQf5Z7ar2xTl3DzCTpumfp4IUnzTvuaV2kf27mB24/Sssb/sV/O4FTpnnq37ZQbQU+CPn3Fedcx/2y+bTNH0dwN8f8csP4n5f7b7cS24I8IHYx590zn3dh8Ua1t9O+3dH2n4Fv91yJAeVk/OeNE3fCXwv8BPOuW+/xLq3035fbF8O2j5+DHgQeBx4Hfhlv/x22b871vYr+K0Cx8zzRWDtFn2X67I0Tdf8/Wng95EwdsOHR/j70371g7jfV7svq/7x5PJ9aWmabqRpOk7TdAf4NUIa4rbYvzvZ9iv4/QXwkHPuAedcCXg/8Olb/J2u2pxz0865hj4Gvht4DtmXD/rVPgj8oX/8aeD9zrmyc+4BZBL4V27ut75qu6p98aFx1zn3pK+CfsC8Z9+ZAru3H0R+P7hN9u+OtltdcbnYDfg+4BtIFe3nbvX3ucZ9eAtSEfxL4D/qfgBzwB8jk8L/GJg17/k5v88vsc+qhMBvI6HfNuLh/Ni17AtwEgGRV4BfxXca3erbRfbv/waeBb6OAN49B3X/ilv+VrS3FVZYYXek7dewt7DCCivshloBfoUVVtgdaQX4FVZYYXekFeBXWGGF3ZFWgF9hhRV2R1oBfoUVVtgdaQX4FVZYYXek/f/1ToIhEC3r+wAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# DEM\n", - "fname = os.path.join(proj_dir, 'dem_tc.data/dem_VV.img')\n", - "data, atr = readfile.read(fname)\n", - "\n", - "# plot\n", - "data[data == -32768] = np.nan # get rid of -32768 value\n", - "plt.figure()\n", - "plt.imshow(data, cmap='jet')\n", - "plt.colorbar()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/hdfeos5.md b/docs/hdfeos5.md index 3beeb3ed8..d7fcfd7d6 100644 --- a/docs/hdfeos5.md +++ b/docs/hdfeos5.md @@ -1,7 +1,8 @@ We support output geocoded displacement time-series product into [HDF-EOS5](http://hdfeos.org) format via `save_hdfeos5.py`. This is designed to easily share the InSAR time-series products to the broader community. ```bash -save_hdfeos5.py geo_timeseries_ECMWF_ramp_demErr.h5 -c geo_temporalCoherence.h5 -m geo_maskTempCoh.h5 -g geo_geometryRadar.h5 +save_hdfeos5.py geo_timeseries_ERA5_ramp_demErr.h5 --tc geo_temporalCoherence.h5 --asc geo_avgSpatialCoh.h5 -m geo_maskTempCoh.h5 -g geo_geometryRadar.h5 +save_hdfeos5.py timeseries_ERA5_ramp_demErr.h5 --tc temporalCoherence.h5 --asc avgSpatialCoh.h5 -m maskTempCoh.h5 -g inputs/geometryGeo.h5 ``` ### File structure ### @@ -15,8 +16,9 @@ Attributes metadata in dict. /date 1D array of string in size of (n, ) in YYYYMMDD format. /bperp 1D array of float32 in size of (n, ) in meter /quality - /temporalCoherence 2D array of float32 in size of ( l, w). /mask 2D array of bool_ in size of ( l, w). + /temporalCoherence 2D array of float32 in size of ( l, w). + /avgSpatialCoherence 2D array of float32 in size of ( l, w). /geometry /height 2D array of float32 in size of ( l, w) in meter. /incidenceAngle 2D array of float32 in size of ( l, w) in degree. diff --git a/docs/installation.md b/docs/installation.md index 474d28775..4118f0e72 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -70,19 +70,19 @@ chmod +x Miniconda3-latest-MacOSX-x86_64.sh You may need to close and restart the shell for changes to take effect. -Run the following in your terminal to install the dependencies to a new environment _mintpy_ (recommended): +Run the following in your terminal to install the dependencies to a new environment _**mintpy**_ (recommended): ``` -$CONDA_PREFIX/bin/conda env create -f $MINTPY_HOME/docs/conda_env.yml -$CONDA_PREFIX/bin/conda activate mintpy +conda env create -f $MINTPY_HOME/docs/conda_env.yml +conda activate mintpy ``` -Or run the following in your terminal to install the dependencies to the default environment _base_: +Or run the following in your terminal to install the dependencies to the default environment _**base**_: ``` # install dependencies with conda -$CONDA_PREFIX/bin/conda config --add channels conda-forge -$CONDA_PREFIX/bin/conda install --yes --file $MINTPY_HOME/docs/conda.txt +conda config --add channels conda-forge +conda install --yes --file $MINTPY_HOME/docs/conda.txt $CONDA_PREFIX/bin/pip install git+https://github.com/tylere/pykml.git ``` diff --git a/mintpy/.travis.yml b/mintpy/.travis.yml deleted file mode 100644 index d2b464d3f..000000000 --- a/mintpy/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: python -python: - - "2.7" -# command to install dependencies - -# command to run tests -#script: pytest - diff --git a/mintpy/asc_desc2horz_vert.py b/mintpy/asc_desc2horz_vert.py index 06a800266..b0b26c4a1 100755 --- a/mintpy/asc_desc2horz_vert.py +++ b/mintpy/asc_desc2horz_vert.py @@ -25,16 +25,15 @@ cd AlosAT424/mintpy mask.py velocity.h5 -m maskTempCoh.h5 geocode.py velocity_msk.h5 -l inputs/geometryRadar.h5 -x 0.00027778 -y -0.00027778 --bbox 32.0 32.5 130.1 130.5 - cd AlosDT73/mintpy mask.py velocity.h5 -m maskTempCoh.h5 geocode.py velocity_msk.h5 -l inputs/geometryRadar.h5 -x 0.00027778 -y -0.00027778 --bbox 32.0 32.5 130.1 130.5 - asc_desc2horz_vert.py AlosAT424/mintpy/geo_velocity_msk.h5 AlosDT73/mintpy/geo_velocity_msk.h5 # write horz/vert to two files asc_desc2horz_vert.py AlosAT424/mintpy/velocity_msk.h5 AlosDT73/mintpy/velocity_msk.h5 asc_desc2horz_vert.py AlosAT424/mintpy/velocity_msk.h5 AlosDT73/mintpy/velocity_msk.h5 --azimuth 16 + asc_desc2horz_vert.py AlosAT424/mintpy/velocity_msk.h5 AlosDT73/mintpy/velocity_msk.h5 --dset step20200107 # write all asc/desc/horz/vert datasets into one file asc_desc2horz_vert.py Alos2AT131/mintpy/20171219_20190702.unw Alos2DT23/mintpy/20171211_20190819.unw --output-one Kirishima2017post.h5 @@ -50,6 +49,7 @@ def create_parser(): parser.add_argument('file', nargs=2, help='ascending and descending files\n' + 'Both files need to be geocoded in the same spatial resolution.') + parser.add_argument('-d', '--dset', dest='dsname', type=str, help='dataset to use, default: 1st dataset') parser.add_argument('--azimuth', '--az', dest='azimuth', type=float, default=90.0, help='azimuth angle in degree (clockwise) of the direction of the horizontal movement\n' + 'default is 90.0 for E-W component, assuming no N-S displacement.\n' + @@ -164,18 +164,19 @@ def get_design_matrix(atr1, atr2, az_angle=90): return A -def asc_desc2horz_vert(fname1, fname2): +def asc_desc2horz_vert(fname1, fname2, dsname=None): """Decompose asc / desc LOS data into horz / vert data. Parameters: fname1/2 : str, LOS data Returns: dH/dV : 2D matrix atr : dict, metadata with updated size and resolution. """ + print('---------------------') fnames = [fname1, fname2] # 1. Extract the common area of two input files # Basic info atr_list = [] for fname in fnames: - atr_list.append(readfile.read_attribute(fname)) + atr_list.append(readfile.read_attribute(fname, datasetName=dsname)) # Common AOI in lalo west, east, south, north = get_overlap_lalo(atr_list[0], atr_list[1]) @@ -183,20 +184,28 @@ def asc_desc2horz_vert(fname1, fname2): lat_step = float(atr_list[0]['Y_STEP']) width = int(round((east - west) / lon_step)) length = int(round((south - north) / lat_step)) + print('common area in SNWE: {}'.format((south, north, west, east))) # 2. Read data in common AOI: LOS displacement, heading angle, incident angle - print('---------------------') dLOS = np.zeros((2, width*length), dtype=np.float32) for i in range(len(fnames)): fname = fnames[i] - print('reading '+fname) - atr = readfile.read_attribute(fname) + atr = readfile.read_attribute(fname, datasetName=dsname) # get box2read for the current file coord = ut.coordinate(atr) [x0, x1] = coord.lalo2yx([west, east], coord_type='lon') [y0, y1] = coord.lalo2yx([north, south], coord_type='lat') - dLOS[i, :] = readfile.read(fname, box=(x0, y0, x0 + width, y0 + length))[0].flatten() + box = (x0, y0, x0 + width, y0 + length) + + dLOS[i, :] = readfile.read(fname, box=box, datasetName=dsname)[0].flatten() + + # msg + msg = 'read ' + if dsname: + msg += '{} '.format(dsname) + msg += 'from file: {}'.format(fname) + print(msg) # 3. Project displacement from LOS to Horizontal and Vertical components print('---------------------') @@ -209,6 +218,9 @@ def asc_desc2horz_vert(fname1, fname2): # 4. Update Attributes atr = atr_list[0].copy() + if dsname: + atr['FILE_TYPE'] = dsname + atr['WIDTH'] = str(width) atr['LENGTH'] = str(length) atr['X_FIRST'] = str(west) @@ -257,11 +269,12 @@ def write_to_one_file(outfile, dH, dV, atr, dLOS, atr_list, ref_file=None): def main(iargs=None): inps = cmd_line_parse(iargs) - dH, dV, atr, dLOS, atr_list = asc_desc2horz_vert(inps.file[0], inps.file[1]) + dH, dV, atr, dLOS, atr_list = asc_desc2horz_vert(inps.file[0], inps.file[1], dsname=inps.dsname) print('---------------------') if inps.one_outfile: write_to_one_file(inps.one_outfile, dH, dV, atr, dLOS, atr_list, ref_file=inps.ref_file) + else: print('writing horizontal component to file: '+inps.outfile[0]) writefile.write(dH, out_file=inps.outfile[0], metadata=atr, ref_file=inps.ref_file) diff --git a/docs/resources/colormaps/BlueWhiteOrangeRed.cpt b/mintpy/data/colormaps/BlueWhiteOrangeRed.cpt similarity index 100% rename from docs/resources/colormaps/BlueWhiteOrangeRed.cpt rename to mintpy/data/colormaps/BlueWhiteOrangeRed.cpt diff --git a/docs/resources/colormaps/DEM_print.cpt b/mintpy/data/colormaps/DEM_print.cpt similarity index 100% rename from docs/resources/colormaps/DEM_print.cpt rename to mintpy/data/colormaps/DEM_print.cpt diff --git a/docs/resources/colormaps/GMT_haxby.cpt b/mintpy/data/colormaps/GMT_haxby.cpt similarity index 100% rename from docs/resources/colormaps/GMT_haxby.cpt rename to mintpy/data/colormaps/GMT_haxby.cpt diff --git a/docs/resources/colormaps/GMT_no_green.cpt b/mintpy/data/colormaps/GMT_no_green.cpt similarity index 100% rename from docs/resources/colormaps/GMT_no_green.cpt rename to mintpy/data/colormaps/GMT_no_green.cpt diff --git a/docs/resources/colormaps/batlow.cpt b/mintpy/data/colormaps/batlow.cpt similarity index 100% rename from docs/resources/colormaps/batlow.cpt rename to mintpy/data/colormaps/batlow.cpt diff --git a/docs/resources/colormaps/hawaii.cpt b/mintpy/data/colormaps/hawaii.cpt similarity index 100% rename from docs/resources/colormaps/hawaii.cpt rename to mintpy/data/colormaps/hawaii.cpt diff --git a/docs/resources/colormaps/oleron.cpt b/mintpy/data/colormaps/oleron.cpt similarity index 100% rename from docs/resources/colormaps/oleron.cpt rename to mintpy/data/colormaps/oleron.cpt diff --git a/docs/resources/colormaps/roma.cpt b/mintpy/data/colormaps/roma.cpt similarity index 100% rename from docs/resources/colormaps/roma.cpt rename to mintpy/data/colormaps/roma.cpt diff --git a/docs/resources/colormaps/seminf-haxby.cpt b/mintpy/data/colormaps/seminf-haxby.cpt similarity index 100% rename from docs/resources/colormaps/seminf-haxby.cpt rename to mintpy/data/colormaps/seminf-haxby.cpt diff --git a/docs/resources/colormaps/temp-c.cpt b/mintpy/data/colormaps/temp-c.cpt similarity index 100% rename from docs/resources/colormaps/temp-c.cpt rename to mintpy/data/colormaps/temp-c.cpt diff --git a/docs/resources/colormaps/temperature.cpt b/mintpy/data/colormaps/temperature.cpt similarity index 100% rename from docs/resources/colormaps/temperature.cpt rename to mintpy/data/colormaps/temperature.cpt diff --git a/docs/resources/colormaps/vik.cpt b/mintpy/data/colormaps/vik.cpt similarity index 100% rename from docs/resources/colormaps/vik.cpt rename to mintpy/data/colormaps/vik.cpt diff --git a/docs/resources/colormaps/vikO.cpt b/mintpy/data/colormaps/vikO.cpt similarity index 100% rename from docs/resources/colormaps/vikO.cpt rename to mintpy/data/colormaps/vikO.cpt diff --git a/docs/resources/colormaps/wiki-2.0.cpt b/mintpy/data/colormaps/wiki-2.0.cpt similarity index 100% rename from docs/resources/colormaps/wiki-2.0.cpt rename to mintpy/data/colormaps/wiki-2.0.cpt diff --git a/docs/resources/colormaps/wiki-schwarzwald-d050.cpt b/mintpy/data/colormaps/wiki-schwarzwald-d050.cpt similarity index 100% rename from docs/resources/colormaps/wiki-schwarzwald-d050.cpt rename to mintpy/data/colormaps/wiki-schwarzwald-d050.cpt diff --git a/docs/resources/colormaps/wiki-scotland.cpt b/mintpy/data/colormaps/wiki-scotland.cpt similarity index 100% rename from docs/resources/colormaps/wiki-scotland.cpt rename to mintpy/data/colormaps/wiki-scotland.cpt diff --git a/docs/resources/dygraph-combined.js b/mintpy/data/dygraph-combined.js similarity index 100% rename from docs/resources/dygraph-combined.js rename to mintpy/data/dygraph-combined.js diff --git a/docs/examples/input_files/FernandinaSenDT128.txt b/mintpy/data/input_files/FernandinaSenDT128.txt similarity index 100% rename from docs/examples/input_files/FernandinaSenDT128.txt rename to mintpy/data/input_files/FernandinaSenDT128.txt diff --git a/docs/examples/input_files/GalapagosAlosAT133.template b/mintpy/data/input_files/GalapagosAlosAT133.template similarity index 100% rename from docs/examples/input_files/GalapagosAlosAT133.template rename to mintpy/data/input_files/GalapagosAlosAT133.template diff --git a/docs/examples/input_files/GalapagosEnvA2T061.txt b/mintpy/data/input_files/GalapagosEnvA2T061.txt similarity index 100% rename from docs/examples/input_files/GalapagosEnvA2T061.txt rename to mintpy/data/input_files/GalapagosEnvA2T061.txt diff --git a/docs/examples/input_files/GalapagosSenDT128.template b/mintpy/data/input_files/GalapagosSenDT128.template similarity index 100% rename from docs/examples/input_files/GalapagosSenDT128.template rename to mintpy/data/input_files/GalapagosSenDT128.template diff --git a/docs/examples/input_files/KirishimaAlos2DT23F2970.template b/mintpy/data/input_files/KirishimaAlos2DT23F2970.template similarity index 100% rename from docs/examples/input_files/KirishimaAlos2DT23F2970.template rename to mintpy/data/input_files/KirishimaAlos2DT23F2970.template diff --git a/docs/examples/input_files/KujuAlosAT422F650.txt b/mintpy/data/input_files/KujuAlosAT422F650.txt similarity index 100% rename from docs/examples/input_files/KujuAlosAT422F650.txt rename to mintpy/data/input_files/KujuAlosAT422F650.txt diff --git a/mintpy/data/input_files/NCalAlos2ScanSARDT169.txt b/mintpy/data/input_files/NCalAlos2ScanSARDT169.txt new file mode 100644 index 000000000..8320a557f --- /dev/null +++ b/mintpy/data/input_files/NCalAlos2ScanSARDT169.txt @@ -0,0 +1,26 @@ +# vim: set filetype=cfg: +##-------------------------------- MintPy -----------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = isce +##NOTE: 150408 is the reference date of alosStack processing. +## (parameter "reference date of the stack" of alosStack input xml file) +##---------for ISCE only: +mintpy.load.metaFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/150408.track.xml +mintpy.load.baselineDir = $DATA_DIR/NCalAlos2ScanSARDT169/baseline +##---------interferogram datasets: +mintpy.load.unwFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/filt_*-*_5rlks_28alks.unw +mintpy.load.corFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/*-*_5rlks_28alks.cor +mintpy.load.connCompFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/filt_*-*_5rlks_28alks.unw.conncomp +##---------geometry datasets: +mintpy.load.demFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.hgt +mintpy.load.lookupYFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.lat +mintpy.load.lookupXFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.lon +mintpy.load.incAngleFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.los +mintpy.load.azAngleFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.los +mintpy.load.waterMaskFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.wbd + +mintpy.reference.yx = 1500, 200 +mintpy.networkInversion.weightFunc = no +mintpy.topographicResidual.pixelwiseGeometry = no + diff --git a/docs/examples/input_files/README.md b/mintpy/data/input_files/README.md similarity index 81% rename from docs/examples/input_files/README.md rename to mintpy/data/input_files/README.md index cf110e363..da7ed177e 100644 --- a/docs/examples/input_files/README.md +++ b/mintpy/data/input_files/README.md @@ -7,6 +7,9 @@ #### ISCE/stripmapStack #### + [KirishimaAlos2DT23F2970.template](KirishimaAlos2DT23F2970.template) +#### ISCE/alosStack #### ++ [NCalAlos2ScanSARDT169.txt](NCalAlos2ScanSARDT169.txt) + #### ARIA #### + [SanFranSenDT42.txt](SanFranSenDT42.txt) @@ -14,6 +17,9 @@ + [GalapagosEnvA2T061.txt](GalapagosEnvA2T061.txt) + [WellsEnvD2T399.txt](WellsEnvD2T399.txt) +#### SNAP #### ++ [WCapeSenAT29.txt](WCapeSenAT29.txt) + #### ROI_PAC #### + [KujuAlosAT422F650.txt](KujuAlosAT422F650.txt) + [GalapagosAlosAT133.template](GalapagosAlosAT133.template) diff --git a/mintpy/data/input_files/SanFranSenDT42.txt b/mintpy/data/input_files/SanFranSenDT42.txt new file mode 100644 index 000000000..f4472b18b --- /dev/null +++ b/mintpy/data/input_files/SanFranSenDT42.txt @@ -0,0 +1,21 @@ +# vim: set filetype=cfg: +mintpy.load.processor = aria #[isce, aria, snap, gamma, roipac], auto for isce +##---------interferogram datasets: +mintpy.load.unwFile = ../stack/unwrapStack.vrt +mintpy.load.corFile = ../stack/cohStack.vrt +mintpy.load.connCompFile = ../stack/connCompStack.vrt +##---------geometry datasets: +mintpy.load.demFile = ../DEM/*.dem +mintpy.load.incAngleFile = ../incidenceAngle/*.vrt +mintpy.load.azAngleFile = ../azimuthAngle/*.vrt +mintpy.load.waterMaskFile = ../mask/water*.msk + +mintpy.network.excludeIfgIndex = auto #25, 30, 34, 40, 46, 52, 56, 57, 61, 62, 67, 70, 74, 80 +mintpy.reference.lalo = 37.69, -122.07 +mintpy.unwrapError.method = bridging +mintpy.deramp = no + +## options to speedup the processing (fast but not the best) +mintpy.networkInversion.weightFunc = no +mintpy.topographicResidual.pixelwiseGeometry = no + diff --git a/mintpy/data/input_files/WCapeSenAT29.txt b/mintpy/data/input_files/WCapeSenAT29.txt new file mode 100644 index 000000000..df4689211 --- /dev/null +++ b/mintpy/data/input_files/WCapeSenAT29.txt @@ -0,0 +1,15 @@ +# vim: set filetype=cfg: +##-------------------------------- MintPy -----------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = snap +mintpy.load.unwFile = ../interferograms/*/*/Unw_*.img +mintpy.load.corFile = ../interferograms/*/*/coh_*.img +mintpy.load.demFile = ../dem*/dem*.img + +mintpy.subset.lalo = -33.10:-32.95, 17.98:18.13 +mintpy.reference.lalo = -33, 18.05 + +mintpy.networkInversion.weightFunc = var +mintpy.deramp = linear +mintpy.topographicResidual.pixelwiseGeometry = no diff --git a/docs/examples/input_files/WellsEnvD2T399.txt b/mintpy/data/input_files/WellsEnvD2T399.txt similarity index 100% rename from docs/examples/input_files/WellsEnvD2T399.txt rename to mintpy/data/input_files/WellsEnvD2T399.txt diff --git a/docs/resources/shaded_dot.png b/mintpy/data/shaded_dot.png similarity index 100% rename from docs/resources/shaded_dot.png rename to mintpy/data/shaded_dot.png diff --git a/docs/resources/star.png b/mintpy/data/star.png similarity index 100% rename from docs/resources/star.png rename to mintpy/data/star.png diff --git a/mintpy/defaults/auto_path.py b/mintpy/defaults/auto_path.py index fffe803c1..e911b947b 100644 --- a/mintpy/defaults/auto_path.py +++ b/mintpy/defaults/auto_path.py @@ -62,7 +62,6 @@ mintpy.load.unwFile = ../PROCESS/DONE/IFG*/filt*.unw mintpy.load.corFile = ../PROCESS/DONE/IFG*/filt*.cor mintpy.load.connCompFile = ../PROCESS/DONE/IFG*/filt*snap_connect.byt -mintpy.load.intFile = None mintpy.load.demFile = ../PROCESS/DONE/*${m_date12}*/radar_*rlks.hgt mintpy.load.lookupYFile = ../PROCESS/GEO/geo_${m_date12}/geomap_*rlks.trans @@ -78,7 +77,6 @@ mintpy.load.unwFile = ../PROCESS/DONE/IFG*/diff*rlks.unw mintpy.load.corFile = ../PROCESS/DONE/IFG*/*filt*rlks.cor mintpy.load.connCompFile = None -mintpy.load.intFile = None mintpy.load.demFile = ../PROCESS/SIM/sim_${m_date12}/sim*.hgt_sim mintpy.load.lookupYFile = ../PROCESS/SIM/sim_${m_date12}/sim*.UTM_TO_RDC @@ -89,11 +87,28 @@ mintpy.load.bperpFile = ../merged/baselines/*/*.base_perp ''' +AUTO_PATH_ARIA = '''##----------Default file path of ARIA products +mintpy.load.processor = aria +mintpy.load.unwFile = ../stack/unwrapStack.vrt +mintpy.load.corFile = ../stack/cohStack.vrt +mintpy.load.connCompFile = ../stack/connCompStack.vrt + +mintpy.load.demFile = ../DEM/*.dem +mintpy.load.lookupYFile = None +mintpy.load.lookupXFile = None +mintpy.load.incAngleFile = ../incidenceAngle/*.vrt +mintpy.load.azAngleFile = ../azimuthAngle/*.vrt +mintpy.load.shadowMaskFile = None +mintpy.load.waterMaskFile = ../mask/water*.msk +''' + + AUTO_PATH_DICT = { 'isce_tops' : AUTO_PATH_ISCE_TOPS, 'isce_stripmap' : AUTO_PATH_ISCE_STRIPMAP, 'roipac' : AUTO_PATH_ROIPAC, 'gamma' : AUTO_PATH_GAMMA, + 'aria' : AUTO_PATH_ARIA, } prefix = 'mintpy.load.' diff --git a/mintpy/defaults/smallbaselineApp.cfg b/mintpy/defaults/smallbaselineApp.cfg index d5db5a39b..85e72b418 100644 --- a/mintpy/defaults/smallbaselineApp.cfg +++ b/mintpy/defaults/smallbaselineApp.cfg @@ -29,7 +29,7 @@ mintpy.load.baselineDir = auto #[path of the baseline dir], i.e.: ./baseline ##---------interferogram datasets: mintpy.load.unwFile = auto #[path pattern of unwrapped interferogram files] mintpy.load.corFile = auto #[path pattern of spatial coherence files] -mintpy.load.connCompFile = auto #[path pattern of connected components files], optional +mintpy.load.connCompFile = auto #[path pattern of connected components files], optional but recommend mintpy.load.intFile = auto #[path pattern of wrapped interferogram files], optional mintpy.load.ionoFile = auto #[path pattern of ionospheric delay files], optional ##---------offset datasets (optional): @@ -40,10 +40,10 @@ mintpy.load.offSnrFile = auto #[path pattern of offset signal-to-noise rati mintpy.load.demFile = auto #[path of DEM file] mintpy.load.lookupYFile = auto #[path of latitude /row /y coordinate file], not required for geocoded data mintpy.load.lookupXFile = auto #[path of longitude/column/x coordinate file], not required for geocoded data -mintpy.load.incAngleFile = auto #[path of incidence angle file], optional +mintpy.load.incAngleFile = auto #[path of incidence angle file], optional but recommend mintpy.load.azAngleFile = auto #[path of azimuth angle file], optional -mintpy.load.shadowMaskFile = auto #[path of shadow mask file], optional -mintpy.load.waterMaskFile = auto #[path of water mask file], optional +mintpy.load.shadowMaskFile = auto #[path of shadow mask file], optional but recommend +mintpy.load.waterMaskFile = auto #[path of water mask file], optional but recommend mintpy.load.bperpFile = auto #[path pattern of 2D perpendicular baseline file], optional ##---------subset (optional): ## if both yx and lalo are specified, use lalo option unless a) no lookup file AND b) dataset is in radar coord @@ -80,9 +80,9 @@ mintpy.network.referenceFile = auto #[date12_list.txt / ifgramStack.h5 / no], ## auto - randomly select a pixel with coherence > minCoherence ## however, manually specify using prior knowledge of the study area is highly recommended ## with the following guideline (section 4.3 in Yunjun et al., 2019): -## 1) located in a coherence area +## 1) located in a coherence area, to minimize the decorrelation effect. ## 2) not affected by strong atmospheric turbulence, i.e. ionospheric streaks -## 3) close to and with similar elevation as the AOI to minimize the impact of spatially correlated atmospheric delay +## 3) close to and with similar elevation as the AOI, to minimize the impact of spatially correlated atmospheric delay mintpy.reference.yx = auto #[257,151 / auto] mintpy.reference.lalo = auto #[31.8,130.8 / auto] mintpy.reference.maskFile = auto #[filename / no], auto for maskConnComp.h5 @@ -159,7 +159,6 @@ mintpy.networkInversion.shadowMask = auto #[yes / no], auto for yes [if shadowM ## a. height_correlation - correct stratified tropospheric delay (Doin et al., 2009, J Applied Geop) ## b. pyaps - use Global Atmospheric Models (GAMs) data (Jolivet et al., 2011; 2014) ## ERA5 - ERA-5 from ECMWF [need to install pyaps3 on GitHub; recommended and turn ON by default] -## ECMWF - ERA-Interim from ECMWF [need to install pyaps on Caltech/EarthDef] ## MERRA - MERRA-2 from NASA [need to install pyaps on Caltech/EarthDef] ## NARR - NARR from NOAA [need to install pyaps on Caltech/EarthDef; recommended for N America] mintpy.troposphericDelay.method = auto #[pyaps / height_correlation / no], auto for pyaps @@ -171,7 +170,7 @@ mintpy.troposphericDelay.method = auto #[pyaps / height_correlation / no], auto ## directory, then MintPy applications will download the GAM files into the indicated directory. ## MintPy application will look for the GAM files in the directory before downloading a new one to ## prevent downloading multiple copies if you work with different dataset that cover the same date/time. -mintpy.troposphericDelay.weatherModel = auto #[ERA5 / ECMWF / MERRA / NARR], auto for ERA5 +mintpy.troposphericDelay.weatherModel = auto #[ERA5 / MERRA / NARR], auto for ERA5 mintpy.troposphericDelay.weatherDir = auto #[path2directory], auto for WEATHER_DIR or "./" ## Notes for height_correlation: @@ -241,6 +240,7 @@ mintpy.velocity.bootstrapCount = auto #[int>1], auto for 400, number of iterat ########## 11.1 geocode (post-processing) +# for input dataset in radar coordinates only # commonly used resolution in meters and in degrees (on equator) # 100, 60, 50, 30, 20, 10 # 0.000925926, 0.000555556, 0.000462963, 0.000277778, 0.000185185, 0.000092593 diff --git a/mintpy/dem_error.py b/mintpy/dem_error.py index 8a38ce1dc..9c57b55bb 100755 --- a/mintpy/dem_error.py +++ b/mintpy/dem_error.py @@ -226,33 +226,10 @@ def read_geometry(inps): return inps -def get_design_matrix4time_func(date_list, poly_order=2, step_func_dates=[]): - """get design matrix for temporal deformation model""" - # temporal baseline in the unit of years - tbase = ptime.date_list2tbase(date_list)[0] - tbase = np.array(tbase, dtype=np.float32) / 365.25 - - # 1. Polynomial - 2D matrix in size of (numDate, polyOrder+1) - A_def = np.ones((len(date_list), 1), np.float32) - for i in range(poly_order): - Ai = np.array(tbase**(i+1) / gamma(i+2), np.float32).reshape(-1, 1) - A_def = np.hstack((A_def, Ai)) - - # 2. Step function - 2D matrix in size of (numDate, len(step_func_dates)) - if step_func_dates: - t_steps = ptime.yyyymmdd2years(step_func_dates) - t = np.array(ptime.yyyymmdd2years(date_list)) - for t_step in t_steps: - Ai = np.array(t > t_step, np.float32).reshape(-1, 1) - A_def = np.hstack((A_def, Ai)) - - return A_def - - -def estimate_dem_error(ts0, A0, tbase, drop_date=None, phaseVelocity=False, num_step=0): +def estimate_dem_error(ts0, G0, tbase, drop_date=None, phaseVelocity=False, num_step=0): """Estimate DEM error with least square optimization. Parameters: ts0 : 2D np.array in size of (numDate, numPixel), original displacement time-series - A0 : 2D np.array in size of (numDate, model_num), design matrix in [A_geom, A_def] + G0 : 2D np.array in size of (numDate, model_num), design matrix in [G_geom, G_defo] tbase : 2D np.array in size of (numDate, 1), temporal baseline drop_date : 1D np.array in bool data type, mark the date used in the estimation phaseVelocity : bool, use phase history or phase velocity for minimization @@ -261,31 +238,31 @@ def estimate_dem_error(ts0, A0, tbase, drop_date=None, phaseVelocity=False, num_ corrected timeseries = tsOrig - delta_z_phase ts_res : 2D np.array in size of (numDate, numPixel), residual timeseries = tsOrig - delta_z_phase - defModel - Example: delta_z, ts_cor, ts_res = estimate_dem_error(ts, A, tbase, drop_date) + Example: delta_z, ts_cor, ts_res = estimate_dem_error(ts, G, tbase, drop_date) """ if len(ts0.shape) == 1: ts0 = ts0.reshape(-1, 1) if drop_date is None: drop_date = np.ones(ts0.shape[0], np.bool_) - # Prepare Design matrix A and observations ts for inversion - A = A0[drop_date, :] + # Prepare Design matrix G and observations ts for inversion + G = G0[drop_date, :] ts = ts0[drop_date, :] if phaseVelocity: tbase = tbase[drop_date, :] - A = np.diff(A, axis=0) / np.diff(tbase, axis=0) + G = np.diff(G, axis=0) / np.diff(tbase, axis=0) ts = np.diff(ts, axis=0) / np.diff(tbase, axis=0) # Inverse using L-2 norm to get unknown parameters X # X = [delta_z, constC, vel, acc, deltaAcc, ..., step1, step2, ...] - # equivalent to X = np.dot(np.dot(np.linalg.inv(np.dot(A.T, A)), A.T), ts) - # X = np.dot(np.linalg.pinv(A), ts) - X = linalg.lstsq(A, ts, cond=1e-15)[0] + # equivalent to X = np.dot(np.dot(np.linalg.inv(np.dot(G.T, G)), G.T), ts) + # X = np.dot(np.linalg.pinv(G), ts) + X = linalg.lstsq(G, ts, cond=1e-15)[0] # Prepare Outputs delta_z = X[0, :] - ts_cor = ts0 - np.dot(A0[:, 0].reshape(-1, 1), delta_z.reshape(1, -1)) - ts_res = ts0 - np.dot(A0, X) + ts_cor = ts0 - np.dot(G0[:, 0].reshape(-1, 1), delta_z.reshape(1, -1)) + ts_res = ts0 - np.dot(G0, X) step_def = None if num_step > 0: @@ -308,7 +285,7 @@ def estimate_dem_error(ts0, A0, tbase, drop_date=None, phaseVelocity=False, num_ return delta_z, ts_cor, ts_res, step_def -def correct_dem_error(inps, A_def): +def correct_dem_error(inps, G_defo): """Correct DEM error of input timeseries file""" # Read Date Info ts_obj = timeseries(inps.timeseries_file) @@ -340,15 +317,22 @@ def correct_dem_error(inps, A_def): print('skip pixels with NaN in ANY acquisitions') mask *= np.sum(np.isnan(ts_data), axis=0) == 0 + tcoh_file = os.path.join(os.path.dirname(inps.timeseries_file), 'temporalCoherence.h5') + if os.path.isfile(tcoh_file): + print('skip pixels with ZERO temporal coherence') + tcoh = readfile.read(tcoh_file)[0].flatten() + mask *= tcoh != 0. + del tcoh + if inps.rangeDist.size == 1: - A_geom = inps.pbase / (inps.rangeDist * inps.sinIncAngle) - A = np.hstack((A_geom, A_def)) + G_geom = inps.pbase / (inps.rangeDist * inps.sinIncAngle) + G = np.hstack((G_geom, G_defo)) ts_data = ts_data[:, mask] (delta_z_i, ts_cor_i, ts_res_i, - step_model_i) = estimate_dem_error(ts_data, A, + step_model_i) = estimate_dem_error(ts_data, G, tbase=tbase, drop_date=drop_date, phaseVelocity=inps.phaseVelocity, @@ -390,13 +374,13 @@ def correct_dem_error(inps, A_def): pbase = inps.pbase else: pbase = inps.pbase[:, i].reshape(-1, 1) - A_geom = pbase / (inps.rangeDist[i] * inps.sinIncAngle[i]) - A = np.hstack((A_geom, A_def)) + G_geom = pbase / (inps.rangeDist[i] * inps.sinIncAngle[i]) + G = np.hstack((G_geom, G_defo)) (delta_z_i, ts_cor_i, ts_res_i, - step_model_i) = estimate_dem_error(ts_data[:, i], A, + step_model_i) = estimate_dem_error(ts_data[:, i], G, tbase=tbase, drop_date=drop_date, phaseVelocity=inps.phaseVelocity, @@ -462,7 +446,7 @@ def main(iargs=None): start_time = time.time() inps = read_geometry(inps) - ## model setup info + # key msg msg = '-'*80 msg += '\ncorrect topographic phase residual (DEM error) (Fattahi & Amelung, 2013, IEEE-TGRS)' msg += '\nordinal least squares (OLS) inversion with L2-norm minimization on: phase' @@ -476,11 +460,14 @@ def main(iargs=None): msg += '\n'+'-'*80 print(msg) - A_def = get_design_matrix4time_func(date_list=timeseries(inps.timeseries_file).get_date_list(), - poly_order=inps.polyOrder, - step_func_dates=inps.stepFuncDate) + # get design matrix for temporal deformation model + model = dict() + model['polynomial'] = inps.polyOrder + model['step'] = inps.stepFuncDate + date_list = timeseries(inps.timeseries_file).get_date_list() + G_defo = timeseries.get_design_matrix4time_func(date_list, model) - inps = correct_dem_error(inps, A_def) + inps = correct_dem_error(inps, G_defo) m, s = divmod(time.time()-start_time, 60) print('time used: {:02.0f} mins {:02.1f} secs.'.format(m, s)) diff --git a/mintpy/dev/process_isce_stack_v1.py b/mintpy/dev/process_isce_stack_v1.py deleted file mode 100755 index 7694d5a94..000000000 --- a/mintpy/dev/process_isce_stack_v1.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/usr/bin/env python3 -############################################################ -# Program is part of MintPy # -# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi # -# Author: Zhang Yunjun, 2019-03-19 # -############################################################ - - -import os -import glob -import argparse -import subprocess -import configparser -import shutil -from mintpy.objects import sensor -from mintpy.utils import readfile, utils as ut - - -##################################################################################### -EXAMPLE = """example: - cd $SCRATCHDIR/KirishimaAlosAT424F620_630 - process_isce_stack.py -t KirishimaAlosAT424F620_630.txt --bsub -e $NOTIFICATIONEMAIL - process_isce_stack.py -t KirishimaAlosAT424F620_630.txt --start 2 --end 7 - process_isce_stack.py --reset -""" - -TEMPLATE = """template: -##------------------------------- ISCE/stripmapStack OPTIONS ------------------## -isce.processor = stripmapStack #[stripmapStack, topsStack] -isce.ALOS.fbd2fbs = yes -isce.demSNWE = 31.1, 32.8, 130.1, 131.9 #[S, N, W, E] in degree -isce.demFile = ${SCRATCHDIR}/KirishimaAlosAT424F620_630/DEM/gsi10m.dem -isce.azimuthLooks = 20 -isce.rangeLooks = 8 -isce.maxTempBaseline = 1800 -isce.maxPerpBaseline = 1800 -isce.referenceDate = 20080212 -isce.unwrapMethod = snaphu -isce.filtStrength = 0.5 -isce.applyWaterMask = yes -""" - -def create_parser(): - parser = argparse.ArgumentParser(description='HPC Wrapper for ISCE stack processor.', - formatter_class=argparse.RawTextHelpFormatter, - epilog=TEMPLATE+'\n'+EXAMPLE) - - parser.add_argument('-t','--template', dest='templateFile', type=str, help='template file') - parser.add_argument('--start', dest='startNum', type=int, help='Start submitting at named run number.') - parser.add_argument('--end', dest='endNum', type=int, help='End submitting at named run number.') - parser.add_argument('--reset', action='store_true', help='clean the directory before re-run.') - - parser.add_argument('--submit','--bsub', dest='bsub', action='store_true', help='submit this script as a job to generic queue.') - parser.add_argument('-r','--memory', type=int, default=2000, help='memory for bsub. Default: 2000') - parser.add_argument('-w','--walltime', type=str, default='48:00', help='wall time for bsub. Default: 48:00') - parser.add_argument('-e','--email', dest='email', help='email address to send notification when all jobs finished.') - return parser - - -def cmd_line_parse(iargs=None): - parser = create_parser() - inps = parser.parse_args(args=iargs) - - if not inps.reset and not inps.templateFile: - raise SystemExit('ERROR: at least one of the following arguments are required: -t/--template, --reset') - - if inps.templateFile: - inps.templateFile = os.path.abspath(inps.templateFile) - - #if any(not os.path.isdir(i) for i in ['configs', 'run_files']): - # msg = 'ERROR: NO configs or run_files folder found in the current directory!' - # raise SystemExit(msg) - return inps - - -def read_inps2dict(inps): - print('read options from template file: '+os.path.basename(inps.templateFile)) - template = readfile.read_template(inps.templateFile) - template = ut.check_template_auto_value(template) - - iDict = vars(inps) - key_prefix = 'isce.' - key_list = [i.split(key_prefix)[1] for i in template.keys() if i.startswith(key_prefix)] - for key in key_list: - iDict[key] = template[key_prefix+key] - - iDict['sensor'], iDict['projectName'] = sensor.project_name2sensor_name(iDict['templateFile']) - return iDict - - -##################################################################################### -def reset_process_directory(): - cmd_str="""------ Copy and paste the following the command to reset the process direction ---- -rm -r baselines/ configs/ coregSLC/ geom_reference/ Igrams/ merged/ offsets/ refineSecondaryTiming/ run_* SLC/ referenceShelve/ -cd download -rm -rf 20* AL* -mv ARCHIVED_FILES/* . -cd .. - """ - print(cmd_str) - return - - -def prepare_ALOS(iDict): - # uncompress tar/zip files - cmd = 'prepRawALOS.py -i ./download -o ./SLC -t "" ' - if iDict['ALOS.fbd2fbs']: - cmd += ' --dual2single ' - print(cmd) - os.system(cmd) - return 'run_unPackALOS' - - -def prepare_ALOS2(iDict): - # uncompress tar/zip files - cmd = 'prepSlcALOS2.py -i ./download -o ./SLC -t "" ' - print(cmd) - os.system(cmd) - return 'run_unPackALOS2' - - -def prepare_stack(iDict): - cmd = ('stackStripMap.py -W interferogram -s ./SLC -d {d} -u {u} -f {f} ' - ' -t {t} -b {b} -a {a} -r {r}').format(d=iDict['demFile'], - u=iDict['unwrapMethod'], - f=iDict['filtStrength'], - t=iDict['maxTempBaseline'], - b=iDict['maxPerpBaseline'], - a=iDict['azimuthLooks'], - r=iDict['rangeLooks']) - if 'referenceDate' in iDict.keys(): - cmd += ' -m {}'.format(iDict['referenceDate']) - if iDict['applyWaterMask']: - cmd += ' --applyWaterMask' - - #for downloaded data in SLC format - if iDict['sensor'] in ['Alos2']: - cmd += ' --nofocus --zero ' - - print(cmd) - status = subprocess.Popen(cmd, shell=True).wait() - if status is not 0: - raise RuntimeError('Error while runing stackStripMap.py.') - return status - - -def run_stack(iDict, run_file_dir='run_files'): - # check run_files and configs directory - if any(not os.path.isdir(i) for i in ['configs', 'run_files']): - msg = 'NO configs or run_files folder found in the current directory!' - raise NotADirectoryError(msg) - - # go to run_files directory - dir_orig = os.getcwd() - run_file_dir = os.path.join(dir_orig, run_file_dir) - os.chdir(run_file_dir) - - # copy job config file - config_file = os.path.join(dir_orig, 'job4{}.cfg'.format(iDict['processor'])) - if not os.path.isfile(config_file): - config_dir = os.path.expandvars('${MINTPY_HOME}/mintpy/defaults') - config_file_src = os.path.join(config_dir, os.path.basename(config_file)) - shutil.copy2(config_file_src, dir_orig) - print('copy job config file {} to the working directory: {}'.format(os.path.basename(config_file), dir_orig)) - else: - print('use existing job config file {} in the working directory: {}'.format(os.path.basename(config_file), dir_orig)) - - # read job config file - config = configparser.ConfigParser(delimiters='=') - config.read(config_file) - - # check start/end step number - num_step = len(config.sections()) - print('number of steps: {}'.format(num_step)) - if not iDict['startNum']: - iDict['startNum'] = 1 - if not iDict['endNum']: - iDict['endNum'] = num_step - - # submit job step by step - for step_num in range(iDict['startNum'], iDict['endNum']+1): - step_prefix = 'run_{:d}_'.format(step_num) - step_name = [i for i in config.sections() if i.startswith(step_prefix)][0] - - cmd = 'split_jobs.py {f} -r {r} -w {w}'.format(f=step_name, - r=config[step_name]['memory'], - w=config[step_name]['walltime']) - print(cmd) - status = subprocess.Popen(cmd, shell=True).wait() - if status is not 0: - raise RuntimeError("Error in step {}".format(step_name)) - - # go back to original directory - os.chdir(dir_orig) - return status - - -def write_job_file(iDict): - """Write job file to submit process_isce_stack.py as a job""" - # command line - cmd = 'process_isce_stack.py -t {}'.format(iDict['templateFile']) - if iDict['startNum']: - cmd += ' --start {} '.format(iDict['startNum']) - if iDict['endNum']: - cmd += ' --end {} '.format(iDict['endNum']) - print('run the following command in bsub mode') - print(cmd) - - # write job file - job_dir = os.getcwd() - job_file = os.path.join(job_dir, 'z_input_process_isce_stack.job') - job_name = sensor.project_name2sensor_name(iDict['templateFile'])[1] - with open(job_file, 'w') as f: - f.write('#! /bin/tcsh\n') - f.write('#BSUB -J {}\n'.format(job_name)) - f.write('#BSUB -P insarlab\n') - f.write('#BSUB -o z_output_{}.%J.o\n'.format(job_name)) - f.write('#BSUB -e z_output_{}.%J.e\n'.format(job_name)) - f.write('#BSUB -W {}\n'.format(iDict['walltime'])) - f.write('#BSUB -q general\n') - f.write('#BSUB -n 1\n') - f.write('#BSUB -R "rusage[mem={}]"\n'.format(iDict['memory'])) - if iDict['email']: - f.write('#BSUB -u {}\n'.format(iDict['email'])) - f.write('#BSUB -N\n') - - # write cd work directory - f.write('\n') - f.write('cd {}\n'.format(job_dir)) - f.write('{}\n'.format(cmd)) - print('finished writing job file: {}'.format(job_file)) - return job_file - - -def copy_referenceShelve(iDict, out_dir='referenceShelve'): - """Copy shelve files into root directory""" - proj_dir = os.path.abspath(os.getcwd()) - - # check folders - shelve_dir = os.path.join(proj_dir, out_dir) - if os.path.isdir(shelve_dir) : - print('referenceShelve folder already exists: {}'.format(shelve_dir)) - return - else: - print('create directory: {}'.format(shelve_dir)) - os.makedirs(shelve_dir) - - # check files - shelve_files = ['data.bak','data.dat','data.dir'] - if all(os.path.isfile(os.path.join(shelve_dir, i)) for i in shelve_files): - print('all shelve files already exists') - return - else: - date_list = sorted([os.path.basename(i) for i in glob.glob('SLC/*')]) - m_date = iDict.get('referenceDate', date_list[0]) - slc_dir = os.path.join(proj_dir, 'SLC/{}'.format(m_date)) - for shelve_file in shelve_files: - shelve_file = os.path.join(slc_dir, shelve_file) - shutil.copy2(shelve_file, shelve_dir) - print('copy {} to {}'.format(shelve_file, shelve_dir)) - return - - -##################################################################################### -def main(iargs=None): - inps = cmd_line_parse() - - if inps.reset: - reset_process_directory() - return - - iDict = read_inps2dict(inps) - - job_file = write_job_file(iDict) - if iDict['bsub']: - cmd = 'bsub < {}'.format(job_file) - print(cmd) - os.system(cmd) - return - - # copy template file - print('copy template file to the current directory') - try: - shutil.copy2(iDict['templateFile'], os.getcwd()) - except shutil.SameFileError: - # code when Exception occur - pass - - if not inps.startNum: - # prepare RAW/SLC data - if iDict['sensor'] == 'Alos': - run_file = prepare_ALOS(iDict) - elif iDict['sensor'] == 'Alos2': - run_file = prepare_ALOS2(iDict) - else: - raise ValueError('unsupported sensor: {}'.format(iDict['sensor'])) - - # submit run file as jobs - cmd = 'split_jobs.py {} -r 2000 -w 0:30'.format(run_file) - print(cmd) - os.system(cmd) - - # prepare stack - prepare_stack(iDict) - - # submit stack as jobs - run_stack(iDict) - - copy_referenceShelve(iDict) - - return - -##################################################################################### -if __name__ == '__main__': - main() diff --git a/mintpy/dev/run_isce_stack.py b/mintpy/dev/run_isce_stack.py new file mode 100755 index 000000000..945b43dee --- /dev/null +++ b/mintpy/dev/run_isce_stack.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python3 +############################################################ +# Program is part of MintPy # +# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi # +# Author: Zhang Yunjun, Mar 2019 # +############################################################ + + +import os +import sys +import glob +import datetime as dt +import shutil +import argparse +import subprocess +import numpy as np +from mintpy.objects import sensor +from mintpy.utils import readfile + +OMP_NUM_THREADS = int(os.environ.get('OMP_NUM_THREADS', 1)) + +KEY_PREFIX = 'isce.' + +##################################################################################### +EXAMPLE = """example: + run_isce_stack.py AtacamaSenAT120.txt # prepare run_files + run_isce_stack.py AtacamaSenAT120.txt --run # prepare / execute run_files + + # continue processing from certain step [only when DEM and run_files/configs already exist] + run_isce_stack.py AtacamaSenAT120.txt --start 2 --end 7 + + # clean up directory before re-processing + run_isce_stack.py AtacamaSenAT120.txt --reset +""" + +TEMPLATE = """template: +##------------------------------- ISCE tops/stripmapStack ------------------------------## +isce.processor = stripmapStack #[stripmapStack, topsStack], auto for topsStack +isce.workflow = interferogram #[slc / correlation / interferogram / offset], auto for interferogram +isce.demSNWE = 31.1, 32.8, 130.1, 131.9 #[S, N, W, E] in degree, auto for none +isce.demFile = ./DEM/gsi10m.dem.wgs84 #DEM file name, auto for none (generate on the fly) +isce.demSource = srtm1 #[srtm1, srtm3, nasadem, gsi_dehm], auto for srtm1 +isce.demFillValue = 0 #[0 / 1 / -32768], value used to fill missing DEMs, auto for -32768 +isce.boundingBox = none #[S, N, W, E] in degree, auto for none +isce.referenceDate = none #[20150101 / no], auto for none (1st date) +isce.azimuthLooks = 3 #[int], auto for 3 +isce.rangeLooks = 9 #[int], auto for 9 +isce.filtStrength = 0.5 #[0.0-1.0], auto for 0.5 +isce.unwrapMethod = snaphu #[snaphu / icu], auto for snaphu +isce.useGPU = no #[yes / no], auto for no +isce.numThread = 8 #[int>=1], number of total threads, auto for 8 + +##----------for topsStack only: +isce.coregistration = geometry #[geometry / NESD], auto for geometry +isce.swathNum = 1,2 #[1,2,3], auto for '1,2,3' +isce.numConnection = 5 #[int>=1], auto for 3 +isce.orbitDir = ~/bak/aux/aux_poeorb/ #Directory with all orbit files +isce.auxDir = ~/bak/aux/aux_cal/ #Directory with all aux files +isce.startDate = none #[20140825 / no], auto for none (1st date) +isce.endDate = none #[20190622 / no], auto for none (last date) + +##----------for stripmapStack only: +## Sensors with zero doppler SLC: ALOS2 +## link: https://github.com/isce-framework/isce2/blob/master/components/isceobj/StripmapProc/Factories.py#L61 +isce.zeroDopper = no #[yes / no], use zero doppler geometry for processing, auto for no +isce.focus = no #[yes / no], do focus, auto for yes (for RAW data) +isce.ALOS.fbd2fbs = yes #[yes / no], auto for yes, convert FBD to FBS for ALOS-1 +isce.ALOS2.polarization = HH #[HH / VV], auto for HH +isce.maxTempBaseline = 1800 # auto for 1800 days +isce.maxPerpBaseline = 1800 # auto for 1800 meters +isce.applyWaterMask = yes # auto for yes +""" + + +AUTO_DICT = { + 'isce.processor' : 'topsStack', + 'isce.workflow' : 'interferogram', + 'isce.demSNWE' : None, + 'isce.demFile' : None, + 'isce.demSource' : 'srtm1', + 'isce.demFillValue' : '-32768', + 'isce.boundingBox' : None, + 'isce.referenceDate' : None, + 'isce.azimuthLooks' : '3', + 'isce.rangeLooks' : '9', + 'isce.filterStrength' : '0.5', + 'isce.unwrapMethod' : 'snaphu', + 'isce.useGPU' : False, + 'isce.numThread' : 8, + + #for topsStack only + 'isce.coregistration' : 'geometry', + 'isce.swathNum' : '1,2,3', + 'isce.numConnection' : '3', + 'isce.orbitDir' : '~/bak/aux/aux_poeorb/', + 'isce.auxDir' : '~/bak/aux/aux_cal/', + 'isce.startDate' : None, + 'isce.endDate' : None, + + #for stripmapStack only + 'isce.zeroDoppler' : False, + 'isce.focus' : True, + 'isce.ALOS.fbd2fbs' : True, + 'isce.ALOS2.polarization' : 'HH', + 'isce.maxTempBaseline' : '1800', + 'isce.maxPerpBaseline' : '1800', + 'isce.applyWaterMask' : True, +} + + +##################################################################################### + +def create_parser(): + parser = argparse.ArgumentParser(description='Driver script of ISCE-2 (tops/stripmap) stack processor.', + formatter_class=argparse.RawTextHelpFormatter, + epilog=TEMPLATE+'\n'+EXAMPLE) + + parser.add_argument('templateFile', type=str, help='template file') + parser.add_argument('--run', dest='runStack', action='store_true', help='run/execute the run files') + parser.add_argument('--reset', action='store_true', help='clean the directory before re-run.') + + step = parser.add_argument_group('Steps to run','This enables --run option automatically') + parser.add_argument('--start', dest='startStep', type=int, help='Start processing at named run number.') + parser.add_argument('--end', dest='endStep', type=int, help='End processing at named run number.') + + return parser + + +def cmd_line_parse(iargs=None): + parser = create_parser() + inps = parser.parse_args(args=iargs) + + if not inps.reset and not inps.templateFile: + raise SystemExit('ERROR: at least one of the following arguments are required: -t/--template, --reset') + + if inps.templateFile: + inps.templateFile = os.path.abspath(inps.templateFile) + + # --start/end + if inps.startStep or inps.endStep: + inps.runStack = True + + #if any(not os.path.isdir(i) for i in ['configs', 'run_files']): + # msg = 'ERROR: NO configs or run_files folder found in the current directory!' + # raise SystemExit(msg) + return inps + + +##################################################################################### + +def fill_and_translate_template_auto_value(tempDict, autoDict=AUTO_DICT): + """Fill the missing template option with default value and + translate special values + """ + + # 1. translate auto value: remove them, then fill them in step 2 + for key, value in iter(tempDict.items()): + if value == 'auto': + tempDict.pop(key) + + # 2. fill missing options + for key in autoDict.keys(): + if key not in tempDict.keys(): + tempDict[key] = autoDict[key] + + # 3. translate special values: yes/no/true/false/none + specialValues = {'yes' : True, + 'true' : True, + 'no' : False, + 'false': False, + 'none' : None, + } + for key, value in tempDict.items(): + value = str(value).lower() + if value in specialValues.keys(): + tempDict[key] = specialValues[value] + + return tempDict + + +def read_inps2dict(inps): + print('read options from template file: '+os.path.basename(inps.templateFile)) + template = readfile.read_template(inps.templateFile) + template = fill_and_translate_template_auto_value(template) + + # merge template and inps into iDict + iDict = vars(inps) + key_list = [i.split(KEY_PREFIX)[1] for i in template.keys() if i.startswith(KEY_PREFIX)] + for key in key_list: + iDict[key] = template[KEY_PREFIX + key] + # add extra info: + iDict['sensor'], iDict['projectName'] = sensor.project_name2sensor_name(iDict['templateFile']) + + # check + if iDict['processor'] not in ['topsStack', 'stripmapStack']: + msg = 'un-recognized ISCE-2 stack processor: {}'.format(iDict['processor']) + msg += 'supported processors: [topsStack, stripmapStack]' + raise ValueError(msg) + + # expand all paths to abspath + for key in iDict.keys(): + if key.endswith(('File', 'Dir')) and iDict[key]: + iDict[key] = os.path.expanduser(iDict[key]) + iDict[key] = os.path.expandvars(iDict[key]) + iDict[key] = os.path.abspath(iDict[key]) + + # --text_cmd + if iDict['processor'] == 'topsStack': + iDict['text_cmd'] = 'export PATH=${PATH}:${ISCE_STACK}/topsStack' + else: + iDict['text_cmd'] = 'export PATH=${PATH}:${ISCE_STACK}/stripmapStack' + + return iDict + + +##################################################################################### +def copy_referenceShelve(iDict, out_dir='referenceShelve'): + """Copy shelve files into root directory""" + proj_dir = os.path.abspath(os.getcwd()) + + # check folders + shelve_dir = os.path.join(proj_dir, out_dir) + if os.path.isdir(shelve_dir) : + print('referenceShelve folder already exists: {}'.format(shelve_dir)) + return + else: + print('create directory: {}'.format(shelve_dir)) + os.makedirs(shelve_dir) + + # check files + shelve_files = ['data.bak','data.dat','data.dir'] + if all(os.path.isfile(os.path.join(shelve_dir, i)) for i in shelve_files): + print('all shelve files already exists') + return + else: + date_list = sorted([os.path.basename(i) for i in glob.glob('SLC/*')]) + m_date = iDict.get('referenceDate', date_list[0]) + slc_dir = os.path.join(proj_dir, 'SLC/{}'.format(m_date)) + for shelve_file in shelve_files: + shelve_file = os.path.join(slc_dir, shelve_file) + shutil.copy2(shelve_file, shelve_dir) + print('copy {} to {}'.format(shelve_file, shelve_dir)) + return + + +def reset_process_directory(processor='topsStack'): + """Reset processing directory in order to re-run. + """ + + if processor == 'topsStack': + cmd_str = """------ Copy and paste the following the command to reset the process direction ---- + +""" + + elif processor == 'stripmapStack': + cmd_str="""------ Copy and paste the following the command to reset the process direction ---- +rm -r baselines/ configs/ coregSLC/ geom_reference/ Igrams/ merged/ offsets/ refineSecondaryTiming/ run_* SLC/ referenceShelve/ +cd download +rm -rf 20* AL* +mv ARCHIVED_FILES/* . +cd .. + """ + + print(cmd_str) + return + + +##################################################################################### + +def run_sh_file(sh_file, text_cmd=None): + """Run shell file""" + print('running {}'.format(os.path.basename(sh_file))) + + # change file permission + cmd = 'chmod +x {}'.format(sh_file) + print(cmd) + os.system(cmd) + + # execute + cmd = '' + if text_cmd: + cmd += text_cmd + '; ' + cmd += sh_file + print(cmd) + status = subprocess.Popen(cmd, shell=True).wait() + if status != 0: + raise RuntimeError("Error in {}".format(sh_file)) + print('finished running {} with status {}'.format(sh_file, status)) + + return status + + +def prepare_dem(iDict): + """Prepare DEM for stack processing""" + + # DEM dir + dir_orig = os.path.abspath(os.getcwd()) + dem_dir = os.path.join(dir_orig, 'DEM') + os.makedirs(dem_dir, exist_ok=True) + + if iDict['demFile'] and os.path.isfile(iDict['demFile']): + print('input DEM file exists: {}, skip re-generation.'.format(iDict['demFile'])) + return iDict + + else: + dem_files = glob.glob(os.path.join(dem_dir, '*.dem.wgs84')) + if len(dem_files) > 0 and os.path.isfile(dem_files[0]): + print('use existing DEM file: {}'.format(dem_files[0])) + iDict['demFile'] = dem_files[0] + return iDict + + else: + print('genenrating new DEM ...') + + # auto demSNWE from bbox + if not iDict['demSNWE']: + if iDict['boundingBox']: + bbox = [float(i) for i in iDict['boundingBox'].split(',')] + dem_bbox = [bbox[0]-3, bbox[1]+3, bbox[2]-3, bbox[3]+3] + else: + raise ValueError('required demSNWE not found!') + else: + dem_bbox = [float(i) for i in iDict['demSNWE'].split(',')] + + + os.chdir(dem_dir) + print('go to directory', dem_dir) + + # compose command line for DEM generation with dem(_gsi).py + if iDict['demSource'] == 'gsi_dehm': + # DEHM from GSI Japan + cmd = 'dem_gsi.py --bbox {}'.format(' '.join(str(i) for i in dem_bbox)) + dem_file = os.path.join(dem_dir, 'gsi10m.dem.wgs84') + + else: + # isce/dem.py takes integer input only + dem_bbox = [np.rint(i).astype(int) for i in dem_bbox] + cmd = 'dem.py --action stitch --bbox {}'.format(' '.join(str(i) for i in dem_bbox)) + cmd += ' --report --source {}'.format(iDict['demSource'].split('srtm')[1]) + cmd += ' --correct --filling --filling_value {}'.format(iDict['demFillValue']) + dem_file = os.path.join(dem_dir, 'demLat*.dem.wgs84') + + # run command line + print(cmd) + status = subprocess.Popen(cmd, shell=True).wait() + if status != 0: + raise RuntimeError("Error in DEM generation.") + + # clean up + cmd = 'rm -f demLat*.dem demLat*.dem.xml demLat*.dem.vrt' + print(cmd) + os.system(cmd) + + cmd = 'fixImageXml.py -i demLat*.dem.wgs84 -f' + print(cmd) + os.system(cmd) + + # get DEM filename + dem_files = glob.glob(dem_file) + if len(dem_files) > 0: + iDict['demFile'] = dem_files[0] + else: + raise FileNotFoundError('DEM file not found in {}'.format(dem_file)) + + # go back to the original directory + os.chdir(dir_orig) + print('go to directory', dir_orig) + return iDict + + +def prepare_ALOS(iDict): + # uncompress tar/zip files + cmd = 'prepRawALOS.py -i ./download -o ./SLC -t "" ' + + # convert ALOS PALSAR FBD mode to FBS mode + if iDict.get('ALOS.fbd2fbs', True): + cmd += ' --dual2single ' + + print(cmd) + os.system(cmd) + + # run unPack script + run_sh_file('run_unPackALOS', text_cmd=iDict['text_cmd']) + return + + +def prepare_ALOS2(iDict): + # uncompress tar/zip files + cmd = 'prepSlcALOS2.py -i ./download -o ./SLC -t ""' + + if iDict.get('ALOS2.polarization', None): + cmd += ' --polarization {}'.format(iDict['ALOS2.polarization']) + + print(cmd) + os.system(cmd) + + # run unPack script + run_sh_file('run_unPackALOS2', text_cmd=iDict['text_cmd']) + return + + +def prepare_stack(iDict): + """Call stack*.py to generate run_files and config folders""" + + # load corresponding stack processor module + proc = iDict['processor'] + if proc == 'topsStack': + from topsStack import stackSentinel as prep_stack + else: + from stripmapStack import stackStripMap as prep_stack + scp_name = os.path.basename(prep_stack.__file__) + + ##### compose the command line script + ##----------required + # common + iargs = [ + '--slc_directory', os.path.abspath('./SLC'), + '--workflow', iDict['workflow'], + '--dem', iDict['demFile'], + '--azimuth_looks', iDict['azimuthLooks'], + '--range_looks', iDict['rangeLooks'], + '--filter_strength', iDict['filtStrength'], + '--unw_method', iDict['unwrapMethod'], + ] + + # unique + if proc == 'topsStack': + iargs += [ + '--coregistration', iDict['coregistration'], + '--num_connections', iDict['numConnection'], + '--aux_directory', iDict['auxDir'], + '--orbit_directory', iDict['orbitDir'], + ] + + else: + iargs += [ + '--time_threshold', iDict['maxTempBaseline'], + '--baseline_threshold', iDict['maxPerpBaseline'], + ] + + ##----------optional + # common + if iDict['referenceDate']: + iargs += ['--reference_date', iDict['referenceDate']] + + if iDict['boundingBox']: + iargs += ['--bbox', ' '.join(i for i in iDict['boundingBox'].split(','))] + + if iDict['useGPU']: + iargs += ['--useGPU'] + + # unique + if proc == 'topsStack': + if iDict['startDate']: + t0 = dt.datetime.strptime(iDict['startDate'], '%Y%m%d') + iargs += ['--start_date', t0.strftime('%Y-%m-%d')] + + if iDict['endDate']: + t1 = dt.datetime.strptime(iDict['endDate'], '%Y%m%d') + iargs += ['--stop_date', t1.strftime('%Y-%m-%d')] + + if iDict['swathNum']: + iargs += ['--swath_num', ' '.join(i for i in iDict['swathNum'].split(','))] + + if iDict['numThread']: + num_proc = int(int(iDict['numThread']) / OMP_NUM_THREADS) + num_proc = max(num_proc, 1) + iargs += ['--num-process', str(num_proc)] + + else: + if iDict['applyWaterMask']: + iargs += ['--applyWaterMask'] + + if iDict['sensor'] in ['Alos2']: + iargs += ['--nofocus', '--zero'] + + ##### run the command line script + print(scp_name, ' '.join(iargs)) + prep_stack.main(iargs) + + return + + +def run_stack(iDict, run_file_dir='run_files'): + """ + """ + + # check dir: run_files and configs + for dir_name in ['configs', 'run_files']: + if not os.path.isdir(dir_name): + raise NotADirectoryError('NO {} folder found in the current directory!'.format(dir_name)) + + # go to run_files directory + dir_orig = os.path.abspath(os.getcwd()) + run_file_dir = os.path.join(dir_orig, run_file_dir) + os.chdir(run_file_dir) + print('go to directory: {}'.format(run_file_dir)) + + # remove un-necessary *.job files + job_files = glob.glob(os.path.join(run_file_dir, 'run_*_*.job')) + for job_file in job_files: + os.remove(job_file) + print('remove file: {}'.format(job_file)) + + # grab all run files + run_files = glob.glob(os.path.join(run_file_dir, 'run_[0-9]_*')) # stripmapStack + run_files += glob.glob(os.path.join(run_file_dir, 'run_[0-9][0-9]_*')) # topsStack + run_files = sorted([i for i in run_files if '.' not in os.path.basename(i)]) # clean up + + # start/end step + # note that python list starts from zero + num_step = len(run_files) + for key, val in zip(['startStep', 'endStep'], [1, num_step]): + if not iDict[key]: + iDict[key] = val + else: + iDict[key] = int(iDict[key]) + step0 = iDict['startStep'] - 1 + step1 = iDict['endStep'] - 1 + print('number of steps: {}'.format(num_step)) + print('steps to run: {}'.format(run_files[step0:step1+1])) + + # submit job step by step + for step_ind in range(step0, step1+1): + print('\n\n'+'#'*50) + run_sh_file(run_files[step_ind], text_cmd=iDict['text_cmd']) + + # go back to original directory + os.chdir(dir_orig) + print('go to directory: {}'.format(dir_orig)) + return + + + +##################################################################################### +def main(iargs=None): + inps = cmd_line_parse(iargs) + iDict = read_inps2dict(inps) + + # --reset option + if inps.reset: + reset_process_directory(iDict['processor']) + return + + # --start option + if inps.startStep: + status = run_stack(iDict) + return status + + # prepare DEM + iDict = prepare_dem(iDict) + + # prepare RAW/SLC data (for stripmapStack only) + if iDict['processor'] == 'stripmapStack': + + if iDict['sensor'] == 'Alos': + run_file = prepare_ALOS(iDict) + + elif iDict['sensor'] == 'Alos2': + run_file = prepare_ALOS2(iDict) + + else: + raise ValueError('unsupported sensor: {}'.format(iDict['sensor'])) + + # prepare stack processing + prepare_stack(iDict) + + # run stack processing + if inps.runStack: + run_stack(iDict) + + return + + +##################################################################################### +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/mintpy/examples/input_files/FernandinaSenDT128.txt b/mintpy/examples/input_files/FernandinaSenDT128.txt new file mode 100644 index 000000000..69e80d58f --- /dev/null +++ b/mintpy/examples/input_files/FernandinaSenDT128.txt @@ -0,0 +1,38 @@ +# vim: set filetype=cfg: +##------------------------------- ISCE/topsStack ----------------------## +#ssaraopt = --platform=SENTINEL-1A,SENTINEL-1B -r 128 -f 589,590,591,592,593 -e 2017-07-01 +#sentinelStack.boundingBox = '-1 0.15 -91.6 -90.9' +#sentinelStack.subswath = 1 2 # comment +#sentinelStack.numConnections = 5 # comment +#sentinelStack.azimuthLooks = 5 # comment +#sentinelStack.rangeLooks = 15 # comment +#sentinelStack.filtStrength = 0.2 # comment +#sentinelStack.unwMethod = snaphu # comment +#sentinelStack.coregistration = auto # comment +#subset.y0:y1,x0:x1 = 1150:1600,1070:1670 + + +##-------------------------------- MintPy -----------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = isce +##---------for ISCE only: +mintpy.load.metaFile = ../reference/IW*.xml +mintpy.load.baselineDir = ../baselines +##---------interferogram datasets: +mintpy.load.unwFile = ../merged/interferograms/*/filt_*.unw +mintpy.load.corFile = ../merged/interferograms/*/filt_*.cor +mintpy.load.connCompFile = ../merged/interferograms/*/filt_*.unw.conncomp +##---------geometry datasets: +mintpy.load.demFile = ../merged/geom_reference/hgt.rdr +mintpy.load.lookupYFile = ../merged/geom_reference/lat.rdr +mintpy.load.lookupXFile = ../merged/geom_reference/lon.rdr +mintpy.load.incAngleFile = ../merged/geom_reference/los.rdr +mintpy.load.azAngleFile = ../merged/geom_reference/los.rdr +mintpy.load.shadowMaskFile = ../merged/geom_reference/shadowMask.rdr +mintpy.load.waterMaskFile = None + +mintpy.reference.lalo = -0.30,-91.43 +mintpy.topographicResidual.stepFuncDate = 20170910,20180613 #eruption dates +mintpy.deramp = linear + diff --git a/mintpy/examples/input_files/GalapagosAlosAT133.template b/mintpy/examples/input_files/GalapagosAlosAT133.template new file mode 100644 index 000000000..b3dc6993e --- /dev/null +++ b/mintpy/examples/input_files/GalapagosAlosAT133.template @@ -0,0 +1,78 @@ +# vim: set filetype=cfg: +################### Job Submission Setting ################# +walltimeSLC = 5:00 +walltimeCoregist = 3:00 +walltimeProcessFilt = 15:00 +walltimeProcessDone = 8:00 +memorySLC = 12000 +memoryCoregist = 15000 # change this for more frames +memoryProcessFilt = 12000 +memoryProcessDone = 8000 + +########### Data Download and Focusing ##################### +track = 133 +startFrame = 7160 +endFrame = 7180 +startDate = 070115 +endDate = 110313 +masterDate = 100610 +raw2slc_OrbitType = HDR +ssaraopt = --platform=Alos -r 133 -f 7160,7170,7180 --beamMode FBS,FBD -s 2007-01-01 + +########### Select Pairs / Network ######################### +select.network.method = all +select.network.perpBaseMax = 1800 +select.network.lengthDayMax = 1800 +select.network.lengthDayMin = 0 +select.network.seasonal = yes +select.network.dopOverlapThresh = 15 + + +############### 2 Pass InSAR - ROI_PAC ##################### +DEM = $SC/GalapagosT133F7160_7180AlosA/DEM/srtm1.dem +DEMg = $SC/GalapagosT133F7160_7180AlosA/DEM/srtm1_60m.dem +flattening = orbit +OrbitType = HDR +Rlooks_sim = 2 +Rlooks_int = 8 +Rlooks_unw = 8 +unw_method = snaphu +snap_conncomthresh = 60 +Filt_method = psfilt_adapt_filt +FilterStrength = 0.5/4 +UnwrappedThreshold = 0.10 +maskingwith0height = TRUE +ampmagFineopt = 120/120/256/64 +meter_per_cycle = 6.283 +pixel_ratio = 2 +cleanopt = 1 + + + + +################ Time Series - MintPy ###################### +mintpy.load.processor = roipac #[isce,roipac,gamma,], auto for isce +mintpy.load.updateMode = auto #[yes / no], auto for yes, skip re-loading if HDF5 files are complete +mintpy.load.compression = auto #[gzip / lzf / no], auto for no [recommended]. +##---------interferogram datasets: +mintpy.load.unwFile = $GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/interferograms/*/filt*.unw +mintpy.load.corFile = $GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/interferograms/*/filt*.cor +mintpy.load.connCompFile = $GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/interferograms/*/filt*snap_connect.byt +mintpy.load.intFile = None #$GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/interferograms/*/filt*rlks.int +##---------geometry datasets: +mintpy.load.demFile = $GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/geom_master/radar_*rlks.hgt +mintpy.load.lookupYFile = $GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/geom_master/geomap_*rlks.trans +mintpy.load.lookupXFile = $GALAPAGOS_DIR/GalapagosAlosAT133/ROIPAC/geom_master/geomap_*rlks.trans +##---------extra metadata to be loaded (to adjust for the bias in ROIPAC lookup table) +SUBSET_XMIN = 1 +SUBSET_YMIN = 15 + +mintpy.subset.yx = 800:3700,0:1400 +mintpy.reference.lalo = -0.71, -91.31 #-0.31, -91.22 +mintpy.unwrapError.method = bridging +mintpy.unwrapError.waterMaskFile = waterMask.h5 +mintpy.unwrapError.ramp = linear +mintpy.network.coherenceBased = yes +mintpy.network.aoiLALO = -0.84:-0.81,-91.16:-91.12 +mintpy.topographicResidual.stepFuncDate = 20080529, 20090420, 20100519 #Cerro Azul, Fernandina, Alcedo, +mintpy.deramp = linear diff --git a/mintpy/examples/input_files/GalapagosEnvA2T061.txt b/mintpy/examples/input_files/GalapagosEnvA2T061.txt new file mode 100644 index 000000000..c91a17f3c --- /dev/null +++ b/mintpy/examples/input_files/GalapagosEnvA2T061.txt @@ -0,0 +1,18 @@ +# vim: set filetype=cfg: +##-------------------------------- MintPy ------------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = gamma +##---------interferogram datasets: +mintpy.load.unwFile = ../GAMMA/interferograms/*/diff*rlks.unw +mintpy.load.corFile = ../GAMMA/interferograms/*/*filt*rlks.cor +mintpy.load.connCompFile = None +##---------geometry datasets: +mintpy.load.demFile = ../GAMMA/geometry/sim*rlks.rdc.dem +mintpy.load.lookupYFile = ../GAMMA/geometry/sim*rlks.UTM_TO_RDC +mintpy.load.lookupXFile = ../GAMMA/geometry/sim*rlks.UTM_TO_RDC + +mintpy.subset.yx = 200:2000,200:1562 +mintpy.reference.lalo = -0.31,-91.22 +mintpy.deramp = linear + diff --git a/mintpy/examples/input_files/GalapagosSenDT128.template b/mintpy/examples/input_files/GalapagosSenDT128.template new file mode 100644 index 000000000..d52690735 --- /dev/null +++ b/mintpy/examples/input_files/GalapagosSenDT128.template @@ -0,0 +1,42 @@ +# vim: set filetype=cfg: +##----------------------------- SentinelStack/ISCE ---------------------## +cleanopt = 1 # [ 0 / 1 / 2 / 3 4] 0: no cleaning, 1: largest files, 2: merged/etc, PROCESS dirs, 3: SLC,RAW, 4: everything +ssaraopt = --platform=SENTINEL-1A,SENTINEL-1B -r 128 -f 587,588,589,590,591,592,593 -e 2018-07-01 +processor = isce +sentinelStack.demDir = /nethome/famelung/Sentinel/GalapagosT128SenVVD/DEM +sentinelStack.boundingBox = '-1 0.15 -91.6 -90.9' +sentinelStack.subswath = 1 2 # comment +sentinelStack.numConnections = 5 # comment +sentinelStack.azimuthLooks = 5 # comment +sentinelStack.rangeLooks = 15 # comment +sentinelStack.filtStrength = 0.2 # comment +sentinelStack.unwMethod = snaphu # comment +sentinelStack.coregistration = auto # comment + + + +##-------------------------------- MintPy -----------------------------## +mintpy.load.processor = isce +##---------for ISCE only: +mintpy.load.metaFile = $GALAPAGOS_DIR/GalapagosSenDT128/master/IW*.xml +mintpy.load.baselineDir = $GALAPAGOS_DIR/GalapagosSenDT128/baselines +##---------interferogram datasets: +mintpy.load.unwFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/interferograms/*/filt_*.unw +mintpy.load.corFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/interferograms/*/filt_*.cor +mintpy.load.connCompFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/interferograms/*/filt_*.unw.conncomp +##---------geometry datasets: +mintpy.load.demFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/hgt.rdr +mintpy.load.lookupYFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/lat.rdr +mintpy.load.lookupXFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/lon.rdr +mintpy.load.incAngleFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/los.rdr +mintpy.load.azAngleFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/los.rdr +mintpy.load.shadowMaskFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/shadowMask.rdr +mintpy.load.waterMaskFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/geom_master/waterMask.rdr +mintpy.load.bperpFile = $GALAPAGOS_DIR/GalapagosSenDT128/merged/baseline_grid/*/bperp.rdr + +mintpy.subset.yx = 400:2400,0:1700 +mintpy.reference.lalo = -0.31,-91.22 +mintpy.network.endDate = 20180626 +mintpy.unwrapError.method = bridging+phase_closure #[bridging / phase_closure / no], auto for no +mintpy.topographicResidual.stepFuncDate = 20150524,20150616,20170321,20170910,20180613 #Wolf,Wolf,CerroAzul,Fernandina,Fernandina +mintpy.deramp = linear diff --git a/mintpy/examples/input_files/KirishimaAlos2DT23F2970.template b/mintpy/examples/input_files/KirishimaAlos2DT23F2970.template new file mode 100644 index 000000000..4a39526db --- /dev/null +++ b/mintpy/examples/input_files/KirishimaAlos2DT23F2970.template @@ -0,0 +1,48 @@ +# vim: set filetype=cfg: +##------------------------------- ISCE/stripmapStack OPTIONS ------------------## +isce.processor = stripmapStack #[stripmapStack, topsStack] +isce.demSNWE = 31, 33, 130, 132 #[S, N, W, E] in degree +isce.demFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/DEM/gsi10m.dem.wgs84 +isce.azimuthLooks = 10 +isce.rangeLooks = 8 +isce.maxTempBaseline = 400 +isce.maxPerpBaseline = 200 +isce.unwrapMethod = snaphu +isce.filtStrength = 0.5 +isce.applyWaterMask = yes + + +##------------------------------- mintpy OPTIONS -------------------------------## +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = isce +##---------for ISCE only: +mintpy.load.metaFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/masterShelve/data.dat +mintpy.load.baselineDir = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/baselines +##---------interferogram datasets: +mintpy.load.unwFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/Igrams/*/filt_*.unw +mintpy.load.corFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/Igrams/*/filt_*.cor +mintpy.load.connCompFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/Igrams/*/filt_*.unw.conncomp +##---------geometry datasets: +mintpy.load.demFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/hgt.rdr +mintpy.load.lookupYFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/lat.rdr +mintpy.load.lookupXFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/lon.rdr +mintpy.load.incAngleFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/los.rdr +mintpy.load.azAngleFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/los.rdr +mintpy.load.shadowMaskFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/shadowMask.rdr +mintpy.load.waterMaskFile = ${SCRATCHDIR}/KirishimaAlos2DT23F2970/geom_master/waterMask.rdr +mintpy.load.bperpFile = None + +#mintpy.reference.lalo = 31.73, 131.07 +mintpy.network.coherenceBased = yes +mintpy.network.keepMinSpanTree = no +mintpy.network.minCoherence = 0.6 +mintpy.troposphericDelay.weatherModel = ERA5 +mintpy.deramp = linear + + +##------------------------------- HDF-EOS 5 metadata ———————————————————————————## +ORBIT_DIRECTION = DESCENDING +trackNumber = 23 +first_frame = 2970 +last_frame = 2970 + diff --git a/mintpy/examples/input_files/KujuAlosAT422F650.txt b/mintpy/examples/input_files/KujuAlosAT422F650.txt new file mode 100644 index 000000000..15c1a5647 --- /dev/null +++ b/mintpy/examples/input_files/KujuAlosAT422F650.txt @@ -0,0 +1,30 @@ +# vim: set filetype=cfg: +########## 1. Load Data (--load to exit after this step) +## auto - automatic path pattern for Univ of Miami file structure +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = roipac #[isce,roipac,gamma,], auto for isce +##---------interferogram datasets: +mintpy.load.unwFile = ./../interferograms/*/filt_*.unw #[path2unw_file] +mintpy.load.corFile = ./../interferograms/*/filt_*.cor #[path2cor_file] +mintpy.load.connCompFile = None #[path2conn_file] +mintpy.load.intFile = None #[path2int_file] +##---------geometry datasets: +mintpy.load.demFile = ./../geometry/radar*.hgt #[path2hgt_file] +mintpy.load.lookupYFile = ./../geometry/geomap*.trans #[path2lookup_lat/y_file] +mintpy.load.lookupXFile = ./../geometry/geomap*.trans #[path2lookup_lon/x_file] +mintpy.load.incAngleFile = None #[path2los_file] +mintpy.load.azAngleFile = None #[path2los_file] +mintpy.load.shadowMaskFile = None #[path2shadow_file] +mintpy.load.waterMaskFile = None #[path2water_mask_file] + +##————————————————————————————— PROCESSING Options ——-----————————————————————## +mintpy.reference.lalo = 33.0655, 131.2076 +mintpy.deramp = linear + +##————————————————————————————— HDF-EOS5 Attributes -—————————————————————————## +beam_mode = SM +relative_orbit = 422 +processing_type = LOS_TIMESERIES +processing_software = ROIPAC +first_frame = 650 +last_frame = 650 diff --git a/mintpy/examples/input_files/NCalAlos2ScanSARDT169.txt b/mintpy/examples/input_files/NCalAlos2ScanSARDT169.txt new file mode 100644 index 000000000..8320a557f --- /dev/null +++ b/mintpy/examples/input_files/NCalAlos2ScanSARDT169.txt @@ -0,0 +1,26 @@ +# vim: set filetype=cfg: +##-------------------------------- MintPy -----------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = isce +##NOTE: 150408 is the reference date of alosStack processing. +## (parameter "reference date of the stack" of alosStack input xml file) +##---------for ISCE only: +mintpy.load.metaFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/150408.track.xml +mintpy.load.baselineDir = $DATA_DIR/NCalAlos2ScanSARDT169/baseline +##---------interferogram datasets: +mintpy.load.unwFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/filt_*-*_5rlks_28alks.unw +mintpy.load.corFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/*-*_5rlks_28alks.cor +mintpy.load.connCompFile = $DATA_DIR/NCalAlos2ScanSARDT169/pairs/*-*/insar/filt_*-*_5rlks_28alks.unw.conncomp +##---------geometry datasets: +mintpy.load.demFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.hgt +mintpy.load.lookupYFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.lat +mintpy.load.lookupXFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.lon +mintpy.load.incAngleFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.los +mintpy.load.azAngleFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.los +mintpy.load.waterMaskFile = $DATA_DIR/NCalAlos2ScanSARDT169/dates_resampled/150408/insar/*_5rlks_28alks.wbd + +mintpy.reference.yx = 1500, 200 +mintpy.networkInversion.weightFunc = no +mintpy.topographicResidual.pixelwiseGeometry = no + diff --git a/mintpy/examples/input_files/README.md b/mintpy/examples/input_files/README.md new file mode 100644 index 000000000..da7ed177e --- /dev/null +++ b/mintpy/examples/input_files/README.md @@ -0,0 +1,25 @@ +### Example template files for various InSAR processors ### + +#### ISCE/topsStack #### ++ [FernandinaSenDT128.txt](FernandinaSenDT128.txt) ++ [GalapagosSenDT128.template](GalapagosSenDT128.template) + +#### ISCE/stripmapStack #### ++ [KirishimaAlos2DT23F2970.template](KirishimaAlos2DT23F2970.template) + +#### ISCE/alosStack #### ++ [NCalAlos2ScanSARDT169.txt](NCalAlos2ScanSARDT169.txt) + +#### ARIA #### ++ [SanFranSenDT42.txt](SanFranSenDT42.txt) + +#### GAMMA #### ++ [GalapagosEnvA2T061.txt](GalapagosEnvA2T061.txt) ++ [WellsEnvD2T399.txt](WellsEnvD2T399.txt) + +#### SNAP #### ++ [WCapeSenAT29.txt](WCapeSenAT29.txt) + +#### ROI_PAC #### ++ [KujuAlosAT422F650.txt](KujuAlosAT422F650.txt) ++ [GalapagosAlosAT133.template](GalapagosAlosAT133.template) diff --git a/docs/examples/input_files/SanFranSenDT42.txt b/mintpy/examples/input_files/SanFranSenDT42.txt similarity index 100% rename from docs/examples/input_files/SanFranSenDT42.txt rename to mintpy/examples/input_files/SanFranSenDT42.txt diff --git a/mintpy/examples/input_files/WCapeSenAT29.txt b/mintpy/examples/input_files/WCapeSenAT29.txt new file mode 100644 index 000000000..df4689211 --- /dev/null +++ b/mintpy/examples/input_files/WCapeSenAT29.txt @@ -0,0 +1,15 @@ +# vim: set filetype=cfg: +##-------------------------------- MintPy -----------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = snap +mintpy.load.unwFile = ../interferograms/*/*/Unw_*.img +mintpy.load.corFile = ../interferograms/*/*/coh_*.img +mintpy.load.demFile = ../dem*/dem*.img + +mintpy.subset.lalo = -33.10:-32.95, 17.98:18.13 +mintpy.reference.lalo = -33, 18.05 + +mintpy.networkInversion.weightFunc = var +mintpy.deramp = linear +mintpy.topographicResidual.pixelwiseGeometry = no diff --git a/mintpy/examples/input_files/WellsEnvD2T399.txt b/mintpy/examples/input_files/WellsEnvD2T399.txt new file mode 100644 index 000000000..691d141cb --- /dev/null +++ b/mintpy/examples/input_files/WellsEnvD2T399.txt @@ -0,0 +1,32 @@ +# vim: set filetype=cfg: +##------------------------ MintPy ----------------------------------## +mintpy.load.processor = gamma +mintpy.load.unwFile = ./../interferograms/*/diff_*rlks.unw +mintpy.load.corFile = ./../interferograms/*/filt_*rlks.cor +mintpy.load.connCompFile = None +mintpy.load.intFile = None + +mintpy.load.demFile = ./../geometry/sim_*.rdc.dem +mintpy.load.lookupYFile = ./../geometry/sim_*.UTM_TO_RDC +mintpy.load.lookupXFile = ./../geometry/sim_*.UTM_TO_RDC +mintpy.load.incAngleFile = None +mintpy.load.azAngleFile = None +mintpy.load.shadowMaskFile = None +mintpy.load.waterMaskFile = None + +mintpy.subset.lalo = 41.02:41.32, -115.10:-114.65 +mintpy.reference.lalo = 41.03, -114.70 +mintpy.network.coherenceBased = yes #[yes / no], auto for yes, exclude interferograms with coherence < minCoherence +mintpy.network.keepMinSpanTree = no +mintpy.network.excludeDate = 20071231, 20080204, 20080310 +mintpy.topographicResidual.stepFuncDate = 20080221 + +##------------------------ HDF-EOS5 --------------------------------## +mission = Env # ERS,ENV,S1,RS1,RS2,CSK,TSX,JERS,ALOS,ALOS2 +relative_orbit = 399 +first_frame = 2781 +last_frame = 2781 +beam_mode = IS # S2,FB08,IW +processing_software = GAMMA + + diff --git a/mintpy/ifgram_inversion.py b/mintpy/ifgram_inversion.py index d38a68c38..1bd82672a 100755 --- a/mintpy/ifgram_inversion.py +++ b/mintpy/ifgram_inversion.py @@ -7,6 +7,12 @@ ############################################################ # Recommend import: # from mintpy import ifgram_inversion as ifginv +# +# Offset inversion considerations (different from phases): +# 1. referencing is turned off because offset is spatially absolute measure +# 2. zero value is valid for offset +# 3. unit is ground pixel size in range/azimuth directions +# 4. add Az/Rg suffix in all output files to distinguish azimuth/range import os @@ -36,11 +42,12 @@ ################################################################################################ EXAMPLE = """example: - ifgram_inversion.py inputs/ifgramStack.h5 -t smallbaselineApp.cfg --update - ifgram_inversion.py inputs/ifgramStack.h5 -w no # turn off weight for fast processing - ifgram_inversion.py. inputs/ifgramStack.h5 -c no # turn off parallel processing - # invert offset stack - ifgram_inversion.py inputs/ifgramStack.h5 -i azimuthOffset --water-mask waterMask.h5 --mask-dset offsetSNR --mask-threshold 5 + ifgram_inversion.py inputs/ifgramStack.h5 -t smallbaselineApp.cfg --update + ifgram_inversion.py inputs/ifgramStack.h5 -w no # turn off weight for fast processing + ifgram_inversion.py inputs/ifgramStack.h5 -c no # turn off parallel processing + # offset + ifgram_inversion.py inputs/ifgramStack.h5 -i rangeOffset -w no -m waterMask.h5 --md offsetSNR --mt 5 + ifgram_inversion.py inputs/ifgramStack.h5 -i azimuthOffset -w no -m waterMask.h5 --md offsetSNR --mt 5 """ TEMPLATE = get_template_content('invert_network') @@ -68,21 +75,21 @@ def create_parser(): epilog=REFERENCE+'\n'+TEMPLATE+'\n'+EXAMPLE) # input dataset parser.add_argument('ifgramStackFile', help='interferograms stack file to be inverted') - parser.add_argument('--template', '-t', dest='templateFile', help='template text file with options') + parser.add_argument('-t','--template', dest='templateFile', help='template text file with options') parser.add_argument('-i','-d', '--dset', dest='obsDatasetName', type=str, help='dataset name of unwrap phase / offset to be used for inversion' '\ne.g.: unwrapPhase, unwrapPhase_bridging, ...') - parser.add_argument('--water-mask', '-m', dest='waterMaskFile', + parser.add_argument('-m','--water-mask', dest='waterMaskFile', help='Skip inversion on the masked out region, i.e. water.') # options rarely used or changed - parser.add_argument('-o', '--output', dest='outfile', nargs=2, metavar=('TS_FILE', 'TCOH_FILE'), - default=['timeseries.h5', 'temporalCoherence.h5', 'numInvIfgram.h5'], + parser.add_argument('-o', '--output', dest='outfile', nargs=3, + metavar=('TS_FILE', 'TCOH_FILE', 'NUM_INV_FILE'), help='Output file name. (default: %(default)s).') parser.add_argument('--ref-date', dest='ref_date', help='Reference date, first date by default.') - parser.add_argument('--skip-reference', dest='skip_ref', action='store_true', - help='Skip checking reference pixel value, for simulation testing.') + parser.add_argument('--skip-reference','--skip-ref', dest='skip_ref', action='store_true', + help='[for offset and testing] do not apply spatial referencing.') # solver solver = parser.add_argument_group('solver', 'solver for the network inversion problem') @@ -101,11 +108,11 @@ def create_parser(): # mask mask = parser.add_argument_group('mask', 'mask observation data before inversion') - mask.add_argument('--mask-dset', dest='maskDataset', + mask.add_argument('--mask-dset','--mask-dataset','--md', dest='maskDataset', help='dataset used to mask unwrapPhase, e.g. coherence, connectComponent') - mask.add_argument('--mask-threshold', dest='maskThreshold', metavar='NUM', type=float, default=0.4, + mask.add_argument('--mask-thres','--mask-threshold','--mt', dest='maskThreshold', metavar='NUM', type=float, default=0.4, help='threshold to generate mask when mask is coherence (default: %(default)s).') - mask.add_argument('--min-redundancy', dest='minRedundancy', metavar='NUM', type=float, default=1.0, + mask.add_argument('--min-redun','--min-redundancy','--mr', dest='minRedundancy', metavar='NUM', type=float, default=1.0, help='minimum redundancy of interferograms for every SAR acquisition. (default: %(default)s).') # computing @@ -115,7 +122,7 @@ def create_parser(): par = parser.add_argument_group('parallel', 'parallel processing using dask') par.add_argument('-c', '--cluster', '--cluster-type', dest='cluster', type=str, - default='local', choices=cluster.CLUSTER_LIST + ['no'], + default='no', choices=cluster.CLUSTER_LIST + ['no'], help='Cluster to use for parallel computing, no to turn OFF. (default: %(default)s).') par.add_argument('--num-worker', dest='numWorker', type=str, default='4', help='Number of workers to use (default: %(default)s).') @@ -249,15 +256,26 @@ def run_or_skip(inps): flag = 'run' print('1) NOT ALL output files found: {}.'.format(inps.outfile)) else: - print('1) output files already exist: {}.'.format(inps.outfile)) - with h5py.File(inps.ifgramStackFile, 'r') as f: - ti = float(f[inps.obsDatasetName].attrs.get('MODIFICATION_TIME', os.path.getmtime(inps.ifgramStackFile))) - to = min(os.path.getmtime(i) for i in inps.outfile) - if ti > to: + # check if time-series file is partly written using file size + # since time-series file is not compressed + with h5py.File(inps.outfile[0], 'r') as f: + fsize_ref = f['timeseries'].size * 4 + fsize = os.path.getsize(inps.outfile[0]) + if fsize <= fsize_ref: flag = 'run' - print('2) output files are NOT newer than input dataset: {}.'.format(inps.obsDatasetName)) + print('1) output file {} is NOT fully written.'.format(inps.outfile[0])) + else: - print('2) output dataset is newer than input dataset: {}.'.format(inps.obsDatasetName)) + print('1) output files already exist: {}.'.format(inps.outfile)) + # check modification time + with h5py.File(inps.ifgramStackFile, 'r') as f: + ti = float(f[inps.obsDatasetName].attrs.get('MODIFICATION_TIME', os.path.getmtime(inps.ifgramStackFile))) + to = min(os.path.getmtime(i) for i in inps.outfile) + if ti > to: + flag = 'run' + print('2) output files are NOT newer than input dataset: {}.'.format(inps.obsDatasetName)) + else: + print('2) output dataset is newer than input dataset: {}.'.format(inps.obsDatasetName)) # check configuration if flag == 'skip': @@ -282,7 +300,7 @@ def run_or_skip(inps): ################################# Time-series Estimator ################################### def estimate_timeseries(A, B, tbase_diff, ifgram, weight_sqrt=None, min_norm_velocity=True, - rcond=1e-5, min_redundancy=1., skip_zero_value=True): + rcond=1e-5, min_redundancy=1.): """Estimate time-series from a stack/network of interferograms with Least Square minimization on deformation phase / velocity. @@ -315,7 +333,8 @@ def estimate_timeseries(A, B, tbase_diff, ifgram, weight_sqrt=None, min_norm_vel tbase_diff - 2D np.array in size of (num_date-1, 1), differential temporal baseline history ifgram - 2D np.array in size of (num_ifgram, num_pixel), - phase of all interferograms + phase/offset of all interferograms. + no-data value: NaN. weight_sqrt - 2D np.array in size of (num_ifgram, num_pixel), square root of weight of all interferograms min_norm_velocity - bool, assume minimum-norm deformation velocity, or not @@ -338,26 +357,29 @@ def estimate_timeseries(A, B, tbase_diff, ifgram, weight_sqrt=None, min_norm_vel temp_coh = 0. num_inv_obs = 0 - # skip zero phase value - if skip_zero_value and not np.all(ifgram): - idx = (ifgram[:, 0] != 0.).flatten() - A = A[idx, :] - B = B[idx, :] + # skip nan phase/offset value + # apply to the pixel-wised inversion only + # since the region-wised inversion has valid obs in all pairs + if np.any(np.isnan(ifgram)): + flag = (~np.isnan(ifgram[:, 0])).flatten() + A = A[flag, :] + B = B[flag, :] # skip the pixel if its redundancy < threshold if np.min(np.sum(A != 0., axis=0)) < min_redundancy: return ts, temp_coh, num_inv_obs # check matrix invertability - if weight_sqrt is not None: #for WLS only because OLS contains it already + # for WLS only because OLS contains it already + if weight_sqrt is not None: try: linalg.inv(np.dot(B.T, B)) except linalg.LinAlgError: return ts, temp_coh, num_inv_obs - ifgram = ifgram[idx, :] + ifgram = ifgram[flag, :] if weight_sqrt is not None: - weight_sqrt = weight_sqrt[idx, :] + weight_sqrt = weight_sqrt[flag, :] # update number of observations used for inversion num_inv_obs = A.shape[0] @@ -417,7 +439,7 @@ def calc_temporal_coherence(ifgram, G, X): chunk_size = int(ut.round_to_1(2e5 / num_ifgram)) if num_pixel > chunk_size: num_chunk = int(np.ceil(num_pixel / chunk_size)) - num_chunk_step = int(ut.round_to_1(num_chunk / 5)) + num_chunk_step = max(1, int(ut.round_to_1(num_chunk / 5))) print(('calcualting temporal coherence in chunks of {} pixels' ': {} chunks in total ...').format(chunk_size, num_chunk)) @@ -608,20 +630,28 @@ def mask_unwrap_phase(pha_data, stack_obj, box, mask_ds_name=None, mask_threshol if mask_ds_name and mask_ds_name in stack_obj.datasetNames: if print_msg: print('reading {} in {} * {} ...'.format(mask_ds_name, box, num_ifgram)) + msk_data = stack_obj.read(datasetName=mask_ds_name, box=box, dropIfgram=dropIfgram, print_msg=False).reshape(num_ifgram, -1) + # set all NaN values in coherence, connectComponent, offsetSNR to zero + # to avoid RuntimeWarning msg during math operation msk_data[np.isnan(msk_data)] = 0 + if mask_ds_name in ['coherence', 'offsetSNR']: msk_data = msk_data >= mask_threshold if print_msg: - print('mask out pixels with {} < {}'.format(mask_ds_name, mask_threshold)) + print('mask out pixels with {} < {} by setting them to NaN'.format(mask_ds_name, mask_threshold)) + elif mask_ds_name in ['connectComponent']: if print_msg: - print('mask out pixels with {} == 0'.format(mask_ds_name)) - pha_data[msk_data == 0.] = 0. + print('mask out pixels with {} == 0 by setting them to NaN'.format(mask_ds_name)) + + # set values of mask-out pixels to NaN + pha_data[msk_data == 0.] = np.nan del msk_data + return pha_data @@ -652,13 +682,13 @@ def calc_weight(stack_obj, box, weight_func='var', dropIfgram=True, chunk_size=1 num_pixel = weight.shape[1] if 'NCORRLOOKS' in stack_obj.metadata.keys(): - L = np.rint(float(stack_obj.metadata['NCORRLOOKS'])).astype(np.int16) + L = float(stack_obj.metadata['NCORRLOOKS']) else: # use the typical ratio of resolution vs pixel size of Sentinel-1 IW mode L = int(stack_obj.metadata['ALOOKS']) * int(stack_obj.metadata['RLOOKS']) - L = np.rint(L / 1.94) + L /= 1.94 # make sure L >= 1 - L = max(L, 1) + L = max(np.rint(L).astype(int), 1) # convert coherence to weight chunk-by-chunk to save memory num_chunk = int(np.ceil(num_pixel / chunk_size)) @@ -749,12 +779,6 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u #time_idx = [i for i in range(num_date)] #time_idx.remove(ref_idx) - # skip zero value in the network inversion for phase - if 'phase' in obs_ds_name.lower(): - skip_zero_value = True - else: - skip_zero_value = False - # 1.1 read / calcualte weight if weight_func in ['no', 'sbas']: weight = None @@ -772,6 +796,12 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u obs_ds_name=obs_ds_name, dropIfgram=True) + # translate zero phase value to nan (no-data value) + # becuase it's the common filled value used in phase masking + if 'phase' in obs_ds_name.lower(): + pha_data[pha_data == 0.] = np.nan + print('convert zero value in {} to NaN (no-data value)'.format(obs_ds_name)) + pha_data = mask_unwrap_phase(pha_data, stack_obj, box, @@ -784,28 +814,38 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u # 1.3.1 - Water Mask if water_mask_file: - print('skip pixels on water with mask from file: {}'.format(os.path.basename(water_mask_file))) + print('skip pixels (on the water) with zero value in file: {}'.format(os.path.basename(water_mask_file))) atr_msk = readfile.read_attribute(water_mask_file) len_msk, wid_msk = int(atr_msk['LENGTH']), int(atr_msk['WIDTH']) if (len_msk, wid_msk) != (stack_obj.length, stack_obj.width): raise ValueError('Input water mask file has different size from ifgramStack file.') - dsName = [i for i in readfile.get_dataset_list(water_mask_file) if i in ['waterMask', 'mask']][0] + dsNames = readfile.get_dataset_list(water_mask_file) + dsName = [i for i in dsNames if i in ['waterMask', 'mask']][0] waterMask = readfile.read(water_mask_file, datasetName=dsName, box=box)[0].flatten() mask *= np.array(waterMask, dtype=np.bool_) del waterMask - # 1.3.2 - Mask for Zero Phase in ALL ifgrams + # 1.3.2 - Mask for NaN value in ALL ifgrams + print('skip pixels with {} = NaN in all interferograms'.format(obs_ds_name)) + mask *= ~np.all(np.isnan(pha_data), axis=0) + + # 1.3.3 Mask for zero quality measure (average spatial coherence/SNR) + # usually due to lack of data in the processing if 'phase' in obs_ds_name.lower(): - print('skip pixels with zero/nan value in all interferograms') - with warnings.catch_warnings(): - # ignore warning message for all-NaN slices - warnings.simplefilter("ignore", category=RuntimeWarning) - phase_stack = np.nanmean(pha_data, axis=0) - mask *= np.multiply(~np.isnan(phase_stack), phase_stack != 0.) - del phase_stack - - # 1.3.3 invert pixels on mask 1+2 + quality_file = os.path.join(os.path.dirname(ifgram_file), '../avgSpatialCoh.h5') + elif 'offset' in obs_ds_name.lower(): + quality_file = os.path.join(os.path.dirname(ifgram_file), '../avgSpatialSnr.h5') + else: + quality_file = None + + if quality_file and os.path.isfile(quality_file): + print('skip pixels with zero value in file: {}'.format(os.path.basename(quality_file))) + quality = readfile.read(quality_file, box=box)[0].flatten() + mask *= quality != 0. + del quality + + # invert pixels on mask 1+2 num_pixel2inv = int(np.sum(mask)) idx_pixel2inv = np.where(mask)[0] print('number of pixels to invert: {} out of {} ({:.1f}%)'.format( @@ -830,25 +870,23 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u # 2.2 un-weighted inversion (classic SBAS) if weight_func in ['no', 'sbas']: - # a. mask for Non-Zero Phase in ALL ifgrams (share one B in sbas inversion) - if 'phase' in obs_ds_name.lower(): - mask_all_net = np.all(pha_data, axis=0) - mask_all_net *= mask - else: - mask_all_net = np.array(mask) + # a. split mask into mask_all/part_net + # mask for valid (~NaN) observations in ALL ifgrams (share one B in sbas inversion) + mask_all_net = np.all(~np.isnan(pha_data), axis=0) + mask_all_net *= mask mask_part_net = mask ^ mask_all_net del mask # b. invert once for all pixels with obs in all ifgrams if np.sum(mask_all_net) > 0: print(('inverting pixels with valid phase in all ifgrams' - ' ({:.0f} pixels) ...').format(np.sum(mask_all_net))) + ' ({:.0f} pixels; {:.1f}%) ...').format(np.sum(mask_all_net), + np.sum(mask_all_net)/num_pixel2inv*100)) tsi, tcohi, num_ifgi = estimate_timeseries(A, B, tbase_diff, ifgram=pha_data[:, mask_all_net], weight_sqrt=None, min_norm_velocity=min_norm_velocity, - min_redundancy=min_redundancy, - skip_zero_value=skip_zero_value) + min_redundancy=min_redundancy) ts[:, mask_all_net] = tsi temp_coh[mask_all_net] = tcohi num_inv_ifg[mask_all_net] = num_ifgi @@ -856,7 +894,8 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u # c. pixel-by-pixel for pixels with obs not in all ifgrams if np.sum(mask_part_net) > 0: print(('inverting pixels with valid phase in some ifgrams' - ' ({:.0f} pixels) ...').format(np.sum(mask_part_net))) + ' ({:.0f} pixels; {:.1f}%) ...').format(np.sum(mask_part_net), + np.sum(mask_all_net)/num_pixel2inv*100)) num_pixel2inv = int(np.sum(mask_part_net)) idx_pixel2inv = np.where(mask_part_net)[0] prog_bar = ptime.progressBar(maxValue=num_pixel2inv) @@ -866,8 +905,7 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u ifgram=pha_data[:, idx], weight_sqrt=None, min_norm_velocity=min_norm_velocity, - min_redundancy=min_redundancy, - skip_zero_value=skip_zero_value) + min_redundancy=min_redundancy) ts[:, idx] = tsi.flatten() temp_coh[idx] = tcohi num_inv_ifg[idx] = num_ifgi @@ -884,8 +922,7 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u ifgram=pha_data[:, idx], weight_sqrt=weight[:, idx], min_norm_velocity=min_norm_velocity, - min_redundancy=min_redundancy, - skip_zero_value=skip_zero_value) + min_redundancy=min_redundancy) ts[:, idx] = tsi.flatten() temp_coh[idx] = tcohi num_inv_ifg[idx] = num_ifgi @@ -911,11 +948,13 @@ def ifgram_inversion_patch(ifgram_file, box=None, ref_phase=None, obs_ds_name='u elif obs_ds_name == 'azimuthOffset': az_pixel_size = ut.azimuth_ground_resolution(stack_obj.metadata) + az_pixel_size /= float(stack_obj.metadata['ALOOKS']) ts *= az_pixel_size print('converting azimuth offset unit from pixel ({:.2f} m) to meter'.format(az_pixel_size)) elif obs_ds_name == 'rangeOffset': rg_pixel_size = float(stack_obj.metadata['RANGE_PIXEL_SIZE']) + rg_pixel_size /= float(stack_obj.metadata['RLOOKS']) ts *= rg_pixel_size print('converting range offset unit from pixel ({:.2f} m) to meter'.format(rg_pixel_size)) @@ -990,41 +1029,32 @@ def ifgram_inversion(inps=None): for key in configKeys: meta[key_prefix+key] = str(vars(inps)[key]) - # 2.2 instantiate time-series - date_dtype = np.dtype('S{}'.format(len(date_list[0]))) - dsNameDict = { - "date" : (date_dtype, (num_date,)), - "bperp" : (np.float32, (num_date,)), - "timeseries" : (np.float32, (num_date, length, width)), - } - meta['FILE_TYPE'] = 'timeseries' meta['UNIT'] = 'm' meta['REF_DATE'] = date_list[0] - ts_obj = timeseries(inps.tsFile) - ts_obj.layout_hdf5(dsNameDict, meta) - - # write date time-series - date_list_utf8 = [dt.encode('utf-8') for dt in date_list] - writefile.write_hdf5_block(inps.tsFile, date_list_utf8, datasetName='date') - - # write bperp time-series + # 2.2 instantiate time-series + dates = np.array(date_list, dtype=np.string_) pbase = stack_obj.get_perp_baseline_timeseries(dropIfgram=True) - writefile.write_hdf5_block(inps.tsFile, pbase, datasetName='bperp') + ds_name_dict = { + "date" : [dates.dtype, (num_date,), dates], + "bperp" : [np.float32, (num_date,), pbase], + "timeseries" : [np.float32, (num_date, length, width), None], + } + writefile.layout_hdf5(inps.tsFile, ds_name_dict, meta) # 2.3 instantiate temporal coherence - dsNameDict = {"temporalCoherence" : (np.float32, (length, width))} meta['FILE_TYPE'] = 'temporalCoherence' meta['UNIT'] = '1' meta.pop('REF_DATE') - writefile.layout_hdf5(inps.tempCohFile, dsNameDict, metadata=meta) + ds_name_dict = {"temporalCoherence" : [np.float32, (length, width)]} + writefile.layout_hdf5(inps.tempCohFile, ds_name_dict, metadata=meta) # 2.4 instantiate number of inverted observations - dsNameDict = {"mask" : (np.float32, (length, width))} meta['FILE_TYPE'] = 'mask' meta['UNIT'] = '1' - writefile.layout_hdf5(inps.numInvFile, dsNameDict, metadata=meta) + ds_name_dict = {"mask" : [np.float32, (length, width)]} + writefile.layout_hdf5(inps.numInvFile, ds_name_dict, metadata=meta) ## 3. run the inversion / estimation and write to disk diff --git a/mintpy/ifgram_reconstruction.py b/mintpy/ifgram_reconstruction.py index b351d116e..5c6a29ee4 100755 --- a/mintpy/ifgram_reconstruction.py +++ b/mintpy/ifgram_reconstruction.py @@ -6,6 +6,7 @@ ############################################################ +import sys import argparse import numpy as np from mintpy.objects import ifgramStack @@ -14,8 +15,8 @@ ##################################################################################### EXAMPLE = """example: - ifgram_reconstruction.py timeseries.h5 inputs/ifgramStack.h5 - ifgram_reconstruction.py timeseries_ECWMF_ramp_demErr.h5 inputs/ifgramStack.h5 -d reconCorUnwrapPhase + ifgram_reconstruction.py timeseries_ERA5_ramp_demErr.h5 + ifgram_reconstruction.py timeseries_ERA5_ramp_demErr.h5 -r inputs/ifgramStack.h5 -o ifgramStackRecon.h5 """ def create_parser(): @@ -26,7 +27,7 @@ def create_parser(): parser.add_argument('timeseries_file', type=str, help='time-series file.') parser.add_argument('-r', dest='ifgram_file', type=str, default='./inputs/ifgramStack.h5', help='reference interferograms stack file') - parser.add_argument('-o','--output', dest='out_file', default='reconUnwrapIfgram.h5', + parser.add_argument('-o','--output', dest='out_file', default='ifgramStackRecon.h5', help='output filename for the reconstructed interferograms.') return parser @@ -50,11 +51,9 @@ def timeseries2ifgram(ts_file, ifgram_file, out_file='reconUnwrapIfgram.h5'): print('reconstructing the interferograms from timeseries') stack_obj = ifgramStack(ifgram_file) stack_obj.open(print_msg=False) - A1 = stack_obj.get_design_matrix4timeseries(stack_obj.get_date12_list(dropIfgram=False))[0] - num_ifgram = A1.shape[0] - A0 = -1.*np.ones((num_ifgram, 1)) - A = np.hstack((A0, A1)) - ifgram_est = np.dot(A, ts_data).reshape(num_ifgram, length, width) + date12_list = stack_obj.get_date12_list(dropIfgram=False) + A = stack_obj.get_design_matrix4timeseries(date12_list, refDate='no')[0] + ifgram_est = np.dot(A, ts_data).reshape(A.shape[0], length, width) ifgram_est = np.array(ifgram_est, dtype=ts_data.dtype) del ts_data @@ -73,4 +72,5 @@ def main(iargs=None): ##################################################################################### if __name__ == '__main__': - main() + main(sys.argv[1:]) + diff --git a/mintpy/load_data.py b/mintpy/load_data.py index 83ed14a71..95c377910 100755 --- a/mintpy/load_data.py +++ b/mintpy/load_data.py @@ -26,6 +26,8 @@ ################################################################# +PROCESSOR_LIST = ['isce', 'aria', 'snap', 'gamma', 'roipac'] + datasetName2templateKey = {'unwrapPhase' : 'mintpy.load.unwFile', 'coherence' : 'mintpy.load.corFile', 'connectComponent': 'mintpy.load.connCompFile', @@ -89,8 +91,7 @@ def create_parser(): parser.add_argument('--project', type=str, dest='PROJECT_NAME', help='project name of dataset for INSARMAPS Web Viewer') - parser.add_argument('--processor', type=str, dest='processor', - choices={'isce', 'snap', 'gamma', 'roipac', 'doris', 'gmtsar'}, + parser.add_argument('--processor', type=str, dest='processor', choices=PROCESSOR_LIST, help='InSAR processor/software of the file', default='isce') parser.add_argument('--enforce', '-f', dest='updateMode', action='store_false', help='Disable the update mode, or skip checking dataset already loaded.') @@ -133,7 +134,14 @@ def cmd_line_parse(iargs=None): ################################################################# def read_inps2dict(inps): - """Read input Namespace object info into inpsDict""" + """Read input Namespace object info into inpsDict + + It grab the following contents into inpsDict + 1. inps & all template files + 2. configurations: processor, autoPath, updateMode, compression + 3. extra metadata: PLATFORM, PROJECT_NAME, + 4. translate autoPath + """ # Read input info into inpsDict inpsDict = vars(inps) inpsDict['PLATFORM'] = None @@ -248,6 +256,7 @@ def read_subset_box(inpsDict): box4geo_lut = ut.coordinate(atrLut).bbox_geo2radar(geo_box) print('box to read for geocoded lookup file in y/x: {}'.format(box4geo_lut)) + inpsDict['geocoded'] = geocoded inpsDict['box'] = pix_box inpsDict['box4geo_lut'] = box4geo_lut return inpsDict @@ -322,6 +331,7 @@ def skip_files_with_inconsistent_size(dsPathDict, pix_box=None, dsName='unwrapPh dsNames = list(dsPathDict.keys()) date12_list = [atr['DATE12'] for atr in atr_list] + num_drop = 0 for i in range(len(date12_list)): if length_list[i] != common_length or width_list[i] != common_width: date12 = date12_list[i] @@ -333,10 +343,11 @@ def skip_files_with_inconsistent_size(dsPathDict, pix_box=None, dsName='unwrapPh if len(fnames) > 0: dsPathDict[dsName].remove(fnames[0]) msg += '\n\t{}\t({}, {})'.format(date12, length_list[i], width_list[i]) + num_drop += 1 msg += '\n'+'-'*30 - msg += '\nSkip loading the interferograms above.' - msg += '\nContinue to load the rest interferograms.' + msg += '\nSkip loading the above interferograms ({}).'.format(num_drop) + msg += '\nContinue to load the rest interferograms ({}).'.format(len(date12_list) - num_drop) msg += '\n'+'*'*80+'\n' print(msg) return dsPathDict @@ -581,9 +592,12 @@ def prepare_metadata(inpsDict): elif processor == 'isce': from mintpy import prep_isce + from mintpy.utils.isce_utils import get_processor + meta_files = sorted(glob.glob(inpsDict['mintpy.load.metaFile'])) if len(meta_files) < 1: warnings.warn('No input metadata file found: {}'.format(inpsDict['mintpy.load.metaFile'])) + try: # metadata and auxliary data meta_file = meta_files[0] @@ -595,7 +609,11 @@ def prepare_metadata(inpsDict): obs_keys = [i for i in obs_keys if i in inpsDict.keys()] obs_paths = [inpsDict[key] for key in obs_keys if inpsDict[key].lower() != 'auto'] if len(obs_paths) > 0: - obs_dir = os.path.dirname(os.path.dirname(obs_paths[0])) + processor = get_processor(meta_file) + if processor == 'alosStack': + obs_dir = os.path.dirname(obs_paths[0]) + else: + obs_dir = os.path.dirname(os.path.dirname(obs_paths[0])) obs_file = os.path.basename(obs_paths[0]) else: obs_dir = None @@ -608,12 +626,65 @@ def prepare_metadata(inpsDict): if obs_dir is not None: iargs += ['-d', obs_dir, '-f', obs_file] print('prep_isce.py', ' '.join(iargs)) - + # run module prep_isce.main(iargs) except: - pass + warnings.warn('prep_isce.py failed. Assuming its result exists and continue...') + + elif processor == 'aria': + from mintpy import prep_aria + + ## compose input arguments + # use the default template file is exists & input + default_temp_files = [fname for fname in inpsDict['template_file'] if fname.endswith('smallbaselineApp.cfg')] + if len(default_temp_files) > 0: + temp_file = default_temp_files[0] + else: + temp_file = inpsDict['template_file'][0] + iargs = ['--template', temp_file] + + # file name/dir/path + ARG2OPT_DICT = { + '--stack-dir' : 'mintpy.load.unwFile', + '--unwrap-stack-name' : 'mintpy.load.unwFile', + '--coherence-stack-name': 'mintpy.load.corFile', + '--conn-comp-stack-name': 'mintpy.load.connCompFile', + '--dem' : 'mintpy.load.demFile', + '--incidence-angle' : 'mintpy.load.incAngleFile', + '--azimuth-angle' : 'mintpy.load.azAngleFile', + '--water-mask' : 'mintpy.load.waterMaskFile', + } + + for arg_name, opt_name in ARG2OPT_DICT.items(): + arg_value = inpsDict.get(opt_name, 'auto') + if arg_value.lower() not in ['auto', 'no', 'none']: + if arg_name.endswith('dir'): + iargs += [arg_name, os.path.dirname(arg_value)] + elif arg_name.endswith('name'): + iargs += [arg_name, os.path.basename(arg_value)] + else: + iargs += [arg_name, arg_value] + + # configurations + if inpsDict['compression']: + iargs += ['--compression', inpsDict['compression']] + if inpsDict['updateMode']: + iargs += ['--update'] + + ## run + print('prep_aria.py', ' '.join(iargs)) + try: + prep_aria.main(iargs) + except: + warnings.warn('prep_aria.py failed. Assuming its result exists and continue...') + + else: + msg = 'un-recognized InSAR processor: {}'.format(processor) + msg += '\nsupported processors: {}'.format(PROCESSOR_LIST) + raise ValueError(msg) + return @@ -624,7 +695,13 @@ def print_write_setting(inpsDict): print('updateMode : {}'.format(updateMode)) print('compression: {}'.format(comp)) box = inpsDict['box'] - boxGeo = inpsDict['box4geo_lut'] + + # box for geometry file in geo-coordinates + if not inpsDict.get('geocoded', False): + boxGeo = inpsDict['box4geo_lut'] + else: + boxGeo = box + return updateMode, comp, box, boxGeo @@ -646,8 +723,14 @@ def main(iargs=None): # read input options inpsDict = read_inps2dict(inps) + + # prepare metadata prepare_metadata(inpsDict) + # skip data writing for aria as it is included in prep_aria + if inpsDict['processor'] == 'aria': + return + inpsDict = read_subset_box(inpsDict) extraDict = get_extra_metadata(inpsDict) diff --git a/mintpy/modify_network.py b/mintpy/modify_network.py index a76adfc87..1dd86ce1c 100755 --- a/mintpy/modify_network.py +++ b/mintpy/modify_network.py @@ -25,6 +25,11 @@ Yunjun, Z., H. Fattahi, and F. Amelung (2019), Small baseline InSAR time series analysis: Unwrapping error correction and noise reduction, Computers & Geosciences, 133, 104331, doi:10.1016/j.cageo.2019.104331. + + Chaussard, E., R. Bürgmann, H. Fattahi, R. M. Nadeau, T. Taira, C. W. Johnson, and I. Johanson + (2015), Potential for larger earthquakes in the East San Francisco Bay Area due to the direct + connection between the Hayward and Calaveras Faults, Geophysical Research Letters, 42(8), + 2734-2741, doi:10.1002/2015GL063575. """ TEMPLATE = get_template_content('modify_network') @@ -213,7 +218,7 @@ def reset_network(stackFile): else: with h5py.File(stackFile, 'r+') as f: f['dropIfgram'][:] = True - ut.touch(os.path.splitext(os.path.basename(stackFile))[0]+'_coherence_spatialAvg.txt') + ut.touch('coherenceSpatialAvg.txt') return stackFile @@ -457,7 +462,7 @@ def main(iargs=None): if inps.date12_to_drop is not None: ifgramStack(inps.file).update_drop_ifgram(date12List_to_drop=inps.date12_to_drop) - ut.touch(os.path.splitext(os.path.basename(inps.file))[0]+'_coherence_spatialAvg.txt') + ut.touch('coherenceSpatialAvg.txt') print('Done.') return diff --git a/mintpy/multilook.py b/mintpy/multilook.py index 6266f27a5..fa087819e 100755 --- a/mintpy/multilook.py +++ b/mintpy/multilook.py @@ -104,7 +104,10 @@ def multilook_data(data, lks_y, lks_x): # reshape to more dimensions and collapse the extra dimensions with mean temp = crop_data.reshape((new_shape[0] // lks_y, lks_y, new_shape[1] // lks_x, lks_x)) - coarse_data = np.nanmean(temp, axis=(1, 3)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + coarse_data = np.nanmean(temp, axis=(1, 3)) elif len(shape) == 3: # crop data to the exact multiple of the multilook number @@ -117,7 +120,10 @@ def multilook_data(data, lks_y, lks_x): temp = crop_data.reshape((new_shape[0], new_shape[1] // lks_y, lks_y, new_shape[2] // lks_x, lks_x)) - coarse_data = np.nanmean(temp, axis=(2, 4)) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + coarse_data = np.nanmean(temp, axis=(2, 4)) return coarse_data diff --git a/mintpy/objects/colors.py b/mintpy/objects/colors.py index ace24fdb3..0d0cfb98b 100644 --- a/mintpy/objects/colors.py +++ b/mintpy/objects/colors.py @@ -87,8 +87,8 @@ def __init__(self, cmap_name, cmap_lut=256, vlist=[0.0, 0.7, 1.0], cpt_dir=None) if cpt_dir: self.cpt_dirs.append(cpt_dir) - # add cpt files existed in $MINTPY/docs/resources/colormaps directory - self.cpt_dirs.append(os.path.join(os.path.dirname(__file__), '../../docs/resources/colormaps')) + # add cpt files existed in $MINTPY/mintpy/data/colormaps directory + self.cpt_dirs.append(os.path.join(os.path.dirname(__file__), '../data/colormaps')) # add cpt files if GMT is installed using MacPorts for macOS users gmt_cpt_dir = '/opt/local/share/gmt/cpt' diff --git a/mintpy/objects/gps.py b/mintpy/objects/gps.py index 39d825f90..96f35fe4d 100644 --- a/mintpy/objects/gps.py +++ b/mintpy/objects/gps.py @@ -413,7 +413,7 @@ def get_gps_los_velocity(self, geom_obj, start_date=None, end_date=None, ref_sit gps_comp=gps_comp)[0:2] date_list = [dt.strftime(i, '%Y%m%d') for i in dates] if len(date_list) > 2: - A = timeseries.get_design_matrix4average_velocity(date_list) + A = timeseries.get_design_matrix4time_func(date_list) self.velocity = np.dot(np.linalg.pinv(A), dis)[0] else: self.velocity = np.nan diff --git a/mintpy/objects/ramp.py b/mintpy/objects/ramp.py index dce861421..fc91f02f9 100644 --- a/mintpy/objects/ramp.py +++ b/mintpy/objects/ramp.py @@ -75,6 +75,7 @@ def deramp(data, mask_in, ramp_type='linear', metadata=None): # estimate ramp X = np.dot(np.linalg.pinv(G[mask, :], rcond=1e-15), data[mask, :]) ramp = np.dot(G, X) + ramp = np.array(ramp, dtype=data.dtype) del X # reference in space if metadata @@ -85,7 +86,6 @@ def deramp(data, mask_in, ramp_type='linear', metadata=None): # do not change pixel with original zero value ramp[data == 0] = 0 - ramp = np.array(ramp, dtype=data.dtype) data_out = data - ramp if len(dshape) == 3: diff --git a/mintpy/objects/stack.py b/mintpy/objects/stack.py index 3e7e56b21..e0b674aa0 100644 --- a/mintpy/objects/stack.py +++ b/mintpy/objects/stack.py @@ -12,7 +12,7 @@ import sys import re import time -from datetime import datetime as dt +from datetime import datetime as dt, timedelta import h5py import numpy as np from mintpy.utils import ptime @@ -183,6 +183,10 @@ def open(self, print_msg=True): # time info self.dateFormat = ptime.get_date_str_format(self.dateList[0]) self.times = np.array([dt.strptime(i, self.dateFormat) for i in self.dateList]) + # add hh/mm/ss info to the datetime objects + if 'T' not in self.dateFormat or all(i.hour==0 and i.minute==0 for i in self.times): + utc_sec = float(self.metadata['CENTER_LINE_UTC']) + self.times = np.array([i + timedelta(seconds=utc_sec) for i in self.times]) self.tbase = np.array([(i.days + i.seconds / (24 * 60 * 60)) for i in (self.times - self.times[self.refIndex])], dtype=np.float32) @@ -262,7 +266,7 @@ def read(self, datasetName=None, box=None, squeeze=True, print_msg=True): if box is None: box = [0, 0, self.width, self.length] - data = ds[dateFlag, box[1]:box[3], box[0]:box[2]] + data = ds[:, box[1]:box[3], box[0]:box[2]][dateFlag] if squeeze: data = np.squeeze(data) return data @@ -398,13 +402,26 @@ def timeseries_rms(self, maskFile=None, outFile=None): """Calculate the Root Mean Square for each acquisition of time-series and output result to a text file. """ - # Calculate RMS - data = self.read() + # Get date list + date_list = self.get_date_list() + num_date = len(date_list) + + # Get mask if maskFile and os.path.isfile(maskFile): print('read mask from file: '+maskFile) mask = singleDataset(maskFile).read() - data[:, mask == 0] = np.nan - self.rms = np.sqrt(np.nanmean(np.square(data), axis=(1, 2))) + + # Calculate RMS one date at a time + self.rms = np.zeros(num_date) * np.nan + print('reading {} data from file: {} ...'.format(self.name, self.file)) + prog_bar = ptime.progressBar(maxValue=num_date) + for i in range(num_date): + data = self.read(datasetName='{}'.format(date_list[i]), print_msg=False) + if maskFile and os.path.isfile(maskFile): + data[mask == 0] = np.nan + self.rms[i] = np.sqrt(np.nanmean(np.square(data), axis=(0, 1))) + prog_bar.update(i+1, suffix='{}/{}'.format(i+1, num_date)) + prog_bar.close() # Write text file header = 'Root Mean Square in space for each acquisition of time-series\n' @@ -449,34 +466,127 @@ def save2bl_list_file(self, out_file='bl_list.txt'): # Functions for Unwrap error correction @staticmethod - def get_design_matrix4average_velocity(date_list, refDate=None): + def get_design_matrix4time_func(date_list, model=None, refDate=None): """design matrix/function model of linear velocity estimation - Parameters: date_list : list of string in YYYYMMDD format - Returns: A : 2D array of int in size of (numDate, 2) + Parameters: date_list : list of str in YYYYMMDD format, size=num_date + model : dict of time functions, e.g.: + {'polynomial' : 2, # int, polynomial with 1 (linear), 2 (quadratic), 3 (cubic), etc. + 'periodic' : [1.0, 0.5], # list of float, period(s) in years. 1.0 (annual), 0.5 (semiannual), etc. + 'step' : ['20061014'], # list of str, date(s) in YYYYMMDD. + ... + } + Returns: A : 2D array of design matrix in size of (num_date, num_param) + num_param = (poly_deg + 1) + 2*len(periodic) + len(steps) """ - # convert list of YYYYMMDD into array of years in float - date_format = ptime.get_date_str_format(date_list[0]) - dt_list = [dt.strptime(i, date_format) for i in date_list] - yr_list = [(d.year + (d.timetuple().tm_yday - 1) / 365.25 + - d.hour / (365.25 * 24) + - d.minute / (365.25 * 24 * 60) + - d.second / (365.25 * 24 * 60 * 60)) - for d in dt_list] - yr_diff = np.array(yr_list) + + def get_design_matrix4polynomial_func(yr_diff, degree): + """design matrix/function model of linear/polynomial velocity estimation + + The k! denominator makes the estimated polynomial coefficient (c_k) physically meaningful: + k=1 makes c_1 the velocity; + k=2 makes c_2 the acceleration; + k=3 makes c_3 the acceleration rate; + + Parameters: yr_diff: time difference from refDate in decimal years + degree : polynomial models: 1=linear, 2=quadratic, 3=cubic, etc. + Returns: A : 2D array of poly-coeff. in size of (num_date, degree+1) + """ + A = np.zeros([len(yr_diff), degree + 1], dtype=np.float32) + for i in range(degree+1): + A[:,i] = (yr_diff**i) / np.math.factorial(i) + + return A + + def get_design_matrix4periodic_func(yr_diff, periods): + """design matrix/function model of periodic velocity estimation + Parameters: yr_diff : 1D array of time difference from refDate in decimal years + periods : list of period in years: 1=annual, 0.5=semiannual, etc. + Returns: A : 2D array of periodic sine & cosine coeff. in size of (num_date, 2) + """ + num_date = len(yr_diff) + num_period = len(periods) + A = np.zeros((num_date, 2*num_period), dtype=np.float32) + + for i, period in enumerate(periods): + c0, c1 = 2*i, 2*i+1 + A[:, c0] = np.cos(2*np.pi/period * yr_diff) + A[:, c1] = np.sin(2*np.pi/period * yr_diff) + + return A + + + def get_design_matrix4step_func(date_list, step_date_list): + """design matrix/function model of coseismic velocity estimation + Parameters: date_list : list of dates in YYYYMMDD format + step_date_list : Heaviside step function(s) with date in YYYYMMDD + Returns: A : 2D array of zeros & ones in size of (num_date, num_step) + """ + num_date = len(date_list) + num_step = len(step_date_list) + A = np.zeros((num_date, num_step), dtype=np.float32) + + t = np.array(ptime.yyyymmdd2years(date_list)) + t_steps = ptime.yyyymmdd2years(step_date_list) + for i, t_step in enumerate(t_steps): + A[:, i] = np.array(t > t_step).flatten() + + return A + + ## prepare time info + # convert list of date into array of years in float + yr_diff = np.array(ptime.yyyymmdd2years(date_list)) # reference date if refDate is None: refDate = date_list[0] yr_diff -= yr_diff[date_list.index(refDate)] - A = np.ones([len(date_list), 2], dtype=np.float32) - A[:, 0] = yr_diff + ## construct design matrix A + # default model value + if not model: + model = {'polynomial' : 1} + + # read the models + poly_deg = model.get('polynomial', 0) + periods = model.get('periodic', []) + steps = model.get('step', []) + num_period = len(periods) + num_step = len(steps) + + num_param = (poly_deg + 1) + (2 * num_period) + num_step + if num_param <= 1: + raise ValueError('NO time functions specified!') + + # initialize the design matrix + num_date = len(yr_diff) + A = np.zeros((num_date, num_param), dtype=np.float32) + c0 = 0 + + # update linear/polynomial term(s) + if poly_deg > 0: + c1 = c0 + poly_deg + 1 + A[:, c0:c1] = get_design_matrix4polynomial_func(yr_diff, poly_deg) + c0 = c1 + + # update periodic term(s) + if num_period > 0: + c1 = c0 + 2 * num_period + A[:, c0:c1] = get_design_matrix4periodic_func(yr_diff, periods) + c0 = c1 + + # update coseismic/step term(s) + if num_step > 0: + c1 = c0 + num_step + A[:, c0:c1] = get_design_matrix4step_func(date_list, steps) + c0 = c1 + return A ################################ timeseries class end ################################## + ################################# geometry class begin ################################# class geometry: """ Geometry object. @@ -591,7 +701,7 @@ def read(self, datasetName=geometryDatasetNames[0], box=None, print_msg=True): else: for e in datasetName: dateFlag[self.dateList.index(e)] = True - data = ds[dateFlag, box[1]:box[3], box[0]:box[2]] + data = ds[:, box[1]:box[3], box[0]:box[2]][dateFlag] data = np.squeeze(data) return data ################################# geometry class end ################################### @@ -773,7 +883,7 @@ def read(self, datasetName='unwrapPhase', box=None, print_msg=True, dropIfgram=F if box is None: box = (0, 0, self.width, self.length) - data = ds[dateFlag, box[1]:box[3], box[0]:box[2]] + data = ds[:, box[1]:box[3], box[0]:box[2]][dateFlag] data = np.squeeze(data) return data @@ -858,7 +968,7 @@ def get_reference_phase(self, unwDatasetName='unwrapPhase', skip_reference=False self.open(print_msg=False) if skip_reference: ref_phase = np.zeros(self.get_size(dropIfgram=dropIfgram)[0], np.float32) - print('skip checking reference pixel info - This is for SIMULATION ONLY.') + print('skip checking reference pixel info - This is for offset and testing ONLY.') elif 'REF_Y' not in self.metadata.keys(): raise ValueError('No REF_X/Y found!\nrun reference_point.py to select reference pixel.') else: @@ -896,43 +1006,57 @@ def nonzero_mask(self, datasetName=None, print_msg=True, dropIfgram=True): print('') return mask - def temporal_average(self, datasetName='coherence', dropIfgram=True): + def temporal_average(self, datasetName='coherence', dropIfgram=True, row_step=200): self.open(print_msg=False) if datasetName is None: datasetName = 'coherence' print('calculate the temporal average of {} in file {} ...'.format(datasetName, self.file)) + + # index of pairs to read + ifgram_flag = np.ones(self.numIfgram, dtype=np.bool_) + if dropIfgram: + ifgram_flag = self.dropIfgram + if np.all(ifgram_flag == 0.): + raise Exception(('ALL interferograms are marked as dropped, ' + 'can not calculate temporal average.')) + + # temporal baseline for phase + # with unit of years (float64 for very short tbase of UAVSAR data) if 'unwrapPhase' in datasetName: phase2range = -1 * float(self.metadata['WAVELENGTH']) / (4.0 * np.pi) - # temporal baseline in years (float64 for very short tbase of UAVSAR data) tbase = np.array(self.tbaseIfgram, dtype=np.float64) / 365.25 + tbase = tbase[ifgram_flag] with h5py.File(self.file, 'r') as f: dset = f[datasetName] - num_ifgram, length, width = dset.shape - dmean = np.zeros((length, width), dtype=np.float32) - drop_ifgram_flag = np.ones(num_ifgram, dtype=np.bool_) - if dropIfgram: - drop_ifgram_flag = self.dropIfgram - if np.all(drop_ifgram_flag == 0.): - raise Exception(('ALL interferograms are marked as dropped, ' - 'can not calculate temporal average.')) - - num2read = np.sum(drop_ifgram_flag) - idx2read = np.where(drop_ifgram_flag)[0] - for i in range(num2read): - idx = idx2read[i] - data = dset[idx, :, :] + + # reference value for phase + ref_val = None + if 'unwrapPhase' in datasetName and self.refY: + ref_val = dset[:, self.refY, self.refX][ifgram_flag] + + # calculate lines by lines + num_step = np.ceil(self.length / row_step).astype(int) + dmean = np.zeros(dset.shape[1:3], dtype=np.float32) + for i in range(num_step): + r0 = i * row_step + r1 = min(r0 + row_step, self.length) + data = dset[:, r0:r1, :][ifgram_flag] + + # referencing / normalizing for phase if 'unwrapPhase' in datasetName: if self.refY: try: - data -= data[self.refY, self.refX] + data -= np.tile(ref_val.reshape(-1, 1, 1), (1, data.shape[1], data.shape[2])) except: pass - data *= (phase2range / tbase[idx]) - dmean += data - sys.stdout.write('\rreading interferogram {}/{} ...'.format(i+1, num2read)) + for j in range(data.shape[0]): + data[j,:,:] *= (phase2range / tbase[j]) + + # use nanmean to better handle NaN values + dmean[r0:r1, :] = np.nanmean(data, axis=0) + sys.stdout.write('\rreading & calculating lines {}/{} ...'.format(r1, self.length)) sys.stdout.flush() - dmean *= 1./np.sum(self.dropIfgram) print('') return dmean @@ -1007,10 +1131,12 @@ def get_design_matrix4triplet(date12_list): @staticmethod def get_design_matrix4timeseries(date12_list, refDate=None): """Return design matrix of the input ifgramStack for timeseries estimation - Parameters: date12_list : list of string in YYYYMMDD_YYYYMMDD format - refDate : str, date in YYYYMMDD format - Returns: A : 2D array of float32 in size of (num_ifgram, num_date-1) - B : 2D array of float32 in size of (num_ifgram, num_date-1) + Parameters: date12_list - list of string in YYYYMMDD_YYYYMMDD format + refDate - str, date in YYYYMMDD format + set to None for the 1st date + set to 'no' to disable reference date + Returns: A - 2D array of float32 in size of (num_ifgram, num_date-1) + B - 2D array of float32 in size of (num_ifgram, num_date-1) Examples: obj = ifgramStack('./inputs/ifgramStack.h5') A, B = obj.get_design_matrix4timeseries(obj.get_date12_list(dropIfgram=True)) A = ifgramStack.get_design_matrix4timeseries(date12_list, refDate='20101022')[0] @@ -1040,14 +1166,20 @@ def get_design_matrix4timeseries(date12_list, refDate=None): B[i, ind1:ind2] = tbase[ind1+1:ind2+1] - tbase[ind1:ind2] # Remove reference date as it can not be resolved - if refDate is None: - refDate = date_list[0] - if refDate: - ind_r = date_list.index(refDate) - A = np.hstack((A[:, 0:ind_r], A[:, (ind_r+1):])) - B = B[:, :-1] + if refDate != 'no': + # default refDate + if refDate is None: + refDate = date_list[0] + + # apply refDate + if refDate: + ind_r = date_list.index(refDate) + A = np.hstack((A[:, 0:ind_r], A[:, (ind_r+1):])) + B = B[:, :-1] + return A, B + def get_perp_baseline_timeseries(self, dropIfgram=True): """Get spatial perpendicular baseline in timeseries from ifgramStack, ignoring dropped ifgrams""" # read pbase of interferograms @@ -1238,7 +1370,7 @@ def read(self, datasetName=None, box=None, print_msg=True): else: for e in datasetName: dateFlag[self.dateList.index(e)] = True - data = ds[dateFlag, box[1]:box[3], box[0]:box[2]] + data = ds[:, box[1]:box[3], box[0]:box[2]][dateFlag] data = np.squeeze(data) return data ################################# HDF-EOS5 class end ################################### diff --git a/mintpy/objects/stackDict.py b/mintpy/objects/stackDict.py index b7cf0442d..d630c8dc4 100644 --- a/mintpy/objects/stackDict.py +++ b/mintpy/objects/stackDict.py @@ -287,6 +287,7 @@ class geometryDict: 'incidenceAngle':'$PROJECT_DIR/merged/geom_reference/los.rdr', 'heandingAngle' :'$PROJECT_DIR/merged/geom_reference/los.rdr', 'shadowMask' :'$PROJECT_DIR/merged/geom_reference/shadowMask.rdr', + 'waterMask' :'$PROJECT_DIR/merged/geom_reference/waterMask.rdr', 'bperp' :bperpDict ... } @@ -507,7 +508,24 @@ def write2hdf5(self, outputFile='geometryRadar.h5', access_mode='w', box=None, c t=str(dsDataType), s=dsShape, c=str(compression))) + + # read data = np.array(self.read(family=dsName, box=box)[0], dtype=dsDataType) + # water body: -1 for water and 0 for land + # water mask: 0 for water and 1 for land + fname = os.path.basename(self.datasetDict[dsName]) + if fname.startswith('waterBody') or fname.endswith('.wbd'): + data = data > -0.5 + print((' input file "{}" is water body (-1/0 for water/land), ' + 'convert to water mask (0/1 for water/land).'.format(fname))) + + if dsName == 'height': + noDataValueDEM = -32768 + if np.any(data == noDataValueDEM): + data[data == noDataValueDEM] = np.nan + print(' convert no-data value for DEM {} to NaN.'.format(noDataValueDEM)) + + # write ds = f.create_dataset(dsName, data=data, chunks=True, diff --git a/mintpy/plot_network.py b/mintpy/plot_network.py index c21a54df5..e2d2cea33 100755 --- a/mintpy/plot_network.py +++ b/mintpy/plot_network.py @@ -37,8 +37,7 @@ plot_network.py inputs/ifgramStack.h5 plot_network.py inputs/ifgramStack.h5 -t smallbaselineApp.cfg --nodisplay #Save figures to files without display plot_network.py inputs/ifgramStack.h5 -t smallbaselineApp.cfg --show-kept #Do not plot dropped ifgrams - - plot_network.py ifgramStack_coherence_spatialAvg.txt + plot_network.py coherenceSpatialAvg.txt # offsetSNR plot_network.py inputs/ifgramStack.h5 -d offsetSNR -v 0 20 @@ -57,7 +56,7 @@ def create_parser(): epilog=EXAMPLE) parser.add_argument('file', - help='file with network information, ifgramStack.h5 or ifgramStack_coherence_spatialAvg.txt') + help='file with network information, ifgramStack.h5 or coherenceSpatialAvg.txt') parser.add_argument('--show-kept', dest='disp_drop', action='store_false', help='display kept interferograms only, without dropped interferograms') parser.add_argument('-d', '--dset', type=str, dest='dsetName', default='coherence', @@ -238,7 +237,7 @@ def main(iargs=None): # Plot inps.cbar_label = 'Average Spatial Coherence' - figNames = [i+'.pdf' for i in ['BperpHistory', 'CoherenceMatrix', 'CoherenceHistory', 'Network']] + figNames = [i+'.pdf' for i in ['bperpHistory', 'coherenceMatrix', 'coherenceHistory', 'network']] # Fig 1 - Baseline History fig, ax = plt.subplots(figsize=inps.fig_size) diff --git a/mintpy/prep_aria.py b/mintpy/prep_aria.py index 2aadc5505..fd561a5af 100755 --- a/mintpy/prep_aria.py +++ b/mintpy/prep_aria.py @@ -8,23 +8,27 @@ import os import time +import glob import argparse import h5py import numpy as np -import glob -from mintpy.objects import ifgramStack, geometry, sensor -from mintpy.utils import ptime, readfile, writefile, utils as ut + try: from osgeo import gdal except ImportError: raise ImportError('Can not import gdal [version>=3.0]!') +from mintpy.objects import ifgramStack, geometry, sensor +from mintpy.utils import ptime, readfile, writefile, utils as ut +from mintpy.subset import read_subset_template2box + #################################################################################### EXAMPLE = """example: - prep_aria.py -t SanFranSenDT42.txt --update - prep_aria.py -s stack/ -d DEM/SRTM_3arcsec.dem -i incidenceAngle/*.vrt - prep_aria.py -s stack/ -d DEM/SRTM_3arcsec.dem -i incidenceAngle/*.vrt -a azimuthAngle/*.vrt --water-mask mask/watermask.msk + prep_aria.py -t smallbaselineApp.cfg # recommended + prep_aria.py -t SanFranSenDT42.txt + prep_aria.py -s ../stack/ -d ../DEM/SRTM_3arcsec.dem -i ../incidenceAngle/*.vrt + prep_aria.py -s ../stack/ -d ../DEM/SRTM_3arcsec.dem -i ../incidenceAngle/*.vrt -a ../azimuthAngle/*.vrt -w ../mask/watermask.msk # before above, one should run ARIA-tools to download / extract / prepare inteferograms stack. # reference: https://github.com/aria-tools/ARIA-tools @@ -49,6 +53,10 @@ mintpy.load.incAngleFile = ../incidenceAngle/*.vrt mintpy.load.azAngleFile = ../azimuthAngle/*.vrt mintpy.load.waterMaskFile = ../mask/watermask.msk + ##---------subset (optional): + ## if both yx and lalo are specified, use lalo option + mintpy.subset.yx = auto #[y0:y1,x0:x1 / no], auto for no + mintpy.subset.lalo = auto #[lat0:lat1,lon0:lon1 / no], auto for no """ @@ -60,12 +68,14 @@ def create_parser(): parser.add_argument('-t','--template', dest='template_file', type=str, help='template file with the options') - parser.add_argument('--update', dest='updateMode', action='store_true', - help='Enable the update mode: checking dataset already loaded.') parser.add_argument('-o', '--output', type=str, nargs=2, dest='outfile', default=['./inputs/ifgramStack.h5', './inputs/geometryGeo.h5'], help='output HDF5 file') + parser.add_argument('--update', dest='updateMode', action='store_true', + help='Enable the update mode: checking dataset already loaded.') + parser.add_argument('--compression', choices={'gzip', 'lzf', None}, default=None, + help='HDF5 file compression, default: %(default)s') # ifgramStack stack = parser.add_argument_group('interferogram stack') @@ -79,7 +89,7 @@ def create_parser(): default="cohStack.vrt", help='Name of the stack VRT file of coherence data.\n'+ 'default: %(default)s') - stack.add_argument('-l','--conn-comp-name', dest='connCompFile', type=str, + stack.add_argument('-l','--conn-comp-stack-name', dest='connCompFile', type=str, default="connCompStack.vrt", help='Name of the stack VRT file of connected component data.\n' + 'default: %(default)s') @@ -92,7 +102,7 @@ def create_parser(): help='Name of the incidence angle file') geom.add_argument('-a','--az-angle','--azimuth-angle', dest='azAngleFile', type=str, help='Name of the azimuth angle file.') - geom.add_argument('--water-mask', dest='waterMaskFile', type=str, + geom.add_argument('-w','--water-mask', dest='waterMaskFile', type=str, help='Name of the water mask file') return parser @@ -106,11 +116,11 @@ def cmd_line_parse(iargs = None): inps = read_template2inps(inps.template_file, inps) # --stack-dir - elif inps.stackDir is not None: - inps.stackDir = os.path.abspath(inps.stackDir) - inps.corFile = os.path.join(inps.stackDir, inps.corFile) - inps.unwFile = os.path.join(inps.stackDir, inps.unwFile) - inps.connCompFile = os.path.join(inps.stackDir, inps.connCompFile) + if inps.stackDir is not None: + inps.stackDir = os.path.abspath(inps.stackDir) + inps.corFile = os.path.join(inps.stackDir, os.path.basename(inps.corFile)) + inps.unwFile = os.path.join(inps.stackDir, os.path.basename(inps.unwFile)) + inps.connCompFile = os.path.join(inps.stackDir, os.path.basename(inps.connCompFile)) # check datasets # 1. translate wildcard path input with search result @@ -134,7 +144,6 @@ def cmd_line_parse(iargs = None): elif key in required_ds_keys: # raise exception if any required DS is missing - parser.print_usage() raise SystemExit('ERROR: no file found for {} in input path: "{}"!'.format(key, iDict[key])) return inps @@ -150,11 +159,23 @@ def read_template2inps(template_file, inps=None): template = readfile.read_template(template_file) template = ut.check_template_auto_value(template) + # ignore template options with default auto values + # so that options from input arguments have higher priority + # than template options with auto values + for key in list(template.keys()): + if template[key] == 'auto': + template.pop(key) + + # pass options from template to inps key_prefix = 'mintpy.load.' keys = [i for i in list(iDict.keys()) if key_prefix+i in template.keys()] for key in keys: value = template[key_prefix+key] - if value: + if key in ['updateMode', 'compression']: + iDict[key] = value + elif key in ['unwFile']: + iDict['stackDir'] = os.path.dirname(value) + elif value: iDict[key] = str(value) return inps @@ -172,35 +193,44 @@ def run_or_skip(inps, dsNameDict, out_file): return flag # check 3 - output dataset info - in_size = (inps.length, inps.width) + key = [i for i in ['unwrapPhase', 'height'] if i in dsNameDict.keys()][0] + ds_shape = dsNameDict[key][1] + in_shape = ds_shape[-2:] if 'unwrapPhase' in dsNameDict.keys(): # compare date12 and size ds = gdal.Open(inps.unwFile, gdal.GA_ReadOnly) in_date12_list = [ds.GetRasterBand(i+1).GetMetadata("unwrappedPhase")['Dates'] - for i in range(inps.num_pair)] + for i in range(ds_shape[0])] in_date12_list = ['_'.join(d.split('_')[::-1]) for d in in_date12_list] - out_obj = ifgramStack(out_file) - out_obj.open(print_msg=False) - out_size = (out_obj.length, out_obj.width) - out_date12_list = out_obj.get_date12_list(dropIfgram=False) + try: + out_obj = ifgramStack(out_file) + out_obj.open(print_msg=False) + out_shape = (out_obj.length, out_obj.width) + out_date12_list = out_obj.get_date12_list(dropIfgram=False) - if out_size == in_size and set(in_date12_list).issubset(set(out_date12_list)): - print(('All date12 exists in file {} with same size as required,' - ' no need to re-load.'.format(os.path.basename(out_file)))) - flag = 'skip' + if out_shape == in_shape and set(in_date12_list).issubset(set(out_date12_list)): + print(('All date12 exists in file {} with same size as required,' + ' no need to re-load.'.format(os.path.basename(out_file)))) + flag = 'skip' + except: + pass elif 'height' in dsNameDict.keys(): # compare dataset names and size in_dsNames = list(dsNameDict.keys()) + in_size = in_shape[0] * in_shape[1] * 4 * len(in_dsNames) out_obj = geometry(out_file) out_obj.open(print_msg=False) - out_size = (out_obj.length, out_obj.width) out_dsNames = out_obj.datasetNames + out_shape = (out_obj.length, out_obj.width) + out_size = os.path.getsize(out_file) - if out_size == in_size and set(in_dsNames).issubset(set(out_dsNames)): + if (set(in_dsNames).issubset(set(out_dsNames)) + and out_shape == in_shape + and out_size > in_size * 0.3): print(('All datasets exists in file {} with same size as required,' ' no need to re-load.'.format(os.path.basename(out_file)))) flag = 'skip' @@ -208,6 +238,45 @@ def run_or_skip(inps, dsNameDict, out_file): return flag +def read_subset_box(template_file, meta): + """Read subset info from template file + + Parameters: template_file - str, path of template file + meta - dict, metadata + Returns: pix_box - tuple of 4 int in (x0, y0, x1, y1) + meta - dict, metadata + """ + + if template_file and os.path.isfile(template_file): + + # read subset info from template file + pix_box, geo_box = read_subset_template2box(template_file) + + # geo_box --> pix_box + if geo_box is not None: + coord = ut.coordinate(meta) + pix_box = coord.bbox_geo2radar(geo_box) + pix_box = coord.check_box_within_data_coverage(pix_box) + print('input bounding box in lalo: {}'.format(geo_box)) + + else: + pix_box = None + + if pix_box is not None: + # update metadata against the new bounding box + print('input bounding box in yx: {}'.format(pix_box)) + meta = ut.subset_attribute(meta, pix_box) + else: + # translate box of None to tuple of 4 int + length, width = int(meta['LENGTH']), int(meta['WIDTH']) + pix_box = (0, 0, width, length) + + # ensure all index are in int16 + pix_box = tuple([int(i) for i in pix_box]) + + return pix_box, meta + + #################################################################################### def extract_metadata(stack): @@ -301,14 +370,26 @@ def extract_metadata(stack): return meta -def write_geometry(outfile, demFile, incAngleFile, azAngleFile=None, waterMaskFile=None): +def write_geometry(outfile, demFile, incAngleFile, azAngleFile=None, waterMaskFile=None, box=None): + """Write geometry HDF5 file from list of VRT files.""" + print('-'*50) + # box to gdal arguments + # link: https://gdal.org/python/osgeo.gdal.Dataset-class.html#ReadAsArray + if box is not None: + kwargs = dict(xoff=box[0], + yoff=box[1], + xsize=box[2]-box[0], + ysize=box[3]-box[1]) + else: + kwargs = dict() + print('writing data to HDF5 file {} with a mode ...'.format(outfile)) h5 = h5py.File(outfile, 'a') # height ds = gdal.Open(demFile, gdal.GA_ReadOnly) - data = np.array(ds.ReadAsArray(), dtype=np.float32) + data = np.array(ds.ReadAsArray(**kwargs), dtype=np.float32) data[data == ds.GetRasterBand(1).GetNoDataValue()] = np.nan h5['height'][:,:] = data @@ -317,14 +398,14 @@ def write_geometry(outfile, demFile, incAngleFile, azAngleFile=None, waterMaskFi # incidenceAngle ds = gdal.Open(incAngleFile, gdal.GA_ReadOnly) - data = ds.ReadAsArray() + data = ds.ReadAsArray(**kwargs) data[data == ds.GetRasterBand(1).GetNoDataValue()] = np.nan h5['incidenceAngle'][:,:] = data # azimuthAngle if azAngleFile is not None: ds = gdal.Open(azAngleFile, gdal.GA_ReadOnly) - data = ds.ReadAsArray() + data = ds.ReadAsArray(**kwargs) data[data == ds.GetRasterBand(1).GetNoDataValue()] = np.nan # azimuth angle of the line-of-sight vector: # ARIA: vector from target to sensor measured from the east in counterclockwise direction @@ -336,12 +417,12 @@ def write_geometry(outfile, demFile, incAngleFile, azAngleFile=None, waterMaskFi # waterMask if waterMaskFile is not None: ds = gdal.Open(waterMaskFile, gdal.GA_ReadOnly) - water_mask = ds.ReadAsArray() + water_mask = ds.ReadAsArray(**kwargs) water_mask[water_mask == ds.GetRasterBand(1).GetNoDataValue()] = False # assign False to invalid pixels based on incAngle data ds = gdal.Open(incAngleFile, gdal.GA_ReadOnly) - data = ds.ReadAsArray() + data = ds.ReadAsArray(**kwargs) water_mask[data == ds.GetRasterBand(1).GetNoDataValue()] = False h5['waterMask'][:,:] = water_mask @@ -350,7 +431,9 @@ def write_geometry(outfile, demFile, incAngleFile, azAngleFile=None, waterMaskFi return outfile -def write_ifgram_stack(outfile, unwStack, cohStack, connCompStack): +def write_ifgram_stack(outfile, unwStack, cohStack, connCompStack, box=None): + """Write ifgramStack HDF5 file from stack VRT files + """ print('-'*50) print('opening {}, {}, {} with gdal ...'.format(os.path.basename(unwStack), @@ -384,6 +467,17 @@ def write_ifgram_stack(outfile, unwStack, cohStack, connCompStack): d12 = '{}_{}'.format(d12[0], d12[1]) d12BandDict[d12] = ii+1 d12List = sorted(d12BandDict.keys()) + print('number of interferograms: {}'.format(len(d12List))) + + # box to gdal arguments + # link: https://gdal.org/python/osgeo.gdal.Band-class.html#ReadAsArray + if box is not None: + kwargs = dict(xoff=box[0], + yoff=box[1], + win_xsize=box[2]-box[0], + win_ysize=box[3]-box[1]) + else: + kwargs = dict() print('writing data to HDF5 file {} with a mode ...'.format(outfile)) h5 = h5py.File(outfile, "a") @@ -399,7 +493,7 @@ def write_ifgram_stack(outfile, unwStack, cohStack, connCompStack): h5["dropIfgram"][ii] = True bnd = dsUnw.GetRasterBand(bndIdx) - data = bnd.ReadAsArray() + data = bnd.ReadAsArray(**kwargs) data[data == noDataValueUnw] = 0 #assign pixel with no-data to 0 h5["unwrapPhase"][ii,:,:] = -1.0*data #date2_date1 -> date1_date2 @@ -407,12 +501,12 @@ def write_ifgram_stack(outfile, unwStack, cohStack, connCompStack): h5["bperp"][ii] = -1.0*bperp #date2_date1 -> date1_date2 bnd = dsCoh.GetRasterBand(bndIdx) - data = bnd.ReadAsArray() + data = bnd.ReadAsArray(**kwargs) data[data == noDataValueCoh] = 0 #assign pixel with no-data to 0 h5["coherence"][ii,:,:] = data bnd = dsComp.GetRasterBand(bndIdx) - data = bnd.ReadAsArray() + data = bnd.ReadAsArray(**kwargs) data[data == noDataValueComp] = 0 #assign pixel with no-data to 0 h5["connectComponent"][ii,:,:] = data @@ -440,60 +534,65 @@ def main(iargs=None): # extract metadata meta = extract_metadata(inps.unwFile) - inps.length = meta["LENGTH"] - inps.width = meta["WIDTH"] - inps.num_pair = meta["NUMBER_OF_PAIRS"] + box, meta = read_subset_box(inps.template_file, meta) + + length = int(meta["LENGTH"]) + width = int(meta["WIDTH"]) + num_pair = meta["NUMBER_OF_PAIRS"] # prepare output directory out_dir = os.path.dirname(inps.outfile[0]) - if not os.path.exists(out_dir): - os.makedirs(out_dir) + os.makedirs(out_dir, exist_ok=True) ########## output file 1 - ifgramStack # define dataset structure for ifgramStack dsNameDict = { - "date" : (np.dtype('S8'), (inps.num_pair, 2)), - "dropIfgram" : (np.bool_, (inps.num_pair,)), - "bperp" : (np.float32, (inps.num_pair,)), - "unwrapPhase" : (np.float32, (inps.num_pair, inps.length, inps.width)), - "coherence" : (np.float32, (inps.num_pair, inps.length, inps.width)), - "connectComponent" : (np.int16, (inps.num_pair, inps.length, inps.width)), + "date" : (np.dtype('S8'), (num_pair, 2)), + "dropIfgram" : (np.bool_, (num_pair,)), + "bperp" : (np.float32, (num_pair,)), + "unwrapPhase" : (np.float32, (num_pair, length, width)), + "coherence" : (np.float32, (num_pair, length, width)), + "connectComponent" : (np.int16, (num_pair, length, width)), } if run_or_skip(inps, dsNameDict, out_file=inps.outfile[0]) == 'run': # initiate h5 file with defined structure meta['FILE_TYPE'] = 'ifgramStack' - writefile.layout_hdf5(inps.outfile[0], dsNameDict, meta) + writefile.layout_hdf5(inps.outfile[0], dsNameDict, meta, + compression=inps.compression) # write data to h5 file in disk write_ifgram_stack(inps.outfile[0], inps.unwFile, inps.corFile, - inps.connCompFile) + inps.connCompFile, + box=box) ########## output file 2 - geometryGeo # define dataset structure for geometry dsNameDict = { - "height" : (np.float32, (inps.length, inps.width)), - "incidenceAngle" : (np.float32, (inps.length, inps.width)), - "slantRangeDistance" : (np.float32, (inps.length, inps.width)), + "height" : (np.float32, (length, width)), + "incidenceAngle" : (np.float32, (length, width)), + "slantRangeDistance" : (np.float32, (length, width)), } if inps.azAngleFile is not None: - dsNameDict["azimuthAngle"] = (np.float32, (inps.length, inps.width)) + dsNameDict["azimuthAngle"] = (np.float32, (length, width)) if inps.waterMaskFile is not None: - dsNameDict["waterMask"] = (np.bool_, (inps.length, inps.width)) + dsNameDict["waterMask"] = (np.bool_, (length, width)) if run_or_skip(inps, dsNameDict, out_file=inps.outfile[1]) == 'run': # initiate h5 file with defined structure meta['FILE_TYPE'] = 'geometry' - writefile.layout_hdf5(inps.outfile[1], dsNameDict, meta) + writefile.layout_hdf5(inps.outfile[1], dsNameDict, meta, + compression=inps.compression) # write data to disk write_geometry(inps.outfile[1], demFile=inps.demFile, incAngleFile=inps.incAngleFile, azAngleFile=inps.azAngleFile, - waterMaskFile=inps.waterMaskFile) + waterMaskFile=inps.waterMaskFile, + box=box) print('-'*50) return inps.outfile diff --git a/mintpy/prep_fringe.py b/mintpy/prep_fringe.py index 40afe2577..2646c3b40 100755 --- a/mintpy/prep_fringe.py +++ b/mintpy/prep_fringe.py @@ -138,7 +138,7 @@ def prepare_metadata(meta_file, geom_src_dir, box=None): # add LAT/LON_REF1/2/3/4, HEADING, A/RLOOKS meta = isce_utils.extract_geometry_metadata(geom_src_dir, - metadata=meta, + meta=meta, box=box, fext_list=[geom_ext]) @@ -173,15 +173,7 @@ def prepare_timeseries(outfile, unw_file, metadata, processor, baseline_dir=None num_date = len(date_list) print('number of acquisitions: {}\n{}'.format(num_date, date_list)) - # define dataset structure - length, width = int(meta['LENGTH']), int(meta['WIDTH']) - dsNameDict = { - "date" : (np.dtype("S8"), (num_date,)), - "timeseries" : (np.float32, (num_date, length, width)) - } - # baseline info - baseline_dict = {} if baseline_dir is not None: # read baseline data baseline_dict = isce_utils.read_baseline_timeseries(baseline_dir, @@ -193,21 +185,24 @@ def prepare_timeseries(outfile, unw_file, metadata, processor, baseline_dir=None pbase_top, pbase_bottom = baseline_dict[date_list[i]] pbase[i] = (pbase_top + pbase_bottom) / 2.0 - # update dataset structure - dsNameDict["bperp"] = (np.float32, (num_date,)) + # define dataset structure + length, width = int(meta['LENGTH']), int(meta['WIDTH']) + dates = np.array(date_list, dtype=np.string_) + ds_name_dict = { + "date" : [dates.dtype, (num_date,), dates], + "bperp" : [np.float32, (num_date,), pbase], + "timeseries" : [np.float32, (num_date, length, width), None], + } # initiate HDF5 file meta["FILE_TYPE"] = "timeseries" meta["UNIT"] = "m" meta['REF_DATE'] = ref_date - writefile.layout_hdf5(outfile, dsNameDict, meta) + writefile.layout_hdf5(outfile, ds_name_dict, meta) # writing data to HDF5 file print('writing data to HDF5 file {} with a mode ...'.format(outfile)) with h5py.File(outfile, "a") as f: - f["date"][:,] = np.array([np.string_(i) for i in date_list]) - f["bperp"][:,] = pbase - prog_bar = ptime.progressBar(maxValue=num_file) for i in range(num_file): # read data using gdal diff --git a/mintpy/prep_gamma.py b/mintpy/prep_gamma.py index 33e3fe8ef..b113c7fd9 100755 --- a/mintpy/prep_gamma.py +++ b/mintpy/prep_gamma.py @@ -334,11 +334,18 @@ def extract_metadata4geometry_radar(fname): except: m_date = str(re.findall('\d{6}', fname_base)[0]) - # search existing par file - geom_dir = os.path.dirname(fname) #PROJECT_DIR/geom_reference - ifg_dir = os.path.join(geom_dir, '../*/{}_*'.format(m_date)) #PROJECT_DIR/interferograms/{m_date}_20141225 - m_par_files = [os.path.join(geom_dir, '*{}*{}'.format(m_date, ext)) for ext in PAR_EXT_LIST] - m_par_files += [os.path.join(ifg_dir, '*{}*{}'.format(m_date, ext)) for ext in PAR_EXT_LIST] + ## search existing par file + # potential directories + geom_dir = os.path.dirname(fname) + proj_dir = os.path.dirname(geom_dir) + par_dirs = [geom_dir, #PROJECT_DIR/geom_reference + os.path.join(proj_dir, '*/{}_*'.format(m_date[2:])), #PROJECT_DIR/interferograms/{m_date}_(20)141225 + os.path.join(proj_dir, '*/{}-*'.format(m_date[2:]))] #PROJECT_DIR/interferograms/{m_date}-(20)141225 + # potential file patterns + m_par_files = [] + for par_dir in par_dirs: + m_par_files += [os.path.join(par_dir, '*{}*{}'.format(m_date, ext)) for ext in PAR_EXT_LIST] + # search existing files that meet the file patterns m_par_files = ut.get_file_list(m_par_files) # read par file diff --git a/mintpy/prep_isce.py b/mintpy/prep_isce.py index 3ea90ec81..64e47e702 100755 --- a/mintpy/prep_isce.py +++ b/mintpy/prep_isce.py @@ -20,9 +20,12 @@ EXAMPLE = """example: # interferogram stack - prep_isce.py -d ./merged/interferograms -m ./reference/IW1.xml -b ./baselines -g ./merged/geom_reference #for topsStack - prep_isce.py -d ./Igrams -m ./referenceShelve/data.dat -b ./baselines -g ./geom_reference #for stripmapStack - prep_isce.py -m 20120507_slc_crop.xml -g ./geometry #for stripmapApp + prep_isce.py -d ./merged/interferograms -m ./reference/IW1.xml -b ./baselines -g ./merged/geom_reference #for topsStack + prep_isce.py -d ./Igrams -m ./referenceShelve/data.dat -b ./baselines -g ./geom_reference #for stripmapStack + prep_isce.py -m 20120507_slc_crop.xml -g ./geometry #for stripmapApp + + # 150408 is the reference date of alosStack processing + prep_isce.py -d "pairs/*-*/insar" -m "pairs/*-*/150408.track.xml" -b baseline -g dates_resampled/150408/insar #for alosStack # offset stack from topsStack prep_isce.py -d ./merged/offsets -f filtAz*.off -m ./reference/IW1.xml -b ./baselines -g ./merged/offsets/geom_reference @@ -33,22 +36,22 @@ def create_parser(): parser = argparse.ArgumentParser(description='Prepare ISCE metadata files.', formatter_class=argparse.RawTextHelpFormatter, epilog=EXAMPLE) - parser.add_argument('-d', '--ds-dir', '--dset-dir', dest='dsetDir', type=str, default=None, + parser.add_argument('-d', '--ds-dir', '--dset-dir', dest='dsetDir', type=str, default=None, required=True, help='The directory which contains all pairs\n' 'e.g.: $PROJECT_DIR/merged/interferograms OR \n' + ' $PROJECT_DIR/pairs/*-*/insar OR \n' ' $PROJECT_DIR/merged/offsets') - parser.add_argument('-f', '--file-pattern', nargs = '+', dest='dsetFiles', type=str, - default=['filt_*.unw'], - help='A list of files that will be used in mintpy\n' - 'e.g.: filt_fine.unw filt_fine.cor OR\n' - ' filtAz*.off filtRa*.off') - parser.add_argument('-m', '--meta-file', dest='metaFile', type=str, default=None, + parser.add_argument('-f', '--file-pattern', nargs = '+', dest='dsetFiles', type=str, default=['filt_*.unw'], + help='List of observation files, e.g.: filt_fine.unw OR filtAz*.off') + parser.add_argument('-m', '--meta-file', dest='metaFile', type=str, default=None, required=True, help='Metadata file to extract common metada for the stack:\n' - 'e.g.: for ISCE/topsStack: reference/IW3.xml;\n' - ' for ISCE/stripmapStack: referenceShelve/data.dat') + 'e.g.: for ISCE/topsStack : reference/IW3.xml;\n' + ' for ISCE/stripmapStack: referenceShelve/data.dat;\n' + ' for ISCE/alosStack : pairs/150408-150701/150408.track.xml\n' + ' where 150408 is the reference date of stack processing') parser.add_argument('-b', '--baseline-dir', dest='baselineDir', type=str, default=None, help=' directory with baselines ') - parser.add_argument('-g', '--geometry-dir', dest='geometryDir', type=str, default=None, + parser.add_argument('-g', '--geometry-dir', dest='geometryDir', type=str, default=None, required=True, help=' directory with geometry files ') parser.add_argument('--force', dest='update_mode', action='store_false', help='Force to overwrite all .rsc metadata files.') @@ -58,9 +61,15 @@ def create_parser(): def cmd_line_parse(iargs = None): parser = create_parser() inps = parser.parse_args(args=iargs) - if all(not i for i in [inps.dsetDir, inps.geometryDir, inps.metaFile]): - parser.print_usage() - raise SystemExit('ERROR: at least one of the following arguments are required: -i, -g, -m') + + # translate wildcard in metaFile + if "*" in inps.metaFile: + fnames = glob.glob(inps.metaFile) + if len(fnames) > 0: + inps.metaFile = fnames[0] + else: + raise FileNotFoundError(inps.metaFile) + return inps @@ -89,17 +98,32 @@ def add_ifgram_metadata(metadata_in, dates=[], baseline_dict={}): return metadata -def prepare_geometry(geom_dir, metadata=dict(), update_mode=True): +def prepare_geometry(geom_dir, metadata=dict(), processor='tops', update_mode=True): """Prepare and extract metadata from geometry files""" + print('prepare .rsc file for geometry files') + geom_dir = os.path.abspath(geom_dir) + # grab all existed files - isce_files = [os.path.join(os.path.abspath(geom_dir), '{}.rdr'.format(i)) - for i in ['hgt','lat','lon','los','shadowMask','incLocal']] - isce_files = [i for i in isce_files if os.path.isfile(i)] - if len(isce_files) == 0: - isce_files = [os.path.join(os.path.abspath(geom_dir), '{}.rdr.full'.format(i)) - for i in ['hgt','lat','lon','los','shadowMask','incLocal']] + if processor in ['tops', 'stripmap']: + fbases = ['hgt', 'lat', 'lon', 'los', 'shadowMask', 'incLocal'] + isce_files = [os.path.join(geom_dir, '{}.rdr'.format(i)) for i in fbases] isce_files = [i for i in isce_files if os.path.isfile(i)] + if len(isce_files) == 0: + isce_files = [os.path.join(geom_dir, '{}.rdr.full'.format(i)) for i in fbases] + isce_files = [i for i in isce_files if os.path.isfile(i)] + + elif processor in ['alosStack']: + alooks = metadata['ALOOKS'] + rlooks = metadata['RLOOKS'] + isce_files = glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.hgt'.format(rlooks, alooks)))+\ + glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.lat'.format(rlooks, alooks)))+\ + glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.lon'.format(rlooks, alooks)))+\ + glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.los'.format(rlooks, alooks)))+\ + glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.wbd'.format(rlooks, alooks))) + + else: + raise Exception('unknown processor: {}'.format(processor)) # write rsc file for each file for isce_file in isce_files: @@ -115,12 +139,18 @@ def prepare_geometry(geom_dir, metadata=dict(), update_mode=True): writefile.write_roipac_rsc(geom_metadata, rsc_file, update_mode=update_mode, print_msg=True) - return metadata + return -def prepare_stack(inputDir, filePattern, metadata=dict(), baseline_dict=dict(), update_mode=True): +def prepare_stack(inputDir, filePattern, metadata=dict(), baseline_dict=dict(), processor='tops', update_mode=True): print('prepare .rsc file for ', filePattern) - isce_files = sorted(glob.glob(os.path.join(os.path.abspath(inputDir), '*', filePattern))) + if processor in ['tops', 'stripmap']: + isce_files = sorted(glob.glob(os.path.join(os.path.abspath(inputDir), '*', filePattern))) + elif processor == 'alosStack': + isce_files = sorted(glob.glob(os.path.join(os.path.abspath(inputDir), filePattern))) + else: + raise ValueError('Un-recognized ISCE stack processor: {}'.format(processor)) + if len(isce_files) == 0: raise FileNotFoundError('no file found in pattern: {}'.format(filePattern)) @@ -130,7 +160,14 @@ def prepare_stack(inputDir, filePattern, metadata=dict(), baseline_dict=dict(), for i in range(num_file): # prepare metadata for current file isce_file = isce_files[i] - dates = os.path.basename(os.path.dirname(isce_file)).split('_') # to modify to YYYYMMDDTHHMMSS + if processor in ['tops', 'stripmap']: + dates = os.path.basename(os.path.dirname(isce_file)).split('_') # to modify to YYYYMMDDTHHMMSS + elif processor == 'alosStack': + dates = os.path.basename(os.path.dirname(os.path.dirname(isce_file))).split('-') # to modify to YYYYMMDDTHHMMSS + dates = ptime.yyyymmdd(dates) + else: + raise ValueError('Un-recognized ISCE stack processor: {}'.format(processor)) + ifg_metadata = readfile.read_attribute(isce_file, metafile_ext='.xml') ifg_metadata.update(metadata) ifg_metadata = add_ifgram_metadata(ifg_metadata, dates, baseline_dict) @@ -161,9 +198,10 @@ def main(iargs=None): # prepare metadata for geometry file if inps.geometryDir: - metadata = prepare_geometry(inps.geometryDir, - metadata=metadata, - update_mode=inps.update_mode) + prepare_geometry(inps.geometryDir, + metadata=metadata, + processor=inps.processor, + update_mode=inps.update_mode) # read baseline info baseline_dict = {} @@ -174,9 +212,11 @@ def main(iargs=None): # prepare metadata for ifgram file if inps.dsetDir and inps.dsetFiles: for namePattern in inps.dsetFiles: - prepare_stack(inps.dsetDir, namePattern, + prepare_stack(inps.dsetDir, + namePattern, metadata=metadata, baseline_dict=baseline_dict, + processor=inps.processor, update_mode=inps.update_mode) print('Done.') return diff --git a/mintpy/prep_snap.py b/mintpy/prep_snap.py index 865db6d39..5202cebbf 100755 --- a/mintpy/prep_snap.py +++ b/mintpy/prep_snap.py @@ -110,7 +110,7 @@ def extract_snap_metadata(fname): # Read XML and extract required vals - using basic file reader # xmltree or minidom might be better but this works - with open(fname) as f: + with open(fname, 'r') as f: lines = f.readlines() # use lists so that elements where there are more than one, only the first mention can be extracted - # Usually the first mention (list item 0) is the final subsetted/geocoded product metadata diff --git a/mintpy/save_hdfeos5.py b/mintpy/save_hdfeos5.py index f8609ce2d..580e2e47e 100755 --- a/mintpy/save_hdfeos5.py +++ b/mintpy/save_hdfeos5.py @@ -29,21 +29,24 @@ TEMPALTE = TEMPLATE = get_template_content('hdfeos5') EXAMPLE = """example: - save_hdfeos5.py geo_timeseries_ERA5_ramp_demErr.h5 -c geo_temporalCoherence.h5 -m geo_maskTempCoh.h5 -g geo_geometryRadar.h5 + save_hdfeos5.py geo_timeseries_ERA5_ramp_demErr.h5 --tc geo_temporalCoherence.h5 --asc geo_avgSpatialCoh.h5 -m geo_maskTempCoh.h5 -g geo_geometryRadar.h5 + save_hdfeos5.py timeseries_ERA5_ramp_demErr.h5 --tc temporalCoherence.h5 --asc avgSpatialCoh.h5 -m maskTempCoh.h5 -g inputs/geometryGeo.h5 """ def create_parser(): parser = argparse.ArgumentParser(description='Convert MintPy timeseries product into HDF-EOS5 format\n' + - 'https://earthdata.nasa.gov/esdis/eso/standards-and-references/hdf-eos5', + ' https://earthdata.nasa.gov/esdis/eso/standards-and-references/hdf-eos5', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=TEMPALTE+'\n'+EXAMPLE) parser.add_argument('timeseries_file', default='timeseries.h5', help='Timeseries file') parser.add_argument('-t', '--template', dest='template_file', help='Template file') - parser.add_argument('-c', '--coherence', dest='coherence_file', required=True, - help='Coherence/correlation file, i.e. avgSpatialCoh.h5, temporalCoherence.h5') + parser.add_argument('--tc','--temp-coh', dest='temp_coh_file', required=True, + help='Coherence/correlation file, i.e. temporalCoherence.h5') + parser.add_argument('--asc','--avg-spatial-coh', dest='avg_spatial_coh_file', required=True, + help='Average spatial coherence file, i.e. avgSpatialCoh.h5') parser.add_argument('-m', '--mask', dest='mask_file', required=True, help='Mask file') parser.add_argument('-g', '--geometry', dest='geom_file', required=True, help='geometry file') @@ -255,7 +258,7 @@ def read_template2inps(template_file, inps=None): return inps -def write2hdf5(out_file, ts_file, coh_file, mask_file, geom_file, metadata): +def write2hdf5(out_file, ts_file, tcoh_file, scoh_file, mask_file, geom_file, metadata): """Write HDF5 file in HDF-EOS5 format""" ts_obj = timeseries(ts_file) ts_obj.open(print_msg=False) @@ -312,7 +315,7 @@ def write2hdf5(out_file, ts_file, coh_file, mask_file, geom_file, metadata): ## 1 - temporalCoherence dsName = 'temporalCoherence' - data = readfile.read(coh_file)[0] + data = readfile.read(tcoh_file)[0] print(('create dataset /{d:<{w}} of {t:<10} in size of {s}' ' with compression={c}').format(d='{}/{}'.format(gName, dsName), w=maxDigit, @@ -328,7 +331,25 @@ def write2hdf5(out_file, ts_file, coh_file, mask_file, geom_file, metadata): dset.attrs['_FillValue'] = FLOAT_ZERO dset.attrs['Units'] = '1' - ## 2 - mask + ## 2 - avgSpatialCoherence + dsName = 'avgSpatialCoherence' + data = readfile.read(scoh_file)[0] + print(('create dataset /{d:<{w}} of {t:<10} in size of {s}' + ' with compression={c}').format(d='{}/{}'.format(gName, dsName), + w=maxDigit, + t=str(data.dtype), + s=data.shape, + c=compression)) + dset = group.create_dataset(dsName, + data=data, + chunks=True, + compression=compression) + dset.attrs['Title'] = dsName + dset.attrs['MissingValue'] = FLOAT_ZERO + dset.attrs['_FillValue'] = FLOAT_ZERO + dset.attrs['Units'] = '1' + + ## 3 - mask dsName = 'mask' data = readfile.read(mask_file, datasetName='mask')[0] print(('create dataset /{d:<{w}} of {t:<10} in size of {s}' @@ -420,7 +441,8 @@ def main(iargs=None): # Open HDF5 File write2hdf5(out_file=outName, ts_file=inps.timeseries_file, - coh_file=inps.coherence_file, + tcoh_file=inps.temp_coh_file, + scoh_file=inps.avg_spatial_coh_file, mask_file=inps.mask_file, geom_file=inps.geom_file, metadata=meta_dict) diff --git a/mintpy/save_kmz_timeseries.py b/mintpy/save_kmz_timeseries.py index 73d2610a0..41790307c 100755 --- a/mintpy/save_kmz_timeseries.py +++ b/mintpy/save_kmz_timeseries.py @@ -636,7 +636,7 @@ def main(iargs=None): f.write(etree.tostring(kml_root, pretty_print=True).decode('utf-8')) ## Copy auxiliary files - res_dir = os.path.join(os.path.dirname(__file__), "../docs/resources") + res_dir = os.path.join(os.path.dirname(__file__), "data") for fname in [inps.star_file, inps.dot_file, inps.dygraph_file]: src_file = os.path.join(res_dir, os.path.basename(fname)) shutil.copy2(src_file, inps.work_dir) diff --git a/mintpy/select_network.py b/mintpy/select_network.py index 09a48c970..67940a6c8 100755 --- a/mintpy/select_network.py +++ b/mintpy/select_network.py @@ -455,7 +455,7 @@ def plot_network_info(inps): if not inps.disp_fig: plt.switch_backend('Agg') - out_fig_name = os.path.join(inps.out_dir, 'Network{}'.format(inps.figext)) + out_fig_name = os.path.join(inps.out_dir, 'network{}'.format(inps.figext)) log('plot network / pairs to file: '+os.path.basename(out_fig_name)) fig1, ax1 = plt.subplots(figsize=inps.fig_size) ax1 = pp.plot_network(ax1, @@ -466,7 +466,7 @@ def plot_network_info(inps): print_msg=False) plt.savefig(out_fig_name, bbox_inches='tight', dpi=inps.figdpi) - out_fig_name = os.path.join(inps.out_dir, 'BperpHistory{}'.format(inps.figext)) + out_fig_name = os.path.join(inps.out_dir, 'bperpHistory{}'.format(inps.figext)) log('plot baseline history to file: '+os.path.basename(out_fig_name)) fig2, ax2 = plt.subplots(figsize=inps.fig_size) ax2 = pp.plot_perp_baseline_hist(ax2, @@ -474,7 +474,7 @@ def plot_network_info(inps): inps.pbase_list) plt.savefig(out_fig_name, bbox_inches='tight', dpi=inps.figdpi) - out_fig_name = os.path.join(inps.out_dir, 'CoherenceMatrix{}'.format(inps.figext)) + out_fig_name = os.path.join(inps.out_dir, 'coherenceMatrix{}'.format(inps.figext)) if inps.cohList: log('plot predicted coherence matrix to file: '+os.path.basename(out_fig_name)) fig3, ax3 = plt.subplots(figsize=inps.fig_size) diff --git a/sh/compare_velocity_with_diff_tropo.sh b/mintpy/sh/compare_velocity_with_diff_tropo.sh similarity index 100% rename from sh/compare_velocity_with_diff_tropo.sh rename to mintpy/sh/compare_velocity_with_diff_tropo.sh diff --git a/sh/load_data_aoi.sh b/mintpy/sh/load_data_aoi.sh similarity index 100% rename from sh/load_data_aoi.sh rename to mintpy/sh/load_data_aoi.sh diff --git a/sh/plot_smallbaselineApp.sh b/mintpy/sh/plot_smallbaselineApp.sh similarity index 97% rename from sh/plot_smallbaselineApp.sh rename to mintpy/sh/plot_smallbaselineApp.sh index dc260ef1b..eb222432d 100755 --- a/sh/plot_smallbaselineApp.sh +++ b/mintpy/sh/plot_smallbaselineApp.sh @@ -99,6 +99,9 @@ if [ $plot_timeseries -eq 1 ]; then #w/o trop delay correction file=timeseries_ramp.h5; test -f $file && $view $file $opt >> $log_file file=timeseries_demErr_ramp.h5; test -f $file && $view $file $opt >> $log_file + + #w/o trop delay and deramp + file=timeseries_demErr.h5; test -f $file && $view $file $opt >> $log_file fi diff --git a/sh/post_aoi.sh b/mintpy/sh/post_aoi.sh similarity index 100% rename from sh/post_aoi.sh rename to mintpy/sh/post_aoi.sh diff --git a/sh/post_process_Alos.sh b/mintpy/sh/post_process_Alos.sh similarity index 100% rename from sh/post_process_Alos.sh rename to mintpy/sh/post_process_Alos.sh diff --git a/sh/run_stripmap_stack.sh b/mintpy/sh/run_stripmap_stack.sh similarity index 100% rename from sh/run_stripmap_stack.sh rename to mintpy/sh/run_stripmap_stack.sh diff --git a/mintpy/simulation/decorrelation.py b/mintpy/simulation/decorrelation.py index a3e36c737..658b861cf 100644 --- a/mintpy/simulation/decorrelation.py +++ b/mintpy/simulation/decorrelation.py @@ -18,17 +18,19 @@ ######################################## Statistics ############################################ -def phase_pdf_ds(L, coherence=None, phi_num=1000, epsilon=1e-3): +def phase_pdf_ds(L, coherence=None, phi_num=1000, coh_step=0.005): """Marginal PDF of interferometric phase for distributed scatterers (DS) + Eq. 66 (Tough et al., 1995) and Eq. 4.2.23 (Hanssen, 2001) + Parameters: L - int, number of independent looks coherence - 1D np.array for the range of coherence, with value < 1.0 for valid operation phi_num - int, number of phase sample for the numerical calculation + coh_step - float, incremental step of coherence Returns: pdf - 2D np.array, phase PDF in size of (phi_num, len(coherence)) coherence - 1D np.array for the range of coherence Example: - epsilon = 1e-4 - coh = np.linspace(0., 1-epsilon, 1000) + coh = np.linspace(0.005, 1.0, 200) - 0.0025 pdf, coh = phase_pdf_ds(1, coherence=coh) """ @@ -49,9 +51,11 @@ def gamma(x): except ValueError: return float('inf') - + # default coherence value if coherence is None: - coherence = np.linspace(0., 1.-epsilon, 1000) + coh_num = int(1. / coh_step) + coherence = np.linspace(coh_step, 1., num=coh_num) - coh_step / 2.0 + coherence = np.array(coherence, np.float64).reshape(1, -1) phi = np.linspace(-np.pi, np.pi, phi_num, dtype=np.float64).reshape(-1, 1) @@ -76,10 +80,11 @@ def gamma(x): pdf = B*C + sumD pdf = np.multiply(A, pdf) - return pdf, coherence.flatten() + + return pdf, coherence.astype(np.float32).flatten() -def phase_variance_ds(L, coherence=None, epsilon=1e-3): +def phase_variance_ds(L, coherence=None, coh_step=0.005): """Interferometric phase variance for distributed scatterers (DS) Eq. 2.1.2 (Box et al., 2015) and Eq. 4.2.27 (Hanssen, 2001) Parameters: L - int, number of independent looks @@ -87,14 +92,17 @@ def phase_variance_ds(L, coherence=None, epsilon=1e-3): Returns: var - 1D np.array, phase variance in size of (len(coherence)) in radians^2 coherence - 1D np.array for the range of coherence Example: - epsilon = 1e-4 - coh = np.linspace(0., 1-epsilon, 1000) + coh = np.linspace(0.005, 1.0, 200) - 0.0025 var, coh = phase_variance_ds(1, coherence=coh) """ + + # default coherence value if coherence is None: - coherence = np.linspace(0., 1.-epsilon, 1000, dtype=np.float64) - phi_num = len(coherence) + coh_num = int(1. / coh_step) + coherence = np.linspace(coh_step, 1., num=coh_num) - coh_step / 2.0 + coherence = np.array(coherence, dtype=np.float64) + phi_num = len(coherence) phi = np.linspace(-np.pi, np.pi, phi_num, dtype=np.float64).reshape(-1, 1) phi_step = 2*np.pi/phi_num @@ -129,8 +137,10 @@ def phase_variance_ps(L, coherence=None, epsilon=1e-3): ########################################## Simulations ######################################### -def calibrate_coherence4phase_pdf_bias(coh, L, coh_step=0.02, num_sample=1e5, print_msg=True): - """Calculate coherence for the bias caused by the imperfect phase PDF of DS. +def calibrate_coherence4phase_pdf_bias(coh, L, coh_step=0.02, num_sample=1e5, print_msg=True, + poly_deg=None, poly_rcond=1e-5): + """Calibrate coherence for the bias caused by the imperfect phase PDF of DS + during decorrelation noise simulation. By comparing the estimated coherence from the simulated decorrelation noise based on its PDF with the specified true coherence. @@ -142,14 +152,18 @@ def calibrate_coherence4phase_pdf_bias(coh, L, coh_step=0.02, num_sample=1e5, pr L - int, number of independent looks coh_step - float, step of coherence to generate lookup table num_sample - int, number of samples used for coherence estimation + poly_deg - int, polynomial order degree. Default: 10 for L <=10, 40 for 10 < L <= 20. + poly_rcond - float, relative condition number of the fit. Returns: coh_cal - 1/2/3D np.ndarray of float32 for calibrated coherence + ffit - the polynomial function of this calibration curve """ + if L > 20: + raise ValueError('input L ({}) > 20! Polynomial fitting will be poorly conditioned!'.format(L)) ## 1. calculate the bias due to phase PDF numerically # list of coherence num_coh = int(1 / coh_step) - coh_sim = np.linspace(1, coh_step, num=num_coh) - coh_sim -= coh_step/2 # use value in the bin center + coh_sim = np.linspace(1, coh_step, num=num_coh) - coh_step / 2.0 # loop over different coherence coh_est = np.zeros(num_coh, dtype=np.float32) @@ -158,18 +172,35 @@ def calibrate_coherence4phase_pdf_bias(coh, L, coh_step=0.02, num_sample=1e5, pr sim_pha = sample_decorrelation_phase(coh_sim[i], L=L, size=num_sample) # form interferometric phase - sim_int = np.array(np.exp(1j*sim_pha), dtype=np.complex64) + sim_int = np.array(np.exp(1j * sim_pha), dtype=np.complex64) # calc coherence from interferometric phase coh_est[i] = np.abs(np.mean(sim_int)) + # add 1 as the last data point + # for better fitting for values close to 1. + coh_sim = np.hstack((coh_sim, np.array([1]))) + coh_est = np.hstack((coh_est, np.array([1]))) + ## 2. fit a polynomial curve to calibrate the bias + # default degree of polynomial + if poly_deg is None: + if L < 10: + poly_deg = 10 + else: + poly_deg = 40 if print_msg: - print('calibrate the coherence for the phase PDF bias with a polynomial of degree 10') - ffit = poly.Polynomial(poly.polyfit(coh_est, coh_sim, deg=10, rcond=1e-15)) + print('calibrate coherence for the phase PDF bias with a polynomial of degree {}'.format(poly_deg)) + + # https://numpy.org/doc/stable/reference/generated/numpy.polynomial.polynomial.polyfit.html + ffit = poly.Polynomial(poly.polyfit(coh_est, coh_sim, deg=poly_deg, rcond=poly_rcond, full=True)[0]) + + ## 3. apply the calibration coh_cal = ffit(coh) + coh_cal[coh_cal < 0] = 0 + coh_cal[coh_cal > 1] = 1 - return coh_cal + return coh_cal, ffit def coherence2decorrelation_phase(coh, L, coh_step=0.01, num_repeat=1, scale=1.0, display=False, print_msg=True): @@ -281,7 +312,7 @@ def sample_decorrelation_phase(coherence, L, size=1, phi_num=1000, display=False #################################### Weight Functions ##################################### -def coherence2phase_variance(coherence, L=32, scatter='DS', epsilon=1e-3, print_msg=False): +def coherence2phase_variance(coherence, L=32, scatter='DS', coh_step=0.005, print_msg=False): """Convert coherence to phase variance, depending on the type of scatterers For DS, it is equation (66) from Tough et al. (1995). @@ -295,17 +326,14 @@ def coherence2phase_variance(coherence, L=32, scatter='DS', epsilon=1e-3, print_ lineStr = ' number of independent looks L={}'.format(L) if L > 80: L = 80 - lineStr += ', use L=80 to avoid dividing by 0 in calculation with negligible effect' + lineStr += ', use L=80 to avoid dividing by 0 with negligible effect' if print_msg: print(lineStr) - coh_num = 1000 - coh_min = 0.0 + epsilon - coh_max = 1.0 - epsilon - coh_lut = np.linspace(coh_min, coh_max, coh_num) + coh_num = int(1. / coh_step) + coh_lut = np.linspace(coh_step, 1.0, num=coh_num) - coh_step / 2.0 coh_min = np.min(coh_lut) coh_max = np.max(coh_lut) - coh_step = (coh_max - coh_min) / (coh_num - 1) coherence = np.array(coherence) coherence[coherence < coh_min] = coh_min @@ -376,5 +404,6 @@ def coherence2weight(coh_data, weight_func='var', L=20, epsilon=5e-2, print_msg= if weight is not None: weight = np.array(weight, np.float32) del coh_data + return weight diff --git a/mintpy/simulation/simulation.py b/mintpy/simulation/simulation.py index 6be048abb..baa79207a 100644 --- a/mintpy/simulation/simulation.py +++ b/mintpy/simulation/simulation.py @@ -217,7 +217,7 @@ def estimate_coherence(ifgram, L=20, win_size=25): def timeseries2velocity(date_list, defo_list): # date_list --> design_matrix - A = timeseries.get_design_matrix4average_velocity(date_list) + A = timeseries.get_design_matrix4time_func(date_list) A_inv = np.linalg.pinv(A) # least square inversion diff --git a/mintpy/smallbaselineApp.py b/mintpy/smallbaselineApp.py index d7a557f12..59bd70626 100755 --- a/mintpy/smallbaselineApp.py +++ b/mintpy/smallbaselineApp.py @@ -102,7 +102,7 @@ def cmd_line_parse(iargs=None): # -v (print software version) if inps.version: - print(mintpy.version.description) + print(mintpy.version.release_description) sys.exit(0) # check all input template files @@ -166,7 +166,7 @@ def read_inps2run_steps(inps, step_list=STEP_LIST): if len(run_steps) > 0: # for single step - compact version info if len(run_steps) == 1: - print(mintpy.version.description) + print(mintpy.version.release_description) else: print(mintpy.version.logo) @@ -235,7 +235,7 @@ def startup(self): self._read_template() # 4. Copy the plot shell file - sh_file = os.path.join(os.path.dirname(__file__), '../sh/plot_smallbaselineApp.sh') + sh_file = os.path.join(os.path.dirname(__file__), 'sh/plot_smallbaselineApp.sh') def grab_latest_update_date(fname, prefix='# Latest update:'): try: @@ -342,25 +342,18 @@ def run_load_data(self, step_name): self._copy_aux_file() # 2) loading data - stack_processor = self.template['mintpy.load.processor'].lower() - if stack_processor == 'aria': - from mintpy import prep_aria - iargs = ['--template', self.templateFile, '--update'] - prep_aria.main(iargs) + # compose list of input arguments + # instead of using command line then split + # to support path with whitespace + iargs = ['--template', self.templateFile] + if self.customTemplateFile: + iargs += [self.customTemplateFile] + if self.projectName: + iargs += ['--project', self.projectName] - else: - # compose list of input arguments - # instead of using command line then split - # to support path with whitespace - iargs = ['--template', self.templateFile] - if self.customTemplateFile: - iargs += [self.customTemplateFile] - if self.projectName: - iargs += ['--project', self.projectName] - - # run command line - print('load_data.py', ' '.join(iargs)) - mintpy.load_data.main(iargs) + # run command line + print('load_data.py', ' '.join(iargs)) + mintpy.load_data.main(iargs) # come back to working directory os.chdir(self.workDir) @@ -418,9 +411,9 @@ def run_network_modification(self, step_name, plot=True): """Modify network of interferograms before the network inversion.""" # check the existence of ifgramStack.h5 stack_file, geom_file = ut.check_loaded_dataset(self.workDir, print_msg=False)[1:3] - coh_txt = '{}_coherence_spatialAvg.txt'.format(os.path.splitext(os.path.basename(stack_file))[0]) + coh_txt = 'coherenceSpatialAvg.txt' try: - net_fig = [i for i in ['Network.pdf', 'pic/Network.pdf'] if os.path.isfile(i)][0] + net_fig = [i for i in ['network.pdf', 'pic/network.pdf'] if os.path.isfile(i)][0] except: net_fig = None @@ -446,20 +439,24 @@ def run_network_modification(self, step_name, plot=True): mintpy.modify_network.main(iargs) # 3) plot network - if self.template['mintpy.plot'] and plot: - iargs = [stack_file, '-t', self.templateFile, '--nodisplay'] + iargs = [stack_file, '-t', self.templateFile, '--nodisplay'] - dsNames = readfile.get_dataset_list(stack_file) - if any('phase' in i.lower() for i in dsNames): - iargs += ['-d', 'coherence', '-v', '0.2', '1.0'] - elif any('offset' in i.lower() for i in dsNames): - iargs += ['-d', 'offsetSNR', '-v', '0', '20'] + dsNames = readfile.get_dataset_list(stack_file) + if any('phase' in i.lower() for i in dsNames): + iargs += ['-d', 'coherence', '-v', '0.2', '1.0'] + elif any('offset' in i.lower() for i in dsNames): + iargs += ['-d', 'offsetSNR', '-v', '0', '20'] + + print('\nplot_network.py', ' '.join(iargs)) - print('\nplot_network.py', ' '.join(iargs)) + # run + if self.template['mintpy.plot'] and plot: if ut.run_or_skip(out_file=net_fig, in_file=[stack_file, coh_txt, self.templateFile], check_readable=False) == 'run': mintpy.plot_network.main(iargs) + else: + print('mintpy.plot is turned OFF, skip plotting network.') return @@ -509,7 +506,7 @@ def run_reference_point(self, step_name): def run_quick_overview(self, step_name): """A quick overview on the interferogram stack for: 1) avgPhaseVelocity.h5: possible ground deformation through interferogram stacking - 2) numNonzeroIntClosure.h5: phase unwrapping errors through the integer ambiguity of phase closure + 2) numTriNonzeroIntAmbiguity.h5: phase unwrapping errors through the integer ambiguity of phase closure """ # check the existence of ifgramStack.h5 stack_file = ut.check_loaded_dataset(self.workDir, print_msg=False)[1] @@ -520,7 +517,7 @@ def run_quick_overview(self, step_name): print('temporal_average.py', ' '.join(iargs)) mintpy.temporal_average.main(iargs) - # 2) calculate the integer ambiguity of closure phase + # 2) calculate the number of interferogram triplets with non-zero integer ambiguity water_mask_file = 'waterMask.h5' iargs = [stack_file, '--water-mask', water_mask_file, '--action', 'calculate', '--update'] print('unwrap_error_phase_closure.py', ' '.join(iargs)) @@ -904,7 +901,7 @@ def run_geocode(self, step_name): os.makedirs(out_dir, exist_ok=True) geom_file, lookup_file = ut.check_loaded_dataset(self.workDir, print_msg=False)[2:4] - in_files = [geom_file, 'temporalCoherence.h5', ts_file, 'velocity.h5'] + in_files = [geom_file, 'temporalCoherence.h5', 'avgSpatialCoh.h5', ts_file, 'velocity.h5'] iargs = ['-l', lookup_file, '-t', self.templateFile, '--outdir', out_dir, '--update'] for in_file in in_files: iargs += [in_file] @@ -972,17 +969,20 @@ def run_save2hdfeos5(self, step_name): ut.add_attribute(ts_file, self.customTemplate) tcoh_file = 'temporalCoherence.h5' - mask_file = 'geo_maskTempCoh.h5' + scoh_file = 'avgSpatialCoh.h5' + mask_file = 'maskTempCoh.h5' geom_file = ut.check_loaded_dataset(self.workDir, print_msg=False)[2] if 'geo' in ts_file: tcoh_file = './geo/geo_temporalCoherence.h5' + scoh_file = './geo/geo_avgSpatialCoh.h5' mask_file = './geo/geo_maskTempCoh.h5' geom_file = './geo/geo_{}'.format(os.path.basename(geom_file)) # cmd print('--------------------------------------------') iargs = [ts_file, - '-c', tcoh_file, + '--tc', tcoh_file, + '--asc', scoh_file, '-m', mask_file, '-g', geom_file, '-t', self.templateFile] @@ -995,7 +995,7 @@ def run_save2hdfeos5(self, step_name): hdfeos5_file = ut.get_file_list('{}_*.he5'.format(SAT))[0] except: hdfeos5_file = None - if ut.run_or_skip(out_file=hdfeos5_file, in_file=[ts_file, tcoh_file, mask_file, geom_file]) == 'run': + if ut.run_or_skip(out_file=hdfeos5_file, in_file=[ts_file, tcoh_file, scoh_file, mask_file, geom_file]) == 'run': mintpy.save_hdfeos5.main(iargs) else: print('save time-series to HDF-EOS5 format is OFF.') diff --git a/mintpy/timeseries2velocity.py b/mintpy/timeseries2velocity.py index 6b16ee8cd..68fea3387 100755 --- a/mintpy/timeseries2velocity.py +++ b/mintpy/timeseries2velocity.py @@ -2,13 +2,17 @@ ############################################################ # Program is part of MintPy # # Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi # -# Author: Heresh Fattahi, Zhang Yunjun, Emre Havazli, 2013 # +# Author: Zhang Yunjun, Heresh Fattahi, Yuan-Kai Liu, # +# Emre Havazli, 2013 # ############################################################ import os +import sys import argparse +import h5py import numpy as np +from scipy import linalg from mintpy.objects import timeseries, giantTimeseries, HDFEOS from mintpy.defaults.template import get_template_content from mintpy.utils import readfile, writefile, ptime, utils as ut @@ -38,9 +42,7 @@ EXAMPLE = """example: timeseries2velocity.py timeseries_ERA5_demErr.h5 - timeseries2velocity.py timeseries_ERA5_demErr_ramp.h5 -t smallbaselineApp.cfg --update timeseries2velocity.py timeseries_ERA5_demErr_ramp.h5 -t KyushuT73F2980_2990AlosD.template - timeseries2velocity.py timeseries.h5 --start-date 20080201 timeseries2velocity.py timeseries.h5 --start-date 20080201 --end-date 20100508 timeseries2velocity.py timeseries.h5 --exclude-date exclude_date.txt @@ -48,8 +50,11 @@ timeseries2velocity.py NSBAS-PARAMS.h5 timeseries2velocity.py TS-PARAMS.h5 - # bootstrapping + # bootstrapping for STD calculation timeseries2velocity.py timeseries_ERA5_demErr.h5 --bootstrap + + # complex time functions + timeseries2velocity.py timeseries_ERA5_ramp_demErr.h5 --poly 3 --period 1 0.5 --step 20170910 """ DROP_DATE_TXT = """exclude_date.txt: @@ -85,6 +90,22 @@ def create_parser(): '--exclude 20040502 20060708 20090103\n' + '--exclude exclude_date.txt\n'+DROP_DATE_TXT) + # time functions + model = parser.add_argument_group('deformation model', 'a suite of time functions') + model.add_argument('--polynomial', '--poly', '--poly-order', dest='polynomial', type=int, default=1, + help='a polynomial function with the input degree (default: %(default)s). E.g.:\n' + + '--polynomial 1 # linear\n' + + '--polynomial 2 # quadratic\n' + + '--polynomial 3 # cubic\n') + model.add_argument('--periodic', '--peri', dest='periodic', type=float, nargs='+', default=[], + help='periodic function(s) with period in decimal years (default: %(default)s). E.g.:\n' + + '--periodic 1.0 # an annual cycle\n' + + '--periodic 1.0 0.5 # an annual cycle plus a semi-annual cycle\n') + model.add_argument('--step', dest='step', type=str, nargs='+', default=[], + help='step function(s) at YYYYMMDD (default: %(default)s). E.g.:\n' + + '--step 20061014 # coseismic step at 2006-10-14\n' + + '--step 20110311 20120928 # coseismic steps at 2011-03-11 and 2012-09-28\n') + # bootstrap bootstrap = parser.add_argument_group('bootstrapping', 'estimating the mean / STD of the velocity estimator') bootstrap.add_argument('--bootstrap', '--bootstrapping', dest='bootstrap', action='store_true', @@ -111,6 +132,8 @@ def cmd_line_parse(iargs=None): if inps.bootstrap: print('bootstrapping is turned ON.') + if (inps.polynomial != 1 or inps.periodic or inps.step): + raise ValueError('bootstrapping currently support polynomial ONLY and ONLY with the order of 1!') return inps @@ -256,31 +279,41 @@ def read_date_info(inps): ############################################################################ -def estimate_velocity(date_list, ts_obs, model=['linear']): +def estimate_velocity(date_list, dis_ts, model): """ - Velocity estimator. + Deformation model estimator, using a suite of linear, periodic, step function(s). - Linear velocity is assumed currently. More complex model, i.e. periodic, step can be added here. + Gm = d Parameters: date_list - list of str, dates in YYYYMMDD format - ts_obs - 2D np.array, displacement observation in size of (num_date, num_pixel) - model - list of str, list of models used for velocity estimation. - Returns: A - 2D np.array, design matrix in size of (num_date, num_par) - X - 2D np.array, parameter solution in size of (num_par, num_pixel) + dis_ts - 2D np.ndarray, displacement observation in size of (num_date, num_pixel) + model - dict of time functions, e.g.: + {'polynomial' : 2, # int, polynomial with 1 (linear), 2 (quadratic), 3 (cubic), etc. + 'periodic' : [1.0, 0.5], # list of float, period(s) in years. 1.0 (annual), 0.5 (semiannual), etc. + 'step' : ['20061014'], # list of str, date(s) in YYYYMMDD. + ... + } + Returns: G - 2D np.ndarray, design matrix in size of (num_date, num_par) + m - 2D np.ndarray, parameter solution in size of (num_par, num_pixel) + e2 - 1D np.ndarray, sum of squared residual in size of (num_pixel,) """ - if 'linear' in model: - A = timeseries.get_design_matrix4average_velocity(date_list) - else: - raise ValueError('linear model is NOT included in the velocity estimation! Are you sure?!') + print('estimate deformation model with the following assumed time functions:') + for key, value in model.items(): + print('{:<10} : {}'.format(key, value)) + + if 'polynomial' not in model.keys(): + raise ValueError('linear/polynomial model is NOT included! Are you sure?!') + + G = timeseries.get_design_matrix4time_func(date_list, model) # least squares solver - # The following is equivalent - # X = scipy.linalg.lstsq(A, ts_obs, cond=1e-15)[0] - # It is not used because it can not handle NaN value in ts_obs - X = np.dot(np.linalg.pinv(A), ts_obs) + # Opt. 1: m = np.linalg.pinv(G).dot(dis_ts) + # Opt. 2: m = scipy.linalg.lstsq(G, dis_ts, cond=1e-15)[0] + # Numpy is not used because it can not handle NaN value in dis_ts + m, e2 = linalg.lstsq(G, dis_ts)[:2] - return A, X + return G, m, e2 def run_velocity_estimation(inps): @@ -291,6 +324,17 @@ def run_velocity_estimation(inps): if atr['UNIT'] == 'mm': ts_data *= 1./1000. length, width = int(atr['LENGTH']), int(atr['WIDTH']) + num_date = inps.numDate + + # get deformation model from parsers + model = dict() + model['polynomial'] = inps.polynomial + model['periodic'] = inps.periodic + model['step'] = inps.step + poly_deg = inps.polynomial + num_period = len(inps.periodic) + num_step = len(inps.step) + num_param = (poly_deg + 1) + (2 * num_period) + num_step if inps.bootstrap: """ @@ -303,19 +347,15 @@ def run_velocity_estimation(inps): boot_vel_lin = np.zeros((inps.bootstrapCount, (length*width)), dtype=dataType) ts_date = np.array(inps.dateList) - prog_bar = ptime.progressBar(maxValue=inps.bootstrapCount) for i in range(inps.bootstrapCount): # bootstrap resampling - boot_ind = resample(np.arange(inps.numDate), - replace=True, - n_samples=inps.numDate) + boot_ind = resample(np.arange(inps.numDate), replace=True, n_samples=inps.numDate) boot_ind.sort() # velocity estimation - A, X = estimate_velocity(ts_date[boot_ind].tolist(), ts_data[boot_ind]) - - boot_vel_lin[i] = np.array(X[0, :], dtype=dataType) + m = estimate_velocity(ts_date[boot_ind].tolist(), ts_data[boot_ind], model)[1] + boot_vel_lin[i] = np.array(m[1, :], dtype=dataType) prog_bar.update(i+1, suffix='iteration {} / {}'.format(i+1, inps.bootstrapCount)) prog_bar.close() @@ -323,16 +363,55 @@ def run_velocity_estimation(inps): vel_lin = boot_vel_lin.mean(axis=0).reshape(length,width) vel_std = boot_vel_lin.std(axis=0).reshape(length,width) - else: - A, X = estimate_velocity(inps.dateList, ts_data) - vel_lin = np.array(X[0, :].reshape(length, width), dtype=dataType) - - # velocity STD (Eq. (10), Fattahi and Amelung, 2015) - ts_diff = ts_data - np.dot(A, X) - t_diff = A[:, 0] - np.mean(A[:, 0]) - vel_std = np.sqrt(np.sum(ts_diff ** 2, axis=0) / np.sum(t_diff ** 2) / (inps.numDate - 2)) - vel_std = np.array(vel_std.reshape(length, width), dtype=dataType) - + # prepare attributes + atr['FILE_TYPE'] = 'velocity' + atr['UNIT'] = 'm/year' + atr['START_DATE'] = inps.dateList[0] + atr['END_DATE'] = inps.dateList[-1] + atr['DATE12'] = '{}_{}'.format(inps.dateList[0], inps.dateList[-1]) + # config parameter + print('add/update the following configuration metadata:\n{}'.format(configKeys)) + for key in configKeys: + atr[key_prefix+key] = str(vars(inps)[key]) + + # write to HDF5 file + dsDict = dict() + dsDict['velocity'] = vel_lin + dsDict['velocityStd'] = vel_std + writefile.write(dsDict, out_file=inps.outfile, metadata=atr) + return inps.outfile + + # mask of pixels to invert + mask = np.ones(length*width, np.bool_) + print('skip pixels with zero/nan value in all acquisitions') + ts_stack = np.nanmean(ts_data, axis=0) + mask *= np.multiply(~np.isnan(ts_stack), ts_stack!=0.) + del ts_stack + + ## Solve Gm = d + m = np.zeros((num_param, length*width), dtype=dataType) + e2 = np.zeros((length*width), dtype=dataType) + G, m[:, mask], e2[mask] = estimate_velocity(inps.dateList, ts_data[:, mask], model) + + ## Compute the covariance matrix for model parameters: Gm = d + # C_m_hat = (G.T * C_d^-1, * G)^-1 # the most generic form + # = sigma^2 * (G.T * G)^-1 # assuming the obs error is normally distributed in time. + # Based on the law of integrated expectation, we estimate the obs sigma^2 using + # the OLS estimation residual e_hat_i = d_i - d_hat_i + # sigma^2 = sigma_hat^2 * N / (N - P) + # = (e_hat.T * e_hat) / (N - P) # sigma_hat^2 = (e_hat.T * e_hat) / N + + G_inv = linalg.inv(np.dot(G.T, G)) + var_param = e2.reshape(1, -1) / (num_date - num_param) + m_std = np.sqrt(np.dot(np.diag(G_inv).reshape(-1, 1), var_param)) + + ## for linear velocity, the STD can also be calculated using Eq. (10) from Fattahi and Amelung (2015, JGR) + #ts_diff = ts_data - np.dot(G, m) + #t_diff = G[:, 1] - np.mean(G[:, 1]) + #vel_std = np.sqrt(np.sum(ts_diff ** 2, axis=0) / np.sum(t_diff ** 2) / (num_date - 2)) + #vel_std = np.array(vel_std.reshape(length, width), dtype=dataType) + + ##### write to HDF5 file # prepare attributes atr['FILE_TYPE'] = 'velocity' atr['UNIT'] = 'm/year' @@ -344,11 +423,92 @@ def run_velocity_estimation(inps): for key in configKeys: atr[key_prefix+key] = str(vars(inps)[key]) - # write to HDF5 file - dsDict = dict() - dsDict['velocity'] = vel_lin - dsDict['velocityStd'] = vel_std - writefile.write(dsDict, out_file=inps.outfile, metadata=atr) + # utils setup/function for hdf5 file writing + def print_create_dataset(dsName, dtype=dataType, shape=(length, width)): + print(('create dataset /{d:<20} of {t:<25} in size of {s:<12} ' + 'with compression={c}').format(d=dsName, + t=str(dataType), + s=str(shape), + c=str(None))) + kwargs = dict(dtype=dataType, shape=(length, width), chunks=True, compression=None) + + print('open file: {} with "w" mode'.format(inps.outfile)) + with h5py.File(inps.outfile, 'w') as f: + + # write attributes in the root level + for key, value in atr.items(): + f.attrs[key] = str(value) + + # write dataset + # 1. polynomial + if poly_deg > 0: + for i in range(1, poly_deg+1): + # dataset name + if i == 1: + dsName = 'velocity' + unit = 'm/year' + elif i == 2: + dsName = 'acceleration' + unit = 'm/year^2' + else: + dsName = 'poly{}'.format(i) + unit = 'm/year^{}'.format(i) + + # write + print_create_dataset(dsName) + ds = f.create_dataset(dsName, data=m[i, :], **kwargs) + ds.attrs['UNIT'] = unit + + print_create_dataset(dsName+'Std') + ds = f.create_dataset(dsName+'Std', data=m_std[i, :], **kwargs) + ds.attrs['UNIT'] = unit + + # 2. periodic + p0 = poly_deg + 1 + if num_period > 0: + for i in range(num_period): + # calculate the amplitude and phase of the periodic signal + # following equation (9-10) in Minchew et al. (2017, JGR) + coef_cos = m[p0 + 2*i, :] + coef_sin = m[p0 + 2*i + 1, :] + period_amp = np.sqrt(coef_cos**2 + coef_sin**2) + period_pha = np.zeros(length*width, dtype=dataType) + period_pha[mask] = np.arctan(coef_cos[mask] / coef_sin[mask]) + + # dataset basename + period = model['periodic'][i] + if period == 1: + dsName = 'annualAmp' + elif period == 0.5: + dsName = 'semiAnnualAmp' + else: + dsName = 'periodY{}Amp'.format(period) + + # write to hdf5 file + print_create_dataset(dsName) + ds = f.create_dataset(dsName, data=period_amp, **kwargs) + ds.attrs['UNIT'] = 'm' + + # 1. figure out a proper way to save the phase data in radians + # and keeping smart display in view.py + # to avoid messy scaling together with dataset in m + # 2. add code for the error propagation for the periodic amp/pha + + # 3. step + p0 = (poly_deg + 1) + (2 * num_period) + if num_step > 0: + for i in range(num_step): + dsName = 'step{}'.format(model['step'][i]) + + print_create_dataset(dsName) + ds = f.create_dataset(dsName, data=m[p0+i, :], **kwargs) + ds.attrs['UNIT'] = 'm' + + print_create_dataset(dsName+'Std') + ds = f.create_dataset(dsName+'Std', data=m_std[p0+i, :], **kwargs) + ds.attrs['UNIT'] = 'm' + + print('finished writing to file: {}'.format(inps.outfile)) return inps.outfile @@ -368,4 +528,4 @@ def main(iargs=None): ############################################################################ if __name__ == '__main__': - main() + main(sys.argv[1:]) diff --git a/mintpy/tropo_pyaps.py b/mintpy/tropo_pyaps.py index 64f7da730..219784a2f 100755 --- a/mintpy/tropo_pyaps.py +++ b/mintpy/tropo_pyaps.py @@ -57,7 +57,7 @@ following https://disc.gsfc.nasa.gov/earthdata-login. """ -WEATHER_DIR_DEMO = """--weather-dir ~/WEATHER +WEATHER_DIR_DEMO = """--weather-dir ~/data/aux WEATHER/ /ECMWF ERA-Int_20030329_06.grb diff --git a/mintpy/tropo_pyaps3.py b/mintpy/tropo_pyaps3.py index 82a55463b..1b844a817 100755 --- a/mintpy/tropo_pyaps3.py +++ b/mintpy/tropo_pyaps3.py @@ -80,16 +80,12 @@ For ERA-5 from CDS, you need to agree to the Terms of Use of every datasets that you intend to download. """ -WEATHER_DIR_DEMO = """--weather-dir ~/atmosphere +WEATHER_DIR_DEMO = """--weather-dir ~/data/aux atmosphere/ /ERA5 ERA5_N20_N40_E120_E140_20060624_14.grb ERA5_N20_N40_E120_E140_20060924_14.grb ... - /ECMWF - ERA-Int_20030329_06.grb - ERA-Int_20030503_06.grb - ... /MERRA merra-20110126-06.nc4 merra-20110313-06.nc4 @@ -118,7 +114,7 @@ def create_parser(): delay = parser.add_argument_group('delay calculation') delay.add_argument('-m', '--model', '-s', dest='tropo_model', default='ERA5', choices={'ERA5'}, - #choices={'ERA5', 'ERAINT', 'MERRA', 'NARR'}, + #choices={'ERA5', 'MERRA', 'NARR'}, help='source of the atmospheric model (default: %(default)s).') delay.add_argument('--delay', dest='delay_type', default='comb', choices={'comb', 'dry', 'wet'}, help='Delay type to calculate, comb contains both wet and dry delays (default: %(default)s).') @@ -128,6 +124,9 @@ def create_parser(): 'e.g.: '+WEATHER_DIR_DEMO) delay.add_argument('-g','--geomtry', dest='geom_file', type=str, help='geometry file including height, incidenceAngle and/or latitude and longitude') + delay.add_argument('--custom-height', dest='custom_height', type=float, + help='[for testing] specify a custom height value for delay calculation.') + delay.add_argument('--tropo-file', dest='tropo_file', type=str, help='tropospheric delay file name') delay.add_argument('--verbose', dest='verbose', action='store_true', help='Verbose message.') @@ -594,15 +593,21 @@ def get_dataset_size(fname): # check existing tropo delay file if (ut.run_or_skip(out_file=inps.tropo_file, in_file=inps.grib_files, print_msg=False) == 'skip' - and get_dataset_size(inps.tropo_file) == get_dataset_size(inps.geom_file)): - print('{} file exists and is newer than all GRIB files, skip updating.'.format(inps.tropo_file)) + and get_dataset_size(inps.tropo_file) == get_dataset_size(inps.geom_file) + and all(i in timeseries(inps.tropo_file).get_date_list() for i in inps.date_list)): + print('{} file exists, is newer than all GRIB files and contains all dates, skip updating'.format(inps.tropo_file)) return # prepare geometry data geom_obj = geometry(inps.geom_file) geom_obj.open() - inps.dem = geom_obj.read(datasetName='height') inps.inc = geom_obj.read(datasetName='incidenceAngle') + inps.dem = geom_obj.read(datasetName='height') + + # for testing + if inps.custom_height: + print('use input custom height of {} m for vertical integration'.format(inps.custom_height)) + inps.dem[:] = inps.custom_height if 'latitude' in geom_obj.datasetNames: # for dataset in geo OR radar coord with lookup table in radar-coord (isce, doris) @@ -656,7 +661,7 @@ def correct_timeseries(dis_file, tropo_file, cor_dis_file): print('correcting relative delay for input time-series using diff.py') from mintpy import diff - iargs = [dis_file, tropo_file, '-o', cor_dis_file] + iargs = [dis_file, tropo_file, '-o', cor_dis_file, '--force'] print('diff.py', ' '.join(iargs)) diff.main(iargs) return cor_dis_file diff --git a/mintpy/tsview.py b/mintpy/tsview.py index 92e59ae30..6b8e8ef04 100755 --- a/mintpy/tsview.py +++ b/mintpy/tsview.py @@ -40,7 +40,7 @@ def create_parser(): parser = argparse.ArgumentParser(description='Interactive time-series viewer', formatter_class=argparse.RawTextHelpFormatter, epilog=EXAMPLE) - parser.add_argument('timeseries_file', nargs='+', + parser.add_argument('file', nargs='+', help='time-series file to display\n' 'i.e.: timeseries_ERA5_ramp_demErr.h5 (MintPy)\n' ' LS-PARAMS.h5 (GIAnT)\n' @@ -108,7 +108,7 @@ def cmd_line_parse(iargs=None): raise NotImplementedError(msg) if inps.file_label: - if len(inps.file_label) != len(inps.timeseries_file): + if len(inps.file_label) != len(inps.file): raise Exception('input number of labels != number of files.') if (not inps.disp_fig or inps.outfile) and not inps.save_fig: @@ -139,7 +139,7 @@ def cmd_line_parse(iargs=None): ########################################################################################### def read_init_info(inps): # Time Series Info - ts_file0 = inps.timeseries_file[0] + ts_file0 = inps.file[0] atr = readfile.read_attribute(ts_file0) inps.key = atr['FILE_TYPE'] if inps.key == 'timeseries': @@ -153,7 +153,7 @@ def read_init_info(inps): obj.open(print_msg=inps.print_msg) if not inps.file_label: - inps.file_label = [str(i) for i in list(range(len(inps.timeseries_file)))] + inps.file_label = [str(i) for i in list(range(len(inps.file)))] # default mask file if not inps.mask_file and 'masked' not in ts_file0: @@ -245,7 +245,7 @@ def read_init_info(inps): if inps.ref_lalo[1] > 180.: inps.ref_lalo[1] -= 360. inps.ref_yx = inps.coord.geo2radar(inps.ref_lalo[0], inps.ref_lalo[1], print_msg=False)[0:2] - if not inps.ref_yx: + if not inps.ref_yx and 'REF_Y' in atr.keys(): inps.ref_yx = [int(atr['REF_Y']), int(atr['REF_X'])] # Initial Pixel Coord @@ -319,7 +319,7 @@ def read_timeseries_data(inps): """ # read list of 3D time-series ts_data = [] - for fname in inps.timeseries_file: + for fname in inps.file: vprint('reading timeseries from file {} ...'.format(fname)) data, atr = readfile.read(fname, datasetName=inps.date_list, box=inps.pix_box) try: @@ -341,7 +341,7 @@ def read_timeseries_data(inps): # Mask file: input mask file + non-zero ts pixels - ref_point mask = np.ones(ts_data[0].shape[-2:], np.bool_) - msk = pp.read_mask(inps.timeseries_file[0], + msk = pp.read_mask(inps.file[0], mask_file=inps.mask_file, datasetName='displacement', box=inps.pix_box, @@ -381,7 +381,7 @@ def read_timeseries_data(inps): vprint('display range: {} {}'.format(inps.vlim, inps.disp_unit)) # default ylim - num_file = len(inps.timeseries_file) + num_file = len(inps.file) if not inps.ylim: ts_data_mli = multilook_data(np.squeeze(ts_data[-1]), 4, 4) if inps.zero_first: @@ -497,7 +497,7 @@ def save_ts_plot(yx, fig_img, fig_pts, d_ts, inps): # TXT - point time-series outName = '{}_ts.txt'.format(inps.outfile_base) - header_info = 'timeseries_file={}\n'.format(inps.timeseries_file) + header_info = 'time-series file={}\n'.format(inps.file) header_info += '{}\n'.format(_get_ts_title(yx[0], yx[1], inps.coord)) header_info += 'reference pixel: y={}, x={}\n'.format(inps.ref_yx[0], inps.ref_yx[1]) header_info += 'reference date: {}\n'.format(inps.date_list[inps.ref_idx]) diff --git a/mintpy/unwrap_error_phase_closure.py b/mintpy/unwrap_error_phase_closure.py index 9bf48149d..0a76bc821 100755 --- a/mintpy/unwrap_error_phase_closure.py +++ b/mintpy/unwrap_error_phase_closure.py @@ -172,9 +172,9 @@ def run_or_skip(inps): ########################################################################################## -def calc_num_nonzero_integer_closure_phase(ifgram_file, mask_file=None, dsName='unwrapPhase', - out_file=None, step=50, update_mode=True): - """Calculate the number of non-zero integer ambiguity of closure phase. +def calc_num_triplet_with_nonzero_integer_ambiguity(ifgram_file, mask_file=None, dsName='unwrapPhase', + out_file=None, step=50, update_mode=True): + """Calculate the number of triplets with non-zero integer ambiguity of closure phase. T_int as shown in equation (8-9) and inline in Yunjun et al. (2019, CAGEO). @@ -185,22 +185,48 @@ def calc_num_nonzero_integer_closure_phase(ifgram_file, mask_file=None, dsName=' step - int, number of row in each block to calculate T_int update_mode - bool Returns: out_file - str, custom output filename - Example: calc_num_nonzero_integer_closure_phase('inputs/ifgramStack.h5', mask_file='waterMask.h5') + Example: calc_num_triplet_with_nonzero_integer_ambiguity('inputs/ifgramStack.h5', mask_file='waterMask.h5') """ # default output file path + out_dir = os.path.dirname(os.path.dirname(ifgram_file)) if out_file is None: - out_dir = os.path.dirname(os.path.dirname(ifgram_file)) if dsName == 'unwrapPhase': # skip the default dsName in output filename - out_file = 'numNonzeroIntClosure.h5' + out_file = 'numTriNonzeroIntAmbiguity.h5' else: - out_file = 'numNonzeroIntClosure4{}.h5'.format(dsName) + out_file = 'numTriNonzeroIntAmbiguity4{}.h5'.format(dsName) out_file = os.path.join(out_dir, out_file) + # update mode if update_mode and os.path.isfile(out_file): - print('output file "{}" already exists, skip re-calculating.'.format(out_file)) - return out_file + print('update mode: ON') + print('1) output file "{}" already exists'.format(out_file)) + flag = 'skip' + + # check modification time + with h5py.File(ifgram_file, 'r') as f: + ti = float(f[dsName].attrs.get('MODIFICATION_TIME', os.path.getmtime(ifgram_file))) + to = os.path.getmtime(out_file) + if ti > to: + print('2) output file is NOT newer than input dataset') + flag = 'run' + else: + print('2) output file is newer than input dataset') + + # check REF_Y/X + key_list = ['REF_Y', 'REF_X'] + atri = readfile.read_attribute(ifgram_file) + atro = readfile.read_attribute(out_file) + if not all(atri[i] == atro[i] for i in key_list): + print('3) NOT all key configurations are the same: {}'.format(key_list)) + flag = 'run' + else: + print('3) all key configurations are the same: {}'.format(key_list)) + + print('run or skip: {}.'.format(flag)) + if flag == 'skip': + return out_file # read ifgramStack file stack_obj = ifgramStack(ifgram_file) @@ -246,14 +272,20 @@ def calc_num_nonzero_integer_closure_phase(ifgram_file, mask_file=None, dsName=' closure_int = np.round((closure_pha - ut.wrap(closure_pha)) / (2.*np.pi)) num_nonzero_closure[r0:r1, :] = np.sum(closure_int != 0, axis=0).reshape(-1, width) - prog_bar.update(i+1, every=1) + prog_bar.update(i+1, every=1, suffix='line {} / {}'.format(r0, length)) prog_bar.close() # mask if mask_file is not None: - print('masking with file', mask_file) mask = readfile.read(mask_file)[0] num_nonzero_closure[mask == 0] = np.nan + print('mask out pixels with zero in file:', mask_file) + + coh_file = os.path.join(out_dir, 'avgSpatialCoh.h5') + if os.path.isfile(coh_file): + coh = readfile.read(coh_file)[0] + num_nonzero_closure[coh == 0] = np.nan + print('mask out pixels with zero in file:', coh_file) # write to disk print('write to file', out_file) @@ -263,13 +295,13 @@ def calc_num_nonzero_integer_closure_phase(ifgram_file, mask_file=None, dsName=' writefile.write(num_nonzero_closure, out_file, meta) # plot - plot_num_nonzero_integer_closure_phase(out_file) + plot_num_triplet_with_nonzero_integer_ambiguity(out_file) return out_file -def plot_num_nonzero_integer_closure_phase(fname, display=False, font_size=12, fig_size=[9,3]): - """Plot the histogram for the number of non-zero integer ambiguity +def plot_num_triplet_with_nonzero_integer_ambiguity(fname, display=False, font_size=12, fig_size=[9,3]): + """Plot the histogram for the number of triplets with non-zero integer ambiguity Fig. 3d-e in Yunjun et al. (2019, CAGEO). """ @@ -299,7 +331,7 @@ def plot_num_nonzero_integer_closure_phase(fname, display=False, font_size=12, f ax.hist(data[~np.isnan(data)].flatten(), range=(0, vmax), log=True, bins=vmax) # axis format - ax.set_xlabel(r'# of non-zero integer ambiguity $T_{int}$', fontsize=font_size) + ax.set_xlabel(r'# of triplets w non-zero int ambiguity $T_{int}$', fontsize=font_size) ax.set_ylabel('# of pixels', fontsize=font_size) ax.xaxis.set_minor_locator(ticker.AutoMinorLocator()) ax.yaxis.set_major_locator(ticker.LogLocator(base=10.0, numticks=15)) @@ -565,14 +597,14 @@ def main(iargs=None): else: # calculate the number of triplets with non-zero integer ambiguity - out_file = calc_num_nonzero_integer_closure_phase(inps.ifgram_file, - mask_file=inps.waterMaskFile, - dsName=inps.datasetNameIn, - update_mode=inps.update_mode) + out_file = calc_num_triplet_with_nonzero_integer_ambiguity(inps.ifgram_file, + mask_file=inps.waterMaskFile, + dsName=inps.datasetNameIn, + update_mode=inps.update_mode) # for debug - #plot_num_nonzero_integer_closure_phase(out_file) + #plot_num_triplet_with_nonzero_integer_ambiguity(out_file) m, s = divmod(time.time()-start_time, 60) - print('\ntime used: {:02.0f} mins {:02.1f} secs\nDone.'.format(m, s)) + print('time used: {:02.0f} mins {:02.1f} secs\nDone.'.format(m, s)) return diff --git a/mintpy/utils/isce_utils.py b/mintpy/utils/isce_utils.py old mode 100644 new mode 100755 index 363bb5866..4aac727ce --- a/mintpy/utils/isce_utils.py +++ b/mintpy/utils/isce_utils.py @@ -3,6 +3,8 @@ # Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi # # Author: Zhang Yunjun, Heresh Fattahi, Apr 2020 # ############################################################ +# 2020-07: Talib Oliver-Cabrera, add UAVSAR support w/in stripmapStack +# 2020-10: Cunren Liang, add alosStack support # Recommend import: # from mintpy.utils import isce_utils @@ -10,9 +12,10 @@ import os import glob import shelve +import datetime import numpy as np from mintpy.objects import sensor -from mintpy.utils import readfile, writefile, utils1 as ut +from mintpy.utils import ptime, readfile, writefile, utils1 as ut # suppress matplotlib DEBUG message import logging @@ -25,6 +28,83 @@ +def get_processor(meta_file): + """ + Get the name of ISCE processor (imaging mode) + """ + meta_dir = os.path.dirname(meta_file) + tops_meta_file = os.path.join(meta_dir, 'IW*.xml') + stripmap_meta_files = [os.path.join(meta_dir, i) for i in ['data.dat', 'data']] + alosStack_meta_frame_files = glob.glob(os.path.join(meta_dir, 'f1_*', '*.frame.xml')) + + processor = None + if len(glob.glob(tops_meta_file)) > 0: + # topsStack + processor = 'tops' + + elif any(os.path.isfile(i) for i in stripmap_meta_files): + # stripmapStack + processor = 'stripmap' + + elif alosStack_meta_frame_files != []: + # alosStack + processor = 'alosStack' + + elif meta_file.endswith('.xml'): + # stripmapApp + processor = 'stripmap' + + else: + raise ValueError('Un-recognized ISCE processor for metadata file: {}'.format(meta_file)) + return processor + + +def get_IPF(proj_dir, ts_file): + """Grab the IPF version number of each sub-swatch for Sentinel-1 time-series + + Parameters: proj_dir - str, path of the project directory + E.g.: ~/data/AtacamaSenDT149 + ts_file - str, path of HDF5 file for time-series + Returns: date_list - list of str, dates in YYYYMMDD format + IFP_IW1/2/3 - list of str, IFP version number + """ + from mintpy.objects import timeseries + + s_dir = os.path.join(proj_dir, 'secondarys') + m_dir = os.path.join(proj_dir, 'reference') + + # date list + date_list = timeseries(ts_file).get_date_list() + num_date = len(date_list) + # reference date + m_date = [i for i in date_list if not os.path.isdir(os.path.join(s_dir, i))][0] + + # grab IPF numver + IPF_IW1, IPF_IW2, IPF_IW3 = [], [], [] + prog_bar = ptime.progressBar(maxValue=num_date) + for i in range(num_date): + date_str = date_list[i] + + # get xml_dir + if date_str == m_date: + xml_dir = m_dir + else: + xml_dir = os.path.join(s_dir, date_str) + + # grab IPF version number + for j, IPF_IW in enumerate([IPF_IW1, IPF_IW2, IPF_IW3]): + xml_file = os.path.join(xml_dir, 'IW{}.xml'.format(j+1)) + IPFv = load_product(xml_file).processingSoftwareVersion + IPF_IW.append('{:.02f}'.format(float(IPFv))) + + prog_bar.update(i+1, suffix='{} IW1/2/3'.format(date_str)) + prog_bar.close() + return date_list, IPF_IW1, IPF_IW2, IPF_IW3 + + + +##################################### multilook ####################################### + def multilook_number2resolution(meta_file, az_looks, rg_looks): # get full resolution info az_pixel_size, az_spacing, rg_pixel_size, rg_spacing = get_full_resolution(meta_file) @@ -110,31 +190,6 @@ def get_full_resolution(meta_file): return az_pixel_size, az_spacing, rg_pixel_size, rg_spacing -def get_processor(meta_file): - """ - Get the name of ISCE processor (imaging mode) - """ - meta_dir = os.path.dirname(meta_file) - tops_meta_file = os.path.join(meta_dir, 'IW*.xml') - stripmap_meta_files = [os.path.join(meta_dir, i) for i in ['data.dat', 'data']] - - processor = None - if len(glob.glob(tops_meta_file)) > 0: - # topsStack - processor = 'tops' - - elif any(os.path.isfile(i) for i in stripmap_meta_files): - # stripmapStack - processor = 'stripmap' - - elif meta_file.endswith('.xml'): - # stripmapApp - processor = 'stripmap' - - else: - raise ValueError('Un-recognized ISCE processor for metadata file: {}'.format(meta_file)) - return processor - ##################################### metadata ####################################### def load_product(xmlname): @@ -152,7 +207,7 @@ def extract_isce_metadata(meta_file, geom_dir=None, rsc_file=None, update_mode=T Parameters: meta_file : str, path of metadata file, reference/IW1.xml or referenceShelve/data.dat geom_dir : str, path of geometry directory. rsc_file : str, output file name of ROIPAC format rsc file. None for not write to disk. - Returns: metadata : dict + Returns: meta : dict frame : object, isceobj.Scene.Frame.Frame / isceobj.Scene.Burst.Burst """ # check existing rsc_file @@ -163,30 +218,36 @@ def extract_isce_metadata(meta_file, geom_dir=None, rsc_file=None, update_mode=T processor = get_processor(meta_file) if processor == 'tops': print('extract metadata from ISCE/topsStack xml file:', meta_file) - metadata, frame = extract_tops_metadata(meta_file) + meta, frame = extract_tops_metadata(meta_file) + + elif processor == 'alosStack': + print('extract metadata from ISCE/alosStack xml file:', meta_file) + meta, frame = extract_alosStack_metadata(meta_file, geom_dir) else: print('extract metadata from ISCE/stripmapStack shelve file:', meta_file) - metadata, frame = extract_stripmap_metadata(meta_file) + meta, frame = extract_stripmap_metadata(meta_file) # 2. extract metadata from geometry file if geom_dir: - metadata = extract_geometry_metadata(geom_dir, metadata) + if processor != 'alosStack': + meta = extract_geometry_metadata(geom_dir, meta) # 3. common metadata - metadata['PROCESSOR'] = 'isce' - metadata['ANTENNA_SIDE'] = '-1' + meta['PROCESSOR'] = 'isce' + if 'ANTENNA_SIDE' not in meta.keys(): + meta['ANTENNA_SIDE'] = '-1' # convert all value to string format - for key, value in metadata.items(): - metadata[key] = str(value) + for key, value in meta.items(): + meta[key] = str(value) # write to .rsc file - metadata = readfile.standardize_metadata(metadata) + meta = readfile.standardize_metadata(meta) if rsc_file: print('writing ', rsc_file) - writefile.write_roipac_rsc(metadata, rsc_file) - return metadata, frame + writefile.write_roipac_rsc(meta, rsc_file) + return meta, frame def extract_tops_metadata(xml_file): @@ -203,57 +264,63 @@ def extract_tops_metadata(xml_file): burst = obj.bursts[0] burstEnd = obj.bursts[-1] - metadata = {} - metadata['prf'] = burst.prf - metadata['startUTC'] = burst.burstStartUTC - metadata['stopUTC'] = burstEnd.burstStopUTC - metadata['radarWavelength'] = burst.radarWavelength - metadata['startingRange'] = burst.startingRange - metadata['passDirection'] = burst.passDirection - metadata['polarization'] = burst.polarization - metadata['trackNumber'] = burst.trackNumber - metadata['orbitNumber'] = burst.orbitNumber - metadata['PLATFORM'] = sensor.standardize_sensor_name(obj.spacecraftName) - - time_seconds = (burst.burstStartUTC.hour * 3600.0 + - burst.burstStartUTC.minute * 60.0 + - burst.burstStartUTC.second) - metadata['CENTER_LINE_UTC'] = time_seconds + meta = {} + meta['prf'] = burst.prf + meta['startUTC'] = burst.burstStartUTC + meta['stopUTC'] = burstEnd.burstStopUTC + meta['radarWavelength'] = burst.radarWavelength + meta['startingRange'] = burst.startingRange + meta['passDirection'] = burst.passDirection + meta['polarization'] = burst.polarization + meta['trackNumber'] = burst.trackNumber + meta['orbitNumber'] = burst.orbitNumber + + try: + meta['PLATFORM'] = sensor.standardize_sensor_name(obj.spacecraftName) + except: + if os.path.basename(xml_file).startswith('IW'): + meta['PLATFORM'] = 'sen' + + time_seconds = (burst.sensingMid.hour * 3600.0 + + burst.sensingMid.minute * 60.0 + + burst.sensingMid.second) + meta['CENTER_LINE_UTC'] = time_seconds orbit = burst.orbit peg = orbit.interpolateOrbit(burst.sensingMid, method='hermite') # Sentinel-1 TOPS pixel spacing Vs = np.linalg.norm(peg.getVelocity()) #satellite speed - metadata['azimuthPixelSize'] = Vs*burst.azimuthTimeInterval - metadata['rangePixelSize'] = burst.rangePixelSize + meta['azimuthPixelSize'] = Vs * burst.azimuthTimeInterval + meta['rangePixelSize'] = burst.rangePixelSize # Sentinel-1 TOPS spatial resolution iw_str = 'IW2' if os.path.basename(xml_file).startswith('IW'): iw_str = os.path.splitext(os.path.basename(xml_file))[0] - metadata['azimuthResolution'] = sensor.SENSOR_DICT['sen'][iw_str]['azimuth_resolution'] - metadata['rangeResolution'] = sensor.SENSOR_DICT['sen'][iw_str]['range_resolution'] + meta['azimuthResolution'] = sensor.SENSOR_DICT['sen'][iw_str]['azimuth_resolution'] + meta['rangeResolution'] = sensor.SENSOR_DICT['sen'][iw_str]['range_resolution'] - refElp = Planet(pname='Earth').ellipsoid - llh = refElp.xyz_to_llh(peg.getPosition()) - refElp.setSCH(llh[0], llh[1], orbit.getENUHeading(burst.sensingMid)) - metadata['earthRadius'] = refElp.pegRadCur - metadata['altitude'] = llh[2] + elp = Planet(pname='Earth').ellipsoid + llh = elp.xyz_to_llh(peg.getPosition()) + elp.setSCH(llh[0], llh[1], orbit.getENUHeading(burst.sensingMid)) + meta['HEADING'] = orbit.getENUHeading(burst.sensingMid) + meta['earthRadius'] = elp.pegRadCur + meta['altitude'] = llh[2] # for Sentinel-1 - metadata['beam_mode'] = 'IW' - metadata['swathNumber'] = burst.swathNumber + meta['beam_mode'] = 'IW' + meta['swathNumber'] = burst.swathNumber # 1. multipel subswaths xml_files = glob.glob(os.path.join(os.path.dirname(xml_file), 'IW*.xml')) if len(xml_files) > 1: swath_num = [load_product(fname).bursts[0].swathNumber for fname in xml_files] - metadata['swathNumber'] = ''.join(str(i) for i in sorted(swath_num)) + meta['swathNumber'] = ''.join(str(i) for i in sorted(swath_num)) # 2. calculate ASF frame number for Sentinel-1 - metadata['firstFrameNumber'] = int(0.2 * (burst.burstStartUTC - obj.ascendingNodeTime).total_seconds()) - metadata['lastFrameNumber'] = int(0.2 * (burstEnd.burstStopUTC - obj.ascendingNodeTime).total_seconds()) - return metadata, burst + meta['firstFrameNumber'] = int(0.2 * (burst.burstStartUTC - obj.ascendingNodeTime).total_seconds()) + meta['lastFrameNumber'] = int(0.2 * (burstEnd.burstStopUTC - obj.ascendingNodeTime).total_seconds()) + return meta, burst def extract_stripmap_metadata(meta_file): @@ -280,49 +347,214 @@ def extract_stripmap_metadata(meta_file): else: raise ValueError('un-recognized isce/stripmap metadata file: {}'.format(meta_file)) - metadata = {} - metadata['prf'] = frame.PRF - metadata['startUTC'] = frame.sensingStart - metadata['stopUTC'] = frame.sensingStop - metadata['radarWavelength'] = frame.radarWavelegth - metadata['startingRange'] = frame.startingRange - metadata['polarization'] = str(frame.polarization).replace('/', '') - if metadata['polarization'].startswith("b'"): - metadata['polarization'] = metadata['polarization'][2:4] - metadata['trackNumber'] = frame.trackNumber - metadata['orbitNumber'] = frame.orbitNumber - metadata['PLATFORM'] = sensor.standardize_sensor_name(frame.platform.getSpacecraftName()) - - time_seconds = (frame.sensingStart.hour * 3600.0 + - frame.sensingStart.minute * 60.0 + - frame.sensingStart.second) - metadata['CENTER_LINE_UTC'] = time_seconds + meta = {} + meta['prf'] = frame.PRF + meta['startUTC'] = frame.sensingStart + meta['stopUTC'] = frame.sensingStop + meta['radarWavelength'] = frame.radarWavelegth + meta['startingRange'] = frame.startingRange + meta['trackNumber'] = frame.trackNumber + meta['orbitNumber'] = frame.orbitNumber + meta['PLATFORM'] = sensor.standardize_sensor_name(frame.platform.getSpacecraftName()) + meta['polarization'] = str(frame.polarization).replace('/', '') + if meta['polarization'].startswith("b'"): + meta['polarization'] = meta['polarization'][2:4] + + time_seconds = (frame.sensingMid.hour * 3600.0 + + frame.sensingMid.minute * 60.0 + + frame.sensingMid.second) + meta['CENTER_LINE_UTC'] = time_seconds orbit = frame.orbit peg = orbit.interpolateOrbit(frame.sensingMid, method='hermite') Vs = np.linalg.norm(peg.getVelocity()) #satellite speed - metadata['azimuthResolution'] = frame.platform.antennaLength / 2.0 - metadata['azimuthPixelSize'] = Vs / frame.PRF + meta['azimuthResolution'] = frame.platform.antennaLength / 2.0 + meta['azimuthPixelSize'] = Vs / frame.PRF frame.getInstrument() rgBandwidth = frame.instrument.pulseLength * frame.instrument.chirpSlope - metadata['rangeResolution'] = abs(SPEED_OF_LIGHT / (2.0 * rgBandwidth)) - metadata['rangePixelSize'] = frame.instrument.rangePixelSize + meta['rangeResolution'] = abs(SPEED_OF_LIGHT / (2.0 * rgBandwidth)) + meta['rangePixelSize'] = frame.instrument.rangePixelSize - refElp = Planet(pname='Earth').ellipsoid - llh = refElp.xyz_to_llh(peg.getPosition()) - refElp.setSCH(llh[0], llh[1], orbit.getENUHeading(frame.sensingMid)) - metadata['earthRadius'] = refElp.pegRadCur - metadata['altitude'] = llh[2] + elp = Planet(pname='Earth').ellipsoid + llh = elp.xyz_to_llh(peg.getPosition()) + elp.setSCH(llh[0], llh[1], orbit.getENUHeading(frame.sensingMid)) + meta['HEADING'] = orbit.getENUHeading(frame.sensingMid) + meta['earthRadius'] = elp.pegRadCur + meta['altitude'] = llh[2] # for StripMap - metadata['beam_mode'] = 'SM' - return metadata, frame + meta['beam_mode'] = 'SM' + return meta, frame + + +def extract_alosStack_metadata(meta_file, geom_dir): + """Read metadata for ISCE/ALOS-2/4 stack from the following files: + pairs/*-*/ + {date}.track.xml + f*_*/{}.frame.xml + f1_{frameNum}/{frameNum}.frame.xml + + pairs + shelve file for StripMap stack from ISCE + Parameters: meta_file : str, path of the shelve file, i.e. referenceShelve/data.dat + Returns: meta : dict, metadata + frame : isceobj.Scene.Frame.Frame object + """ + + import isce + import isceobj + from isceobj.Planet.Planet import Planet + + track = load_track(os.path.dirname(meta_file), os.path.basename(meta_file).strip('.track.xml')) + rlooks, alooks, width, length = extract_image_size_alosStack(geom_dir) + spotlightModes, stripmapModes, scansarNominalModes, scansarWideModes, scansarModes = alos2_acquisition_modes() + + meta = {} + meta['prf'] = track.prf + meta['startUTC'] = track.sensingStart + datetime.timedelta(seconds=(alooks-1.0)/2.0*track.azimuthLineInterval) + meta['stopUTC'] = meta['startUTC'] + datetime.timedelta(seconds=(length-1)*alooks*track.azimuthLineInterval) + meta['radarWavelength'] = track.radarWavelength + meta['startingRange'] = track.startingRange + (rlooks-1.0)/2.0*track.rangePixelSize + meta['passDirection'] = track.passDirection.upper() + meta['polarization'] = track.frames[0].swaths[0].polarization + #meta['trackNumber'] = track.trackNumber + #meta['orbitNumber'] = track.orbitNumber + + meta['PLATFORM'] = sensor.standardize_sensor_name('alos2') + + sensingMid = meta['startUTC'] + datetime.timedelta(seconds=(meta['stopUTC']-meta['startUTC']).total_seconds()/2.0) + time_seconds = (sensingMid.hour * 3600.0 + + sensingMid.minute * 60.0 + + sensingMid.second) + meta['CENTER_LINE_UTC'] = time_seconds + + peg = track.orbit.interpolateOrbit(sensingMid, method='hermite') + Vs = np.linalg.norm(peg.getVelocity()) + meta['azimuthPixelSize'] = Vs * track.azimuthLineInterval + meta['rangePixelSize'] = track.rangePixelSize + + azBandwidth = track.prf * 0.8 + if track.operationMode in scansarNominalModes: + azBandwidth /= 5.0 + if track.operationMode in scansarWideModes: + azBandwidth /= 7.0 + #use a mean burst synchronizatino here + if track.operationMode in scansarModes: + azBandwidth *= 0.85 + + meta['azimuthResolution'] = Vs * (1.0/azBandwidth) + meta['rangeResolution'] = 0.5 * SPEED_OF_LIGHT * (1.0/track.frames[0].swaths[0].rangeBandwidth) + + elp = Planet(pname='Earth').ellipsoid + llh = elp.xyz_to_llh(peg.getPosition()) + elp.setSCH(llh[0], llh[1], track.orbit.getENUHeading(sensingMid)) + meta['HEADING'] = track.orbit.getENUHeading(sensingMid) + meta['earthRadius'] = elp.pegRadCur + meta['altitude'] = llh[2] + + meta['beam_mode'] = track.operationMode + meta['swathNumber'] = ''.join(str(swath.swathNumber) for swath in track.frames[0].swaths) + + meta['firstFrameNumber'] = track.frames[0].frameNumber + meta['lastFrameNumber'] = track.frames[-1].frameNumber + + meta['ALOOKS'] = alooks + meta['RLOOKS'] = rlooks + + # NCORRLOOKS for coherence calibration + rgfact = float(meta['rangeResolution']) / float(meta['rangePixelSize']) + azfact = float(meta['azimuthResolution']) / float(meta['azimuthPixelSize']) + meta['NCORRLOOKS'] = meta['RLOOKS'] * meta['ALOOKS'] / (rgfact * azfact) + + # update pixel_size for multilooked data + meta['rangePixelSize'] *= meta['RLOOKS'] + meta['azimuthPixelSize'] *= meta['ALOOKS'] + + edge = 3 + lat_file = glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.lat'.format(rlooks, alooks)))[0] + img = isceobj.createImage() + img.load(lat_file+'.xml') + width = img.width + length = img.length + data = np.memmap(lat_file, dtype='float64', mode='r', shape=(length, width)) + meta['LAT_REF1'] = str(data[0+edge, 0+edge]) + meta['LAT_REF2'] = str(data[0+edge, -1-edge]) + meta['LAT_REF3'] = str(data[-1-edge, 0+edge]) + meta['LAT_REF4'] = str(data[-1-edge, -1-edge]) + + lon_file = glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.lon'.format(rlooks, alooks)))[0] + data = np.memmap(lon_file, dtype='float64', mode='r', shape=(length, width)) + meta['LON_REF1'] = str(data[0+edge, 0+edge]) + meta['LON_REF2'] = str(data[0+edge, -1-edge]) + meta['LON_REF3'] = str(data[-1-edge, 0+edge]) + meta['LON_REF4'] = str(data[-1-edge, -1-edge]) + + los_file = glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.los'.format(rlooks, alooks)))[0] + data = np.memmap(los_file, dtype='float32', mode='r', shape=(length*2, width))[0:length*2:2, :] + inc_angle = data[int(length/2), int(width/2)] + meta['CENTER_INCIDENCE_ANGLE'] = str(inc_angle) + + pointingDirection = {'right': -1, 'left' :1} + meta['ANTENNA_SIDE'] = str(pointingDirection[track.pointingDirection]) + + return meta, track + + +def alos2_acquisition_modes(): + ''' + return ALOS-2 acquisition mode + ''' + + spotlightModes = ['SBS'] + stripmapModes = ['UBS', 'UBD', 'HBS', 'HBD', 'HBQ', 'FBS', 'FBD', 'FBQ'] + scansarNominalModes = ['WBS', 'WBD', 'WWS', 'WWD'] + scansarWideModes = ['VBS', 'VBD'] + scansarModes = ['WBS', 'WBD', 'WWS', 'WWD', 'VBS', 'VBD'] + + return (spotlightModes, stripmapModes, scansarNominalModes, scansarWideModes, scansarModes) + + +def extract_image_size_alosStack(geom_dir): + import isce + import isceobj + + # grab the number of looks in azimuth / range direction + lats = glob.glob(os.path.join(geom_dir, '*_*rlks_*alks.lat')) + rlooks = max([int(os.path.splitext(os.path.basename(x))[0].split('_')[1].strip('rlks')) for x in lats]) + alooks = max([int(os.path.splitext(os.path.basename(x))[0].split('_')[2].strip('alks')) for x in lats]) + + # grab the number of rows / coluns + lat = glob.glob(os.path.join(geom_dir, '*_{}rlks_{}alks.lat'.format(rlooks, alooks)))[0] + img = isceobj.createImage() + img.load(lat+'.xml') + width = img.width + length = img.length + + return (rlooks, alooks, width, length) + + +def load_track(trackDir, date): + ''' + Load the track using Product Manager. + trackDir: where *.track.xml is located + date: YYMMDD + ''' + + track = load_product(os.path.join(trackDir, '{}.track.xml'.format(date))) + + track.frames = [] + fnames = sorted(glob.glob(os.path.join(trackDir, 'f*_*/{}.frame.xml'.format(date)))) + for fname in fnames: + track.frames.append(load_product(fname)) + + return track + ##################################### geometry ####################################### -def extract_multilook_number(geom_dir, metadata=dict(), fext_list=['.rdr','.geo','.rdr.full','.geo.full']): +def extract_multilook_number(geom_dir, meta=dict(), fext_list=['.rdr','.geo','.rdr.full','.geo.full']): for fbase in ['hgt','lat','lon','los']: fbase = os.path.join(geom_dir, fbase) for fext in fext_list: @@ -335,23 +567,23 @@ def extract_multilook_number(geom_dir, metadata=dict(), fext_list=['.rdr','.geo' if os.path.isfile(fullXmlFile): fullXmlDict = readfile.read_isce_xml(fullXmlFile) xmlDict = readfile.read_attribute(fnames[0]) - metadata['ALOOKS'] = int(int(fullXmlDict['LENGTH']) / int(xmlDict['LENGTH'])) - metadata['RLOOKS'] = int(int(fullXmlDict['WIDTH']) / int(xmlDict['WIDTH'])) + meta['ALOOKS'] = int(int(fullXmlDict['LENGTH']) / int(xmlDict['LENGTH'])) + meta['RLOOKS'] = int(int(fullXmlDict['WIDTH']) / int(xmlDict['WIDTH'])) break # default value for key in ['ALOOKS', 'RLOOKS']: - if key not in metadata: - metadata[key] = 1 + if key not in meta: + meta[key] = 1 # NCORRLOOKS for coherence calibration - rgfact = float(metadata['rangeResolution']) / float(metadata['rangePixelSize']) - azfact = float(metadata['azimuthResolution']) / float(metadata['azimuthPixelSize']) - metadata['NCORRLOOKS'] = metadata['RLOOKS'] * metadata['ALOOKS'] / (rgfact * azfact) - return metadata + rgfact = float(meta['rangeResolution']) / float(meta['rangePixelSize']) + azfact = float(meta['azimuthResolution']) / float(meta['azimuthPixelSize']) + meta['NCORRLOOKS'] = meta['RLOOKS'] * meta['ALOOKS'] / (rgfact * azfact) + return meta -def extract_geometry_metadata(geom_dir, metadata=dict(), box=None, fbase_list=['hgt','lat','lon','los'], +def extract_geometry_metadata(geom_dir, meta=dict(), box=None, fbase_list=['hgt','lat','lon','los'], fext_list=['.rdr','.geo','.rdr.full','.geo.full']): """Extract / update metadata from geometry files @@ -389,51 +621,42 @@ def get_nonzero_row_number(data, buffer=2): msg += '\n file basenme: {}'.format(fbase_list) msg += '\n file extension: {}'.format(fext_list) print(msg) - return metadata + return meta print('extract metadata from geometry files: {}'.format([os.path.basename(i) for i in geom_files])) # get A/RLOOKS - metadata = extract_multilook_number(geom_dir, metadata, fext_list=fext_list) + meta = extract_multilook_number(geom_dir, meta, fext_list=fext_list) # update pixel_size for multilooked data - metadata['rangePixelSize'] *= metadata['RLOOKS'] - metadata['azimuthPixelSize'] *= metadata['ALOOKS'] + meta['rangePixelSize'] *= meta['RLOOKS'] + meta['azimuthPixelSize'] *= meta['ALOOKS'] - # get LAT/LON_REF1/2/3/4 and HEADING into metadata + # get LAT/LON_REF1/2/3/4 into metadata for geom_file in geom_files: if 'lat' in os.path.basename(geom_file): data = readfile.read(geom_file, box=box)[0] r0, r1 = get_nonzero_row_number(data) - metadata['LAT_REF1'] = str(data[r0, 0]) - metadata['LAT_REF2'] = str(data[r0, -1]) - metadata['LAT_REF3'] = str(data[r1, 0]) - metadata['LAT_REF4'] = str(data[r1, -1]) + meta['LAT_REF1'] = str(data[r0, 0]) + meta['LAT_REF2'] = str(data[r0, -1]) + meta['LAT_REF3'] = str(data[r1, 0]) + meta['LAT_REF4'] = str(data[r1, -1]) if 'lon' in os.path.basename(geom_file): data = readfile.read(geom_file, box=box)[0] r0, r1 = get_nonzero_row_number(data) - metadata['LON_REF1'] = str(data[r0, 0]) - metadata['LON_REF2'] = str(data[r0, -1]) - metadata['LON_REF3'] = str(data[r1, 0]) - metadata['LON_REF4'] = str(data[r1, -1]) + meta['LON_REF1'] = str(data[r0, 0]) + meta['LON_REF2'] = str(data[r0, -1]) + meta['LON_REF3'] = str(data[r1, 0]) + meta['LON_REF4'] = str(data[r1, -1]) if 'los' in os.path.basename(geom_file): - data = readfile.read(geom_file, datasetName='az', box=box)[0] - # HEADING - data[data == 0.] = np.nan - az_angle = np.nanmean(data) - # convert isce azimuth angle to roipac orbit heading angle - head_angle = -1 * (270 + az_angle) - head_angle -= np.round(head_angle / 360.) * 360. - metadata['HEADING'] = str(head_angle) - # CENTER_INCIDENCE_ANGLE data = readfile.read(geom_file, datasetName='inc', box=box)[0] data[data == 0.] = np.nan inc_angle = data[int(data.shape[0]/2), int(data.shape[1]/2)] - metadata['CENTER_INCIDENCE_ANGLE'] = str(inc_angle) - return metadata + meta['CENTER_INCIDENCE_ANGLE'] = str(inc_angle) + return meta ##################################### baseline ####################################### @@ -474,11 +697,29 @@ def read_stripmap_baseline(baseline_file): return [bperp_top, bperp_bottom] +def read_alosStack_baseline(baseline_file): + '''read baseline file generated by alosStack + ''' + bDict = {} + with open(baseline_file, 'r') as f: + lines = [line for line in f if line.strip() != ''] + for x in lines[2:]: + blist = x.split() + #to fit into the format of other processors, all alos satellites are after 2000 + blist[0] = '20' + blist[0] + blist[1] = '20' + blist[1] + bDict[blist[1]] = [float(blist[3]), float(blist[3])] + bDict[blist[0]] = [0, 0] + + return bDict, blist[0] + + def read_baseline_timeseries(baseline_dir, processor='tops', ref_date=None): """Read bperp time-series from files in baselines directory Parameters: baseline_dir : str, path to the baselines directory processor : str, tops for Sentinel-1/TOPS stripmap for StripMap data + ref_date : str, reference date in (YY)YYMMDD Returns: bDict : dict, in the following format: {'20141213': [0.0, 0.0], '20141225': [104.6, 110.1], @@ -492,30 +733,43 @@ def read_baseline_timeseries(baseline_dir, processor='tops', ref_date=None): bFiles = sorted(glob.glob(os.path.join(baseline_dir, '*/*.txt'))) elif processor == 'stripmap': bFiles = sorted(glob.glob(os.path.join(baseline_dir, '*.txt'))) + elif processor == 'alosStack': + # all baselines are in baseline_center.txt + bFiles = glob.glob(os.path.join(baseline_dir, 'baseline_center.txt')) else: raise ValueError('Un-recognized ISCE stack processor: {}'.format(processor)) + if len(bFiles) == 0: print('WARNING: no baseline text file found in dir {}'.format(os.path.abspath(baseline_dir))) return None - # ignore files with different date1 - # when re-run with different reference date - date1s = [os.path.basename(i).split('_')[0] for i in bFiles] - date1 = ut.most_common(date1s) - bFiles = [i for i in bFiles if os.path.basename(i).split('_')[0] == date1] + if processor in ['tops', 'stripmap']: + # ignore files with different date1 + # when re-run with different reference date + date1s = [os.path.basename(i).split('_')[0] for i in bFiles] + date1 = ut.most_common(date1s) + bFiles = [i for i in bFiles if os.path.basename(i).split('_')[0] == date1] + + # read files into dict + bDict = {} + for bFile in bFiles: + dates = os.path.basename(bFile).split('.txt')[0].split('_') + if processor == 'tops': + bDict[dates[1]] = read_tops_baseline(bFile) + else: + bDict[dates[1]] = read_stripmap_baseline(bFile) + bDict[dates[0]] = [0, 0] + ref_date0 = dates[0] + + elif processor == 'alosStack': + bDict, ref_date0 = read_alosStack_baseline(bFiles[0]) - # read files into dict - bDict = {} - for bFile in bFiles: - dates = os.path.basename(bFile).split('.txt')[0].split('_') - if processor == 'tops': - bDict[dates[1]] = read_tops_baseline(bFile) - else: - bDict[dates[1]] = read_stripmap_baseline(bFile) - bDict[dates[0]] = [0, 0] + else: + raise ValueError('Un-recognized ISCE stack processor: {}'.format(processor)) # change reference date - if ref_date is not None and ref_date != dates[0]: + if ref_date is not None and ref_date != ref_date0: + ref_date = ptime.yyyymmdd(ref_date) print('change reference date to {}'.format(ref_date)) ref_bperp = bDict[ref_date] @@ -524,4 +778,3 @@ def read_baseline_timeseries(baseline_dir, processor='tops', ref_date=None): bDict[key][1] -= ref_bperp[1] return bDict - diff --git a/mintpy/utils/network.py b/mintpy/utils/network.py index 4976d862e..2d2ba6c91 100644 --- a/mintpy/utils/network.py +++ b/mintpy/utils/network.py @@ -161,7 +161,7 @@ def get_date12_list(fname, dropIfgram=False): Example: date12List = get_date12_list('ifgramStack.h5') date12List = get_date12_list('ifgramStack.h5', dropIfgram=True) - date12List = get_date12_list('ifgramStack_coherence_spatialAvg.txt') + date12List = get_date12_list('coherenceSpatialAvg.txt') """ date12_list = [] ext = os.path.splitext(fname)[1].lower() @@ -555,7 +555,7 @@ def pair_merge(pairs1, pairs2): return pairs -def select_pairs_all(date_list, date12_format='YYMMDD-YYMMDD'): +def select_pairs_all(date_list, date_format='YYMMDD'): """Select All Possible Pairs/Interferograms Input : date_list - list of date in YYMMDD/YYYYMMDD format Output: date12_list - list date12 in YYMMDD-YYMMDD format @@ -567,7 +567,7 @@ def select_pairs_all(date_list, date12_format='YYMMDD-YYMMDD'): date6_list = ptime.yymmdd(date8_list) date12_list = list(itertools.combinations(date6_list, 2)) date12_list = [date12[0]+'-'+date12[1] for date12 in date12_list] - if date12_format == 'YYYYMMDD_YYYYMMDD': + if date_format == 'YYYYMMDD': date12_list = ptime.yyyymmdd_date12(date12_list) return date12_list @@ -576,9 +576,6 @@ def select_pairs_sequential(date_list, num_conn=2, date_format=None): """Select Pairs in a Sequential way: For each acquisition, find its num_connection nearest acquisitions in the past time. - Reference: - Fattahi, H., and F. Amelung (2013), DEM Error Correction in InSAR Time Series, IEEE TGRS, 51(7), 4249-4259. - Parameters: date_list - list of str for date num_conn - int, number of sequential connections date_format - str / None, output date format @@ -612,7 +609,7 @@ def select_pairs_sequential(date_list, num_conn=2, date_format=None): return date12_list -def select_pairs_hierarchical(date_list, pbase_list, temp_perp_list, date12_format='YYMMDD-YYMMDD'): +def select_pairs_hierarchical(date_list, pbase_list, temp_perp_list, date_format='YYMMDD'): """Select Pairs in a hierarchical way using list of temporal and perpendicular baseline thresholds For each temporal/perpendicular combination, select all possible pairs; and then merge all combination results together for the final output (Zhao, 2015). @@ -646,12 +643,12 @@ def select_pairs_hierarchical(date_list, pbase_list, temp_perp_list, date12_form pbase_max) date12_list += date12_list_tmp date12_list = sorted(list(set(date12_list))) - if date12_format == 'YYYYMMDD_YYYYMMDD': + if date_format == 'YYYYMMDD': date12_list = ptime.yyyymmdd_date12(date12_list) return date12_list -def select_pairs_delaunay(date_list, pbase_list, norm=True, date12_format='YYMMDD-YYMMDD'): +def select_pairs_delaunay(date_list, pbase_list, norm=True, date_format='YYMMDD'): """Select Pairs using Delaunay Triangulation based on temporal/perpendicular baselines Inputs: date_list : list of date in YYMMDD/YYYYMMDD format @@ -683,12 +680,12 @@ def select_pairs_delaunay(date_list, pbase_list, norm=True, date12_format='YYMMD # Convert index into date12 date12_list = [date6_list[idx[0]]+'-'+date6_list[idx[1]] for idx in date12_idx_list] - if date12_format == 'YYYYMMDD_YYYYMMDD': + if date_format == 'YYYYMMDD_YYYYMMDD': date12_list = ptime.yyyymmdd_date12(date12_list) return date12_list -def select_pairs_mst(date_list, pbase_list, date12_format='YYMMDD-YYMMDD'): +def select_pairs_mst(date_list, pbase_list, date_format='YYMMDD'): """Select Pairs using Minimum Spanning Tree technique Connection Cost is calculated using the baseline distance in perp and scaled temporal baseline (Pepe and Lanari, 2006, TGRS) plane. @@ -729,12 +726,12 @@ def select_pairs_mst(date_list, pbase_list, date12_format='YYMMDD-YYMMDD'): idx = sorted([m_idx_list[i], s_idx_list[i]]) date12 = date6_list[idx[0]]+'-'+date6_list[idx[1]] date12_list.append(date12) - if date12_format == 'YYYYMMDD_YYYYMMDD': + if date_format == 'YYYYMMDD': date12_list = ptime.yyyymmdd_date12(date12_list) return date12_list -def select_pairs_star(date_list, m_date=None, pbase_list=[], date12_format='YYMMDD-YYMMDD'): +def select_pairs_star(date_list, m_date=None, pbase_list=[], date_format='YYMMDD'): """Select Star-like network/interferograms/pairs, it's a single reference network, similar to PS approach. Usage: m_date : reference date, choose it based on the following cretiria: @@ -765,7 +762,7 @@ def select_pairs_star(date_list, m_date=None, pbase_list=[], date12_format='YYMM if s_idx is not m_idx] date12_list = [date6_list[idx[0]]+'-'+date6_list[idx[1]] for idx in date12_idx_list] - if date12_format == 'YYYYMMDD_YYYYMMDD': + if date_format == 'YYYYMMDD': date12_list = ptime.yyyymmdd_date12(date12_list) return date12_list diff --git a/mintpy/utils/plot.py b/mintpy/utils/plot.py index 84dce7d4c..d99be829b 100644 --- a/mintpy/utils/plot.py +++ b/mintpy/utils/plot.py @@ -165,7 +165,7 @@ def add_figure_argument(parser): # colormap fig.add_argument('-c', '--colormap', dest='colormap', help='colormap used for display, i.e. jet, cmy, RdBu, hsv, jet_r, temperature, viridis, etc.\n' - 'More at https://mintpy.readthedocs.io/en/latest/resources/colormaps/') + 'More at https://mintpy.readthedocs.io/en/latest/api/colormaps/') fig.add_argument('--cm-lut','--cmap-lut', dest='cmap_lut', type=int, default=256, metavar='NUM', help='number of increment of colormap lookup table (default: %(default)s).') fig.add_argument('--cm-vlist','--cmap-vlist', dest='cmap_vlist', type=float, nargs=3, default=[0.0, 0.7, 1.0], @@ -255,20 +255,23 @@ def add_mask_argument(parser): def add_map_argument(parser): # Map mapg = parser.add_argument_group('Map', 'for one subplot in geo-coordinates only') - mapg.add_argument('--coastline', dest='coastline', type=str, default='no', - choices={'10m', '50m', '110m', 'no'}, - help="Draw coastline with specified resolution (default: %(default)s).") + mapg.add_argument('--coastline', dest='coastline', type=str, choices={'10m', '50m', '110m'}, + help="Draw coastline with specified resolution (default: %(default)s).\n" + "This will enable --lalo-label option.\n" + "Link: https://scitools.org.uk/cartopy/docs/latest/matplotlib/geoaxes.html" + "#cartopy.mpl.geoaxes.GeoAxes.coastlines") + # lalo label - mapg.add_argument('--lalo-loc', dest='lalo_loc', type=int, nargs=4, default=[1, 0, 0, 1], - metavar=('left', 'right', 'top', 'bottom'), - help='Draw lalo label in [left, right, top, bottom] (default: %(default)s).') mapg.add_argument('--lalo-label', dest='lalo_label', action='store_true', help='Show N, S, E, W tick label for plot in geo-coordinate.\n' 'Useful for final figure output.') - mapg.add_argument('--lalo-max-num', dest='lalo_max_num', type=int, default=4, metavar='NUM', - help='Maximum number of lalo tick label (default: %(default)s).') mapg.add_argument('--lalo-step', dest='lalo_step', metavar='DEG', type=float, help='Lat/lon step for lalo-label option.') + mapg.add_argument('--lalo-max-num', dest='lalo_max_num', type=int, default=3, metavar='NUM', + help='Maximum number of lalo tick label (default: %(default)s).') + mapg.add_argument('--lalo-loc', dest='lalo_loc', type=int, nargs=4, default=[1, 0, 0, 1], + metavar=('left', 'right', 'top', 'bottom'), + help='Draw lalo label in [left, right, top, bottom] (default: %(default)s).') mapg.add_argument('--lat-label', dest='lat_label_direction', type=str, choices={'horizontal', 'vertical'}, default='horizontal', help='Rotate Lat label from default horizontal to vertical (to save space).') @@ -1712,7 +1715,7 @@ def read_mask(fname, mask_file=None, datasetName=None, box=None, print_msg=True) except: mask_file = None if print_msg: - print('Can not open mask file:', mask_file) + print('Can not open mask file:', mask_file) elif k in ['HDFEOS']: if datasetName.split('-')[0] in timeseriesDatasetNames: diff --git a/mintpy/utils/ptime.py b/mintpy/utils/ptime.py index 1c8fb58f4..3e61c2c74 100644 --- a/mintpy/utils/ptime.py +++ b/mintpy/utils/ptime.py @@ -50,7 +50,7 @@ def get_date_str_format(date_str): date_str_format = '%y%m%d' else: - raise ValueError('un-recognized date string format!') + raise ValueError('un-recognized date string format for "{}"!'.format(date_str)) return date_str_format diff --git a/mintpy/utils/readfile.py b/mintpy/utils/readfile.py index a75f755d3..8e4eacc1c 100644 --- a/mintpy/utils/readfile.py +++ b/mintpy/utils/readfile.py @@ -177,13 +177,14 @@ ######################################################################### -def read(fname, box=None, datasetName=None, print_msg=True): +def read(fname, box=None, datasetName=None, print_msg=True, xstep=1, ystep=1): """Read one dataset and its attributes from input file. - Parameters: fname : str, path of file to read + Parameters: fname : str, path of file to read datasetName : str or list of str, slice names - box : 4-tuple of int area to read, defined in (x0, y0, x1, y1) in pixel coordinate - Returns: data : 2/3-D matrix in numpy.array format, return None if failed - atr : dictionary, attributes of data, return None if failed + box : 4-tuple of int area to read, defined in (x0, y0, x1, y1) in pixel coordinate + x/ystep : int, number of pixels to pick/multilook for each output pixel + Returns: data : 2/3-D matrix in numpy.array format, return None if failed + atr : dictionary, attributes of data, return None if failed Examples: from mintpy.utils import readfile data, atr = readfile.read('velocity.h5') @@ -212,16 +213,25 @@ def read(fname, box=None, datasetName=None, print_msg=True): # Read Data fext = os.path.splitext(os.path.basename(fname))[1].lower() if fext in ['.h5', '.he5']: - data = read_hdf5_file(fname, datasetName=datasetName, box=box) + data = read_hdf5_file(fname, + datasetName=datasetName, + box=box, + xstep=xstep, + ystep=ystep) + else: - data, atr = read_binary_file(fname, datasetName=datasetName, box=box) + data, atr = read_binary_file(fname, + datasetName=datasetName, + box=box, + xstep=xstep, + ystep=ystep) return data, atr ######################################################################### -def read_hdf5_file(fname, datasetName=None, box=None): +def read_hdf5_file(fname, datasetName=None, box=None, xstep=1, ystep=1): """ - Parameters: fname : str, name of HDF5 file to read + Parameters: fname : str, name of HDF5 file to read datasetName : str or list of str, dataset name in root level with/without date info 'timeseries' 'timeseries-20150215' @@ -235,9 +245,10 @@ def read_hdf5_file(fname, datasetName=None, box=None): 'cmask' 'igram-20150215_20150227' ... - box : 4-tuple of int area to read, defined in (x0, y0, x1, y1) in pixel coordinate - Returns: data : 2D/3D array - atr : dict, metadata + box : 4-tuple of int area to read, defined in (x0, y0, x1, y1) in pixel coordinate + x/ystep : int, number of pixels to pick/multilook for each output pixel + Returns: data : 2D/3D array + atr : dict, metadata """ # File Info: list of slice / dataset / dataset2d / dataset3d slice_list = get_slice_list(fname) @@ -276,7 +287,11 @@ def read_hdf5_file(fname, datasetName=None, box=None): # 2D dataset if ds.ndim == 2: - data = ds[box[1]:box[3], box[0]:box[2]] + data = ds[box[1]:box[3], + box[0]:box[2]] + if xstep * ystep > 1: + data = data[int(ystep/2)::ystep, + int(xstep/2)::xstep] # 3D dataset elif ds.ndim == 3: @@ -291,19 +306,26 @@ def read_hdf5_file(fname, datasetName=None, box=None): slice_flag[date_list.index(d)] = True # read data - data = ds[slice_flag, box[1]:box[3], box[0]:box[2]] + data = ds[:, + box[1]:box[3], + box[0]:box[2]][slice_flag] + if xstep * ystep > 1: + data = data[:, + int(ystep/2)::ystep, + int(xstep/2)::xstep] data = np.squeeze(data) return data -def read_binary_file(fname, datasetName=None, box=None): +def read_binary_file(fname, datasetName=None, box=None, xstep=1, ystep=1): """Read data from binary file, such as .unw, .cor, etc. - Parameters: fname : str, path/name of binary file + Parameters: fname : str, path/name of binary file datasetName : str, dataset name for file with multiple bands of data e.g.: incidenceAngle, azimuthAngle, rangeCoord, azimuthCoord, ... - box : 4-tuple of int area to read, defined in (x0, y0, x1, y1) in pixel coordinate - Returns: data : 2D array in size of (length, width) in BYTE / int16 / float32 / complex64 / float64 etc. - atr : dict, metadata of binary file + box : 4-tuple of int area to read, defined in (x0, y0, x1, y1) in pixel coordinate + x/ystep : int, number of pixels to pick/multilook for each output pixel + Returns: data : 2D array in size of (length, width) in BYTE / int16 / float32 / complex64 / float64 etc. + atr : dict, metadata of binary file """ # Basic Info fbase, fext = os.path.splitext(os.path.basename(fname)) @@ -340,7 +362,7 @@ def read_binary_file(fname, datasetName=None, box=None): data_type = dataTypeDict[data_type] k = atr['FILE_TYPE'].lower().replace('.', '') - if k in ['unw']: + if k in ['unw', 'cor']: band = min(2, num_band) if datasetName and datasetName in ['band1','intensity','magnitude']: band = 1 @@ -382,7 +404,7 @@ def read_binary_file(fname, datasetName=None, box=None): # data structure - file specific based on file extension data_type = 'float32' - num_band = 1 + num_band = 1 if fext in ['.unw', '.cor', '.hgt', '.msk']: num_band = 2 @@ -456,7 +478,9 @@ def read_binary_file(fname, datasetName=None, box=None): num_band=num_band, band_interleave=band_interleave, band=band, - cpx_band=cpx_band) + cpx_band=cpx_band, + xstep=xstep, + ystep=ystep) if 'DATA_TYPE' not in atr: atr['DATA_TYPE'] = data_type @@ -620,6 +644,8 @@ def read_attribute(fname, datasetName=None, standardize=True, metafile_ext=None) k = 'geometry' elif any(i in g1_list+d1_list for i in ['timeseries', 'displacement']): k = 'timeseries' + elif any(i in d1_list for i in ['velocity']): + k = 'velocity' elif 'HDFEOS' in g1_list: k = 'HDFEOS' elif 'recons' in d1_list: @@ -777,7 +803,7 @@ def get_hdf5_dataset(name, obj): 'cfloat': 'complex64', } data_type = atr.get('DATA_TYPE', 'none').lower() - if data_type is not 'none' and data_type in dataTypeDict.keys(): + if data_type != 'none' and data_type in dataTypeDict.keys(): atr['DATA_TYPE'] = dataTypeDict[data_type] # UNIT @@ -1134,7 +1160,8 @@ def attribute_gamma2roipac(par_dict_in): ######################################################################### def read_binary(fname, shape, box=None, data_type='float32', byte_order='l', - num_band=1, band_interleave='BIL', band=1, cpx_band='phase'): + num_band=1, band_interleave='BIL', band=1, cpx_band='phase', + xstep=1, ystep=1): """Read binary file using np.fromfile Parameters: fname : str, path/name of data file to read shape : tuple of 2 int in (length, width) @@ -1144,20 +1171,21 @@ def read_binary(fname, shape, box=None, data_type='float32', byte_order='l', int8, int16, int32 float16, float32, float64 complex64, complex128 - byte_order : str, little/big-endian - num_band : int, number of bands + byte_order : str, little/big-endian + num_band : int, number of bands band_interleave : str, band interleaving scheme, e.g.: BIP BIL BSQ - band : int, band of interest, between 1 and num_band. + band : int, band of interest, between 1 and num_band. cpx_band : str, e.g.: real, imag, imaginary phase, mag, magnitude cpx - Returns: data : 2D np.array + x/ystep : int, number of pixels to pick/multilook for each output pixel + Returns: data : 2D np.array Examples: # ISCE files atr = read_attribute(fname) shape = (int(atr['LENGTH']), int(atr['WIDTH'])) @@ -1224,6 +1252,11 @@ def read_binary(fname, shape, box=None, data_type='float32', byte_order='l', else: raise ValueError('unrecognized complex band:', cpx_band) + # skipping/multilooking + if xstep * ystep > 1: + data = data[int(ystep/2)::ystep, + int(xstep/2)::xstep] + return data diff --git a/mintpy/utils/utils0.py b/mintpy/utils/utils0.py index 3cff6786d..7c57ff354 100644 --- a/mintpy/utils/utils0.py +++ b/mintpy/utils/utils0.py @@ -197,8 +197,8 @@ def azimuth_ground_resolution(atr): proc = 'isce' if proc in ['roipac', 'isce']: Re = float(atr['EARTH_RADIUS']) - Height = float(atr['HEIGHT']) - az_step = float(atr['AZIMUTH_PIXEL_SIZE']) * Re/(Re+Height) + height = float(atr['HEIGHT']) + az_step = float(atr['AZIMUTH_PIXEL_SIZE']) * Re / (Re + height) elif proc == 'gamma': az_step = float(atr['AZIMUTH_PIXEL_SIZE']) return az_step @@ -234,6 +234,31 @@ def touch(fname_list, times=None): #################################### Geometry ########################################## +def vtec2range_delay(vtec, inc_angle_iono, freq): + """Calculate/predict the range delay in SAR from TEC in zenith direction + + L-band: 1.2575 GHz (ALOS2, NISAR-L) + S-band: 3.2 GHz (NISAR-S) + C-band: 5.405 GHz (Sentinel-1) + + Parameters: vtec - float, zenith TEC in TECU + inc_angle_iono - float/np.ndarray, incidence angle at the ionospheric shell in deg + freq - float, radar carrier frequency in Hz. + Returns: rg_delay - float/np.ndarray, predicted range delay in meters + """ + # ignore no-data value in inc_angle + if type(inc_angle_iono) is np.ndarray: + inc_angle_iono[inc_angle_iono == 0] = np.nan + + # convert to TEC in LOS based on equation (3) in Chen and Zebker (2012) + tec = vtec / np.cos(inc_angle_iono * np.pi / 180.0) + + # calculate range delay based on equation (1) in Chen and Zebker (2012) + range_delay = (tec * 1e16 * K / (freq**2)).astype(np.float32) + + return range_delay + + def lalo_ground2iono_shell_along_los(lat, lon, inc_angle=30, head_angle=-168, iono_height=450e3): """Convert the lat/lon of a point on the ground to the ionosphere thin-shell along the line-of-sight (LOS) direction. @@ -727,18 +752,46 @@ def check_parallel(file_num=1, print_msg=True, maxParallelNum=8): #################################### Math / Statistics ################################### +def median_abs_deviation(data, center=None, scale=0.67449): + """Compute the median absolute deviation of the data along the LAST axis. + + This function is also included as: + scipy.stats.median_abs_deviation() in scipy v1.5.0 + https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.median_abs_deviation.html + statsmodels.robust.mad() in statsmodels + https://www.statsmodels.org/dev/generated/statsmodels.robust.scale.mad.html + + Parameters: data - 1/2D np.ndarray, input array + center - 0/1D np.ndarray or None + scale - float, the normalization constant + Returns: mad - 0/1D np.ndarray + """ + # compute MAD over 1/2D matrix only + data = np.array(data) + if data.ndim > 2: + ntime = data.shape[0] + data = data.reshape(ntime, -1) + + # default center value: median + if center is None: + center = np.nanmedian(data, axis=-1) + + # calculation + if data.ndim == 2: + center = np.tile(center.reshape(-1,1), (1, data.shape[1])) + mad = np.nanmedian(np.abs(data - center), axis=-1) * scale + return mad + + def median_abs_deviation_threshold(data, center=None, cutoff=3.): """calculate rms_threshold based on the standardised residual - outlier detection with median absolute deviation. - https://www.statsmodels.org/dev/generated/statsmodels.robust.scale.mad.html + + Outlier detection with median absolute deviation. """ - data = np.array(data) if center is None: - center = np.median(data) - #from statsmodels.robust import mad - #data_mad = mad(data, center=center) - data_mad = np.median(np.abs(data - center)) / 0.67448975019608171 - threshold = center + cutoff * data_mad + center = np.nanmedian(data) + mad = median_abs_deviation(data, center=center) + threshold = center + cutoff * mad return threshold diff --git a/mintpy/utils/utils1.py b/mintpy/utils/utils1.py index 477a81b75..87bdeed2b 100644 --- a/mintpy/utils/utils1.py +++ b/mintpy/utils/utils1.py @@ -9,6 +9,7 @@ import os +import re import time import glob import shutil @@ -180,21 +181,24 @@ def read_text_file(fname): if not box: box = (0, 0, int(atr['WIDTH']), int(atr['LENGTH'])) - # If input is text file - suffix = '' + # default output filename if k == 'ifgramStack': - suffix += '_'+datasetName - suffix += '_spatialAvg.txt' + prefix = datasetName + else: + prefix = os.path.splitext(os.path.basename(File))[0] + suffix = 'SpatialAvg.txt' + txtFile = prefix + suffix + + # If input is text file if File.endswith(suffix): print('Input file is spatial average txt already, read it directly') meanList, dateList = read_text_file(File) return meanList, dateList # Read existing txt file only if 1) data file is older AND 2) same AOI - txtFile = os.path.splitext(os.path.basename(File))[0]+suffix file_line = '# Data file: {}\n'.format(os.path.basename(File)) mask_line = '# Mask file: {}\n'.format(maskFile) - aoi_line = '# AOI box: {}\n'.format(box) + aoi_line = '# AOI box: {}\n'.format(box) try: # Read AOI line from existing txt file fl = open(txtFile, 'r') @@ -471,7 +475,7 @@ def get_geometry_file(dset_list, work_dir=None, coord='geo', abspath=True, print return geom_file -def update_template_file(template_file, extra_dict): +def update_template_file(template_file, extra_dict, delimiter='='): """Update option value in template_file with value from input extra_dict""" # Compare and skip updating template_file if no new option value found. update = False @@ -487,12 +491,17 @@ def update_template_file(template_file, extra_dict): tmp_file = template_file+'.tmp' f_tmp = open(tmp_file, 'w') for line in open(template_file, 'r'): - c = [i.strip() for i in line.strip().split('=', 1)] + c = [i.strip() for i in line.strip().split(delimiter, 1)] if not line.startswith(('%', '#')) and len(c) > 1: key = c[0] value = str.replace(c[1], '\n', '').split("#")[0].strip() if key in extra_dict.keys() and extra_dict[key] != value: - line = line.replace(value, extra_dict[key], 1) + # use "= {OLD_VALUE}" for search/replace to be more robust + # against the scenario when key name contains {OLD_VALUE} + # i.e. mintpy.load.autoPath + old_value_str = re.findall(delimiter+'[\s]*'+value, line)[0] + new_value_str = old_value_str.replace(value, extra_dict[key]) + line = line.replace(old_value_str, new_value_str, 1) print(' {}: {} --> {}'.format(key, value, extra_dict[key])) f_tmp.write(line) f_tmp.close() @@ -698,6 +707,8 @@ def run_deramp(fname, ramp_type, mask_file=None, out_file=None, datasetName=None start_time = time.time() atr = readfile.read_attribute(fname) k = atr['FILE_TYPE'] + length = int(atr['LENGTH']) + width = int(atr['WIDTH']) print('remove {} ramp from file: {}'.format(ramp_type, fname)) if not out_file: @@ -711,16 +722,31 @@ def run_deramp(fname, ramp_type, mask_file=None, out_file=None, datasetName=None mask = readfile.read(mask_file)[0] print('read mask file: '+mask_file) else: - mask = np.ones((int(atr['LENGTH']), int(atr['WIDTH']))) + mask = np.ones((length, width), dtype=np.bool_) print('use mask of the whole area') # deramping if k == 'timeseries': - print('reading data ...') - data = readfile.read(fname)[0] - print('estimating phase ramp ...') - data = deramp(data, mask, ramp_type=ramp_type, metadata=atr)[0] - writefile.write(data, out_file, ref_file=fname) + # write HDF5 file with defined metadata and (empty) dataset structure + writefile.layout_hdf5(out_file, ref_file=fname, print_msg=True) + + print('estimating phase ramp one date at a time ...') + date_list = timeseries(fname).get_date_list() + num_date = len(date_list) + prog_bar = ptime.progressBar(maxValue=num_date) + for i in range(num_date): + # read + data = readfile.read(fname, datasetName=date_list[i])[0] + # deramp + data = deramp(data, mask, ramp_type=ramp_type, metadata=atr)[0] + # write + writefile.write_hdf5_block(out_file, data, + datasetName='timeseries', + block=[i, i+1, 0, length, 0, width], + print_msg=False) + prog_bar.update(i+1, suffix='{}/{}'.format(i+1, num_date)) + prog_bar.close() + print('finished writing to file: {}'.format(out_file)) elif k == 'ifgramStack': obj = ifgramStack(fname) @@ -734,8 +760,11 @@ def run_deramp(fname, ramp_type, mask_file=None, out_file=None, datasetName=None dsOut = f[dsNameOut] print('access HDF5 dataset /{}'.format(dsNameOut)) else: - dsOut = f.create_dataset(dsNameOut, shape=(obj.numIfgram, obj.length, obj.width), - dtype=np.float32, chunks=True, compression=None) + dsOut = f.create_dataset(dsNameOut, + shape=(obj.numIfgram, length, width), + dtype=np.float32, + chunks=True, + compression=None) print('create HDF5 dataset /{}'.format(dsNameOut)) prog_bar = ptime.progressBar(maxValue=obj.numIfgram) diff --git a/mintpy/utils/writefile.py b/mintpy/utils/writefile.py index 5ec8cc1f7..67aa3aece 100644 --- a/mintpy/utils/writefile.py +++ b/mintpy/utils/writefile.py @@ -55,13 +55,12 @@ def write(datasetDict, out_file, metadata=None, ref_file=None, compression=None) compression = readfile.get_hdf5_compression(ref_file) # list of auxiliary datasets - atr_ref = readfile.read_attribute(ref_file) - shape_ref = (int(atr_ref['LENGTH']), int(atr_ref['WIDTH'])) + shape2d = (int(meta['LENGTH']), int(meta['WIDTH'])) with h5py.File(ref_file, 'r') as fr: auxDsNames = [i for i in fr.keys() if (i not in list(datasetDict.keys()) and isinstance(fr[i], h5py.Dataset) - and fr[i].shape[-2:] != shape_ref)] + and fr[i].shape[-2:] != shape2d)] else: auxDsNames = [] @@ -189,82 +188,137 @@ def write(datasetDict, out_file, metadata=None, ref_file=None, compression=None) return out_file -def layout_hdf5(fname, dsNameDict, metadata): - """Create HDF5 file with defined metadata and (empty) dataset structure +######################################################################### - Parameters: fname - str, HDF5 file path - dsNameDict - dict, dataset structure definition, as below: - metadata - dict, metadata - Returns: fname - str, HDF5 file path +def layout_hdf5(fname, ds_name_dict=None, metadata=None, ref_file=None, compression=None, print_msg=True): + """Create HDF5 file with defined metadata and (empty) dataset structure - Example: + Parameters: fname - str, HDF5 file path + ds_name_dict - dict, dataset structure definition + {dname : [dtype, dshape], + dname : [dtype, dshape, None], + dname : [dtype, dshape, 1/2/3D np.ndarray], #for aux data + ... + } + metadata - dict, metadata + ref_file - str, reference file for the data structure + compression - str, HDF5 compression type + Returns: fname - str, HDF5 file path + + Example: layout_hdf5('timeseries_ERA5.h5', ref_file='timeseries.h5') + layout_hdf5('timeseries_ERA5.5h', ds_name_dict, metadata) # structure for ifgramStack - dsNameDict = { - "date" : (np.dtype('S8'), (inps.num_pair, 2)), - "dropIfgram" : (np.bool_, (inps.num_pair,)), - "bperp" : (np.float32, (inps.num_pair,)), - "unwrapPhase" : (np.float32, (inps.num_pair, inps.length, inps.width)), - "coherence" : (np.float32, (inps.num_pair, inps.length, inps.width)), - "connectComponent" : (np.int16, (inps.num_pair, inps.length, inps.width)), + ds_name_dict = { + "date" : [np.dtype('S8'), (num_ifgram, 2)], + "dropIfgram" : [np.bool_, (num_ifgram,)], + "bperp" : [np.float32, (num_ifgram,)], + "unwrapPhase" : [np.float32, (num_ifgram, length, width)], + "coherence" : [np.float32, (num_ifgram, length, width)], + "connectComponent" : [np.int16, (num_ifgram, length, width)], } # structure for geometry - dsNameDict = { - "height" : (np.float32, (inps.length, inps.width)), - "incidenceAngle" : (np.float32, (inps.length, inps.width)), - "slantRangeDistance" : (np.float32, (inps.length, inps.width)), + ds_name_dict = { + "height" : [np.float32, (length, width), None], + "incidenceAngle" : [np.float32, (length, width), None], + "slantRangeDistance" : [np.float32, (length, width), None], } # structure for timeseries - dsNameDict = { - "date" : (np.dtype("S8"), (numDates,)), - "bperp" : (np.float32, (numDates,)), - "timeseries" : (np.float32, (numDates, length, width)) + dates = np.array(date_list, np.string_) + ds_name_dict = { + "date" : [np.dtype("S8"), (num_date,), dates], + "bperp" : [np.float32, (num_date,), pbase], + "timeseries" : [np.float32, (num_date, length, width)], } """ - print('-'*50) - print('create HDF5 file {} with w mode'.format(fname)) - h5 = h5py.File(fname, "w") + # get meta from metadata and ref_file + if metadata: + meta = {key: value for key, value in metadata.items()} + elif ref_file: + with h5py.File(ref_file, 'r') as fr: + meta = {key: value for key, value in fr.attrs.items()} + if print_msg: + print('grab metadata from ref_file: {}'.format(ref_file)) + else: + raise ValueError('No metadata or ref_file found.') + + # check ds_name_dict + if ds_name_dict is None: + ds_name_dict = {} + + if ref_file and os.path.splitext(ref_file)[1] in ['.h5', '.he5']: + shape2d = (int(meta['LENGTH']), int(meta['WIDTH'])) + with h5py.File(ref_file, 'r') as fr: + for key in fr.keys(): + ds = fr[key] + if isinstance(ds, h5py.Dataset): + # save all dataset info + ds_name_dict[key] = [ds.dtype, ds.shape, None] + + # save auxliary dataset value + if ds.shape[-2:] != shape2d: + ds_name_dict[key][2] = ds[:] + + if print_msg: + print('grab dataset structure from ref_file: {}'.format(ref_file)) + else: + raise ValueError('No ds_name_dict or ref_file found.') + + # create file + f = h5py.File(fname, "w") + if print_msg: + print('-'*50) + print('create HDF5 file: {} with w mode'.format(fname)) # initiate dataset - for key in dsNameDict.keys(): - data_type = dsNameDict[key][0] - data_shape = dsNameDict[key][1] + max_digit = max([len(i) for i in ds_name_dict.keys()]) + for key in ds_name_dict.keys(): + data_type = ds_name_dict[key][0] + data_shape = ds_name_dict[key][1] - # turn ON compression + # turn ON compression for conn comp if key in ['connectComponent']: compression = 'lzf' - else: - compression = None # changable dataset shape if len(data_shape) == 3: - maxShape = (None, data_shape[1], data_shape[2]) + max_shape = (None, data_shape[1], data_shape[2]) else: - maxShape = data_shape - - print("create dataset: {d:<25} of {t:<25} in size of {s}".format(d=key, - t=str(data_type), - s=data_shape)) - h5.create_dataset(key, - shape=data_shape, - maxshape=maxShape, - dtype=data_type, - chunks=True, - compression=compression) + max_shape = data_shape + + # create empty dataset + if print_msg: + print(("create dataset : {d:<{w}} of {t:<25} in size of {s} with " + "compression = {c}").format(d=key, + w=max_digit, + t=str(data_type), + s=data_shape, + c=compression)) + ds = f.create_dataset(key, + shape=data_shape, + maxshape=max_shape, + dtype=data_type, + chunks=True, + compression=compression) + + # write auxliary data + if len(ds_name_dict[key]) > 2 and ds_name_dict[key][2] is not None: + ds[:] = np.array(ds_name_dict[key][2]) # write attributes - for key in metadata.keys(): - h5.attrs[key] = metadata[key] + for key in meta.keys(): + f.attrs[key] = meta[key] - h5.close() - print('close HDF5 file {}'.format(fname)) + f.close() + if print_msg: + print('close HDF5 file: {}'.format(fname)) return fname -def write_hdf5_block(fname, data, datasetName, block=None, mode='a'): +def write_hdf5_block(fname, data, datasetName, block=None, mode='a', print_msg=True): """Write data to existing HDF5 dataset in disk block by block. Parameters: data - np.ndarray 1/2/3D matrix datasetName - str, dataset name @@ -297,11 +351,11 @@ def write_hdf5_block(fname, data, datasetName, block=None, mode='a'): 0, shape[2]] # write - print('-'*50) - print('open HDF5 file {} in {} mode'.format(fname, mode)) - with h5py.File(fname, mode) as f: + if print_msg: + print('-'*50) + print('open HDF5 file {} in {} mode'.format(fname, mode)) print("writing dataset /{:<25} block: {}".format(datasetName, block)) - + with h5py.File(fname, mode) as f: if len(block) == 6: f[datasetName][block[0]:block[1], block[2]:block[3], @@ -314,7 +368,8 @@ def write_hdf5_block(fname, data, datasetName, block=None, mode='a'): elif len(block) == 2: f[datasetName][block[0]:block[1]] = data - print('close HDF5 file {}.'.format(fname)) + if print_msg: + print('close HDF5 file {}.'.format(fname)) return fname @@ -364,6 +419,9 @@ def remove_hdf5_dataset(fname, datasetNames, print_msg=True): return fname + +######################################################################### + def write_roipac_rsc(metadata, out_file, update_mode=False, print_msg=False): """Write attribute dict into ROI_PAC .rsc file Inputs: @@ -409,6 +467,73 @@ def write_roipac_rsc(metadata, out_file, update_mode=False, print_msg=False): return out_file +def write_isce_xml(fname, width, length, bands=1, data_type='FLOAT', scheme='BIP'): + """Write XML metadata file in ISCE-2 format + + Parameters: fname - str, path of data file + width - int, number of columns + length - int, number of rows + bands - int, number of band + data_type - str, data type name in ISCE convention + readfile.GDAL2ISCE_DATATYPE + scheme - str, band interleave, BIP, BIL, BSQ + """ + import isce + import isceobj + + img = isceobj.Image.createImage() + img.setFilename(fname) + img.setWidth(width) + img.setLength(length) + img.setAccessMode('READ') + img.bands = bands + img.dataType = data_type + img.scheme = scheme + img.renderHdr() + img.renderVRT() + + return + + +def write_isce_file(data, out_file, file_type='isce_unw'): + """write data to file in ISCE format + + Parameters: data - 2D np.ndarray, binary data matrix + out_file - str, path of output binary data file + file_type - str, file type + Returns: out_file - str, path of output binary data file + """ + import isce + import isceobj + + # fix potential typo + file_type = file_type.replace('-', '_') + + # write data to binary file + data.tofile(out_file) + + # write isce xml metadata file + length, width = data.shape + + if file_type == 'isce_unw': + width = int(width / 2) + write_isce_xml(out_file, width, length, bands=2, data_type='FLOAT', scheme='BIL') + + elif file_type == 'isce_int': + write_isce_xml(out_file, width, length, bands=1, data_type='CFLOAT', scheme='BIL') + + elif file_type == 'isce_cor': + write_isce_xml(out_file, width, length, bands=1, data_type='FLOAT', scheme='BIL') + + else: + raise ValueError('un-recognized ISCE file type: {}'.format(file_type)) + + return out_file + + + +######################################################################### + def write_float32(*args): """Write ROI_PAC rmg format with float32 precision (BIL) Format of the binary file is same as roi_pac unw, cor, or hgt data. diff --git a/mintpy/version.py b/mintpy/version.py index 2071a1ee1..345ebc4c9 100644 --- a/mintpy/version.py +++ b/mintpy/version.py @@ -1,10 +1,13 @@ #!/usr/bin/env python3 # grab version / date of the latest commit + + import os import subprocess ########################################################################### + def get_release_info(version='v1.2.3', date='2020-07-14'): """Grab version and date of the latest commit from a git repository""" # go to the repository directory @@ -31,27 +34,36 @@ def get_release_info(version='v1.2.3', date='2020-07-14'): # go back to the original directory os.chdir(dir_orig) return version, date + + ########################################################################### release_version, release_date = get_release_info() +release_description = """MintPy release version {v}, release date {d}""".format( + v=release_version, + d=release_date, +) # generate_from: http://patorjk.com/software/taag/ logo = """ -_________________________________________________ - ____ ____ _ _ _______ -|_ \ / _| (_) / |_|_ __ \ - | \/ | __ _ .--. `| |-' | |__) |_ __ - | |\ /| | [ | [ `.-. | | | | ___/[ \ [ ] - _| |_\/_| |_ | | | | | | | |, _| |_ \ '/ / -|_____||_____|[___][___||__]\__/|_____| [\_: / - \__.' - - Miami InSAR Time-series software in Python +___________________________________________________________ + + /## /## /## /## /####### + | ### /###|__/ | ## | ##__ ## + | #### /#### /## /####### /###### | ## \ ## /## /## + | ## ##/## ##| ##| ##__ ##|_ ##_/ | #######/| ## | ## + | ## ###| ##| ##| ## \ ## | ## | ##____/ | ## | ## + | ##\ # | ##| ##| ## | ## | ## /##| ## | ## | ## + | ## \/ | ##| ##| ## | ## | ####/| ## | ####### + |__/ |__/|__/|__/ |__/ \___/ |__/ \____ ## + /## | ## + | ######/ + Miami InSAR Time-series software in Python \______/ MintPy {v}, {d} -_________________________________________________ +___________________________________________________________ """.format(v=release_version, d=release_date) website = 'https://github.com/insarlab/MintPy' -description = """MintPy release version {v}, release date {d}""".format(v=release_version, - d=release_date) +description = 'Miami INsar Time-series software in PYthon' + diff --git a/mintpy/view.py b/mintpy/view.py index 94cf092b0..f5c389937 100755 --- a/mintpy/view.py +++ b/mintpy/view.py @@ -48,6 +48,7 @@ view.py timeseries.h5 --ex drop_date.txt #exclude dates to plot view.py timeseries.h5 '*2017*' #all acquisitions in 2017 view.py timeseries.h5 '*2017*' '*2018*' #all acquisitions in 2017 and 2018 + view.py timeseries.h5 20200616_20200908 #reconstruct interferogram on the fly view.py ifgramStack.h5 coherence view.py ifgramStack.h5 unwrapPhase- #unwrapPhase only in the presence of unwrapPhase_bridging @@ -300,11 +301,17 @@ def update_inps_with_file_metadata(inps, metadata): print_msg=inps.print_msg) # Map info - coordinate unit and map projection + # Use cartopy GeoAxes instance if input data is: + # 1. geocoded in the unit of degrees AND + # 2. displayed in geo-coordinates + # to support: + # 1. fancy lat/lon label + # 2. coastline inps.coord_unit = metadata.get('Y_UNIT', 'degrees').lower() if (inps.geo_box and inps.fig_coord == 'geo' and inps.coord_unit.startswith('deg') - and inps.lalo_label): + and (inps.lalo_label or inps.coastline)): inps.proj_obj = eval('ccrs.{}()'.format(inps.map_projection)) else: inps.proj_obj = None @@ -347,13 +354,21 @@ def update_inps_with_file_metadata(inps, metadata): ################################################################################################## def update_data_with_plot_inps(data, metadata, inps): - # Seed Point + # 1. spatial referencing with respect to the seed point if inps.ref_yx: # and inps.ref_yx != [int(metadata['REF_Y']), int(metadata['REF_X'])]: + # update ref_y/x to subset try: ref_y = inps.ref_yx[0] - inps.pix_box[1] ref_x = inps.ref_yx[1] - inps.pix_box[0] except: pass + + # update ref_y/x to multilooking + if inps.multilook_num > 1: + ref_y = int((ref_y - int(inps.multilook_num / 2)) / inps.multilook_num) + ref_x = int((ref_x - int(inps.multilook_num / 2)) / inps.multilook_num) + + # applying spatial referencing if len(data.shape) == 2: data -= data[ref_y, ref_x] elif len(data.shape) == 3: @@ -363,7 +378,7 @@ def update_data_with_plot_inps(data, metadata, inps): else: inps.ref_yx = None - # Convert data to display unit and wrap + # 2. scale data based on the display unit and re-wrap (data, inps.disp_unit, inps.disp_scale, @@ -376,7 +391,7 @@ def update_data_with_plot_inps(data, metadata, inps): if inps.wrap: inps.vlim = inps.wrap_range - # 1.6 Min / Max - Data/Display + # 3. update display min/max inps.dlim = [np.nanmin(data), np.nanmax(data)] if not inps.vlim: # and data.ndim < 3: inps.vlim = [np.nanmin(data), np.nanmax(data)] @@ -435,9 +450,10 @@ def plot_slice(ax, data, metadata, inps=None): vprint('map projection: {}'.format(inps.map_projection)) # Draw coastline using cartopy resolution parameters - if inps.coastline != "no": + if inps.coastline: vprint('draw coast line with resolution: {}'.format(inps.coastline)) ax.coastlines(resolution=inps.coastline) + inps.lalo_label = True # Plot DEM if inps.dem_file: @@ -621,6 +637,20 @@ def format_coord(x, y): # Status bar # extent is (-0.5, -0.5, width-0.5, length-0.5) + + # read lats/lons if exist + geom_file = os.path.join(os.path.dirname(metadata['FILE_PATH']), 'inputs/geometryRadar.h5') + if os.path.isfile(geom_file): + try: + lats = readfile.read(geom_file, datasetName='latitude', box=inps.pix_box)[0] + lons = readfile.read(geom_file, datasetName='longitude', box=inps.pix_box)[0] + except: + msg = 'WARNING: no latitude / longitude found in file: {}'.format(os.path.basename(geom_file)) + msg += ', skip showing lat/lon in the status bar.' + vprint(msg) + else: + geom_file = None + def format_coord(x, y): msg = 'x={:.1f}, y={:.1f}'.format(x, y) col = int(np.rint(x - inps.pix_box[0])) @@ -631,7 +661,9 @@ def format_coord(x, y): if inps.dem_file: h = dem[row, col] msg += ', h={:.0f} m'.format(h) - #msg += ', v =' + # lat/lon + if geom_file: + msg += ', lat={:.4f}, lon={:.4f}'.format(lats[row, col], lons[row, col]) return msg ax.format_coord = format_coord @@ -743,12 +775,19 @@ def read_dataset_input(inps): if len(inps.dset) > 0 or len(inps.dsetNumList) > 0: # message if len(inps.dset) > 0: - print('input dataset: "{}"'.format(inps.dset)) + vprint('input dataset: "{}"'.format(inps.dset)) - # search + # special rule for special file types if inps.key == 'velocity': inps.search_dset = False vprint('turning glob search OFF for {} file'.format(inps.key)) + + elif inps.key == 'timeseries' and len(inps.dset) == 1 and '_' in inps.dset[0]: + date1, date2 = inps.dset[0].split('_') + inps.dset = [date2] + inps.ref_date = date1 + + # search inps.dsetNumList = search_dataset_input(inps.sliceList, inps.dset, inps.dsetNumList, @@ -770,6 +809,7 @@ def read_dataset_input(inps): inps.dset = [obj.sliceList[0].split('-')[0]] else: inps.dset = inps.sliceList + inps.dsetNumList = search_dataset_input(inps.sliceList, inps.dset, inps.dsetNumList, @@ -789,10 +829,12 @@ def read_dataset_input(inps): if inps.ref_date: if inps.key not in timeseriesKeyNames: inps.ref_date = None + ref_date = search_dataset_input(inps.sliceList, [inps.ref_date], [], inps.search_dset)[0][0] + if not ref_date: vprint('WARNING: input reference date is not included in input file!') vprint('input reference date: '+inps.ref_date) @@ -915,22 +957,31 @@ def update_figure_setting(inps): def read_data4figure(i_start, i_end, inps, metadata): """Read multiple datasets for one figure into 3D matrix based on i_start/end""" data = np.zeros((i_end - i_start, - inps.pix_box[3] - inps.pix_box[1], - inps.pix_box[2] - inps.pix_box[0])) + np.rint((inps.pix_box[3] - inps.pix_box[1]) / inps.multilook_num - 1e-4).astype(int), + np.rint((inps.pix_box[2] - inps.pix_box[0]) / inps.multilook_num - 1e-4).astype(int), + ), dtype=np.float32) # fast reading for single dataset type if (len(inps.dsetFamilyList) == 1 and inps.key in ['timeseries', 'giantTimeseries', 'ifgramStack', 'HDFEOS', 'geometry']): + vprint('reading data as a 3D matrix ...') dset_list = [inps.dset[i] for i in range(i_start, i_end)] - data[:] = readfile.read(inps.file, datasetName=dset_list, box=inps.pix_box)[0] + data[:] = readfile.read(inps.file, + datasetName=dset_list, + box=inps.pix_box, + xstep=inps.multilook_num, + ystep=inps.multilook_num)[0] if inps.key == 'ifgramStack': # reference pixel info in unwrapPhase if inps.dsetFamilyList[0].startswith('unwrapPhase') and inps.file_ref_yx: ref_y, ref_x = inps.file_ref_yx ref_box = (ref_x, ref_y, ref_x+1, ref_y+1) - ref_data = readfile.read(inps.file, datasetName=dset_list, box=ref_box, print_msg=False)[0] + ref_data = readfile.read(inps.file, + datasetName=dset_list, + box=ref_box, + print_msg=False)[0] for i in range(data.shape[0]): mask = data[i, :, :] != 0. data[i, mask] -= ref_data[i] @@ -943,7 +994,9 @@ def read_data4figure(i_start, i_end, inps, metadata): d = readfile.read(inps.file, datasetName=inps.dset[i], box=inps.pix_box, - print_msg=False)[0] + print_msg=False, + xstep=inps.multilook_num, + ystep=inps.multilook_num)[0] data[i - i_start, :, :] = d prog_bar.update(i - i_start + 1, suffix=inps.dset[i].split('/')[-1]) prog_bar.close() @@ -954,33 +1007,29 @@ def read_data4figure(i_start, i_end, inps, metadata): ref_data = readfile.read(inps.file, datasetName=inps.ref_date, box=inps.pix_box, - print_msg=False)[0] + print_msg=False, + xstep=inps.multilook_num, + ystep=inps.multilook_num)[0] data -= ref_data - # v/dlim, adjust data if all subplots share the same unit - # This could be: + # check if all subplots share the same data unit, they could have/be: # 1) the same type OR # 2) velocity or timeseries OR # 3) horizontal/vertical output from asc_desc2horz_vert.py # 4) data/model output from load_gbis.py OR # 5) binary files with multiple undefined datasets, as band1, band2, etc. - if (len(inps.dsetFamilyList) == 1 + if (len(inps.dsetFamilyList) == 1 or inps.key in ['velocity', 'timeseries', 'inversion'] or all(d in inps.dsetFamilyList for d in ['horizontal', 'vertical']) or inps.dsetFamilyList == ['data','model','residual'] or inps.dsetFamilyList == ['band{}'.format(i+1) for i in range(len(inps.dsetFamilyList))]): - data, inps = update_data_with_plot_inps(data, metadata, inps) - if (not inps.vlim - and not (inps.dsetFamilyList[0].startswith('unwrap') and not inps.file_ref_yx) - and inps.dsetFamilyList[0] not in ['bperp']): - data_mli = multilook_data(data, 10, 10) - inps.vlim = [np.nanmin(data_mli), np.nanmax(data_mli)] - del data_mli - inps.dlim = [np.nanmin(data), np.nanmax(data)] + same_unit4all_subplots = True + else: + same_unit4all_subplots = False - # multilook - if inps.multilook: - data = multilook_data(data, inps.multilook_num, inps.multilook_num) + # adjust data due to spatial referencing and unit related scaling + if same_unit4all_subplots: + data, inps = update_data_with_plot_inps(data, metadata, inps) # mask if inps.msk is not None: @@ -991,6 +1040,17 @@ def read_data4figure(i_start, i_end, inps, metadata): if inps.zero_mask: vprint('masking pixels with zero value') data = np.ma.masked_where(data == 0., data) + + # update display min/max + if (same_unit4all_subplots + and all(arg not in sys.argv for arg in ['-v', '--vlim', '--wrap']) + and not (inps.dsetFamilyList[0].startswith('unwrap') and not inps.file_ref_yx) + and inps.dsetFamilyList[0] not in ['bperp']): + data_mli = multilook_data(data, 10, 10) + inps.vlim = [np.nanmin(data_mli), np.nanmax(data_mli)] + del data_mli + inps.dlim = [np.nanmin(data), np.nanmax(data)] + return data @@ -1067,7 +1127,7 @@ def format_coord(x, y): if num_subplot <= 20: subplot_title = '{}\n{}'.format(i, title_str) elif num_subplot <= 50: - subplot_title = title_str + subplot_title = title_str.replace('_','\n').replace('-','\n') else: subplot_title = '{}'.format(i) @@ -1196,12 +1256,14 @@ def prepare4multi_subplots(inps, metadata): for dsFamily in inps.dsetFamilyList: if any(i in dsFamily.lower() for i in ['mask', 'coord']): auto_multilook = False + if auto_multilook: inps.multilook, inps.multilook_num = check_multilook_input(inps.pix_box, inps.fig_row_num, inps.fig_col_num) if inps.msk is not None: - inps.msk = multilook_data(inps.msk, inps.multilook_num, inps.multilook_num) + inps.msk = inps.msk[int(inps.multilook_num/2)::inps.multilook_num, + int(inps.multilook_num/2)::inps.multilook_num] # Reference pixel for timeseries and ifgramStack #metadata = readfile.read_attribute(inps.file) @@ -1238,9 +1300,9 @@ def prepare4multi_subplots(inps, metadata): dem = readfile.read(inps.dem_file, datasetName='height', box=inps.pix_box, - print_msg=False)[0] - if inps.multilook: - dem = multilook_data(dem, inps.multilook_num, inps.multilook_num) + print_msg=False, + xstep=inps.multilook_num, + ystep=inps.multilook_num)[0] (inps.dem_shade, inps.dem_contour, inps.dem_contour_seq) = pp.prepare_dem_background(dem=dem, @@ -1261,7 +1323,8 @@ def prep_slice(cmd, auto_fig=False): atr : dict, metadata inps : namespace, input argument for plot setup Example: - fig, ax = plt.subplots(figsize=[4, 3], projection=ccrs.PlateCarree()) + subplot_kw = dict(projection=ccrs.PlateCarree()) + fig, ax = plt.subplots(figsize=[4, 3], subplot_kw=subplot_kw) geo_box = (-91.670, -0.255, -91.370, -0.515) # W, N, E, S cmd = 'view.py geo_velocity.h5 velocity --mask geo_maskTempCoh.h5 ' cmd += '--sub-lon {w} {e} --sub-lat {s} {n} '.format(w=geo_box[0], n=geo_box[1], e=geo_box[2], s=geo_box[3]) diff --git a/mkdocs.yml b/mkdocs.yml index 0f75397d2..c7426b8e7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,16 +25,17 @@ nav: - User Guide: - Example datasets: demo_dataset.md - Example directory structure: dir_structure.md - - Example template files: examples/input_files/README.md + - Example template files: ../mintpy/examples/input_files/README.md - Parallel processing with Dask: dask.md - Tutorials in Jupyter Notebook: https://github.com/insarlab/MintPy-tutorial + - Frequently Asked Questions: FAQs.md - Output: - Google Earth KMZ: google_earth.md - HDF-EOS5: hdfeos5.md - QGIS: QGIS.md - API Documentation: - Attributes: api/attributes.md - - Colormaps: resources/colormaps/README.md + - Colormaps: api/colormaps.md - Coordinates: api/coord.md - Data structure: api/data_structure.md - Docker: docker.md diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..1870a2edb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + diff --git a/setup.py b/setup.py index 6c2de6c13..522fc6d27 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,119 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="mintpy_beta-meissam", # Replace with your own username - version="0.0.1", - author="Meissam Mehdizadeh", - author_email="meissam.mehdizadeh@gmail.com", - description="MintPy Beta Version", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/mehdizadehm/MintPy", - packages=setuptools.find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - python_requires='>=3.6', -) +#!/usr/bin/env python +############################################################ +# Program is part of MintPy # +# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi # +# Author: Zhang Yunjun, Nov 2020 # +############################################################ + + +# Always prefer setuptools over distutils +import os +from setuptools import setup, find_packages + + +# Grab from README file: long_description +with open("docs/README.md", "r") as f: + long_description = f.read() + +# Grab from version.py file: version and description +with open("mintpy/version.py", "r") as f: + lines = f.readlines() + # version + line = [line for line in lines if line.startswith("def get_release_info")][0].strip() + version = line.replace("'",'"').split('"')[1].split('v')[1] + # description + line = [line for line in lines if line.startswith("description")][0].strip() + description = line.replace("'",'"').split('"')[1] + # website + line = [line for line in lines if line.startswith("website")][0].strip() + website = line.replace("'",'"').split('"')[1] + + +def do_setup(): + setup( + name="mintpy", + version=version, + description=description, + long_description=long_description, + long_description_content_type="text/markdown", + url=website, + author="Zhang Yunjun, Heresh Fattahi", + author_email="yunjunzgeo@gmail.com", + + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + ], + download_url=("https://github.com/insarlab/MintPy/archive/v{}.tar.gz".format(version)), + keywords="InSAR, deformation, time-series, volcano, earthquake, tectonics, geodesy, geophysics, remote-sensing", + + # package discovery + packages=find_packages(), + + # dependencies + python_requires=">=3.6", + install_requires=[ + "cartopy", + "cdsapi", + "cvxopt", + "dask>=1.0", + "dask-jobqueue>=0.3", + "defusedxml", + "ecCodes", + "gfortran_linux-64;platform_system=='Linux'", + "gfortran_osx-64;platform_system=='Darwin'", + "h5py", + "lxml", + "matplotlib", + "netcdf4", + "numpy", + "openmp", + "pygrib", + "pyhdf", + "pykdtree", + "pyproj", + "pyresample", + "setuptools", + "scikit-image", + "scikit-learn", + "scipy", + ], + dependency_links=[ + "git+https://github.com/tylere/pykml.git" + ], + + # data files + include_package_data=True, + package_data={ + "mintpy": [ + "data/*.js", + "data/*.png", + "data/colormaps/*.cpt", + "data/input_files/*.txt", + "data/input_files/*.template", + "data/input_files/*.md", + "defaults/*.cfg", + "defaults/*.yaml", + "sh/*.sh", + ], + }, + + project_urls={ + "Bug Reports": "https://github.com/insarlab/mintpy/issues", + "Documentation": "https://mintpy.readthedocs.io/", + "Source": "https://github.com/insarlab/mintpy", + }, + ) + + +if __name__ == "__main__": + do_setup() + diff --git a/test/configs/FernandinaSenDT128.txt b/test/configs/FernandinaSenDT128.txt index 1e010854d..1be80f496 100644 --- a/test/configs/FernandinaSenDT128.txt +++ b/test/configs/FernandinaSenDT128.txt @@ -3,27 +3,13 @@ mintpy.compute.cluster = local ########## 1. Load Data (--load to exit after this step) ## load_data.py -H to check more details and example inputs. mintpy.load.processor = isce -##---------for ISCE only: -mintpy.load.metaFile = ../reference/IW*.xml -mintpy.load.baselineDir = ../baselines -##---------interferogram datasets: -mintpy.load.unwFile = ../merged/interferograms/*/filt_*.unw -mintpy.load.corFile = ../merged/interferograms/*/filt_*.cor -mintpy.load.connCompFile = ../merged/interferograms/*/filt_*.unw.conncomp -##---------geometry datasets: -mintpy.load.demFile = ../merged/geom_reference/hgt.rdr -mintpy.load.lookupYFile = ../merged/geom_reference/lat.rdr -mintpy.load.lookupXFile = ../merged/geom_reference/lon.rdr -mintpy.load.incAngleFile = ../merged/geom_reference/los.rdr -mintpy.load.azAngleFile = ../merged/geom_reference/los.rdr -mintpy.load.shadowMaskFile = ../merged/geom_reference/shadowMask.rdr -mintpy.load.waterMaskFile = None +mintpy.load.autoPath = yes -mintpy.reference.lalo = -0.30,-91.43 +mintpy.reference.lalo = -0.30,-91.43 mintpy.networkInversion.weightFunc = no mintpy.troposphericDelay.weatherModel = ERA5 mintpy.topographicResidual.pixelwiseGeometry = no mintpy.topographicResidual.stepFuncDate = 20170910,20180613 #eruption dates -mintpy.deramp = linear -mintpy.save.hdfEos5 = yes +mintpy.deramp = linear +mintpy.save.hdfEos5 = yes diff --git a/test/configs/SanFranSenDT42.txt b/test/configs/SanFranSenDT42.txt new file mode 100644 index 000000000..79127bd5f --- /dev/null +++ b/test/configs/SanFranSenDT42.txt @@ -0,0 +1,14 @@ +# vim: set filetype=cfg: +mintpy.load.processor = aria #[isce, aria, snap, gamma, roipac], auto for isce +mintpy.load.autoPath = yes + +mintpy.subset.lalo = 37.35:38.00, -122.45:-121.80 +mintpy.network.endDate = 20170510 +mintpy.reference.lalo = 37.69, -122.07 +mintpy.unwrapError.method = bridging +mintpy.deramp = no + +mintpy.networkInversion.weightFunc = no +mintpy.troposphericDelay.method = no +mintpy.topographicResidual.pixelwiseGeometry = no + diff --git a/test/configs/WCapeSenAT29.txt b/test/configs/WCapeSenAT29.txt new file mode 100644 index 000000000..5a39b050b --- /dev/null +++ b/test/configs/WCapeSenAT29.txt @@ -0,0 +1,16 @@ +# vim: set filetype=cfg: +##-------------------------------- MintPy -----------------------------## +########## 1. Load Data (--load to exit after this step) +## load_data.py -H to check more details and example inputs. +mintpy.load.processor = snap +mintpy.load.unwFile = ../interferograms/*/*/Unw_*.img +mintpy.load.corFile = ../interferograms/*/*/coh_*.img +mintpy.load.demFile = ../dem*/dem*.img + +mintpy.subset.lalo = -33.10:-32.95, 17.98:18.13 +mintpy.reference.lalo = -33, 18.05 + +mintpy.networkInversion.weightFunc = no +mintpy.deramp = linear +mintpy.topographicResidual.pixelwiseGeometry = no + diff --git a/test/test_smallbaselineApp.py b/test/test_smallbaselineApp.py index 2314edbdd..455eac099 100755 --- a/test/test_smallbaselineApp.py +++ b/test/test_smallbaselineApp.py @@ -26,6 +26,8 @@ 'https://zenodo.org/record/3952953/files/FernandinaSenDT128.tar.xz', 'https://zenodo.org/record/3952950/files/WellsEnvD2T399.tar.xz', 'https://zenodo.org/record/3952917/files/KujuAlosAT422F650.tar.xz', + 'https://zenodo.org/record/4265413/files/SanFranSenDT42.tar.xz', + 'https://zenodo.org/record/4127335/files/WCapeSenAT29.tar.xz', ] PROJ_NAME_LIST = [os.path.basename(url).split('.tar.xz')[0] for url in URL_LIST] @@ -109,11 +111,11 @@ def test_dataset(dset_name, test_dir, fresh_start=True, test_pyaps=False): else: # remove existing directory if os.path.isdir(dset_name): - print('removing existing project directory: {}'.format(dset_name)) + print('remove existing project directory: {}'.format(dset_name)) shutil.rmtree(dset_name) # uncompress tar file - print('extract content from tar file: {}'.format(tar_file)) + print('extracting content from tar file: {}'.format(tar_file)) tar = tarfile.open(tar_file) tar.extractall() tar.close() @@ -132,7 +134,7 @@ def test_dataset(dset_name, test_dir, fresh_start=True, test_pyaps=False): cmd = 'smallbaselineApp.py {}'.format(template_file) print(cmd) status = subprocess.Popen(cmd, shell=True).wait() - if status is not 0: + if status != 0: raise RuntimeError('Test failed for example dataset {}'.format(dset_name)) # custom plot of velocity map