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  @SuppressWarnings("serial")
60  public class ImageClusteringExample {
61  
62      /** Empty constructor.
63       * <p>
64       * This constructor is not strictly necessary, but it prevents spurious
65       * javadoc warnings with JDK 18 and later.
66       * </p>
67       * @since 3.0
68       */
69      public ImageClusteringExample() { // NOPMD - unnecessary constructor added intentionally to make javadoc happy
70          // nothing to do
71      }
72  
73      /** Main frame for displaying clusters. */
74      public static class Display extends ExampleFrame {
75  
76          /** Reference image. */
77          private BufferedImage referenceImage;
78  
79          /** Cluster image. */
80          private BufferedImage clusterImage;
81  
82          /** Reference raster. */
83          private Raster referenceRaster;
84  
85          /** Painter for the clusters. */
86          private ImagePainter painter;
87  
88          /** Spinner. */
89          private JSpinner clusterSizeSpinner;
90  
91          /** Simple constructor.
92           * @throws IOException if image cannot be created
93           */
94          public Display() throws IOException {
95              setTitle("Hipparchus: Image Clustering Example");
96              setSize(900, 350);
97  
98              setLayout(new FlowLayout());
99  
100             Box bar = Box.createHorizontalBox();
101 
102             ClassLoader classLoader = ExampleUtils.class.getClassLoader();
103             referenceImage = ExampleUtils.resizeImage(
104                     ImageIO.read(classLoader.getResourceAsStream("ColorfulBird.jpg")),
105                     350,
106                     240,
107                     BufferedImage.TYPE_INT_RGB);
108 
109             referenceRaster = referenceImage.getData();
110 
111             clusterImage = new BufferedImage(referenceImage.getWidth(),
112                                              referenceImage.getHeight(),
113                                              BufferedImage.TYPE_INT_RGB);
114 
115             JLabel picLabel = new JLabel(new ImageIcon(referenceImage));
116             bar.add(picLabel);
117 
118             painter = new ImagePainter(clusterImage.getWidth(), clusterImage.getHeight());
119             bar.add(painter);
120 
121             JPanel controlBox = new JPanel();
122             controlBox.setLayout(new GridLayout(5, 1));
123             controlBox.setBorder(BorderFactory.createLineBorder(Color.black, 1));
124 
125             JPanel sizeBox = new JPanel();
126             JLabel sizeLabel = new JLabel("Clusters:");
127             sizeBox.add(sizeLabel);
128 
129             SpinnerNumberModel model = new SpinnerNumberModel(3, 2, 10, 1);
130             clusterSizeSpinner = new JSpinner(model);
131 
132             sizeLabel.setLabelFor(clusterSizeSpinner);
133             sizeBox.add(clusterSizeSpinner);
134             controlBox.add(sizeBox, BorderLayout.NORTH);
135 
136             JButton startButton = new JButton("Cluster");
137             startButton.setActionCommand("cluster");
138             controlBox.add(startButton, BorderLayout.CENTER);
139 
140             bar.add(controlBox);
141 
142             add(bar);
143 
144             startButton.addActionListener(new ActionListener() {
145                 public void actionPerformed(ActionEvent e) {
146                     clusterImage();
147                 }
148             });
149         }
150 
151         /** Display clusters.
152          */
153         private void clusterImage() {
154             List<PixelClusterable> pixels = new ArrayList<PixelClusterable>();
155             for (int row = 0; row < referenceImage.getHeight(); row++) {
156                 for (int col = 0; col < referenceImage.getWidth(); col++) {
157                     pixels.add(new PixelClusterable(col, row));
158                 }
159             }
160 
161             int clusterSize = ((Number) clusterSizeSpinner.getValue()).intValue();
162             KMeansPlusPlusClusterer<PixelClusterable> clusterer =
163                     new KMeansPlusPlusClusterer<PixelClusterable>(clusterSize);
164             List<CentroidCluster<PixelClusterable>> clusters = clusterer.cluster(pixels);
165 
166             WritableRaster raster = clusterImage.getRaster();
167             for (CentroidCluster<PixelClusterable> cluster : clusters) {
168                 double[] color = cluster.getCenter().getPoint();
169                 for (PixelClusterable pixel : cluster.getPoints()) {
170                     raster.setPixel(pixel.x, pixel.y, color);
171                 }
172             }
173 
174             Display.this.repaint();
175         }
176 
177         /** Container for one pixel that can be used in clusters. */
178         private class PixelClusterable implements Clusterable {
179 
180             /** Abscissa. */
181             private final int x;
182 
183             /** Ordinate. */
184             private final int y;
185 
186             /** Color. */
187             private double[] color;
188 
189             /** Simple constructor.
190              * @param x abscissa
191              * @param y ordinate
192              */
193             PixelClusterable(int x, int y) {
194                 this.x = x;
195                 this.y = y;
196                 this.color = null;
197             }
198 
199             /** {@inheritDoc} */
200             @Override
201             public double[] getPoint() {
202                 if (color == null) {
203                     color = referenceRaster.getPixel(x, y, (double[]) null);
204                 }
205                 return color;
206             }
207 
208         }
209 
210         /** Painter for clusters. */
211         private class ImagePainter extends Component {
212 
213             /** Width. */
214             private int width;
215 
216             /** Height. */
217             private int height;
218 
219             /** Simple constructor.
220              * @param width width
221              * @param height height
222              */
223             ImagePainter(int width, int height) {
224                 this.width = width;
225                 this.height = height;
226             }
227 
228             /** {@inheritDoc} */
229             @Override
230             public Dimension getPreferredSize() {
231                 return new Dimension(width, height);
232             }
233 
234             /** {@inheritDoc} */
235             @Override
236             public Dimension getMinimumSize() {
237                 return getPreferredSize();
238             }
239 
240             /** {@inheritDoc} */
241             @Override
242             public Dimension getMaximumSize() {
243                 return getPreferredSize();
244             }
245 
246             /** {@inheritDoc} */
247             @Override
248             public void paint(Graphics g) {
249                 g.drawImage(clusterImage, 0, 0, this);
250             }
251 
252         }
253 
254     }
255 
256     /** Program entry point.
257      * @param args program arguments (unused here)
258      * @throws IOException if display frame cannot be created.
259      */
260     public static void main(String[] args) throws IOException {
261         ExampleUtils.showExampleFrame(new Display());
262     }
263 
264 }