net.imglib2.transform.integer.MixedTransform Maven / Gradle / Ivy
Show all versions of imglib2 Show documentation
/*
* #%L
* ImgLib2: a general-purpose, multidimensional image processing library.
* %%
* Copyright (C) 2009 - 2024 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld,
* John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke,
* Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner,
* Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert,
* Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin,
* Jean-Yves Tinevez and Michael Zinsmaier.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package net.imglib2.transform.integer;
import java.util.Arrays;
import net.imglib2.Localizable;
import net.imglib2.Positionable;
import net.imglib2.concatenate.Concatenable;
import net.imglib2.concatenate.PreConcatenable;
/**
* Mixed transform allows to express common integer view transformations such as
* translation, rotation, rotoinversion, and projection.
*
*
* It transform a n-dimensional source vector to a m-dimensional target vector,
* and can be represented as a m+1 × n+1 homogeneous
* matrix. The mixed transform can be decomposed as follows:
*
*
* - project down (discard some components of the source vector)
* - component permutation
* - component inversion
* - project up (add zero components in the target vector)
* - translation
*
*
*
* The project down and component permutation steps are implemented by the
* {@link #setComponentMapping(int[]) component mapping}. This is a lookup array
* that specifies for each target dimension from which source dimension it is
* taken.
* Note, that it is not allowed to set this array such that a source component
* is mapped to several target components!
*
*
*
* @author Tobias Pietzsch
*/
public class MixedTransform extends AbstractMixedTransform implements Concatenable< Mixed >, PreConcatenable< Mixed >
{
/**
* dimension of source vector.
*/
protected final int numSourceDimensions;
/**
* translation is added to the target vector after applying permutation,
* projection, inversion operations.
*/
protected final long[] translation;
/**
* for each component of the target vector (before translation). should the
* value be taken from a source vector component (false) or should it be
* zero (true).
*/
protected final boolean[] zero;
/**
* for each component of the target vector (before translation). should the
* source vector component be inverted (true).
*/
protected final boolean[] invert;
/**
* for each component of the target vector (before translation). from which
* source vector component should it be taken.
*/
protected final int[] component;
public MixedTransform( final int sourceDim, final int targetDim )
{
super( targetDim );
this.numSourceDimensions = sourceDim;
translation = new long[ targetDim ];
zero = new boolean[ targetDim ];
invert = new boolean[ targetDim ];
component = new int[ targetDim ];
for ( int d = 0; d < targetDim; ++d )
{
if ( d < sourceDim )
{
component[ d ] = d;
}
else
{
component[ d ] = 0;
zero[ d ] = true;
}
}
}
@Override
public int numSourceDimensions()
{
return numSourceDimensions;
}
@Override
public void getTranslation( final long[] t )
{
assert t.length == numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
t[ d ] = translation[ d ];
}
@Override
public long getTranslation( final int d )
{
assert d <= numTargetDimensions;
return translation[ d ];
}
public void setTranslation( final long[] t )
{
assert t.length == numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
translation[ d ] = t[ d ];
}
public void setInverseTranslation( final long[] tinv )
{
assert tinv.length == numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
translation[ d ] = -tinv[ d ];
}
@Override
public void getComponentZero( final boolean[] zero )
{
assert zero.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
zero[ d ] = this.zero[ d ];
}
}
@Override
public boolean getComponentZero( final int d )
{
assert d <= numTargetDimensions;
return zero[ d ];
}
/**
* Set which target dimensions are _not_ taken from source dimensions.
*
*
* For instance, if the transform maps 2D (x,y) coordinates to the first two
* components of a 3D (x,y,z) coordinate, this will be [false, false, true]
*
*
* @param zero
* array that says for each component of the target vector
* (before translation) whether the value should be taken from a
* source vector component (false) or should be set to zero
* (true).
*/
public void setComponentZero( final boolean[] zero )
{
assert zero.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
this.zero[ d ] = zero[ d ];
}
}
@Override
public void getComponentMapping( final int[] component )
{
assert component.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
component[ d ] = this.component[ d ];
}
}
@Override
public int getComponentMapping( final int d )
{
assert d <= numTargetDimensions;
return component[ d ];
}
/**
* Set for each target dimensions from which source dimension it is taken.
*
*
* For instance, if the transform maps 2D (x,y) coordinates to the first two
* components of a 3D (x,y,z) coordinate, this will be [0, 1, x]. Here, x
* can be any value because the third target dimension does not correspond
* to any source dimension, which can be realized using
* {@link #setComponentZero(boolean[])}.
*
*
*
* Note, that it is not allowed to set the {@code component} array such that
* a source component is mapped to several target components!
*
*
* @param component
* array that says for each component of the target vector
* (before translation) from which source vector component it
* should be taken.
*/
public void setComponentMapping( final int[] component )
{
assert component.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
this.component[ d ] = component[ d ];
}
}
@Override
public void getComponentInversion( final boolean[] invert )
{
assert invert.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
invert[ d ] = this.invert[ d ];
}
}
@Override
public boolean getComponentInversion( final int d )
{
assert d <= numTargetDimensions;
return invert[ d ];
}
/**
* Set for each target component, whether the source component it is taken
* from should be inverted.
*
*
* For instance, if rotating a 2D (x,y) coordinates by 180 degrees will map
* it to (-x,-y). In this case, this will be [true, true].
*
*
* @param invert
* array that says for each component of the target vector
* (before translation) whether the source vector component it is
* taken from should be inverted (true).
*/
public void setComponentInversion( final boolean[] invert )
{
assert invert.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
this.invert[ d ] = invert[ d ];
}
}
@Override
public void apply( final long[] source, final long[] target )
{
assert source.length >= numSourceDimensions;
assert target.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
target[ d ] = translation[ d ];
if ( !zero[ d ] )
{
final long v = source[ component[ d ] ];
if ( invert[ d ] )
target[ d ] -= v;
else
target[ d ] += v;
}
}
}
@Override
public void apply( final int[] source, final int[] target )
{
assert source.length >= numSourceDimensions;
assert target.length >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
target[ d ] = ( int ) translation[ d ];
if ( !zero[ d ] )
{
final long v = source[ component[ d ] ];
if ( invert[ d ] )
target[ d ] -= v;
else
target[ d ] += v;
}
}
}
@Override
public void apply( final Localizable source, final Positionable target )
{
assert source.numDimensions() >= numSourceDimensions;
assert target.numDimensions() >= numTargetDimensions;
for ( int d = 0; d < numTargetDimensions; ++d )
{
long pos = translation[ d ];
if ( !zero[ d ] )
{
final long v = source.getLongPosition( component[ d ] );
if ( invert[ d ] )
pos -= v;
else
pos += v;
}
target.setPosition( pos, d );
}
}
@Override
public MixedTransform concatenate( final Mixed t )
{
assert this.numSourceDimensions == t.numTargetDimensions();
final MixedTransform result = new MixedTransform( t.numSourceDimensions(), this.numTargetDimensions );
for ( int d = 0; d < result.numTargetDimensions; ++d )
{
result.translation[ d ] = this.translation[ d ];
if ( this.zero[ d ] )
{
result.zero[ d ] = true;
result.invert[ d ] = false;
result.component[ d ] = 0;
}
else
{
final int c = this.component[ d ];
final long v = t.getTranslation( c );
if ( this.invert[ d ] )
result.translation[ d ] -= v;
else
result.translation[ d ] += v;
if ( t.getComponentZero( c ) )
{
result.zero[ d ] = true;
result.invert[ d ] = false;
result.component[ d ] = 0;
}
else
{
result.zero[ d ] = false;
result.invert[ d ] = ( this.invert[ d ] != t.getComponentInversion( c ) );
result.component[ d ] = t.getComponentMapping( c );
}
}
}
return result;
}
@Override
public Class< Mixed > getConcatenableClass()
{
return Mixed.class;
}
@Override
public MixedTransform preConcatenate( final Mixed t )
{
assert t.numSourceDimensions() == this.numTargetDimensions;
final MixedTransform result = new MixedTransform( this.numSourceDimensions, t.numTargetDimensions() );
for ( int d = 0; d < result.numTargetDimensions; ++d )
{
result.translation[ d ] = t.getTranslation( d );
if ( t.getComponentZero( d ) )
{
result.zero[ d ] = true;
result.invert[ d ] = false;
result.component[ d ] = 0;
}
else
{
final int c = t.getComponentMapping( d );
final long v = this.translation[ c ];
if ( t.getComponentInversion( d ) )
result.translation[ d ] -= v;
else
result.translation[ d ] += v;
if ( this.zero[ c ] )
{
result.zero[ d ] = true;
result.invert[ d ] = false;
result.component[ d ] = 0;
}
else
{
result.zero[ d ] = false;
result.invert[ d ] = ( t.getComponentInversion( d ) != this.invert[ c ] );
result.component[ d ] = this.component[ c ];
}
}
}
return result;
}
@Override
public Class< Mixed > getPreConcatenableClass()
{
return Mixed.class;
}
/**
* set parameters to transform
.
*
* @param transform
*/
public void set( final Mixed transform )
{
assert numSourceDimensions == transform.numSourceDimensions();
assert numTargetDimensions == transform.numTargetDimensions();
transform.getTranslation( translation );
transform.getComponentZero( zero );
transform.getComponentMapping( component );
transform.getComponentInversion( invert );
}
/**
* Get the matrix that transforms homogeneous source points to homogeneous
* target points. For testing purposes.
*/
@Override
public double[][] getMatrix()
{
final double[][] mat = new double[ numTargetDimensions + 1 ][ numSourceDimensions + 1 ];
mat[ numTargetDimensions ][ numSourceDimensions ] = 1;
for ( int d = 0; d < numTargetDimensions; ++d )
{
mat[ d ][ numSourceDimensions ] = translation[ d ];
}
for ( int d = 0; d < numTargetDimensions; ++d )
{
if ( zero[ d ] == false )
{
mat[ d ][ component[ d ] ] = invert[ d ] ? -1 : 1;
}
}
return mat;
}
/**
* Check whether the transforms has a full mapping of source to target
* components (no source component is discarded).
*
* @return whether there is a full mapping of source to target components.
*/
public boolean hasFullSourceMapping()
{
final boolean[] sourceMapped = new boolean[ numSourceDimensions ];
for ( int d = 0; d < numTargetDimensions; ++d )
{
if ( !zero[ d ] )
{
sourceMapped[ component[ d ] ] = true;
}
}
for ( int d = 0; d < numSourceDimensions; ++d )
{
if ( !sourceMapped[ d ] ) { return false; }
}
return true;
}
@Override
public String toString()
{
return "MixedTransform{" +
numSourceDimensions + "->" + numTargetDimensions +
", component=" + Arrays.toString( component ) +
", invert=" + Arrays.toString( invert ) +
", zero=" + Arrays.toString( zero ) +
", translation=" + Arrays.toString( translation ) +
'}';
}
}