From a2c3dfa600bd8ac048ae66d10182e8f215e06e61 Mon Sep 17 00:00:00 2001 From: Oliver-Loeffler Date: Sat, 1 Aug 2020 12:31:18 +0200 Subject: [PATCH] Decoupling of matrix computation from image-registration API (#16) * Created an API to hook-in custom linear algebra solver implementations. * Created a SolverProvider as service hook. * Provided 4 equivalent solver implementations. * Removed the Jama and La4J specific implementations. * Introduced the SolverProvider as linear algebra lookup service. * Updated all calculation, transform and equation classes accordingly. * Fixed typo in POM. --- README.md | 8 +- pom.xml | 19 +- .../registration/ModelEquation.java | 5 + ...aModel.java => DefaultRigidBodyModel.java} | 258 ++++++++--------- .../alignment/RigidBodyLa4JModel.java | 84 ------ .../alignment/RigidModelEquation.java | 4 +- .../alignment/RigidTransformCalculation.java | 4 +- .../distortions/AffineModelEquation.java | 18 +- .../AffineTransformCalculation.java | 4 +- ...fineModel.java => DefaultAffineModel.java} | 263 ++++++++--------- ... => DefaultOneDimensionalAffineModel.java} | 270 +++++++++--------- .../solver/ApacheMathCommonsSolver.java | 40 +++ .../registration/solver/Deltas.java | 33 +++ .../registration/solver/EjmlSolver.java | 25 ++ .../registration/solver/JamaSolver.java | 28 ++ .../registration/solver/La4jSolver.java | 43 +++ .../registration/solver/References.java | 79 +++++ .../registration/solver/Solution.java | 26 ++ .../registration/solver/Solver.java | 14 + .../registration/solver/SolverProvider.java | 12 + .../registration/solver/package-info.java | 1 + .../RigidTransformCalculationTest.java | 2 +- ...nsionalAffineTransformCalculationTest.java | 6 +- 23 files changed, 722 insertions(+), 524 deletions(-) create mode 100644 src/main/java/net/raumzeitfalle/registration/ModelEquation.java rename src/main/java/net/raumzeitfalle/registration/alignment/{RigidBodyJamaModel.java => DefaultRigidBodyModel.java} (58%) delete mode 100644 src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyLa4JModel.java rename src/main/java/net/raumzeitfalle/registration/distortions/{JamaAffineModel.java => DefaultAffineModel.java} (53%) rename src/main/java/net/raumzeitfalle/registration/distortions/{JamaOneDimensionalAffineModel.java => DefaultOneDimensionalAffineModel.java} (65%) create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/ApacheMathCommonsSolver.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/Deltas.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/EjmlSolver.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/JamaSolver.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/La4jSolver.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/References.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/Solution.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/Solver.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/SolverProvider.java create mode 100644 src/main/java/net/raumzeitfalle/registration/solver/package-info.java diff --git a/README.md b/README.md index 7641d2a..d629c75 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,13 @@ Image registration is the process of finding the transform to match a given image with a desired reference. This library supports rigid body transforms (translation and rotation) as well as affine transforms (anisotropic scaling, anisotropic rotation/shear aka. non-orthogonality). Currently non-linear transforms are not supported. Versions up to and including 0.0.4 run with Java-8, all later version starting with -0.0.5 require Java-11. +0.0.6 require Java-11. + +Version 0.0.5 will introduce a method to select different linear algebra frameworks to +be used in `image-registration`. Here it will be possible to choose either `gov.nist.math.jama`, +`org.la4j`, `org.ejml` or `org.apache.commons.math3`. There will be a core or api JAR +for `image-registration` and there will be a group of JARs providing a solver implementation. +The solver will be selected using the Java SPI (Service Provider Interface) mechanism. These methods are used e.g. in photomask manufacturing, medical imaging or geospatial applications. Control point or feature based methods have only limited scope of use in medical imaging, there intensity based or voxel based methods are preferred due to the natural structure of medical image data. These additional intensity and voxel based methods are not supported by this library. diff --git a/pom.xml b/pom.xml index c12c895..1814812 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ net.raumzeitfalle.registration image-registration 0.0.5-SNAPSHOT - A colletion of functions to perform rigid and affine transforms for image registration. + A collection of functions to perform rigid and affine transforms for image registration. 2019 Oliver Loeffler, Raumzeitfalle.net @@ -22,7 +22,9 @@ 0.6.0 1.1.1 - + 0.39 + 3.6.1 + 5.6.0 3.0.0-M3 @@ -255,6 +257,19 @@ ${gov.nist.math.jama.version} compile + + + org.ejml + ejml-simple + ${org.ejml.version} + compile + + + + org.apache.commons + commons-math3 + ${org.apache.math.commons.version} + org.junit.jupiter diff --git a/src/main/java/net/raumzeitfalle/registration/ModelEquation.java b/src/main/java/net/raumzeitfalle/registration/ModelEquation.java new file mode 100644 index 0000000..4657cd8 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/ModelEquation.java @@ -0,0 +1,5 @@ +package net.raumzeitfalle.registration; + +public interface ModelEquation extends Orientable { + public double getDeltaValue(); +} diff --git a/src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyJamaModel.java b/src/main/java/net/raumzeitfalle/registration/alignment/DefaultRigidBodyModel.java similarity index 58% rename from src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyJamaModel.java rename to src/main/java/net/raumzeitfalle/registration/alignment/DefaultRigidBodyModel.java index f5a667b..e579012 100644 --- a/src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyJamaModel.java +++ b/src/main/java/net/raumzeitfalle/registration/alignment/DefaultRigidBodyModel.java @@ -1,136 +1,122 @@ -/*- - * #%L - * Image-Registration - * %% - * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package net.raumzeitfalle.registration.alignment; - -import java.util.Collection; -import java.util.Iterator; - -import Jama.Matrix; -import Jama.QRDecomposition; -import net.raumzeitfalle.registration.Dimension; -import net.raumzeitfalle.registration.Orientation; -import net.raumzeitfalle.registration.Orientable; - -/** - * - * Calculates translation and rotation for the given set of equations. A rigid - * transform preserves all distances between points. - * - *
    - *
  • Translation
  • - *
  • Rotation
  • - *
- * - * @author oliver - * - */ -final class RigidBodyJamaModel implements RigidBodyModel { - - private void prepare(Collection equations, Matrix references, Matrix deltas, Orientation direction) { - int row = 0; - Iterator it = equations.iterator(); - while (it.hasNext()) { - row = addEquation(references, deltas, row, it.next(), direction); - } - } - - private int addEquation(Matrix references, Matrix deltas, int row, RigidModelEquation eq, Orientation direction) { - - if (Orientation.X.equals(direction)) { - references.set(row, 0, eq.getXf()); - references.set(row, 1, eq.getDesignValue()); - } - - if (Orientation.Y.equals(direction)) { - references.set(row, 0, eq.getYf()); - references.set(row, 1, eq.getDesignValue()); - } - - if (Orientation.XY.equals(direction)) { - references.set(row, 0, eq.getXf()); - references.set(row, 1, eq.getYf()); - references.set(row, 2, eq.getDesignValue()); - } - - deltas.set(row, 0, eq.getDeltaValue()); - row++; - return row; - } - - private RigidTransform solve(Matrix references, Matrix deltas, Orientation direction) { - - QRDecomposition qr = new QRDecomposition(references); - - Matrix rInverse = qr.getR().inverse(); - Matrix qTransposed = qr.getQ().transpose(); - Matrix solved = rInverse.times(qTransposed).times(deltas); - - return createTransform(solved,direction); - } - - private RigidTransform createTransform(Matrix solved, Orientation direction) { - - if (Orientation.X.equals(direction)) { - return SimpleRigidTransform - .with(solved.get(0, 0), 0.0, solved.get(1, 0)); - } - - if (Orientation.Y.equals(direction)) { - return SimpleRigidTransform - .with(0.0, solved.get(0, 0), solved.get(1, 0)); - } - - return SimpleRigidTransform - .with(solved.get(0, 0), solved.get(1, 0), solved.get(2, 0)); - } - - @Override - public RigidTransform solve(Collection equations, Dimension dimension) { - - int rows = equations.size(); - int cols = dimension.getDimensions()+1; - - Orientation direction = dimension.getDirection(); - - Matrix deltas = new Matrix(rows, 1); - Matrix references = new Matrix(rows, cols); - prepare(equations, references, deltas, direction); - - // escape here before singular matrix exception can be thrown - if (Orientation.XY.equals(direction) && equations.size() == 2) { - double tx = deltas.get(0, 0); - double ty = deltas.get(1, 0); - return SimpleRigidTransform.translation(tx, ty); - } - - if (Orientation.X.equals(direction) && equations.size() == 1) { - double tx = deltas.get(0, 0); - return SimpleRigidTransform.shiftX(tx); - } - - if (Orientation.Y.equals(direction) && equations.size() == 1) { - double ty = deltas.get(0, 0); - return SimpleRigidTransform.shiftY(ty); - } - - return solve(references, deltas, direction); - } - -} +/*- + * #%L + * Image-Registration + * %% + * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package net.raumzeitfalle.registration.alignment; + +import java.util.Collection; +import java.util.Iterator; + +import net.raumzeitfalle.registration.Dimension; +import net.raumzeitfalle.registration.Orientable; +import net.raumzeitfalle.registration.Orientation; +import net.raumzeitfalle.registration.solver.Deltas; +import net.raumzeitfalle.registration.solver.References; +import net.raumzeitfalle.registration.solver.Solution; +import net.raumzeitfalle.registration.solver.Solver; +import net.raumzeitfalle.registration.solver.SolverProvider; + +/** + * + * Calculates translation and rotation for the given set of equations. A rigid + * transform preserves all distances between points. + * + *
    + *
  • Translation
  • + *
  • Rotation
  • + *
+ * + * @author oliver + * + */ +final class DefaultRigidBodyModel implements RigidBodyModel { + + private void prepare(Collection equations, References references, Deltas deltas, Orientation direction) { + int row = 0; + Iterator it = equations.iterator(); + while (it.hasNext()) { + row = addEquation(references, deltas, row, it.next(), direction); + } + } + + private int addEquation(References references, Deltas deltas, int row, RigidModelEquation eq, Orientation direction) { + + references.set(row, eq, direction); + deltas.set(row, eq); + + row++; + return row; + } + + private RigidTransform solve(References references, Deltas deltas, Orientation direction) { + + Solver solver = new SolverProvider().getSolver(); + Solution solution = solver.apply(references, deltas); + + return createTransform(solution,direction); + } + + private RigidTransform createTransform(Solution solved, Orientation direction) { + + if (Orientation.X.equals(direction)) { + return SimpleRigidTransform + .with(solved.get(0), 0.0, solved.get(1)); + } + + if (Orientation.Y.equals(direction)) { + return SimpleRigidTransform + .with(0.0, solved.get(0), solved.get(1)); + } + + return SimpleRigidTransform + .with(solved.get(0), solved.get(1), solved.get(2)); + } + + @Override + public RigidTransform solve(Collection equations, Dimension dimension) { + + int rows = equations.size(); + int cols = dimension.getDimensions()+1; + + Orientation direction = dimension.getDirection(); + + Deltas deltas = new Deltas(rows); + References references = new References(rows, cols); + prepare(equations, references, deltas, direction); + + // escape here before singular matrix exception can be thrown + if (Orientation.XY.equals(direction) && equations.size() == 2) { + double tx = deltas.get(0); + double ty = deltas.get(1); + return SimpleRigidTransform.translation(tx, ty); + } + + if (Orientation.X.equals(direction) && equations.size() == 1) { + double tx = deltas.get(0); + return SimpleRigidTransform.shiftX(tx); + } + + if (Orientation.Y.equals(direction) && equations.size() == 1) { + double ty = deltas.get(0); + return SimpleRigidTransform.shiftY(ty); + } + + return solve(references, deltas, direction); + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyLa4JModel.java b/src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyLa4JModel.java deleted file mode 100644 index c2e19e3..0000000 --- a/src/main/java/net/raumzeitfalle/registration/alignment/RigidBodyLa4JModel.java +++ /dev/null @@ -1,84 +0,0 @@ -/*- - * #%L - * Image-Registration - * %% - * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package net.raumzeitfalle.registration.alignment; - -import java.util.Collection; -import java.util.Iterator; - -import org.la4j.LinearAlgebra.InverterFactory; -import org.la4j.Matrix; -import org.la4j.decomposition.QRDecompositor; - -import net.raumzeitfalle.registration.Dimension; -import net.raumzeitfalle.registration.Orientable; - -public class RigidBodyLa4JModel implements RigidBodyModel { - - @Override - public RigidTransform solve(Collection equations, Dimension dimension) { - - int n = 3; - - Matrix laRef = org.la4j.Matrix.zero(equations.size(), n); - Matrix laDeltas = org.la4j.Matrix.zero(equations.size(), 1); - - prepare(equations, laRef, laDeltas); - - Matrix laResult = solve(laRef, laDeltas); - - double translationX = laResult.get(0, 0); - double translationY = laResult.get(1, 0); - double rotation = laResult.get(2, 0); - - return SimpleRigidTransform.with(translationX, translationY, rotation); - } - - private Matrix solve(org.la4j.Matrix laRef, org.la4j.Matrix laDeltas) { - QRDecompositor laQr = new QRDecompositor(laRef); - Matrix[] laQrResult = laQr.decompose(); - - Matrix q = laQrResult[0]; - Matrix qTrans = q.transpose(); - - Matrix r = laQrResult[1]; - Matrix rInv = InverterFactory.GAUSS_JORDAN.create(r).inverse(); - - Matrix laRinvQTransp = rInv.multiply(qTrans); - - return laRinvQTransp.multiply(laDeltas); - } - - private void prepare(Collection equations, Matrix laRef, Matrix laDeltas) { - int row = 0; - Iterator it = equations.iterator(); - while(it.hasNext()) { - row = addEquation(laRef, laDeltas, row, it.next()); - } - } - - private int addEquation(Matrix laRef, Matrix laDeltas, int row, RigidModelEquation eq) { - laRef.set(row, 0, eq.getXf()); - laRef.set(row, 1, eq.getYf()); - laRef.set(row, 2, eq.getDesignValue()); - laDeltas.set(row, 0, eq.getDeltaValue()); - row++; - return row; - } -} diff --git a/src/main/java/net/raumzeitfalle/registration/alignment/RigidModelEquation.java b/src/main/java/net/raumzeitfalle/registration/alignment/RigidModelEquation.java index 45b1f47..352ce7b 100644 --- a/src/main/java/net/raumzeitfalle/registration/alignment/RigidModelEquation.java +++ b/src/main/java/net/raumzeitfalle/registration/alignment/RigidModelEquation.java @@ -22,11 +22,11 @@ import java.util.stream.Stream; import java.util.stream.Stream.Builder; +import net.raumzeitfalle.registration.ModelEquation; import net.raumzeitfalle.registration.Orientation; -import net.raumzeitfalle.registration.Orientable; import net.raumzeitfalle.registration.displacement.Displacement; -final class RigidModelEquation implements Orientable { +public final class RigidModelEquation implements ModelEquation { public static Stream from(Displacement d) { diff --git a/src/main/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculation.java b/src/main/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculation.java index a1d432e..b8daf34 100644 --- a/src/main/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculation.java +++ b/src/main/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculation.java @@ -42,7 +42,7 @@ public final class RigidTransformCalculation implements BiFunctionX, translationY and rotation) for the given collection of displacements. * When no displacements are left after filtering with the given predicate, then the {@link SkipRigidTransform} is returned. Otherwise the results is returned as {@link SimpleRigidTransform}. *

- * The {@link RigidBodyJamaModel} is used for calculation. + * The {@link DefaultRigidBodyModel} is used for calculation. *

* For cases where no sites are selected for calculation (e.g. the predicate does not match any given displacement), * A {@link SkipRigidTransform} is returned instead of a {@link SimpleRigidTransform}. When used in data processing, diff --git a/src/main/java/net/raumzeitfalle/registration/distortions/AffineModelEquation.java b/src/main/java/net/raumzeitfalle/registration/distortions/AffineModelEquation.java index 027db9f..ad48d60 100644 --- a/src/main/java/net/raumzeitfalle/registration/distortions/AffineModelEquation.java +++ b/src/main/java/net/raumzeitfalle/registration/distortions/AffineModelEquation.java @@ -22,11 +22,11 @@ import java.util.stream.Stream; import java.util.stream.Stream.Builder; +import net.raumzeitfalle.registration.ModelEquation; import net.raumzeitfalle.registration.Orientation; -import net.raumzeitfalle.registration.Orientable; import net.raumzeitfalle.registration.displacement.Displacement; -public final class AffineModelEquation implements Orientable { +public final class AffineModelEquation implements ModelEquation { public static Stream from(Displacement d) { @@ -86,31 +86,31 @@ public String toString() { return "FirstOrderEquation [REF: (" + sx + ", " + sy + ", " + ox + ", " + oy + ", " + tx + ", " + ty + "), Delta: (" + deltaValue + ") ]"; } - double getSx() { + public double getSx() { return sx; } - double getSy() { + public double getSy() { return sy; } - double getOx() { + public double getOx() { return ox; } - double getOy() { + public double getOy() { return oy; } - double getTx() { + public double getTx() { return tx; } - double getTy() { + public double getTy() { return ty; } - double getDeltaValue() { + public double getDeltaValue() { return deltaValue; } diff --git a/src/main/java/net/raumzeitfalle/registration/distortions/AffineTransformCalculation.java b/src/main/java/net/raumzeitfalle/registration/distortions/AffineTransformCalculation.java index b626b0c..afa73fa 100644 --- a/src/main/java/net/raumzeitfalle/registration/distortions/AffineTransformCalculation.java +++ b/src/main/java/net/raumzeitfalle/registration/distortions/AffineTransformCalculation.java @@ -43,7 +43,7 @@ public final class AffineTransformCalculation implements BiFunction finalEquations SpatialDistribution distribution ) { try { - JamaOneDimensionalAffineModel oneDimModel = new JamaOneDimensionalAffineModel(distribution); + DefaultOneDimensionalAffineModel oneDimModel = new DefaultOneDimensionalAffineModel(distribution); return oneDimModel.solve(finalEquations, dimension); } catch (Exception e) { diff --git a/src/main/java/net/raumzeitfalle/registration/distortions/JamaAffineModel.java b/src/main/java/net/raumzeitfalle/registration/distortions/DefaultAffineModel.java similarity index 53% rename from src/main/java/net/raumzeitfalle/registration/distortions/JamaAffineModel.java rename to src/main/java/net/raumzeitfalle/registration/distortions/DefaultAffineModel.java index 278e418..5caa814 100644 --- a/src/main/java/net/raumzeitfalle/registration/distortions/JamaAffineModel.java +++ b/src/main/java/net/raumzeitfalle/registration/distortions/DefaultAffineModel.java @@ -1,141 +1,122 @@ -/*- - * #%L - * Image-Registration - * %% - * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package net.raumzeitfalle.registration.distortions; - -import java.util.Collection; -import java.util.Iterator; - -import Jama.Matrix; -import Jama.QRDecomposition; -import net.raumzeitfalle.registration.Dimension; -import net.raumzeitfalle.registration.Orientable; -import net.raumzeitfalle.registration.Orientation; - -/** - * - * Calculates translation (x,y) and scale (x,y) and non-orthogonality (shear, x,y) for the given collection of equations. - * An affine transform preserves parallelism. - * - *

    - *
  • Translation
  • - *
  • Rotation
  • - *
  • Scale
  • - *
  • Shear
  • - *
- * - * @author oliver - * - */ -final class JamaAffineModel implements AffineModel { - - @Override - public AffineTransform solve(Collection equations, - Dimension dimension) { - - // there are 3 coefficients per direction - int cols = dimension.getDimensions() * 3; - int rows = equations.size(); - - Matrix references = new Matrix(rows, cols); - Matrix deltas = new Matrix(rows, 1); - - Orientation direction = dimension.getDirection(); - prepare(equations, references, deltas, direction); - - return solve(references, deltas, direction); - } - - private AffineTransform solve(Matrix references, Matrix deltas, Orientation direction) { - QRDecomposition qr = new QRDecomposition(references); - Matrix Rinverse = qr.getR().inverse(); - Matrix Qtransposed = qr.getQ().transpose(); - - Matrix solved = Rinverse.times(Qtransposed) - .times(deltas); - - return createTransform(solved, direction); - } - - private AffineTransform createTransform(Matrix solved, Orientation direction) { - - if (Orientation.X.equals(direction)) { - double tx = solved.get(2, 0); - double ox = solved.get(1, 0); - double sx = solved.get(0, 0); - return SimpleAffineTransform.forX(tx, sx, ox); - } - - if (Orientation.Y.equals(direction)) { - double ty = solved.get(2, 0); - double oy = solved.get(1, 0); - double sy = solved.get(0, 0); - return SimpleAffineTransform.forX(ty, sy, oy); - } - - double sx = solved.get(0, 0); - double sy = solved.get(1, 0); - double ox = solved.get(2, 0); - double oy = solved.get(3, 0); - double tx = solved.get(4, 0); - double ty = solved.get(5, 0); - - return SimpleAffineTransform.forXY(tx, ty, sx, sy, ox, oy); - } - - private void prepare(Collection equations, Matrix references, Matrix deltas, - Orientation direction) { - int row = 0; - Iterator it = equations.iterator(); - while (it.hasNext()) { - row = addEquation(references, deltas, row, it.next(), direction); - } - } - - private int addEquation(Matrix references, Matrix deltas, int row, AffineModelEquation eq, - Orientation direction) { - - if (Orientation.X.equals(direction)) { - references.set(row, 0, eq.getSx()); - references.set(row, 1, eq.getOy()); - references.set(row, 2, eq.getTx()); - } - - if (Orientation.Y.equals(direction)) { - references.set(row, 0, eq.getSy()); - references.set(row, 1, eq.getOx()); - references.set(row, 2, eq.getTy()); - } - - if (Orientation.XY.equals(direction)) { - references.set(row, 0, eq.getSx()); - references.set(row, 1, eq.getSy()); - references.set(row, 2, eq.getOx()); - references.set(row, 3, eq.getOy()); - references.set(row, 4, eq.getTx()); - references.set(row, 5, eq.getTy()); - } - - deltas.set(row, 0, eq.getDeltaValue()); - row++; - - return row; - } - -} +/*- + * #%L + * Image-Registration + * %% + * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package net.raumzeitfalle.registration.distortions; + +import java.util.Collection; +import java.util.Iterator; + +import net.raumzeitfalle.registration.Dimension; +import net.raumzeitfalle.registration.Orientable; +import net.raumzeitfalle.registration.Orientation; +import net.raumzeitfalle.registration.solver.Deltas; +import net.raumzeitfalle.registration.solver.References; +import net.raumzeitfalle.registration.solver.Solution; +import net.raumzeitfalle.registration.solver.Solver; +import net.raumzeitfalle.registration.solver.SolverProvider; + +/** + * + * Calculates translation (x,y) and scale (x,y) and non-orthogonality (shear, x,y) for the given collection of equations. + * An affine transform preserves parallelism. + * + *
    + *
  • Translation
  • + *
  • Rotation
  • + *
  • Scale
  • + *
  • Shear
  • + *
+ * + * @author oliver + * + */ +final class DefaultAffineModel implements AffineModel { + + @Override + public AffineTransform solve(Collection equations, + Dimension dimension) { + + // there are 3 coefficients per direction + int cols = dimension.getDimensions() * 3; + int rows = equations.size(); + + References references = new References(rows, cols); + Deltas deltas = new Deltas(rows); + + Orientation direction = dimension.getDirection(); + prepare(equations, references, deltas, direction); + + return solve(references, deltas, direction); + } + + private AffineTransform solve(References references, Deltas deltas, Orientation direction) { + + Solver solver = new SolverProvider().getSolver(); + Solution solution = solver.apply(references, deltas); + + return createTransform(solution, direction); + } + + private AffineTransform createTransform(Solution solved, Orientation direction) { + + if (Orientation.X.equals(direction)) { + double tx = solved.get(2); + double ox = solved.get(1); + double sx = solved.get(0); + return SimpleAffineTransform.forX(tx, sx, ox); + } + + if (Orientation.Y.equals(direction)) { + double ty = solved.get(2); + double oy = solved.get(1); + double sy = solved.get(0); + return SimpleAffineTransform.forX(ty, sy, oy); + } + + double sx = solved.get(0); + double sy = solved.get(1); + double ox = solved.get(2); + double oy = solved.get(3); + double tx = solved.get(4); + double ty = solved.get(5); + + return SimpleAffineTransform.forXY(tx, ty, sx, sy, ox, oy); + } + + private void prepare(Collection equations, References references, Deltas deltas, + Orientation direction) { + int row = 0; + Iterator it = equations.iterator(); + while (it.hasNext()) { + row = addEquation(references, deltas, row, it.next(), direction); + } + } + + private int addEquation(References references, Deltas deltas, int row, AffineModelEquation eq, + Orientation direction) { + + references.set2D(row, eq, direction); + deltas.set(row, eq); + + row++; + + return row; + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/distortions/JamaOneDimensionalAffineModel.java b/src/main/java/net/raumzeitfalle/registration/distortions/DefaultOneDimensionalAffineModel.java similarity index 65% rename from src/main/java/net/raumzeitfalle/registration/distortions/JamaOneDimensionalAffineModel.java rename to src/main/java/net/raumzeitfalle/registration/distortions/DefaultOneDimensionalAffineModel.java index 8e21b13..059a898 100644 --- a/src/main/java/net/raumzeitfalle/registration/distortions/JamaOneDimensionalAffineModel.java +++ b/src/main/java/net/raumzeitfalle/registration/distortions/DefaultOneDimensionalAffineModel.java @@ -1,141 +1,129 @@ -/*- - * #%L - * Image-Registration - * %% - * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package net.raumzeitfalle.registration.distortions; - -import java.util.Collection; -import java.util.Iterator; - -import Jama.Matrix; -import Jama.QRDecomposition; -import net.raumzeitfalle.registration.Dimension; -import net.raumzeitfalle.registration.Distribution; -import net.raumzeitfalle.registration.Orientable; -import net.raumzeitfalle.registration.Orientation; -import net.raumzeitfalle.registration.SpatialDistribution; - -class JamaOneDimensionalAffineModel implements AffineModel { - - private final Distribution spatialOrientation; - - JamaOneDimensionalAffineModel(SpatialDistribution distribution) { - this.spatialOrientation = distribution.getDistribution(); - } - - @Override - public AffineTransform solve(Collection equations, - Dimension dimension) { - - /* - * Assumption: - * all data points distributed along one vertical line - * different Y locations, exactly one X location - * => this would render the normal affine model to fail - * with a singular matrix - * - * What can be calculated? - * 1. translation-x - * 2. translation-y - * 3. scale-y - * 4. ortho-y (aka. rotation in this case) - * - * What can't be calcutaled? - * 1. scale-x (this would require multiple design x-locations) - * - * Same applies in case of data distributed along one horizontal - * line. - * - */ - - int cols = 4; // dimension.getDimensions() * 2; - int rows = equations.size(); - - Matrix references = new Matrix(rows, cols); - Matrix deltas = new Matrix(rows, 1); - - Orientation direction = dimension.getDirection(); - - prepare(equations, references, deltas, direction); - - return solve(references, deltas, direction); - } - - private void prepare(Collection equations, Matrix references, Matrix deltas, - Orientation direction) { - int row = 0; - Iterator it = equations.iterator(); - while (it.hasNext()) { - row = addEquation(references, deltas, row, it.next(), direction); - } - } - - private int addEquation(Matrix references, Matrix deltas, int row, AffineModelEquation eq, - Orientation direction) { - - if (eq.matches(Orientation.X)) { - references.set(row, 0, eq.getSx()); - references.set(row, 1, eq.getOy()); - references.set(row, 2, eq.getTx()); - references.set(row, 3, eq.getTy()); - } - - if (eq.matches(Orientation.Y)) { - references.set(row, 0, eq.getSy()); - references.set(row, 1, eq.getOx()); - references.set(row, 2, eq.getTx()); - references.set(row, 3, eq.getTy()); - } - - deltas.set(row, 0, eq.getDeltaValue()); - row++; - return row; - - } - - private AffineTransform solve(Matrix references, Matrix deltas, Orientation direction) { - QRDecomposition qr = new QRDecomposition(references); - Matrix Rinverse = qr.getR().inverse(); - Matrix Qtransposed = qr.getQ().transpose(); - - Matrix solved = Rinverse.times(Qtransposed) - .times(deltas); - - return createTransform(solved, direction); - } - - private AffineTransform createTransform(Matrix solved, Orientation direction) { - - double scale = solved.get(0, 0); - double rot = solved.get(1, 0); - double tx = solved.get(2, 0); - double ty = solved.get(3, 0); - - if (Distribution.HORIZONTAL.equals(spatialOrientation)) { - return SimpleAffineTransform.horizontal(tx, ty, scale, rot); - } - - if (Distribution.VERTICAL.equals(spatialOrientation)) { - return SimpleAffineTransform.vertical(tx, ty, scale, rot); - } - - String message = String.format("Evaluation of data with %s spatial orientation not supported.", spatialOrientation); - throw new UnsupportedOperationException(message); - } - -} +/*- + * #%L + * Image-Registration + * %% + * Copyright (C) 2019 Oliver Loeffler, Raumzeitfalle.net + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package net.raumzeitfalle.registration.distortions; + +import java.util.Collection; +import java.util.Iterator; + +import net.raumzeitfalle.registration.Dimension; +import net.raumzeitfalle.registration.Distribution; +import net.raumzeitfalle.registration.Orientable; +import net.raumzeitfalle.registration.Orientation; +import net.raumzeitfalle.registration.SpatialDistribution; +import net.raumzeitfalle.registration.solver.Deltas; +import net.raumzeitfalle.registration.solver.References; +import net.raumzeitfalle.registration.solver.Solution; +import net.raumzeitfalle.registration.solver.Solver; +import net.raumzeitfalle.registration.solver.SolverProvider; + +class DefaultOneDimensionalAffineModel implements AffineModel { + + private final Distribution spatialOrientation; + + DefaultOneDimensionalAffineModel(SpatialDistribution distribution) { + this.spatialOrientation = distribution.getDistribution(); + } + + @Override + public AffineTransform solve(Collection equations, + Dimension dimension) { + + /* + * Assumption: + * all data points distributed along one vertical line + * different Y locations, exactly one X location + * => this would render the normal affine model to fail + * with a singular matrix + * + * What can be calculated? + * 1. translation-x + * 2. translation-y + * 3. scale-y + * 4. ortho-y (aka. rotation in this case) + * + * What can't be calcutaled? + * 1. scale-x (this would require multiple design x-locations) + * + * Same applies in case of data distributed along one horizontal + * line. + * + */ + + int cols = 4; // dimension.getDimensions() * 2; + int rows = equations.size(); + + References references = new References(rows, cols); + Deltas deltas = new Deltas(rows); + + Orientation direction = dimension.getDirection(); + + prepare(equations, references, deltas, direction); + + return solve(references, deltas, direction); + } + + private void prepare(Collection equations, References references, Deltas deltas, + Orientation direction) { + int row = 0; + Iterator it = equations.iterator(); + while (it.hasNext()) { + row = addEquation(references, deltas, row, it.next(), direction); + } + } + + private int addEquation(References references, Deltas deltas, int row, AffineModelEquation eq, + Orientation direction) { + + references.set1D(row, eq, direction); + deltas.set(row, eq); + + row++; + return row; + + } + + private AffineTransform solve(References references, Deltas deltas, Orientation direction) { + + Solver solver = new SolverProvider().getSolver(); + Solution solution = solver.apply(references, deltas); + + return createTransform(solution, direction); + } + + private AffineTransform createTransform(Solution solved, Orientation direction) { + + double scale = solved.get(0); + double rot = solved.get(1); + double tx = solved.get(2); + double ty = solved.get(3); + + if (Distribution.HORIZONTAL.equals(spatialOrientation)) { + return SimpleAffineTransform.horizontal(tx, ty, scale, rot); + } + + if (Distribution.VERTICAL.equals(spatialOrientation)) { + return SimpleAffineTransform.vertical(tx, ty, scale, rot); + } + + String message = String.format("Evaluation of data with %s spatial orientation not supported.", spatialOrientation); + throw new UnsupportedOperationException(message); + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/ApacheMathCommonsSolver.java b/src/main/java/net/raumzeitfalle/registration/solver/ApacheMathCommonsSolver.java new file mode 100644 index 0000000..7613f76 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/ApacheMathCommonsSolver.java @@ -0,0 +1,40 @@ +package net.raumzeitfalle.registration.solver; + +import org.apache.commons.math3.linear.MatrixUtils; +import org.apache.commons.math3.linear.QRDecomposition; +import org.apache.commons.math3.linear.RealMatrix; +import org.apache.commons.math3.linear.SingularValueDecomposition; + + +class ApacheMathCommonsSolver implements Solver { + + public Solution solve(References references, Deltas deviations) { + + RealMatrix refs = MatrixUtils.createRealMatrix(references.getArray()); + RealMatrix deltas = MatrixUtils.createColumnRealMatrix(deviations.getArray()); + + QRDecomposition qr = new QRDecomposition(refs); + + RealMatrix rInverse = createInverse(qr); + + RealMatrix qTransposed = qr.getQ().transpose(); + + RealMatrix solution = rInverse.multiply(qTransposed) + .multiply(deltas); + + return new Solution(solution.getColumn(0)); + + } + + private RealMatrix createInverse(QRDecomposition qr) { + + RealMatrix r = qr.getR(); + + if (r.isSquare()) { + return MatrixUtils.inverse(r); + } + + return new SingularValueDecomposition(r).getSolver().getInverse(); + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/Deltas.java b/src/main/java/net/raumzeitfalle/registration/solver/Deltas.java new file mode 100644 index 0000000..1bdd53f --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/Deltas.java @@ -0,0 +1,33 @@ +package net.raumzeitfalle.registration.solver; + +import net.raumzeitfalle.registration.ModelEquation; + +public final class Deltas { + + private final double[] deltas; + + public Deltas(int rows) { + this.deltas = new double[rows]; + } + + public final double[] getArray() { + return this.deltas; + } + + protected final void set(int row, double deltaValue) { + this.deltas[row] = deltaValue; + } + + public final void set(int row, ModelEquation eq) { + set(row, eq.getDeltaValue()); + } + + public final double get(int row) { + return this.deltas[row]; + } + + public final int rows() { + return this.deltas.length; + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/EjmlSolver.java b/src/main/java/net/raumzeitfalle/registration/solver/EjmlSolver.java new file mode 100644 index 0000000..75da24a --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/EjmlSolver.java @@ -0,0 +1,25 @@ +package net.raumzeitfalle.registration.solver; + +import org.ejml.simple.SimpleMatrix; + +class EjmlSolver implements Solver { + + public Solution solve(References references, Deltas deviations) { + + SimpleMatrix refs = new SimpleMatrix(references.getArray()); + SimpleMatrix deltas = new SimpleMatrix(deviations.rows(), 1, true, deviations.getArray()); + + SimpleMatrix coeffs = refs.solve(deltas); + + int rows = coeffs.numRows(); + double[] coefficients = new double[coeffs.numRows()]; + + for (int i = 0; i < rows; i++) { + coefficients[i] = coeffs.get(i, 0); + } + + return new Solution(coefficients); + + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/JamaSolver.java b/src/main/java/net/raumzeitfalle/registration/solver/JamaSolver.java new file mode 100644 index 0000000..0df1116 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/JamaSolver.java @@ -0,0 +1,28 @@ +package net.raumzeitfalle.registration.solver; + +import Jama.Matrix; +import Jama.QRDecomposition; + +class JamaSolver implements Solver { + + public Solution solve(References references, Deltas deviations) { + + Matrix refs = new Matrix(references.getArray()); + Matrix deltas = new Matrix(deviations.getArray(), deviations.rows()); + + QRDecomposition qr = new QRDecomposition(refs); + + Matrix rInverse = qr.getR().inverse(); + Matrix qTransposed = qr.getQ().transpose(); + Matrix solution = rInverse.times(qTransposed) + .times(deltas); + + return new Solution(solution.getColumnVector(0)); + + } + + @Override + public Solution apply(References t, Deltas u) { + return solve(t, u); + } +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/La4jSolver.java b/src/main/java/net/raumzeitfalle/registration/solver/La4jSolver.java new file mode 100644 index 0000000..2359104 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/La4jSolver.java @@ -0,0 +1,43 @@ +package net.raumzeitfalle.registration.solver; + +import org.la4j.LinearAlgebra.InverterFactory; +import org.la4j.Matrix; +import org.la4j.decomposition.QRDecompositor; + + +class La4jSolver implements Solver { + + public Solution solve(References references, Deltas deviations) { + + Matrix laRef = Matrix.from2DArray(references.getArray()); + Matrix laDeltas = Matrix.from1DArray(deviations.rows(), 1, deviations.getArray()); + + QRDecompositor laQr = new QRDecompositor(laRef); + Matrix[] laQrResult = laQr.decompose(); + + Matrix q = laQrResult[0]; + Matrix qTrans = q.transpose(); + + Matrix r = laQrResult[1]; + Matrix rInv = InverterFactory.GAUSS_JORDAN.create(r).inverse(); + + Matrix laRinvQTransp = rInv.multiply(qTrans); + + Matrix solved = laRinvQTransp.multiply(laDeltas); + + int rows = solved.rows(); + double[] coefficients = new double[solved.rows()]; + for (int i = 0; i < rows; i++) { + coefficients[i] = solved.get(i, 0); + } + + return new Solution(coefficients); + + } + + @Override + public Solution apply(References t, Deltas u) { + return solve(t, u); + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/References.java b/src/main/java/net/raumzeitfalle/registration/solver/References.java new file mode 100644 index 0000000..c870cd2 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/References.java @@ -0,0 +1,79 @@ +package net.raumzeitfalle.registration.solver; + +import net.raumzeitfalle.registration.Orientation; +import net.raumzeitfalle.registration.alignment.RigidModelEquation; +import net.raumzeitfalle.registration.distortions.AffineModelEquation; + +public final class References { + + private final double[][] matrix; + + public References(int rows, int cols) { + this.matrix = new double[rows][cols]; + } + + public final double[][] getArray() { + return this.matrix; + } + + public final void set(int row, int column, double value) { + this.matrix[row][column] = value; + } + + public final void set(int row, RigidModelEquation eq, Orientation direction) { + if (Orientation.X.equals(direction)) { + set(row, 0, eq.getXf()); + set(row, 1, eq.getDesignValue()); + } + + if (Orientation.Y.equals(direction)) { + set(row, 0, eq.getYf()); + set(row, 1, eq.getDesignValue()); + } + + if (Orientation.XY.equals(direction)) { + set(row, 0, eq.getXf()); + set(row, 1, eq.getYf()); + set(row, 2, eq.getDesignValue()); + } + } + + public final void set2D(int row, AffineModelEquation eq, Orientation direction) { + if (Orientation.X.equals(direction)) { + set(row, 0, eq.getSx()); + set(row, 1, eq.getOy()); + set(row, 2, eq.getTx()); + } + + if (Orientation.Y.equals(direction)) { + set(row, 0, eq.getSy()); + set(row, 1, eq.getOx()); + set(row, 2, eq.getTy()); + } + + if (Orientation.XY.equals(direction)) { + set(row, 0, eq.getSx()); + set(row, 1, eq.getSy()); + set(row, 2, eq.getOx()); + set(row, 3, eq.getOy()); + set(row, 4, eq.getTx()); + set(row, 5, eq.getTy()); + } + } + + public final void set1D(int row, AffineModelEquation eq, Orientation direction) { + if (eq.matches(Orientation.X)) { + set(row, 0, eq.getSx()); + set(row, 1, eq.getOy()); + set(row, 2, eq.getTx()); + set(row, 3, eq.getTy()); + } + + if (eq.matches(Orientation.Y)) { + set(row, 0, eq.getSy()); + set(row, 1, eq.getOx()); + set(row, 2, eq.getTx()); + set(row, 3, eq.getTy()); + } + } +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/Solution.java b/src/main/java/net/raumzeitfalle/registration/solver/Solution.java new file mode 100644 index 0000000..5b95534 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/Solution.java @@ -0,0 +1,26 @@ +package net.raumzeitfalle.registration.solver; + +public final class Solution { + + private final double[] coefficients; + + Solution(double[] coefficients) { + this.coefficients = coefficients; + } + + public final double get(int index) { + if (index < 0) { + throw new IllegalArgumentException("index must be > or = to zero."); + } + + if (index > this.coefficients.length && this.coefficients.length > 0) { + throw new IllegalArgumentException("index must be >= 0 and < " + this.coefficients.length); + } + + if (index > this.coefficients.length) { + throw new IllegalArgumentException("Solution is empty, no coefficients available."); + } + + return this.coefficients[index]; + } +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/Solver.java b/src/main/java/net/raumzeitfalle/registration/solver/Solver.java new file mode 100644 index 0000000..3304688 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/Solver.java @@ -0,0 +1,14 @@ +package net.raumzeitfalle.registration.solver; + +import java.util.function.BiFunction; + +public interface Solver extends BiFunction { + + public Solution solve(References t, Deltas u); + + @Override + public default Solution apply(References t, Deltas u) { + return solve(t, u); + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/SolverProvider.java b/src/main/java/net/raumzeitfalle/registration/solver/SolverProvider.java new file mode 100644 index 0000000..9188e8c --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/SolverProvider.java @@ -0,0 +1,12 @@ +package net.raumzeitfalle.registration.solver; + +public class SolverProvider { + + public Solver getSolver() { + // return new EjmlSolver(); + // return new La4jSolver(); + return new JamaSolver(); + // return new ApacheMathCommonsSolver(); + } + +} diff --git a/src/main/java/net/raumzeitfalle/registration/solver/package-info.java b/src/main/java/net/raumzeitfalle/registration/solver/package-info.java new file mode 100644 index 0000000..6acbd59 --- /dev/null +++ b/src/main/java/net/raumzeitfalle/registration/solver/package-info.java @@ -0,0 +1 @@ +package net.raumzeitfalle.registration.solver; \ No newline at end of file diff --git a/src/test/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculationTest.java b/src/test/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculationTest.java index 2bebbf6..b718320 100644 --- a/src/test/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculationTest.java +++ b/src/test/java/net/raumzeitfalle/registration/alignment/RigidTransformCalculationTest.java @@ -37,7 +37,7 @@ class RigidTransformCalculationTest { private static final double TOLERANCE = 1E-11; - private final RigidBodyModel model = new RigidBodyJamaModel(); + private final RigidBodyModel model = new DefaultRigidBodyModel(); private final BiFunction, Predicate, diff --git a/src/test/java/net/raumzeitfalle/registration/distortions/OneDimensionalAffineTransformCalculationTest.java b/src/test/java/net/raumzeitfalle/registration/distortions/OneDimensionalAffineTransformCalculationTest.java index b4550d0..a0259ce 100644 --- a/src/test/java/net/raumzeitfalle/registration/distortions/OneDimensionalAffineTransformCalculationTest.java +++ b/src/test/java/net/raumzeitfalle/registration/distortions/OneDimensionalAffineTransformCalculationTest.java @@ -59,7 +59,7 @@ void displacementsAlongVerticalLine() { AffineTransform result = funtionUnderTest.apply(undisplaced, d->true); - assertTrue(result.getClass().equals(SimpleAffineTransform.class)); + assertEquals(SimpleAffineTransform.class, result.getClass()); assertEquals( tx, result.getTranslationX(), TOLERANCE); assertEquals( ty, result.getTranslationY(), TOLERANCE); @@ -76,7 +76,7 @@ void displacementsAlongVerticalLine() { assertEquals( -1.000, result.getOrtho()*1E6, 1E-6); } - @Disabled("Not yet implemented") + @Disabled("Functionality to be implemented") @Test void displacementsAlongVerticalLine_oneDimensional_y() { // 1ppm rotation @@ -94,7 +94,7 @@ void displacementsAlongVerticalLine_oneDimensional_y() { AffineTransform result = funtionUnderTest.apply(undisplaced, d->true); - assertTrue(result.getClass().equals(SimpleAffineTransform.class)); + assertEquals(SimpleAffineTransform.class, result.getClass()); assertEquals( 0d, result.getTranslationX(), TOLERANCE); assertEquals( ty, result.getTranslationY(), TOLERANCE);