/*-
* #%L
* Fiji distribution of ImageJ for the life sciences.
* %%
* Copyright (C) 2007 - 2017 Fiji developers.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package mpicbg.spim.mpicbg;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import mpicbg.models.AbstractAffineModel3D;
import mpicbg.models.AffineModel3D;
import mpicbg.models.ErrorStatistic;
import mpicbg.models.HomographyModel2D;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.Model;
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
import mpicbg.models.PointMatch;
import mpicbg.models.RigidModel3D;
import mpicbg.models.Tile;
import mpicbg.models.TileConfiguration;
import mpicbg.spim.io.IOFunctions;
import mpicbg.spim.registration.ViewDataBeads;
import mpicbg.spim.registration.ViewStructure;
import mpicbg.spim.registration.bead.Bead;
import mpicbg.spim.registration.segmentation.Nucleus;
import mpicbg.spim.vis3d.VisualizationSketchTikZ;
import mpicbg.util.TransformUtils;
import spim.vecmath.Transform3D;
import spim.vecmath.Vector3d;
public class TileConfigurationSPIM
{
final static private DecimalFormat decimalFormat = new DecimalFormat();
final static private DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols();
final private Set< TileSPIM > tiles = new HashSet< TileSPIM >();
final public Set< TileSPIM > getTiles(){ return tiles; }
final private Set< TileSPIM > fixedTiles = new HashSet< TileSPIM >();
final public Set< TileSPIM > getFixedTiles(){ return fixedTiles; }
private double minError = Double.MAX_VALUE;
final public double getMinError() { return minError; }
private double maxError = 0.0;
final public double getMaxError() { return maxError; }
private double error = Double.MAX_VALUE;
final public double getError() { return error; }
protected int debugLevel;
public TileConfigurationSPIM( final int debugLevel )
{
this.debugLevel = debugLevel;
decimalFormatSymbols.setGroupingSeparator( ',' );
decimalFormatSymbols.setDecimalSeparator( '.' );
decimalFormat.setDecimalFormatSymbols( decimalFormatSymbols );
decimalFormat.setMaximumFractionDigits( 3 );
decimalFormat.setMinimumFractionDigits( 3 );
}
protected void println( String s )
{
if ( debugLevel <= ViewStructure.DEBUG_MAIN )
IOFunctions.println( s );
}
/**
* Cleanup.
*/
public void clear()
{
tiles.clear();
fixedTiles.clear();
minError = Double.MAX_VALUE;
maxError = 0.0;
error = Double.MAX_VALUE;
}
/**
* Add a single {@link Tile}.
*
* @param t
*/
final public void addTile( final TileSPIM t ){ tiles.add( t ); }
/**
* Add a {@link Collection} of {@link Tile Tiles}.
*
* @param t
*/
final public void addTiles( final Collection< ? extends TileSPIM > t ){ tiles.addAll( t ); }
/**
* Add all {@link Tile Tiles} of another {@link TileConfiguration}.
*
* @param t
*/
final public void addTiles( final TileConfigurationSPIM t ){ tiles.addAll( t.tiles ); }
/**
* Fix a single {@link Tile}.
*
* @param t
*/
final public void fixTile( final TileSPIM t ){ fixedTiles.add( t ); }
/**
* Update all {@link PointMatch Correspondences} in all {@link Tile Tiles}
* and estimate the average displacement.
*/
final protected void update()
{
double cd = 0.0;
minError = Double.MAX_VALUE;
maxError = 0.0;
for ( TileSPIM t : tiles )
{
t.update();
double d = t.getDistance();
if ( d < minError ) minError = d;
if ( d > maxError ) maxError = d;
cd += d;
}
cd /= tiles.size();
error = cd;
}
final public void computeError()
{
update();
if ( debugLevel <= ViewStructure.DEBUG_MAIN )
{
println( " average displacement: " + decimalFormat.format( error ) + "px" );
println( " minimal displacement: " + decimalFormat.format( minError ) + "px" );
println( " maximal displacement: " + decimalFormat.format( maxError ) + "px" );
}
}
/**
* Minimize the displacement of all {@link PointMatch Correspondence pairs}
* of all {@link Tile Tiles}
*
* @param maxAllowedError do not accept convergence if error is < max_error
* @param maxIterations stop after that many iterations even if there was
* no minimum found
* @param maxPlateauwidth convergence is reached if the average absolute
* slope in an interval of this size and half this size is smaller than
* 0.0001 (in double accuracy). This is assumed to prevent the algorithm
* from stopping at plateaus smaller than this value.
* @param debugLevel defines if the Optimizer prints the output at the end of the process
*/
public void optimize(
final double maxAllowedError,
final int maxIterations,
final int maxPlateauwidth,
final int debugLevel ) throws NotEnoughDataPointsException, IllDefinedDataPointsException
{
final ErrorStatistic observer = new ErrorStatistic( maxPlateauwidth + 1 );
int i = 0;
boolean proceed = i < maxIterations;
while ( proceed )
{
for ( final TileSPIM tile : tiles )
{
if ( fixedTiles.contains( tile ) ) continue;
tile.update();
tile.fitModel();
tile.update();
}
update();
observer.add( error );
if ( i > maxPlateauwidth )
{
proceed = error > maxAllowedError;
int d = maxPlateauwidth;
while ( !proceed && d >= 1 )
{
try
{
proceed |= Math.abs( observer.getWideSlope( d ) ) > 0.0001;
}
catch ( Exception e ) { e.printStackTrace(); }
d /= 2;
}
}
proceed &= ++i < maxIterations;
}
if ( debugLevel <= ViewStructure.DEBUG_MAIN )
{
println( "Successfully optimized configuration of " + tiles.size() + " tiles after " + i + " iterations:" );
println( " average displacement: " + decimalFormat.format( error ) + "px" );
println( " minimal displacement: " + decimalFormat.format( minError ) + "px" );
println( " maximal displacement: " + decimalFormat.format( maxError ) + "px" );
}
}
/**
* Minimize the displacement of all {@link PointMatch Correspondence pairs}
* of all {@link Tile Tiles}
*
* @param maxAllowedError do not accept convergence if error is < max_error
* @param maxIterations stop after that many iterations even if there was
* no minimum found
* @param maxPlateauwidth convergence is reached if the average absolute
* slope in an interval of this size and half this size is smaller than
* 0.0001 (in double accuracy). This is assumed to prevent the algorithm
* from stopping at plateaus smaller than this value.
* @param debugLevel defines if the Optimizer prints the output at the end of the process
*/
public void optimizeWithSketchTikZ(
final double maxAllowedError,
final int maxIterations,
final int maxPlateauwidth,
final int debugLevel ) throws NotEnoughDataPointsException, IllDefinedDataPointsException
{
final ErrorStatistic observer = new ErrorStatistic( maxPlateauwidth + 1 );
int i = 0;
final double factor = 0.005;
boolean proceed = i < maxIterations;
while ( proceed )
{
for ( final TileSPIM tile : tiles )
{
if ( fixedTiles.contains( tile ) ) continue;
tile.updateWithDections();
if ( i > 0 )
{
tile.fitModel();
tile.updateWithDections();
}
}
//if ( i == 0 )
//if ( error < 7.5 )
{
SketchTikZFileObject files = SketchTikZFileObject.initOutputFile( "src/templates/beadimage-movie.sk", "src/templates/movie/dros_nucleibased_" + i + ".sk" );
for ( TileSPIM tile : tiles )
{
final AbstractAffineModel3D<?> m = (AbstractAffineModel3D<?>)tile.getModel().copy();
Transform3D t = TransformUtils.getTransform3D1( m );
ViewDataBeads parent = tile.getParent();
// the bounding box is not scaled yet, so we have to apply
// the correct z stretching
Transform3D tmp = new Transform3D();
Transform3D tmp2 = new Transform3D(t);
tmp.setScale( new Vector3d(1, 1, parent.getZStretching()) );
tmp2.mul( tmp );
t = tmp2;
// back up the model
final AbstractAffineModel3D backUp = (AbstractAffineModel3D)tile.getModel().copy();
backUp.set( (AbstractAffineModel3D)parent.getTile().getModel() );
parent.getTile().getModel().set( TransformUtils.getAffineModel3D( t ) );
/*
Transform3D backUpT3D = parent.transformation;
Transform3D backUpIT3D = parent.inverseTransformation;
parent.transformation = t;
parent.inverseTransformation = new Transform3D( t );
parent.inverseTransformation.invert();
*/
System.out.println("Writing view " + parent.getName() + " @ iteration " + i );
files.getOutput().println( VisualizationSketchTikZ.drawView( parent, factor ) );
files.getOutput().println( VisualizationSketchTikZ.drawBeads( parent.getBeadStructure().getBeadList(), TransformUtils.getTransform3D1( m ), "Bead", factor, 2 ) );
for ( Bead bead : parent.getBeadStructure().getBeadList() )
{
double distance = bead.getDistance();
if ( distance >= 0 )
{
int color = (int)Math.round( Math.log10( distance + 1 ) * 256.0 );
// max value == 100
if ( color > 511 )
color = 511;
if ( color < 0)
color = 0;
files.getOutput().println( VisualizationSketchTikZ.drawBead( bead, TransformUtils.getTransform3D1( m ), "RansacBead" + color, factor ) );
}
}
// write back old (unscaled) model
parent.getTile().getModel().set( backUp );
//parent.transformation = backUpT3D;
//parent.inverseTransformation = backUpIT3D;
}
files.finishFiles();
//System.exit( 0 );
}
update();
observer.add( error );
if ( i > maxPlateauwidth )
{
proceed = error > maxAllowedError;
int d = maxPlateauwidth;
while ( !proceed && d >= 1 )
{
try
{
proceed |= Math.abs( observer.getWideSlope( d ) ) > 0.0001;
}
catch ( Exception e ) { e.printStackTrace(); }
d /= 2;
}
}
proceed &= ++i < maxIterations;
}
if ( debugLevel <= ViewStructure.DEBUG_MAIN )
{
println( "Successfully optimized configuration of " + tiles.size() + " tiles after " + i + " iterations:" );
println( " average displacement: " + decimalFormat.format( error ) + "px" );
println( " minimal displacement: " + decimalFormat.format( minError ) + "px" );
println( " maximal displacement: " + decimalFormat.format( maxError ) + "px" );
}
}
/**
* Minimize the displacement of all {@link PointMatch Correspondence pairs}
* of all {@link Tile Tiles}
*
* @param maxAllowedError do not accept convergence if error is > max_error
* @param maxIterations stop after that many iterations even if there was
* no minimum found
* @param maxPlateauwidth convergence is reached if the average absolute
* slope in an interval of this size and half this size is smaller than
* 0.0001 (in double accuracy). This is assumed to prevent the algorithm
* from stopping at plateaus smaller than this value.
* @param debugLevel defines if the Optimizer prints the output at the end of the process
*/
/**
* Minimize the displacement of all {@link PointMatch Correspondence pairs}
* of all {@link Tile Tiles}
*
* @param maxAllowedError do not accept convergence if error is > max_error
* @param maxIterations stop after that many iterations even if there was
* no minimum found
* @param maxPlateauwidth convergence is reached if the average absolute
* slope in an interval of this size and half this size is smaller than
* 0.0001 (in double accuracy). This is assumed to prevent the algorithm
* from stopping at plateaus smaller than this value.
* @param debugLevel defines if the Optimizer prints the output at the end of the process
*/
public void optimizeWithSketchTikZNuclei(
final double maxAllowedError,
final int maxIterations,
final int maxPlateauwidth,
final int debugLevel ) throws NotEnoughDataPointsException, IllDefinedDataPointsException
{
final ErrorStatistic observer = new ErrorStatistic( maxPlateauwidth + 1 );
//
// parameters for movie
//
// how many detailed iterations to show (one frame per updated angle)
final int numDetailedIterations = 5;
// till which error show one frame per iteration
final double thresholdErrorFastMovie = 7.5;
// from the error threshold on, every how many iterations to do one frame
final int ratioFastMovie = 8;
// in a first run find minX, minY, minZ
// for that we need a constant order of tiles
final ArrayList<ViewDataBeads> tilesSorted = new ArrayList<ViewDataBeads>();
for ( final TileSPIM<?> tile : tiles )
tilesSorted.add( tile.getParent() );
Collections.sort( tilesSorted );
int i = 0, j = 0, frame = 0;
final double factor = 0.01 * 0.45;
int freeTiles = 0;
for ( final Tile<?> tile : tiles )
if ( !fixedTiles.contains( tile ) )
freeTiles++;
boolean proceed = i < maxIterations;
while ( proceed )
{
//for ( final TileSPIM<?> tile1 : tiles )
for ( final ViewDataBeads view : tilesSorted )
{
final TileSPIM<?> tile1 = view.getTile();
if ( fixedTiles.contains( tile1 ) ) continue;
tile1.updateWithDections();
if ( i > 0 )
{
tile1.fitModel();
tile1.updateWithDections();
}
// to get the most accurate error
update();
if ( j >= freeTiles-1 && ( j <= (numDetailedIterations+1)*freeTiles ||
( error > thresholdErrorFastMovie && j%freeTiles == 0 ) ||
( error <= thresholdErrorFastMovie && j%(freeTiles*ratioFastMovie) == 0 )) )
{
System.out.println( j + "(" + i + ") " + error + " frame=" + frame );
//if ( frame < 10 || frame == 525 )
{
SketchTikZFileObject files = SketchTikZFileObject.initOutputFile( "src/templates/fish-movie.sk", "src/templates/movie_fish/fish_" + frame + ".sk" );
for ( TileSPIM tile : tiles )
{
final AffineModel3D m = new AffineModel3D();
m.set( (AffineModel3D)tile.getModel() );
Transform3D t = TransformUtils.getTransform3D( m );
ViewDataBeads parent = tile.getParent();
// the bounding box is not scaled yet, so we have to apply
// the correct z stretching
Transform3D tmp = new Transform3D();
Transform3D tmp2 = new Transform3D(t);
tmp.setScale( new Vector3d(1, 1, parent.getZStretching()) );
tmp2.mul( tmp );
t = tmp2;
// back up the model
AffineModel3D backUp = new AffineModel3D( );
backUp.set( (AffineModel3D)parent.getTile().getModel() );
parent.getTile().getModel().set( TransformUtils.getAffineModel3D( t ) );
/*
Transform3D backUpT3D = parent.transformation;
Transform3D backUpIT3D = parent.inverseTransformation;
parent.transformation = t;
parent.inverseTransformation = new Transform3D( t );
parent.inverseTransformation.invert();
*/
//System.out.println("Writing view " + parent.getName() + " @ iteration " + i );
files.getOutput().println( VisualizationSketchTikZ.drawView( parent, factor ) );
files.getOutput().println( VisualizationSketchTikZ.drawNuclei( parent.getNucleiStructure().getNucleiList(), TransformUtils.getTransform3D( m ), factor ) );
for ( Nucleus nucleus : parent.getNucleiStructure().getNucleiList() )
{
double distance = nucleus.getDistance();
if ( distance >= 0 )
{
int color = (int)Math.round( Math.log10( distance + 1 ) * 256.0 );
// max value == 100
if ( color > 511 )
color = 511;
if ( color < 0)
color = 0;
files.getOutput().println( VisualizationSketchTikZ.drawNucleus( nucleus, TransformUtils.getTransform3D( m ), "RansacBead" + color, factor ) );
}
}
// write back old (unscaled) model
parent.getTile().getModel().set( backUp );
//parent.transformation = backUpT3D;
//parent.inverseTransformation = backUpIT3D;
}
files.putTextEntry( 14.0f, -4.5f, "Iteration " + i + " (" + decimalFormat.format( error ) + " px)" );
files.finishFiles();
}
frame++;
//System.exit( 0 );
}
++j;
}
update();
observer.add( error );
if ( i > maxPlateauwidth )
{
proceed = error > maxAllowedError;
int d = maxPlateauwidth;
while ( !proceed && d >= 1 )
{
try
{
proceed |= Math.abs( observer.getWideSlope( d ) ) > 0.0001;
}
catch ( Exception e ) { e.printStackTrace(); }
d /= 2;
}
}
proceed &= ++i < maxIterations;
}
if ( debugLevel <= ViewStructure.DEBUG_MAIN )
{
println( "Successfully optimized configuration of " + tiles.size() + " tiles after " + i + " iterations:" );
println( " average displacement: " + decimalFormat.format( error ) + "px" );
println( " minimal displacement: " + decimalFormat.format( minError ) + "px" );
println( " maximal displacement: " + decimalFormat.format( maxError ) + "px" );
}
}
/*
public void optimizeWithSketchTikZ(
double maxAllowedError,
int maxIterations,
int maxPlateauwidth,
ViewDataBeads[] views,
boolean showDetails ) throws NotEnoughDataPointsException, IllDefinedDataPointsException
{
final double factor = 0.005;
ErrorStatistic observer = new ErrorStatistic( maxPlateauwidth + 1 );
int i = 0;
while ( i < maxIterations ) // do not run forever
{
for ( TileSPIM tile : tiles )
{
if ( !fixedTiles.contains( tile ) )
{
tile.updateWithBeads();
if ( i > 0 )
{
tile.fitModel();
tile.updateWithBeads();
}
}
}
if ( i == 10 )
{
FileObject files = FileObject.initOutputFile( "src/templates/beadimage-movie.sk", "src/templates/movie/beadimage_" + i + ".sk" );
for ( TileSPIM tile : tiles )
{
AffineModel3D m = (AffineModel3D) tile.getModel();
Transform3D t = MathLib.getTransform3D( m );
ViewDataBeads parent = tile.getParent();
// the bounding box is not scaled yet, so we have to apply
// the correct z stretching
Transform3D tmp = new Transform3D();
Transform3D tmp2 = new Transform3D(t);
tmp.setScale( new Vector3d(1, 1, parent.getZStretching()) );
tmp2.mul( tmp );
t = tmp2;
// back up the model
AffineModel3D backUp = new AffineModel3D( );
backUp.set( parent.getTile().getModel() );
parent.getTile().getModel().set( MathLib.getAffineModel3D( t ) );
System.out.println("Writing view " + parent.getName() + " @ iteration " + i );
files.getOutput().println( VisualizationSketchTikZ.drawView( parent, factor ) );
files.getOutput().println( VisualizationSketchTikZ.drawBeads( parent.getBeadStructure().getBeadList(), MathLib.getTransform3D( m ), "Bead", factor ) );
for ( Bead bead : parent.getBeadStructure().getBeadList() )
{
double distance = bead.getDistance();
if ( distance >= 0 )
{
int color = Math.round( (double)Math.log10( distance + 1 ) * 256f );
// max value == 100
if ( color > 511 )
color = 511;
if ( color < 0)
color = 0;
files.getOutput().println( VisualizationSketchTikZ.drawBead( bead, MathLib.getTransform3D( m ), "RansacBead" + color, factor ) );
}
}
// write back old (unscaled) model
parent.getTile().getModel().set( backUp );
}
files.finishFiles();
System.exit( 0 );
}
update();
observer.add( error );
if (
i >= maxPlateauwidth &&
error < maxAllowedError &&
Math.abs( observer.getWideSlope( maxPlateauwidth ) ) <= 0.0001 &&
Math.abs( observer.getWideSlope( maxPlateauwidth / 2 ) ) <= 0.0001 )
{
break;
}
++i;
}
if (showDetails)
{
System.out.println( "Successfully optimized configuration of " + tiles.size() + " tiles after " + i + " iterations:" );
System.out.println( " average displacement: " + decimalFormat.format( error ) + "px" );
System.out.println( " minimal displacement: " + decimalFormat.format( minError ) + "px" );
System.out.println( " maximal displacement: " + decimalFormat.format( maxError ) + "px" );
}
}
*/
/**
* Computes a pre-alignemnt of all non-fixed {@link Tile}s by propagating the pairwise
* models. This does not give a correct registration but a very good starting point
* for the global optimization. This is necessary for models where the global optimization
* is not guaranteed to converge like the {@link HomographyModel2D}, {@link RigidModel3D}, ...
*
* @return - a list of {@link Tile}s that could not be pre-aligned
* @throws NotEnoughDataPointsException
// * @throws {@link IllDefinedDataPointsException}
*/
public List< Tile< ? > > preAlign() throws NotEnoughDataPointsException, IllDefinedDataPointsException
{
// first get order all tiles by
// a) unaligned
// b) aligned - which initially only contains the fixed ones
final ArrayList< Tile< ? > > unAlignedTiles = new ArrayList< Tile< ? > >();
final ArrayList< Tile< ? > > alignedTiles = new ArrayList< Tile< ? > >();
// if no tile is fixed, take another */
if ( getFixedTiles().size() == 0 )
{
final Iterator< ? extends Tile > it = this.getTiles().iterator();
alignedTiles.add( it.next() );
while ( it.hasNext() )
unAlignedTiles.add( it.next() );
}
else
{
for ( final Tile< ? > tile : this.getTiles() )
{
if ( this.getFixedTiles().contains( tile ) )
alignedTiles.add( tile );
else
unAlignedTiles.add( tile );
}
}
// we go through each fixed/aligned tile and try to find a pre-alignment
// for all other unaligned tiles
for ( final ListIterator< Tile< ?> > referenceIterator = alignedTiles.listIterator(); referenceIterator.hasNext(); )
{
// once all tiles are aligned we can quit this loop
if ( unAlignedTiles.size() == 0 )
break;
// get the next reference tile (either a fixed or an already aligned one
final Tile< ? > referenceTile = referenceIterator.next();
// transform all reference points into the reference coordinate system
// so that we get the direct model even if we are not anymore at the
// level of the fixed tile
referenceTile.apply();
// now we go through the unaligned tiles to see if we can align it to the current reference tile one
for ( final ListIterator< Tile< ?> > targetIterator = unAlignedTiles.listIterator(); targetIterator.hasNext(); )
{
// get the tile that we want to preregister
final Tile< ? > targetTile = targetIterator.next();
// target tile is connected to reference tile
if ( referenceTile.getConnectedTiles().contains( targetTile ) )
{
// extract all PointMatches between reference and target tile and fit a model only on these
final ArrayList< PointMatch > pm = getConnectingPointMatches( targetTile, referenceTile );
// are there enough matches?
if ( pm.size() > targetTile.getModel().getMinNumMatches() )
{
// fit the model of the targetTile to the subset of matches
// mapping its local coordinates target.p.l into the world
// coordinates reference.p.w
// this will give us an approximation for the global optimization
targetTile.getModel().fit( pm );
// now that we managed to fit the model we remove the
// Tile from unaligned tiles and add it to aligned tiles
targetIterator.remove();
// now add the aligned target tile to the end of the reference list
int countFwd = 0;
while ( referenceIterator.hasNext() )
{
referenceIterator.next();
++countFwd;
}
referenceIterator.add( targetTile );
// move back to the current position
// (+1 because it add just behind the current position)
for ( int j = 0; j < countFwd + 1; ++j )
referenceIterator.previous();
}
}
}
}
return unAlignedTiles;
}
/**
* Returns an {@link ArrayList} of {@link PointMatch} that connect the targetTile and the referenceTile. The order of the
* {@link PointMatch} is PointMatch.p1 = target, PointMatch.p2 = reference. A {@link Model}.fit() will then solve the fit
* so that target.p1.l is mapped to reference.p2.w.
*
* @param targetTile - the {@link Tile} for which a {@link Model} can fit
* @param referenceTile - the {@link Tile} to which target will map
*
* @return - an {@link ArrayList} of all {@link PointMatch} that target and reference share
*/
public ArrayList<PointMatch> getConnectingPointMatches( final Tile<?> targetTile, final Tile<?> referenceTile )
{
final Set< PointMatch > referenceMatches = referenceTile.getMatches();
final ArrayList< Point > referencePoints = new ArrayList<Point>( referenceMatches.size() );
// add all points from the reference tile so that we can search for them
for ( final PointMatch pm : referenceMatches )
referencePoints.add( pm.getP1() );
// the result arraylist containing only the pointmatches from the target file
final ArrayList< PointMatch > connectedPointMatches = new ArrayList<PointMatch>();
// look for all PointMatches where targetTile.PointMatch.Point2 == referenceTile.PointMatch.Point1
// i.e. a PointMatch of the target tile that links a Point which is part of reference tile
for ( final PointMatch pm : targetTile.getMatches() )
if ( referencePoints.contains( pm.getP2() ) )
connectedPointMatches.add( pm );
return connectedPointMatches;
}
}