diff --git a/librosa/decompose.py b/librosa/decompose.py index 4c1c631bc9..20e5ae1c9e 100644 --- a/librosa/decompose.py +++ b/librosa/decompose.py @@ -223,15 +223,18 @@ def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0): of the percussive filter. power : float > 0 [scalar] - Exponent for the Wiener filter when constructing mask matrices. - - Mask matrices are defined by - `mask_H = (r_H ** power) / (r_H ** power + r_P ** power)` - where `r_H` and `r_P` are the median-filter responses for - harmonic and percussive components. + Exponent for the Wiener filter when constructing soft mask matrices. mask : bool - Return the (binary) masking matrices instead of components + Return the masking matrices instead of components. + + Masking matrices contain non-negative real values that + can be used to measure the assignment of energy from `S` + into harmonic or percussive components. + + Components can be recovered by multiplying `S * mask_H` + or `S * mask_P`. + margin : float or tuple (margin_harmonic, margin_percussive) margin size(s) for the masks (as described in [2]_) @@ -294,18 +297,17 @@ def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0): >>> mask_H, mask_P = librosa.decompose.hpss(D, mask=True) >>> mask_H - array([[ 1., 0., ..., 0., 0.], - [ 1., 0., ..., 0., 0.], + array([[ 1.000e+00, 1.469e-01, ..., 2.648e-03, 2.164e-03], + [ 1.000e+00, 2.368e-01, ..., 9.413e-03, 7.703e-03], ..., - [ 0., 0., ..., 0., 0.], - [ 0., 0., ..., 0., 0.]]) + [ 8.869e-01, 5.673e-02, ..., 4.603e-02, 1.247e-05], + [ 7.068e-01, 2.194e-02, ..., 4.453e-02, 1.205e-05]], dtype=float32) >>> mask_P - array([[ 0., 1., ..., 1., 1.], - [ 0., 1., ..., 1., 1.], + array([[ 2.858e-05, 8.531e-01, ..., 9.974e-01, 9.978e-01], + [ 1.586e-05, 7.632e-01, ..., 9.906e-01, 9.923e-01], ..., - [ 1., 1., ..., 1., 1.], - [ 1., 1., ..., 1., 1.]]) - + [ 1.131e-01, 9.433e-01, ..., 9.540e-01, 1.000e+00], + [ 2.932e-01, 9.781e-01, ..., 9.555e-01, 1.000e+00]], dtype=float32) Separate into harmonic/percussive/residual components by using a margin > 1.0 @@ -343,7 +345,8 @@ def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0): # margin minimum is 1.0 if margin_harm < 1 or margin_perc < 1: - raise ParameterError("Margins must be >= 1.0. A typical range is between 1 and 10.") + raise ParameterError("Margins must be >= 1.0. " + "A typical range is between 1 and 10.") # Compute median filters. Pre-allocation here preserves memory layout. harm = np.empty_like(S) @@ -363,15 +366,7 @@ def hpss(S, kernel_size=31, power=2.0, mask=False, margin=1.0): split_zeros=split_zeros) if mask: - # If the margins are tight, H + P should sum to 1, so we use >=. - # Otherwise, comparisons should be strict on both sides. - if margin_harm == 1 and margin_perc == 1: - compare = np.greater_equal - else: - compare = np.greater - - return ((mask_harm > mask_perc).astype(float), - compare(mask_perc, mask_harm).astype(float)) + return mask_harm, mask_perc return ((S * mask_harm) * phase, (S * mask_perc) * phase) diff --git a/tests/test_decompose.py b/tests/test_decompose.py index 4cbfd4e6d3..f8e288f4bf 100644 --- a/tests/test_decompose.py +++ b/tests/test_decompose.py @@ -79,7 +79,8 @@ def test_real_hpss(): D = np.abs(librosa.stft(y)) def __hpss_test(window, power, mask, margin): - H, P = librosa.decompose.hpss(D, kernel_size=window, power=power, mask=mask, margin=margin) + H, P = librosa.decompose.hpss(D, kernel_size=window, power=power, + mask=mask, margin=margin) if margin == 1.0 or margin == (1.0, 1.0): if mask: @@ -87,8 +88,8 @@ def __hpss_test(window, power, mask, margin): else: assert np.allclose(H + P, D) else: - if mask: - assert not np.any(H.astype(bool) & P.astype(bool)) + if mask: + assert np.all(H + P <= np.ones_like(D)) else: assert np.all(H + P <= D)