feature.spectral_centroid / bandwidth / rolloff / contrast / poly_features upcast float32 spectrogram to float64
Description
librosa.feature.spectral_centroid, spectral_bandwidth, spectral_rolloff,
spectral_contrast and poly_features upcast a float32 magnitude spectrogram
to float64, while the rest of librosa.feature (melspectrogram, mfcc,
chroma_stft, spectral_flatness, rms) and the core (stft, which has an
explicit dtype-preserving dtype= argument) preserve float32.
The cause is the frequency grid: each of these five functions does
if freq is None:
freq = fft_frequencies(sr=sr, n_fft=n_fft) # always float64and then multiplies/subtracts it with the spectrogram (freq * S,
np.abs(freq - centroid), ind * freq, …), which promotes the whole result to
float64.
Steps/Code to Reproduce
import numpy as np, librosa
y = np.random.default_rng(0).standard_normal(22050).astype(np.float32)
S = np.abs(librosa.stft(y, n_fft=1024)).astype(np.float32) # float32
for name in ["melspectrogram", "mfcc", "spectral_flatness",
"spectral_centroid", "spectral_bandwidth",
"spectral_rolloff", "spectral_contrast", "poly_features"]:
fn = getattr(librosa.feature, name)
kw = {"S": S**2, "sr": 22050} if name == "melspectrogram" else {"S": S}
print(f"{name:20s} -> {fn(**kw).dtype}")melspectrogram -> float32
mfcc -> float32
spectral_flatness -> float32
spectral_centroid -> float64 # <- upcast
spectral_bandwidth -> float64 # <- upcast
spectral_rolloff -> float64 # <- upcast
spectral_contrast -> float64 # <- upcast
poly_features -> float64 # <- upcastExpected Results
float32 in → float32 out, consistent with the other librosa.feature
functions and with stft.
Actual Results
float64 out. In a float32 pipeline this doubles the memory of these feature
arrays and breaks a downstream assert x.dtype == np.float32 or a
float32-only model input, silently.
Versions
librosa 0.11.0, numpy 2.4.6 (also present on main @ f808bac,
librosa/feature/spectral.py lines ~182 / 340 / 474 / 670 / 1045).
Suggested fix
Cast the internally-generated grid to the spectrogram dtype right after
fft_frequencies, only when librosa generated it (a user-supplied freq is
respected as-is):
if freq is None:
freq = fft_frequencies(sr=sr, n_fft=n_fft).astype(S.dtype)in each of the five functions. Happy to open the PR with a
tests/test_features.py case (parametrised float32 / float64, assert
out.dtype == S.dtype) if this direction looks right.
Source: librosa/librosa