| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| package org.apache.sis.referencing.operation.transform; |
| |
| import java.util.List; |
| import java.util.Arrays; |
| import java.util.Random; |
| import org.opengis.util.FactoryException; |
| import org.opengis.referencing.operation.Matrix; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.MathTransformFactory; |
| import org.opengis.referencing.operation.TransformException; |
| import org.apache.sis.referencing.operation.matrix.Matrices; |
| import org.apache.sis.referencing.operation.matrix.Matrix3; |
| import org.apache.sis.util.ArraysExt; |
| |
| // Test imports |
| import org.junit.Test; |
| import org.apache.sis.test.TestUtilities; |
| import org.apache.sis.test.DependsOn; |
| import static org.apache.sis.test.Assert.*; |
| |
| // Branch-dependent imports |
| // (all imports removed) |
| |
| |
| /** |
| * Tests {@link PassThroughTransform}. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.0 |
| * @since 0.5 |
| * @module |
| */ |
| @DependsOn({ |
| CoordinateDomainTest.class, |
| LinearTransformTest.class, |
| ExponentialTransform1DTest.class |
| }) |
| public final strictfp class PassThroughTransformTest extends MathTransformTestCase { |
| /** |
| * The random number generator to be used in this test. |
| */ |
| private Random random; |
| |
| /** |
| * Verifies argument validation performed by {@link MathTransforms#passThrough(int, MathTransform, int)}. |
| */ |
| @Test |
| public void testIllegalArgument() { |
| final MathTransform subTransform = MathTransforms.identity(1); |
| try { |
| MathTransforms.passThrough(-1, subTransform, 0); |
| fail("An illegal argument should have been detected"); |
| } catch (IllegalArgumentException e) { |
| final String message = e.getMessage(); |
| assertTrue(message, message.contains("firstAffectedCoordinate")); |
| } |
| try { |
| MathTransforms.passThrough(0, subTransform, -1); |
| fail("An illegal argument should have been detected"); |
| } catch (IllegalArgumentException e) { |
| final String message = e.getMessage(); |
| assertTrue(message, message.contains("numTrailingCoordinates")); |
| } |
| } |
| |
| /** |
| * Tests the pass through transform using an identity transform. |
| * The "pass-through" of such transform shall be itself the identity transform. |
| * |
| * @throws TransformException should never happen. |
| */ |
| @Test |
| public void testIdentity() throws TransformException { |
| final Matrix matrix = new Matrix3(); |
| runTest(MathTransforms.linear(matrix), IdentityTransform.class); |
| } |
| |
| /** |
| * Tests the pass-through transform using an affine transform. |
| * The "pass-through" of such transforms are optimized using matrix arithmetic. |
| * |
| * @throws TransformException should never happen. |
| */ |
| @Test |
| public void testLinear() throws TransformException { |
| final Matrix matrix = new Matrix3( |
| 4, 0, 0, |
| 0, 3, 0, |
| 0, 0, 1); |
| runTest(MathTransforms.linear(matrix), LinearTransform.class); |
| } |
| |
| /** |
| * Tests the general pass-through transform. |
| * This test uses a non-linear sub-transform for preventing the factory method to optimize. |
| * |
| * @throws TransformException should never happen. |
| */ |
| @Test |
| public void testPassthrough() throws TransformException { |
| runTest(ExponentialTransform1D.create(10, 2), PassThroughTransform.class); |
| } |
| |
| /** |
| * Tests the general pass-through transform with a sub-transform going from 3D to 2D points. |
| * |
| * @throws TransformException should never happen. |
| */ |
| @Test |
| public void testDimensionDecrease() throws TransformException { |
| isInverseTransformSupported = false; |
| runTest(new PseudoTransform(3, 2), PassThroughTransform.class); |
| } |
| |
| /** |
| * Tests the general pass-through transform with a sub-transform going from 2D to 3D points. |
| * |
| * @throws TransformException should never happen. |
| */ |
| @Test |
| public void testDimensionIncrease() throws TransformException { |
| isInverseTransformSupported = false; |
| runTest(new PseudoTransform(2, 3), PassThroughTransform.class); |
| } |
| |
| /** |
| * Tests a pass-through transform built using the given sub-transform. |
| * |
| * @param subTransform the sub-transform to use for building pass-through transform. |
| * @param expectedClass the expected implementation class of pass-through transforms. |
| * @throws TransformException if a transform failed. |
| */ |
| private void runTest(final MathTransform subTransform, final Class<? extends MathTransform> expectedClass) |
| throws TransformException |
| { |
| random = TestUtilities.createRandomNumberGenerator(); |
| /* |
| * Test many combinations of "first affected coordinate" and "number of trailing coordinates" parameters. |
| * For each combination we create a passthrough transform, test it with the 'verifyTransform' method. |
| */ |
| for (int firstAffectedCoordinate=0; firstAffectedCoordinate<=3; firstAffectedCoordinate++) { |
| for (int numTrailingCoordinates=0; numTrailingCoordinates<=3; numTrailingCoordinates++) { |
| final int numAdditionalOrdinates = firstAffectedCoordinate + numTrailingCoordinates; |
| transform = MathTransforms.passThrough(firstAffectedCoordinate, subTransform, numTrailingCoordinates); |
| if (numAdditionalOrdinates == 0) { |
| assertSame("Failed to recognize that no passthrough was needed.", subTransform, transform); |
| continue; |
| } |
| assertNotSame(subTransform, transform); |
| assertTrue ("Wrong transform class.", expectedClass.isInstance(transform)); |
| assertEquals ("Wrong number of source dimensions.", |
| subTransform.getSourceDimensions() + numAdditionalOrdinates, transform.getSourceDimensions()); |
| assertEquals ("Wrong number of target dimensions.", |
| subTransform.getTargetDimensions() + numAdditionalOrdinates, transform.getTargetDimensions()); |
| verifyTransform(subTransform, firstAffectedCoordinate); |
| } |
| } |
| } |
| |
| /** |
| * Tests the current {@linkplain #transform transform} using an array of random coordinate values, |
| * and compares the result against the expected ones. This method computes itself the expected results. |
| * |
| * @param subTransform the sub transform used by the current pass-through transform. |
| * @param firstAffectedCoordinate first input/output dimension used by {@code subTransform}. |
| * @throws TransformException if a transform failed. |
| */ |
| private void verifyTransform(final MathTransform subTransform, final int firstAffectedCoordinate) throws TransformException { |
| validate(); |
| /* |
| * Prepare two arrays: |
| * - passthrough data, to be given to the transform to be tested. |
| * - sub-transform data, which we will use internally for verifying the pass-through work. |
| */ |
| final int sourceDim = transform.getSourceDimensions(); |
| final int targetDim = transform.getTargetDimensions(); |
| final int subSrcDim = subTransform.getSourceDimensions(); |
| final int subTgtDim = subTransform.getTargetDimensions(); |
| final int numPts = ORDINATE_COUNT / sourceDim; |
| final double[] passthroughData = CoordinateDomain.RANGE_10.generateRandomInput(random, sourceDim, numPts); |
| final double[] subTransformData = new double[numPts * StrictMath.max(subSrcDim, subTgtDim)]; |
| Arrays.fill(subTransformData, Double.NaN); |
| for (int i=0; i<numPts; i++) { |
| System.arraycopy(passthroughData, firstAffectedCoordinate + i*sourceDim, |
| subTransformData, i*subSrcDim, subSrcDim); |
| } |
| subTransform.transform(subTransformData, 0, subTransformData, 0, numPts); |
| assertFalse(ArraysExt.hasNaN(subTransformData)); |
| /* |
| * Build the array of expected data by copying ourself the sub-transform results. |
| */ |
| final int numTrailingCoordinates = targetDim - subTgtDim - firstAffectedCoordinate; |
| final double[] expectedData = new double[targetDim * numPts]; |
| for (int i=0; i<numPts; i++) { |
| int srcOffset = i * sourceDim; |
| int dstOffset = i * targetDim; |
| final int s = firstAffectedCoordinate + subSrcDim; |
| System.arraycopy(passthroughData, srcOffset, expectedData, dstOffset, firstAffectedCoordinate); |
| System.arraycopy(subTransformData, i*subTgtDim, expectedData, dstOffset += firstAffectedCoordinate, subTgtDim); |
| System.arraycopy(passthroughData, srcOffset+s, expectedData, dstOffset + subTgtDim, numTrailingCoordinates); |
| } |
| assertEquals(subTransform.isIdentity(), Arrays.equals(passthroughData, expectedData)); |
| /* |
| * Now process to the transform and compares the results with the expected ones. |
| */ |
| tolerance = 0; // Results should be strictly identical because we used the same inputs. |
| final double[] transformedData = new double[StrictMath.max(sourceDim, targetDim) * numPts]; |
| transform.transform(passthroughData, 0, transformedData, 0, numPts); |
| assertCoordinatesEqual("PassThroughTransform results do not match the results computed by this test.", |
| targetDim, expectedData, 0, transformedData, 0, numPts, false); |
| /* |
| * Test inverse transform. |
| */ |
| if (isInverseTransformSupported) { |
| tolerance = 1E-8; |
| Arrays.fill(transformedData, Double.NaN); |
| transform.inverse().transform(expectedData, 0, transformedData, 0, numPts); |
| assertCoordinatesEqual("Inverse of PassThroughTransform do not give back the original data.", |
| sourceDim, passthroughData, 0, transformedData, 0, numPts, false); |
| } |
| /* |
| * Verify the consistency between different 'transform(…)' methods. |
| */ |
| final float[] sourceAsFloat = ArraysExt.copyAsFloats(passthroughData); |
| final float[] targetAsFloat = verifyConsistency(sourceAsFloat); |
| assertEquals("Unexpected length of transformed array.", expectedData.length, targetAsFloat.length); |
| } |
| |
| /** |
| * Tests {@link PassThroughTransform#tryConcatenate(boolean, MathTransform, MathTransformFactory)}. |
| * This tests creates a non-linear transform of 6→7 dimensions, then applies a filter keeping only |
| * target dimensions 1, 4 and 6 (corresponding to source dimensions 1 and 5). |
| * |
| * @throws FactoryException if an error occurred while combining the transforms. |
| */ |
| @Test |
| public void testTryConcatenate() throws FactoryException { |
| PassThroughTransform ps = new PassThroughTransform(2, new PseudoTransform(2, 3), 2); |
| MathTransform c = ps.tryConcatenate(false, MathTransforms.linear(Matrices.create(4, 8, new double[] { |
| 0, 1, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 1, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 1, 0, |
| 0, 0, 0, 0, 0, 0, 0, 1})), null); |
| |
| final List<MathTransform> steps = MathTransforms.getSteps(c); |
| assertEquals("Number of steps", 3, steps.size()); |
| /* |
| * We need to remove source dimensions 0, 2, 3 and 4. We can not remove dimensions 2 and 3 before |
| * pass-through because they are used by the sub-transform. It leaves us dimensions 0 and 4 which |
| * can be removed here. |
| */ |
| assertMatrixEquals("Expected removal of dimensions 0 and 4 before pass-through", Matrices.create(5, 7, new double[] { |
| 0, 1, 0, 0, 0, 0, 0, |
| 0, 0, 1, 0, 0, 0, 0, |
| 0, 0, 0, 1, 0, 0, 0, |
| 0, 0, 0, 0, 0, 1, 0, |
| 0, 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(0)), 0); |
| /* |
| * The number of pass-through dimensions have decreased from 2 to 1 on both sides of the sub-transform. |
| */ |
| final PassThroughTransform reduced = (PassThroughTransform) steps.get(1); |
| assertEquals("firstAffectedCoordinate", 1, reduced.firstAffectedCoordinate); |
| assertEquals("numTrailingCoordinates", 1, reduced.numTrailingCoordinates); |
| assertSame ("subTransform", ps.subTransform, reduced.subTransform); |
| /* |
| * We still have to remove source dimensions 2 and 3. Since we removed dimension 0 in previous step, |
| * the indices of dimensions to removed have shifted to 1 and 2. |
| */ |
| assertMatrixEquals("Expected removal of dimensions 1 and 2 after pass-through", Matrices.create(4, 6, new double[] { |
| 1, 0, 0, 0, 0, 0, |
| 0, 0, 0, 1, 0, 0, |
| 0, 0, 0, 0, 1, 0, |
| 0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), 0); |
| } |
| } |