View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.samples.clustering;
23  
24  import java.awt.BorderLayout;
25  import java.awt.Color;
26  import java.awt.Component;
27  import java.awt.Dimension;
28  import java.awt.FlowLayout;
29  import java.awt.Graphics;
30  import java.awt.GridLayout;
31  import java.awt.event.ActionEvent;
32  import java.awt.event.ActionListener;
33  import java.awt.image.BufferedImage;
34  import java.awt.image.Raster;
35  import java.awt.image.WritableRaster;
36  import java.io.IOException;
37  import java.util.ArrayList;
38  import java.util.List;
39  
40  import javax.imageio.ImageIO;
41  import javax.swing.BorderFactory;
42  import javax.swing.Box;
43  import javax.swing.ImageIcon;
44  import javax.swing.JButton;
45  import javax.swing.JLabel;
46  import javax.swing.JPanel;
47  import javax.swing.JSpinner;
48  import javax.swing.SpinnerNumberModel;
49  
50  import org.hipparchus.clustering.CentroidCluster;
51  import org.hipparchus.clustering.Clusterable;
52  import org.hipparchus.clustering.KMeansPlusPlusClusterer;
53  import org.hipparchus.samples.ExampleUtils;
54  import org.hipparchus.samples.ExampleUtils.ExampleFrame;
55  
56  /**
57   * This example shows how clustering can be applied to images.
58   */
59  //CHECKSTYLE: stop HideUtilityClassConstructor
60  @SuppressWarnings("serial")
61  public class ImageClusteringExample {
62  
63      /** Empty constructor.
64       * <p>
65       * This constructor is not strictly necessary, but it prevents spurious
66       * javadoc warnings with JDK 18 and later.
67       * </p>
68       * @since 3.0
69       */
70      public ImageClusteringExample() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
71          // nothing to do
72      }
73  
74      /** Main frame for displaying clusters. */
75      public static class Display extends ExampleFrame {
76  
77          /** Reference image. */
78          private BufferedImage referenceImage;
79  
80          /** Cluster image. */
81          private BufferedImage clusterImage;
82  
83          /** Reference raster. */
84          private Raster referenceRaster;
85  
86          /** Painter for the clusters. */
87          private ImagePainter painter;
88  
89          /** Spinner. */
90          private JSpinner clusterSizeSpinner;
91  
92          /** Simple constructor.
93           * @throws IOException if image cannot be created
94           */
95          public Display() throws IOException {
96              setTitle("Hipparchus: Image Clustering Example");
97              setSize(900, 350);
98  
99              setLayout(new FlowLayout());
100 
101             Box bar = Box.createHorizontalBox();
102 
103             ClassLoader classLoader = ExampleUtils.class.getClassLoader();
104             referenceImage = ExampleUtils.resizeImage(
105                     ImageIO.read(classLoader.getResourceAsStream("ColorfulBird.jpg")),
106                     350,
107                     240,
108                     BufferedImage.TYPE_INT_RGB);
109 
110             referenceRaster = referenceImage.getData();
111 
112             clusterImage = new BufferedImage(referenceImage.getWidth(),
113                                              referenceImage.getHeight(),
114                                              BufferedImage.TYPE_INT_RGB);
115 
116             JLabel picLabel = new JLabel(new ImageIcon(referenceImage));
117             bar.add(picLabel);
118 
119             painter = new ImagePainter(clusterImage.getWidth(), clusterImage.getHeight());
120             bar.add(painter);
121 
122             JPanel controlBox = new JPanel();
123             controlBox.setLayout(new GridLayout(5, 1));
124             controlBox.setBorder(BorderFactory.createLineBorder(Color.black, 1));
125 
126             JPanel sizeBox = new JPanel();
127             JLabel sizeLabel = new JLabel("Clusters:");
128             sizeBox.add(sizeLabel);
129 
130             SpinnerNumberModel model = new SpinnerNumberModel(3, 2, 10, 1);
131             clusterSizeSpinner = new JSpinner(model);
132 
133             sizeLabel.setLabelFor(clusterSizeSpinner);
134             sizeBox.add(clusterSizeSpinner);
135             controlBox.add(sizeBox, BorderLayout.NORTH);
136 
137             JButton startButton = new JButton("Cluster");
138             startButton.setActionCommand("cluster");
139             controlBox.add(startButton, BorderLayout.CENTER);
140 
141             bar.add(controlBox);
142 
143             add(bar);
144 
145             startButton.addActionListener(e -> clusterImage());
146         }
147 
148         /** Display clusters.
149          */
150         private void clusterImage() {
151             List<PixelClusterable> pixels = new ArrayList<>();
152             for (int row = 0; row < referenceImage.getHeight(); row++) {
153                 for (int col = 0; col < referenceImage.getWidth(); col++) {
154                     pixels.add(new PixelClusterable(col, row));
155                 }
156             }
157 
158             int clusterSize = ((Number) clusterSizeSpinner.getValue()).intValue();
159             KMeansPlusPlusClusterer<PixelClusterable> clusterer =
160                     new KMeansPlusPlusClusterer<>(clusterSize);
161             List<CentroidCluster<PixelClusterable>> clusters = clusterer.cluster(pixels);
162 
163             WritableRaster raster = clusterImage.getRaster();
164             for (CentroidCluster<PixelClusterable> cluster : clusters) {
165                 double[] color = cluster.getCenter().getPoint();
166                 for (PixelClusterable pixel : cluster.getPoints()) {
167                     raster.setPixel(pixel.x, pixel.y, color);
168                 }
169             }
170 
171             Display.this.repaint();
172         }
173 
174         /** Container for one pixel that can be used in clusters. */
175         private class PixelClusterable implements Clusterable {
176 
177             /** Abscissa. */
178             private final int x;
179 
180             /** Ordinate. */
181             private final int y;
182 
183             /** Color. */
184             private double[] color;
185 
186             /** Simple constructor.
187              * @param x abscissa
188              * @param y ordinate
189              */
190             PixelClusterable(int x, int y) {
191                 this.x = x;
192                 this.y = y;
193                 this.color = null;
194             }
195 
196             /** {@inheritDoc} */
197             @Override
198             public double[] getPoint() {
199                 if (color == null) {
200                     color = referenceRaster.getPixel(x, y, (double[]) null);
201                 }
202                 return color;
203             }
204 
205         }
206 
207         /** Painter for clusters. */
208         private class ImagePainter extends Component {
209 
210             /** Width. */
211             private int width;
212 
213             /** Height. */
214             private int height;
215 
216             /** Simple constructor.
217              * @param width width
218              * @param height height
219              */
220             ImagePainter(int width, int height) {
221                 this.width = width;
222                 this.height = height;
223             }
224 
225             /** {@inheritDoc} */
226             @Override
227             public Dimension getPreferredSize() {
228                 return new Dimension(width, height);
229             }
230 
231             /** {@inheritDoc} */
232             @Override
233             public Dimension getMinimumSize() {
234                 return getPreferredSize();
235             }
236 
237             /** {@inheritDoc} */
238             @Override
239             public Dimension getMaximumSize() {
240                 return getPreferredSize();
241             }
242 
243             /** {@inheritDoc} */
244             @Override
245             public void paint(Graphics g) {
246                 g.drawImage(clusterImage, 0, 0, this);
247             }
248 
249         }
250 
251     }
252 
253     /** Program entry point.
254      * @param args program arguments (unused here)
255      * @throws IOException if display frame cannot be created.
256      */
257     public static void main(String[] args) throws IOException {
258         ExampleUtils.showExampleFrame(new Display());
259     }
260 
261 }