diff --git a/src/userguide/java/org/apache/commons/math3/userguide/genetics/ImageEvolutionExample.java b/src/userguide/java/org/apache/commons/math3/userguide/genetics/ImageEvolutionExample.java
index ba7a019df..f89740070 100644
--- a/src/userguide/java/org/apache/commons/math3/userguide/genetics/ImageEvolutionExample.java
+++ b/src/userguide/java/org/apache/commons/math3/userguide/genetics/ImageEvolutionExample.java
@@ -9,6 +9,7 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
+import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@@ -16,8 +17,6 @@ import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JFrame;
import javax.swing.JLabel;
import org.apache.commons.math3.genetics.Chromosome;
@@ -26,14 +25,34 @@ import org.apache.commons.math3.genetics.GeneticAlgorithm;
import org.apache.commons.math3.genetics.Population;
import org.apache.commons.math3.genetics.TournamentSelection;
import org.apache.commons.math3.genetics.UniformCrossover;
+import org.apache.commons.math3.userguide.ExampleUtils;
+import org.apache.commons.math3.userguide.ExampleUtils.ExampleFrame;
/**
- * Based on http://www.nihilogic.dk/labs/evolving-images/
+ * This example shows a more advanced use of a genetic algorithm: approximate a raster image
+ * with ~100 semi-transparent polygons of length 6.
+ *
+ * The fitness function is quite simple yet expensive to compute:
+ *
+ * - draw the polygons of a chromosome to an image
+ * - compare each pixel with the corresponding reference image
+ *
+ * To improve the speed of the calculation, we calculate the fitness not on the original image size,
+ * but rather on a scaled down version, which is sufficient to demonstrate the power of such a genetic algorithm.
+ *
+ * TODO:
+ * - improve user interface
+ * - make algorithm parameters configurable
+ * - add a gallery of results after x iterations / minutes (either automatic or based on button click)
+ * - allow loading / selection of other images
+ * - add logging in the user interface, e.g. number of generations, time spent, ...
+ *
+ * @see Evolving Images with JavaScript and canvas (Nihilogic)
*/
@SuppressWarnings("serial")
-public class ImageEvolutionExample extends JComponent {
+public class ImageEvolutionExample {
- public static final int POPULATION_SIZE = 40;
+ public static final int POPULATION_SIZE = 50;
public static final int TOURNAMENT_ARITY = 2;
public static final float MUTATION_RATE = 0.02f;
public static final float MUTATION_CHANGE = 0.1f;
@@ -41,65 +60,160 @@ public class ImageEvolutionExample extends JComponent {
public static final int POLYGON_LENGTH = 6;
public static final int POLYGON_COUNT = 100;
- private GeneticAlgorithm ga;
- private Population currentPopulation;
- private Chromosome bestFit;
-
- private Thread internalThread;
- private volatile boolean noStopRequested;
-
- private BufferedImage ref;
- private BufferedImage buf;
-
- private ImagePainter painter;
-
- public ImageEvolutionExample() throws Exception {
- super();
- setLayout(new FlowLayout());
-
- Box bar = Box.createHorizontalBox();
-
- ref = ImageIO.read(new File("resources/canvas_small.png"));
-
- JLabel picLabel = new JLabel(new ImageIcon(ref));
- bar.add(picLabel);
-
- painter = new ImagePainter(ref);
- bar.add(painter);
-
- buf = new BufferedImage(ref.getWidth(), ref.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ public static class Display extends ExampleFrame {
- // TODO: improve this
- PolygonChromosome.setRefImage(ref);
- PolygonChromosome.setTestImage(buf);
+ private GeneticAlgorithm ga;
+ private Population currentPopulation;
+ private Chromosome bestFit;
- add(bar);
+ private Thread internalThread;
+ private volatile boolean noStopRequested;
- JButton startButton = new JButton("Start");
- startButton.setActionCommand("start");
- add(startButton);
+ private BufferedImage ref;
+
+ private BufferedImage referenceImage;
+ private BufferedImage testImage;
+
+ private ImagePainter painter;
- startButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (isAlive()) {
- stopRequest();
- } else {
- startEvolution();
+ public Display() throws Exception {
+ setTitle("Commons-Math: Image Evolution Example");
+ setSize(600, 400);
+
+ setLayout(new FlowLayout());
+
+ Box bar = Box.createHorizontalBox();
+
+ ref = ImageIO.read(new File("resources/monalisa.png"));
+ //ref = ImageIO.read(new File("resources/feather-small.gif"));
+
+ referenceImage = resizeImage(ref, 50, 50, BufferedImage.TYPE_INT_ARGB);
+ testImage = new BufferedImage(referenceImage.getWidth(), referenceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+ JLabel picLabel = new JLabel(new ImageIcon(ref));
+ bar.add(picLabel);
+
+ painter = new ImagePainter(ref.getWidth(), ref.getHeight());
+ bar.add(painter);
+
+ // set the images used for calculating the fitness function:
+ // refImage - the reference image
+ // testImage - the test image to draw the current chromosome
+ PolygonChromosome.setRefImage(referenceImage);
+ PolygonChromosome.setTestImage(testImage);
+
+ add(bar);
+
+ JButton startButton = new JButton("Start");
+ startButton.setActionCommand("start");
+ add(startButton);
+
+ startButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (isAlive()) {
+ stopRequest();
+ } else {
+ startEvolution();
+ }
}
+ });
+
+ // initialize a new genetic algorithm
+ ga = new GeneticAlgorithm(new UniformCrossover(0.5), 1.0,
+ new RandomPolygonMutation(MUTATION_RATE, MUTATION_CHANGE), 1.0,
+ new TournamentSelection(TOURNAMENT_ARITY));
+
+ // initial population
+ currentPopulation = getInitialPopulation();
+ bestFit = currentPopulation.getFittestChromosome();
+ }
+
+ public boolean isAlive() {
+ return internalThread != null && internalThread.isAlive();
+ }
+
+ public void stopRequest() {
+ noStopRequested = false;
+ internalThread.interrupt();
+ internalThread = null;
+ }
+
+ public void startEvolution() {
+ noStopRequested = true;
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ double lastBestFit = Double.MIN_VALUE;
+ int evolution = 0;
+ while (noStopRequested) {
+ currentPopulation = ga.nextGeneration(currentPopulation);
+
+ System.out.println("generation: " + evolution++ + ": " + bestFit.toString());
+ bestFit = currentPopulation.getFittestChromosome();
+
+ if (lastBestFit > bestFit.getFitness()) {
+ System.out.println("gotcha");
+ }
+ lastBestFit = bestFit.getFitness();
+ painter.repaint();
+ }
+ } catch (Exception x) {
+ // in case ANY exception slips through
+ x.printStackTrace();
+ }
+ }
+ };
+
+ internalThread = new Thread(r);
+ internalThread.start();
+ }
+
+ private class ImagePainter extends Component {
+
+ private int width;
+ private int height;
+
+ public ImagePainter(int width, int height) {
+ this.width = width;
+ this.height = height;
}
- });
- // initialize a new genetic algorithm
- ga = new GeneticAlgorithm(new UniformCrossover(0.5), 1.0,
- new RandomPolygonMutation(MUTATION_RATE, MUTATION_CHANGE), 1.0,
- new TournamentSelection(TOURNAMENT_ARITY));
+ public Dimension getPreferredSize() {
+ return new Dimension(width, height);
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return getPreferredSize();
+ }
+
+ @Override
+ public Dimension getMaximumSize() {
+ return getPreferredSize();
+ }
+
+ public void paint(Graphics g) {
+ PolygonChromosome chromosome = (PolygonChromosome) bestFit;
+ chromosome.draw((Graphics2D) g, ref.getWidth(), ref.getHeight());
+ }
+
+ }
- // initial population
- currentPopulation = getInitialPopulation();
- bestFit = currentPopulation.getFittestChromosome();
}
- private Population getInitialPopulation() {
+ public static void main(String[] args) throws Exception {
+ ExampleUtils.showExampleFrame(new Display());
+ }
+
+ private static BufferedImage resizeImage(BufferedImage originalImage, int width, int height, int type) throws IOException {
+ BufferedImage resizedImage = new BufferedImage(width, height, type);
+ Graphics2D g = resizedImage.createGraphics();
+ g.drawImage(originalImage, 0, 0, width, height, null);
+ g.dispose();
+ return resizedImage;
+ }
+
+ private static Population getInitialPopulation() {
List popList = new LinkedList();
for (int i = 0; i < POPULATION_SIZE; i++) {
popList.add(PolygonChromosome.randomChromosome(POLYGON_LENGTH, POLYGON_COUNT));
@@ -107,99 +221,4 @@ public class ImageEvolutionExample extends JComponent {
return new ElitisticListPopulation(popList, popList.size(), 0.25);
}
- public boolean isAlive() {
- return internalThread != null && internalThread.isAlive();
- }
-
- public void stopRequest() {
- noStopRequested = false;
- internalThread.interrupt();
- internalThread = null;
- }
-
- public void startEvolution() {
- noStopRequested = true;
- Runnable r = new Runnable() {
- public void run() {
- try {
- double lastBestFit = Double.MIN_VALUE;
- int evolution = 0;
- while (noStopRequested) {
- currentPopulation = ga.nextGeneration(currentPopulation);
-
- System.out.println("generation: " + evolution++ + ": " + bestFit.toString());
- bestFit = currentPopulation.getFittestChromosome();
-
- if (lastBestFit > bestFit.getFitness()) {
- System.out.println("gotcha");
- }
- lastBestFit = bestFit.getFitness();
- painter.repaint();
- }
- } catch (Exception x) {
- // in case ANY exception slips through
- x.printStackTrace();
- }
- }
- };
-
- internalThread = new Thread(r);
- internalThread.start();
- }
-
- private static void createAndShowGUI() throws Exception {
- // Create and set up the window.
- JFrame frame = new JFrame("Image Evolution Example");
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-
- frame.getContentPane().add(new ImageEvolutionExample());
-
- // Display the window.
- frame.pack();
- frame.setSize(new Dimension(500, 300));
- frame.setVisible(true);
- }
-
- public static void main(String[] args) {
- // Schedule a job for the event-dispatching thread:
- // creating and showing this application's GUI.
- javax.swing.SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- createAndShowGUI();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- }
-
- private class ImagePainter extends Component {
- BufferedImage ref;
-
- public ImagePainter(final BufferedImage ref) {
- this.ref = ref;
- }
-
- public Dimension getPreferredSize() {
- return new Dimension(ref.getWidth(), ref.getHeight());
- }
-
- @Override
- public Dimension getMinimumSize() {
- return getPreferredSize();
- }
-
- @Override
- public Dimension getMaximumSize() {
- return getPreferredSize();
- }
-
- public void paint(Graphics g) {
- PolygonChromosome chromosome = (PolygonChromosome) bestFit;
- chromosome.draw((Graphics2D) g, ref.getWidth(), ref.getHeight());
- }
-
- }
-
}
diff --git a/src/userguide/java/org/apache/commons/math3/userguide/genetics/Polygon.java b/src/userguide/java/org/apache/commons/math3/userguide/genetics/Polygon.java
index c83f525c3..e9d95f04a 100644
--- a/src/userguide/java/org/apache/commons/math3/userguide/genetics/Polygon.java
+++ b/src/userguide/java/org/apache/commons/math3/userguide/genetics/Polygon.java
@@ -1,5 +1,9 @@
package org.apache.commons.math3.userguide.genetics;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.geom.GeneralPath;
+
import org.apache.commons.math3.genetics.GeneticAlgorithm;
import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.util.FastMath;
@@ -13,7 +17,7 @@ public class Polygon {
// 2 | blue component
// 3 | alpha channel
//
- protected float[] data;
+ private float[] data;
public static Polygon randomPolygon(int length) {
final int polygonSize = 4 + 2 * length;
@@ -58,4 +62,18 @@ public class Polygon {
return mutated;
}
+ public void draw(Graphics2D g, int width, int height) {
+ g.setColor(new Color(data[0], data[1], data[2], data[3]));
+
+ GeneralPath path = new GeneralPath();
+ path.moveTo(data[4] * width, data[5] * height);
+
+ int polygonLength = (data.length - 4) / 2;
+ for (int j = 1; j < polygonLength; j++) {
+ path.lineTo(data[4 + j * 2] * width, data[5 + j * 2] * height);
+ }
+ path.closePath();
+
+ g.fill(path);
+ }
}
diff --git a/src/userguide/java/org/apache/commons/math3/userguide/genetics/PolygonChromosome.java b/src/userguide/java/org/apache/commons/math3/userguide/genetics/PolygonChromosome.java
index 38deaff06..e30bb59f2 100644
--- a/src/userguide/java/org/apache/commons/math3/userguide/genetics/PolygonChromosome.java
+++ b/src/userguide/java/org/apache/commons/math3/userguide/genetics/PolygonChromosome.java
@@ -4,7 +4,6 @@ import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
-import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
@@ -55,15 +54,15 @@ public class PolygonChromosome extends AbstractListChromosome {
g2.dispose();
int[] refPixels = refImage.getData().getPixels(0, 0, refImage.getWidth(), refImage.getHeight(), (int[]) null);
- int[] pixels = testImage.getData().getPixels(0, 0, testImage.getWidth(), testImage.getHeight(), (int[]) null);
+ int[] testPixels = testImage.getData().getPixels(0, 0, testImage.getWidth(), testImage.getHeight(), (int[]) null);
int diff = 0;
- int p = width * height * 4 - 1;
+ int p = width * height * 4 - 1; // 4 channels: rgba
int idx = 0;
do {
if (idx++ % 4 != 0) {
- int dp = pixels[p] - refPixels[p];
+ int dp = testPixels[p] - refPixels[p];
if (dp < 0) {
diff -= dp;
} else {
@@ -76,27 +75,15 @@ public class PolygonChromosome extends AbstractListChromosome {
}
public void draw(Graphics2D g, int width, int height) {
- g.setBackground(Color.RED);
+ g.setBackground(Color.WHITE);
g.clearRect(0, 0, width, height);
- //if (true) return;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
List polygons = getPolygonRepresentation();
for (Polygon p : polygons) {
- g.setColor(new Color(p.data[0], p.data[1], p.data[2], p.data[3]));
-
- GeneralPath path = new GeneralPath();
- path.moveTo(p.data[4] * width, p.data[5] * height);
-
- int polygonLength = (p.data.length - 4) / 2;
- for (int j = 1; j < polygonLength; j++) {
- path.lineTo(p.data[4 + j * 2] * width, p.data[5 + j * 2] * height);
- }
- path.closePath();
-
- g.fill(path);
+ p.draw(g, width, height);
}
}
diff --git a/src/userguide/resources/feather-small.gif b/src/userguide/resources/feather-small.gif
new file mode 100644
index 000000000..9696a0023
Binary files /dev/null and b/src/userguide/resources/feather-small.gif differ
diff --git a/src/userguide/resources/monalisa.png b/src/userguide/resources/monalisa.png
new file mode 100644
index 000000000..f35a3b265
Binary files /dev/null and b/src/userguide/resources/monalisa.png differ
diff --git a/src/userguide/resources/references.txt b/src/userguide/resources/references.txt
new file mode 100644
index 000000000..427b41477
--- /dev/null
+++ b/src/userguide/resources/references.txt
@@ -0,0 +1 @@
+monalisa.png - http://commons.wikimedia.org/wiki/File:Mona_Lisa.jpg