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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/mintpy/cli/plot_coherence_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# right: matrix view
# show color jump same as the coherence threshold in network inversion with pixel-wised masking
plot_coherence_matrix.py inputs/ifgramStack.h5 --cmap-vlist 0 0.4 1
plot_coherence_matrix.py inputs/ifgramStack.h5 --axis-format time --yx 216 310
"""


Expand All @@ -42,6 +43,11 @@ def create_parser(subparsers=None):
help='Point of interest in lat/lon')
parser.add_argument('--lookup','--lut', dest='lookup_file',
help='Lookup file to convert lat/lon into y/x')

# format
parser.add_argument('--ax-fmt', '--axis-format', dest='axis_format',
choices=['index', 'time'], default='time',
help='Coherence matrix axis format: index or time (default: %(default)s).')
parser.add_argument('-c','--cmap', dest='cmap_name', default='RdBu_truncate',
help='Colormap for coherence matrix.\nDefault: RdBu_truncate')
parser.add_argument('--cmap-vlist', dest='cmap_vlist', type=float, nargs=3, default=[0.0, 0.7, 1.0],
Expand All @@ -61,6 +67,7 @@ def create_parser(subparsers=None):
parser.add_argument('-t','--template', dest='template_file',
help='temporal file.')

# output
parser.add_argument('--save', dest='save_fig',
action='store_true', help='save the figure')
parser.add_argument('--nodisplay', dest='disp_fig',
Expand Down
3 changes: 3 additions & 0 deletions src/mintpy/cli/plot_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def create_parser(subparsers=None):

# Figure Setting
fig = parser.add_argument_group('Figure', 'Figure settings for display')
fig.add_argument('--ax-fmt', '--axis-format', dest='axis_format',
choices=['index', 'time'], default='time',
help='Coherence matrix axis format: index or time (default: %(default)s).')
fig.add_argument('--fs', '--fontsize', type=int,
default=12, help='font size in points')
fig.add_argument('--lw', '--linewidth', dest='linewidth',
Expand Down
31 changes: 16 additions & 15 deletions src/mintpy/plot_coherence_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,25 +179,26 @@ def plot_coherence_matrix4pixel(self, yx):
plotDict['disp_legend'] = False

# plot
coh_mat = pp.plot_coherence_matrix(
self.ax_mat,
date12List=self.date12_list,
cohList=coh.tolist(),
date12List_drop=ex_date12_list,
p_dict=plotDict,
)[1]
if self.axis_format == 'time':
pp.plot_coherence_matrix_time_axis(
self.ax_mat,
date12List=self.date12_list,
cohList=coh.tolist(),
date12List_drop=ex_date12_list,
p_dict=plotDict,
)
else:
pp.plot_coherence_matrix(
self.ax_mat,
date12List=self.date12_list,
cohList=coh.tolist(),
date12List_drop=ex_date12_list,
p_dict=plotDict,
)

self.ax_mat.annotate('ifgrams\navailable', xy=(0.05, 0.05), xycoords='axes fraction', fontsize=12)
self.ax_mat.annotate('ifgrams\nused', ha='right', xy=(0.95, 0.85), xycoords='axes fraction', fontsize=12)

# status bar
def format_coord(x, y):
row, col = int(y+0.5), int(x+0.5)
date12 = sorted([self.date_list[row], self.date_list[col]])
date12 = [f'{i[0:4]}-{i[4:6]}-{i[6:8]}' for i in date12]
return f'x={date12[0]}, y={date12[1]}, v={coh_mat[row, col]:.3f}'
self.ax_mat.format_coord = format_coord

# info
msg = f'pixel in yx = {tuple(yx)}, '
if self.fig_coord == 'geo':
Expand Down
30 changes: 20 additions & 10 deletions src/mintpy/plot_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,31 @@ def plot_network(inps):
)
if inps.save_fig:
fig.savefig(fig_names[1], **kwargs)
print(f'save figure to {fig_names[2]}')
print(f'save figure to {fig_names[1]}')

# Fig 3 - Coherence Matrix
# Fig 3 - Coherence Matrix (index or time axis)
fig_size3 = np.mean(inps.fig_size)
fig, ax = plt.subplots(figsize=[fig_size3, fig_size3])
ax = pp.plot_coherence_matrix(
ax,
inps.date12List,
inps.cohList,
inps.date12List_drop,
p_dict=vars(inps),
)[0]
if inps.axis_format == 'time':
ax = pp.plot_coherence_matrix_time_axis(
ax,
inps.date12List,
inps.cohList,
inps.date12List_drop,
p_dict=vars(inps),
)[0]
fig.tight_layout()
else:
ax = pp.plot_coherence_matrix(
ax,
inps.date12List,
inps.cohList,
inps.date12List_drop,
p_dict=vars(inps),
)[0]
if inps.save_fig:
fig.savefig(fig_names[2], **kwargs)
print(f'save figure to {fig_names[1]}')
print(f'save figure to {fig_names[2]}')

# Fig 4 - Interferogram Network
fig, ax = plt.subplots(figsize=inps.fig_size)
Expand Down
165 changes: 161 additions & 4 deletions src/mintpy/utils/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,11 +905,11 @@ def plot_coherence_matrix(ax, date12List, cohList, date12List_drop=[], p_dict={}
date12List = ptime.yyyymmdd_date12(date12List)
coh_mat = pnet.coherence_matrix(date12List, cohList)

# Date Convert (also used by status bar below)
m_dates = [i.split('_')[0] for i in date12List]
s_dates = [i.split('_')[1] for i in date12List]
dateList = sorted(list(set(m_dates + s_dates)))
if date12List_drop:
# Date Convert
m_dates = [i.split('_')[0] for i in date12List]
s_dates = [i.split('_')[1] for i in date12List]
dateList = sorted(list(set(m_dates + s_dates)))
# Set dropped pairs' value to nan, in upper triangle only.
for date12 in date12List_drop:
idx1, idx2 = (dateList.index(i) for i in date12.split('_'))
Expand Down Expand Up @@ -956,9 +956,166 @@ def plot_coherence_matrix(ax, date12List, cohList, date12List_drop=[], p_dict={}
ax.plot([], [], label='Lower: Ifgrams all')
ax.legend(loc=p_dict['legend_loc'], handlelength=0)

# Status bar
def format_coord(x, y):
row, col = int(y + 0.5), int(x + 0.5)
if 0 <= row < len(dateList) and 0 <= col < len(dateList):
date12 = sorted([dateList[row], dateList[col]])
date12 = [f'{i[0:4]}-{i[4:6]}-{i[6:8]}' for i in date12]
return f'x={date12[0]}, y={date12[1]}, v={coh_mat[row, col]:.3f}'
return ''

ax.format_coord = format_coord

return ax, coh_mat, im


def plot_coherence_matrix_time_axis(ax, date12List, cohList, date12List_drop=[], p_dict={}):
Comment thread
yunjunz marked this conversation as resolved.
"""Plot Coherence Matrix with continuous time axis
Parameters: ax : matplotlib.pyplot.Axes,
date12List : list of date12 in YYYYMMDD_YYYYMMDD format
cohList : list of float, coherence value
date12List_drop : list of date12 for date12 marked as dropped
p_dict : dict of plot setting
Returns: ax : matplotlib.pyplot.Axes
coh_mat : 2D np.array in size of [num_date, num_date]
mesh : matplotlib.collections.QuadMesh object
"""
# Figure Setting
if 'ds_name' not in p_dict.keys(): p_dict['ds_name'] = 'Coherence'
if 'fontsize' not in p_dict.keys(): p_dict['fontsize'] = 12
if 'disp_title' not in p_dict.keys(): p_dict['disp_title'] = True
if 'fig_title' not in p_dict.keys(): p_dict['fig_title'] = '{} Matrix'.format(p_dict['ds_name'])
if 'colormap' not in p_dict.keys(): p_dict['colormap'] = 'RdBu_truncate'
if 'cbar_label' not in p_dict.keys(): p_dict['cbar_label'] = p_dict['ds_name']
if 'vlim' not in p_dict.keys(): p_dict['vlim'] = (0.2, 1.0)
if 'disp_cbar' not in p_dict.keys(): p_dict['disp_cbar'] = True
if 'legend_loc' not in p_dict.keys(): p_dict['legend_loc'] = 'best'
if 'disp_legend' not in p_dict.keys(): p_dict['disp_legend'] = True

# 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'], mpl.colors.LinearSegmentedColormap):
cmap = p_dict['colormap']
else:
raise ValueError('unrecognized colormap input: {}'.format(p_dict['colormap']))

date12List = ptime.yyyymmdd_date12(date12List)
coh_mat = pnet.coherence_matrix(date12List, cohList)

m_dates = [i.split('_')[0] for i in date12List]
s_dates = [i.split('_')[1] for i in date12List]
dateList = ptime.yyyymmdd(sorted(list(set(m_dates + s_dates))))
dates = [dt.datetime.strptime(i, '%Y%m%d') for i in dateList]

if date12List_drop:
date12List_drop = ptime.yyyymmdd_date12(date12List_drop)
for date12 in date12List_drop:
idx1, idx2 = (dateList.index(i) for i in date12.split('_'))
coh_mat[idx1, idx2] = np.nan
Comment thread
yunjunz marked this conversation as resolved.

# Plotting strategy for the time-axis coherence matrix:
# 1) Build a date-centered grid: each acquisition sits at the midpoint between
# neighboring cell edges; the first/last edges extend outward by half of the
# adjacent interval so edge cells match the in-network cell width.
# 2) Convert grid edges to matplotlib date numbers via mdates.date2num() because
# pcolormesh() requires numeric vertex coordinates for datetime axes.
grid_points = [dates[0] - (dates[1] - dates[0]) / 2]
for date1, date2 in zip(dates[:-1], dates[1:]):
grid_points.append(date1 + (date2 - date1) / 2)
grid_points.append(dates[-1] + (dates[-1] - dates[-2]) / 2)

grid_nums = mdates.date2num(grid_points)
X, Y = np.meshgrid(grid_nums, grid_nums)

# Plot diagonal grids (gray/black, zorder=1): mark acquisition dates for visual reference, and
# to distinguish them from un-selected / dropped interferograms (off-diagonal NaNs).
diag_mat = np.diag(np.ones(coh_mat.shape[0]))
diag_mat[diag_mat == 0.] = np.nan
ax.pcolormesh(X, Y, diag_mat, cmap='gray_r', vmin=0.0, vmax=1.0, shading='auto', zorder=1)

# Plot off-diagonal grids (zorder=0): coherence of each ifgram pair; upper triangle may exclude
# dropped pairs (NaN -> white via set_bad) while lower triangle keeps the full network.
cmap.set_bad('white')
mesh = ax.pcolormesh(
X, Y, coh_mat,
cmap=cmap,
vmin=p_dict['vlim'][0],
vmax=p_dict['vlim'][1],
shading='auto',
zorder=0,
)

# axis format
# x-axis: reuse auto_adjust_xaxis_date() year labels
# y-axis: copy the same locators/formatters from the x-axis to be consistent
ax.set_aspect('equal', adjustable='box')
ax = auto_adjust_xaxis_date(ax, dates, buffer_year=None, fontsize=p_dict['fontsize'])[0]

# for short span (<=1.5 yr), use same-line labels — year at Jan, odd month num at others
span_years = (dates[-1] - dates[0]).days / 365.25
if span_years <= 1.5:
def _month_or_year(x, pos=None):
d = mdates.num2date(x).replace(tzinfo=None)
if d.month == 1:
return str(d.year)
return str(d.month)
for axis in [ax.xaxis, ax.yaxis]:
axis.set_major_locator(mdates.MonthLocator(bymonth=range(1, 13, 2)))
axis.set_major_formatter(ticker.FuncFormatter(_month_or_year))
axis.set_minor_locator(mdates.MonthLocator())
else:
# Sync y-axis tick locators/formatters with the x-axis (after auto_adjust)
ax.yaxis.set_major_locator(ax.xaxis.get_major_locator())
ax.yaxis.set_major_formatter(ax.xaxis.get_major_formatter())
ax.yaxis.set_minor_locator(ax.xaxis.get_minor_locator())

# Invert y-axis so early dates are at the top (same visual layout as the index matrix)
ax.set_ylim(ax.get_xlim()[::-1])
ax.set_xlabel('Time', fontsize=p_dict['fontsize'])
ax.set_ylabel('Time', fontsize=p_dict['fontsize'])
# Rotate y tick labels 90 deg for readable date/month labels along the left edge
for label in ax.get_yticklabels():
label.set_rotation(90)
label.set_va('center')
ax.tick_params(which='both', direction='out',
bottom=True, top=True, left=True, right=True)

if p_dict['disp_title']:
ax.set_title(p_dict['fig_title'])

# Colorbar
if p_dict['disp_cbar']:
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", "3%", pad="3%")
cbar = ax.figure.colorbar(mesh, cax=cax)
cbar.set_label(p_dict['cbar_label'], fontsize=p_dict['fontsize'])

# Legend
if date12List_drop and p_dict['disp_legend']:
ax.plot([], [], label='Upper: Ifgrams used')
ax.plot([], [], label='Lower: Ifgrams all')
ax.legend(loc=p_dict['legend_loc'], handlelength=0)

# Status bar
def format_coord(x, y):
col = np.searchsorted(grid_nums, x, side='right') - 1
row = np.searchsorted(grid_nums, y, side='right') - 1
if 0 <= row < len(dates) and 0 <= col < len(dates):
date1 = dates[col].strftime('%Y-%m-%d')
date2 = dates[row].strftime('%Y-%m-%d')
coh_val = coh_mat[row, col]
if not np.isnan(coh_val):
return f'x={date1}, y={date2}, v={coh_val:.3f}'
return f'x={date1}, y={date2}, v=NaN'
return ''

ax.format_coord = format_coord

return ax, coh_mat, mesh


def plot_num_triplet_with_nonzero_integer_ambiguity(fname, disp_fig=False, font_size=12, fig_size=[9,3]):
"""Plot the histogram for the number of triplets with non-zero integer ambiguity.

Expand Down