-
Notifications
You must be signed in to change notification settings - Fork 296
coherence matrix plot: add timeaxis mode to better support irregular temporal sampling #1452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Refactor timeaxis plotting logic to utils/plot.py - Add black diagonal cells in timeaxis mode - Set default colormap to RdBu_truncate (same as normal mode) - Fix colorbar vlim to use [cmap_vlist[0], cmap_vlist[-1]] - Ensure upper triangle shows only kept pairs, lower triangle shows all pairs - Add timeaxis coherence matrix plot to plot_network.py
|
💖 Thanks for opening this pull request! Please check out our contributing guidelines. 💖 |
Reviewer's GuideImplements a new continuous time-axis coherence matrix plotting function and integrates it into both the standalone coherence-matrix viewer and the network plotting workflow, including a new CLI flag and figure layout changes. Sequence diagram for time-axis coherence matrix interaction in viewersequenceDiagram
actor User
participant CLI_plot_coherence_matrix
participant coherenceMatrixViewer
participant PlotUtils as PlotUtils
participant Matplotlib
User->>CLI_plot_coherence_matrix: run with args (including optional time_axis)
CLI_plot_coherence_matrix->>coherenceMatrixViewer: create(inps)
CLI_plot_coherence_matrix->>coherenceMatrixViewer: open()
CLI_plot_coherence_matrix->>coherenceMatrixViewer: plot()
coherenceMatrixViewer->>Matplotlib: plt.subplots(figname_img)
coherenceMatrixViewer->>coherenceMatrixViewer: plot_init_image()
coherenceMatrixViewer->>Matplotlib: plt.subplots(figname_mat)
coherenceMatrixViewer->>Matplotlib: mpl_connect(button_press_event, update_coherence_matrix)
Matplotlib-->>User: display image and matrix windows
User->>Matplotlib: click on image pixel
Matplotlib->>coherenceMatrixViewer: update_coherence_matrix(event)
coherenceMatrixViewer->>coherenceMatrixViewer: compute yx from event
alt time_axis is True
coherenceMatrixViewer->>coherenceMatrixViewer: plot_coherence_matrix4pixel_time_axis(yx)
coherenceMatrixViewer->>PlotUtils: plot_coherence_matrix_time_axis(ax_mat, date12List, cohList, date12List_drop, p_dict)
PlotUtils-->>coherenceMatrixViewer: ax_mat, Z, mesh
else time_axis is False
coherenceMatrixViewer->>coherenceMatrixViewer: plot_coherence_matrix4pixel(yx)
coherenceMatrixViewer->>PlotUtils: plot_coherence_matrix(ax_mat, date12List, cohList, date12List_drop, p_dict)
PlotUtils-->>coherenceMatrixViewer: ax_mat, coh_mat, im
end
coherenceMatrixViewer->>coherenceMatrixViewer: update_image_marker(yx)
coherenceMatrixViewer->>Matplotlib: draw_idle(), flush_events()
Matplotlib-->>User: updated coherence matrix and marker
Sequence diagram for plot_network adding time-axis coherence matrix figuresequenceDiagram
participant Caller as plot_network_caller
participant plot_network
participant PlotUtils as PlotUtils
participant Matplotlib
Caller->>plot_network: call(inps)
plot_network->>Matplotlib: subplots() for pbaseHistory
plot_network->>Matplotlib: subplots() for coherenceHistory
plot_network->>PlotUtils: plot_coherence_matrix(ax, inps.date12List, inps.cohList, inps.date12List_drop, p_dict)
PlotUtils-->>plot_network: ax, coh_mat, im
alt inps.cohList is not None
plot_network->>Matplotlib: subplots() for coherenceMatrixTimeAxis
plot_network->>PlotUtils: plot_coherence_matrix_time_axis(ax, inps.date12List, inps.cohList, inps.date12List_drop, p_dict)
PlotUtils-->>plot_network: ax, Z, mesh
end
plot_network->>Matplotlib: subplots() for network
plot_network->>PlotUtils: plot_network(ax, inps.pbaseDict, inps.date12List, inps.ifgIndexDict, inps.dropIfgIndexDict, inps.date12List_drop)
PlotUtils-->>plot_network: ax
opt inps.save_fig
plot_network->>Matplotlib: savefig(fig_names)
end
Class diagram for coherenceMatrixViewer and time-axis plotting utilitiesclassDiagram
class coherenceMatrixViewer {
- figname_img
- figsize_img
- fig_img
- ax_img
- cbar_img
- img
- figname_mat
- figsize_mat
- fig_mat
- ax_mat
- time_axis
+ open()
+ plot()
+ plot_init_image()
+ plot_coherence_matrix4pixel(yx)
+ plot_coherence_matrix4pixel_time_axis(yx)
+ update_coherence_matrix(event)
+ update_image_marker(yx)
}
class PlotUtils {
+ plot_coherence_matrix(ax, date12List, cohList, date12List_drop, p_dict)
+ plot_coherence_matrix_time_axis(ax, date12List, cohList, date12List_drop, p_dict)
}
class plot_network {
+ plot_network(inps)
}
coherenceMatrixViewer ..> PlotUtils : uses
plot_network ..> PlotUtils : uses
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - I've found 3 issues, and left some high level feedback:
- The month tick label formatting in
plot_coherence_matrix_time_axisusesstrftime('%-m'), which is not portable on Windows; consider usingstrftime('%m').lstrip('0')instead to avoid platform-specific behavior. - In
update_image_marker, removing existing markers by scanning all children for a'^'marker is brittle if other plots use the same marker; it would be more robust to keep a reference to the specific marker artist and update/remove that directly. - The date-to-grid index mapping in
plot_coherence_matrix_time_axisdoes a nested search overgrid_pointsfor each date, which is O(n^2); you could precompute bin edges and usenp.searchsortedor a similar vectorized approach to simplify and speed up this section.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The month tick label formatting in `plot_coherence_matrix_time_axis` uses `strftime('%-m')`, which is not portable on Windows; consider using `strftime('%m').lstrip('0')` instead to avoid platform-specific behavior.
- In `update_image_marker`, removing existing markers by scanning all children for a `'^'` marker is brittle if other plots use the same marker; it would be more robust to keep a reference to the specific marker artist and update/remove that directly.
- The date-to-grid index mapping in `plot_coherence_matrix_time_axis` does a nested search over `grid_points` for each date, which is O(n^2); you could precompute bin edges and use `np.searchsorted` or a similar vectorized approach to simplify and speed up this section.
## Individual Comments
### Comment 1
<location> `src/mintpy/utils/plot.py:987-992` </location>
<code_context>
+ if 'disp_cbar' not in p_dict.keys(): p_dict['disp_cbar'] = 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']))
+
+ # Normalize date12 format
</code_context>
<issue_to_address>
**issue:** Broaden the accepted colormap types to handle generic Matplotlib colormap instances.
`p_dict['colormap']` may be a generic Matplotlib colormap (e.g. `ListedColormap` like `viridis`), but the code only accepts strings or `LinearSegmentedColormap`, causing valid colormaps to raise `ValueError`. To support all Matplotlib colormap instances passed in directly (including custom ones), consider checking against the base `mpl.colors.Colormap` instead of just `LinearSegmentedColormap`.
</issue_to_address>
### Comment 2
<location> `src/mintpy/utils/plot.py:1189` </location>
<code_context>
+ # Only add label for odd months
+ if tick_dates[i].month % 2 == 1:
+ label_positions.append(mid_point)
+ month_labels.append(tick_dates[i].strftime('%-m'))
+ is_january.append(tick_dates[i].month == 1)
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid using `strftime('%-m')` since it is not portable across platforms (e.g., Windows).
On platforms like Windows, `%-m` isn’t supported (it uses `%#m` instead), which can raise a `ValueError`. To keep this portable, derive the month number directly instead of via `strftime`, e.g.:
```python
month_labels.append(str(tick_dates[i].month))
```
This preserves the intended month labels without relying on platform-specific behavior.
</issue_to_address>
### Comment 3
<location> `src/mintpy/utils/plot.py:963` </location>
<code_context>
return ax, coh_mat, im
+def plot_coherence_matrix_time_axis(ax, date12List, cohList, date12List_drop=[], p_dict={}):
+ """Plot Coherence Matrix with continuous time axis
+ Parameters: ax : matplotlib.pyplot.Axes,
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring `plot_coherence_matrix_time_axis` by extracting helpers for date parsing, grid construction, tick layout, and coordinate formatting while removing unused structures to keep behavior but greatly simplify the function.
You can keep all functionality but significantly reduce complexity and duplication by extracting a few helpers and simplifying some data paths. Two concrete wins:
1. **Remove unused structures & redundant parsing**
2. **Factor grid mapping & ticks into small helpers**
### 1. Remove `coh_dict` and redundant date parsing
`coh_dict` is built but never used; only `coh_dict_ordered` and `excluded_pairs` are used when filling `Z`. Also, the “ensure we have datetime objects” block re-parses dates that could be normalized once.
You can simplify the coherence dictionary creation to a single pass that:
- Normalizes strings to canonical `%Y%m%d` once
- Converts to `datetime` once using a shared helper
- Populates `coh_dict_ordered` and `excluded_pairs` only
Example:
```python
def _parse_date_yyyymmdd(date_str: str, cache: dict) -> datetime:
if date_str in cache:
return cache[date_str]
# normalize with ptime first
norm = ptime.yyyymmdd([date_str])[0]
try:
dt_obj = datetime.strptime(norm, "%Y%m%d")
except ValueError:
if len(norm) == 6:
dt_obj = datetime.strptime("20" + norm, "%Y%m%d")
else:
raise
cache[date_str] = dt_obj
cache[norm] = dt_obj
return dt_obj
```
Then in `plot_coherence_matrix_time_axis`:
```python
date_cache = {}
coh_dict_ordered = {}
excluded_pairs = set()
for date12, coh_val in zip(date12List, cohList):
d1_str, d2_str = date12.split("_")
d1 = _parse_date_yyyymmdd(d1_str, date_cache)
d2 = _parse_date_yyyymmdd(d2_str, date_cache)
coh_dict_ordered[(d1, d2)] = float(coh_val)
pair_norm = (min(d1, d2), max(d1, d2))
if date12 in date12List_drop:
excluded_pairs.add(pair_norm)
```
This removes:
- `coh_dict`
- The “if date1_str not in date_objs / if date1 is None” re-checks
- Multiple `ptime.yyyymmdd` calls in different branches
### 2. Simplify grid index mapping with a helper
The manual nested loop assigning dates to grid cells can be replaced with a small helper that uses numeric days and `np.searchsorted`, which will be easier to reason about and reuse (e.g., for the `format_coord` function).
```python
def _build_time_grid(dates: list[datetime]) -> tuple[np.ndarray, dict]:
dates_sorted = np.array(sorted(dates))
base = dates_sorted.min()
# grid_points as before
grid_points = [dates_sorted[0]]
for i in range(len(dates_sorted) - 1):
mid = dates_sorted[i] + (dates_sorted[i + 1] - dates_sorted[i]) / 2
grid_points.append(mid)
grid_points.append(dates_sorted[-1])
grid_points = np.array(grid_points)
days_grid = (grid_points - base).astype("timedelta64[D]").astype(int)
date_days = (dates_sorted - base).astype("timedelta64[D]").astype(int)
# indices: date falls into bin returned by searchsorted-1
idx = np.searchsorted(days_grid, date_days, side="right") - 1
date_to_idx = {d: int(i) for d, i in zip(dates_sorted, idx)}
return days_grid, date_to_idx
```
Usage inside `plot_coherence_matrix_time_axis`:
```python
days_grid, date_to_grid_idx = _build_time_grid(date_list)
X, Y = np.meshgrid(days_grid, days_grid)
Z = np.full((len(days_grid) - 1, len(days_grid) - 1), np.nan)
```
This replaces the manual `for date in date_list: for grid_idx in range(...):` block and also gives you `days_grid` for both plotting and `format_coord`.
### 3. Extract tick/label layout into a helper
The month/year tick logic is correct but verbose. Moving it into a helper keeps the main plot function focused on building `Z` and the mesh.
```python
def _setup_month_year_ticks(ax, base_date: datetime, dates: list[datetime]):
min_date = min(dates)
max_date = max(dates)
# compute tick_dates exactly as now
# ...
tick_positions = [(d - base_date).days for d in tick_dates]
# compute major/minor ticks & labels as now
# ...
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, minor=True)
ax.set_yticks(major_ticks)
ax.set_yticks(minor_ticks, minor=True)
ax.set_xticklabels([''] * len(major_ticks))
ax.set_xticklabels([''] * len(minor_ticks), minor=True)
ax.set_yticklabels([''] * len(major_ticks))
ax.set_yticklabels([''] * len(minor_ticks), minor=True)
# month and year labels via ax.text as now
# ...
```
Then in the main function:
```python
base_date = min(date_list)
# ... build grid / Z ...
_setup_month_year_ticks(ax, base_date, date_list)
```
This doesn’t change behavior, but it shortens the main function and makes the “plot layout” responsibility clearly separated.
### 4. Extract formatter to a factory
The inline `format_coord` closure adds to the size of the main function and re-creates the “nearest grid index from days” logic. Once you have `days_grid` and `Z`, you can move this to a small factory:
```python
def make_coh_matrix_formatter(days_grid: np.ndarray, grid_points, Z: np.ndarray):
days_grid = np.asarray(days_grid)
def _formatter(x, y):
x_idx = np.argmin(np.abs(days_grid - x))
y_idx = np.argmin(np.abs(days_grid - y))
x_idx = np.clip(x_idx, 0, len(grid_points) - 1)
y_idx = np.clip(y_idx, 0, len(grid_points) - 1)
d1 = grid_points[x_idx]
d2 = grid_points[y_idx]
val = np.nan
if x_idx < len(grid_points) - 1 and y_idx < len(grid_points) - 1:
val = Z[y_idx, x_idx]
if not np.isnan(val):
return f"x={d1:%Y-%m-%d}, y={d2:%Y-%m-%d}, v={val:.3f}"
return f"x={d1:%Y-%m-%d}, y={d2:%Y-%m-%d}, v=NaN"
return _formatter
```
Then in `plot_coherence_matrix_time_axis`:
```python
ax.format_coord = make_coh_matrix_formatter(days_grid, grid_points, Z)
```
---
These changes keep all behavior intact, but:
- Remove unused structures (`coh_dict`) and repeated date parsing logic.
- Encapsulate the most intricate pieces (grid construction, tick labeling, formatter) into helpers, reducing the monolithic size and cognitive load of `plot_coherence_matrix_time_axis`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| 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'])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: Broaden the accepted colormap types to handle generic Matplotlib colormap instances.
p_dict['colormap'] may be a generic Matplotlib colormap (e.g. ListedColormap like viridis), but the code only accepts strings or LinearSegmentedColormap, causing valid colormaps to raise ValueError. To support all Matplotlib colormap instances passed in directly (including custom ones), consider checking against the base mpl.colors.Colormap instead of just LinearSegmentedColormap.
| # Only add label for odd months | ||
| if tick_dates[i].month % 2 == 1: | ||
| label_positions.append(mid_point) | ||
| month_labels.append(tick_dates[i].strftime('%-m')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Avoid using strftime('%-m') since it is not portable across platforms (e.g., Windows).
On platforms like Windows, %-m isn’t supported (it uses %#m instead), which can raise a ValueError. To keep this portable, derive the month number directly instead of via strftime, e.g.:
month_labels.append(str(tick_dates[i].month))This preserves the intended month labels without relying on platform-specific behavior.
| return ax, coh_mat, im | ||
|
|
||
|
|
||
| def plot_coherence_matrix_time_axis(ax, date12List, cohList, date12List_drop=[], p_dict={}): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider refactoring plot_coherence_matrix_time_axis by extracting helpers for date parsing, grid construction, tick layout, and coordinate formatting while removing unused structures to keep behavior but greatly simplify the function.
You can keep all functionality but significantly reduce complexity and duplication by extracting a few helpers and simplifying some data paths. Two concrete wins:
- Remove unused structures & redundant parsing
- Factor grid mapping & ticks into small helpers
1. Remove coh_dict and redundant date parsing
coh_dict is built but never used; only coh_dict_ordered and excluded_pairs are used when filling Z. Also, the “ensure we have datetime objects” block re-parses dates that could be normalized once.
You can simplify the coherence dictionary creation to a single pass that:
- Normalizes strings to canonical
%Y%m%donce - Converts to
datetimeonce using a shared helper - Populates
coh_dict_orderedandexcluded_pairsonly
Example:
def _parse_date_yyyymmdd(date_str: str, cache: dict) -> datetime:
if date_str in cache:
return cache[date_str]
# normalize with ptime first
norm = ptime.yyyymmdd([date_str])[0]
try:
dt_obj = datetime.strptime(norm, "%Y%m%d")
except ValueError:
if len(norm) == 6:
dt_obj = datetime.strptime("20" + norm, "%Y%m%d")
else:
raise
cache[date_str] = dt_obj
cache[norm] = dt_obj
return dt_objThen in plot_coherence_matrix_time_axis:
date_cache = {}
coh_dict_ordered = {}
excluded_pairs = set()
for date12, coh_val in zip(date12List, cohList):
d1_str, d2_str = date12.split("_")
d1 = _parse_date_yyyymmdd(d1_str, date_cache)
d2 = _parse_date_yyyymmdd(d2_str, date_cache)
coh_dict_ordered[(d1, d2)] = float(coh_val)
pair_norm = (min(d1, d2), max(d1, d2))
if date12 in date12List_drop:
excluded_pairs.add(pair_norm)This removes:
coh_dict- The “if date1_str not in date_objs / if date1 is None” re-checks
- Multiple
ptime.yyyymmddcalls in different branches
2. Simplify grid index mapping with a helper
The manual nested loop assigning dates to grid cells can be replaced with a small helper that uses numeric days and np.searchsorted, which will be easier to reason about and reuse (e.g., for the format_coord function).
def _build_time_grid(dates: list[datetime]) -> tuple[np.ndarray, dict]:
dates_sorted = np.array(sorted(dates))
base = dates_sorted.min()
# grid_points as before
grid_points = [dates_sorted[0]]
for i in range(len(dates_sorted) - 1):
mid = dates_sorted[i] + (dates_sorted[i + 1] - dates_sorted[i]) / 2
grid_points.append(mid)
grid_points.append(dates_sorted[-1])
grid_points = np.array(grid_points)
days_grid = (grid_points - base).astype("timedelta64[D]").astype(int)
date_days = (dates_sorted - base).astype("timedelta64[D]").astype(int)
# indices: date falls into bin returned by searchsorted-1
idx = np.searchsorted(days_grid, date_days, side="right") - 1
date_to_idx = {d: int(i) for d, i in zip(dates_sorted, idx)}
return days_grid, date_to_idxUsage inside plot_coherence_matrix_time_axis:
days_grid, date_to_grid_idx = _build_time_grid(date_list)
X, Y = np.meshgrid(days_grid, days_grid)
Z = np.full((len(days_grid) - 1, len(days_grid) - 1), np.nan)This replaces the manual for date in date_list: for grid_idx in range(...): block and also gives you days_grid for both plotting and format_coord.
3. Extract tick/label layout into a helper
The month/year tick logic is correct but verbose. Moving it into a helper keeps the main plot function focused on building Z and the mesh.
def _setup_month_year_ticks(ax, base_date: datetime, dates: list[datetime]):
min_date = min(dates)
max_date = max(dates)
# compute tick_dates exactly as now
# ...
tick_positions = [(d - base_date).days for d in tick_dates]
# compute major/minor ticks & labels as now
# ...
ax.set_xticks(major_ticks)
ax.set_xticks(minor_ticks, minor=True)
ax.set_yticks(major_ticks)
ax.set_yticks(minor_ticks, minor=True)
ax.set_xticklabels([''] * len(major_ticks))
ax.set_xticklabels([''] * len(minor_ticks), minor=True)
ax.set_yticklabels([''] * len(major_ticks))
ax.set_yticklabels([''] * len(minor_ticks), minor=True)
# month and year labels via ax.text as now
# ...Then in the main function:
base_date = min(date_list)
# ... build grid / Z ...
_setup_month_year_ticks(ax, base_date, date_list)This doesn’t change behavior, but it shortens the main function and makes the “plot layout” responsibility clearly separated.
4. Extract formatter to a factory
The inline format_coord closure adds to the size of the main function and re-creates the “nearest grid index from days” logic. Once you have days_grid and Z, you can move this to a small factory:
def make_coh_matrix_formatter(days_grid: np.ndarray, grid_points, Z: np.ndarray):
days_grid = np.asarray(days_grid)
def _formatter(x, y):
x_idx = np.argmin(np.abs(days_grid - x))
y_idx = np.argmin(np.abs(days_grid - y))
x_idx = np.clip(x_idx, 0, len(grid_points) - 1)
y_idx = np.clip(y_idx, 0, len(grid_points) - 1)
d1 = grid_points[x_idx]
d2 = grid_points[y_idx]
val = np.nan
if x_idx < len(grid_points) - 1 and y_idx < len(grid_points) - 1:
val = Z[y_idx, x_idx]
if not np.isnan(val):
return f"x={d1:%Y-%m-%d}, y={d2:%Y-%m-%d}, v={val:.3f}"
return f"x={d1:%Y-%m-%d}, y={d2:%Y-%m-%d}, v=NaN"
return _formatterThen in plot_coherence_matrix_time_axis:
ax.format_coord = make_coh_matrix_formatter(days_grid, grid_points, Z)These changes keep all behavior intact, but:
- Remove unused structures (
coh_dict) and repeated date parsing logic. - Encapsulate the most intricate pieces (grid construction, tick labeling, formatter) into helpers, reducing the monolithic size and cognitive load of
plot_coherence_matrix_time_axis.
…version Fix the issue where inv_quality[idx] assignment fails when inv_quali is returned as a 1D array instead of a scalar for single pixel processing. Use np.atleast_1d(inv_quali)[0] to ensure scalar value extraction.
Summary
This PR adds a new timeaxis mode for coherence matrix plotting and enhances the
plot_network.pyto display both standard and timeaxis coherence matrices.Changes
1. Enhanced
plot_coherence_matrix.py- Split coherence matrix into two views2. Added timeaxis mode for coherence matrix plotting
A new timeaxis mode has been implemented that displays coherence matrices with a continuous time axis instead of discrete date indices. This provides better visualization for temporal coherence patterns.
3. Added timeaxis-coherence matrix in
plot_network.pyThis allows users to compare both visualization approaches side by side when running
plot_network.py.Files Changed
src/mintpy/cli/plot_coherence_matrix.py: Added--time-axisCLI optionsrc/mintpy/plot_coherence_matrix.py: Implemented timeaxis mode supportsrc/mintpy/plot_network.py: Added timeaxis coherence matrix figuresrc/mintpy/utils/plot.py: Addedplot_coherence_matrix_time_axis()functionUsage
In
plot_coherence_matrix.py:plot_coherence_matrix.py ifgramStack.h5 --time-axis
In
plot_network.py:The timeaxis coherence matrix is automatically included as Fig 3 when coherence data is available.
Summary by Sourcery
Add a continuous time-axis coherence matrix visualization alongside the existing date-indexed matrix and wire it into both the standalone viewer and network plotting workflow.
New Features:
Enhancements: