Skip to content

plot

crosspeak.plot

plot_contour(matrix, wavenumbers, *, wavenumbers_x=None, ax=None, title=None, cmap='vlag', n_levels=50, descending=True, mask_diagonal=False)

...

Parameters:

Name Type Description Default
matrix ndarray

2D correlation matrix.

required
wavenumbers ndarray

Wavenumber axis values for matrix axis 0 (rows, y-axis of plot). Length must equal matrix.shape[0].

required
wavenumbers_x ndarray | None

Optional. Wavenumber axis values for matrix axis 1 (cols, x-axis). Length must equal matrix.shape[1]. If None (default), wavenumbers is used for both axes — the standard homospectral case.

None
Source code in src/crosspeak/plot.py
 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
def plot_contour(
    matrix: np.ndarray,
    wavenumbers: np.ndarray,
    *,
    wavenumbers_x: np.ndarray | None = None,
    ax=None,
    title: str | None = None,
    cmap: str = "vlag",
    n_levels: int = 50,
    descending: bool = True,
    mask_diagonal: bool = False,
):
    """...

    Parameters
    ----------
    matrix
        2D correlation matrix.
    wavenumbers
        Wavenumber axis values for matrix axis 0 (rows, y-axis of plot).
        Length must equal matrix.shape[0].
    wavenumbers_x
        Optional. Wavenumber axis values for matrix axis 1 (cols, x-axis).
        Length must equal matrix.shape[1]. If None (default), `wavenumbers`
        is used for both axes — the standard homospectral case.
    """
    if wavenumbers_x is None:
        wavenumbers_x = wavenumbers

    if mask_diagonal and matrix.shape[0] == matrix.shape[1]:
        matrix = matrix.astype(float, copy=True)
        np.fill_diagonal(matrix, np.nan)

    matrix = np.asarray(matrix)
    wavenumbers = np.asarray(wavenumbers)

    if matrix.ndim != 2:
        raise ValueError(f"matrix must be 2D, got shape {matrix.shape}")
    if matrix.shape[0] != wavenumbers.size:
        raise ValueError(
            f"matrix.shape[0] ({matrix.shape[0]}) must equal wavenumbers size ({wavenumbers.size})"
        )
    if matrix.shape[1] != wavenumbers_x.size:
        raise ValueError(
            f"matrix.shape[1] ({matrix.shape[1]}) must equal "
            f"wavenumbers_x size ({wavenumbers_x.size})"
        )
    if wavenumbers.ndim != 1:
        raise ValueError(f"wavenumbers must be 1D, got shape {wavenumbers.shape}")
    if matrix.shape[0] != wavenumbers.size:
        raise ValueError(
            f"matrix dimension {matrix.shape[0]} doesn't match "
            f"wavenumber axis length {wavenumbers.size}"
        )

    if ax is None:
        _, ax = plt.subplots(layout="constrained")

    # Pin zero to the middle of the diverging colormap regardless of data asymmetry
    vmax = np.nanmax(np.abs(matrix))
    if vmax == 0:
        vmax = 1.0
    norm = TwoSlopeNorm(vmin=-vmax, vcenter=0, vmax=vmax)

    cf = ax.contourf(
        wavenumbers_x,
        wavenumbers,
        matrix,
        levels=n_levels,
        cmap=cmap,
        norm=norm,
    )

    ax.contour(
        wavenumbers_x,
        wavenumbers,
        matrix,
        levels=10,
        colors="black",
        linewidths=0.4,
        alpha=0.6,
    )

    if descending:
        ax.set_xlim(wavenumbers_x.max(), wavenumbers_x.min())
        ax.set_ylim(wavenumbers.max(), wavenumbers.min())
    else:
        ax.set_xlim(wavenumbers_x.min(), wavenumbers_x.max())
        ax.set_ylim(wavenumbers.min(), wavenumbers.max())

    ax.set_xlabel("Wavenumber (cm⁻¹)")
    ax.set_ylabel("Wavenumber (cm⁻¹)")
    ax.set_aspect("equal" if wavenumbers is wavenumbers_x else "auto")

    if title:
        ax.set_title(title)

    plt.colorbar(cf, ax=ax, label="Correlation intensity")

    return ax

