Kurtogram — Control Systems/Time Frequency
Control_Systems/Time_Frequency/Kurtogram · 1 input / 4 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Kurtogram
Control Systems / Time Frequency
The spectral kurtosis of every band of a filter-bank tree over the last L
samples – MATLAB's kurtogram. Each band's coefficients y give
k = mean(|y|⁴) ÷ mean(|y|²)² − 2
which is near zero for a stationary Gaussian band and large for an impulsive one, so the largest cell of the map points at the band where a bearing or gear fault stands out best. The block also reports that band's centre frequency, bandwidth and window length – the band-pass an envelope analysis would use.
Ports
- u – the sampled signal, scalar: one channel and its own frame.
- K – the kurtogram, [2·level, 3·2level] (and [1, 3] at level 0). Row 1 is the whole frame; row 2·l holds the 2l bands of level l and row 2·l+1 the 3·2l−1 thirds grown from the level above; every band fills the columns it covers, so a row reads left to right as frequency from 0 to fs/2.
- fc – [1, 1], the centre frequency in hertz of the band with the largest kurtosis.
- BW – [1, 1], that band's bandwidth in hertz.
- wc – [1, 1], the window length it corresponds to
(2level+1 samples), which is MATLAB's
wc.
Parameters
- Frame Length – L, the samples the tree is built from: a whole number from 16 to 1024. The three HDL targets hold one register per sample, so a long frame is a large generated core.
- Hop Size – the samples from one kurtogram to the next, 1 to L. Between them the four outputs hold.
- Level – the depth of the tree, 0 to 4. MATLAB's own ceiling applies: a level needs 64·2level samples in the frame (level 1 needs 128, level 2 needs 256), and a deeper level than the frame allows is refused with that number rather than quietly reduced. Level 0 is the undecomposed frame: a 1×3 map holding its kurtosis.
- Sample Rate (Hz) – fs, read only by fc and
BW (the map does not depend on it). MATLAB's
kurtogram(x)with no rate is this block at 2π, which puts fc in radians per sample. - Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Frames and timing
The frame is the last L samples, oldest first. The first is taken when the register fills – at sample L − 1, counting from 0 – and then every Hop Size samples; between frames the outputs hold, and before the first one they are all zero.
Code export
All ten targets: Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog and PLC Structured Text. The filter bank is fixed by the configuration and inlined as numbers, so no generated core computes a sine or a cosine; the tree is emitted as loops, so the generated file does not grow with the frame length.
The three HDL targets carry the whole tree in real arithmetic
and are therefore simulation-only: a kurtosis is a fourth moment divided by
the square of a second one, which leaves Q16.16 in both directions. The window
itself is held in the port's fixed-point format, so quantization happens only at
the port boundary.
⚠ The band with the largest kurtosis is a DISCRETE answer, so fc, BW and wc jump when two bands change places. On a frame where the two largest cells are within a quantum of each other, a fixed-point input can pick the other band and those three outputs then differ by a whole band. That is arithmetic, not a code-generation defect: the map itself still agrees.
Simulink bridge
None (Support::None). kurtogram is a Signal
Processing Toolbox function and that toolbox ships no Simulink library, so
there is no path a diagram could name. The bridge reports this block rather than
dropping it silently, and it therefore has no parity testbench; code export
verification still covers it across all ten languages. Sampling Time (s) →
SampleTime, as on every block, is not used here for the same reason.
Notes
- Stateful, and discrete by nature: a register of L samples, a frame counter and the held answer.
- Every loop is bounded by the configuration – the tree has 2level+1 nodes and each is one filter pass – so the worst-case cost of a frame is fixed, which is what makes the block exportable at all.
- Verified against R2026a: 52 frames at levels 0 to 3, worst 9.6e-14 on the map and exact on fc, wc and BW (the source banner carries what was checked).
- What it is not. Spectral Kurtosis in Spectral Measurements computes the kurtosis per FFT BIN over several short-time frames; this block computes it per BAND of a filter-bank tree over one frame, which is what makes it a map over both frequency and window length.
- No state space. A fourth moment is not linear in the frame, so model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Time_Frequency/Kurtogram |
| family | Control_Systems/Time_Frequency |
| solver environment class | ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Kurtogram |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Kurtogram/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Kurtogram.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Kurtogram/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Kurtogram.h |
| default size on canvas | 150 × 100 px |
| ports at insert | 1 in, 4 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | u |
| 2 | out | ICoreDouble | K |
| 3 | out | ICoreDouble | fc |
| 4 | out | ICoreDouble | BW |
| 5 | out | ICoreDouble | wc |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Frame Length | 128 | — |
Hop Size | 64 | — |
Level | 1 | — |
Sample Rate (Hz) | 1 | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
Caveat (shown to the user): kurtogram is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library at all -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Kurtogram -- the spectral kurtosis of every band of a filter-bank tree, MATLAB's kurtogram The last L samples are split by a tree of complex quadrature filters: every level halves the band (and, one level short of the deepest, also thirds it), and each band's SPECTRAL KURTOSIS
k = mean(|y|^4) / mean(|y|^2)^2 - 2
is one cell of the map. A stationary Gaussian band reads near zero and an impulsive one reads large, so the band with the largest value is where a fault stands out best against the rest of the signal -- which is what the other three outputs report: its centre frequency, its bandwidth and the window length that resolves it.
MEASURED AGAINST R2026a BEFORE A LINE WAS WRITTEN, and what was checked is the whole map and not just its shape: over 52 frames (lengths 100 to 512, levels 0 to 3, impulsive signals centred on seven different bands plus noise) this block's reference run reproduces kurtogram(x, fs, level) to a worst 9.6e-14 on the map, and fc, wc and BW EXACTLY. The facts that a wrong implementation would get wrong, each one measured:
- the prototype filters are fir1(16, 0.4) and fir1(24, 2/3*0.4), modulated by
exp(1i*pi*k/4) and exp(3i*pi*k/4) for the binary pair, exp(1i*pi*k/6), 3/6 and 5/6 for the ternary triple;
- the high-pass binary child is multiplied by (-1)^n with n counted from ONE, so its first
coefficient changes sign -- and that sign reaches every level below it;
- a node's kurtosis drops the first 16 coefficients (24 for a ternary node), which is the
filter's own transient, and the ROOT has one more subtracted than the others because it is real rather than analytic;
- the maximum is taken with a STRICT >, in MATLAB's depth-first order, so a tie keeps the
earlier node; and
- MATLAB's own ceiling on the depth is floor(log2(L) - 6) -- 64 points per band -- which is
why a level needs a frame of 64*2^level samples.
Support::None: kurtogram is a Signal Processing Toolbox FUNCTION and that toolbox ships no Simulink library, so there is no counterpart to bridge to or run a parity testbench against.
Sample results#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 [2x6] entry 0 | out ICoreDouble-Out-1 | out ICoreDouble-Out-2 |
|---|---|---|---|---|
| 0 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 0.4 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
| 0.8 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 1.2 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
| 1.6 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 2 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
| 2.4 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 2.8 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
| 3.2 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 3.6 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
| 4 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 4.4 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
| 4.8 | -2 | [0, 0, 0, 0]… | 0 | 0 |
| 5.2 | 0.5 | [0, 0, 0, 0]… | 0 | 0 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 0 |
ramp | Ramp: slope 1 from t = 0 | 0 … 0 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 0 |
step | Step: 0 -> 1 at t = 1 s | 0 … 0 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit 23d8841c6561ca4bb64cd9b5b64da9638de12629 · produced by docsSample --out <folder> --blocks Fixed_Wing_Point_Mass Kernel_Classifier_Predictor Kernel_Regression_Predictor Rotor Rotor_With_Flap_Effects Multirotor Multirotor_With_Flap_Effects Dynamic_Inflow_3_State Kurtogram Empirical_Mode_Decomposition Modal_FRF Order_Spectrum --steps 60 · data docs/generated/samples/Control_Systems__Time_Frequency__Kurtogram.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).