diff --git a/pom.xml b/pom.xml
index 8cf502b..d12a1da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,5 +146,11 @@
gov.nist.math
jama
+
+
+ junit
+ junit
+ test
+
diff --git a/src/main/java/ch/fmi/ModelFitter.java b/src/main/java/ch/fmi/ModelFitter.java
new file mode 100644
index 0000000..285ecc4
--- /dev/null
+++ b/src/main/java/ch/fmi/ModelFitter.java
@@ -0,0 +1,167 @@
+
+package ch.fmi;
+
+import java.util.ArrayList;
+import mpicbg.models.Affine2D;
+import mpicbg.models.Affine3D;
+import mpicbg.models.AffineModel2D;
+import mpicbg.models.AffineModel3D;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.Model;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.Point;
+import mpicbg.models.PointMatch;
+import mpicbg.models.RigidModel2D;
+import mpicbg.models.RigidModel3D;
+import mpicbg.models.SimilarityModel2D;
+import mpicbg.models.SimilarityModel3D;
+import mpicbg.models.TranslationModel2D;
+import mpicbg.models.TranslationModel3D;
+import org.scijava.ItemIO;
+import org.scijava.command.Command;
+import org.scijava.plugin.Parameter;
+import org.scijava.plugin.Plugin;
+
+@Plugin(type = Command.class, headless = true,
+ menuPath = "FMI>Fit Transformation Model to Paired Point Sets")
+public class ModelFitter implements Command {
+
+ final static protected String TRANSLATION = "Translation";
+ final static protected String RIGID = "Rigid";
+ final static protected String SIMILARITY = "Similarity";
+ final static protected String AFFINE = "Affine";
+ final static protected String DIM2D = "2D";
+ final static protected String DIM3D = "3D";
+
+ @Parameter(label = "Type of Transformation", choices = { TRANSLATION, RIGID,
+ SIMILARITY, AFFINE })
+ private String transformType;
+
+ @Parameter(label = "Dimensionality", choices = { DIM2D, DIM3D })
+ private String dim;
+
+ @Parameter(label = "Set 1 - X Coordinates")
+ private double[] x1;
+
+ @Parameter(label = "Set 1 - Y Coordinates")
+ private double[] y1;
+
+ @Parameter(label = "Set 1 - Z Coordinates", required = false)
+ private double[] z1 = null;
+
+ @Parameter(label = "Set 2 - X Coordinates")
+ private double[] x2;
+
+ @Parameter(label = "Set 2 - Y Coordinates")
+ private double[] y2;
+
+ @Parameter(label = "Set 2 - Z Coordinates", required = false)
+ private double[] z2 = null;
+
+ @Parameter(type = ItemIO.OUTPUT)
+ private double[] affine;
+
+ private Model> model;
+
+ @Override
+ public void run() {
+ // Choose model
+ switch (transformType) {
+ case TRANSLATION:
+ model = dim.equals(DIM2D) ? //
+ new TranslationModel2D() : new TranslationModel3D();
+ break;
+ case RIGID:
+ model = dim.equals(DIM2D) ? //
+ new RigidModel2D() : new RigidModel3D();
+ break;
+ case SIMILARITY:
+ model = dim.equals(DIM2D) ? //
+ new SimilarityModel2D() : new SimilarityModel3D();
+ break;
+ case AFFINE:
+ model = dim.equals(DIM2D) ? //
+ new AffineModel2D() : new AffineModel3D();
+ break;
+ }
+
+ // Prepare point correspondences (assuming positional correspondence)
+ assert x1.length == y1.length : "X and Y vectors for first point set need to be equal length";
+ assert x2.length == y2.length : "X and Y vectors for second point set need to be equal length";
+ assert x1.length == x2.length : "Both point sets need to have equal length";
+
+ ArrayList correspondences = new ArrayList<>();
+ for (int i = 0; i < x1.length; i++) {
+ double[] pos1 = new double[dim.equals(DIM2D) ? 2 : 3];
+ pos1[0] = x1[i];
+ pos1[1] = y1[i];
+ if (dim.equals(DIM3D)) pos1[2] = z1[i];
+ Point p1 = new Point(pos1);
+ double[] pos2 = new double[dim.equals(DIM2D) ? 2 : 3];
+ pos2[0] = x2[i];
+ pos2[1] = y2[i];
+ if (dim.equals(DIM3D)) pos2[2] = z2[i];
+ Point p2 = new Point(pos2);
+ correspondences.add(new PointMatch(p1, p2));
+ }
+
+ // Fit the model
+ try {
+ model.fit(correspondences);
+ }
+ catch (NotEnoughDataPointsException exc) {
+ // TODO Auto-generated catch block
+ exc.printStackTrace();
+ }
+ catch (IllDefinedDataPointsException exc) {
+ // TODO Auto-generated catch block
+ exc.printStackTrace();
+ }
+
+ // Retrieve results
+ affine = new double[12];
+ double[] temp;
+ switch (dim) {
+ case DIM2D:
+ temp = new double[6];
+ ((Affine2D>) model).toArray(temp);
+ map2DColsTo3DRows(temp, affine);
+ break;
+ case DIM3D:
+ temp = new double[12];
+ ((Affine3D>) model).toArray(temp);
+ mapColsToRows(temp, affine);
+ break;
+ }
+ }
+
+ private void map2DColsTo3DRows(double[] temp, double[] mat) {
+ mat[0] = temp[0];
+ mat[1] = temp[2];
+ mat[2] = 0; //
+ mat[3] = temp[4];
+ mat[4] = temp[1];
+ mat[5] = temp[3];
+ mat[6] = 0; //
+ mat[7] = temp[5];
+ mat[8] = 0; //
+ mat[9] = 0; //
+ mat[10] = 1; //
+ mat[11] = 0; //
+ }
+
+ private void mapColsToRows(double[] temp, double[] mat) {
+ mat[0] = temp[0];
+ mat[1] = temp[3];
+ mat[2] = temp[6];
+ mat[3] = temp[9];
+ mat[4] = temp[1];
+ mat[5] = temp[4];
+ mat[6] = temp[7];
+ mat[7] = temp[10];
+ mat[8] = temp[2];
+ mat[9] = temp[5];
+ mat[10] = temp[8];
+ mat[11] = temp[11];
+ }
+}
diff --git a/src/test/java/ch/fmi/ModelFitterTest.java b/src/test/java/ch/fmi/ModelFitterTest.java
new file mode 100644
index 0000000..671d19d
--- /dev/null
+++ b/src/test/java/ch/fmi/ModelFitterTest.java
@@ -0,0 +1,148 @@
+
+package ch.fmi;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.scijava.Context;
+import org.scijava.command.CommandModule;
+import org.scijava.command.CommandService;
+
+public class ModelFitterTest {
+
+ private Context context;
+
+ @Before
+ public void initialize() {
+ context = new Context();
+ }
+
+ @After
+ public void disposeContext() {
+ if (context != null) {
+ context.dispose();
+ context = null;
+ }
+ }
+
+ @Test
+ public void test3DSimilarityModel() throws InterruptedException,
+ ExecutionException
+ {
+ // Create Point Arrays
+ double[] x1 = { 0, 1, 1, 1 };
+ double[] y1 = { 0, 0, 1, 1 };
+ double[] z1 = { 0, 0, 0, 1 };
+ double[] x2 = { 2, 2, 4, 4 };
+ double[] y2 = { 0, -2, -2, -2 };
+ double[] z2 = { 0, 0, 0, 2 };
+
+ Map inputMap = new HashMap<>();
+ inputMap.put("transformType", ModelFitter.SIMILARITY);
+ inputMap.put("dim", ModelFitter.DIM3D);
+ inputMap.put("x1", x1);
+ inputMap.put("y1", y1);
+ inputMap.put("z1", z1);
+ inputMap.put("x2", x2);
+ inputMap.put("y2", y2);
+ inputMap.put("z2", z2);
+
+ // Fit Model
+ CommandService commandService = context.getService(CommandService.class);
+ CommandModule module = commandService.run(ModelFitter.class, true, inputMap)
+ .get();
+
+ // Compare
+ double[] expected = { //
+ 0, 2, 0, 2, //
+ -2, 0, 0, 0, //
+ 0, 0, 2, 0 //
+ };
+ double[] result = (double[]) module.getOutput("affine");
+ System.out.println(Arrays.toString(result));
+ assertArrayEquals("Affine matrix", expected, result, 0.01);
+ }
+
+ @Test
+ public void test3DAffineModel() throws InterruptedException,
+ ExecutionException
+ {
+ // Create Point Arrays
+ double[] x1 = { 0, 1, 1, 1 };
+ double[] y1 = { 0, 0, 1, 1 };
+ double[] z1 = { 0, 0, 0, 1 };
+ double[] x2 = { 2, 2, 4, 4 };
+ double[] y2 = { 0, -2, -3, -3 };
+ double[] z2 = { 0, 0, 0, 2 };
+
+ Map inputMap = new HashMap<>();
+ inputMap.put("transformType", ModelFitter.AFFINE);
+ inputMap.put("dim", ModelFitter.DIM3D);
+ inputMap.put("x1", x1);
+ inputMap.put("y1", y1);
+ inputMap.put("z1", z1);
+ inputMap.put("x2", x2);
+ inputMap.put("y2", y2);
+ inputMap.put("z2", z2);
+
+ // Fit Model
+ CommandService commandService = context.getService(CommandService.class);
+ CommandModule module = commandService.run(ModelFitter.class, true, inputMap)
+ .get();
+
+ // Compare
+ double[] expected = { //
+ 0, 2, 0, 2, //
+ -2, -1, 0, 0, //
+ 0, 0, 2, 0 //
+ };
+ double[] result = (double[]) module.getOutput("affine");
+ System.out.println(Arrays.toString(result));
+ assertArrayEquals("Affine matrix", expected, result, 0.01);
+ }
+
+ @Test
+ public void test2DAffineModel() throws InterruptedException,
+ ExecutionException
+ {
+ // Create Point Arrays
+ double[] x1 = { 0, 1, 1, 0 };
+ double[] y1 = { 0, 0, 1, 1 };
+ double[] x2 = { 2, 2, 4, 4 };
+ double[] y2 = { 0, -2, -3, -1 };
+
+ Map inputMap = new HashMap<>();
+ inputMap.put("transformType", ModelFitter.AFFINE);
+ inputMap.put("dim", ModelFitter.DIM2D);
+ inputMap.put("x1", x1);
+ inputMap.put("y1", y1);
+ inputMap.put("x2", x2);
+ inputMap.put("y2", y2);
+
+ inputMap.put("z1", x1); // dummy input
+ inputMap.put("z2", x1); // dummy input
+
+ // Fit Model
+ CommandService commandService = context.getService(CommandService.class);
+ CommandModule module = commandService.run(ModelFitter.class, true, inputMap)
+ .get();
+
+ // Compare
+ double[] expected = { //
+ 0, 2, 0, 2, //
+ -2, -1, 0, 0, //
+ 0, 0, 1, 0 //
+ };
+
+ double[] result = (double[]) module.getOutput("affine");
+ System.out.println(Arrays.toString(result));
+
+ assertArrayEquals("Affine matrix", expected, result, 0.01);
+ }
+}