noise improvements with (i)stft

This commit is contained in:
Benoît Sierro
2024-01-11 10:43:37 +01:00
parent c3ea042d71
commit 7c7e7b7533
2 changed files with 60 additions and 27 deletions

40
examples/noise_study.py Normal file
View File

@@ -0,0 +1,40 @@
import matplotlib.pyplot as plt
import numpy as np
import scgenerator as sc
def main():
nperseg = 513
nseg = 240
ntot = (nperseg - 1) * (nseg - 1)
fs = 44100
nf = 45611
f = np.linspace(0, fs // 2, nf)
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()
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)
plt.plot(t, y, ls="", marker=".")
plt.show()
newnoise = noise.from_time_series(y, t[1] - t[0], nperseg=(nperseg - 1) * 2)
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()
if __name__ == "__main__":
main()

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable, ClassVar, Sequence
from typing import Sequence
import numpy as np
import scipy.signal as ss
@@ -68,7 +68,9 @@ class NoiseMeasurement:
detrend = "constant"
if window is None:
window = "boxcar"
freq, psd = ss.welch(signal, fs=1 / dt, window=window, nperseg=nperseg, detrend=detrend)
freq, psd = ss.welch(
signal, fs=1 / dt, window=window, nperseg=nperseg, detrend=detrend, scaling="density"
)
return cls(freq, psd)
@@ -78,7 +80,6 @@ class NoiseMeasurement:
wavelength: float | None = None,
power: float | None = None,
left: int = 1,
right: int = -1,
) -> tuple[np.ndarray, np.ndarray]:
"""
Transforms the PSD in a way that makes it easy to plot
@@ -118,7 +119,7 @@ class NoiseMeasurement:
if dB:
psd = math.to_dB(psd, ref=1.0)
return self.freq[left:right], psd[left:right]
return self.freq[left:], psd[left:]
def sample_spectrum(
self, nf: int, dt: float | None = None, log_mode: bool = False
@@ -166,8 +167,8 @@ class NoiseMeasurement:
def time_series(
self,
nt: int | np.ndarray,
phase: np.ndarray | None = None,
nf: int,
nseg: int,
dt: float | None = None,
log_mode: bool = False,
) -> tuple[np.ndarray, np.ndarray]:
@@ -176,35 +177,27 @@ class NoiseMeasurement:
Parameters
----------
nt : int
number of points to sample. must be even.
phase : np.ndarray, shape (nt//2)+1 | None, optional
phase to apply to the amplitude spectrum before taking the inverse Fourier transform.
if None, A random phase is then applied.
nf : int
number of frequency points to sample. Recommended is a power of 2 + 1 (129, 513, ...)
nseg : int
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
log_mode : bool, optional
sample on a log-log scale rather than on a linear scale, by default False
"""
freq, spec = self.sample_spectrum(nt // 2 + 1, dt, log_mode)
spec[1:-1] *= 0.5
if phase is None:
phase = 2 * np.pi * self.rng.random(len(freq) - 2)
elif phase.shape[-1] != len(freq) - 2:
phase = np.interp(freq, self.freq, phase)[1:-1]
time, dt = math.irfftfreq(freq, True)
freq, amp = self.sample_spectrum(nf, dt, log_mode)
fs = freq[-1] * 2
amp = np.asarray(np.sqrt(spec), dtype=complex)
# 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)))
# DC is assumed to be always positive
# Nyquist frequency has an unknowable phase
# amp[-1] *= self.rng.choice((1, -1))
amp[1:-1] *= np.exp(1j * phase)
signal = np.fft.irfft(amp) * np.sqrt(nt / dt)
return time, np.fft.fftshift(signal)
time, signal = ss.istft(amp, fs, nperseg=(nf - 1) * 2, noverlap=nf - 1, scaling="psd")
return time, signal * np.sqrt(2)
def root_integrated(self) -> np.ndarray:
"""