/** * 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.align; import ij.IJ; import ij.ImagePlus; import ij.process.ImageProcessor; import ini.trakem2.display.Layer; import ini.trakem2.display.Patch; import ini.trakem2.parallel.ExecutorProvider; import ini.trakem2.utils.Filter; import ini.trakem2.utils.Utils; import java.awt.Rectangle; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import mpicbg.ij.SIFT; import mpicbg.imagefeatures.Feature; import mpicbg.imagefeatures.FloatArray2DSIFT; /** * @author Stephan Saalfeld saalfeld@mpi-cbg.de */ final public class AlignmentUtils { final static public class ParamPointMatch implements Serializable { private static final long serialVersionUID = 7526084042028501775L; final public FloatArray2DSIFT.Param sift = new FloatArray2DSIFT.Param(); /** * Closest/next closest neighbor distance ratio */ public float rod = 0.92f; @Override public boolean equals( final Object o ) { if ( getClass().isInstance( o ) ) { final ParamPointMatch oppm = ( ParamPointMatch )o; return oppm.sift.equals( sift ) & oppm.rod == rod; } else return false; } public boolean clearCache = true; public int maxNumThreadsSift = Runtime.getRuntime().availableProcessors(); } private AlignmentUtils() {} final static public String layerName( final Layer layer ) { return new StringBuffer( "layer z=" ) .append( String.format( "%.3f", layer.getZ() ) ) .append( " `" ) .append( layer.getTitle() ) .append( "'" ) .toString(); } final static public List< Patch > filterPatches( final Layer layer, final Filter< Patch > filter ) { final List< Patch > patches = layer.getAll( Patch.class ); if ( filter != null ) { for ( final Iterator< Patch > it = patches.iterator(); it.hasNext(); ) { if ( !filter.accept( it.next() ) ) it.remove(); } } return patches; } /** * Extract SIFT features and save them into the project folder. * * @param layerRange the list of layers to be aligned * @param box a rectangular region of interest that will be used for alignment * @param scale scale factor <= 1.0 * @param filter a name based filter for Patches (can be null) * @param siftParam SIFT extraction parameters * @param clearCache * @param numThreads * @throws ExecutionException * @throws InterruptedException */ final static protected void extractAndSaveLayerFeatures( final List< Layer > layerRange, final Rectangle box, final double scale, final Filter< Patch > filter, final FloatArray2DSIFT.Param siftParam, final boolean clearCache, final int numThreads ) throws ExecutionException, InterruptedException { final long sTime = System.currentTimeMillis(); final ExecutorService exec = ExecutorProvider.getExecutorService(1.0f / (float)numThreads); /* extract features for all slices and store them to disk */ final AtomicInteger counter = new AtomicInteger( 0 ); final ArrayList< Future< ArrayList< Feature > > > siftTasks = new ArrayList< Future< ArrayList< Feature > > >(); for (final Layer layer : layerRange) { siftTasks.add(exec.submit( new LayerFeatureCallable(layer, box, scale, filter, siftParam, clearCache))); } /* join */ try { for ( final Future< ArrayList< Feature > > fu : siftTasks ) { IJ.showProgress( counter.getAndIncrement(), layerRange.size() - 1 ); fu.get(); } } catch ( final InterruptedException e ) { Utils.log( "Feature extraction interrupted." ); siftTasks.clear(); //exec.shutdownNow(); throw e; } catch ( final ExecutionException e ) { Utils.log( "Execution exception during feature extraction." ); siftTasks.clear(); //exec.shutdownNow(); throw e; } siftTasks.clear(); IJ.log("Extracted features in " + (System.currentTimeMillis() - sTime) + "ms"); //exec.shutdown(); } private static class LayerFeatureCallable implements Callable<ArrayList<Feature>>, Serializable { private final Layer layer; private final Filter<Patch> filter; private final boolean clearCache; private final Rectangle finalBox; final FloatArray2DSIFT.Param siftParam; final double scale; public LayerFeatureCallable(final Layer layer, final Rectangle finalBox, final double scale, final Filter<Patch> filter, final FloatArray2DSIFT.Param siftParam, final boolean clearCache) { this.layer = layer; this.filter = filter; this.clearCache = clearCache; this.finalBox = finalBox; this.siftParam = siftParam; this.scale = scale; } @Override public ArrayList<Feature> call() throws Exception { final String layerName = layerName( layer ); //IJ.showProgress( counter.getAndIncrement(), layerRange.size() - 1 ); final List< Patch > patches = filterPatches( layer, filter ); ArrayList< Feature > fs = null; if ( !clearCache ) fs = mpicbg.trakem2.align.Util.deserializeFeatures( layer.getProject(), siftParam, "layer", layer.getId() ); if ( null == fs ) { /* free memory */ layer.getProject().getLoader().releaseAll(); final FloatArray2DSIFT sift = new FloatArray2DSIFT( siftParam ); final SIFT ijSIFT = new SIFT( sift ); fs = new ArrayList< Feature >(); final ImageProcessor ip = layer.getProject().getLoader().getFlatImage( layer, finalBox, scale, 0xffffffff, ImagePlus.GRAY8, Patch.class, patches, true ).getProcessor(); ijSIFT.extractFeatures( ip, fs ); Utils.log( fs.size() + " features extracted for " + layerName ); if ( !mpicbg.trakem2.align.Util.serializeFeatures( layer.getProject(), siftParam, "layer", layer.getId(), fs ) ) Utils.log( "FAILED to store serialized features for " + layerName ); } else Utils.log( fs.size() + " features loaded for " + layerName ); return fs; } } }