spectral_kurtosis — ACOUSTICS bearing op

• 데이터 종류: signal → table

• 호출: import fullseye as fs; fs.ledger.spectral_kurtosis(x, rate, win=None, hop=None, window='hann')(구현을 직접 호출하려면 import acoustics; acoustics.spectral_kurtosis(x, rate, win=None, hop=None, window='hann'), 원장에서 가져오려면 opsacoustics.get("spectral_kurtosis"))

사용법

어느 대역이 충격적인가 —— 즉 어디서 복조할 것인가.

> 아래 상세 설명은 원문입니다 —— 요약과 제목은 번역되어 있습니다.

envelope_spectrum needs a band, and picking it by eye from a spectrum picks the *loudest* band, which is usually a gear mesh or a line harmonic rather than the bearing. Spectral kurtosis picks the *most non-stationary* band instead: for each frequency bin it measures the fourth-order behaviour of that bin's STFT coefficient across frames.

The normalisation is chosen so the two reference cases are exact:

• stationary complex circular Gaussian noise gives SK = 0,

• a pure tone (constant magnitude in its bin) gives SK = -1,

• a repetitive transient gives SK > 0, and the larger it is the more concentrated in time the band's content is.

Measured over 8192 samples at 16 kHz at the default window (64, 509 interior

frames): white Gaussian noise gives a mean SK of -0.0444 over the interior bins, against the estimator's own standard deviation 4/sqrt(509) = 0.1773, so it is zero; and a 2 kHz tone gives -1.0000 in its bin. Both reference cases land on their closed forms.

The answer is a band, and it depends on the window — measured, not asserted. The frame has to be *shorter than the gap between transients*, or every frame contains one and the band looks perfectly stationary. On the mode="impulse" bearing signal (25.6 kHz, resonance 3000 Hz, impulses every 9.35 ms, ring time constant 1.06 ms):

====== ========== ======= ========== =============

win frame (ms) max SK at (Hz) bin spacing

====== ========== ======= ========== =============

16 0.62 29.58 6400 1600 Hz

32 1.25 12.86 1600 800 Hz

64 2.50 5.38 2000 400 Hz

128 5.00 1.66 1600 200 Hz

256 10.00 -0.13 12200 100 Hz

====== ========== ======= ========== =============

The last row is the failure mode: at a 10 ms frame against a 9.35 ms impulse spacing, every frame holds exactly one impulse, the band is stationary by construction, and the operator reports a *negative* kurtosis at an unrelated frequency. Nothing raises. So window_seconds is returned, to be compared against the repetition period you expect, and sweeping win is part of using this operator rather than an optimisation.

What survives the sweep is the *band*, not the bin. On the same signal with noise_sigma=0.05 (the noiseless one is impulsive in every bin at once and its top six bins differ by 0.03, which is itself worth knowing), the six highest bins at win=64 are 2000, 2400, 1600, 4000, 3600 and 1200 Hz — bracketing the true 3000 Hz resonance without any of them being it. And that is enough: feeding the band this operator returns straight into envelope_spectrum recovers the defect rate exactly — measured 107.0000 Hz from the 1600-2400 Hz band the operator chose by itself, with no knowledge of the resonance.

The band is returned, not left to the caller to assemble. band_lo / band_hi are max_freq -+ bin_hz *clamped into the open interval* (0, rate/2) that envelope_spectrum accepts, so envelope_spectrum(x, rate, sk["band_lo"], sk["band_hi"]) is always a legal call. Assembling the band by hand is not: freqs runs up to and including Nyquist, so whenever the winning bin is the topmost interior one, max_freq + bin_hz lands exactly *on* Nyquist and envelope_spectrum refuses it — correctly, since no such band exists in the recording. Measured on the mode="am" bearing signal (25600 Hz, 1 s, 3 kHz carrier, 107 Hz defect, m = 0.5), whose kurtosis maximum lands on the top interior bin (12400 Hz, bin_hz 400):

=========================== ===================== ==============================

band handed to the consumer value envelope_spectrum

=========================== ===================== ==============================

max_freq -+ bin_hz 12000.0 - 12800.0 Hz ValueError (12800 = Nyquist)

band_lo / band_hi 12000.0 - 12600.0 Hz returns

=========================== ===================== ==============================

Returning is not the same as finding something, and this row is the honest

case: max_kurtosis is -0.2725 against noise_sigma 0.1001, so there was never a band to find — an amplitude-modulated tone is stationary in every bin, which is exactly what a negative SK says. Demodulating the band anyway gives peak_freq 1.0000 Hz at band_fraction

5.080e-05: nothing lives up there, and the returned diagnostics say so. (The same signal over the known resonance, 2000-4000 Hz, gives peak_freq 107.0000 at band_fraction 0.9999.) The contract this repair adds is only that the handoff is *legal* — refusing to answer a well-posed call is the caller's bug to hit, whereas judging the answer stays with max_kurtosis / noise_sigma / band_fraction.

The clamp margin is half a bin: an edge cannot be placed more finely than bin_hz in the first place, and half a bin is the smallest offset that is still a resolvable distance from the boundary — no epsilon, no rate-dependent fudge. band_lo < band_hi always holds, because max_freq is by construction an interior bin and therefore at least one full bin away from both 0 and Nyquist.

win defaults to the largest power of two that leaves at least 8 interior frames, clamped to [16, 64] — short, for the reason in the table — and the value used is returned. Fewer than 8 interior frames makes the fourth moment meaningless and is refused.

DC and Nyquist bins are excluded from max_kurtosis / max_freq: their STFT coefficients are real, not complex circular, so the -2 normalisation is the wrong one there and they read about -1 for noise. They are still present in kurtosis with the same formula, and real_bins names them.

Returns a dict: freqs, kurtosis, max_kurtosis, max_freq, band_lo, band_hi (the demodulation band, ready for envelope_spectrum), n_frames, win, hop, real_bins, window_seconds, bin_hz, noise_sigma (the estimator's own standard deviation, 4/sqrt(n_frames) — a peak below this is not a finding).

Raises ValueError: everything stft refuses, plus a signal too short for 8 frames at the chosen window.

자세한 사용 가이드

• acoustic_condition_monitoring 패밀리 가이드

참고(샘플 데이터·문헌)

• 샘플 데이터 카탈로그(DL URL / 라이선스) —— 2-D 는 skimage.data(BSD/public)+ 합성, 3-D 는 실데이터 소스(Stanford/PDS 등)의 DL URL.

• 연산자의 내력·참고문헌 —— 이 연산자 족의 바탕이 된 연구/기법의 출처.

• 알고리즘의 정전(저자·연도)과 용도는 위의 패밀리 사용 가이드에 적혀 있습니다.

실행 가능한 예제(이 연산자를 실제로 호출하는 검증된 샘플)

• acoustic_condition_monitoring — py -3.11 examples/acoustic_condition_monitoring.py

• poc_bearing_diagnosis — py -3.11 examples/poc_bearing_diagnosis.py

타입이 이어지는 다음 연산자(table 를 입력으로 받는 것)

istft

같은 카테고리(bearing)

envelope_spectrum · bearing_defect_frequencies · cepstrum


*Provenance: acoustics.py — ACOUSTICS 연산자 레지스트리. 이 op 노트는 tools/opdocs.py md 가 자동 생성합니다(직접 편집하지 마세요).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.