| --- |
| layout: post |
| status: PUBLISHED |
| published: true |
| title: Fruity Eclipse Collections |
| id: 27af8a99-d989-443c-b403-1def88b1c6c3 |
| date: '2022-10-13 11:05:54 -0400' |
| categories: groovy |
| tags: |
| - eclipsecollections |
| - kmeans |
| - datascience |
| - groovy |
| permalink: groovy/entry/fruity-eclipse-collections |
| --- |
| <p>This blog post continues on to some degree from the <a href="https://blogs.apache.org/roller-ui/authoring/entryEdit.rol?weblog=groovy&bean.id=f948dba2-e4a8-40b1-80b7-3de48b36520a" target="_blank">previous post</a>, but instead of deep learning, we'll look at clustering using k-means after first exploring some top methods of <a href="https://www.eclipse.org/collections/" target="_blank">Eclipse Collections</a> with fruit emoji examples.</p> |
| <h2>Eclipse Collections Fruit Salad</h2> |
| <p>First, we'll define a Fruit enum (it adds one additional fruit compared to the related <a href="https://github.com/eclipse/eclipse-collections-kata/tree/master/top-methods-kata-solutions" target="_blank">Eclipse Collections kata</a>):</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/8d0d5468-f58a-445a-8e79-854c7815d861" style="width:100%" alt="code for fruit enum"></p> |
| <p>We can use this enum in the following examples:</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/c272dd74-9d35-49b7-8857-c7fccf1989f0" style="width:100%" alt="usage.png"><br></p></p> |
| <p>The last example calculates red fruit in parallel threads. As coded, it uses virtual threads when run on JDK19 with preview features enabled. You can follow the suggestion in the comment to run on other JDK versions or with normal threads. In addition to Eclipse Collections, we have the GPars library on our classpath. Here we are only using one method which is managing pool lifecycle for us.</p> |
| <h2>Exploring emoji colors</h2> |
| <p>For some fun, let's look at whether the nominated color of each fruit matches the color of the related emoji. As in the previous blog, we'll use the slightly nicer <a href="https://fonts.google.com/noto/specimen/Noto+Color+Emoji?preview.text=%F0%9F%8D%8E%F0%9F%8D%91%F0%9F%8D%8C%F0%9F%8D%92%F0%9F%8D%8A%F0%9F%8D%87&preview.text_type=custom">Noto Color Emoji</a><span style="color: rgb(51, 51, 51); font-size: 15.4px;"> fonts for our fruit as shown here:</span></p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/4e4f1abe-0775-442d-ba17-7488e3eeca49" style="width:50%" alt="2022-10-12 14_16_42-Noto Color Emoji - Google Fonts.png"></p> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">We'll use an Eclipse Collection BiMap to switch back and forth between the color names and java.awt colors:</span></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#bbb529;">@Field </span><span style="color:#cc7832;">public static </span><span style="color:#9876aa;font-style:italic;">COLOR_OF </span>= BiMaps.<span style="color:#9876aa;font-style:italic;">immutable</span>.ofAll([<br> WHITE: <span style="color:#9876aa;font-style:italic;">WHITE</span>, RED: <span style="color:#9876aa;font-style:italic;">RED</span>, GREEN: <span style="color:#9876aa;font-style:italic;">GREEN</span>, BLUE: <span style="color:#9876aa;font-style:italic;">BLUE</span>,<br> ORANGE: <span style="color:#9876aa;font-style:italic;">ORANGE</span>, YELLOW: <span style="color:#9876aa;font-style:italic;">YELLOW</span>, MAGENTA: <span style="color:#9876aa;font-style:italic;">MAGENTA<br></span>])<br><span style="color:#bbb529;">@Field </span><span style="color:#cc7832;">public static </span><span style="color:#9876aa;font-style:italic;">NAME_OF </span>= <span style="color:#9876aa;font-style:italic;">COLOR_OF</span>.inverse()</pre> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">We are also going to use some helper functions to switch between RGB and HSB color values:</span></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">static </span>hsb(<span style="color:#cc7832;">int </span>r, <span style="color:#cc7832;">int </span>g, <span style="color:#cc7832;">int </span>b) {<br> <span style="color:#cc7832;">float</span>[] hsb = <span style="color:#cc7832;">new float</span>[<span style="color:#6897bb;">3</span>]<br> <span style="color:#9876aa;font-style:italic;">RGBtoHSB</span>(r, g, b, hsb)<br> hsb<br>}<br><br><span style="color:#cc7832;">static </span>rgb(BufferedImage image, <span style="color:#cc7832;">int </span>x, <span style="color:#cc7832;">int </span>y) {<br> <span style="color:#cc7832;">int </span>rgb = image.getRGB(x, y)<br> <span style="color:#cc7832;">int </span>r = (rgb >> <span style="color:#6897bb;">16</span>) & <span style="color:#6897bb;">0xFF<br></span><span style="color:#6897bb;"> </span><span style="color:#cc7832;">int </span>g = (rgb >> <span style="color:#6897bb;">8</span>) & <span style="color:#6897bb;">0xFF<br></span><span style="color:#6897bb;"> </span><span style="color:#cc7832;">int </span>b = rgb & <span style="color:#6897bb;">0xFF<br></span><span style="color:#6897bb;"> </span>[r, g, b]<br>}<br></pre> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">The HSB color space represents colors in a spectrum from 0 to 360 degrees:</span></p> |
| <p><img src="https://nycdoe-cs4all.github.io/images/lessons/unit_1/3.2/circle.png" style="width:30%" alt="Color Circle, Credit: https://nycdoe-cs4all.github.io/units/1/lessons/lesson_3.2"></p> |
| <p><span style="color: rgb(156, 156, 148);"><span style="font-size: 10px;">Image credit: </span><span style="font-size: 10px;">https://nycdoe-cs4all.github.io/units/1/lessons/lesson_3.2</span></span></p> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">We have two helper methods to assist with colors. The first picks out "<i>mostly black</i>" and "<i>mostly white</i>" colors while the second uses a switch expression to carve out some regions of the color space for our colors of interest:</span></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">static </span>range(<span style="color:#cc7832;">float</span>[] hsb) {<br> <span style="color:#cc7832;">if </span>(hsb[<span style="color:#6897bb;">1</span>] < <span style="color:#6897bb;">0.1 </span>&& hsb[<span style="color:#6897bb;">2</span>] > <span style="color:#6897bb;">0.9</span>) <span style="color:#cc7832;">return </span>[<span style="color:#6897bb;">0</span>, <span style="color:#9876aa;font-style:italic;">WHITE</span>]<br> <span style="color:#cc7832;">if </span>(hsb[<span style="color:#6897bb;">2</span>] < <span style="color:#6897bb;">0.1</span>) <span style="color:#cc7832;">return </span>[<span style="color:#6897bb;">0</span>, <span style="color:#9876aa;font-style:italic;">BLACK</span>]<br> <span style="color:#cc7832;">int </span>deg = (hsb[<span style="color:#6897bb;">0</span>] * <span style="color:#6897bb;">360</span>).round()<br> <span style="color:#cc7832;">return </span>[deg, <span style="color:#9876aa;font-style:italic;">range</span>(deg)]<br>}<br><br><span style="color:#cc7832;">static </span>range(<span style="color:#cc7832;">int </span>deg) {<br> <span style="color:#cc7832;">switch </span>(deg) {<br> <span style="color:#cc7832;">case </span><span style="color:#6897bb;">0</span>..<<span style="color:#6897bb;">16 </span>-> <span style="color:#9876aa;font-style:italic;">RED<br></span><span style="color:#9876aa;font-style:italic;"> </span><span style="color:#cc7832;">case </span><span style="color:#6897bb;">16</span>..<<span style="color:#6897bb;">35 </span>-> <span style="color:#9876aa;font-style:italic;">ORANGE<br></span><span style="color:#9876aa;font-style:italic;"> </span><span style="color:#cc7832;">case </span><span style="color:#6897bb;">35</span>..<<span style="color:#6897bb;">75 </span>-> <span style="color:#9876aa;font-style:italic;">YELLOW<br></span><span style="color:#9876aa;font-style:italic;"> </span><span style="color:#cc7832;">case </span><span style="color:#6897bb;">75</span>..<<span style="color:#6897bb;">160 </span>-> <span style="color:#9876aa;font-style:italic;">GREEN<br></span><span style="color:#9876aa;font-style:italic;"> </span><span style="color:#cc7832;">case </span><span style="color:#6897bb;">160</span>..<<span style="color:#6897bb;">250 </span>-> <span style="color:#9876aa;font-style:italic;">BLUE<br></span><span style="color:#9876aa;font-style:italic;"> </span><span style="color:#cc7832;">case </span><span style="color:#6897bb;">250</span>..<<span style="color:#6897bb;">330 </span>-> <span style="color:#9876aa;font-style:italic;">MAGENTA<br></span><span style="color:#9876aa;font-style:italic;"> </span><span style="color:#cc7832;">default </span>-> <span style="color:#9876aa;font-style:italic;">RED<br></span><span style="color:#9876aa;font-style:italic;"> </span>}<br>}</pre> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">Note that the JDK doesn't have a standard color of PURPLE, so we combine purple with magenta by choosing an appropriate broad spectrum for MAGENTA.</span></p> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">We used a <a href="https://plotly.com/javascript/" target="_blank">Plotly</a> 3D interactive scatterplot (as supported by the <a href="https://jtablesaw.github.io/tablesaw/userguide/Introduction_to_Plotting" target="_blank">Tablesaw</a> </span><font color="#333333"><span style="font-size: 15.4px;">Java dataframe and visualization library</span></font><span style="color: rgb(51, 51, 51); font-size: 15.4px;">) to visualize our emoji colors (as degrees on the color spectrum) vs the XY coordinates:</span></p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/85b4c127-52a3-4cc6-a7fc-1e72aa49e8b8" style="width:100%" alt="2022-10-13 20_04_10-Color vs xy.png"><span style="color: rgb(51, 51, 51); font-size: 15.4px;"><br></span></p> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">We are going to try out 3 approaches for determining the predominant color of each emoji:</span></p> |
| <ol> |
| <li><span style="color: rgb(51, 51, 51); font-size: 15.4px;"><b>Most common color</b>: We find the color spectrum value for each point and count up the number of points of each color. The color with the most points will be selected. This is simple and works in many scenarios but if an apple or cherry has 100 shades of red but only one shade of green for the stalk or a leaf, green may be selected.</span></li> |
| <li><span style="color: rgb(51, 51, 51); font-size: 15.4px;"><b>Most common range</b>: We group each point into a color range. The range with the most points will be selected.</span></li> |
| <li><span style="color: rgb(51, 51, 51); font-size: 15.4px;"><b>Centroid of biggest cluster</b>: We divide our emoji image into a grid of sub-images. We will perform k-means clustering of the RGB values for each point in the sub-image. This will cluster similar colored points together in a cluster. The cluster with the most points will be selected and its centroid will be chosen as the selected pre-dominant color. This approach has the affect of pixelating our sub-image by color. This approach is inspired by this <a href="https://medium.com/swlh/getting-dominant-colour-of-an-image-using-k-means-f7fdca880063" target="_blank">python article</a>.</span></li> |
| </ol> |
| <h3>Most Common Color</h3> |
| <p>Ignoring the background white color, the most common color for our PEACH emoji is a shade of orange. The graph below shows the count of each color:</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/b9f82465-62e2-45c0-926f-634568381be8" style="width:75%" alt="2022-10-17 15_57_40-Color histogram for PEACH.png"></p> |
| <h3>Most Common Range</h3> |
| <p>If instead of counting each color, we group colors into their range and count the numbers in each range, we get the following graph for PEACH:</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/4ce2ac0e-87ac-4509-9fa8-10928419f4a0" style="width:75%" alt="2022-10-17 15_56_58-Range histogram for PEACH.png"></p> |
| <p><br></p> |
| <h3>K-Means</h3> |
| <p>K-Means is an algorithm for finding cluster centroids. For k=3, we would start by picking<br /> |
| 3 random points as our starting centroids.</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/6192d3b7-a3c7-4ce5-b0c0-a3286619dd12" style="width:70%" alt="kmeans_step1.png"></p> |
| <p>We allocate all points to their closest centroid:</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/751eaac0-5ba2-448d-8f67-6ebf53613ac9" style="width:70%" alt="kmeans_step2.png"></p> |
| <p>Given this allocation, we re-calculate each centroid from all of its points:</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/daa9530e-fc5d-458d-8029-a1fd8a62d521" style="width:70%" alt="kmeans_step3.png"></p> |
| <p>We repeat this process until either a stable centroid selection is found, or we have reached a certain number of iterations.</p> |
| <p>We used the K-Means algorithm from <a href="https://commons.apache.org/proper/commons-math/userguide/ml.html#clustering" target="_blank">Apache Commons Math</a>.</p> |
| <p>Here is the kind of result we would expect if run on the complete set of points for the PEACH emoji. The black dots are the centroids. It has found one green, one orange and one red centroid. The centroid with the most points allocated to it should be the most predominant color. (This is another interactive 3D scatterplot.)</p> |
| <p><br></p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/f6bf5f7e-f384-4316-a1da-f8ec62ebae47" style="width:70%" alt="RGB_3D_PEACH.png"></p> |
| <p><br>We can plot the number of points allocated to each cluster as a bar chart. (We used a <a href="https://github.com/alexarchambault/plotly-scala" target="_blank">Scala plotting library</a> to show Groovy integration with Scala.)</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/9bfd0a6e-85a8-4615-b0d7-f719a9459ad3" style="width:70%" alt="2022-10-17 16_56_28-Centroid sizes for PEACH.png"></p> |
| <p>The code for drawing the above chart looks like this:<br></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">var </span>trace = <span style="color:#cc7832;">new </span>Bar(<span style="color:#9876aa;font-style:italic;">intSeq</span>([<span style="color:#6897bb;">1</span>, <span style="color:#6897bb;">2</span>, <span style="color:#6897bb;">3</span>]), <span style="color:#9876aa;font-style:italic;">intSeq</span>(sizes))<br> .withMarker(<span style="color:#cc7832;">new </span>Marker().withColor(<span style="color:#9876aa;font-style:italic;">oneOrSeq</span>(colors)))<br><br><span style="color:#cc7832;">var </span>traces = <span style="color:#9876aa;font-style:italic;">asScala</span>([trace]).toSeq()<br><br><span style="color:#cc7832;">var </span>layout = <span style="color:#cc7832;">new </span>Layout()<br> .withTitle(<span style="color:#6a8759;">"Centroid sizes for </span>$fruit<span style="color:#6a8759;">"</span>)<br> .withShowlegend(<span style="color:#cc7832;">false</span>)<br> .withHeight(<span style="color:#6897bb;">600</span>)<br> .withWidth(<span style="color:#6897bb;">800</span>)<br><br>Plotly.<span style="color:#9876aa;font-style:italic;">plot</span>(path, traces, layout, defaultConfig, <span style="color:#cc7832;">false</span>, <span style="color:#cc7832;">false</span>, <span style="color:#cc7832;">true</span>)</pre></p> |
| <h3>K-Means with subimages</h3> |
| <p>The approach we will take for our third option enhances K-Means. Instead of finding centroids for the whole image as the graphs just shown do, we divide the image into subimages and perform the K-Means on each subimage. Our overall pre-dominant color is determined to be the most common color predicated across all of our subimages.</p> |
| <h3>Putting it all together</h3> |
| <p><span style="color: rgb(51, 51, 51); font-size: 15.4px;">Here is the final code covering all three approaches (including printing some pretty images highlighting the third approach and the Plotly 3D scatter plots):</span></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">var </span>results = Fruit.<span style="color:#9876aa;font-style:italic;">ALL</span>.collect { fruit -><br> <span style="color:#cc7832;">var </span>file = getClass().<span style="color:#9876aa;">classLoader</span>.getResource(<span style="color:#6a8759;">"</span>${fruit.name()}<span style="color:#6a8759;">.png"</span>).<span style="color:#9876aa;">file </span><span style="color:#cc7832;">as </span>File<br> <span style="color:#cc7832;">var </span>image = ImageIO.<span style="color:#9876aa;font-style:italic;">read</span>(file)<br><br> <span style="color:#cc7832;">var </span>colors = [:].withDefault { <span style="color:#6897bb;">0 </span>}<br> <span style="color:#cc7832;">var </span>ranges = [:].withDefault { <span style="color:#6897bb;">0 </span>}<br> <span style="color:#cc7832;">for </span>(x <span style="color:#cc7832;">in </span><span style="color:#6897bb;">0</span>..<image.<span style="color:#9876aa;">width</span>) {<br> <span style="color:#cc7832;">for </span>(y <span style="color:#cc7832;">in </span><span style="color:#6897bb;">0</span>..<image.<span style="color:#9876aa;">height</span>) {<br> <span style="color:#cc7832;">def </span>(<span style="color:#cc7832;">int </span>r, <span style="color:#cc7832;">int </span>g, <span style="color:#cc7832;">int </span>b) = <span style="color:#9876aa;font-style:italic;">rgb</span>(image, x, y)<br> <span style="color:#cc7832;">float</span>[] hsb = <span style="color:#9876aa;font-style:italic;">hsb</span>(r, g, b)<br> <span style="color:#cc7832;">def </span>(deg, range) = <span style="color:#9876aa;font-style:italic;">range</span>(hsb)<br> <span style="color:#cc7832;">if </span>(range != <span style="color:#9876aa;font-style:italic;">WHITE</span>) { <span style="color:#808080;">// ignore white background<br></span><span style="color:#808080;"> </span>ranges[range]++<br> colors[deg]++<br> }<br> }<br> }<br> <span style="color:#cc7832;">var </span>maxRange = ranges.max { e -> e.<span style="color:#9876aa;">value </span>}.<span style="color:#9876aa;">key<br></span><span style="color:#9876aa;"> </span><span style="color:#cc7832;">var </span>maxColor = range(colors.max { e -> e.<span style="color:#9876aa;">value </span>}.<span style="color:#9876aa;">key</span>)<br><br> <span style="color:#cc7832;">int </span>cols = <span style="color:#6897bb;">8</span>, rows = <span style="color:#6897bb;">8<br></span><span style="color:#6897bb;"> </span><span style="color:#cc7832;">int </span>grid = <span style="color:#6897bb;">5 </span><span style="color:#808080;">// thickness of black "grid" between subimages<br></span><span style="color:#808080;"> </span><span style="color:#cc7832;">int </span>stepX = image.<span style="color:#9876aa;">width </span>/ cols<br> <span style="color:#cc7832;">int </span>stepY = image.<span style="color:#9876aa;">height </span>/ rows<br> <span style="color:#cc7832;">var </span>splitImage = <span style="color:#cc7832;">new </span>BufferedImage(image.<span style="color:#9876aa;">width </span>+ (cols - <span style="color:#6897bb;">1</span>) * grid, image.<span style="color:#9876aa;">height </span>+ (rows - <span style="color:#6897bb;">1</span>) * grid, image.<span style="color:#9876aa;">type</span>)<br> <span style="color:#cc7832;">var </span>g2a = splitImage.createGraphics()<br> <span style="color:#cc7832;">var </span>pixelated = <span style="color:#cc7832;">new </span>BufferedImage(image.<span style="color:#9876aa;">width </span>+ (cols - <span style="color:#6897bb;">1</span>) * grid, image.<span style="color:#9876aa;">height </span>+ (rows - <span style="color:#6897bb;">1</span>) * grid, image.<span style="color:#9876aa;">type</span>)<br> <span style="color:#cc7832;">var </span>g2b = pixelated.createGraphics()<br><br> ranges = [:].withDefault { <span style="color:#6897bb;">0 </span>}<br> <span style="color:#cc7832;">for </span>(i <span style="color:#cc7832;">in </span><span style="color:#6897bb;">0</span>..<rows) {<br> <span style="color:#cc7832;">for </span>(j <span style="color:#cc7832;">in </span><span style="color:#6897bb;">0</span>..<cols) {<br> <span style="color:#cc7832;">def </span>clusterer = <span style="color:#cc7832;">new </span>KMeansPlusPlusClusterer(<span style="color:#6897bb;">5</span>, <span style="color:#6897bb;">100</span>)<br> List<DoublePoint> data = []<br> <span style="color:#cc7832;">for </span>(x <span style="color:#cc7832;">in </span><span style="color:#6897bb;">0</span>..<stepX) {<br> <span style="color:#cc7832;">for </span>(y <span style="color:#cc7832;">in </span><span style="color:#6897bb;">0</span>..<stepY) {<br> <span style="color:#cc7832;">def </span>(<span style="color:#cc7832;">int </span>r, <span style="color:#cc7832;">int </span>g, <span style="color:#cc7832;">int </span>b) = <span style="color:#9876aa;font-style:italic;">rgb</span>(image, stepX * j + x, stepY * i + y)<br> <span style="color:#cc7832;">var </span>dp = <span style="color:#cc7832;">new </span>DoublePoint([r, g, b] <span style="color:#cc7832;">as int</span>[])<br> <span style="color:#cc7832;">var </span>hsb = <span style="color:#9876aa;font-style:italic;">hsb</span>(r, g, b)<br> <span style="color:#cc7832;">def </span>(deg, col) = <span style="color:#9876aa;font-style:italic;">range</span>(hsb)<br> data << dp<br> }<br> }<br> <span style="color:#cc7832;">var </span>centroids = clusterer.cluster(data)<br> <span style="color:#cc7832;">var </span>biggestCluster = centroids.max { ctrd -> ctrd.<span style="color:#9876aa;">points</span>.size() }<br> <span style="color:#cc7832;">var </span>ctr = biggestCluster.<span style="color:#9876aa;">center</span>.<span style="color:#9876aa;">point</span>*.intValue()<br> <span style="color:#cc7832;">var </span>hsb = <span style="color:#9876aa;font-style:italic;">hsb</span>(*ctr)<br> <span style="color:#cc7832;">def </span>(_, range) = <span style="color:#9876aa;font-style:italic;">range</span>(hsb)<br> <span style="color:#cc7832;">if </span>(range != <span style="color:#9876aa;font-style:italic;">WHITE</span>) ranges[range]++<br> g2a.drawImage(image, (stepX + grid) * j, (stepY + grid) * i, stepX * (j + <span style="color:#6897bb;">1</span>) + grid * j, stepY * (i + <span style="color:#6897bb;">1</span>) + grid * i,<br> stepX * j, stepY * i, stepX * (j + <span style="color:#6897bb;">1</span>), stepY * (i + <span style="color:#6897bb;">1</span>), <span style="color:#cc7832;">null</span>)<br> g2b.<span style="color:#9876aa;">color </span>= <span style="color:#cc7832;">new </span>Color(*ctr)<br> g2b.fillRect((stepX + grid) * j, (stepY + grid) * i, stepX, stepY)<br> }<br> }<br> g2a.dispose()<br> g2b.dispose()<br><br> <span style="color:#cc7832;">var </span>swing = <span style="color:#cc7832;">new </span>SwingBuilder()<br> <span style="color:#cc7832;">var </span>maxCentroid = ranges.max { e -> e.<span style="color:#9876aa;">value </span>}.<span style="color:#9876aa;">key<br></span><span style="color:#9876aa;"> </span>swing.edt {<br> frame(title: <span style="color:#6a8759;">'Original vs Subimages vs K-Means'</span>,<br> defaultCloseOperation: <span style="color:#9876aa;font-style:italic;">DISPOSE_ON_CLOSE</span>, pack: <span style="color:#cc7832;">true</span>, show: <span style="color:#cc7832;">true</span>) {<br> flowLayout()<br> label(icon: imageIcon(image))<br> label(icon: imageIcon(splitImage))<br> label(icon: imageIcon(pixelated))<br> }<br> }<br><br> [fruit, maxRange, maxColor, maxCentroid]<br>}<br><br>println <span style="color:#6a8759;">"Fruit Expected By max color By max range By k-means"<br></span>results.each { fruit, maxRange, maxColor, maxCentroid -><br> <span style="color:#cc7832;">def </span>colors = [fruit.color, maxColor, maxRange, maxCentroid].collect {<br> <span style="color:#9876aa;font-style:italic;">NAME_OF</span>[it].padRight(<span style="color:#6897bb;">14</span>)<br> }.join().trim()<br> println <span style="color:#6a8759;">"</span>${fruit.emoji.padRight(<span style="color:#6897bb;">6</span>)} $colors<span style="color:#6a8759;">"<br></span>}</pre> |
| <p>Here are the resulting images:</p> |
| <p><img style="width:100%" src="https://blogs.apache.org/groovy/mediaresource/442d7100-5023-43ce-a525-0db682fc7b60" alt="2022-10-13 20_37_25-Original.png"></p> |
| <p><img style="width:100%" src="https://blogs.apache.org/groovy/mediaresource/1fc50ff2-7881-4bd3-a8c7-a6283222d91d" alt="2022-10-13 20_37_08-Original.png"></p> |
| <p><img style="width:100%" src="https://blogs.apache.org/groovy/mediaresource/6b35338d-be4b-40a4-81dc-e317a68fbb1e" alt="2022-10-13 20_36_49-Original.png"></p> |
| <p><img style="width:100%" src="https://blogs.apache.org/groovy/mediaresource/d3ff8625-d429-4c7c-bfcd-531df53a8256" alt="2022-10-13 20_36_27-Original.png"></p> |
| <p><img style="width:100%" src="https://blogs.apache.org/groovy/mediaresource/1f1b755a-7dcf-4c53-b930-07c3ef8f0a2f" alt="2022-10-13 20_36_07-Original.png"></p> |
| <p><img style="width:100%" src="https://blogs.apache.org/groovy/mediaresource/d166e557-2ba2-4058-9bde-fb8b682d4c4b" alt="2022-10-13 20_35_21-Original.png"></p> |
| <p>And, here are the final results:</p> |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/78b1f289-2200-4750-a923-69ac01dc12d2" style="width:70%" alt="final results"></p> |
| <p>In our case, all three approaches yielded the same results. Results for other emojis may vary.</p> |
| <h3>Further information</h3> |
| <ul> |
| <li>Repo with example code: <a href="https://github.com/paulk-asert/fruity-eclipse-collections" target="_blank">https://github.com/paulk-asert/fruity-eclipse-collections</a><a href="https://github.com/paulk-asert/fruity-eclipse-collections" target="_blank"></a></li> |
| <li>Further examples of k-means clustering: <a href="https://github.com/paulk-asert/groovy-data-science/tree/master/subprojects/Whiskey" target="_blank">https://github.com/paulk-asert/groovy-data-science/tree/master/subprojects/Whiskey</a></li> |
| <li>Related slides for clustering: <a href="https://speakerdeck.com/paulk/groovy-data-science?slide=94" target="_blank">https://speakerdeck.com/paulk/groovy-data-science?slide=94</a><a href="https://speakerdeck.com/paulk/groovy-data-science?slide=94" target="_blank"></a></li> |
| <li>Eclipse collections homepage: <a href="https://www.eclipse.org/collections/" target="_blank">https://www.eclipse.org/collections/</a> </li> |
| </ul> |
| <p><br></p> |