Skip to content

Commit

Permalink
64 dedicated clustering tool (#66)
Browse files Browse the repository at this point in the history
* Moved clustering capabilities into Geometry Editor under new Find Clusters tab.
Re-enabled KMeans (and fixed it) as a clustering option.

* KMeans and HDDBSCAN moved into Runnable Task implementations.

* Expectation Maximation moved to dedicated cluster task.
Still buggy...

* DBSCAN and Affinity Propagation tasks available for clustering

* logic for projecting via UMAP or PCA moved to separate dedicated Task classes.

* KMedoids clustering task support.

* ProjectionConfig now updated to support new Clustering GUI options.
  • Loading branch information
Birdasaur authored Jul 3, 2024
1 parent 3a8bb72 commit 0f7e7d5
Show file tree
Hide file tree
Showing 18 changed files with 1,671 additions and 699 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import edu.jhuapl.trinity.data.Manifold;
import edu.jhuapl.trinity.javafx.components.listviews.PointListItem;
import edu.jhuapl.trinity.javafx.events.ManifoldEvent;
import edu.jhuapl.trinity.javafx.events.ManifoldEvent.ProjectionConfig;
import edu.jhuapl.trinity.javafx.javafx3d.Manifold3D;
import edu.jhuapl.trinity.utils.JavaFX3DUtils;
import edu.jhuapl.trinity.utils.ResourceUtils;
Expand Down Expand Up @@ -57,16 +58,24 @@

import static edu.jhuapl.trinity.utils.JavaFX3DUtils.toFX;
import static edu.jhuapl.trinity.utils.JavaFX3DUtils.toFXYZ3D;
import edu.jhuapl.trinity.utils.clustering.ClusterMethod;
import static java.util.stream.Collectors.toList;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.ToggleGroup;

/**
* @author Sean Phillips
*/
public class Shape3DControlPane extends LitPathPane {
public static double SPINNER_PREF_WIDTH = 150;
public static double SELECTION_PREF_WIDTH = 250;
BorderPane bp;
TabPane tabPane;
Tab editorTab;
Tab clusterBuilderTab;
Tab findClustersTab;
private Slider scaleSlider;
private Slider rotateXSlider;
private Slider rotateYSlider;
Expand All @@ -77,6 +86,18 @@ public class Shape3DControlPane extends LitPathPane {
private Label rotateZLabel;
private ListView<PointListItem> pointListView;
private ChoiceBox labelChoiceBox;
private Spinner iterationsSpinner;
private Spinner convergenceSpinner;
private Spinner componentsSpinner;
private Spinner epsilonAlphaSpinner;
private Spinner minimumPointsSpinner;
private Spinner minimumClusterSizeSpinner;
private Spinner minimumLeafSizeSpinner;
private CheckBox parallelCheckBox;
private CheckBox verboseCheckBox;
private RadioButton diagonalRadioButton;
private RadioButton fullCovarianceRadioButton;
private SplitMenuButton clusterMethodMenuButton;
/**
* Format for floating coordinate label
*/
Expand All @@ -85,7 +106,8 @@ public class Shape3DControlPane extends LitPathPane {
private Manifold currentManifold = null;
private final String ALL = "ALL";
public static Color DEFAULT_MANIFOLD_COLOR = Color.WHITE;

ClusterMethod selectedMethod = ClusterMethod.KMEANS;

private static BorderPane createContent() {
BorderPane bpOilSpill = new BorderPane();
return bpOilSpill;
Expand All @@ -98,14 +120,154 @@ public Shape3DControlPane(Scene scene, Pane parent) {

bp = (BorderPane) this.contentPane;
buildEditorTab();
buildFindClustersTab();
buildClusterBuilderTab();
tabPane = new TabPane(clusterBuilderTab, editorTab);
tabPane = new TabPane(clusterBuilderTab, findClustersTab, editorTab);
tabPane.setPadding(Insets.EMPTY);
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.FIXED);
bp.setCenter(tabPane);
}
private void buildFindClustersTab() {
findClustersTab = new Tab("Find Clusters");
BorderPane findClusterBorderPane = new BorderPane();
findClustersTab.setContent(findClusterBorderPane);

componentsSpinner = new Spinner(
new SpinnerValueFactory.IntegerSpinnerValueFactory(2, 20, 5, 1));
componentsSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
componentsSpinner.setEditable(true);
iterationsSpinner = new Spinner(
new SpinnerValueFactory.IntegerSpinnerValueFactory(10, 500, 50, 5));
iterationsSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
iterationsSpinner.setEditable(true);
convergenceSpinner = new Spinner(
new SpinnerValueFactory.DoubleSpinnerValueFactory( 0.001, 0.999, 0.005, 0.001));
convergenceSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
convergenceSpinner.setEditable(true);

epsilonAlphaSpinner = new Spinner(
new SpinnerValueFactory.DoubleSpinnerValueFactory(0.01, 1, 0.5, 0.01));
epsilonAlphaSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
epsilonAlphaSpinner.setEditable(true);

minimumPointsSpinner = new Spinner(
new SpinnerValueFactory.IntegerSpinnerValueFactory(4, 1000, 10, 10));
minimumPointsSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
minimumPointsSpinner.setEditable(true);

minimumClusterSizeSpinner = new Spinner(
new SpinnerValueFactory.IntegerSpinnerValueFactory(4, 1000, 100, 50));
minimumClusterSizeSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
minimumClusterSizeSpinner.setEditable(true);

minimumLeafSizeSpinner = new Spinner(
new SpinnerValueFactory.IntegerSpinnerValueFactory(4, 1000, 100, 50));
minimumLeafSizeSpinner.setPrefWidth(SPINNER_PREF_WIDTH);
minimumLeafSizeSpinner.setEditable(true);

parallelCheckBox = new CheckBox("Force Parallel");
verboseCheckBox = new CheckBox("Verbose Output");

ToggleGroup covarianceToggleGroup = new ToggleGroup();
diagonalRadioButton = new RadioButton("Diagonal");
diagonalRadioButton.setToggleGroup(covarianceToggleGroup);
fullCovarianceRadioButton = new RadioButton("Full");
fullCovarianceRadioButton.setToggleGroup(covarianceToggleGroup);
diagonalRadioButton.setSelected(true);

MenuItem dbscan = new MenuItem("DBSCAN");
MenuItem hdbscan = new MenuItem("HDBSCAN");
MenuItem kmeans = new MenuItem("KMeans");
MenuItem kmedoids = new MenuItem("KMedoids");
MenuItem exmax = new MenuItem("Expectation Maximization");
MenuItem affinity = new MenuItem("Affinity Propagation");
clusterMethodMenuButton = new SplitMenuButton(dbscan, hdbscan,
kmeans, kmedoids, exmax, affinity);
clusterMethodMenuButton.setText("Select method");
clusterMethodMenuButton.setOnAction(e->findClusters());

dbscan.setOnAction((e) -> {
selectedMethod = ClusterMethod.DBSCAN;
clusterMethodMenuButton.setText(dbscan.getText());
});
hdbscan.setOnAction((e) -> {
selectedMethod = ClusterMethod.HDDBSCAN;
clusterMethodMenuButton.setText(hdbscan.getText());
});
kmeans.setOnAction((e) -> {
selectedMethod = ClusterMethod.KMEANS;
clusterMethodMenuButton.setText(kmeans.getText());
});
kmedoids.setOnAction((e) -> {
selectedMethod = ClusterMethod.KMEDIODS;
clusterMethodMenuButton.setText(kmedoids.getText());
});
exmax.setOnAction((e) -> {
selectedMethod = ClusterMethod.EX_MAX;
clusterMethodMenuButton.setText(exmax.getText());
});
affinity.setOnAction((e) -> {
selectedMethod = ClusterMethod.AFFINITY;
clusterMethodMenuButton.setText(affinity.getText());
});
clusterMethodMenuButton.setPrefWidth(SELECTION_PREF_WIDTH);

Label componentsLabel = new Label("Components");
componentsLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label iterationsLabel = new Label("Iterations");
iterationsLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label convergenceLabel = new Label("Convergence");
convergenceLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label covarianceLabel = new Label("Covariance");
covarianceLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label epsilonAlphaLabel = new Label("Epsilon/Alpha");
epsilonAlphaLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label minimumPointsLabel = new Label("Minimum Points");
minimumPointsLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label minimumClusterSizeLabel = new Label("Minimum Cluster Size");
minimumClusterSizeLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label minimumLeafSizeLabel = new Label("Minimum Leaf Size");
minimumLeafSizeLabel.setPrefWidth(SPINNER_PREF_WIDTH);
Label clusteringLabel = new Label("Clustering Method");
clusteringLabel.setPrefWidth(SPINNER_PREF_WIDTH);

VBox vbox = new VBox(10,
new HBox(10, clusteringLabel, clusterMethodMenuButton),
new HBox(10, componentsLabel, componentsSpinner),
new HBox(10, iterationsLabel, iterationsSpinner),
new HBox(10, convergenceLabel, convergenceSpinner),
new HBox(10, epsilonAlphaLabel, epsilonAlphaSpinner),
new HBox(10, minimumPointsLabel, minimumPointsSpinner),
new HBox(10, minimumClusterSizeLabel, minimumClusterSizeSpinner),
new HBox(10, minimumLeafSizeLabel, minimumLeafSizeSpinner),
new HBox(10, parallelCheckBox, verboseCheckBox),
new HBox(10, covarianceLabel, diagonalRadioButton, fullCovarianceRadioButton)
);
findClusterBorderPane.setCenter(vbox);
}

public void findClusters() {
System.out.println("Find Clusters...");
ProjectionConfig pc = new ProjectionConfig();
pc.components = (int) componentsSpinner.getValue();
pc.clusterMethod = selectedMethod;
// pc.useVisiblePoints = useVisibleRadioButton.isSelected();
pc.useVisiblePoints = true;
pc.covariance = diagonalRadioButton.isSelected()
? ProjectionConfig.COVARIANCE_MODE.DIAGONAL
: ProjectionConfig.COVARIANCE_MODE.FULL;
pc.toleranceConvergence = (double) convergenceSpinner.getValue();
pc.maxIterations = (int) iterationsSpinner.getValue();
pc.epsilonAlpha = (double) epsilonAlphaSpinner.getValue();
pc.minimumPoints = (int) minimumPointsSpinner.getValue();
pc.minimumClusterSize = (int) minimumClusterSizeSpinner.getValue();
pc.minimumLeafSize = (int) minimumLeafSizeSpinner.getValue();

clusterMethodMenuButton.getScene().getRoot().fireEvent(
new ManifoldEvent(ManifoldEvent.FIND_PROJECTION_CLUSTERS, pc));
}

private void buildClusterBuilderTab() {
clusterBuilderTab = new Tab("Cluster Builder");
BorderPane clusterBorderPane = new BorderPane();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@
import edu.jhuapl.trinity.javafx.events.ApplicationEvent;
import edu.jhuapl.trinity.javafx.events.CommandTerminalEvent;
import edu.jhuapl.trinity.javafx.events.ManifoldEvent;
import edu.jhuapl.trinity.javafx.events.ManifoldEvent.ProjectionConfig;
import edu.jhuapl.trinity.javafx.javafx3d.Manifold3D;
import edu.jhuapl.trinity.utils.AnalysisUtils;
import edu.jhuapl.trinity.utils.PCAConfig;
import edu.jhuapl.trinity.utils.ResourceUtils;
import edu.jhuapl.trinity.utils.clustering.ClusterMethod;
import edu.jhuapl.trinity.utils.metric.Metric;
import edu.jhuapl.trinity.utils.umap.Umap;
import javafx.fxml.FXML;
Expand All @@ -49,12 +47,10 @@
import javafx.scene.control.ColorPicker;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Slider;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.ImageView;
Expand Down Expand Up @@ -126,19 +122,6 @@ public class ManifoldControlController implements Initializable {
private CheckBox showWireframeCheckBox;
@FXML
private CheckBox showControlPointsCheckBox;
@FXML
private Spinner gmmIterationsSpinner;
@FXML
private Spinner gmmConvergenceSpinner;
@FXML
private Spinner gmmComponentsSpinner;
@FXML
private RadioButton diagonalRadioButton;
@FXML
private RadioButton fullCovarianceRadioButton;
ToggleGroup covarianceToggleGroup;
@FXML
private SplitMenuButton clusterMethodMenuButton;

//PCA Tab
@FXML
Expand Down Expand Up @@ -218,7 +201,6 @@ public class ManifoldControlController implements Initializable {
boolean reactive = true;
Umap latestUmapObject = null;
File latestDir = new File(".");
ClusterMethod selectedMethod = ClusterMethod.KMEANS;

/**
* Initializes the controller class.
Expand Down Expand Up @@ -394,31 +376,6 @@ private void setupUmapControls() {
}

private void setupHullControls() {
gmmComponentsSpinner.setValueFactory(
new SpinnerValueFactory.IntegerSpinnerValueFactory(2, 20, 5, 1));

gmmIterationsSpinner.setValueFactory(
new SpinnerValueFactory.IntegerSpinnerValueFactory(10, 500, 50, 5));

gmmConvergenceSpinner.setValueFactory(
new SpinnerValueFactory.DoubleSpinnerValueFactory(1e4, 1e12, 1e6, 100));

covarianceToggleGroup = new ToggleGroup();
diagonalRadioButton.setToggleGroup(covarianceToggleGroup);
fullCovarianceRadioButton.setToggleGroup(covarianceToggleGroup);

MenuItem kmeans = new MenuItem("KMeans++");
MenuItem exmax = new MenuItem("Expectation Maximization");
kmeans.setOnAction((e) -> {
selectedMethod = ClusterMethod.KMEANS;
clusterMethodMenuButton.setText(kmeans.getText());
});
exmax.setOnAction((e) -> {
selectedMethod = ClusterMethod.EX_MAX;
clusterMethodMenuButton.setText(exmax.getText());
});
clusterMethodMenuButton.getItems().addAll(kmeans, exmax);

//Get a reference to any Distances already collected
List<ManifoldListItem> existingItems = new ArrayList<>();
for (Manifold m : Manifold.getManifolds()) {
Expand Down Expand Up @@ -584,21 +541,21 @@ private void updateActiveManifold3D(Manifold3D manifold3D) {
reactive = true;
}

@FXML
public void findClusters() {
System.out.println("Find Clusters...");
ProjectionConfig pc = new ProjectionConfig();
pc.components = (int) gmmComponentsSpinner.getValue();
pc.clusterMethod = selectedMethod;
pc.useVisiblePoints = useVisibleRadioButton.isSelected();
pc.covariance = diagonalRadioButton.isSelected()
? ProjectionConfig.COVARIANCE_MODE.DIAGONAL
: ProjectionConfig.COVARIANCE_MODE.FULL;
pc.tolerance = (double) gmmConvergenceSpinner.getValue();
pc.maxIterations = (int) gmmIterationsSpinner.getValue();
clusterMethodMenuButton.getScene().getRoot().fireEvent(
new ManifoldEvent(ManifoldEvent.FIND_PROJECTION_CLUSTERS, pc));
}
// @FXML
// public void findClusters() {
// System.out.println("Find Clusters...");
// ProjectionConfig pc = new ProjectionConfig();
// pc.components = (int) gmmComponentsSpinner.getValue();
// pc.clusterMethod = selectedMethod;
// pc.useVisiblePoints = useVisibleRadioButton.isSelected();
// pc.covariance = diagonalRadioButton.isSelected()
// ? ProjectionConfig.COVARIANCE_MODE.DIAGONAL
// : ProjectionConfig.COVARIANCE_MODE.FULL;
// pc.tolerance = (double) gmmConvergenceSpinner.getValue();
// pc.maxIterations = (int) gmmIterationsSpinner.getValue();
// clusterMethodMenuButton.getScene().getRoot().fireEvent(
// new ManifoldEvent(ManifoldEvent.FIND_PROJECTION_CLUSTERS, pc));
// }

@FXML
public void exportMatrix() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ public static enum COVARIANCE_MODE {
DIAGONAL, FULL
}

;
public boolean useVisiblePoints = true;
public ClusterMethod clusterMethod = ClusterMethod.EX_MAX;
public int components = 5;
public int maxIterations = 100;
public double tolerance = 1e4;
public double toleranceConvergence = 1e4;
public double epsilonAlpha = 0.5;
public int minimumPoints = 4;
public int minimumClusterSize = 100;
public int minimumLeafSize = 100;
public boolean forceParallel = false;
public boolean verbose = false;
public COVARIANCE_MODE covariance = COVARIANCE_MODE.DIAGONAL;
}

Expand Down
Loading

0 comments on commit 0f7e7d5

Please sign in to comment.