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(new ActionListener() {
146                 public void actionPerformed(ActionEvent e) {
147                     clusterImage();
148                 }
149             });
150         }
151 
152         /** Display clusters.
153          */
154         private void clusterImage() {
155             List<PixelClusterable> pixels = new ArrayList<PixelClusterable>();
156             for (int row = 0; row < referenceImage.getHeight(); row++) {
157                 for (int col = 0; col < referenceImage.getWidth(); col++) {
158                     pixels.add(new PixelClusterable(col, row));
159                 }
160             }
161 
162             int clusterSize = ((Number) clusterSizeSpinner.getValue()).intValue();
163             KMeansPlusPlusClusterer<PixelClusterable> clusterer =
164                     new KMeansPlusPlusClusterer<PixelClusterable>(clusterSize);
165             List<CentroidCluster<PixelClusterable>> clusters = clusterer.cluster(pixels);
166 
167             WritableRaster raster = clusterImage.getRaster();
168             for (CentroidCluster<PixelClusterable> cluster : clusters) {
169                 double[] color = cluster.getCenter().getPoint();
170                 for (PixelClusterable pixel : cluster.getPoints()) {
171                     raster.setPixel(pixel.x, pixel.y, color);
172                 }
173             }
174 
175             Display.this.repaint();
176         }
177 
178         /** Container for one pixel that can be used in clusters. */
179         private class PixelClusterable implements Clusterable {
180 
181             /** Abscissa. */
182             private final int x;
183 
184             /** Ordinate. */
185             private final int y;
186 
187             /** Color. */
188             private double[] color;
189 
190             /** Simple constructor.
191              * @param x abscissa
192              * @param y ordinate
193              */
194             PixelClusterable(int x, int y) {
195                 this.x = x;
196                 this.y = y;
197                 this.color = null;
198             }
199 
200             /** {@inheritDoc} */
201             @Override
202             public double[] getPoint() {
203                 if (color == null) {
204                     color = referenceRaster.getPixel(x, y, (double[]) null);
205                 }
206                 return color;
207             }
208 
209         }
210 
211         /** Painter for clusters. */
212         private class ImagePainter extends Component {
213 
214             /** Width. */
215             private int width;
216 
217             /** Height. */
218             private int height;
219 
220             /** Simple constructor.
221              * @param width width
222              * @param height height
223              */
224             ImagePainter(int width, int height) {
225                 this.width = width;
226                 this.height = height;
227             }
228 
229             /** {@inheritDoc} */
230             @Override
231             public Dimension getPreferredSize() {
232                 return new Dimension(width, height);
233             }
234 
235             /** {@inheritDoc} */
236             @Override
237             public Dimension getMinimumSize() {
238                 return getPreferredSize();
239             }
240 
241             /** {@inheritDoc} */
242             @Override
243             public Dimension getMaximumSize() {
244                 return getPreferredSize();
245             }
246 
247             /** {@inheritDoc} */
248             @Override
249             public void paint(Graphics g) {
250                 g.drawImage(clusterImage, 0, 0, this);
251             }
252 
253         }
254 
255     }
256 
257     /** Program entry point.
258      * @param args program arguments (unused here)
259      * @throws IOException if display frame cannot be created.
260      */
261     public static void main(String[] args) throws IOException {
262         ExampleUtils.showExampleFrame(new Display());
263     }
264 
265 }