/** * License: GPL * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License 2 * as published by the Free Software Foundation. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * @author Stephan Saalfeld saalfeld@mpi-cbg.de * */ package mpicbg.trakem2.align; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.PathIterator; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import ij.process.ByteProcessor; import ini.trakem2.display.Displayable; import ini.trakem2.display.Layer; import ini.trakem2.display.Patch; import mpicbg.models.Affine2D; import mpicbg.models.Model; import mpicbg.models.NoninvertibleModelException; import mpicbg.models.Point; import mpicbg.models.PointMatch; import mpicbg.models.Tile; import mpicbg.models.TileConfiguration; abstract public class AbstractAffineTile2D< A extends Model< A > & Affine2D< A > > extends mpicbg.models.Tile< A > { private static final long serialVersionUID = -2229469138975635535L; final protected Patch patch; final public Patch getPatch(){ return patch; } final public double getWidth(){ return patch.getWidth(); } final public double getHeight(){ return patch.getHeight(); } /** * A set of virtual point correspondences that are used to connect a tile * to the rest of the {@link TileConfiguration} assuming that the initial * layout was correct. * * Virtual point correspondences are also stored in matches. This is just * to keep track about them. * * Virtual point correspondences have to be removed * for real connections. * * TODO Not yet tested---Do we need these virtual connections? * */ final protected Set< PointMatch > virtualMatches = new HashSet< PointMatch >(); final public Set< PointMatch > getVirtualMatches(){ return virtualMatches; } final public boolean addVirtualMatch( final PointMatch match ) { if ( virtualMatches.add( match ) ) return matches.add( match ); return false; } final public boolean removeVirtualMatch( final PointMatch match ) { if ( virtualMatches.remove( match ) ) return matches.remove( match ); return false; } /** * Remove all virtual {@link PointMatch matches}. * * TODO Not yet tested---Do we need these virtual connections? */ final public void clearVirtualMatches() { for ( final PointMatch m : virtualMatches ) matches.remove( m ); virtualMatches.clear(); } abstract protected void initModel(); public AbstractAffineTile2D( final A model, final Patch patch ) { super( model ); this.patch = patch; initModel(); } final public AffineTransform createAffine() { return model.createAffine(); } final public void updatePatch() { patch.setAffineTransform( createAffine() ); patch.updateMipMaps(); } final public ByteProcessor createMaskedByteImage() { final ByteProcessor mask; final Patch.PatchImage pai = patch.createTransformedImage(); if ( pai.mask == null ) mask = pai.outside; else mask = pai.mask; pai.target.setMinAndMax( patch.getMin(), patch.getMax() ); final ByteProcessor target = ( ByteProcessor )pai.target.convertToByte( true ); /* Other than any other ImageProcessor, ByteProcessors ignore scaling, so ... */ if ( ByteProcessor.class.isInstance( pai.target ) ) { final float s = 255.0f / ( float )( patch.getMax() - patch.getMin() ); final int m = ( int )patch.getMin(); final byte[] targetBytes = ( byte[] )target.getPixels(); for ( int i = 0; i < targetBytes.length; ++i ) { targetBytes[ i ] = ( byte )( Math.max( 0, Math.min( 255, ( ( targetBytes[ i ] & 0xff ) - m ) * s ) ) ); } target.setMinAndMax( 0, 255 ); } if ( mask != null ) { final byte[] targetBytes = ( byte[] )target.getPixels(); final byte[] maskBytes = (byte[])mask.getPixels(); if ( pai.outside != null ) { final byte[] outsideBytes = (byte[])pai.outside.getPixels(); for ( int i = 0; i < outsideBytes.length; ++i ) { if ( ( outsideBytes[ i ]&0xff ) != 255 ) maskBytes[ i ] = 0; final float a = ( float )( maskBytes[ i ] & 0xff ) / 255f; final int t = ( targetBytes[ i ] & 0xff ); targetBytes[ i ] = ( byte )( t * a + 127 * ( 1 - a ) ); } } else { for ( int i = 0; i < targetBytes.length; ++i ) { final float a = ( float )( maskBytes[ i ] & 0xff ) / 255f; final int t = ( targetBytes[ i ] & 0xff ); targetBytes[ i ] = ( byte )( t * a + 127 * ( 1 - a ) ); } } } return target; } final public boolean intersects( final AbstractAffineTile2D< ? > t, final boolean sloppy ) { return patch.intersects( t.patch, sloppy ); } final public boolean intersects( final AbstractAffineTile2D< ? > t ) { return intersects( t, false ); } /** * Add a virtual {@linkplain PointMatch connection} between two * {@linkplain AbstractAffineTile2D Tiles}. The * {@linkplain PointMatch connection} is placed in the center of the * intersection area of both tiles. * * TODO Not yet tested---Do we need these virtual connections? * * @param t */ final public void makeVirtualConnection( final AbstractAffineTile2D< ? > t ) { final Area a = new Area( patch.getPerimeter() ); final Area b = new Area( t.patch.getPerimeter() ); a.intersect( b ); final double[] fa = new double[ 2 ]; int i = 0; final double[] coords = new double[ 6 ]; final PathIterator p = a.getPathIterator( null ); while ( !p.isDone() ) { p.currentSegment( coords ); fa[ 0 ] += coords[ 0 ]; fa[ 1 ] += coords[ 1 ]; ++i; p.next(); } if ( i > 0 ) { fa[ 0 ] /= i; fa[ 1 ] /= i; final double[] fb = fa. clone(); try { model.applyInverseInPlace( fa ); t.model.applyInverseInPlace( fb ); } catch ( final NoninvertibleModelException e ) { return; } final Point pa = new Point( fa ); final Point pb = new Point( fb ); final PointMatch ma = new PointMatch( pa, pb, 0.1f ); final PointMatch mb = new PointMatch( pb, pa, 0.1f ); addVirtualMatch( ma ); addConnectedTile( t ); t.addVirtualMatch( mb ); t.addConnectedTile( this ); } } /** * Pair all {@link AbstractAffineTile2D Tiles} from a {@link List}. * Adds the pairs into tilePairs. * * @param tiles * @param tilePairs */ final static public void pairTiles( final List< AbstractAffineTile2D< ? > > tiles, final List< AbstractAffineTile2D< ? >[] > tilePairs ) { for ( int a = 0; a < tiles.size(); ++a ) { for ( int b = a + 1; b < tiles.size(); ++b ) { final AbstractAffineTile2D< ? > ta = tiles.get( a ); final AbstractAffineTile2D< ? > tb = tiles.get( b ); tilePairs.add( new AbstractAffineTile2D< ? >[]{ ta, tb } ); } } } /** * Search a {@link List} of {@link AbstractAffineTile2D Tiles} for * overlapping pairs. Adds the pairs into tilePairs. * * @param tiles * @param tilePairs */ final static public <AAT extends AbstractAffineTile2D< ? >> void pairOverlappingTiles( final List< AAT > tiles, final List< AbstractAffineTile2D< ? >[] > tilePairs, final boolean sloppyOverlapTest ) { final HashSet< Patch > visited = new HashSet< Patch >(); final ArrayList< AbstractAffineTile2D< ? >[] > tilePairCandidates = new ArrayList< AbstractAffineTile2D< ? >[] >(); /* LUT for tiles */ final Hashtable< Patch, AAT > lut = new Hashtable< Patch, AAT >(); for ( final AAT tile : tiles ) lut.put( tile.patch, tile ); for ( int a = 0; a < tiles.size(); ++a ) { final AbstractAffineTile2D< ? > ta = tiles.get( a ); final Patch pa = ta.patch; visited.add( pa ); final Layer la = pa.getLayer(); for ( final Displayable d : la.getDisplayables( Patch.class, pa.getBoundingBox() ) ) { final Patch pb = ( Patch )d; if ( visited.contains( pb ) ) continue; final AAT tb = lut.get( pb ); if ( tb == null ) continue; tilePairCandidates.add( new AbstractAffineTile2D< ? >[]{ ta, tb } ); } } if ( sloppyOverlapTest ) tilePairs.addAll( tilePairCandidates ); else { // TODO Fix this and use what the user wants to provide final ExecutorService exec = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors() ); final ArrayList< Future< ? > > futures = new ArrayList< Future< ? > >(); for ( final AbstractAffineTile2D< ? >[] tatb : tilePairCandidates ) { futures.add( exec.submit( new Runnable() { @Override public void run() { if ( tatb[ 0 ].intersects( tatb[ 1 ] ) ) synchronized ( tilePairs ) { tilePairs.add( tatb ); } } } ) ); } try { for ( final Future< ? > f : futures ) f.get(); } catch ( final InterruptedException e ) { e.printStackTrace(); } catch ( final ExecutionException e ) { e.printStackTrace(); } finally { exec.shutdown(); } } // // 1. Precompute the Area of each tile's Patch // final HashMap< Patch, Pair< AAT, Area > > m = new HashMap< Patch, Pair< AAT, Area > >(); // // A lazy collection of pairs, computed in parallel ahead of consumption // final Iterable< Pair< AAT, Area > > ts = // new ParallelMapping< AAT, Pair< AAT, Area > >( // tiles, // new TaskFactory< AAT, Pair< AAT, Area > >() { // @Override // public Pair< AAT, Area > process( final AAT tile ) { // return new Pair< AAT, Area >( tile, tile.patch.getArea() ); // } // } // ); // for ( final Pair< AAT, Area > t : ts) { // m.put( t.a.patch, t ); // } // // // 2. Obtain the list of tile pairs, at one list per tile // final Iterable< List<AbstractAffineTile2D< ? >[]> > pairsList = // new ParallelMapping< AAT, List<AbstractAffineTile2D< ? >[]> >( // tiles, // new TaskFactory< AAT, List<AbstractAffineTile2D< ? >[]> >() { // @Override // public List<AbstractAffineTile2D< ? >[]> process( final AAT ta ) { // final Area a; // synchronized (m) { // a = m.get( ta.patch ).b; // } // final ArrayList<AbstractAffineTile2D< ? >[]> seq = new ArrayList<AbstractAffineTile2D< ? >[]>(); // for ( final Patch p : ta.patch.getLayer().getIntersecting( ta.patch, Patch.class ) ) { // if ( p == ta.patch ) // continue; // final Pair< AAT, Area > pair; // synchronized (m) { // pair = m.get(p); // } // // Check that the Patch is among those to consider in the alignment // if ( null != pair ) { // // Check that the Patch visible pixels overlap -- may not if it has an alpha mask or coordinate transform // if ( M.intersects( a, pair.b ) ) { // seq.add( new AbstractAffineTile2D< ? >[]{ ta, pair.a }); // } // } // } // return seq; // } // } // ); // // for (final List<AbstractAffineTile2D<?>[]> list: pairsList) { // tilePairs.addAll(list); // } } /** * Search a {@link List} of {@link AbstractAffineTile2D Tiles} for * overlapping pairs. Adds the pairs into tilePairs. * * @param tiles * @param tilePairs */ final static public <AAT extends AbstractAffineTile2D< ? >> void pairOverlappingTiles( final List< AAT > tiles, final List< AbstractAffineTile2D< ? >[] > tilePairs ) { pairOverlappingTiles( tiles, tilePairs, false ); } /** * Pair all {@link AbstractAffineTile2D Tiles} from two {@link List Lists}. * Adds the pairs into tilePairs. * * @param tilesA * @param tilesB * @param tilePairs */ final static public void pairTiles( final List< AbstractAffineTile2D< ? > > tilesA, final List< AbstractAffineTile2D< ? > > tilesB, final List< AbstractAffineTile2D< ? >[] > tilePairs ) { for ( int a = 0; a < tilesA.size(); ++a ) { for ( int b = 0; b < tilesB.size(); ++b ) { final AbstractAffineTile2D< ? > ta = tilesA.get( a ); final AbstractAffineTile2D< ? > tb = tilesB.get( b ); tilePairs.add( new AbstractAffineTile2D< ? >[]{ ta, tb } ); } } } /** * Search two {@link List Lists} of {@link AbstractAffineTile2D Tiles} for * overlapping pairs. Adds the pairs into tilePairs. * * @param tilesA * @param tilesB * @param tilePairs */ final static public void pairOverlappingTiles( final List< AbstractAffineTile2D< ? > > tilesA, final List< AbstractAffineTile2D< ? > > tilesB, final List< AbstractAffineTile2D< ? >[] > tilePairs, final boolean sloppyOverlapTest ) { for ( int a = 0; a < tilesA.size(); ++a ) { for ( int b = 0; b < tilesB.size(); ++b ) { final AbstractAffineTile2D< ? > ta = tilesA.get( a ); final AbstractAffineTile2D< ? > tb = tilesB.get( b ); if ( ta.intersects( tb, sloppyOverlapTest ) ) tilePairs.add( new AbstractAffineTile2D< ? >[]{ ta, tb } ); } } } /** * Search two {@link List Lists} of {@link AbstractAffineTile2D Tiles} for * overlapping pairs. Adds the pairs into tilePairs. * * @param tilesA * @param tilesB * @param tilePairs */ final static public void pairOverlappingTiles( final List< AbstractAffineTile2D< ? > > tilesA, final List< AbstractAffineTile2D< ? > > tilesB, final List< AbstractAffineTile2D< ? >[] > tilePairs ) { pairOverlappingTiles( tilesA, tilesB, tilePairs, false ); } /** * Extract the common {@linkplain PointMatch PointMatches} of two tiles. * * @param other * @param commonMatches */ final public void commonPointMatches( final Tile< ? > other, final Collection< PointMatch > commonMatches ) { for ( final PointMatch pm : matches ) for ( final PointMatch otherPm : other.getMatches() ) if ( pm.getP1() == otherPm.getP2() ) { commonMatches.add( pm ); break; } } }