Prompt48 commited on
Commit
f40a973
·
verified ·
1 Parent(s): e6d39ba

Upload edit\Qwen3-TTS-test\.venv\Lib\site-packages\librosa\segment.py with huggingface_hub

Browse files
edit//Qwen3-TTS-test//.venv//Lib//site-packages//librosa//segment.py ADDED
@@ -0,0 +1,1414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Temporal segmentation
5
+ =====================
6
+
7
+ Recurrence and self-similarity
8
+ ------------------------------
9
+ .. autosummary::
10
+ :toctree: generated/
11
+
12
+ cross_similarity
13
+ recurrence_matrix
14
+ recurrence_to_lag
15
+ lag_to_recurrence
16
+ timelag_filter
17
+ path_enhance
18
+
19
+ Temporal clustering
20
+ -------------------
21
+ .. autosummary::
22
+ :toctree: generated/
23
+
24
+ agglomerative
25
+ subsegment
26
+ """
27
+
28
+ from decorator import decorator
29
+
30
+ import numpy as np
31
+ import scipy
32
+ import scipy.signal
33
+ import scipy.ndimage
34
+
35
+ import sklearn
36
+ import sklearn.cluster
37
+ import sklearn.feature_extraction
38
+ import sklearn.neighbors
39
+
40
+ from ._cache import cache
41
+ from . import util
42
+ from .filters import diagonal_filter
43
+ from .util.exceptions import ParameterError
44
+ from typing import Any, Callable, Optional, TypeVar, Union, overload
45
+ from typing_extensions import Literal
46
+ from ._typing import _WindowSpec, _FloatLike_co
47
+
48
+ __all__ = [
49
+ "cross_similarity",
50
+ "recurrence_matrix",
51
+ "recurrence_to_lag",
52
+ "lag_to_recurrence",
53
+ "timelag_filter",
54
+ "agglomerative",
55
+ "subsegment",
56
+ "path_enhance",
57
+ ]
58
+
59
+
60
+ @overload
61
+ def cross_similarity(
62
+ data: np.ndarray,
63
+ data_ref: np.ndarray,
64
+ *,
65
+ k: Optional[int] = ...,
66
+ metric: str = ...,
67
+ sparse: Literal[False] = ...,
68
+ mode: str = ...,
69
+ bandwidth: Optional[Union[np.ndarray, _FloatLike_co, str]] = None,
70
+ full: bool = False,
71
+ ) -> np.ndarray:
72
+ ...
73
+
74
+
75
+ @overload
76
+ def cross_similarity(
77
+ data: np.ndarray,
78
+ data_ref: np.ndarray,
79
+ *,
80
+ k: Optional[int] = ...,
81
+ metric: str = ...,
82
+ sparse: Literal[True] = ...,
83
+ mode: str = ...,
84
+ bandwidth: Optional[Union[np.ndarray, _FloatLike_co, str]] = None,
85
+ full: bool = False,
86
+ ) -> scipy.sparse.csc_matrix:
87
+ ...
88
+
89
+
90
+ @cache(level=30)
91
+ def cross_similarity(
92
+ data: np.ndarray,
93
+ data_ref: np.ndarray,
94
+ *,
95
+ k: Optional[int] = None,
96
+ metric: str = "euclidean",
97
+ sparse: bool = False,
98
+ mode: str = "connectivity",
99
+ bandwidth: Optional[Union[np.ndarray, _FloatLike_co, str]] = None,
100
+ full: bool = False,
101
+ ) -> Union[np.ndarray, scipy.sparse.csc_matrix]:
102
+ """Compute cross-similarity from one data sequence to a reference sequence.
103
+
104
+ The output is a matrix ``xsim``, where ``xsim[i, j]`` is non-zero
105
+ if ``data_ref[..., i]`` is a k-nearest neighbor of ``data[..., j]``.
106
+
107
+ Parameters
108
+ ----------
109
+ data : np.ndarray [shape=(..., d, n)]
110
+ A feature matrix for the comparison sequence.
111
+ If the data has more than two dimensions (e.g., for multi-channel inputs),
112
+ the leading dimensions are flattened prior to comparison.
113
+ For example, a stereo input with shape `(2, d, n)` is
114
+ automatically reshaped to `(2 * d, n)`.
115
+
116
+ data_ref : np.ndarray [shape=(..., d, n_ref)]
117
+ A feature matrix for the reference sequence
118
+ If the data has more than two dimensions (e.g., for multi-channel inputs),
119
+ the leading dimensions are flattened prior to comparison.
120
+ For example, a stereo input with shape `(2, d, n_ref)` is
121
+ automatically reshaped to `(2 * d, n_ref)`.
122
+
123
+ k : int > 0 [scalar] or None
124
+ the number of nearest-neighbors for each sample
125
+
126
+ Default: ``k = 2 * ceil(sqrt(n_ref))``,
127
+ or ``k = 2`` if ``n_ref <= 3``
128
+
129
+ metric : str
130
+ Distance metric to use for nearest-neighbor calculation.
131
+
132
+ See `sklearn.neighbors.NearestNeighbors` for details.
133
+
134
+ sparse : bool [scalar]
135
+ if False, returns a dense type (ndarray)
136
+ if True, returns a sparse type (scipy.sparse.csc_matrix)
137
+
138
+ mode : str, {'connectivity', 'distance', 'affinity'}
139
+ If 'connectivity', a binary connectivity matrix is produced.
140
+
141
+ If 'distance', then a non-zero entry contains the distance between
142
+ points.
143
+
144
+ If 'affinity', then non-zero entries are mapped to
145
+ ``exp( - distance(i, j) / bandwidth)`` where ``bandwidth`` is
146
+ as specified below.
147
+
148
+ bandwidth : None, float > 0, ndarray, or str
149
+ str options include ``{'med_k_scalar', 'mean_k', 'gmean_k', 'mean_k_avg', 'gmean_k_avg', 'mean_k_avg_and_pair'}``
150
+
151
+ If ndarray is supplied, use ndarray as bandwidth for each i,j pair.
152
+
153
+ If using ``mode='affinity'``, this can be used to set the
154
+ bandwidth on the affinity kernel.
155
+
156
+ If no value is provided or ``None``, default to ``'med_k_scalar'``.
157
+
158
+ If ``bandwidth='med_k_scalar'``, bandwidth is set automatically to the median
159
+ distance to the k'th nearest neighbor of each ``data[:, i]``.
160
+
161
+ If ``bandwidth='mean_k'``, bandwidth is estimated for each sample-pair (i, j) by taking the
162
+ arithmetic mean between distances to the k-th nearest neighbor for sample i and sample j.
163
+
164
+ If ``bandwidth='gmean_k'``, bandwidth is estimated for each sample-pair (i, j) by taking the
165
+ geometric mean between distances to the k-th nearest neighbor for sample i and j [#z]_.
166
+
167
+ If ``bandwidth='mean_k_avg'``, bandwidth is estimated for each sample-pair (i, j) by taking the
168
+ arithmetic mean between the average distances to the first k-th nearest neighbors for
169
+ sample i and sample j.
170
+ This is similar to the approach in Wang et al. (2014) [#w]_ but does not include the distance
171
+ between i and j.
172
+
173
+ If ``bandwidth='gmean_k_avg'``, bandwidth is estimated for each sample-pair (i, j) by taking the
174
+ geometric mean between the average distances to the first k-th nearest neighbors for
175
+ sample i and sample j.
176
+
177
+ If ``bandwidth='mean_k_avg_and_pair'``, bandwidth is estimated for each sample-pair (i, j) by
178
+ taking the arithmetic mean between three terms: the average distances to the first
179
+ k-th nearest neighbors for sample i and sample j respectively, as well as
180
+ the distance between i and j.
181
+ This is similar to the approach in Wang et al. (2014). [#w]_
182
+
183
+ .. [#z] Zelnik-Manor, Lihi, and Pietro Perona. (2004).
184
+ "Self-tuning spectral clustering." Advances in neural information processing systems 17.
185
+
186
+ .. [#w] Wang, Bo, et al. (2014).
187
+ "Similarity network fusion for aggregating data types on a genomic scale." Nat Methods 11, 333–337.
188
+ https://doi.org/10.1038/nmeth.2810
189
+
190
+ full : bool
191
+ If using ``mode ='affinity'`` or ``mode='distance'``, this option can be used to compute
192
+ the full affinity or distance matrix as opposed a sparse matrix with only none-zero terms
193
+ for the first k-neighbors of each sample.
194
+ This option has no effect when using ``mode='connectivity'``.
195
+
196
+ When using ``mode='distance'``, setting ``full=True`` will ignore ``k`` and ``width``.
197
+ When using ``mode='affinity'``, setting ``full=True`` will use ``k`` exclusively for
198
+ bandwidth estimation, and ignore ``width``.
199
+
200
+ Returns
201
+ -------
202
+ xsim : np.ndarray or scipy.sparse.csc_matrix, [shape=(n_ref, n)]
203
+ Cross-similarity matrix
204
+
205
+ See Also
206
+ --------
207
+ recurrence_matrix
208
+ recurrence_to_lag
209
+ librosa.feature.stack_memory
210
+ sklearn.neighbors.NearestNeighbors
211
+ scipy.spatial.distance.cdist
212
+
213
+ Notes
214
+ -----
215
+ This function caches at level 30.
216
+
217
+ Examples
218
+ --------
219
+ Find nearest neighbors in CQT space between two sequences
220
+
221
+ >>> hop_length = 1024
222
+ >>> y_ref, sr = librosa.load(librosa.ex('pistachio'))
223
+ >>> y_comp, sr = librosa.load(librosa.ex('pistachio'), offset=10)
224
+ >>> chroma_ref = librosa.feature.chroma_cqt(y=y_ref, sr=sr, hop_length=hop_length)
225
+ >>> chroma_comp = librosa.feature.chroma_cqt(y=y_comp, sr=sr, hop_length=hop_length)
226
+ >>> # Use time-delay embedding to get a cleaner recurrence matrix
227
+ >>> x_ref = librosa.feature.stack_memory(chroma_ref, n_steps=10, delay=3)
228
+ >>> x_comp = librosa.feature.stack_memory(chroma_comp, n_steps=10, delay=3)
229
+ >>> xsim = librosa.segment.cross_similarity(x_comp, x_ref)
230
+
231
+ Or fix the number of nearest neighbors to 5
232
+
233
+ >>> xsim = librosa.segment.cross_similarity(x_comp, x_ref, k=5)
234
+
235
+ Use cosine similarity instead of Euclidean distance
236
+
237
+ >>> xsim = librosa.segment.cross_similarity(x_comp, x_ref, metric='cosine')
238
+
239
+ Use an affinity matrix instead of binary connectivity
240
+
241
+ >>> xsim_aff = librosa.segment.cross_similarity(x_comp, x_ref, metric='cosine', mode='affinity')
242
+
243
+ Plot the feature and recurrence matrices
244
+
245
+ >>> import matplotlib.pyplot as plt
246
+ >>> fig, ax = plt.subplots(ncols=2, sharex=True, sharey=True)
247
+ >>> imgsim = librosa.display.specshow(xsim, x_axis='s', y_axis='s',
248
+ ... hop_length=hop_length, ax=ax[0])
249
+ >>> ax[0].set(title='Binary cross-similarity (symmetric)')
250
+ >>> imgaff = librosa.display.specshow(xsim_aff, x_axis='s', y_axis='s',
251
+ ... cmap='magma_r', hop_length=hop_length, ax=ax[1])
252
+ >>> ax[1].set(title='Cross-affinity')
253
+ >>> ax[1].label_outer()
254
+ >>> fig.colorbar(imgsim, ax=ax[0], orientation='horizontal', ticks=[0, 1])
255
+ >>> fig.colorbar(imgaff, ax=ax[1], orientation='horizontal')
256
+ """
257
+ data_ref = np.atleast_2d(data_ref)
258
+ data = np.atleast_2d(data)
259
+
260
+ if not np.allclose(data_ref.shape[:-1], data.shape[:-1]):
261
+ raise ParameterError(
262
+ f"data_ref.shape={data_ref.shape} and data.shape={data.shape} do not match on leading dimension(s)"
263
+ )
264
+
265
+ # swap data axes so the feature axis is last
266
+ data_ref = np.swapaxes(data_ref, -1, 0)
267
+ n_ref = data_ref.shape[0]
268
+ # Use F-ordering for reshape to preserve leading axis
269
+ data_ref = data_ref.reshape((n_ref, -1), order="F")
270
+
271
+ data = np.swapaxes(data, -1, 0)
272
+ n = data.shape[0]
273
+ data = data.reshape((n, -1), order="F")
274
+
275
+ if mode not in ["connectivity", "distance", "affinity"]:
276
+ raise ParameterError(
277
+ (
278
+ f"Invalid mode='{mode}'. Must be one of "
279
+ "['connectivity', 'distance', 'affinity']"
280
+ )
281
+ )
282
+ if k is None:
283
+ k = min(n_ref, 2 * np.ceil(np.sqrt(n_ref)))
284
+
285
+ k = int(k)
286
+
287
+ # using k for bandwidth estimation also and decouple k for full mode
288
+ bandwidth_k = k
289
+ if full and (mode != "connectivity"):
290
+ k = n
291
+
292
+ # Build the neighbor search object
293
+ # `auto` mode does not work with some choices of metric. Rather than special-case
294
+ # those here, we instead use a fall-back to brute force if auto fails.
295
+ try:
296
+ knn = sklearn.neighbors.NearestNeighbors(
297
+ n_neighbors=min(n_ref, k), metric=metric, algorithm="auto"
298
+ )
299
+ except ValueError:
300
+ knn = sklearn.neighbors.NearestNeighbors(
301
+ n_neighbors=min(n_ref, k), metric=metric, algorithm="brute"
302
+ )
303
+
304
+ knn.fit(data_ref)
305
+
306
+ # Get the knn graph
307
+ if mode == "affinity":
308
+ # sklearn's nearest neighbor doesn't support affinity,
309
+ # so we use distance here and then do the conversion post-hoc
310
+ kng_mode = "distance"
311
+ else:
312
+ kng_mode = mode
313
+
314
+ xsim = knn.kneighbors_graph(X=data, mode=kng_mode).tolil()
315
+
316
+ if not full:
317
+ # Retain only the top-k links per point
318
+ for i in range(n):
319
+ # Get the links from point i
320
+ links = xsim[i].nonzero()[1]
321
+
322
+ # Order them ascending
323
+ idx = links[np.argsort(xsim[i, links].toarray())][0]
324
+
325
+ # Everything past the kth closest gets squashed
326
+ xsim[i, idx[k:]] = 0
327
+
328
+ # Convert a compressed sparse row (CSR) format
329
+ xsim = xsim.tocsr()
330
+ xsim.eliminate_zeros()
331
+
332
+ if mode == "connectivity":
333
+ xsim = xsim.astype(bool)
334
+ elif mode == "affinity":
335
+ aff_bandwidth = __affinity_bandwidth(xsim, bandwidth, bandwidth_k)
336
+ xsim.data[:] = np.exp(xsim.data / (-1 * aff_bandwidth))
337
+
338
+ # Transpose to n_ref by n
339
+ xsim = xsim.T
340
+
341
+ if not sparse:
342
+ xsim = xsim.toarray()
343
+
344
+ return xsim
345
+
346
+
347
+ @overload
348
+ def recurrence_matrix(
349
+ data: np.ndarray,
350
+ *,
351
+ k: Optional[int] = ...,
352
+ width: int = ...,
353
+ metric: str = ...,
354
+ sym: bool = ...,
355
+ sparse: Literal[True] = ...,
356
+ mode: str = ...,
357
+ bandwidth: Optional[Union[np.ndarray, _FloatLike_co, str]] = ...,
358
+ self: bool = ...,
359
+ axis: int = ...,
360
+ full: bool = False,
361
+ ) -> scipy.sparse.csc_matrix:
362
+ ...
363
+
364
+
365
+ @overload
366
+ def recurrence_matrix(
367
+ data: np.ndarray,
368
+ *,
369
+ k: Optional[int] = ...,
370
+ width: int = ...,
371
+ metric: str = ...,
372
+ sym: bool = ...,
373
+ sparse: Literal[False] = ...,
374
+ mode: str = ...,
375
+ bandwidth: Optional[Union[np.ndarray, _FloatLike_co, str]] = ...,
376
+ self: bool = ...,
377
+ axis: int = ...,
378
+ full: bool = False,
379
+ ) -> np.ndarray:
380
+ ...
381
+
382
+
383
+ @cache(level=30)
384
+ def recurrence_matrix(
385
+ data: np.ndarray,
386
+ *,
387
+ k: Optional[int] = None,
388
+ width: int = 1,
389
+ metric: str = "euclidean",
390
+ sym: bool = False,
391
+ sparse: bool = False,
392
+ mode: str = "connectivity",
393
+ bandwidth: Optional[Union[np.ndarray, _FloatLike_co, str]] = None,
394
+ self: bool = False,
395
+ axis: int = -1,
396
+ full: bool = False,
397
+ ) -> Union[np.ndarray, scipy.sparse.csc_matrix]:
398
+ """Compute a recurrence matrix from a data matrix.
399
+
400
+ ``rec[i, j]`` is non-zero if ``data[..., i]`` is a k-nearest neighbor
401
+ of ``data[..., j]`` and ``|i - j| >= width``
402
+
403
+ The specific value of ``rec[i, j]`` can have several forms, governed
404
+ by the ``mode`` parameter below:
405
+
406
+ - Connectivity: ``rec[i, j] = 1 or 0`` indicates that frames ``i`` and ``j`` are repetitions
407
+
408
+ - Affinity: ``rec[i, j] > 0`` measures how similar frames ``i`` and ``j`` are. This is also
409
+ known as a (sparse) self-similarity matrix.
410
+
411
+ - Distance: ``rec[i, j] > 0`` measures how distant frames ``i`` and ``j`` are. This is also
412
+ known as a (sparse) self-distance matrix.
413
+
414
+ The general term *recurrence matrix* can refer to any of the three forms above.
415
+
416
+ Parameters
417
+ ----------
418
+ data : np.ndarray [shape=(..., d, n)]
419
+ A feature matrix.
420
+ If the data has more than two dimensions (e.g., for multi-channel inputs),
421
+ the leading dimensions are flattened prior to comparison.
422
+ For example, a stereo input with shape `(2, d, n)` is
423
+ automatically reshaped to `(2 * d, n)`.
424
+
425
+ k : int > 0 [scalar] or None
426
+ the number of nearest-neighbors for each sample
427
+
428
+ Default: ``k = 2 * ceil(sqrt(t - 2 * width + 1))``,
429
+ or ``k = 2`` if ``t <= 2 * width + 1``
430
+
431
+ width : int >= 1 [scalar]
432
+ only link neighbors ``(data[..., i], data[..., j])``
433
+ if ``|i - j| >= width``
434
+
435
+ ``width`` cannot exceed the length of the data.
436
+
437
+ metric : str
438
+ Distance metric to use for nearest-neighbor calculation.
439
+
440
+ See `sklearn.neighbors.NearestNeighbors` for details.
441
+
442
+ sym : bool [scalar]
443
+ set ``sym=True`` to only link mutual nearest-neighbors
444
+
445
+ sparse : bool [scalar]
446
+ if False, returns a dense type (ndarray)
447
+ if True, returns a sparse type (scipy.sparse.csc_matrix)
448
+
449
+ mode : str, {'connectivity', 'distance', 'affinity'}
450
+ If 'connectivity', a binary connectivity matrix is produced.
451
+
452
+ If 'distance', then a non-zero entry contains the distance between
453
+ points.
454
+
455
+ If 'affinity', then non-zero entries are mapped to
456
+ ``exp( - distance(i, j) / bandwidth)`` where ``bandwidth`` is
457
+ as specified below.
458
+
459
+ bandwidth : None, float > 0, ndarray, or str
460
+ str options include ``{'med_k_scalar', 'mean_k', 'gmean_k', 'mean_k_avg', 'gmean_k_avg', 'mean_k_avg_and_pair'}``
461
+
462
+ If ndarray is supplied, use ndarray as bandwidth for each i,j pair.
463
+
464
+ If using ``mode='affinity'``, the ``bandwidth`` option can be used to set the
465
+ bandwidth on the affinity kernel.
466
+
467
+ If no value is provided or ``None``, default to ``'med_k_scalar'``.
468
+
469
+ If ``bandwidth='med_k_scalar'``, a scalar bandwidth is set to the median distance
470
+ of the k-th nearest neighbor for all samples.
471
+
472
+ If ``bandwidth='mean_k'``, bandwidth is estimated for each sample-pair (i, j) by taking the
473
+ arithmetic mean between distances to the k-th nearest neighbor for sample i and sample j.
474
+
475
+ If ``bandwidth='gmean_k'``, bandwidth is estimated for each sample-pair (i, j) by taking the
476
+ geometric mean between distances to the k-th nearest neighbor for sample i and j [#z]_.
477
+
478
+ If ``bandwidth='mean_k_avg'``, bandwidth is estimated for each sample-pair (i, j) by taking the
479
+ arithmetic mean between the average distances to the first k-th nearest neighbors for
480
+ sample i and sample j.
481
+ This is similar to the approach in Wang et al. (2014) [#w]_ but does not include the distance
482
+ between i and j.
483
+
484
+ If ``bandwidth='gmean_k_avg'``, bandwidth is estimated for each sample-pair (i, j) by taking the
485
+ geometric mean between the average distances to the first k-th nearest neighbors for
486
+ sample i and sample j.
487
+
488
+ If ``bandwidth='mean_k_avg_and_pair'``, bandwidth is estimated for each sample-pair (i, j) by
489
+ taking the arithmetic mean between three terms: the average distances to the first
490
+ k-th nearest neighbors for sample i and sample j respectively, as well as
491
+ the distance between i and j.
492
+ This is similar to the approach in Wang et al. (2014). [#w]_
493
+
494
+ .. [#z] Zelnik-Manor, Lihi, and Pietro Perona. (2004).
495
+ "Self-tuning spectral clustering." Advances in neural information processing systems 17.
496
+
497
+ .. [#w] Wang, Bo, et al. (2014).
498
+ "Similarity network fusion for aggregating data types on a genomic scale." Nat Methods 11, 333–337.
499
+ https://doi.org/10.1038/nmeth.2810
500
+
501
+ self : bool
502
+ If ``True``, then the main diagonal is populated with self-links:
503
+ 0 if ``mode='distance'``, and 1 otherwise.
504
+
505
+ If ``False``, the main diagonal is left empty.
506
+
507
+ axis : int
508
+ The axis along which to compute recurrence.
509
+ By default, the last index (-1) is taken.
510
+
511
+ full : bool
512
+ If using ``mode ='affinity'`` or ``mode='distance'``, this option can be used to compute
513
+ the full affinity or distance matrix as opposed a sparse matrix with only none-zero terms
514
+ for the first k-neighbors of each sample.
515
+ This option has no effect when using ``mode='connectivity'``.
516
+
517
+ When using ``mode='distance'``, setting ``full=True`` will ignore ``k`` and ``width``.
518
+ When using ``mode='affinity'``, setting ``full=True`` will use ``k`` exclusively for
519
+ bandwidth estimation, and ignore ``width``.
520
+
521
+ Returns
522
+ -------
523
+ rec : np.ndarray or scipy.sparse.csc_matrix, [shape=(t, t)]
524
+ Recurrence matrix
525
+
526
+ See Also
527
+ --------
528
+ sklearn.neighbors.NearestNeighbors
529
+ scipy.spatial.distance.cdist
530
+ librosa.feature.stack_memory
531
+ recurrence_to_lag
532
+
533
+ Notes
534
+ -----
535
+ This function caches at level 30.
536
+
537
+ Examples
538
+ --------
539
+ Find nearest neighbors in CQT space
540
+
541
+ >>> y, sr = librosa.load(librosa.ex('nutcracker'))
542
+ >>> hop_length = 1024
543
+ >>> chroma = librosa.feature.chroma_cqt(y=y, sr=sr, hop_length=hop_length)
544
+ >>> # Use time-delay embedding to get a cleaner recurrence matrix
545
+ >>> chroma_stack = librosa.feature.stack_memory(chroma, n_steps=10, delay=3)
546
+ >>> R = librosa.segment.recurrence_matrix(chroma_stack)
547
+
548
+ Or fix the number of nearest neighbors to 5
549
+
550
+ >>> R = librosa.segment.recurrence_matrix(chroma_stack, k=5)
551
+
552
+ Suppress neighbors within +- 7 frames
553
+
554
+ >>> R = librosa.segment.recurrence_matrix(chroma_stack, width=7)
555
+
556
+ Use cosine similarity instead of Euclidean distance
557
+
558
+ >>> R = librosa.segment.recurrence_matrix(chroma_stack, metric='cosine')
559
+
560
+ Require mutual nearest neighbors
561
+
562
+ >>> R = librosa.segment.recurrence_matrix(chroma_stack, sym=True)
563
+
564
+ Use an affinity matrix instead of binary connectivity
565
+
566
+ >>> R_aff = librosa.segment.recurrence_matrix(chroma_stack, metric='cosine',
567
+ ... mode='affinity')
568
+
569
+ Plot the feature and recurrence matrices
570
+
571
+ >>> import matplotlib.pyplot as plt
572
+ >>> fig, ax = plt.subplots(ncols=2, sharex=True, sharey=True)
573
+ >>> imgsim = librosa.display.specshow(R, x_axis='s', y_axis='s',
574
+ ... hop_length=hop_length, ax=ax[0])
575
+ >>> ax[0].set(title='Binary recurrence (symmetric)')
576
+ >>> imgaff = librosa.display.specshow(R_aff, x_axis='s', y_axis='s',
577
+ ... hop_length=hop_length, cmap='magma_r', ax=ax[1])
578
+ >>> ax[1].set(title='Affinity recurrence')
579
+ >>> ax[1].label_outer()
580
+ >>> fig.colorbar(imgsim, ax=ax[0], orientation='horizontal', ticks=[0, 1])
581
+ >>> fig.colorbar(imgaff, ax=ax[1], orientation='horizontal')
582
+ """
583
+ data = np.atleast_2d(data)
584
+
585
+ # Swap observations to the first dimension and flatten the rest
586
+ data = np.swapaxes(data, axis, 0)
587
+ t = data.shape[0]
588
+ # Use F-ordering here to preserve leading axis layout
589
+ data = data.reshape((t, -1), order="F")
590
+
591
+ if width < 1 or width >= (t - 1) // 2:
592
+ raise ParameterError(
593
+ "width={} must be at least 1 and at most (data.shape[{}] - 1) // 2={}".format(
594
+ width, axis, (t - 1) // 2
595
+ )
596
+ )
597
+
598
+ if mode not in ["connectivity", "distance", "affinity"]:
599
+ raise ParameterError(
600
+ (
601
+ f"Invalid mode='{mode}'. Must be one of "
602
+ "['connectivity', 'distance', 'affinity']"
603
+ )
604
+ )
605
+ if k is None:
606
+ k = 2 * np.ceil(np.sqrt(t - 2 * width + 1))
607
+
608
+ k = int(k)
609
+
610
+ # using k for bandwidth estimation also and decouple k for full mode
611
+ bandwidth_k = k
612
+ if full and (mode != "connectivity"):
613
+ k = t
614
+
615
+ # Build the neighbor search object
616
+ try:
617
+ knn = sklearn.neighbors.NearestNeighbors(
618
+ n_neighbors=min(t - 1, k + 2 * width), metric=metric, algorithm="auto"
619
+ )
620
+ except ValueError:
621
+ knn = sklearn.neighbors.NearestNeighbors(
622
+ n_neighbors=min(t - 1, k + 2 * width), metric=metric, algorithm="brute"
623
+ )
624
+
625
+ knn.fit(data)
626
+
627
+ # Get the knn graph
628
+ if mode == "affinity":
629
+ kng_mode = "distance"
630
+ else:
631
+ kng_mode = mode
632
+
633
+ rec = knn.kneighbors_graph(mode=kng_mode).tolil()
634
+
635
+ if not full:
636
+ # Remove connections within width
637
+ for diag in range(-width + 1, width):
638
+ rec.setdiag(0, diag)
639
+
640
+ # Retain only the top-k links per point
641
+ for i in range(t):
642
+ # Get the links from point i
643
+ links = rec[i].nonzero()[1]
644
+
645
+ # Order them ascending
646
+ idx = links[np.argsort(rec[i, links].toarray())][0]
647
+
648
+ # Everything past the kth closest gets squashed
649
+ rec[i, idx[k:]] = 0
650
+
651
+ if self:
652
+ if mode == "connectivity":
653
+ rec.setdiag(1)
654
+ elif mode == "affinity":
655
+ # we need to keep the self-loop in here, but not mess up the
656
+ # bandwidth estimation
657
+ #
658
+ # using negative distances here preserves the structure without changing
659
+ # the statistics of the data
660
+ rec.setdiag(-1)
661
+ else:
662
+ rec.setdiag(0)
663
+
664
+ # symmetrize
665
+ if sym:
666
+ # Note: this operation produces a CSR (compressed sparse row) matrix!
667
+ # This is why we have to do it after filling the diagonal in self-mode
668
+ rec = rec.minimum(rec.T)
669
+
670
+ rec = rec.tocsr()
671
+ rec.eliminate_zeros()
672
+
673
+ if mode == "connectivity":
674
+ rec = rec.astype(bool)
675
+ elif mode == "affinity":
676
+ # Set all the negatives back to 0
677
+ # Negatives are temporarily inserted above to preserve the sparsity structure
678
+ # of the matrix without corrupting the bandwidth calculations
679
+ rec.data[rec.data < 0] = 0.0
680
+ aff_bandwidth = __affinity_bandwidth(rec, bandwidth, bandwidth_k)
681
+ rec.data[:] = np.exp(rec.data / (-1 * aff_bandwidth))
682
+
683
+ # Transpose to be column-major
684
+ rec = rec.T
685
+
686
+ if not sparse:
687
+ rec = rec.toarray()
688
+
689
+ return rec
690
+
691
+
692
+ _ArrayOrSparseMatrix = TypeVar(
693
+ "_ArrayOrSparseMatrix", bound=Union[np.ndarray, scipy.sparse.spmatrix]
694
+ )
695
+
696
+
697
+ def recurrence_to_lag(
698
+ rec: _ArrayOrSparseMatrix, *, pad: bool = True, axis: int = -1
699
+ ) -> _ArrayOrSparseMatrix:
700
+ """Convert a recurrence matrix into a lag matrix.
701
+
702
+ ``lag[i, j] == rec[i+j, j]``
703
+
704
+ This transformation turns diagonal structures in the recurrence matrix
705
+ into horizontal structures in the lag matrix.
706
+ These horizontal structures can be used to infer changes in the repetition
707
+ structure of a piece, e.g., the beginning of a new section as done in [#]_.
708
+
709
+ .. [#] Serra, J., Müller, M., Grosche, P., & Arcos, J. L. (2014).
710
+ Unsupervised music structure annotation by time series structure
711
+ features and segment similarity.
712
+ IEEE Transactions on Multimedia, 16(5), 1229-1240.
713
+
714
+ Parameters
715
+ ----------
716
+ rec : np.ndarray, or scipy.sparse.spmatrix [shape=(n, n)]
717
+ A (binary) recurrence matrix, as returned by `recurrence_matrix`
718
+
719
+ pad : bool
720
+ If False, ``lag`` matrix is square, which is equivalent to
721
+ assuming that the signal repeats itself indefinitely.
722
+
723
+ If True, ``lag`` is padded with ``n`` zeros, which eliminates
724
+ the assumption of repetition.
725
+
726
+ axis : int
727
+ The axis to keep as the ``time`` axis.
728
+ The alternate axis will be converted to lag coordinates.
729
+
730
+ Returns
731
+ -------
732
+ lag : np.ndarray
733
+ The recurrence matrix in (lag, time) (if ``axis=1``)
734
+ or (time, lag) (if ``axis=0``) coordinates
735
+
736
+ Raises
737
+ ------
738
+ ParameterError : if ``rec`` is non-square
739
+
740
+ See Also
741
+ --------
742
+ recurrence_matrix
743
+ lag_to_recurrence
744
+ util.shear
745
+
746
+ Examples
747
+ --------
748
+ >>> y, sr = librosa.load(librosa.ex('nutcracker'))
749
+ >>> hop_length = 1024
750
+ >>> chroma = librosa.feature.chroma_cqt(y=y, sr=sr, hop_length=hop_length)
751
+ >>> chroma_stack = librosa.feature.stack_memory(chroma, n_steps=10, delay=3)
752
+ >>> recurrence = librosa.segment.recurrence_matrix(chroma_stack)
753
+ >>> lag_pad = librosa.segment.recurrence_to_lag(recurrence, pad=True)
754
+ >>> lag_nopad = librosa.segment.recurrence_to_lag(recurrence, pad=False)
755
+
756
+ >>> import matplotlib.pyplot as plt
757
+ >>> fig, ax = plt.subplots(nrows=2, sharex=True)
758
+ >>> librosa.display.specshow(lag_pad, x_axis='time', y_axis='lag',
759
+ ... hop_length=hop_length, ax=ax[0])
760
+ >>> ax[0].set(title='Lag (zero-padded)')
761
+ >>> ax[0].label_outer()
762
+ >>> librosa.display.specshow(lag_nopad, x_axis='time', y_axis='lag',
763
+ ... hop_length=hop_length, ax=ax[1])
764
+ >>> ax[1].set(title='Lag (no padding)')
765
+ """
766
+ axis = np.abs(axis)
767
+
768
+ if rec.ndim != 2 or rec.shape[0] != rec.shape[1]:
769
+ raise ParameterError(f"non-square recurrence matrix shape: {rec.shape}")
770
+
771
+ sparse = scipy.sparse.issparse(rec)
772
+
773
+ if sparse:
774
+ # suppress type check here, mypy doesn't know about issparse
775
+ fmt = rec.format # type: ignore
776
+
777
+ t = rec.shape[axis]
778
+
779
+ if pad:
780
+ if sparse:
781
+ padding = np.asarray([[1, 0]], dtype=rec.dtype).swapaxes(axis, 0)
782
+ if axis == 0:
783
+ rec_fmt = "csr"
784
+ else:
785
+ rec_fmt = "csc"
786
+ rec = scipy.sparse.kron(padding, rec, format=rec_fmt)
787
+ else:
788
+ padding = np.array([(0, 0), (0, 0)])
789
+ padding[(1 - axis), :] = [0, t]
790
+ # Suppress type check, mypy doesn't know that rec is an ndarray here
791
+ rec = np.pad(rec, padding, mode="constant") # type: ignore
792
+
793
+ lag: _ArrayOrSparseMatrix = util.shear(rec, factor=-1, axis=axis)
794
+
795
+ if sparse:
796
+ # Suppress type check, mypy doesn't know
797
+ # that lag is sparse here
798
+ lag = lag.asformat(fmt) # type: ignore
799
+
800
+ return lag
801
+
802
+
803
+ def lag_to_recurrence(
804
+ lag: _ArrayOrSparseMatrix, *, axis: int = -1
805
+ ) -> _ArrayOrSparseMatrix:
806
+ """Convert a lag matrix into a recurrence matrix.
807
+
808
+ Parameters
809
+ ----------
810
+ lag : np.ndarray or scipy.sparse.spmatrix
811
+ A lag matrix, as produced by ``recurrence_to_lag``
812
+ axis : int
813
+ The axis corresponding to the time dimension.
814
+ The alternate axis will be interpreted in lag coordinates.
815
+
816
+ Returns
817
+ -------
818
+ rec : np.ndarray or scipy.sparse.spmatrix [shape=(n, n)]
819
+ A recurrence matrix in (time, time) coordinates
820
+ For sparse matrices, format will match that of ``lag``.
821
+
822
+ Raises
823
+ ------
824
+ ParameterError : if ``lag`` does not have the correct shape
825
+
826
+ See Also
827
+ --------
828
+ recurrence_to_lag
829
+
830
+ Examples
831
+ --------
832
+ >>> y, sr = librosa.load(librosa.ex('nutcracker'))
833
+ >>> hop_length = 1024
834
+ >>> chroma = librosa.feature.chroma_cqt(y=y, sr=sr, hop_length=hop_length)
835
+ >>> chroma_stack = librosa.feature.stack_memory(chroma, n_steps=10, delay=3)
836
+ >>> recurrence = librosa.segment.recurrence_matrix(chroma_stack)
837
+ >>> lag_pad = librosa.segment.recurrence_to_lag(recurrence, pad=True)
838
+ >>> lag_nopad = librosa.segment.recurrence_to_lag(recurrence, pad=False)
839
+ >>> rec_pad = librosa.segment.lag_to_recurrence(lag_pad)
840
+ >>> rec_nopad = librosa.segment.lag_to_recurrence(lag_nopad)
841
+
842
+ >>> import matplotlib.pyplot as plt
843
+ >>> fig, ax = plt.subplots(nrows=2, ncols=2, sharex=True)
844
+ >>> librosa.display.specshow(lag_pad, x_axis='s', y_axis='lag',
845
+ ... hop_length=hop_length, ax=ax[0, 0])
846
+ >>> ax[0, 0].set(title='Lag (zero-padded)')
847
+ >>> ax[0, 0].label_outer()
848
+ >>> librosa.display.specshow(lag_nopad, x_axis='s', y_axis='time',
849
+ ... hop_length=hop_length, ax=ax[0, 1])
850
+ >>> ax[0, 1].set(title='Lag (no padding)')
851
+ >>> ax[0, 1].label_outer()
852
+ >>> librosa.display.specshow(rec_pad, x_axis='s', y_axis='time',
853
+ ... hop_length=hop_length, ax=ax[1, 0])
854
+ >>> ax[1, 0].set(title='Recurrence (with padding)')
855
+ >>> librosa.display.specshow(rec_nopad, x_axis='s', y_axis='time',
856
+ ... hop_length=hop_length, ax=ax[1, 1])
857
+ >>> ax[1, 1].set(title='Recurrence (without padding)')
858
+ >>> ax[1, 1].label_outer()
859
+ """
860
+ if axis not in [0, 1, -1]:
861
+ raise ParameterError(f"Invalid target axis: {axis}")
862
+
863
+ axis = np.abs(axis)
864
+
865
+ if lag.ndim != 2 or (
866
+ lag.shape[0] != lag.shape[1] and lag.shape[1 - axis] != 2 * lag.shape[axis]
867
+ ):
868
+ raise ParameterError(f"Invalid lag matrix shape: {lag.shape}")
869
+
870
+ # Since lag must be 2-dimensional, abs(axis) = axis
871
+ t = lag.shape[axis]
872
+
873
+ rec = util.shear(lag, factor=+1, axis=axis)
874
+
875
+ sub_slice = [slice(None)] * rec.ndim
876
+ sub_slice[1 - axis] = slice(t)
877
+ rec_slice: _ArrayOrSparseMatrix = rec[tuple(sub_slice)]
878
+ return rec_slice
879
+
880
+
881
+ _F = TypeVar("_F", bound=Callable[..., Any])
882
+
883
+
884
+ def timelag_filter(function: _F, pad: bool = True, index: int = 0) -> _F:
885
+ """Apply a filter in the time-lag domain.
886
+
887
+ This is primarily useful for adapting image filters to operate on
888
+ `recurrence_to_lag` output.
889
+
890
+ Using `timelag_filter` is equivalent to the following sequence of
891
+ operations:
892
+
893
+ >>> data_tl = librosa.segment.recurrence_to_lag(data)
894
+ >>> data_filtered_tl = function(data_tl)
895
+ >>> data_filtered = librosa.segment.lag_to_recurrence(data_filtered_tl)
896
+
897
+ Parameters
898
+ ----------
899
+ function : callable
900
+ The filtering function to wrap, e.g., `scipy.ndimage.median_filter`
901
+ pad : bool
902
+ Whether to zero-pad the structure feature matrix
903
+ index : int >= 0
904
+ If ``function`` accepts input data as a positional argument, it should be
905
+ indexed by ``index``
906
+
907
+ Returns
908
+ -------
909
+ wrapped_function : callable
910
+ A new filter function which applies in time-lag space rather than
911
+ time-time space.
912
+
913
+ Examples
914
+ --------
915
+ Apply a 31-bin median filter to the diagonal of a recurrence matrix.
916
+ With default, parameters, this corresponds to a time window of about
917
+ 0.72 seconds.
918
+
919
+ >>> y, sr = librosa.load(librosa.ex('nutcracker'), duration=30)
920
+ >>> chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
921
+ >>> chroma_stack = librosa.feature.stack_memory(chroma, n_steps=3, delay=3)
922
+ >>> rec = librosa.segment.recurrence_matrix(chroma_stack)
923
+ >>> from scipy.ndimage import median_filter
924
+ >>> diagonal_median = librosa.segment.timelag_filter(median_filter)
925
+ >>> rec_filtered = diagonal_median(rec, size=(1, 31), mode='mirror')
926
+
927
+ Or with affinity weights
928
+
929
+ >>> rec_aff = librosa.segment.recurrence_matrix(chroma_stack, mode='affinity')
930
+ >>> rec_aff_fil = diagonal_median(rec_aff, size=(1, 31), mode='mirror')
931
+
932
+ >>> import matplotlib.pyplot as plt
933
+ >>> fig, ax = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
934
+ >>> librosa.display.specshow(rec, y_axis='s', x_axis='s', ax=ax[0, 0])
935
+ >>> ax[0, 0].set(title='Raw recurrence matrix')
936
+ >>> ax[0, 0].label_outer()
937
+ >>> librosa.display.specshow(rec_filtered, y_axis='s', x_axis='s', ax=ax[0, 1])
938
+ >>> ax[0, 1].set(title='Filtered recurrence matrix')
939
+ >>> ax[0, 1].label_outer()
940
+ >>> librosa.display.specshow(rec_aff, x_axis='s', y_axis='s',
941
+ ... cmap='magma_r', ax=ax[1, 0])
942
+ >>> ax[1, 0].set(title='Raw affinity matrix')
943
+ >>> librosa.display.specshow(rec_aff_fil, x_axis='s', y_axis='s',
944
+ ... cmap='magma_r', ax=ax[1, 1])
945
+ >>> ax[1, 1].set(title='Filtered affinity matrix')
946
+ >>> ax[1, 1].label_outer()
947
+ """
948
+ def __my_filter(wrapped_f, *args, **kwargs):
949
+ """Wrap the filter with lag conversions"""
950
+ # Map the input data into time-lag space
951
+ args = list(args)
952
+
953
+ args[index] = recurrence_to_lag(args[index], pad=pad)
954
+
955
+ # Apply the filtering function
956
+ result = wrapped_f(*args, **kwargs)
957
+
958
+ # Map back into time-time and return
959
+ return lag_to_recurrence(result)
960
+
961
+ return decorator(__my_filter, function) # type: ignore
962
+
963
+
964
+ @cache(level=30)
965
+ def subsegment(
966
+ data: np.ndarray, frames: np.ndarray, *, n_segments: int = 4, axis: int = -1
967
+ ) -> np.ndarray:
968
+ """Sub-divide a segmentation by feature clustering.
969
+
970
+ Given a set of frame boundaries (``frames``), and a data matrix (``data``),
971
+ each successive interval defined by ``frames`` is partitioned into
972
+ ``n_segments`` by constrained agglomerative clustering.
973
+
974
+ .. note::
975
+ If an interval spans fewer than ``n_segments`` frames, then each
976
+ frame becomes a sub-segment.
977
+
978
+ Parameters
979
+ ----------
980
+ data : np.ndarray
981
+ Data matrix to use in clustering
982
+ frames : np.ndarray [shape=(n_boundaries,)], dtype=int, non-negative]
983
+ Array of beat or segment boundaries, as provided by
984
+ `librosa.beat.beat_track`,
985
+ `librosa.onset.onset_detect`,
986
+ or `agglomerative`.
987
+ n_segments : int > 0
988
+ Maximum number of frames to sub-divide each interval.
989
+ axis : int
990
+ Axis along which to apply the segmentation.
991
+ By default, the last index (-1) is taken.
992
+
993
+ Returns
994
+ -------
995
+ boundaries : np.ndarray [shape=(n_subboundaries,)]
996
+ List of sub-divided segment boundaries
997
+
998
+ See Also
999
+ --------
1000
+ agglomerative : Temporal segmentation
1001
+ librosa.onset.onset_detect : Onset detection
1002
+ librosa.beat.beat_track : Beat tracking
1003
+
1004
+ Notes
1005
+ -----
1006
+ This function caches at level 30.
1007
+
1008
+ Examples
1009
+ --------
1010
+ Load audio, detect beat frames, and subdivide in twos by CQT
1011
+
1012
+ >>> y, sr = librosa.load(librosa.ex('choice'), duration=10)
1013
+ >>> tempo, beats = librosa.beat.beat_track(y=y, sr=sr, hop_length=512)
1014
+ >>> beat_times = librosa.frames_to_time(beats, sr=sr, hop_length=512)
1015
+ >>> cqt = np.abs(librosa.cqt(y, sr=sr, hop_length=512))
1016
+ >>> subseg = librosa.segment.subsegment(cqt, beats, n_segments=2)
1017
+ >>> subseg_t = librosa.frames_to_time(subseg, sr=sr, hop_length=512)
1018
+
1019
+ >>> import matplotlib.pyplot as plt
1020
+ >>> fig, ax = plt.subplots()
1021
+ >>> librosa.display.specshow(librosa.amplitude_to_db(cqt,
1022
+ ... ref=np.max),
1023
+ ... y_axis='cqt_hz', x_axis='time', ax=ax)
1024
+ >>> lims = ax.get_ylim()
1025
+ >>> ax.vlines(beat_times, lims[0], lims[1], color='lime', alpha=0.9,
1026
+ ... linewidth=2, label='Beats')
1027
+ >>> ax.vlines(subseg_t, lims[0], lims[1], color='linen', linestyle='--',
1028
+ ... linewidth=1.5, alpha=0.5, label='Sub-beats')
1029
+ >>> ax.legend()
1030
+ >>> ax.set(title='CQT + Beat and sub-beat markers')
1031
+ """
1032
+ frames = util.fix_frames(frames, x_min=0, x_max=data.shape[axis], pad=True)
1033
+
1034
+ if n_segments < 1:
1035
+ raise ParameterError("n_segments must be a positive integer")
1036
+
1037
+ boundaries = []
1038
+ idx_slices = [slice(None)] * data.ndim
1039
+
1040
+ for seg_start, seg_end in zip(frames[:-1], frames[1:]):
1041
+ idx_slices[axis] = slice(seg_start, seg_end)
1042
+ boundaries.extend(
1043
+ seg_start
1044
+ + agglomerative(
1045
+ data[tuple(idx_slices)], min(seg_end - seg_start, n_segments), axis=axis
1046
+ )
1047
+ )
1048
+
1049
+ return np.array(boundaries)
1050
+
1051
+
1052
+ def agglomerative(
1053
+ data: np.ndarray,
1054
+ k: int,
1055
+ *,
1056
+ clusterer: Optional[sklearn.cluster.AgglomerativeClustering] = None,
1057
+ axis: int = -1,
1058
+ ) -> np.ndarray:
1059
+ """Bottom-up temporal segmentation.
1060
+
1061
+ Use a temporally-constrained agglomerative clustering routine to partition
1062
+ ``data`` into ``k`` contiguous segments.
1063
+
1064
+ Parameters
1065
+ ----------
1066
+ data : np.ndarray
1067
+ data to cluster
1068
+ k : int > 0 [scalar]
1069
+ number of segments to produce
1070
+ clusterer : sklearn.cluster.AgglomerativeClustering, optional
1071
+ An optional AgglomerativeClustering object.
1072
+ If `None`, a constrained Ward object is instantiated.
1073
+ axis : int
1074
+ axis along which to cluster.
1075
+ By default, the last axis (-1) is chosen.
1076
+
1077
+ Returns
1078
+ -------
1079
+ boundaries : np.ndarray [shape=(k,)]
1080
+ left-boundaries (frame numbers) of detected segments. This
1081
+ will always include `0` as the first left-boundary.
1082
+
1083
+ See Also
1084
+ --------
1085
+ sklearn.cluster.AgglomerativeClustering
1086
+
1087
+ Examples
1088
+ --------
1089
+ Cluster by chroma similarity, break into 20 segments
1090
+
1091
+ >>> y, sr = librosa.load(librosa.ex('nutcracker'), duration=15)
1092
+ >>> chroma = librosa.feature.chroma_cqt(y=y, sr=sr)
1093
+ >>> bounds = librosa.segment.agglomerative(chroma, 20)
1094
+ >>> bound_times = librosa.frames_to_time(bounds, sr=sr)
1095
+ >>> bound_times
1096
+ array([ 0. , 0.65 , 1.091, 1.927, 2.438, 2.902, 3.924,
1097
+ 4.783, 5.294, 5.712, 6.13 , 7.314, 8.522, 8.916,
1098
+ 9.66 , 10.844, 11.238, 12.028, 12.492, 14.095])
1099
+
1100
+ Plot the segmentation over the chromagram
1101
+
1102
+ >>> import matplotlib.pyplot as plt
1103
+ >>> import matplotlib.transforms as mpt
1104
+ >>> fig, ax = plt.subplots()
1105
+ >>> trans = mpt.blended_transform_factory(
1106
+ ... ax.transData, ax.transAxes)
1107
+ >>> librosa.display.specshow(chroma, y_axis='chroma', x_axis='time', ax=ax)
1108
+ >>> ax.vlines(bound_times, 0, 1, color='linen', linestyle='--',
1109
+ ... linewidth=2, alpha=0.9, label='Segment boundaries',
1110
+ ... transform=trans)
1111
+ >>> ax.legend()
1112
+ >>> ax.set(title='Power spectrogram')
1113
+ """
1114
+ # Make sure we have at least two dimensions
1115
+ data = np.atleast_2d(data)
1116
+
1117
+ # Swap data index to position 0
1118
+ data = np.swapaxes(data, axis, 0)
1119
+
1120
+ # Flatten the features
1121
+ n = data.shape[0]
1122
+ data = data.reshape((n, -1), order="F")
1123
+
1124
+ if clusterer is None:
1125
+ # Connect the temporal connectivity graph
1126
+ grid = sklearn.feature_extraction.image.grid_to_graph(n_x=n, n_y=1, n_z=1)
1127
+
1128
+ # Instantiate the clustering object
1129
+ clusterer = sklearn.cluster.AgglomerativeClustering(
1130
+ n_clusters=k, connectivity=grid, memory=cache.memory
1131
+ )
1132
+
1133
+ # Fit the model
1134
+ clusterer.fit(data)
1135
+
1136
+ # Find the change points from the labels
1137
+ boundaries = [0]
1138
+ boundaries.extend(list(1 + np.nonzero(np.diff(clusterer.labels_))[0].astype(int)))
1139
+ return np.asarray(boundaries)
1140
+
1141
+
1142
+ def path_enhance(
1143
+ R: np.ndarray,
1144
+ n: int,
1145
+ *,
1146
+ window: _WindowSpec = "hann",
1147
+ max_ratio: float = 2.0,
1148
+ min_ratio: Optional[float] = None,
1149
+ n_filters: int = 7,
1150
+ zero_mean: bool = False,
1151
+ clip: bool = True,
1152
+ **kwargs: Any,
1153
+ ) -> np.ndarray:
1154
+ """Multi-angle path enhancement for self- and cross-similarity matrices.
1155
+
1156
+ This function convolves multiple diagonal smoothing filters with a self-similarity (or
1157
+ recurrence) matrix R, and aggregates the result by an element-wise maximum.
1158
+
1159
+ Technically, the output is a matrix R_smooth such that::
1160
+
1161
+ R_smooth[i, j] = max_theta (R * filter_theta)[i, j]
1162
+
1163
+ where `*` denotes 2-dimensional convolution, and ``filter_theta`` is a smoothing filter at
1164
+ orientation theta.
1165
+
1166
+ This is intended to provide coherent temporal smoothing of self-similarity matrices
1167
+ when there are changes in tempo.
1168
+
1169
+ Smoothing filters are generated at evenly spaced orientations between min_ratio and
1170
+ max_ratio.
1171
+
1172
+ This function is inspired by the multi-angle path enhancement of [#]_, but differs by
1173
+ modeling tempo differences in the space of similarity matrices rather than re-sampling
1174
+ the underlying features prior to generating the self-similarity matrix.
1175
+
1176
+ .. [#] Müller, Meinard and Frank Kurth.
1177
+ "Enhancing similarity matrices for music audio analysis."
1178
+ 2006 IEEE International Conference on Acoustics Speech and Signal Processing Proceedings.
1179
+ Vol. 5. IEEE, 2006.
1180
+
1181
+ .. note:: if using recurrence_matrix to construct the input similarity matrix, be sure to include the main
1182
+ diagonal by setting ``self=True``. Otherwise, the diagonal will be suppressed, and this is likely to
1183
+ produce discontinuities which will pollute the smoothing filter response.
1184
+
1185
+ Parameters
1186
+ ----------
1187
+ R : np.ndarray
1188
+ The self- or cross-similarity matrix to be smoothed.
1189
+ Note: sparse inputs are not supported.
1190
+
1191
+ If the recurrence matrix is multi-dimensional, e.g. `shape=(c, n, n)`,
1192
+ then enhancement is conducted independently for each leading channel.
1193
+
1194
+ n : int > 0
1195
+ The length of the smoothing filter
1196
+
1197
+ window : window specification
1198
+ The type of smoothing filter to use. See `filters.get_window` for more information
1199
+ on window specification formats.
1200
+
1201
+ max_ratio : float > 0
1202
+ The maximum tempo ratio to support
1203
+
1204
+ min_ratio : float > 0
1205
+ The minimum tempo ratio to support.
1206
+ If not provided, it will default to ``1/max_ratio``
1207
+
1208
+ n_filters : int >= 1
1209
+ The number of different smoothing filters to use, evenly spaced
1210
+ between ``min_ratio`` and ``max_ratio``.
1211
+
1212
+ If ``min_ratio = 1/max_ratio`` (the default), using an odd number
1213
+ of filters will ensure that the main diagonal (ratio=1) is included.
1214
+
1215
+ zero_mean : bool
1216
+ By default, the smoothing filters are non-negative and sum to one (i.e. are averaging
1217
+ filters).
1218
+
1219
+ If ``zero_mean=True``, then the smoothing filters are made to sum to zero by subtracting
1220
+ a constant value from the non-diagonal coordinates of the filter. This is primarily
1221
+ useful for suppressing blocks while enhancing diagonals.
1222
+
1223
+ clip : bool
1224
+ If True, the smoothed similarity matrix will be thresholded at 0, and will not contain
1225
+ negative entries.
1226
+
1227
+ **kwargs : additional keyword arguments
1228
+ Additional arguments to pass to `scipy.ndimage.convolve`
1229
+
1230
+ Returns
1231
+ -------
1232
+ R_smooth : np.ndarray, shape=R.shape
1233
+ The smoothed self- or cross-similarity matrix
1234
+
1235
+ See Also
1236
+ --------
1237
+ librosa.filters.diagonal_filter
1238
+ recurrence_matrix
1239
+
1240
+ Examples
1241
+ --------
1242
+ Use a 51-frame diagonal smoothing filter to enhance paths in a recurrence matrix
1243
+
1244
+ >>> y, sr = librosa.load(librosa.ex('nutcracker'))
1245
+ >>> hop_length = 2048
1246
+ >>> chroma = librosa.feature.chroma_cqt(y=y, sr=sr, hop_length=hop_length)
1247
+ >>> chroma_stack = librosa.feature.stack_memory(chroma, n_steps=10, delay=3)
1248
+ >>> rec = librosa.segment.recurrence_matrix(chroma_stack, mode='affinity', self=True)
1249
+ >>> rec_smooth = librosa.segment.path_enhance(rec, 51, window='hann', n_filters=7)
1250
+
1251
+ Plot the recurrence matrix before and after smoothing
1252
+
1253
+ >>> import matplotlib.pyplot as plt
1254
+ >>> fig, ax = plt.subplots(ncols=2, sharex=True, sharey=True)
1255
+ >>> img = librosa.display.specshow(rec, x_axis='s', y_axis='s',
1256
+ ... hop_length=hop_length, ax=ax[0])
1257
+ >>> ax[0].set(title='Unfiltered recurrence')
1258
+ >>> imgpe = librosa.display.specshow(rec_smooth, x_axis='s', y_axis='s',
1259
+ ... hop_length=hop_length, ax=ax[1])
1260
+ >>> ax[1].set(title='Multi-angle enhanced recurrence')
1261
+ >>> ax[1].label_outer()
1262
+ >>> fig.colorbar(img, ax=ax[0], orientation='horizontal')
1263
+ >>> fig.colorbar(imgpe, ax=ax[1], orientation='horizontal')
1264
+ """
1265
+ if min_ratio is None:
1266
+ min_ratio = 1.0 / max_ratio
1267
+ elif min_ratio > max_ratio:
1268
+ raise ParameterError(
1269
+ f"min_ratio={min_ratio} cannot exceed max_ratio={max_ratio}"
1270
+ )
1271
+
1272
+ R_smooth = None
1273
+ for ratio in np.logspace(
1274
+ np.log2(min_ratio), np.log2(max_ratio), num=n_filters, base=2
1275
+ ):
1276
+ kernel = diagonal_filter(window, n, slope=ratio, zero_mean=zero_mean)
1277
+
1278
+ # Expand leading dimensions to match R
1279
+ # This way, if R has shape, eg, [2, 3, n, n]
1280
+ # the expanded kernel will have shape [1, 1, m, m]
1281
+
1282
+ # The following is valid for numpy >= 1.18
1283
+ # kernel = np.expand_dims(kernel, axis=list(np.arange(R.ndim - kernel.ndim)))
1284
+
1285
+ # This is functionally equivalent, but works on numpy 1.17
1286
+ shape = [1] * R.ndim
1287
+ shape[-2:] = kernel.shape
1288
+ kernel = np.reshape(kernel, shape)
1289
+
1290
+ if R_smooth is None:
1291
+ R_smooth = scipy.ndimage.convolve(R, kernel, **kwargs)
1292
+ else:
1293
+ # Compute the point-wise maximum in-place
1294
+ np.maximum(
1295
+ R_smooth, scipy.ndimage.convolve(R, kernel, **kwargs), out=R_smooth
1296
+ )
1297
+
1298
+ if clip:
1299
+ # Clip the output in-place
1300
+ np.clip(R_smooth, 0, None, out=R_smooth) # type: ignore
1301
+
1302
+ return np.asanyarray(R_smooth)
1303
+
1304
+
1305
+ def __affinity_bandwidth(
1306
+ rec: scipy.sparse.csr_matrix,
1307
+ bw_mode: Optional[Union[np.ndarray, _FloatLike_co, str]],
1308
+ k: int,
1309
+ ) -> Union[float, np.ndarray]:
1310
+ # rec should be a csr_matrix
1311
+
1312
+ # the api allows users to specify a scalar bandwidth directly, besides the string based options.
1313
+ if isinstance(bw_mode, np.ndarray):
1314
+ bandwidth = bw_mode
1315
+ # check if bw is the right size
1316
+ if bandwidth.shape != rec.shape:
1317
+ raise ParameterError(
1318
+ f"Invalid matrix bandwidth shape: {bandwidth.shape}."
1319
+ f"Should be {rec.shape}."
1320
+ )
1321
+ if (bandwidth <= 0).any():
1322
+ raise ParameterError(
1323
+ "Invalid bandwidth. All entries must be strictly positive."
1324
+ )
1325
+ return np.array(bandwidth[rec.nonzero()])
1326
+
1327
+ elif isinstance(bw_mode, (int, float)):
1328
+ scalar_bandwidth = float(bw_mode)
1329
+ if scalar_bandwidth <= 0:
1330
+ raise ParameterError(
1331
+ f"Invalid scalar bandwidth={scalar_bandwidth}. Must be strictly positive."
1332
+ )
1333
+ return scalar_bandwidth
1334
+
1335
+ if bw_mode is None:
1336
+ bw_mode = "med_k_scalar"
1337
+
1338
+ if bw_mode not in [
1339
+ "med_k_scalar",
1340
+ "mean_k",
1341
+ "gmean_k",
1342
+ "mean_k_avg",
1343
+ "gmean_k_avg",
1344
+ "mean_k_avg_and_pair",
1345
+ ]:
1346
+ raise ParameterError(
1347
+ f"Invalid bandwidth='{bw_mode}'. Must be either a positive scalar or one of "
1348
+ "['med_k_scalar', 'mean_k', 'gmean_k', 'mean_k_avg', 'gmean_k_avg', 'mean_k_avg_and_pair']"
1349
+ )
1350
+
1351
+ # build a list of list that stores the distances to k nearest neighbors for all t points.
1352
+ t = rec.shape[0]
1353
+ knn_dists = []
1354
+ for i in range(t):
1355
+ # Get the links from point i
1356
+ links = rec[i].nonzero()[1]
1357
+ # catch empty dists lists in knn_dists
1358
+ if len(links) == 0:
1359
+ # Disconnected vertices are only a problem for point-wise bandwidth estimation
1360
+ if bw_mode not in ["med_k_scalar"]:
1361
+ raise ParameterError(f"The sample at time point {i} has no neighbors")
1362
+ else:
1363
+ # If we have no links, then there's no distance
1364
+ # shove a nan in here
1365
+ knn_dists.append(np.array([np.nan]))
1366
+ else:
1367
+ # Compute k nearest neighbors' distance and sort ascending
1368
+ knn_dist_row = np.sort(rec[i, links].toarray()[0])[:k]
1369
+ knn_dists.append(knn_dist_row)
1370
+
1371
+ # take the last element of each list for the distance to kth neighbor
1372
+ dist_to_k = np.asarray([dists[-1] for dists in knn_dists])
1373
+ avg_dist_to_first_ks = np.asarray([np.mean(dists) for dists in knn_dists])
1374
+
1375
+ if bw_mode == "med_k_scalar":
1376
+ if not np.any(np.isfinite(dist_to_k)):
1377
+ raise ParameterError("Cannot estimate bandwidth from an empty graph")
1378
+ return float(np.nanmedian(dist_to_k))
1379
+
1380
+ if bw_mode in ["mean_k", "gmean_k"]:
1381
+ # building bandwidth components (sigma) using sparse matrix structures and indices
1382
+ sigma_i_data = np.empty_like(rec.data)
1383
+ sigma_j_data = np.empty_like(rec.data)
1384
+ for row in range(t):
1385
+ sigma_i_data[rec.indptr[row] : rec.indptr[row + 1]] = dist_to_k[row]
1386
+ col_idx = rec.indices[rec.indptr[row] : rec.indptr[row + 1]]
1387
+ sigma_j_data[rec.indptr[row] : rec.indptr[row + 1]] = dist_to_k[col_idx]
1388
+
1389
+ if bw_mode == "mean_k":
1390
+ out = np.array((sigma_i_data + sigma_j_data) / 2)
1391
+ elif bw_mode == "gmean_k":
1392
+ out = np.array((sigma_i_data * sigma_j_data) ** 0.5)
1393
+
1394
+ if bw_mode in ["mean_k_avg", "gmean_k_avg", "mean_k_avg_and_pair"]:
1395
+ # building bandwidth components (sigma) using sparse matrix structures and indices
1396
+ sigma_i_data = np.empty_like(rec.data)
1397
+ sigma_j_data = np.empty_like(rec.data)
1398
+ for row in range(t):
1399
+ sigma_i_data[rec.indptr[row] : rec.indptr[row + 1]] = avg_dist_to_first_ks[
1400
+ row
1401
+ ]
1402
+ col_idx = rec.indices[rec.indptr[row] : rec.indptr[row + 1]]
1403
+ sigma_j_data[rec.indptr[row] : rec.indptr[row + 1]] = avg_dist_to_first_ks[
1404
+ col_idx
1405
+ ]
1406
+
1407
+ if bw_mode == "mean_k_avg":
1408
+ out = np.array((sigma_i_data + sigma_j_data) / 2)
1409
+ elif bw_mode == "gmean_k_avg":
1410
+ out = np.array((sigma_i_data * sigma_j_data) ** 0.5)
1411
+ elif bw_mode == "mean_k_avg_and_pair":
1412
+ out = np.array((sigma_i_data + sigma_j_data + rec.data) / 3)
1413
+
1414
+ return out