forked from insarlab/MintPy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
plot.py
1872 lines (1600 loc) · 80.8 KB
/
plot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
############################################################
# Program is part of MintPy #
# Copyright (c) 2013, Zhang Yunjun, Heresh Fattahi #
# Author: Zhang Yunjun, 2018 #
############################################################
# Recommend import:
# from mintpy.utils import plot as pp
import os
import csv
import argparse
import warnings
import datetime
import h5py
import numpy as np
import matplotlib as mpl
from matplotlib import (
dates as mdates,
lines as mlines,
pyplot as plt,
ticker,
transforms,
)
from matplotlib.colors import LinearSegmentedColormap
from mpl_toolkits.axes_grid1 import make_axes_locatable
import pyproj
from cartopy import crs as ccrs
from cartopy.mpl import geoaxes, ticker as cticker
from mintpy.objects import timeseriesKeyNames, timeseriesDatasetNames
from mintpy.objects.colors import ColormapExt
from mintpy.objects.coord import coordinate
from mintpy.utils import (
ptime,
readfile,
network as pnet,
utils0 as ut0,
utils1 as ut1,
)
min_figsize_single = 6.0 # default min size in inch, for single plot
max_figsize_single = 10.0 # default min size in inch, for single plot
# default size in inch, for multiple subplots
default_figsize_multi = [15.0, 8.0]
max_figsize_height = 8.0 # max figure size in vertical direction in inch
# default color names in matplotlib
# ref: https://matplotlib.org/users/dflt_style_changes.html
mplColors = ['#1f77b4',
'#ff7f0e',
'#2ca02c',
'#d62728',
'#9467bd',
'#8c564b',
'#e377c2',
'#7f7f7f',
'#bcbd22',
'#17becf']
########################################### Parser utilities ##############################################
def cmd_line_parse(iargs=''):
parser = argparse.ArgumentParser(description='Ploting Parser')
parser = add_data_disp_argument(parser)
parser = add_dem_argument(parser)
parser = add_figure_argument(parser)
parser = add_gps_argument(parser)
parser = add_mask_argument(parser)
parser = add_map_argument(parser)
parser = add_point_argument(parser)
parser = add_reference_argument(parser)
parser = add_save_argument(parser)
parser = add_subset_argument(parser)
inps = parser.parse_args(args=iargs)
return inps
def add_data_disp_argument(parser):
# Data Display Option
data = parser.add_argument_group('Data Display Options', 'Options to adjust the dataset display')
data.add_argument('-v','--vlim', dest='vlim', nargs=2, metavar=('VMIN', 'VMAX'), type=float,
help='Display limits for matrix plotting.')
data.add_argument('-u', '--unit', dest='disp_unit', metavar='UNIT',
help='unit for display. Its priority > wrap')
data.add_argument('--wrap', action='store_true',
help='re-wrap data to display data in fringes.')
data.add_argument('--wrap-range', dest='wrap_range', type=float, nargs=2,
default=[-1.*np.pi, np.pi], metavar=('MIN', 'MAX'),
help='range of one cycle after wrapping (default: %(default)s).')
data.add_argument('--flip-lr', dest='flip_lr',
action='store_true', help='flip left-right')
data.add_argument('--flip-ud', dest='flip_ud',
action='store_true', help='flip up-down')
data.add_argument('--noflip', dest='auto_flip', action='store_false',
help='turn off auto flip for radar coordinate file')
data.add_argument('--multilook-num', dest='multilook_num', type=int, default=1, metavar='NUM',
help='multilook data in X and Y direction with a factor for display (default: %(default)s).')
data.add_argument('--nomultilook', '--no-multilook', dest='multilook', action='store_false',
help='do not multilook, for high quality display. \n'
'If multilook and multilook_num=1, multilook_num will be estimated automatically.\n'
'Useful when displaying big datasets.')
data.add_argument('--alpha', dest='transparency', type=float,
help='Data transparency. \n'
'0.0 - fully transparent, 1.0 - no transparency.')
return parser
def add_dem_argument(parser):
# DEM
dem = parser.add_argument_group('DEM', 'display topography in the background')
dem.add_argument('-d', '--dem', dest='dem_file', metavar='DEM_FILE',
help='DEM file to show topography as background')
dem.add_argument('--dem-noshade', dest='disp_dem_shade', action='store_false',
help='do not show DEM shaded relief')
dem.add_argument('--dem-nocontour', dest='disp_dem_contour', action='store_false',
help='do not show DEM contour lines')
dem.add_argument('--contour-smooth', dest='dem_contour_smooth', type=float, default=3.0,
help='Background topography contour smooth factor - sigma of Gaussian filter. \n'
'Set to 0.0 for no smoothing; (default: %(default)s).')
dem.add_argument('--contour-step', dest='dem_contour_step', metavar='NUM', type=float, default=200.0,
help='Background topography contour step in meters (default: %(default)s).')
dem.add_argument('--contour-linewidth', dest='dem_contour_linewidth', metavar='NUM', type=float, default=0.5,
help='Background topography contour linewidth (default: %(default)s).')
dem.add_argument('--shade-az', dest='shade_azdeg', type=float, default=315., metavar='DEG',
help='The azimuth (0-360, degrees clockwise from North) of the light source (default: %(default)s).')
dem.add_argument('--shade-alt', dest='shade_altdeg', type=float, default=45., metavar='DEG',
help='The altitude (0-90, degrees up from horizontal) of the light source (default: %(default)s).')
dem.add_argument('--shade-min', dest='shade_min', type=float, default=-4000., metavar='MIN',
help='The min height in m of colormap of shaded relief topography (default: %(default)s).')
dem.add_argument('--shade-max', dest='shade_max', type=float, default=999., metavar='MAX',
help='The max height of colormap of shaded relief topography (default: max(DEM)+2000).')
dem.add_argument('--shade-exag', dest='shade_exag', type=float, default=0.5,
help='Vertical exaggeration ratio (default: %(default)s).')
return parser
def add_figure_argument(parser):
"""Arguments for figure setting"""
fig = parser.add_argument_group('Figure', 'Figure settings for display')
fig.add_argument('--fontsize', dest='font_size',
type=int, help='font size')
fig.add_argument('--fontcolor', dest='font_color',
default='k', help='font color (default: %(default)s).')
# axis format
fig.add_argument('--nowhitespace', dest='disp_whitespace',
action='store_false', help='do not display white space')
fig.add_argument('--noaxis', dest='disp_axis',
action='store_false', help='do not display axis')
fig.add_argument('--notick', dest='disp_tick',
action='store_false', help='do not display tick in x/y axis')
# 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/')
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],
help='list of 3 float numbers, for truncated colormap only (default: %(default)s).')
# colorbar
fig.add_argument('--nocbar', '--nocolorbar', dest='disp_cbar',
action='store_false', help='do not display colorbar')
fig.add_argument('--cbar-nbins', dest='cbar_nbins', metavar='NUM',
type=int, help='number of bins for colorbar.')
fig.add_argument('--cbar-ext', dest='cbar_ext', default=None,
choices={'neither', 'min', 'max', 'both', None},
help='Extend setting of colorbar; based on data stat by default.')
fig.add_argument('--cbar-label', dest='cbar_label', default=None, help='colorbar label')
fig.add_argument('--cbar-loc', dest='cbar_loc', type=str, default='right',
help='colorbar location for single plot (default: %(default)s).')
fig.add_argument('--cbar-size', dest='cbar_size', type=str, default="2%",
help='colorbar size and pad (default: %(default)s).')
# title
fig.add_argument('--notitle', dest='disp_title',
action='store_false', help='do not display title')
fig.add_argument('--title-in', dest='fig_title_in',
action='store_true', help='draw title in/out of axes')
fig.add_argument('--figtitle', dest='fig_title',
help='Title shown in the figure.')
fig.add_argument('--title4sen','--title4sentinel1', dest='disp_title4sentinel1', action='store_true',
help='display Sentinel-1 A/B and IPF info in title.')
# size, subplots number and space
fig.add_argument('--figsize', dest='fig_size', metavar=('WID', 'LEN'), type=float, nargs=2,
help='figure size in inches - width and length')
fig.add_argument('--dpi', dest='fig_dpi', metavar='DPI', type=int, default=300,
help='DPI - dot per inch - for display/write (default: %(default)s).')
fig.add_argument('--figext', dest='fig_ext', default='.png',
choices=['.emf', '.eps', '.pdf', '.png', '.ps', '.raw', '.rgba', '.svg', '.svgz'],
help='File extension for figure output file (default: %(default)s).')
fig.add_argument('--fignum', dest='fig_num', type=int, metavar='NUM',
help='number of figure windows')
fig.add_argument('--nrows', dest='fig_row_num', type=int, default=1, metavar='NUM',
help='subplot number in row')
fig.add_argument('--ncols', dest='fig_col_num', type=int, default=1, metavar='NUM',
help='subplot number in column')
fig.add_argument('--wspace', dest='fig_wid_space', type=float,
help='width space between subplots in inches')
fig.add_argument('--hspace', dest='fig_hei_space', type=float,
help='height space between subplots in inches')
fig.add_argument('--no-tight-layout', dest='fig_tight_layout', action='store_false',
help='disable automatic tight layout for multiple subplots')
fig.add_argument('--coord', dest='fig_coord', choices=['radar', 'geo'], default='geo',
help='Display in radar/geo coordination system, for geocoded file only (default: %(default)s).')
fig.add_argument('--animation', action='store_true',
help='enable animation mode')
return parser
def add_gps_argument(parser):
gps = parser.add_argument_group('GPS', 'GPS data to display')
gps.add_argument('--show-gps', dest='disp_gps', action='store_true',
help='Show UNR GPS location within the coverage.')
gps.add_argument('--gps-label', dest='disp_gps_label', action='store_true',
help='Show GPS site name')
gps.add_argument('--gps-comp', dest='gps_component', choices={'enu2los', 'hz2los', 'up2los'},
help='Plot GPS in color indicating deformation velocity direction')
gps.add_argument('--ref-gps', dest='ref_gps_site', type=str, help='Reference GPS site')
gps.add_argument('--gps-start-date', dest='gps_start_date', type=str, metavar='YYYYMMDD',
help='start date of GPS data, default is date of the 1st SAR acquisiton')
gps.add_argument('--gps-end-date', dest='gps_end_date', type=str, metavar='YYYYMMDD',
help='start date of GPS data, default is date of the last SAR acquisiton')
return parser
def add_mask_argument(parser):
mask = parser.add_argument_group('Mask', 'Mask file/options')
mask.add_argument('-m','--mask', dest='mask_file', metavar='FILE',
help='mask file for display. "no" to turn OFF masking.')
mask.add_argument('--zm','--zero-mask', dest='zero_mask', action='store_true',
help='mask pixels with zero value.')
return 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, 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-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-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).')
mapg.add_argument('--projection', dest='map_projection', metavar='NAME', default='PlateCarree',
choices={'PlateCarree', 'LambertConformal'},
help='map projection when plotting in geo-coordinate (default: %(default)s).\n'
'https://scitools.org.uk/cartopy/docs/latest/crs/projections.html\n\n')
# scale bar
mapg.add_argument('--scalebar', nargs=3, metavar=('LEN', 'X', 'Y'), type=float,
default=[0.2, 0.2, 0.1],
help='scale bar distance and location in ratio (default: %(default)s).\n' +
'\tdistance in ratio of total width\n' +
'\tlocation in X/Y in ratio with respect to the lower left corner\n' +
'--scalebar 0.2 0.2 0.1 #for lower left corner\n' +
'--scalebar 0.2 0.2 0.8 #for upper left corner\n' +
'--scalebar 0.2 0.8 0.1 #for lower right corner\n' +
'--scalebar 0.2 0.8 0.8 #for upper right corner\n')
mapg.add_argument('--noscalebar', '--nosbar', dest='disp_scalebar',
action='store_false', help='do not display scale bar.')
mapg.add_argument('--scalebar-pad','--sbar-pad', dest='scalebar_pad', type=float,
default=0.05, help='scale bar label pad in ratio of scalebar width (default: %(default)s).')
return parser
def add_point_argument(parser):
ppt = parser.add_argument_group('Point', 'Plot points defined by y/x or lat/lon')
ppt.add_argument('--pts-marker', dest='pts_marker', type=str, default='k^',
help='Marker of points of interest (default: %(default)s).')
ppt.add_argument('--pts-ms', dest='pts_marker_size', type=float, default=6.,
help='Marker size for points of interest (default: %(default)s).')
pts = ppt.add_mutually_exclusive_group(required=False)
pts.add_argument('--pts-yx', dest='pts_yx', type=int, nargs=2, metavar=('Y', 'X'),
help='Point in Y/X')
pts.add_argument('--pts-lalo', dest='pts_lalo', type=float, nargs=2, metavar=('LAT', 'LON'),
help='Point in Lat/Lon')
pts.add_argument('--pts-file', dest='pts_file', type=str,
help='Text file for point(s) in lat/lon column')
return parser
def add_reference_argument(parser):
ref = parser.add_argument_group('Reference', 'Show / Modify reference in time and space for display')
# reference date
ref.add_argument('--ref-date', dest='ref_date', metavar='DATE',
help='Change reference date for display')
# reference pixel
ref.add_argument('--ref-lalo', dest='ref_lalo', metavar=('LAT', 'LON'), type=float, nargs=2,
help='Change referene point LAT LON for display')
ref.add_argument('--ref-yx', dest='ref_yx', metavar=('Y', 'X'), type=int, nargs=2,
help='Change referene point Y X for display')
# reference pixel style
ref.add_argument('--noreference', dest='disp_ref_pixel',
action='store_false', help='do not show reference point')
ref.add_argument('--ref-marker', dest='ref_marker', default='ks',
help='marker of reference pixel (default: %(default)s).')
ref.add_argument('--ref-size', dest='ref_marker_size', metavar='NUM', type=int, default=6,
help='marker size of reference point (default: %(default)s).')
return parser
def add_save_argument(parser):
save = parser.add_argument_group('Save/Output', 'Save figure and write to file(s)')
save.add_argument('-o', '--outfile', type=str, nargs='*',
help="save the figure with assigned filename.\n"
"By default, it's calculated based on the input file name.")
save.add_argument('--save', dest='save_fig', action='store_true',
help='save the figure')
save.add_argument('--nodisplay', dest='disp_fig', action='store_false',
help='save and do not display the figure')
save.add_argument('--update', dest='update_mode', action='store_true',
help='enable update mode for save figure: skip running if\n'+
'\t1) output file already exists\n'+
'\t2) output file is newer than input file.')
return parser
def add_subset_argument(parser):
# Subset
sub = parser.add_argument_group('Subset', 'Display dataset in subset range')
sub.add_argument('--sub-x','--subx', dest='subset_x', type=int, nargs=2, metavar=('XMIN', 'XMAX'),
help='subset display in x/cross-track/range direction')
sub.add_argument('--sub-y','--suby', dest='subset_y', type=int, nargs=2, metavar=('YMIN', 'YMAX'),
help='subset display in y/along-track/azimuth direction')
sub.add_argument('--sub-lat','--sublat', dest='subset_lat', type=float, nargs=2, metavar=('LATMIN', 'LATMAX'),
help='subset display in latitude')
sub.add_argument('--sub-lon','--sublon', dest='subset_lon', type=float, nargs=2, metavar=('LONMIN', 'LONMAX'),
help='subset display in longitude')
return parser
def read_pts2inps(inps, coord_obj):
"""Read pts_* options"""
## 1. merge pts_file/lalo/yx into pts_yx
# pts_file --> pts_lalo
if inps.pts_file and os.path.isfile(inps.pts_file):
print('read points lat/lon from text file: {}'.format(inps.pts_file))
inps.pts_lalo = np.loadtxt(inps.pts_file, usecols=(0,1), dtype=bytes).astype(float)
# pts_lalo --> pts_yx
if inps.pts_lalo is not None:
# format pts_lalo to 2D array in size of (num_pts, 2)
inps.pts_lalo = np.array(inps.pts_lalo).reshape(-1, 2)
# pts_lalo --> pts_yx
inps.pts_yx = coord_obj.geo2radar(inps.pts_lalo[:, 0],
inps.pts_lalo[:, 1],
print_msg=False)[:2]
inps.pts_yx = np.array(inps.pts_yx).T.reshape(-1, 2)
## 2. pts_yx --> pts_yx/lalo
if inps.pts_yx is not None:
# format pts_yx to 2D array in size of (num_pts, 2)
inps.pts_yx = np.array(inps.pts_yx).reshape(-1, 2)
# pts_yx --> pts_lalo
inps.pts_lalo = coord_obj.radar2geo(inps.pts_yx[:, 0],
inps.pts_yx[:, 1],
print_msg=False)[:2]
inps.pts_lalo = np.array(inps.pts_lalo).T.reshape(-1, 2)
return inps
############################################ Plot Utilities #############################################
def add_inner_title(ax, title, loc, prop=None, **kwargs):
from matplotlib.offsetbox import AnchoredText
from matplotlib.patheffects import withStroke
if prop is None:
prop = dict(size=plt.rcParams['legend.fontsize'])
at = AnchoredText(title, loc=loc, prop=prop,
pad=0., borderpad=0.5,
frameon=False, **kwargs)
ax.add_artist(at)
at.txt._text.set_path_effects([withStroke(foreground="w", linewidth=3)])
return at
def auto_figure_size(shape, disp_cbar=False, ratio=1.0):
"""Get auto figure size based on input data shape"""
length, width = shape
plot_shape = [width*1.25, length]
if not disp_cbar:
plot_shape = [width, length]
fig_scale = min(min_figsize_single/min(plot_shape),
max_figsize_single/max(plot_shape),
max_figsize_height/plot_shape[1])
fig_size = [i*fig_scale*ratio for i in plot_shape]
return fig_size
def auto_figure_title(fname, datasetNames=[], inps_dict=None):
"""Get auto figure title from meta dict and input options
Parameters: fname : str, input file name
datasetNames : list of str, optional, dataset to read for multi dataset/group files
inps_dict : dict, optional, processing attributes, including:
ref_date
pix_box
wrap
Returns: fig_title : str, output figure title
Example: 'geo_velocity.h5' = auto_figure_title('geo_velocity.h5', None, vars(inps))
'101020-110220_ERA5_ramp_demErr' = auto_figure_title('timeseries_ERA5_ramp_demErr.h5', '110220')
"""
if not datasetNames:
datasetNames = []
if isinstance(datasetNames, str):
datasetNames = [datasetNames]
fbase, fext = os.path.splitext(os.path.basename(fname))
atr = readfile.read_attribute(fname)
k = atr['FILE_TYPE']
num_pixel = int(atr['WIDTH']) * int(atr['LENGTH'])
if k == 'ifgramStack':
if len(datasetNames) == 1:
fig_title = datasetNames[0]
if 'unwCor' in fname:
fig_title += '_unwCor'
else:
fig_title = datasetNames[0].split('-')[0]
elif len(datasetNames) == 1 and k in timeseriesKeyNames:
if 'ref_date' in inps_dict.keys():
ref_date = inps_dict['ref_date']
elif 'REF_DATE' in atr.keys():
ref_date = atr['REF_DATE']
else:
ref_date = None
if not ref_date:
fig_title = datasetNames[0]
else:
fig_title = '{}_{}'.format(ref_date, datasetNames[0])
try:
processMark = os.path.basename(fname).split('timeseries')[1].split(fext)[0]
fig_title += processMark
except:
pass
elif k == 'geometry':
if len(datasetNames) == 1:
fig_title = datasetNames[0]
elif datasetNames[0].startswith('bperp'):
fig_title = 'bperp'
else:
fig_title = fbase
elif fext in ['.h5','.he5']:
fig_title = fbase
else:
fig_title = os.path.basename(fname)
if inps_dict.get('pix_box', None):
box = inps_dict['pix_box']
if (box[2] - box[0]) * (box[3] - box[1]) < num_pixel:
fig_title += '_sub'
if inps_dict.get('wrap', False):
fig_title += '_wrap'
wrap_range = inps_dict.get('wrap_range', [-1.*np.pi, np.pi])
if (wrap_range[1] - wrap_range[0]) != 2*np.pi:
fig_title += str(wrap_range[1] - wrap_range[0])
return fig_title
def auto_flip_direction(metadata, ax=None, print_msg=True):
"""Check flip left-right and up-down based on attribute dict, for radar-coded file only"""
# default value
flip_lr = False
flip_ud = False
# auto flip for file in radar coordinates
if 'Y_FIRST' not in metadata.keys() and 'ORBIT_DIRECTION' in metadata.keys():
msg = '{} orbit'.format(metadata['ORBIT_DIRECTION'])
if metadata['ORBIT_DIRECTION'].lower().startswith('a'):
flip_ud = True
msg += ' -> flip up-down'
else:
flip_lr = True
msg += ' -> flip left-right'
if print_msg:
print(msg)
if ax is not None:
if flip_lr:
ax.invert_xaxis()
if flip_ud:
ax.invert_yaxis()
return ax
return flip_lr, flip_ud
def auto_row_col_num(subplot_num, data_shape, fig_size, fig_num=1):
"""Get optimal row and column number given figure size number of subplots
Parameters: subplot_num : int, total number of subplots
data_shape : list of 2 float, data size in pixel in row and column direction of each plot
fig_size : list of 2 float, figure window size in inches
fig_num : int, number of figure windows, optional, default = 1.
Returns: row_num : number of subplots in row direction per figure
col_num : number of subplots in column direction per figure
"""
subplot_num_per_fig = int(np.ceil(float(subplot_num) / float(fig_num)))
data_shape_ratio = float(data_shape[0]) / float(data_shape[1])
num_ratio = fig_size[1] / fig_size[0] / data_shape_ratio
row_num = max(np.sqrt(subplot_num_per_fig * num_ratio), 1.)
col_num = max(np.sqrt(subplot_num_per_fig / num_ratio), 1.)
if row_num == 1.:
col_num = subplot_num_per_fig
elif col_num == 1.:
row_num = subplot_num_per_fig
while np.rint(row_num) * np.rint(col_num) < subplot_num_per_fig:
if row_num % 1 > col_num % 1:
row_num += 0.5
else:
col_num += 0.5
row_num = int(np.rint(row_num))
col_num = int(np.rint(col_num))
return row_num, col_num
def auto_shared_lalo_location(axs, loc=(1,0,0,1), flatten=False):
"""Return the auto lat/lon label location of subplots
Parameters: axs : 2D np.ndarray of matplotlib.axes._subplots.AxesSubplot object
loc : tuple of 4 bool, for (left, right, top, bottom)
flatten : bool, return variable in 2D np.ndarray or in list of flattened array
Returns: locs : 2D np.ndarray of tuple of 4 bool.
"""
nrows, ncols = axs.shape
locs = np.zeros([nrows, ncols, 4], dtype=int)
locs[ :, 0,0] = loc[0]
locs[ :,-1,1] = loc[1]
locs[ 0, :,2] = loc[2]
locs[-1, :,3] = loc[3]
if flatten:
loc_list = list(locs.tolist())
locs = []
for i in range(nrows):
for j in range(ncols):
locs.append(loc_list[i][j])
return locs
def check_colormap_input(metadata, cmap_name=None, datasetName=None, cmap_lut=256,
cmap_vlist=[0.0, 0.7, 1.0], print_msg=True):
gray_dataset_key_words = ['coherence', 'temporal_coherence',
'.cor', '.mli', '.slc', '.amp', '.ramp']
if not cmap_name:
if any(i in gray_dataset_key_words for i in [metadata['FILE_TYPE'],
str(datasetName).split('-')[0]]):
cmap_name = 'gray'
else:
cmap_name = 'jet'
if print_msg:
print('colormap:', cmap_name)
return ColormapExt(cmap_name, cmap_lut, vlist=cmap_vlist).colormap
def auto_adjust_xaxis_date(ax, datevector, fontsize=12, every_year=1, buffer_year=0.2):
"""Adjust X axis
Input:
ax - matplotlib figure axes object
datevector - list of float, date in years
i.e. [2007.013698630137, 2007.521917808219, 2007.6463470319634]
OR list of datetime.datetime objects
every_year - int, number of years per major locator
buffer_year - float in years, None for keep the original xlim range.
Output:
ax - matplotlib figure axes object
dss - datetime.datetime object, xmin
dee - datetime.datetime object, xmax
"""
# convert datetime.datetime format into date in years
if isinstance(datevector[0], datetime.datetime):
datevector = [i.year + (i.timetuple().tm_yday-1)/365.25 for i in datevector]
# Min/Max
if buffer_year is not None:
ts = datevector[0] - buffer_year; ys=int(ts); ms=int((ts - ys) * 12.0)
te = datevector[-1] + buffer_year + 0.1; ye=int(te); me=int((te - ye) * 12.0)
if ms > 12: ys = ys + 1; ms = 1
if me > 12: ye = ye + 1; me = 1
if ms < 1: ys = ys - 1; ms = 12
if me < 1: ye = ye - 1; me = 12
dss = datetime.datetime(ys, ms, 1, 0, 0)
dee = datetime.datetime(ye, me, 1, 0, 0)
else:
(dss, dee) = ax.get_xlim()
ax.set_xlim(dss, dee)
# Label/Tick format
ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d %H:%M:%S')
ax.xaxis.set_major_locator(mdates.YearLocator(every_year))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y'))
ax.xaxis.set_minor_locator(mdates.MonthLocator())
# Label font size
ax.tick_params(labelsize=fontsize)
# fig2.autofmt_xdate() #adjust x overlap by rorating, may enble again
return ax, dss, dee
def auto_adjust_yaxis(ax, dataList, fontsize=12, ymin=None, ymax=None):
"""Adjust Y axis
Input:
ax : matplot figure axes object
dataList : list of float, value in y axis
fontsize : float, font size
ymin : float, lower y axis limit
ymax : float, upper y axis limit
Output:
ax
"""
# Min/Max
dataRange = max(dataList) - min(dataList)
if ymin is None:
ymin = min(dataList) - 0.1*dataRange
if ymax is None:
ymax = max(dataList) + 0.1*dataRange
ax.set_ylim([ymin, ymax])
# Tick/Label setting
#xticklabels = plt.getp(ax, 'xticklabels')
#yticklabels = plt.getp(ax, 'yticklabels')
#plt.setp(yticklabels, 'color', 'k', fontsize=fontsize)
#plt.setp(xticklabels, 'color', 'k', fontsize=fontsize)
return ax
####################################### Plot ################################################
def plot_coherence_history(ax, date12List, cohList, p_dict={}):
"""Plot min/max Coherence of all interferograms for each date"""
# Figure Setting
if 'fontsize' not in p_dict.keys(): p_dict['fontsize'] = 12
if 'linewidth' not in p_dict.keys(): p_dict['linewidth'] = 2
if 'markercolor' not in p_dict.keys(): p_dict['markercolor'] = 'orange'
if 'markersize' not in p_dict.keys(): p_dict['markersize'] = 16
if 'disp_title' not in p_dict.keys(): p_dict['disp_title'] = True
if 'every_year' not in p_dict.keys(): p_dict['every_year'] = 1
if 'vlim' not in p_dict.keys(): p_dict['vlim'] = [0.2, 1.0]
# Get date list
date12List = ptime.yyyymmdd_date12(date12List)
m_dates = [date12.split('_')[0] for date12 in date12List]
s_dates = [date12.split('_')[1] for date12 in date12List]
dateList = sorted(ptime.yyyymmdd(list(set(m_dates + s_dates))))
dates, datevector = ptime.date_list2vector(dateList)
bar_width = ut0.most_common(np.diff(dates).tolist())*3/4
x_list = [i-bar_width/2 for i in dates]
coh_mat = pnet.coherence_matrix(date12List, cohList)
ax.bar(x_list, np.nanmax(coh_mat, axis=0), bar_width.days, label='Max Coherence')
ax.bar(x_list, np.nanmin(coh_mat, axis=0), bar_width.days, label='Min Coherence')
if p_dict['disp_title']:
ax.set_title('Coherence History of All Related Interferograms')
ax = auto_adjust_xaxis_date(ax, datevector, fontsize=p_dict['fontsize'],
every_year=p_dict['every_year'])[0]
ax.set_ylim([p_dict['vlim'][0], p_dict['vlim'][1]])
ax.set_xlabel('Time [years]', fontsize=p_dict['fontsize'])
ax.set_ylabel('Coherence', fontsize=p_dict['fontsize'])
ax.legend(loc='lower right')
return ax
def plot_network(ax, date12List, dateList, pbaseList, p_dict={}, date12List_drop=[], print_msg=True):
"""Plot Temporal-Perp baseline Network
Inputs
ax : matplotlib axes object
date12List : list of string for date12 in YYYYMMDD_YYYYMMDD format
dateList : list of string, for date in YYYYMMDD format
pbaseList : list of float, perp baseline, len=number of acquisition
p_dict : dictionary with the following items:
fontsize
linewidth
markercolor
markersize
cohList : list of float, coherence value of each interferogram, len = number of ifgrams
colormap : string, colormap name
disp_title : bool, show figure title or not, default: True
disp_drop: bool, show dropped interferograms or not, default: True
Output
ax : matplotlib axes object
"""
# Figure Setting
if 'fontsize' not in p_dict.keys(): p_dict['fontsize'] = 12
if 'linewidth' not in p_dict.keys(): p_dict['linewidth'] = 2
if 'markercolor' not in p_dict.keys(): p_dict['markercolor'] = 'orange'
if 'markersize' not in p_dict.keys(): p_dict['markersize'] = 16
# For colorful display of coherence
if 'cohList' not in p_dict.keys(): p_dict['cohList'] = None
if 'ylabel' not in p_dict.keys(): p_dict['ylabel'] = 'Perp Baseline [m]'
if 'cbar_label' not in p_dict.keys(): p_dict['cbar_label'] = 'Average Spatial Coherence'
if 'disp_cbar' not in p_dict.keys(): p_dict['disp_cbar'] = True
if 'colormap' not in p_dict.keys(): p_dict['colormap'] = 'RdBu'
if 'vlim' not in p_dict.keys(): p_dict['vlim'] = [0.2, 1.0]
if 'disp_title' not in p_dict.keys(): p_dict['disp_title'] = True
if 'disp_drop' not in p_dict.keys(): p_dict['disp_drop'] = True
if 'disp_legend' not in p_dict.keys(): p_dict['disp_legend'] = True
if 'every_year' not in p_dict.keys(): p_dict['every_year'] = 1
if 'number' not in p_dict.keys(): p_dict['number'] = None
# support input colormap: string for colormap name, or colormap object directly
if isinstance(p_dict['colormap'], str):
cmap = ColormapExt(p_dict['colormap']).colormap
elif isinstance(p_dict['colormap'], LinearSegmentedColormap):
cmap = p_dict['colormap']
else:
raise ValueError('unrecognized colormap input: {}'.format(p_dict['colormap']))
cohList = p_dict['cohList']
transparency = 0.7
# Date Convert
dateList = ptime.yyyymmdd(sorted(dateList))
dates, datevector = ptime.date_list2vector(dateList)
tbaseList = ptime.date_list2tbase(dateList)[0]
## maxBperp and maxBtemp
date12List = ptime.yyyymmdd_date12(date12List)
ifgram_num = len(date12List)
pbase12 = np.zeros(ifgram_num)
tbase12 = np.zeros(ifgram_num)
for i in range(ifgram_num):
m_date, s_date = date12List[i].split('_')
m_idx = dateList.index(m_date)
s_idx = dateList.index(s_date)
pbase12[i] = pbaseList[s_idx] - pbaseList[m_idx]
tbase12[i] = tbaseList[s_idx] - tbaseList[m_idx]
if print_msg:
print('max perpendicular baseline: {:.2f} m'.format(np.max(np.abs(pbase12))))
print('max temporal baseline: {} days'.format(np.max(tbase12)))
## Keep/Drop - date12
date12List_keep = sorted(list(set(date12List) - set(date12List_drop)))
idx_date12_keep = [date12List.index(i) for i in date12List_keep]
idx_date12_drop = [date12List.index(i) for i in date12List_drop]
if not date12List_drop:
p_dict['disp_drop'] = False
## Keep/Drop - date
m_dates = [i.split('_')[0] for i in date12List_keep]
s_dates = [i.split('_')[1] for i in date12List_keep]
dateList_keep = ptime.yyyymmdd(sorted(list(set(m_dates + s_dates))))
dateList_drop = sorted(list(set(dateList) - set(dateList_keep)))
idx_date_keep = [dateList.index(i) for i in dateList_keep]
idx_date_drop = [dateList.index(i) for i in dateList_drop]
# Ploting
if cohList is not None:
data_min = min(cohList)
data_max = max(cohList)
disp_min = p_dict['vlim'][0]
disp_max = p_dict['vlim'][1]
if print_msg:
print('showing coherence')
print('data range: {}'.format([data_min, data_max]))
print('display range: {}'.format(p_dict['vlim']))
if p_dict['disp_cbar']:
cax = make_axes_locatable(ax).append_axes("right", "3%", pad="3%")
norm = mpl.colors.Normalize(vmin=disp_min, vmax=disp_max)
cbar = mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
cbar.ax.tick_params(labelsize=p_dict['fontsize'])
cbar.set_label(p_dict['cbar_label'], fontsize=p_dict['fontsize'])
# plot low coherent ifgram first and high coherence ifgram later
cohList_keep = [cohList[date12List.index(i)] for i in date12List_keep]
date12List_keep = [x for _, x in sorted(zip(cohList_keep, date12List_keep))]
# Dot - SAR Acquisition
if idx_date_keep:
x_list = [dates[i] for i in idx_date_keep]
y_list = [pbaseList[i] for i in idx_date_keep]
ax.plot(x_list, y_list, 'ko', alpha=0.7, ms=p_dict['markersize'], mfc=p_dict['markercolor'])
if idx_date_drop:
x_list = [dates[i] for i in idx_date_drop]
y_list = [pbaseList[i] for i in idx_date_drop]
ax.plot(x_list, y_list, 'ko', alpha=0.7, ms=p_dict['markersize'], mfc='gray')
## Line - Pair/Interferogram
# interferograms dropped
if p_dict['disp_drop']:
for date12 in date12List_drop:
date1, date2 = date12.split('_')
idx1 = dateList.index(date1)
idx2 = dateList.index(date2)
x = np.array([dates[idx1], dates[idx2]])
y = np.array([pbaseList[idx1], pbaseList[idx2]])
if cohList is not None:
coh = cohList[date12List.index(date12)]
coh_norm = (coh - disp_min) / (disp_max - disp_min)
ax.plot(x, y, '--', lw=p_dict['linewidth'], alpha=transparency, c=cmap(coh_norm))
else:
ax.plot(x, y, '--', lw=p_dict['linewidth'], alpha=transparency, c='k')
# interferograms kept
for date12 in date12List_keep:
date1, date2 = date12.split('_')
idx1 = dateList.index(date1)
idx2 = dateList.index(date2)
x = np.array([dates[idx1], dates[idx2]])
y = np.array([pbaseList[idx1], pbaseList[idx2]])
if cohList is not None:
coh = cohList[date12List.index(date12)]
coh_norm = (coh - disp_min) / (disp_max - disp_min)
ax.plot(x, y, '-', lw=p_dict['linewidth'], alpha=transparency, c=cmap(coh_norm))
else:
ax.plot(x, y, '-', lw=p_dict['linewidth'], alpha=transparency, c='k')
if p_dict['disp_title']:
ax.set_title('Interferogram Network', fontsize=p_dict['fontsize'])
# axis format
ax = auto_adjust_xaxis_date(ax, datevector, fontsize=p_dict['fontsize'],
every_year=p_dict['every_year'])[0]
ax = auto_adjust_yaxis(ax, pbaseList, fontsize=p_dict['fontsize'])
ax.set_xlabel('Time [years]', fontsize=p_dict['fontsize'])
ax.set_ylabel(p_dict['ylabel'], fontsize=p_dict['fontsize'])
ax.tick_params(which='both', direction='in', labelsize=p_dict['fontsize'],
bottom=True, top=True, left=True, right=True)
if p_dict['number'] is not None:
ax.annotate(p_dict['number'], xy=(0.03, 0.92), color='k',
xycoords='axes fraction', fontsize=p_dict['fontsize'])
# Legend
if p_dict['disp_drop'] and p_dict['disp_legend']:
solid_line = mlines.Line2D([], [], color='k', ls='solid', label='Ifg used')
dash_line = mlines.Line2D([], [], color='k', ls='dashed', label='Ifg dropped')
ax.legend(handles=[solid_line, dash_line])
return ax
def plot_perp_baseline_hist(ax, dateList, pbaseList, p_dict={}, dateList_drop=[]):
""" Plot Perpendicular Spatial Baseline History
Inputs
ax : matplotlib axes object
dateList : list of string, date in YYYYMMDD format
pbaseList : list of float, perp baseline
p_dict : dictionary with the following items:
fontsize
linewidth
markercolor
markersize
disp_title : bool, show figure title or not, default: True
every_year : int, number of years for the major tick on xaxis
dateList_drop : list of string, date dropped in YYYYMMDD format
e.g. ['20080711', '20081011']
Output:
ax : matplotlib axes object
"""
# Figure Setting
if 'fontsize' not in p_dict.keys(): p_dict['fontsize'] = 12
if 'linewidth' not in p_dict.keys(): p_dict['linewidth'] = 2
if 'markercolor' not in p_dict.keys(): p_dict['markercolor'] = 'orange'
if 'markersize' not in p_dict.keys(): p_dict['markersize'] = 16
if 'disp_title' not in p_dict.keys(): p_dict['disp_title'] = True
if 'every_year' not in p_dict.keys(): p_dict['every_year'] = 1
transparency = 0.7
# Date Convert
dateList = ptime.yyyymmdd(dateList)
dates, datevector = ptime.date_list2vector(dateList)
# Get index of date used and dropped
# dateList_drop = ['20080711', '20081011'] # for debug
idx_keep = list(range(len(dateList)))
idx_drop = []
for i in dateList_drop:
idx = dateList.index(i)
idx_keep.remove(idx)
idx_drop.append(idx)
# Plot
# ax=fig.add_subplot(111)
# Plot date used
if idx_keep:
x_list = [dates[i] for i in idx_keep]
y_list = [pbaseList[i] for i in idx_keep]
ax.plot(x_list, y_list, '-ko', alpha=transparency, lw=p_dict['linewidth'],
ms=p_dict['markersize'], mfc=p_dict['markercolor'])
# Plot date dropped
if idx_drop:
x_list = [dates[i] for i in idx_drop]
y_list = [pbaseList[i] for i in idx_drop]
ax.plot(x_list, y_list, 'ko', alpha=transparency,
ms=p_dict['markersize'], mfc='gray')
if p_dict['disp_title']:
ax.set_title('Perpendicular Baseline History', fontsize=p_dict['fontsize'])
# axis format
ax = auto_adjust_xaxis_date(ax, datevector, fontsize=p_dict['fontsize'],
every_year=p_dict['every_year'])[0]
ax = auto_adjust_yaxis(ax, pbaseList, fontsize=p_dict['fontsize'])
ax.set_xlabel('Time [years]', fontsize=p_dict['fontsize'])
ax.set_ylabel('Perpendicular Baseline [m]', fontsize=p_dict['fontsize'])
return ax
def plot_rotate_diag_coherence_matrix(ax, coh_list, date12_list, date12_list_drop=[],
rotate_deg=-45., cmap='RdBu', disp_half=False, disp_min=0.2):
"""Plot Rotated Coherence Matrix, suitable for Sentinel-1 data with sequential network"""
# support input colormap: string for colormap name, or colormap object directly
if isinstance(cmap, str):
cmap = ColormapExt(cmap).colormap
elif isinstance(cmap, LinearSegmentedColormap):
pass
else:
raise ValueError('unrecognized colormap input: {}'.format(cmap))
#calculate coherence matrix
coh_mat = pnet.coherence_matrix(date12_list, coh_list)
#for date12_list_drop, set value to nan in upper triangle
if date12_list_drop:
m_dates = [i.split('_')[0] for i in date12_list]
s_dates = [i.split('_')[1] for i in date12_list]
date_list = sorted(list(set(m_dates + s_dates)))
for date12 in date12_list_drop:
idx1, idx2 = [date_list.index(i) for i in date12.split('_')]
coh_mat[idx2, idx1] = np.nan
#aux info
num_img = coh_mat.shape[0]
idx1, idx2 = np.where(~np.isnan(coh_mat))
num_conn = np.max(np.abs(idx1 - idx2))
#plot diagonal - black
diag_mat = np.diag(np.ones(num_img))
diag_mat[diag_mat == 0.] = np.nan
im = ax.imshow(diag_mat, cmap='gray_r', vmin=0.0, vmax=1.0)
im.set_transform(transforms.Affine2D().rotate_deg(rotate_deg) + ax.transData)
im = ax.imshow(coh_mat, vmin=disp_min, vmax=1, cmap=cmap)
im.set_transform(transforms.Affine2D().rotate_deg(rotate_deg) + ax.transData)
#axis format
ymax = np.sqrt(num_conn**2 / 2.) + 0.9
ax.set_xlim(-1, np.sqrt(num_img**2 * 2)-0.7)
if disp_half:
ax.set_ylim(0, ymax)