From c12f3ea43665b0bf38166489f42a5992b4cc7db9 Mon Sep 17 00:00:00 2001 From: Michael Beyer Date: Mon, 21 Jan 2019 19:08:09 +0100 Subject: [PATCH] model and clustering visualization: proof of concept --- pom.xml | 14 ++ .../clustering/AutomaticClusteringHelper.java | 2 - .../clustering/SimpleModelViewer.java | 43 +++- .../middleware/helpers/ComponentHelper.java | 8 + .../middleware/AutomaticClusteringTest.java | 200 ++++++++++++++++-- .../clustering.midSizeDemoCluster.png | Bin 0 -> 11434 bytes .../AffinityPropagationAlgorithm.png | Bin 0 -> 11734 bytes .../DBSCANClusteringAlgorithm.png | Bin 0 -> 11069 bytes .../MarkovClusteringAlgorithm.png | Bin 0 -> 11058 bytes .../SpectralClusteringAlgorithm.png | Bin 0 -> 11417 bytes .../clustering.unambiguousCluster.png | Bin 0 -> 6962 bytes .../AffinityPropagationAlgorithm.png | Bin 0 -> 6113 bytes .../DBSCANClusteringAlgorithm.png | Bin 0 -> 6064 bytes .../MarkovClusteringAlgorithm.png | Bin 0 -> 6962 bytes .../SpectralClusteringAlgorithm.png | Bin 0 -> 6821 bytes 15 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 src/test/resources/clustering/test-images/clustering.midSizeDemoCluster.png create mode 100644 src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/AffinityPropagationAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/DBSCANClusteringAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/MarkovClusteringAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/SpectralClusteringAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.unambiguousCluster.png create mode 100644 src/test/resources/clustering/test-images/clustering.unambiguousCluster/AffinityPropagationAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.unambiguousCluster/DBSCANClusteringAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.unambiguousCluster/MarkovClusteringAlgorithm.png create mode 100644 src/test/resources/clustering/test-images/clustering.unambiguousCluster/SpectralClusteringAlgorithm.png diff --git a/pom.xml b/pom.xml index a28f0d1..6c7631b 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,20 @@ ${Embedded-montiarc-math-rosmsg-generator.version} + + org.graphstream + gs-core + 1.3 + false + + + + org.apache.commons + commons-math + 2.1 + false + + de.monticore.lang.monticar diff --git a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java index 9d2dbd6..2dc1ed8 100644 --- a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java +++ b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/AutomaticClusteringHelper.java @@ -20,8 +20,6 @@ import java.util.stream.Collectors; public class AutomaticClusteringHelper { - static double MAXCOST= 999999; - public static double[][] createAdjacencyMatrix(List subcomps, Collection connectors, Map subcompLabels) { // Nodes = subcomponents // Verts = connectors between subcomponents diff --git a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java index e78ecb8..4bf0b22 100644 --- a/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java +++ b/src/main/java/de/monticore/lang/monticar/generator/middleware/clustering/SimpleModelViewer.java @@ -1,4 +1,43 @@ package de.monticore.lang.monticar.generator.middleware.clustering; -public class SimpleModelViewer { -} +import org.graphstream.graph.Graph; +import org.graphstream.ui.view.Viewer; +import org.graphstream.ui.view.ViewerListener; +import org.graphstream.ui.view.ViewerPipe; + +import javax.swing.*; + +public class SimpleModelViewer implements ViewerListener { + + private Graph graph; + protected boolean loop = true; + + public SimpleModelViewer(Graph g) { + this.graph = g; + } + + public void run() { + Viewer viewer = this.graph.display(); + viewer.getDefaultView().add(new JLabel(graph.getId().toString())); + viewer.setCloseFramePolicy(Viewer.CloseFramePolicy.CLOSE_VIEWER); // set to "HIDE_ONLY" to allow for further communication + ViewerPipe fromViewer = viewer.newViewerPipe(); + fromViewer.addViewerListener(this); + //fromViewer.addSink(this.graph); + while(loop) { + fromViewer.pump(); + } + } + + public void viewClosed(String id) { + this.loop = false; + } + + public void buttonPushed(String id) { + //System.out.println("Button pushed on node "+id); + } + + public void buttonReleased(String id) { + //System.out.println("Button released on node "+id); + } + +} \ No newline at end of file diff --git a/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java b/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java index 493ab97..0a5ed07 100644 --- a/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java +++ b/src/main/java/de/monticore/lang/monticar/generator/middleware/helpers/ComponentHelper.java @@ -29,4 +29,12 @@ public class ComponentHelper { subcomps.forEach(sc -> componentIndecies.put(sc.getFullName(), i[0]++)); return componentIndecies; } + + public static Map getSubcompsLabels(List subcomps) { + Map componentIndecies = new HashMap<>(); + + int[] i = {0}; + subcomps.forEach(sc -> componentIndecies.put(i[0]++, sc.getFullName())); + return componentIndecies; + } } diff --git a/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java b/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java index 18f246c..6ca0bb3 100644 --- a/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java +++ b/src/test/java/de/monticore/lang/monticar/generator/middleware/AutomaticClusteringTest.java @@ -3,10 +3,7 @@ package de.monticore.lang.monticar.generator.middleware; import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ConnectorSymbol; import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol; import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.PortSymbol; -import de.monticore.lang.monticar.generator.middleware.clustering.AutomaticClusteringHelper; -import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringAlgorithm; -import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringAlgorithmFactory; -import de.monticore.lang.monticar.generator.middleware.clustering.ClusteringKind; +import de.monticore.lang.monticar.generator.middleware.clustering.*; import de.monticore.lang.monticar.generator.middleware.clustering.algorithms.*; import com.clust4j.algo.AffinityPropagation; import com.clust4j.algo.AffinityPropagationParameters; @@ -23,10 +20,17 @@ import net.sf.javaml.core.DenseInstance; import net.sf.javaml.core.Instance; import org.apache.commons.math3.linear.Array2DRowRealMatrix; import org.apache.commons.math3.linear.RealMatrix; +import org.graphstream.graph.implementations.SingleGraph; +import org.graphstream.stream.file.FileSinkImages; +import org.graphstream.ui.view.Viewer; +import org.graphstream.ui.view.ViewerPipe; import org.junit.Test; import smile.clustering.DBSCAN; import smile.clustering.SpectralClustering; +import de.monticore.lang.monticar.svggenerator.SVGMain; +import org.graphstream.graph.*; +import javax.swing.*; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -38,6 +42,7 @@ import static org.junit.Assert.assertTrue; public class AutomaticClusteringTest extends AbstractSymtabTest{ public static final String TEST_PATH = "src/test/resources/"; + public static final String TEST_PATH_PNG = "src/test/resources/clustering/test-images/"; @Test @@ -435,6 +440,52 @@ public class AutomaticClusteringTest extends AbstractSymtabTest{ @Test public void testClusteringAlgorithms(){ + TaggingResolver taggingResolver = AbstractSymtabTest.createSymTabAndTaggingResolver(TEST_PATH); + + //String modelName= "clustering.unambiguousCluster"; + String modelName= "clustering.midSizeDemoCluster"; + + ExpandedComponentInstanceSymbol componentInstanceSymbol = taggingResolver.resolve(modelName, ExpandedComponentInstanceSymbol.KIND).orElse(null); + + assertNotNull(componentInstanceSymbol); + + + // get stuff together for adjmatrix + List subcompsOrderedByName = ComponentHelper.getSubcompsOrderedByName(componentInstanceSymbol); + Map labelsForSubcomps = ComponentHelper.getLabelsForSubcomps(subcompsOrderedByName); + Map subcompsLabels = ComponentHelper.getSubcompsLabels(subcompsOrderedByName); + double[][] adjMatrix = AutomaticClusteringHelper.createAdjacencyMatrix(subcompsOrderedByName, + ComponentHelper.getInnerConnectors(componentInstanceSymbol), + labelsForSubcomps); + // build a graph from this stuff + Graph graph = new SingleGraph(modelName); + Node node= null; + Edge edge= null; + String subCompLabel= null; + for(int i = 0; i < adjMatrix[0].length; i++) { + node= graph.addNode(Integer.toString(i)); + subCompLabel= subcompsLabels.get(Integer.parseInt(node.getId())); + subCompLabel= subCompLabel.substring(subCompLabel.lastIndexOf('.') + 1); + node.addAttribute("ui.label", node.getId() + " (" + subCompLabel + ")"); + } + for(int i = 0; i < adjMatrix[0].length; i++) { + for(int j = i; j < adjMatrix[0].length; j++) { + if (adjMatrix[i][j] > 0) { + edge= graph.addEdge(i + "-" + j, Integer.toString(i), Integer.toString(j)); + edge.addAttribute("ui.label", adjMatrix[i][j]); + } + } + } + + FileSinkImages img = new FileSinkImages(FileSinkImages.OutputType.PNG, FileSinkImages.Resolutions.XGA); + img.setStyleSheet("graph { padding: 100px; }"); + img.setLayoutPolicy(FileSinkImages.LayoutPolicy.COMPUTED_FULLY_AT_NEW_IMAGE); + try { img.writeAll(graph, TEST_PATH_PNG + modelName + ".png"); } catch (IOException e) { System.out.println("Couldn't create image file "+TEST_PATH_PNG + modelName + ".png"+ + "\n"+e.getMessage()); }; + + SimpleModelViewer viewer= new SimpleModelViewer(graph); + viewer.run(); + Object[] params; for(ClusteringKind kind : ClusteringKind.values()){ params= null; @@ -449,24 +500,148 @@ public class AutomaticClusteringTest extends AbstractSymtabTest{ }; break; } - testCreateClusters(ClusteringAlgorithmFactory.getFromKind(kind), params); + testCreateClusters(ClusteringAlgorithmFactory.getFromKind(kind), params, componentInstanceSymbol, modelName); } } - private void testCreateClusters(ClusteringAlgorithm algorithm, Object[] params){ - //UnambiguousCluster - TaggingResolver taggingResolver = AbstractSymtabTest.createSymTabAndTaggingResolver(TEST_PATH); + private void testCreateClusters(ClusteringAlgorithm algorithm, Object[] params, ExpandedComponentInstanceSymbol componentInstanceSymbol, String modelName){ - ExpandedComponentInstanceSymbol componentInstanceSymbol = taggingResolver.resolve("clustering.unambiguousCluster", ExpandedComponentInstanceSymbol.KIND).orElse(null); - assertNotNull(componentInstanceSymbol); - - System.out.println(algorithm); + String algoName= algorithm.toString().substring(0, algorithm.toString().lastIndexOf("@")); + String algoNameShort= algoName.substring(algoName.lastIndexOf(".")+1); + System.out.println(algoName); List> clusters = null; if (params != null) clusters = algorithm.cluster(componentInstanceSymbol, params); else clusters = algorithm.cluster(componentInstanceSymbol); + double colorIncrement= 1.0/clusters.size(); + double sizeIncrement= Math.ceil(50/clusters.size()); + double color= 0; + double size= 10; + + // get stuff together for adjmatrix + List subcompsOrderedByName = ComponentHelper.getSubcompsOrderedByName(componentInstanceSymbol); + Map labelsForSubcomps = ComponentHelper.getLabelsForSubcomps(subcompsOrderedByName); + Map subcompsLabels = ComponentHelper.getSubcompsLabels(subcompsOrderedByName); + double[][] adjMatrix = AutomaticClusteringHelper.createAdjacencyMatrix(subcompsOrderedByName, + ComponentHelper.getInnerConnectors(componentInstanceSymbol), + labelsForSubcomps); + // build a graph from this stuff + Graph graph = new SingleGraph(algoNameShort); + Node node= null; + Edge edge= null; + String subCompLabel= null; + for(int i = 0; i < adjMatrix[0].length; i++) { + node= graph.addNode(Integer.toString(i)); + subCompLabel= subcompsLabels.get(Integer.parseInt(node.getId())); + subCompLabel= subCompLabel.substring(subCompLabel.lastIndexOf('.') + 1); + node.addAttribute("ui.label", node.getId() + " (" + subCompLabel + ")"); + } + for(int i = 0; i < adjMatrix[0].length; i++) { + for(int j = i; j < adjMatrix[0].length; j++) { + if (adjMatrix[i][j] > 0) { + edge= graph.addEdge(i + "-" + j, Integer.toString(i), Integer.toString(j)); + edge.addAttribute("ui.label", adjMatrix[i][j]); + } + } + } + + // style (colorize + resize) nodes for clusters + Node n; + Edge e; + String nodeName; + String nodeId; + Set cluster; + List clusterNames; + for(int i = 0; i < clusters.size(); i++) { + cluster = clusters.get(i); + clusterNames = cluster.stream().map(CommonSymbol::getFullName).collect(Collectors.toList()); + for(int j = 0; j < clusterNames.size(); j++) { + nodeId= null; + nodeId= labelsForSubcomps.get(clusterNames.get(j)).toString(); + if (nodeId!=null) { + n= graph.getNode(nodeId); + n.setAttribute("ui.style", "fill-mode: dyn-plain; fill-color: red, black; size: " + size + "px;"); + n.setAttribute("ui.color", color); + + // find cutting edges and delete or re-color them + for(int k = 0; k < adjMatrix[Integer.parseInt(nodeId)].length; k++) { + if (adjMatrix[Integer.parseInt(nodeId)][k] > 0) { + // target node k is not in current cluster + nodeName= subcompsLabels.get(k); + if (!clusterNames.contains(nodeName)) { + e= null; + e= graph.getEdge(Integer.parseInt(nodeId)+"-"+k); + //graph.removeEdge(e); + if (e!=null) e.setAttribute("ui.style", "fill-mode: plain; fill-color: #F0F0F0;"); + } + } + } + } + } + color= color + colorIncrement; + size= size + sizeIncrement; + } + + FileSinkImages img = new FileSinkImages(FileSinkImages.OutputType.PNG, FileSinkImages.Resolutions.XGA); + img.setStyleSheet("graph { padding: 100px; }"); + img.setLayoutPolicy(FileSinkImages.LayoutPolicy.COMPUTED_FULLY_AT_NEW_IMAGE); + try { img.writeAll(graph, TEST_PATH_PNG + modelName + "/" + graph.getId() + ".png"); } catch (IOException ex) { System.out.println("Couldn't create image file "+TEST_PATH_PNG + graph.getId() + ".png"+ + "\n"+ex.getMessage()); }; + + SimpleModelViewer viewer= new SimpleModelViewer(graph); + viewer.run(); + + if (modelName=="clustering.midSizeDemoCluster") { + assertTrue(clusters.size() == 2); + + Set cluster1 = clusters.get(0); + Set cluster2 = clusters.get(1); + assertTrue((cluster1.size() == 3 && cluster2.size() == 4) || + (cluster2.size() == 3 && cluster1.size() == 4) + ); + + List cluster1Names = cluster1.stream() + .map(CommonSymbol::getFullName) + .collect(Collectors.toList()); + + List cluster2Names = cluster2.stream() + .map(CommonSymbol::getFullName) + .collect(Collectors.toList()); + + if (cluster1.size() == 4) { + if (cluster1Names.get(0).endsWith("comp0") || + cluster1Names.get(0).endsWith("comp1") || + cluster1Names.get(0).endsWith("comp2") || + cluster1Names.get(0).endsWith("comp3") + ) { + assertTrue(cluster1Names.contains(modelName + ".comp0")); + assertTrue(cluster1Names.contains(modelName + ".comp1")); + assertTrue(cluster1Names.contains(modelName + ".comp2")); + assertTrue(cluster1Names.contains(modelName + ".comp3")); + + assertTrue(cluster2Names.contains(modelName + ".comp4")); + assertTrue(cluster2Names.contains(modelName + ".comp5")); + assertTrue(cluster2Names.contains(modelName + ".comp6")); + } + } else if (cluster1.size() == 3) { + if (cluster1Names.get(0).endsWith("comp4") || + cluster1Names.get(0).endsWith("comp5") || + cluster1Names.get(0).endsWith("comp6") + ) { + assertTrue(cluster2Names.contains(modelName + ".comp0")); + assertTrue(cluster2Names.contains(modelName + ".comp1")); + assertTrue(cluster2Names.contains(modelName + ".comp2")); + assertTrue(cluster2Names.contains(modelName + ".comp3")); + + assertTrue(cluster1Names.contains(modelName + ".comp4")); + assertTrue(cluster1Names.contains(modelName + ".comp5")); + assertTrue(cluster1Names.contains(modelName + ".comp6")); + } + } + } + if (modelName=="clustering.unambiguousCluster") { if (algorithm instanceof SpectralClusteringAlgorithm) { assertTrue(clusters.size() == 2); @@ -522,6 +697,7 @@ public class AutomaticClusteringTest extends AbstractSymtabTest{ assertTrue(cluster3Names.contains("clustering.unambiguousCluster.compC")); assertTrue(cluster4Names.contains("clustering.unambiguousCluster.compD")); } + } } diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster.png new file mode 100644 index 0000000000000000000000000000000000000000..10f3371181c4f545555f22c09ec288f43323a8b5 GIT binary patch literal 11434 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 YB&MwHlnH={4Zg)Wb@pVTp;OrZ0BehiEC2ui literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/AffinityPropagationAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/AffinityPropagationAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..b0cf79fd2a042e38aa46eac41a07c56d1a13fe34 GIT binary patch literal 11734 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%0DZN9&n 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 K$gpxh`M&_$E!PhK literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/MarkovClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/MarkovClusteringAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..b42c55d09543c84ac42b080051a48702db29e474 GIT binary patch literal 11058 zcmeHtX*`u{_wc&+HWL}6kW#6XA!#!0bV|eN(5MhnrVNqF6z(QXrgKu#U^u0mGITOT ziCscPXd+W~c7t5Ho)^(q;`o&_B#8e3Y zn6z%~Pn!XR@R12(B6xf7Kyebl{NZ&!t=t-5-`Obn+m&Z>!d4f%W*4dPB8^{Os?-rW zZ5i_Y>S+nd6SG)esliPKZ}uajW55|-Y9RPZJ{syFZdN;HdXr=sB6wG@X6FLNRO`Z{XrvLR^ zeD34e9~3O~b!9-f(yHO}Ot5LG6auM*;oonGLiUqg;-I`;ifbtHe}B#?E$^>NEdTRM zlb`>*PTNR6aO^X_0sF4gFh$C`wVIs690_A!HmP2``j*-L%4oCI!XjN~E4T&mSy z2HYYaPCU2yN4qb2$w*q*^)s-h>fdNKba`__43gBk)>3ewu^H@9vu!P%YxG2^!aw z>occz|2(aV$!=;q19Y|%J$~?;;l|KuKnFD*1-joU$!t+nkRoJKVCgqR!H(0ES=61m z6a-v0k^xrF)@4Ht0LIVNL_s59MXVV=5I%kKX2RsRVxNeVhxz50*>aBM$pLMYUre4? z1$0)4I{w_H1F%w(YTVBRA47jdpp!0e(uIJtz6+C<^&`q0yW#zks zz%h9srfEb#RqOaq2KuBrBez)uIPzVp$nr?YV>Wy;EdbtYzth_paJOU^132Ykl#MK~r}~Qmt$SU_71?KZNdY*lk$ibwO$J)?v;mH& z=<5-7moCiA4r?a8SPx*|C=O5;%A$a5bqIh%#jQg-dgB+1srh&g&@^Z*nog28NH=h-zO zLK|kbG98U^?z4iPc*mp1%I9$M#U#W${6!q}q^3YzeTY*`1;2>=UnBF-~9o6h+{*+}gy}tIW4t2`RWSJEG>} zeMI;8VbW^x%s0lql{%$ z7ZhOax}82@EuisU8c+j%+Q_+=yjYrXm2<|AQHmVbZ&?PAe;2zyzc6M2c`S~1{T z>ppR~p{W;TAq#h3Zj*zx7FMc++r)Y;XsFHb^(=It7g<~2Gv#{(=K)8{dNsa0))U2f z;Gc0CKVHUe(?UNwEPpCSUF`VsVVsn>u*XD9z7!`1&ngus!`jvGlPv80{6Z9N%;fd{ zE(D7Xyko*<*^wRJ`(SU&Spx5oMNSP#>lL6eX-2f6|3xSg1U3i4+fVa_;D)a?dylGX z*qV#G!@42hthO}7lrCy4NgA?AD7$RZP+eqM$W{ItElq3X)dRTy?69(9pmi)DKh!Hf zTSv#>BG2I79FgHHCg+!oMVNrz^e=t;GOV;Lg-p4MOBc-yHc53`Pof{@Jt5rZd~yc! zvh8f9#HrMFJhTsO*1y|q2pL~+sBY*lG=EpmeNCpN%aJ~R_2VPEn4EnX3y9R2ThsrV zng>qL;CE3zv#k|DQTs9)GxG&np-LDCH#?%P|TzK`PE}I6t zw72*$`Ye~tC;o@ep?xM+akiq}Mxc{7K9Qet`si3YR33k6fAc`!lnn z#aL*}NuHlmJ7gBE^^{fQ55^^mqM%%}<#BXwLE%u~TZQ9=)iQ6td`0#t`TjHI?prx~ahU)0q>lC#IQLlNhNWa1@`ba9^Le_0z6Y;=eD@+rI@ZqMsLW{9_iJ-7 z+Ajh0pLsZ5#gmr})kz4lcw_Y)PMfUiMd%T`c#7UFcCA~y74q{(2#II8P#CV%!hrC+ zW^q0&$kzmfA0D60V_LOT0!XdoD2`D9RcZ`SHvhuW<}vS{G7<*86A{#o9#M{%zZ5u% z#UH!Nkzs2!E(dzXUa_ag%)mA8IKV39<;s!*oYdEFQa?FO?L-Dry0C`UNIpj*e;-#0 z9xOHAk+5o9tc=0B(zqMwt^w7{!eNlm@(U9VW|s9V;4ym^`>R4yu*pfc$J1$>#y~3(qxxV+>P{}I<6UmyA0$5(~p{8zsB-Q~upwsQ}ap!%xv7c({gOLI`CX?`#T zFHci~+rB3!2(CO-hUybo8LB5Jxn-0p&!9hYwojXTi(RZU*``wfSfbYVV*KlY!uOhk^3X{?QvUcb%=m!s&fWB?ZRb(wv+!`!`3u%zXR# z(cUtk`?sP0o=@TKCuzoi1mpHZB87=yYx6ekc#Ih!(jGLZ%gXrL{`$HD3p5(Y%{$iM*Mwcx!AgH+srg0VTu zdw?p~ohUDrXl?ujF9U?r7CEa$&`zIeXlH#gs_?#!@WVugtr#Me2r}QAhpC5UOQd{8 zDQ?lIEH4(7c@eAPdeO6L9YU!>#1MCNW{fde4TNDfUaFT6&+=9qkM6xIWVUYnN zNeD8XgY;&E-opN)1KP(Y9hd2{0xC(D_^XG4N6<8oo=~Pmm`|g;5MsbX;)X7Q?p`7C zQV?{QaAF<=$MZ=OTjk5czk0==$-DjwzEU&Xp5TjthuV(V`I<;BG=f zGqhMwo$y8yUZS)aW90ZVAv43_j3BWdL#NsAi4)Tiu1t)XETAUZO0$L}#tAGDVZY%h zY7qBl5vSD=y!J(=fXUJ!PwR2G>`+8Z3@RE%9U3!`@)H@Ay=dXHb*SeIS`&lW3*^u? zmOWu3qufZ?*xF;3Z-yEK9)ao}gPu(2f83-Ba;ImQij3ru`jMgw^UgEboG+XV6xrDL zoN{!`bl@kBH-r&AUw)AWrmY})oXEOjv>c@73mnE3|9<(pi_+7r(#)9Hvp|0t$`(;5 zHUk=s5z!oD!ffJX1isBh)7UJu*fEme@EbwQk7tY$bd%@-2JY@^bm&?i`8G%-+ODG8 z-;&X^C>&Ha@vZF=3V2WAdksg1)LwjM!hWGF0fE+-kFupV*r;ov7l0UFHK zB80L?$cse*D~MKC)SAQ$9YAIxPYp+h&LU*Xkc?CqFctT{kdcJ?!-@WF1UZ)=ePsC`TDgK0kiEwrk zUaaDg!FVMyC=$tA2`f3S+{1HZ$ zHrnDNkG8v=K`GwE_H;6~B}7^EOr*4%5JSQuiN>?;5C$@7tLGEsPejK}g2Y+N>L5sp zAXnh!DlyZ9m@Yn%&mf2xBTwv`f??#5Z=nAtjLLKpM%I30?xE0>HZrHA$!t(Z!#Ns6 zo+e&YiFJ>NkG~K%3=s4U?SdepUyX?MN3rjFCMM&LXTqULESQEIdc^Uxi6L8yy6)mv z1#2*FGy|0?6QvReX1n2KGJ3;4O^Aqu>qKY&(BV{6wk8EX+FRCbx{=4+o%x=)3tDLQ z@JlE_r@C<=pfa{dUjC&smUN?MLrMB#Ioy}>pK3>}j z6pNd#Vfm<`2;Svmd7%r_ntT5ShYv|S?c>?>h6;HceUL3^X!T5UkR0muZO_SiJ@Wbo zYk0Qe)v%(P%t+?;=JA;p&Esu@z*2V+2PtO*Uf6Ko5!LDSBWVUv1w+4jQS^}t1!(*5 zp}I)nek6KyUB7`!+K_lcu(RDT3dw0ogJF~x~dKSls!e*+x1SSE)Z zc5$dGKqcE;mVv^6@`$VJ&}fAR1vbv%Om1zO`|`p5$9*!iTZqY z2$RE^x=Aea>!|fkRQql06}p|Qo5Xf>^4S&6~o%1^M%AGa=?0xZc2ZxE4Nbwo;# zVl}O{+nLqMkHdN!WeyH!4A)^dRPPG)eqCGUox#9N{xE=R{7Wd9(SJYrWMA_WQ_MQMmoO)b z@jKl=K6KkX9iS5Fc?WiO6)^2F)UbUgwa>FPCd}8CJVxl1qFMQnAsm4Q{_g=-i`RzW1kk%^v_XW1BL&1&C7k>oHVY}A6L_q zo^570xMy*CVsXuo46dC!rL4*p^~9AXZWIVqjt{=BA2BMa+RCuI9_LqE9?F0P@sHuq zQ?`S$et(>KZT*8s9j%9a)4a0m?q}yzm_E9C-+mAq4bP=SQn`vrOV&v3#2u#h!W$0! ziB5f8&70*N?pS)cxahI_>_57fot~jRF}rrodzu)iYUb(@?`3i=D`8L(99=}1Z+j)I zrfG(!VO3!*(cy0#I+UN{{Gp@w$Ut5du)JM#S!$U*{VofR4;rsu-%*>Zo349u^z~mY z|39nhmwWDt9+iSmE`yA>eG*n4+^RRD$37-_RZetMsxoT(-gSgBAu#?C98x?MmVHa? zeUKnRvpZz3?TD#^Nw*QioUfCtj!0+a+!9ODH3>`n7cAB!dA+P&xNKpuY!n60iYtWO zUVLDs2(>kfH8+cuVVSf03uEzTx1`NN{qC{dp0Qkj1A2bc<-T>P2y%|?-uEw%-<+-U z81+l*hYad_0l14isLOtg@XD`L?GYZO-kx*cm|4f=lV} z@{U?sER;DLyQXj>JHGVE)#`TEk>diFDXcyladY#!(4AFpzUOhPHW+LD=t|%>yJbZOt8WoF#JdJ(X!xPswkf9L4Kcl_H6`<9<6!%7ouoLs1b$)7cDi1D;X z15cOX?&UAofX3oDNbhcKQXLPX!-g8`vF8px;qk)NC+&|}eUa1X@U^f_F6uXsbJ$Xk zW-Z8)OqymQ{CfnY)6kpYtByx)@hQ;MroY2^9%GRz*S=C1OOGrCBoe*z>pA+@=uY{+ zLE4W_-H|Q+?pEd}+={*RUsWWkFUTHyw{|vrZkexPtMFf-Uyr>LTg~bo4V&^e=-R6% z+cH<)be}P^VZKLtMw2I+TQ=NGAHFd`Ko$1r} zl#kX({0&Q4@bPxZ%%mK?`-MIs$S(aR)V4X-=iPeQ>KCE?>_7(C>^aOhIL{;Rwi??b z^lz4CBCDh@*pxIeHqf}DRr&rOOsyp|9qp}y{uj$`v7N0YaxBV6 zt(5_(FFl2n!|6lzQe(YWOOH9Cj=b!Rhbu+F>PnrMo)pz_HK$Lj!l8J%5bQIUMupGZ z8{(qMBu|BF?(Os+$lokf&^iWS7=IEQjdut0 zJB-EiyO%E|OZp6U%!xEF|3uBztav}NmMbx&);$aAe%^Ooj8~bDRlU9>6+UZk`W?k* z9wFWySDBEclr`CL$Fc1@JNteYOC49kGq(7n*l@iUx^MQ8`}d~j>@__MN$LhjWL{&L ze(ikPYF6nU5%_Arq{6pL*!VW7nsY}|Od7H?K8JMk8`Uq6lydmeQCM|urMAe+*L|Dp zro;umDh@5{cHO!&N*>(K)rsn5Z+@?vZfiSHVYGQA8VG%Q6^%!((G#ectXYz&I1{$+**=Gk- ztf059k+Ho#y;&vwA}s8Z2faCj^4H>;XJv;|l+Mwld{92GA{w*)+{bQ->*}@FY*x6Y ze-j10B?i(O*JO)pPM7t^DxIb46J_9&V<~mRz~=qb&VYS?>=b%CVh0*i2-&MAHEv~F z@Kg+xTJ)*1FGpk_rwCQ=%NcMme8?l#B4QH zSLr6qWpssx0c1_mNdLgJ?jCKvZgh?&g`cBKE_LIbALnaebMP(v%_hkCCcb;Te_%l0 zYgTJ6e%CDuclRQ!W)J79Z=%IvXIYGAawzfum+HNh9Y2 z!+QVdl~;8$IixiHpkEG~ji%Kkye$ts(AE0&O)UkU2z%Wz+h^cj;@H4CSszk`KdM+n z_XmmDnS9gih*F?}u_wCh#RBHq%v0=l#isYXEM@QF27rC5ViAPiS9@UdVX0}>HleY= zu9sLnCh9!|UX%B;4-#klJp|rGAw_;Y(R-44500g`c3aiopNc)o*nDl~nD^2fIBIsS zMQq$ux#U)M*?@LdNib+A?->| zU{@(P*htRB`fq7)NV6@sXk%@$zg>}C;b?DxYVFGoihj@-Nfl-*SlQH1QSTWk9EzB( z+hC7>O?b=@RB%)BOm&!e_^?!DhuiEM+4rV5b`R_tA1?Hd;Q7hMZRn_ZGV<=nZx;}d z@KB#^E;%FRWC+JJv)g~Ul4Zg4leKZNHJM%xHbr)!Ljj#RDFc0ez}kgYNiIAQt2B7D zW5KH4FaD!Jp(~cYBw-{!?URALfj@eKdHLVA(vT-y1EKOJ=Rkm&4kvU~zV`SUr1QH3- zftF$F4P)B4fu3qGl|H7$Y(Ae@vf?Q2nXjoxvmT$PdY)Ic7~%V~rxBu#CGyEVXR0{5 z<1b`jugf6wZp#^(_tbUooE|**cU~fNx2206Ff{5q8{cE&)u;kr>(dHWnOuiq3I_GJ zFao$4tiak)7Y)Q8DXkFaL>e(}az-)(q-)GWq{dS4y!X9}h;UD-+RDDDp>G?ddV8d!(V1) zc0bcf9v&E@8RrW85LL5u`Emv>^FrygJ%%~GBnhiCdNJI|kypU+M z3iQ>AF_3;g?b49=3|7+2L6Npc4NWCQ_?yM#ctwV4wqol2gregpj;)mS6Ma0F zF<8@QEz85b&yzG+PLlu8(I!9FVQVz6Qt!ABk2Q~WP^%Ee*NnYu#Cwiz*3eBiT94Lo zbX+dMbe?Wf7=PH^aVQg4Fyj(Wc}}Xem2TwzE3vC^JbH(k&xWQ&N;Tb~s*kY2Ko=6!pHd?!y{_kn#=Qgu8~eQ*uuW23R@R&2TlC?P zF2EjrjO|y&-)c)K|5KgUyTdR&!H83?XwMn>$6@W2)ghlAMSDsDE3txy_3VFtApGy2 gl>aaP4&{swf4r`0d{2pwlMmLd{^h3(bH`)<1Io7cnE(I) literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/SpectralClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.midSizeDemoCluster/SpectralClusteringAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..bc4a0be31719ebdff3feda159703f495df1710c2 GIT binary patch literal 11417 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 z z$!0l~YYoxV!d|8q@DL!2?WOF1U{8e{0#Wp=zdy63G65NK+ySLlBzb! zb{(wpJTt=Iic~q^*P2OHu?>{Huq}2`FC&$lpxlE?s+xt$1a1e;9lX#A?W0CUJst?D zlo~hR$Fc$PN|p=6 z%$9R7eZrKBdtw@o>`67QG4HS{UC2Q8nO~tM)i)m*78mqOC5@y8-L;E72b zcq&sg@?-f*J7-vx(lQ`K_lg{EYU@I@cS`DfJOW{xGN(J$1dW`C=UF17%|>O8mUfMx zB?M8etoy`cJEF?2O0`B*JMmmDNp<+k$g{9bVog`MB2{_v6?QJLD&XGDoT;+m zfRa^?s_lqsQf_bO3aM&aTZCv8IR4a@b4b;2otH;2Y>Um$O?5%4Sa^;lGKV}qJ~A7& z%|_LXqVPI*59G60Zc-}wTS~{dmQ#su`uzl>K1WeLCD4HUo%0$$XlZ}VP!-?E?(Cb0 z(HTG{WiJJj%xFpb&kNyTvQvehtU7wN__e}cHQ@(OjTedafBh;_cqdQO2a+1iUGc

^wr@H>qNG)ZbiXoa&E`oZDvf63B=(Z zq%0u4`DIO}HVf2FDr!OL%mKyxeKXo_=FV^_<}X~~D*RS2T<8C6fp2$?|1~MazaP}b Vwq30T-&7d3J7jNg_4e4){{vk|WN`oh literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/AffinityPropagationAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/AffinityPropagationAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..b425daf5683a8b2463ddab429a8bda502f436e04 GIT binary patch literal 6113 zcmeHLeNa=`6+e%s#HAZzTj>hKWUcLV>`uY%qK1zUc9$;e(t+A_>uSJ+DKqLsmavK< z1oA8$Sz92TvL#kaf=g|7b_sD+Oh9PjT0z#)IAk{<4dmGox7ww%FY-fFxywAPYJ8|h^cTvvgy`w)Xe&aXiY|*Pcp*}aZ9;v3fb2zAh z@oh9MB4`R&5>3}_py|dJX`1+vaLIp}49EQSdD<)>M~8$sTx`eVlNbI##n-N94f!dK zwMAdYf)Ym=11ff=hLu*{n~KQ9OlWRia4NcGj1TN{H`yTliuXixuXU%l?o4N?Gft|Mp_IO%yeru-;1Fo)5V&-GwxacTFr9`4!@_pKKR^M~IGt>EEB> zLIVS3Y5{p7?AET1sI4;(1;%6`-;q57BL99{(AO(q#(aSi$0Gv+rhi?&1*C09r`4>O zV~1cx`2a=0bV&AhAUe9KN-6|`RG*_|@t6&V3YsSCD5$RA{^v zS{7_ZYhNnUJY9?V7n?ffQSL=_S5_l2t2+rif0d;qi68M+SrYai%@GtRN4@T4ssefa zW8ZTd2=%v;DejVcO%s(I7Mh*q9;d{0!5jUp#EquhQ7(Y!2~MZJ-+fP6`#ncU7XM9_wA{}&)W+uYhtM206|TpmvpuZ>0QYT8E(*|wjflXDMKmPF|k zOA^VD*anG{A#o-qZiB=HnRpr`9!2OJNIZ)Ej~+#2S5H~IQZQH8tV0OJsPP_FG=b{2 zXg?6;IjNK*d}`vxtwvWg?N4exmFnOS#5Y8C5 zAxVXIe;SalM?*fRJ>0};-`!tFZ~~==9R&3Wje!crMDGBxJBJzZt+~(`2jW?kEyIQX z2Hw3~kwTQ;qMqoMfI&f9sukByi;i%pJ;rET0ltxieNJx0sC8?6CWJ zINX$K)3>;VI)*q?UWvg;|A$rb`>hBbZCOD$6nP<1&JAK@8G?%@{P_sTQc(QGeRb2j zJ-BBNIueMqv(3<1jJ8HFa-5b*b5LK8cPw6GLjZTDPlB{I_1n`hC^YdF6tBl~Nb}U> zPLQ5hISHom3(0{WLPdsOBgt}M|; zIM8`_HWeC#jw=FYbQ_w|z;Qheb7S3A@(G?v9k67WojTQRS$gX>o)M>h{*iHM+?2FU=h8ioSnh zW(QxTV=goq=dLoq|G1O3o3X6#A3_5zMrAkFWHl&-Q0-=>$r1q*WvjzTW^+$VYFHxq z^go!|HeUGx%zepQ$kqy7xl((0w04vO4ej~aF!EU)DX$JPV1{-FBrZ#oJBbRy|9V+c ZthwD?#<5eLDDBNXKPoF8cuo9^e*katEp`9^ literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/DBSCANClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/DBSCANClusteringAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..0c3275a3633d9f6e4d75ca8d97a5e810c474c045 GIT binary patch literal 6064 zcmeHLeN>az8Gk}7Va_DZtxz~INZHm?se-uh6+&RTLp`UZZndM5B5GyX7Lk<95_m(B zwkv5@YueLVrsZ=xXZBcFWYi1~KTZWmYu>lF;(++j z($v!HcgG$t|0TCo-?F9TAA8$)DQ~<_Ex|M|2|l}P@K@gLjs?>YOxT*~M-3EZl87dW z&2AQ}>s{CCFS_)Mu>S~cCujQ4Yfx@CV0-4W5Dv*St`Fm@?o7ZE;mKZQ?^_+)zi~fYO&hL;G8=_$L~*E3%F|9 z@Cg>hrL&jtyn6rghiJ_*&l(&*N1m)W$rjljr%dxIHozxXJ`^ohJY&3BFPtSvC7jc?v&fu6|i&TpveY@+k%&{iKeMx6~$23f#($ zCHe$ry7N$PILcG@)Jt!g?Uy?>nT?~#An$ReWeBS}NGs-WbbuRYH*{$fwo1#O=~HK5 zkpzd8bxORrwtbH04j5ugqq2W~G#(#zmUasoJdcJ$uO!cr1+;12LJ0BPX0GOx4NySN zk#drpEBY*1b9DK^l3}(~U(m~q5G2Fmvik=`=j+GpJlTjO(xg2pEoQe$8+v<`n$dt& zWI)kJ`0;-h?&Z_1k7(?j$bHr^vVg~NpI*Fxw|V*YNGpKWm$Riv|H7si!lg>7WPoxx zVVXcnBIJe?LT=th$izT`l7ulN+#nGdh-jFIi3AIYVyha z+90A|);$UE!7fE4+kKSwPmx~xdhEE{+a>$TMy^$(E0!ORUyF6K8mPA|#?#%od{Hen zQL5+Df2P}lU_SSp_gc(V^Gfo4gEik|iFhTFV~@s!>Gv{gZ<)(ErUmK6GTDu}Y^?X2 zGw5pNkE`V}?{^g~cUA1&IXf`ixrU%O|p9&yjYik%A%5vzUG4hk(N8)Vd?!jcOKT9MTONH z5NtMoai_q<@C8!wv)T(J@O^oj7QMw-`Hm&uubErJL*spR2fg!b4g;WZ_VaeBe0%kN zfJ5f$n1EZ}mGd+}?)6E!kDi~DtdPXb@|rYP7Aw^KphSnr|))72vYrCqWm zO0Y$Zqwd>%nT|ni1{r+yXr{(+CLQE2>{=m@P^y0iz&z@rh;!B70x%ZpBBYs)oI-#r zXrj}xod%l=6yen`WDPDmMw46SFHm7KOA##owvih2ANP>NwFI!joh75i{49(wqT}wS z>&cK}%?La5xJ0@B=*>G?){5zf)H7sUU?N3-LRI}y-k$h5fXP|nA%CbnHW)f5@OJeb zcC%olpt=F%ALV^a{^I7=ByhYRvWVey(|G{#?sh3KXVr+rWVB1kC+rl!-^*Vi`_9;& zM{J$y%arqld=iLBu|d=oDM%2^LYS)`U7-SANX&fE)&kHyK1s)OglS12-m-@anyFNDdSV}L(B z-X{~>c{M#ujX~$z>S(<0;j1!9~iT51kA<(`4Ec|!OWLg2kO}D)~lTpCzn`$iAm)o z9d2?0%$PMwJtgv2!ICwP^UwoE=FNV{(}|E;0cX;FB^7&XMRLNE=JxYTXPVqC@Ml{w zp;Zya%-2;}t!4TdnG<;jXs>sXLCiDc#m9y>O^Ti;JWdn9ltVMvy<*TR*c9-YrETjR zo4qC=Uh9pHIk^v*APk^gSQ#E=KgJO`t_Ci7+c@xytpcr4NLe3!3|{iQN$^u46OhW! zva>+AUF0eZl5v?X8~IcsDM%y*=rsHvk^*02j`f?W9sFlx#BsXs7gc-JKR^8DzX8>r BHxmE= literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/MarkovClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/MarkovClusteringAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..747a9e7b8e5a1843cfb09bfae527f128c0231d93 GIT binary patch literal 6962 zcmeHLX;>6T8vc4{bx^?>@jyf!5X5Xe#segX(C83BLGVa4qA)}wMkJu_0)jHFcz}`+ zk0>4-Nj8d6#E2Zhz>G0bHyVaSC5QnhYYax@h@zle`_cUjVT1_0cQ<;IU{1^4|C;E*@+FVDrpZqrq9eTDr;o1p11Mj;vopWf&DImW5MSM=Bj;w^siWgd;yNs^w=6q-@0E@2 zf5S~~dvv+3U{$?X9rv^EQkqWwm+H5!Kf- zM%|j?5-=RV)YZAM-S$yt&L=sc#WO-0Jl9p9{v_I1=^nvEbt65o!~Ap3eucI52^E$N zi$fZ_8)`#eF?j~%uvT|$${R~-^1{|;xZr|XF%s1(D%2&>9_jU1cmH->PHuHvL_)eP z)!)s$Y}}&z{JrA%a*xNIVWm5&+}B}W7WJmCMtxU7;lt=!!{+9a)Ec8TjV!&!9GW87 z9&X_5;WcwMU;D^mks~YEI>-X|utL=PAb1nW+#YVy_V0wG(X8&M3Ig9H(q6gXNF-kw zd_bf~1(!XO)x{=)FqrC&Xy7ko7BvnKxP{dK0|*+R;96an8=A~`DD`KRt-y(qR;=Wq;Y~**ni5*a5G_#yrQSqS zAx%j%Ki)K4qFF&}Z=zj9H7}yclwts>l?q_GPPBmES@y74(sF<(X9<%14mrxA90^*8kR(4w7B!fl6$nZ4PO_+81Z_Y_ zl6Qh`2$W(BP0e0%_+l;#aO_OpG=fbiK`DbqqW4vDu2P)_OyfvO!HId4k~cL_MxU$KKhH+zfGNs)b?N9&_28iwt3 z!c8Oa#$>pbcpeNx0)}8B&*XRY)z-&F{DF`LhTx2G7~Q(?u9t=Ql7f)w;Vmf__u(!x z_MMmETHD6_+N*W>@z%SKqn=uPs_P{lU}33&6Bko791hi8k}MdT(M_d-iljKlncCQOB7e7Dv?y?!4(xN zY_o#NL`mvChw|M=RDBYxpefqw4b~K#A=2JZ#BvP@R&a^JY&rt!2^=D#C(>!IwkCp_ zmasTVBmPFx?J7u_&Dv={kgJCf{WY0k2}!+f4l8IvS31a2W)W#0s4a%%sg?{_f=-;j$NC$rcY-W`!`G`bom_!`QEZM7uST)s1ZvKjnn^q zL{rPDTX%IoJdXS2W?f9>^R@LO>C>Wo+QM5y&n0%$IKSQJ*J#h%Zu;Ag+6pp1tTqFd zzvrv$+qOoZ@jSaUeAbhSTQ!fLomw8$a;|pvQ=H*TG-AS`OQojV%f<@{jfedco6l98 ztG(Lcl-u%<1MxFCbWDDCbLG^p^m$8DM}*ZU6u9f}#u-`1#zwiRrR#L8O~@JB`>Cg+ z#dli5!>)D|p3Y3iw_tWCp*AFFQ~L3yUe{H+Ro@=7iwh2~C@X9TnI6)Rxjb@nebuwI zb+)KuXm0Dwd(u`=yW!$P9M>9s%=z5xPEW7-x)}+WfeK|zS5(Hvt|=inoI#U2%DUdn z-{5sVGa_C0WqZkOV|l~L6CJpz}dk#F|b^C8oN8ay|agoXH42Y4jCU8F0( z7xhirs(g;9?w?|D*;tVq6g=QY z{TtJ>bPwW;rF)ArH$}0975M#BRbcY%nJFUZ`DEdz$hhWqJ2uxw^48vr?B#9-5 zuO*fp402#l)P4wjpGfm%f)|lyV+_wI23ypkJtS%y1nwu6QYOroSaR5&%%F!z8p8@_ z4nklWD;R=hLMV~q&Eac_g#9@Y2}74MGg4*%N<**`daGDn(=O%}SU|fF(JSymyFjnN z0@?-h3cS!R5~VD>3f58fc|tuLzh8|VTyY}t!A4MnoIX&_|t+bagsspN@M#&0Zj*+3*8^q zs_vg06y0A}aG#6H z0jc+QCXeqvI3Pnk!7AvF2ZwF~&58Ffel^68^>!un&3;eE{pYxV(EZ&f#xEPrdbY;k zXbE0XG1tQjjW3BkIUXoRkl`Q88HaxWDiI75?Xh5} zer4R=1EFO@+T6Gcq+wY$%)d(fEy1r_AEg+RM14G zp4FQZ3u&>UYkwh)#S2UkEZ0I8(rM>u#vITGAA=^)hS=;6rh!aKxWgbUz>CmmR@sb0 zo=hw~vPKgNp#>zLi4eWPW*qYZV!Z&ZCzcLbw6itO2Ge?CA@q(E1mVW)Qeqf{Hwf*tBnO^jTH%Z?v-w~z&E}4 z*w6_#EnDMX50A*Mal0Mwk(nP}m+`b61<>Z34L^VRNoPX4ZBxj(e{px$1s9Xj^8+?Zf=Xofp5O0T667j&XV!^V m&o8)`1cks-@qfQvv`)NVb8*MaYPZEKTwm{bn$t72TmJ<+A5LBX literal 0 HcmV?d00001 diff --git a/src/test/resources/clustering/test-images/clustering.unambiguousCluster/SpectralClusteringAlgorithm.png b/src/test/resources/clustering/test-images/clustering.unambiguousCluster/SpectralClusteringAlgorithm.png new file mode 100644 index 0000000000000000000000000000000000000000..e631d6dd8fecf17e82f1c03095c3d53187c9a35b GIT binary patch literal 6821 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 literal 0 HcmV?d00001 -- GitLab