/** * 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. */ package mpicbg.trakem2.transform; import ij.process.ByteProcessor; import ij.process.ImageProcessor; import ij.process.ShortProcessor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import mpicbg.models.AffineModel2D; import mpicbg.models.PointMatch; import mpicbg.models.TransformMesh; import mpicbg.util.Util; /** * Specialized {@link mpicbg.ij.TransformMapping} for Patches, that is, rendering * the image, outside mask and mask in one go instead three. * * @author Stephan Saalfeld saalfeld@mpi-cbg.de * @version 0.1a */ public class TransformMeshMappingWithMasks< T extends TransformMesh > extends mpicbg.ij.TransformMeshMapping< T > { final static public class ImageProcessorWithMasks { final public ImageProcessor ip; public ByteProcessor outside = null; public ImageProcessor mask = null; public ImageProcessorWithMasks( final ImageProcessor ip, final ImageProcessor mask, final ByteProcessor outside ) { this.ip = ip; if ( outside != null ) { if ( ip.getWidth() == outside.getWidth() && ip.getHeight() == outside.getHeight() ) this.outside = outside; else System.err.println( "ImageProcessorWithMasks: ip and outside mask differ in size, setting outside = null" ); } if ( mask != null ) { if ( ip.getWidth() == mask.getWidth() && ip.getHeight() == mask.getHeight() ) this.mask = mask; else System.err.println( "ImageProcessorWithMasks: ip and mask differ in size, setting mask = null" ); } } final public int getWidth(){ return ip.getWidth(); } final public int getHeight(){ return ip.getHeight(); } } final static private class MapTriangleThread extends Thread { final private AtomicInteger i; final private List< AffineModel2D > triangles; final private TransformMesh transform; final ImageProcessorWithMasks source, target; MapTriangleThread( final AtomicInteger i, final List< AffineModel2D > triangles, final TransformMesh transform, final ImageProcessorWithMasks source, final ImageProcessorWithMasks target ) { this.i = i; this.triangles = triangles; this.transform = transform; this.source = source; this.target = target; } @Override final public void run() { int k = i.getAndIncrement(); while ( !isInterrupted() && k < triangles.size() ) { if ( source.mask == null ) mapTriangle( transform, triangles.get( k ), source.ip, target.ip, target.outside ); else mapTriangle( transform, triangles.get( k ), source.ip, source.mask, target.ip, target.mask, target.outside ); k = i.getAndIncrement(); } } } final static private class MapTriangleInterpolatedThread extends Thread { final private AtomicInteger i; final private List< AffineModel2D > triangles; final private TransformMesh transform; final ImageProcessorWithMasks source, target; MapTriangleInterpolatedThread( final AtomicInteger i, final List< AffineModel2D > triangles, final TransformMesh transform, final ImageProcessorWithMasks source, final ImageProcessorWithMasks target ) { this.i = i; this.triangles = triangles; this.transform = transform; this.source = source; this.target = target; } @Override final public void run() { int k = i.getAndIncrement(); while ( !isInterrupted() && k < triangles.size() ) { if ( source.mask == null ) mapTriangleInterpolated( transform, triangles.get( k ), source.ip, target.ip, target.outside ); else mapTriangleInterpolated( transform, triangles.get( k ), source.ip, source.mask, target.ip, target.mask, target.outside ); k = i.getAndIncrement(); } } } final static private class MapShortAlphaTriangleThread extends Thread { final private AtomicInteger i; final private List< AffineModel2D > triangles; final private TransformMesh transform; final ShortProcessor source, target; final ByteProcessor alpha; MapShortAlphaTriangleThread( final AtomicInteger i, final List< AffineModel2D > triangles, final TransformMesh transform, final ShortProcessor source, final ByteProcessor alpha, final ShortProcessor target ) { this.i = i; this.triangles = triangles; this.transform = transform; this.source = source; this.alpha = alpha; this.target = target; } @Override final public void run() { int k = i.getAndIncrement(); while ( !isInterrupted() && k < triangles.size() ) { mapShortAlphaTriangle( transform, triangles.get( k ), source, alpha, target ); k = i.getAndIncrement(); } } } public TransformMeshMappingWithMasks( final T t ) { super( t ); } final static protected void mapTriangle( final TransformMesh m, final AffineModel2D ai, final ImageProcessor source, final ImageProcessor target, final ByteProcessor targetOutside ) { final int w = target.getWidth() - 1; final int h = target.getHeight() - 1; final ArrayList< PointMatch > pm = m.getAV().get( ai ); final double[] min = new double[ 2 ]; final double[] max = new double[ 2 ]; calculateBoundingBox( pm, min, max ); final int minX = Math.max( 0, Util.roundPos( min[ 0 ] ) ); final int minY = Math.max( 0, Util.roundPos( min[ 1 ] ) ); final int maxX = Math.min( w, Util.roundPos( max[ 0 ] ) ); final int maxY = Math.min( h, Util.roundPos( max[ 1 ] ) ); final double[] a = pm.get( 0 ).getP2().getW(); final double ax = a[ 0 ]; final double ay = a[ 1 ]; final double[] b = pm.get( 1 ).getP2().getW(); final double bx = b[ 0 ]; final double by = b[ 1 ]; final double[] c = pm.get( 2 ).getP2().getW(); final double cx = c[ 0 ]; final double cy = c[ 1 ]; final double[] t = new double[ 2 ]; for ( int y = minY; y <= maxY; ++y ) { for ( int x = minX; x <= maxX; ++x ) { if ( isInTriangle( ax, ay, bx, by, cx, cy, x, y ) ) { t[ 0 ] = x; t[ 1 ] = y; try { ai.applyInverseInPlace( t ); } catch ( final Exception e ) { //e.printStackTrace( System.err ); continue; } target.set( x, y, source.getPixel( ( int )( t[ 0 ] + 0.5f ), ( int )( t[ 1 ] + 0.5f ) ) ); targetOutside.set( x, y, 0xff ); } } } } final static protected void mapTriangleInterpolated( final TransformMesh m, final AffineModel2D ai, final ImageProcessor source, final ImageProcessor target, final ByteProcessor targetOutside ) { final int w = target.getWidth() - 1; final int h = target.getHeight() - 1; final ArrayList< PointMatch > pm = m.getAV().get( ai ); final double[] min = new double[ 2 ]; final double[] max = new double[ 2 ]; calculateBoundingBox( pm, min, max ); final int minX = Math.max( 0, Util.roundPos( min[ 0 ] ) ); final int minY = Math.max( 0, Util.roundPos( min[ 1 ] ) ); final int maxX = Math.min( w, Util.roundPos( max[ 0 ] ) ); final int maxY = Math.min( h, Util.roundPos( max[ 1 ] ) ); final double[] a = pm.get( 0 ).getP2().getW(); final double ax = a[ 0 ]; final double ay = a[ 1 ]; final double[] b = pm.get( 1 ).getP2().getW(); final double bx = b[ 0 ]; final double by = b[ 1 ]; final double[] c = pm.get( 2 ).getP2().getW(); final double cx = c[ 0 ]; final double cy = c[ 1 ]; final double[] t = new double[ 2 ]; for ( int y = minY; y <= maxY; ++y ) { for ( int x = minX; x <= maxX; ++x ) { if ( isInTriangle( ax, ay, bx, by, cx, cy, x, y ) ) { t[ 0 ] = x; t[ 1 ] = y; try { ai.applyInverseInPlace( t ); } catch ( final Exception e ) { //e.printStackTrace( System.err ); continue; } target.set( x, y, source.getPixelInterpolated( t[ 0 ], t[ 1 ] ) ); targetOutside.set( x, y, 0xff ); } } } } final static protected void mapTriangle( final TransformMesh m, final AffineModel2D ai, final ImageProcessor source, final ImageProcessor sourceMask, final ImageProcessor target, final ImageProcessor targetMask, final ByteProcessor targetOutside ) { final int w = target.getWidth() - 1; final int h = target.getHeight() - 1; final ArrayList< PointMatch > pm = m.getAV().get( ai ); final double[] min = new double[ 2 ]; final double[] max = new double[ 2 ]; calculateBoundingBox( pm, min, max ); final int minX = Math.max( 0, Util.roundPos( min[ 0 ] ) ); final int minY = Math.max( 0, Util.roundPos( min[ 1 ] ) ); final int maxX = Math.min( w, Util.roundPos( max[ 0 ] ) ); final int maxY = Math.min( h, Util.roundPos( max[ 1 ] ) ); final double[] a = pm.get( 0 ).getP2().getW(); final double ax = a[ 0 ]; final double ay = a[ 1 ]; final double[] b = pm.get( 1 ).getP2().getW(); final double bx = b[ 0 ]; final double by = b[ 1 ]; final double[] c = pm.get( 2 ).getP2().getW(); final double cx = c[ 0 ]; final double cy = c[ 1 ]; final double[] t = new double[ 2 ]; for ( int y = minY; y <= maxY; ++y ) { for ( int x = minX; x <= maxX; ++x ) { if ( isInTriangle( ax, ay, bx, by, cx, cy, x, y ) ) { t[ 0 ] = x; t[ 1 ] = y; try { ai.applyInverseInPlace( t ); } catch ( final Exception e ) { //e.printStackTrace( System.err ); continue; } target.set( x, y, source.getPixel( ( int )( t[ 0 ] + 0.5f ), ( int )( t[ 1 ] + 0.5f ) ) ); targetOutside.set( x, y, 0xff ); targetMask.set( x, y, sourceMask.getPixel( ( int )( t[ 0 ] + 0.5f ), ( int )( t[ 1 ] + 0.5f ) ) ); } } } } final static protected void mapTriangleInterpolated( final TransformMesh m, final AffineModel2D ai, final ImageProcessor source, final ImageProcessor sourceMask, final ImageProcessor target, final ImageProcessor targetMask, final ByteProcessor targetOutside ) { final int w = target.getWidth() - 1; final int h = target.getHeight() - 1; final ArrayList< PointMatch > pm = m.getAV().get( ai ); final double[] min = new double[ 2 ]; final double[] max = new double[ 2 ]; calculateBoundingBox( pm, min, max ); final int minX = Math.max( 0, Util.roundPos( min[ 0 ] ) ); final int minY = Math.max( 0, Util.roundPos( min[ 1 ] ) ); final int maxX = Math.min( w, Util.roundPos( max[ 0 ] ) ); final int maxY = Math.min( h, Util.roundPos( max[ 1 ] ) ); final double[] a = pm.get( 0 ).getP2().getW(); final double ax = a[ 0 ]; final double ay = a[ 1 ]; final double[] b = pm.get( 1 ).getP2().getW(); final double bx = b[ 0 ]; final double by = b[ 1 ]; final double[] c = pm.get( 2 ).getP2().getW(); final double cx = c[ 0 ]; final double cy = c[ 1 ]; final double[] t = new double[ 2 ]; for ( int y = minY; y <= maxY; ++y ) { for ( int x = minX; x <= maxX; ++x ) { if ( isInTriangle( ax, ay, bx, by, cx, cy, x, y ) ) { t[ 0 ] = x; t[ 1 ] = y; try { ai.applyInverseInPlace( t ); } catch ( final Exception e ) { //e.printStackTrace( System.err ); continue; } target.set( x, y, source.getPixelInterpolated( t[ 0 ], t[ 1 ] ) ); targetOutside.set( x, y, 0xff ); targetMask.set( x, y, sourceMask.getPixelInterpolated( t[ 0 ], t[ 1 ] ) ); } } } } final static protected void mapShortAlphaTriangle( final TransformMesh m, final AffineModel2D ai, final ShortProcessor source, final ByteProcessor alpha, final ShortProcessor target ) { final int w = target.getWidth() - 1; final int h = target.getHeight() - 1; final ArrayList< PointMatch > pm = m.getAV().get( ai ); final double[] min = new double[ 2 ]; final double[] max = new double[ 2 ]; calculateBoundingBox( pm, min, max ); final int minX = Math.max( 0, Util.roundPos( min[ 0 ] ) ); final int minY = Math.max( 0, Util.roundPos( min[ 1 ] ) ); final int maxX = Math.min( w, Util.roundPos( max[ 0 ] ) ); final int maxY = Math.min( h, Util.roundPos( max[ 1 ] ) ); final double[] a = pm.get( 0 ).getP2().getW(); final double ax = a[ 0 ]; final double ay = a[ 1 ]; final double[] b = pm.get( 1 ).getP2().getW(); final double bx = b[ 0 ]; final double by = b[ 1 ]; final double[] c = pm.get( 2 ).getP2().getW(); final double cx = c[ 0 ]; final double cy = c[ 1 ]; final double[] t = new double[ 2 ]; for ( int y = minY; y <= maxY; ++y ) { for ( int x = minX; x <= maxX; ++x ) { if ( isInTriangle( ax, ay, bx, by, cx, cy, x, y ) ) { t[ 0 ] = x; t[ 1 ] = y; try { ai.applyInverseInPlace( t ); } catch ( final Exception e ) { //e.printStackTrace( System.err ); continue; } final int is = source.getPixelInterpolated( t[ 0 ], t[ 1 ] ); final int it = target.get( x, y ); final double f = alpha.getPixelInterpolated( t[ 0 ], t[ 1 ] ) / 255.0; final double v = it + f * ( is - it ); target.set( x, y, ( int )Math.max( 0, Math.min( 65535, Math.round( v ) ) ) ); } } } } final public void map( final ImageProcessorWithMasks source, final ImageProcessorWithMasks target, final int numThreads ) { target.outside = new ByteProcessor( target.getWidth(), target.getHeight() ); final HashMap< AffineModel2D, ArrayList< PointMatch >> av = transform.getAV(); if ( numThreads > 1 ) { final ArrayList< AffineModel2D > triangles = new ArrayList< AffineModel2D >( av.keySet() ); final AtomicInteger i = new AtomicInteger( 0 ); final ArrayList< Thread > threads = new ArrayList< Thread >( numThreads ); for ( int k = 0; k < numThreads; ++k ) { final Thread mtt = new MapTriangleThread( i, triangles, transform, source, target ); threads.add( mtt ); mtt.start(); } for ( final Thread mtt : threads ) { try { mtt.join(); } catch ( final InterruptedException e ) {} } } else if ( source.mask == null ) { for ( final AffineModel2D triangle : av.keySet() ) { mapTriangle( transform, triangle, source.ip, target.ip, target.outside ); } } else { for ( final AffineModel2D triangle : av.keySet() ) { mapTriangle( transform, triangle, source.ip, source.mask, target.ip, target.mask, target.outside ); } } } final public void mapInterpolated( final ImageProcessorWithMasks source, final ImageProcessorWithMasks target, final int numThreads ) { target.outside = new ByteProcessor( target.getWidth(), target.getHeight() ); source.ip.setInterpolationMethod( ImageProcessor.BILINEAR ); if ( source.mask != null ) { source.mask.setInterpolationMethod( ImageProcessor.BILINEAR ); } final HashMap< AffineModel2D, ArrayList< PointMatch >> av = transform.getAV(); if ( numThreads > 1 ) { final ArrayList< AffineModel2D > triangles = new ArrayList< AffineModel2D >( av.keySet() ); final AtomicInteger i = new AtomicInteger( 0 ); final ArrayList< Thread > threads = new ArrayList< Thread >( numThreads ); for ( int k = 0; k < numThreads; ++k ) { final Thread mtt = new MapTriangleInterpolatedThread( i, triangles, transform, source, target ); threads.add( mtt ); mtt.start(); } for ( final Thread mtt : threads ) { try { mtt.join(); } catch ( final InterruptedException e ) {} } } else if ( source.mask == null ) { for ( final AffineModel2D triangle : av.keySet() ) { mapTriangleInterpolated( transform, triangle, source.ip, target.ip, target.outside ); } } else { for ( final AffineModel2D triangle : av.keySet() ) { mapTriangleInterpolated( transform, triangle, source.ip, source.mask, target.ip, target.mask, target.outside ); } } } final public void map( final ImageProcessorWithMasks source, final ImageProcessorWithMasks target ) { map( source, target, Runtime.getRuntime().availableProcessors() ); } final public void mapInterpolated( final ImageProcessorWithMasks source, final ImageProcessorWithMasks target ) { mapInterpolated( source, target, Runtime.getRuntime().availableProcessors() ); } /** * Render source into target using alpha composition. * Interpolation is specified by the interpolation methods * set in source and alpha. * * @param source * @param alpha * @param target * @param numThreads */ final public void map( final ShortProcessor source, final ByteProcessor alpha, final ShortProcessor target, final int numThreads ) { final List< AffineModel2D > l = new ArrayList< AffineModel2D >(); l.addAll( transform.getAV().keySet() ); final AtomicInteger i = new AtomicInteger( 0 ); final ArrayList< Thread > threads = new ArrayList< Thread >( numThreads ); for ( int k = 0; k < numThreads; ++k ) { final Thread mtt = new MapShortAlphaTriangleThread( i, l, transform, source, alpha, target ); threads.add( mtt ); mtt.start(); } for ( final Thread mtt : threads ) { try { mtt.join(); } catch ( final InterruptedException e ) {} } } /** * Render source into master using alpha composition. * Interpolation is specified by the interpolation methods * set in source and alpha. * * @param source * @param alpha * @param target */ final public void map( final ShortProcessor source, final ByteProcessor alpha, final ShortProcessor target ) { map( source, alpha, target, Runtime.getRuntime().availableProcessors() ); } }