diff --git a/doc/BufSines.rst b/doc/BufSines.rst index 91a5904..d675a18 100644 --- a/doc/BufSines.rst +++ b/doc/BufSines.rst @@ -66,7 +66,7 @@ The minimum duration, in spectral frames, for a sinusoidal track to be accepted as a partial. It allows to remove bubbly pitchy artefactss, but is more CPU intensive and might reject quick pitch material. -:control trackingMethod: +:control trackMethod: The algorithm used to track the sinusoidal continuity between spectral frames. 0 is the default, "Greedy", and 1 is a more expensive [^"Hungarian"]( Neri, J., and Depalle, P., "Fast Partial Tracking of Audio with Real-Time Capability through Linear Programming". Proceedings of DAFx-2018. ) one. diff --git a/doc/DataSet.rst b/doc/DataSet.rst index 36d8c1c..f5093e8 100644 --- a/doc/DataSet.rst +++ b/doc/DataSet.rst @@ -89,6 +89,14 @@ Merge sourceDataSet in the current DataSet. It will update the value of points with the same identifier if overwrite is set to 1. ​To add columns instead, see the 'transformJoin' method of FluidDataSetQuery. +:message kNearest: + + :arg buffer: A |buffer| containing a data point to match against. The number of frames in the buffer must match the dimensionality of the DataSet. + + :arg k: The number of nearest neighbours to return. The identifiers will be sorted, beginning with the nearest. + + Returns the identifiers of the ``k`` points nearest to the one passed. Note that this is a brute force distance measure, and comparatively inefficient for repeated queries against large datasets. For such cases, :fluid-obj:`KDTree` will be more efficient. + :message print: Post an abbreviated content of the DataSet in the window by default, but you can supply a custom action instead. diff --git a/doc/KDTree.rst b/doc/KDTree.rst index 9c6d840..5bb8a97 100644 --- a/doc/KDTree.rst +++ b/doc/KDTree.rst @@ -7,6 +7,8 @@ :discussion: :fluid-obj:`KDTree` facilitates efficient nearest neighbour searches of multi-dimensional data stored in a :fluid-obj:`DataSet`. + k-d trees are most useful for *repeated* querying of a dataset, because there is a cost associated with building them. If you just need to do a single lookup then using the kNearest message of :fluid-obj:`DataSet` will probably be quicker + Whilst k-d trees can offer very good performance relative to naïve search algorithms, they suffer from something called “the curse of dimensionality” (like many algorithms for multi-dimensional data). In practice, this means that as the number of dimensions of your data goes up, the relative performance gains of a k-d tree go down. :control numNeighbours: @@ -34,13 +36,17 @@ :arg buffer: A |buffer| containing a data point to match against. The number of frames in the buffer must match the dimensionality of the :fluid-obj:`DataSet` the tree was fitted to. - :arg k: The number of nearest neighbours to return. The identifiers will be sorted, beginning with the nearest. + :arg k: (optional) The number of nearest neighbours to return. The identifiers will be sorted, beginning with the nearest. + + :arg action: A function that will run when the query returns, whose argument is an array of distances. Returns the identifiers of the ``k`` points nearest to the one passed. :message kNearestDist: :arg buffer: A |buffer| containing a data point to match against. The number of frames in the buffer must match the dimensionality of the :fluid-obj:`DataSet` the tree was fitted to. + + :arg k: (optional) The number of nearest neighbours to return. The identifiers will be sorted, beginning with the nearest. :arg action: A function that will run when the query returns, whose argument is an array of distances. diff --git a/doc/Sines.rst b/doc/Sines.rst index 9964dac..2a61a15 100644 --- a/doc/Sines.rst +++ b/doc/Sines.rst @@ -41,7 +41,7 @@ The minimum duration, in spectral frames, for a sinusoidal track to be accepted as a partial. It allows to remove bubbly pitchy artefacts, but is more CPU intensive and might reject quick pitch material. -:control trackingMethod: +:control trackMethod: The algorithm used to track the sinusoidal continuity between spectral frames. 0 is the default, "Greedy", and 1 is a more expensive [^"Hungarian"]( Neri, J., and Depalle, P., "Fast Partial Tracking of Audio with Real-Time Capability through Linear Programming". Proceedings of DAFx-2018. ) one. diff --git a/example-code/sc/DataSet.scd b/example-code/sc/DataSet.scd index 4dc6d95..bfb966f 100644 --- a/example-code/sc/DataSet.scd +++ b/example-code/sc/DataSet.scd @@ -250,4 +250,29 @@ fork{ } ) +:: +strong::Nearest Neighbour Search in a DataSet:: + +Note: A FluidDataSet can be queried with an input point to return the nearest match to that point. Note: This feature is can be computationally expensive on a large dataset, as it needs to compute the distance of the queried point to each point in the dataset. If you need to perform multiple nearest neighbour queries on a fluid.dataset~ it is recommended to use FluidKDTree. This facility is most useful with smaller, ephemeral datasets such as those returned by FluidDataSetQuery. + +code:: + +// create a small DataSet... +f = FluidDataSet(s) +// and fill it with a grid of data +f.load(Dictionary.newFrom(["cols", 2, "data", Dictionary.newFrom(9.collect{|i|["item-%".format(i), [i.div(3), i.mod(3)] / 2]}.flatten(1))])) + +// the data looks like this +// (item-0 -> [ 0.0, 0.0 ]) (item-1 -> [ 0.0, 0.5 ]) (item-2 -> [ 0.0, 1.0 ]) +// (item-3 -> [ 0.5, 0.0 ]) (item-4 -> [ 0.5, 0.5 ]) (item-5 -> [ 0.5, 1.0 ]) +// (item-6 -> [ 1.0, 0.0 ]) (item-7 -> [ 1.0, 0.5 ]) (item-8 -> [ 1.0, 1.0 ]) + +// create a query buffer... +b = Buffer.alloc(s,2) + +// and fill it with a point +b.sendCollection([1,0]); + +// and request 9 nearest neighbours +f.kNearest(b,9,{|x|x.postln;}) :: \ No newline at end of file diff --git a/example-code/sc/KDTree.scd b/example-code/sc/KDTree.scd index 684222e..c4b5658 100644 --- a/example-code/sc/KDTree.scd +++ b/example-code/sc/KDTree.scd @@ -21,7 +21,7 @@ ds.dump({ defer{ view.highlight_(id); }; - + { var start = Index.kr(slicePoints,index); var end = Index.kr(slicePoints,index+1); @@ -40,15 +40,8 @@ ds.dump({ strong::radius and num neighbours:: code:: -// set some initial values -( -~numNeighbours = 3; -~tree.radius_(0.04); -) - - -// then make the plot, once it's up and you're clicking around, -// change the numbers and re-run the code above to see the differences +// Make a plot; once it's up and you're clicking around, change the numbers of +// queried neighbours and the permitted radius to see the different behaviours ( var ds = FluidDataSet(s).load( Dictionary.newFrom([ @@ -61,18 +54,40 @@ var ds = FluidDataSet(s).load( ) ]) ); -~tree = FluidKDTree(s).fit(ds); + +~tree = FluidKDTree(s); +~tree.numNeighbours = 3; +~tree.radius_(0.04); +~tree.fit(ds); + ds.dump({ arg dict; + var nn, nnd; var xybuf = Buffer.alloc(s,2); defer{ - FluidPlotter(dict:dict,mouseMoveAction:{ + w = Window(\KDTree,Rect(0,0,705,500)).front; + StaticText(w,Rect(500,5,200,20)).string_("numNeighbours:"); + TextField(w,Rect(500,25,200,20)).string_(~tree.numNeighbours.asString).action_{|x|~tree.numNeighbours = x.value.asInteger}; + StaticText(w,Rect(500,45,200,20)).string_("radius:"); + TextField(w,Rect(500,65,200,20)).string_(~tree.radius.asString).action_{|x|~tree.radius = x.value.asFloat}; + StaticText(w,Rect(500,85,200,20)).string_("neighbours:"); + nn = TextView(w, Rect(500,105,200,40)).string_("").editable_(false); + StaticText(w,Rect(500,145,200,20)).string_("distances:"); + nnd = TextView(w, Rect(500,165,200,200)).string_("").editable_(false); + FluidPlotter(w, Rect(5,5,490,490), dict:dict,mouseMoveAction:{ arg view, x, y; xybuf.setn(0,[x,y]); - ~tree.kNearest(xybuf,~numNeighbours,{ + ~tree.kNearest(xybuf,action:{ arg id; defer{ view.highlight_(id); + nn.string = id.asString; + }; + }); + ~tree.kNearestDist(xybuf,action:{ + arg id; + defer{ + nnd.string = id.asString; }; }); });