reverted some noise changes

This commit is contained in:
Benoît Sierro
2024-01-11 16:25:46 +01:00
parent 7c7e7b7533
commit 9b2907b1cb
2 changed files with 82 additions and 34 deletions

View File

@@ -5,34 +5,54 @@ import scgenerator as sc
def main():
nperseg = 513
nf = 513
nseg = 240
ntot = (nperseg - 1) * (nseg - 1)
ntot = (nf - 1) * (nseg - 1)
fs = 44100
nf = 45611
f = np.linspace(0, fs // 2, nf)
nf_full = 45611
f = np.linspace(0, fs // 2, nf_full)
spec = 1 / (f + 1)
spec += np.exp(-(((f - (0.2 * fs)) / (0.5 * fs)) ** 2))
plt.plot(f, spec)
plt.xscale("log")
plt.yscale("log")
plt.show()
spec += np.exp(-(((f - (0.1 * fs)) / (0.5 * fs)) ** 2))
spec += np.exp(-(((f - (0.45 * fs)) / (0.02 * fs)) ** 2))
# spec = np.ones_like(f) * 1e-5
# spec += np.exp(-(((f - 15000) / 300) ** 2))
noise = sc.noise.NoiseMeasurement(f, spec)
t, y = noise.time_series(nf=nperseg, nseg=nseg)
# tf, yf = noise.time_series(nperseg=(ntot // 2) + 1, nseg=1)
t, y = noise.time_series(nf=nf, nseg=nseg)
tf, yf = noise.time_series(nf=ntot // 2 + 1)
print(y.std(), yf.std())
plt.plot(t, y, ls="", marker=".")
plt.plot(tf, yf, ls="", marker=".")
plt.show()
newnoise = noise.from_time_series(y, t[1] - t[0], nperseg=(nperseg - 1) * 2)
newnoise = noise.from_time_series(y, t[1] - t[0], nf=nf)
newnoise_full = noise.from_time_series(yf, tf[1] - tf[0], nperseg=(nf - 1) * 2)
print(newnoise.psd[1:-1].var())
print(newnoise_full.psd[1:-1].var())
fig, ax = plt.subplots()
ax.plot(*noise.plottable())
ax.plot(*newnoise.plottable())
ax.plot(*newnoise_full.plottable(discard_nyquist=True))
ax.set_xscale("log")
axin = plt.gca().inset_axes(
[0.1, 0.1, 0.3, 0.3],
xlim=(1.5e4, fs / 1.9),
ylim=(-3.1, 2.4),
yticklabels=[],
)
# axin.set_xscale("log")
axin.plot(*newnoise.plottable())
axin.plot(*newnoise_full.plottable())
axin.plot(*noise.plottable())
ax.indicate_inset_zoom(axin)
axin.tick_params(labelbottom=False)
plt.plot(*noise.plottable())
f, sxx = noise.sample_spectrum(nperseg)
plt.plot(f, 10 * np.log10(sxx))
plt.plot(*newnoise.plottable())
plt.xscale("log")
plt.show()

View File

@@ -36,6 +36,7 @@ class NoiseMeasurement:
dt: float = 1.0,
window: str | None = "hann",
nperseg: int | None = None,
nf: int | None = None,
detrend: bool | str = "constant",
) -> NoiseMeasurement:
"""
@@ -53,6 +54,9 @@ class NoiseMeasurement:
number of points per segment. The PSD of each segment is computed and then averaged
to reduce variange. By default None, which means only one segment (i.e. the full signal
at once) is computed.
nf : int, optional
specify the number of frequency points rather than the size of each segments. if both
are specified. Takes precedence over nperseg when both are specified.
detrend : bool, optional
remove DC and optionally linear trend, by default only removes DC. See
scipy.signal.welch for more details.
@@ -62,12 +66,18 @@ class NoiseMeasurement:
raise ValueError(
f"got signal of shape {signal.shape}. Only one 1D signals are supported"
)
if nperseg is None:
if nf is not None:
nperseg = (nf - 1) * 2
elif nperseg is None:
nperseg = len(signal)
if detrend is True:
detrend = "constant"
if window is None:
window = "boxcar"
freq, psd = ss.welch(
signal, fs=1 / dt, window=window, nperseg=nperseg, detrend=detrend, scaling="density"
)
@@ -79,7 +89,8 @@ class NoiseMeasurement:
dB: bool = True,
wavelength: float | None = None,
power: float | None = None,
left: int = 1,
left: int | None = None,
discard_nyquist: bool = False,
) -> tuple[np.ndarray, np.ndarray]:
"""
Transforms the PSD in a way that makes it easy to plot
@@ -92,11 +103,13 @@ class NoiseMeasurement:
if both are provided, will be used to compute the quantum noise limit and the PSD will
be output relative to that amount
left : int, optional
Index of the first frequency point. The default (1) is to drop only the 0 frequency
point, as it is outside the plot range when plotting the PSD on a log-log plot.
right : int, optional
index of the last frequency point, non inclusive (i.e. like slices). The default (-1) is
to drop only the Nyquist frequency, as segmentation artificially reduces its value.
Index of the first frequency point. The default is to drop only the 0 frequency
point if it exits to simplify plotting in log-log scales. choose `left=0` to return
everything
discard_nyquist : bool, optional
crop the psd before the Nyquist frequency, as this is a special point that is not
guaranteed to be present and/or accurate depending on how the signal was generated, by
default False
Returns
-------
@@ -113,13 +126,21 @@ class NoiseMeasurement:
>>> plt.show()
"""
psd = self.psd
freq = self.freq
if wavelength is not None and power is not None:
psd = psd / quantum_noise_limit(wavelength, power)
if dB:
psd = math.to_dB(psd, ref=1.0)
return self.freq[left:], psd[left:]
if freq.size % 2 and discard_nyquist:
freq = freq[:-1]
psd = psd[:-1]
if left is None and freq[0] == 0:
left = 1
return freq[left:], psd[left:]
def sample_spectrum(
self, nf: int, dt: float | None = None, log_mode: bool = False
@@ -168,7 +189,7 @@ class NoiseMeasurement:
def time_series(
self,
nf: int,
nseg: int,
nseg: int = 1,
dt: float | None = None,
log_mode: bool = False,
) -> tuple[np.ndarray, np.ndarray]:
@@ -179,7 +200,7 @@ class NoiseMeasurement:
----------
nf : int
number of frequency points to sample. Recommended is a power of 2 + 1 (129, 513, ...)
nseg : int
nseg : int | None,
number of times to sample the spectrum, each time with a different random phase
dt : float | None, optional
if given, choose a sample spacing of dt instead of 1/f_max
@@ -190,14 +211,21 @@ class NoiseMeasurement:
freq, amp = self.sample_spectrum(nf, dt, log_mode)
fs = freq[-1] * 2
# istft expects a stft, not a spectrogram
amp[1:-1] *= 0.5
amp = np.sqrt(amp)
amp = np.tile(amp, (nseg, 1)).T + 0j
amp[1:-1] *= np.exp(2j * np.pi * self.rng.random((nf - 2, nseg)))
right = -1 if nf % 2 else None
time, signal = ss.istft(amp, fs, nperseg=(nf - 1) * 2, noverlap=nf - 1, scaling="psd")
return time, signal * np.sqrt(2)
amp[1:right] *= 0.5
amp = np.sqrt(amp) + 0j
if nseg > 1:
amp = np.tile(amp, (nseg, 1)).T
amp[1:right] *= np.exp(2j * np.pi * self.rng.random((nf - 2, nseg)))
time, signal = ss.istft(amp, fs, nperseg=(nf - 1) * 2, noverlap=nf - 1, scaling="psd")
signal *= np.sqrt(2)
else:
amp[1:right] *= np.exp(2j * np.pi * self.rng.random(nf - 2))
signal = np.fft.irfft(amp) * np.sqrt((amp.size - 1) * 2 * fs)
time = np.arange(len(signal)) / fs
return time, signal
def root_integrated(self) -> np.ndarray:
"""