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); + } +}