plotpress 0.23.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plotpress/__init__.py +108 -0
- plotpress/_interactive.py +2849 -0
- plotpress/_spectral.py +154 -0
- plotpress/_version.py +1 -0
- plotpress/artists.py +1382 -0
- plotpress/axes.py +3221 -0
- plotpress/colors.py +498 -0
- plotpress/figure.py +3084 -0
- plotpress/fonts/__init__.py +51 -0
- plotpress/fonts/families.py +192 -0
- plotpress/fonts/installed.py +82 -0
- plotpress/fonts/metrics.py +265 -0
- plotpress/png.py +93 -0
- plotpress/polar.py +240 -0
- plotpress/primitives.py +335 -0
- plotpress/qt.py +427 -0
- plotpress/raster.py +1316 -0
- plotpress/style.py +91 -0
- plotpress/svg.py +2589 -0
- plotpress/ticker.py +212 -0
- plotpress/transform.py +85 -0
- plotpress/vega.py +1324 -0
- plotpress/vega_lite.py +1199 -0
- plotpress-0.23.2.dist-info/METADATA +378 -0
- plotpress-0.23.2.dist-info/RECORD +28 -0
- plotpress-0.23.2.dist-info/WHEEL +5 -0
- plotpress-0.23.2.dist-info/licenses/LICENSE +21 -0
- plotpress-0.23.2.dist-info/top_level.txt +1 -0
plotpress/_spectral.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Spectral estimators for the signal-processing plot methods.
|
|
2
|
+
|
|
3
|
+
Pure-NumPy Welch-averaged power / cross spectral density, coherence, single-shot
|
|
4
|
+
spectra, and lagged correlation. The conventions (segmenting, Hann window,
|
|
5
|
+
mean detrend, one-sided scaling) follow matplotlib's ``mlab`` closely enough
|
|
6
|
+
that the resulting plots line up -- without pulling in SciPy.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _as_segments(x, NFFT, noverlap):
|
|
15
|
+
"""Split ``x`` into overlapping length-``NFFT`` rows, zero-padded if short."""
|
|
16
|
+
x = np.asarray(x, float).ravel()
|
|
17
|
+
if x.size < NFFT:
|
|
18
|
+
x = np.concatenate([x, np.zeros(NFFT - x.size)])
|
|
19
|
+
step = NFFT - noverlap
|
|
20
|
+
if step <= 0:
|
|
21
|
+
raise ValueError("noverlap must be less than NFFT")
|
|
22
|
+
n_seg = 1 + (x.size - NFFT) // step
|
|
23
|
+
idx = np.arange(NFFT)[None, :] + step * np.arange(n_seg)[:, None]
|
|
24
|
+
return x[idx], step # (n_seg, NFFT)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _windowed_fft(x, NFFT, Fs, noverlap, window, detrend):
|
|
28
|
+
"""Detrended, windowed rFFT of every segment. Returns (Z, freqs, win, step)."""
|
|
29
|
+
segs, step = _as_segments(x, NFFT, noverlap)
|
|
30
|
+
if detrend:
|
|
31
|
+
segs = segs - segs.mean(axis=1, keepdims=True)
|
|
32
|
+
win = window(NFFT) if callable(window) else np.asarray(window, float)
|
|
33
|
+
Z = np.fft.rfft(segs * win, n=NFFT, axis=1)
|
|
34
|
+
freqs = np.fft.rfftfreq(NFFT, d=1.0 / Fs)
|
|
35
|
+
return Z, freqs, win, step
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _onesided_double(P, NFFT):
|
|
39
|
+
"""Double the non-DC / non-Nyquist bins for a one-sided spectrum (in place)."""
|
|
40
|
+
if NFFT % 2 == 0:
|
|
41
|
+
P[..., 1:-1] *= 2.0
|
|
42
|
+
else:
|
|
43
|
+
P[..., 1:] *= 2.0
|
|
44
|
+
return P
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def psd(x, NFFT, Fs, noverlap, window, detrend):
|
|
48
|
+
"""One-sided power spectral density by Welch averaging."""
|
|
49
|
+
Z, freqs, win, _ = _windowed_fft(x, NFFT, Fs, noverlap, window, detrend)
|
|
50
|
+
scale = 1.0 / (Fs * (win ** 2).sum())
|
|
51
|
+
Pxx = _onesided_double(np.abs(Z) ** 2 * scale, NFFT).mean(axis=0)
|
|
52
|
+
return Pxx, freqs
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def csd(x, y, NFFT, Fs, noverlap, window, detrend):
|
|
56
|
+
"""One-sided cross spectral density ``Pxy`` (complex)."""
|
|
57
|
+
Zx, freqs, win, _ = _windowed_fft(x, NFFT, Fs, noverlap, window, detrend)
|
|
58
|
+
Zy, _, _, _ = _windowed_fft(y, NFFT, Fs, noverlap, window, detrend)
|
|
59
|
+
scale = 1.0 / (Fs * (win ** 2).sum())
|
|
60
|
+
Pxy = _onesided_double(Zx * np.conj(Zy) * scale, NFFT).mean(axis=0)
|
|
61
|
+
return Pxy, freqs
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cohere(x, y, NFFT, Fs, noverlap, window, detrend):
|
|
65
|
+
"""Magnitude-squared coherence ``|Pxy|^2 / (Pxx Pyy)`` in ``[0, 1]``.
|
|
66
|
+
|
|
67
|
+
Coherence is only meaningful when the estimate averages several segments;
|
|
68
|
+
a single segment makes it identically 1 (as in matplotlib).
|
|
69
|
+
"""
|
|
70
|
+
Zx, freqs, win, _ = _windowed_fft(x, NFFT, Fs, noverlap, window, detrend)
|
|
71
|
+
Zy, _, _, _ = _windowed_fft(y, NFFT, Fs, noverlap, window, detrend)
|
|
72
|
+
scale = 1.0 / (Fs * (win ** 2).sum())
|
|
73
|
+
Pxx = _onesided_double(np.abs(Zx) ** 2 * scale, NFFT).mean(axis=0)
|
|
74
|
+
Pyy = _onesided_double(np.abs(Zy) ** 2 * scale, NFFT).mean(axis=0)
|
|
75
|
+
Pxy = _onesided_double(Zx * np.conj(Zy) * scale, NFFT).mean(axis=0)
|
|
76
|
+
# An empty/all-zero x and y (zero-padded by _as_segments rather than
|
|
77
|
+
# rejected -- a real segment length is still needed either way) makes
|
|
78
|
+
# Pxx == Pyy == 0 everywhere: 0/0, mathematically undefined coherence,
|
|
79
|
+
# not a bug -- silence the resulting "invalid value" noise rather than
|
|
80
|
+
# rejecting a case NFFT-segmenting already treats as legitimate.
|
|
81
|
+
with np.errstate(invalid="ignore"):
|
|
82
|
+
Cxy = np.abs(Pxy) ** 2 / (Pxx * Pyy)
|
|
83
|
+
return Cxy, freqs
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def specgram(x, NFFT, Fs, noverlap, window, detrend):
|
|
87
|
+
"""Spectrogram: one-sided power per segment. Returns (P, freqs, t).
|
|
88
|
+
|
|
89
|
+
``P`` has shape ``(n_freqs, n_segments)`` -- ready for ``imshow``.
|
|
90
|
+
"""
|
|
91
|
+
Z, freqs, win, step = _windowed_fft(x, NFFT, Fs, noverlap, window, detrend)
|
|
92
|
+
scale = 1.0 / (Fs * (win ** 2).sum())
|
|
93
|
+
P = _onesided_double(np.abs(Z) ** 2 * scale, NFFT) # (n_seg, n_freq)
|
|
94
|
+
t = (np.arange(P.shape[0]) * step + NFFT / 2.0) / Fs
|
|
95
|
+
return P.T, freqs, t
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _single_spectrum(x, Fs, window, detrend):
|
|
99
|
+
"""Windowed rFFT of the whole signal (no segmenting)."""
|
|
100
|
+
x = np.asarray(x, float).ravel()
|
|
101
|
+
if x.size == 0:
|
|
102
|
+
# x.mean() on an empty array below leaks a raw "Mean of empty
|
|
103
|
+
# slice" RuntimeWarning before np.fft eventually raises its own
|
|
104
|
+
# clear error a few lines further on -- raise that same "nothing
|
|
105
|
+
# to do" error here instead, without the noise in front of it.
|
|
106
|
+
raise ValueError("magnitude_spectrum()/angle_spectrum()/phase_spectrum(): x must not be empty")
|
|
107
|
+
if detrend:
|
|
108
|
+
x = x - x.mean()
|
|
109
|
+
n = x.size
|
|
110
|
+
win = window(n) if callable(window) else np.asarray(window, float)
|
|
111
|
+
Z = np.fft.rfft(x * win)
|
|
112
|
+
freqs = np.fft.rfftfreq(n, d=1.0 / Fs)
|
|
113
|
+
return Z, freqs, win, n
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def magnitude_spectrum(x, Fs, window, detrend):
|
|
117
|
+
"""One-sided magnitude spectrum ``|X(f)|``."""
|
|
118
|
+
Z, freqs, win, n = _single_spectrum(x, Fs, window, detrend)
|
|
119
|
+
mag = _onesided_double(np.abs(Z) / win.sum(), n)
|
|
120
|
+
return mag, freqs
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def angle_spectrum(x, Fs, window, detrend):
|
|
124
|
+
"""Wrapped phase spectrum in radians (``-pi..pi``)."""
|
|
125
|
+
Z, freqs, _, _ = _single_spectrum(x, Fs, window, detrend)
|
|
126
|
+
return np.angle(Z), freqs
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def phase_spectrum(x, Fs, window, detrend):
|
|
130
|
+
"""Unwrapped phase spectrum in radians."""
|
|
131
|
+
Z, freqs, _, _ = _single_spectrum(x, Fs, window, detrend)
|
|
132
|
+
return np.unwrap(np.angle(Z)), freqs
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def correlation(x, y, detrend, normed, maxlags):
|
|
136
|
+
"""Lagged cross-correlation. Returns ``(lags, c)`` over ``+-maxlags``."""
|
|
137
|
+
x = np.asarray(x, float).ravel()
|
|
138
|
+
y = np.asarray(y, float).ravel()
|
|
139
|
+
n = x.size
|
|
140
|
+
if y.size != n:
|
|
141
|
+
raise ValueError("x and y must be the same length")
|
|
142
|
+
if detrend:
|
|
143
|
+
x = x - x.mean()
|
|
144
|
+
y = y - y.mean()
|
|
145
|
+
c = np.correlate(x, y, mode="full")
|
|
146
|
+
if normed:
|
|
147
|
+
c = c / (np.sqrt(np.dot(x, x) * np.dot(y, y)) or 1.0)
|
|
148
|
+
if maxlags is None:
|
|
149
|
+
maxlags = n - 1
|
|
150
|
+
if not 0 <= maxlags < n:
|
|
151
|
+
raise ValueError("maxlags must be in 0..len(x)-1")
|
|
152
|
+
lags = np.arange(-maxlags, maxlags + 1)
|
|
153
|
+
c = c[n - 1 - maxlags:n + maxlags]
|
|
154
|
+
return lags, c
|
plotpress/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.23.2"
|