You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
5.7 KiB
Plaintext
202 lines
5.7 KiB
Plaintext
TITLE:: FluidGrid
|
|
summary:: Constrain a 2D DataSet into a Grid.
|
|
categories:: Libraries>FluidCorpusManipulation
|
|
related:: Classes/FluidMDS, Classes/FluidPCA, Classes/FluidDataSet
|
|
|
|
DESCRIPTION::
|
|
|
|
Hello. I put stuff in a 2-dimension link::Classes/FluidDataSet:: in the most even grid possible by minimising the distance I need to move each item around, using some clever algorithms. The grid space can be oversampled to allow for a sparser representation. The resulting grid shape can be constraint in one axis.
|
|
|
|
Please refer to a webpage and an article for more information on the algorithm.
|
|
|
|
CLASSMETHODS::
|
|
|
|
METHOD:: new
|
|
Make a new instance
|
|
|
|
ARGUMENT:: server
|
|
The server on which to run this model
|
|
|
|
ARGUMENT:: oversample
|
|
A factor to oversample the destination grid. The default is 1, so the most compact grid possible will be yield. Factors of 2 or more will allow a larger destination grid, which will respect the original shape a little more, but will therefore be sparser.
|
|
|
|
ARGUMENT:: extent
|
|
The size to which the selected axis will be constraint to. The default is 0, which turns the constraints off.
|
|
|
|
ARGUMENT:: axis
|
|
The axis on which the constraint size is applied to. The default (0) is horizontal, and (1) is vertical.
|
|
|
|
INSTANCEMETHODS::
|
|
|
|
PRIVATE:: init
|
|
|
|
METHOD:: fitTransform
|
|
Fit the model to a link::Classes/FluidDataSet:: and write the new projected data to a destination FluidDataSet.
|
|
ARGUMENT:: sourceDataSet
|
|
Source data, or the DataSet name
|
|
ARGUMENT:: destDataSet
|
|
Destination data, or the DataSet name
|
|
ARGUMENT:: action
|
|
Run when done
|
|
|
|
EXAMPLES::
|
|
|
|
STRONG::A didactic example::
|
|
|
|
code::
|
|
|
|
/// make a simple grid of numbers
|
|
~simple = Dictionary.newFrom(36.collect{|i|[i.asSymbol, [i.mod(9), i.div(9)]]}.flatten(1));
|
|
|
|
//look at it
|
|
(
|
|
w = Window("the source", Rect(128, 64, 230, 100));
|
|
w.drawFunc = {
|
|
Pen.use {
|
|
~simple.keysValuesDo{|key, val|
|
|
Pen.stringCenteredIn(key, Rect((val[0] * 25), (val[1] * 25), 25, 25), Font( "Helvetica", 12 ), Color.black)
|
|
}
|
|
}
|
|
};
|
|
w.refresh;
|
|
w.front;
|
|
)
|
|
|
|
|
|
//load it in a dataset
|
|
~raw = FluidDataSet(s);
|
|
~raw.load(Dictionary.newFrom([\cols, 2, \data, ~simple]));
|
|
|
|
// make a grid out of it
|
|
~grid = FluidGrid(s);
|
|
~gridified = FluidDataSet(s);
|
|
~grid.fitTransform(~raw, ~gridified, action:{~gridified.dump{|x|~gridifiedDict = x["data"];\gridded.postln;}})
|
|
|
|
// watch the grid
|
|
(
|
|
w = Window("a perspective", Rect(358, 64, 350, 230));
|
|
w.drawFunc = {
|
|
Pen.use {
|
|
~gridifiedDict.keysValuesDo{|key, val|
|
|
Pen.stringCenteredIn(key, Rect((val[0] * 25), (val[1] * 25), 25, 25), Font( "Helvetica", 12 ), Color.black)
|
|
}
|
|
}
|
|
};
|
|
w.refresh;
|
|
w.front;
|
|
)
|
|
|
|
// change the constraints and draw again
|
|
(
|
|
~grid.axis_(0).extent_(4).fitTransform(~raw, ~gridified, action:{
|
|
~gridified.dump{|x|
|
|
~gridifiedDict = x["data"];\gridded.postln;
|
|
{w.refresh;}.defer;
|
|
}})
|
|
)
|
|
|
|
// here we constrain in the other dimension
|
|
(
|
|
~grid.axis_(1).extent_(3).fitTransform(~raw, ~gridified, action:{
|
|
~gridified.dump{|x|
|
|
~gridifiedDict = x["data"];\gridded.postln;
|
|
{w.refresh;}.defer;
|
|
}})
|
|
)
|
|
|
|
//oversampling yields the shape...ish!
|
|
(
|
|
~grid.oversample_(2).extent_(0).fitTransform(~raw, ~gridified, action:{
|
|
~gridified.dump{|x|
|
|
~gridifiedDict = x["data"];\gridded.postln;
|
|
{w.refresh;}.defer;
|
|
}})
|
|
)
|
|
::
|
|
|
|
STRONG::A more colourful example::
|
|
|
|
code::
|
|
|
|
// make all dependencies
|
|
(
|
|
~raw = FluidDataSet(s);
|
|
~standardized = FluidDataSet(s);
|
|
~reduced = FluidDataSet(s);
|
|
~normalized = FluidDataSet(s);
|
|
~standardizer = FluidStandardize(s);
|
|
~normalizer = FluidNormalize(s, 0.05, 0.95);
|
|
~umap = FluidUMAP(s).numDimensions_(2).numNeighbours_(5).minDist_(0.2).iterations_(50).learnRate_(0.2);
|
|
~grid = FluidGrid(s);
|
|
~gridified = FluidDataSet(s);
|
|
)
|
|
|
|
// build a dataset of 400 points in 3D (colour in RGB)
|
|
~colours = Dictionary.newFrom(400.collect{|i|[("entry"++i).asSymbol, 3.collect{1.0.rand}]}.flatten(1));
|
|
~raw.load(Dictionary.newFrom([\cols, 3, \data, ~colours]));
|
|
|
|
//First standardize our DataSet, then apply the UMAP to get 2 dimensions, then normalise these 2 for drawing in the full window size
|
|
(
|
|
~standardizer.fitTransform(~raw,~standardized,action:{"Standardized".postln});
|
|
~umap.fitTransform(~standardized,~reduced,action:{"Finished UMAP".postln});
|
|
~normalizer.fitTransform(~reduced,~normalized,action:{"Normalized Output".postln});
|
|
)
|
|
//we recover the reduced dataset
|
|
~normalized.dump{|x| ~normalizedDict = x["data"]};
|
|
|
|
//Visualise the 2D projection of our original 4D data
|
|
(
|
|
w = Window("a perspective", Rect(128, 64, 200, 200));
|
|
w.drawFunc = {
|
|
Pen.use {
|
|
~normalizedDict.keysValuesDo{|key, val|
|
|
Pen.fillColor = Color.new(~colours[key.asSymbol][0], ~colours[key.asSymbol][1],~colours[key.asSymbol][2]);
|
|
Pen.fillOval(Rect((val[0] * 200), (val[1] * 200), 5, 5));
|
|
~colours[key.asSymbol].flat;
|
|
}
|
|
}
|
|
};
|
|
w.refresh;
|
|
w.front;
|
|
)
|
|
|
|
//Force the UMAP-reduced dataset into a grid, normalise for viewing then print in another window
|
|
(
|
|
~grid.fitTransform(~reduced,~gridified,action:{"Gridded Output".postln;
|
|
~normalizer.fitTransform(~gridified,~normalized,action:{"Normalized Output".postln;
|
|
~normalized.dump{|x|
|
|
~normalizedDict = x["data"];
|
|
{
|
|
y = Window("a grid", Rect(328, 64, 200, 200));
|
|
y.drawFunc = {
|
|
Pen.use {
|
|
~normalizedDict.keysValuesDo{|key, val|
|
|
Pen.fillColor = Color.new(~colours[key.asSymbol][0], ~colours[key.asSymbol][1],~colours[key.asSymbol][2]);
|
|
Pen.fillOval(Rect((val[0] * 200), (val[1] * 200), 5, 5));
|
|
~colours[key.asSymbol].flat;
|
|
}
|
|
}
|
|
};
|
|
y.refresh;
|
|
y.front;
|
|
}.defer;
|
|
};
|
|
});
|
|
});
|
|
)
|
|
|
|
// This looks ok, but let's improve it with oversampling
|
|
(
|
|
~grid.oversample_(3).fitTransform(~reduced,~gridified,action:{"Gridded Output".postln;
|
|
~normalizer.fitTransform(~gridified,~normalized,action:{"Normalized Output".postln;
|
|
~normalized.dump{|x|
|
|
~normalizedDict = x["data"];
|
|
{
|
|
y.refresh;
|
|
}.defer;
|
|
};
|
|
});
|
|
});
|
|
)
|
|
::
|