plot_sync_async(sync, asyn, wavenumbers, *, wavenumbers_x=None, title=None, cmap='vlag', n_levels=50, descending=True, figsize=(12, 5))

Plot synchronous and asynchronous 2DCOS matrices side-by-side.

Convenience wrapper around two plot_contour calls. The asynchronous panel has its diagonal masked automatically. Figure uses constrained layout, so colorbars and titles don't crowd the plot area.

Parameters:

Name Type Description Default
sync ndarray

Synchronous correlation matrix.

required
asyn ndarray

Asynchronous correlation matrix. Must have the same shape as sync.

required
wavenumbers ndarray

Wavenumber axis values for matrix axis 0 (y-axis of both panels).

required
wavenumbers_x ndarray | None

Optional. Wavenumber axis values for matrix axis 1 (x-axis). If None, wavenumbers is used for both axes (homospectral). For heterospectral plots, supply the second wavenumber axis here.

None
title str | None

Optional figure-level title (suptitle).

None
cmap str

Passed through to plot_contour / plt.subplots as expected.

'vlag'
n_levels str

Passed through to plot_contour / plt.subplots as expected.

'vlag'
descending str

Passed through to plot_contour / plt.subplots as expected.

'vlag'
figsize str

Passed through to plot_contour / plt.subplots as expected.

'vlag'

Returns:

Type Description
(fig, axes)

axes is a length-2 numpy array of matplotlib Axes. Unpack as fig, (ax_sync, ax_async) = plot_sync_async(...) to reference each.

Raises:

Type Description
ValueError

If sync and asyn have different shapes.

Source code in src/crosspeak/plot.py
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
def plot_sync_async(
    sync: np.ndarray,
    asyn: np.ndarray,
    wavenumbers: np.ndarray,
    *,
    wavenumbers_x: np.ndarray | None = None,
    title: str | None = None,
    cmap: str = "vlag",
    n_levels: int = 50,
    descending: bool = True,
    figsize: tuple[float, float] = (12, 5),
):
    """Plot synchronous and asynchronous 2DCOS matrices side-by-side.

    Convenience wrapper around two `plot_contour` calls. The asynchronous
    panel has its diagonal masked automatically. Figure uses constrained
    layout, so colorbars and titles don't crowd the plot area.

    Parameters
    ----------
    sync
        Synchronous correlation matrix.
    asyn
        Asynchronous correlation matrix. Must have the same shape as `sync`.
    wavenumbers
        Wavenumber axis values for matrix axis 0 (y-axis of both panels).
    wavenumbers_x
        Optional. Wavenumber axis values for matrix axis 1 (x-axis). If None,
        `wavenumbers` is used for both axes (homospectral). For heterospectral
        plots, supply the second wavenumber axis here.
    title
        Optional figure-level title (suptitle).
    cmap, n_levels, descending, figsize
        Passed through to `plot_contour` / `plt.subplots` as expected.

    Returns
    -------
    (fig, axes)
        `axes` is a length-2 numpy array of matplotlib Axes. Unpack as
        `fig, (ax_sync, ax_async) = plot_sync_async(...)` to reference each.

    Raises
    ------
    ValueError
        If `sync` and `asyn` have different shapes.
    """
    if sync.shape != asyn.shape:
        raise ValueError(
            f"sync and asyn must have the same shape; got {sync.shape} and {asyn.shape}"
        )

    fig, axes = plt.subplots(1, 2, figsize=figsize, layout="constrained")
    plot_contour(
        sync,
        wavenumbers,
        wavenumbers_x=wavenumbers_x,
        ax=axes[0],
        title="Synchronous",
        cmap=cmap,
        n_levels=n_levels,
        descending=descending,
    )
    plot_contour(
        asyn,
        wavenumbers,
        wavenumbers_x=wavenumbers_x,
        ax=axes[1],
        title="Asynchronous",
        cmap=cmap,
        n_levels=n_levels,
        descending=descending,
        mask_diagonal=True,
    )
    if title is not None:
        fig.suptitle(title)
    return fig, axes