/*- * #%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 spim.fiji.plugin.interestpointdetection; import ij.ImagePlus; import ij.gui.GenericDialog; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import mpicbg.imglib.image.Image; import mpicbg.imglib.type.numeric.real.FloatType; import mpicbg.imglib.wrapper.ImgLib2; import mpicbg.spim.data.sequence.Channel; import mpicbg.spim.data.sequence.TimePoint; import mpicbg.spim.data.sequence.ViewDescription; import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.io.IOFunctions; import mpicbg.spim.segmentation.InteractiveDoG; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.realtransform.AffineTransform3D; import spim.fiji.plugin.util.GenericDialogAppender; import spim.fiji.spimdata.SpimData2; import spim.fiji.spimdata.interestpoints.InterestPoint; import spim.process.cuda.CUDADevice; import spim.process.cuda.CUDASeparableConvolution; import spim.process.cuda.CUDATools; import spim.process.cuda.NativeLibraryTools; import spim.process.interestpointdetection.ProcessDOG; public class DifferenceOfGaussian extends DifferenceOf implements GenericDialogAppender { public static double defaultUseGPUMem = 75; public static double defaultS = 1.8; public static double defaultT = 0.008; public static double defaultSigma[]; public static double defaultThreshold[]; public static boolean defaultFindMin[]; public static boolean defaultFindMax[]; public static String[] computationOnChoice = new String[]{ "CPU (Java)", "GPU approximate (Nvidia CUDA via JNA)", "GPU accurate (Nvidia CUDA via JNA)" }; public static int defaultComputationChoiceIndex = 0; double[] sigma; double[] threshold; boolean[] findMin; boolean[] findMax; double percentGPUMem = defaultUseGPUMem; /** * 0 ... n == CUDA device i */ ArrayList< CUDADevice > deviceList = null; CUDASeparableConvolution cuda = null; boolean accurateCUDA = false; public DifferenceOfGaussian( final SpimData2 spimData, final List< ViewId > viewIdsToProcess ) { super( spimData, viewIdsToProcess ); } @Override public String getDescription() { return "Difference-of-Gaussian"; } @Override public DifferenceOfGaussian newInstance( final SpimData2 spimData, final List< ViewId > viewIdsToProcess ) { return new DifferenceOfGaussian( spimData, viewIdsToProcess ); } @Override public HashMap< ViewId, List< InterestPoint > > findInterestPoints( final TimePoint t ) { final HashMap< ViewId, List< InterestPoint > > interestPoints = new HashMap< ViewId, List< InterestPoint > >(); for ( final ViewDescription vd : SpimData2.getAllViewIdsForTimePointSorted( spimData, viewIdsToProcess, t ) ) { // make sure not everything crashes if one file is missing try { // // open the corresponding image (if present at this timepoint) // long time1 = System.currentTimeMillis(); if ( !vd.isPresent() ) continue; final Channel c = vd.getViewSetup().getChannel(); final AffineTransform3D correctCoordinates = new AffineTransform3D(); final RandomAccessibleInterval< net.imglib2.type.numeric.real.FloatType > input = openAndDownsample( spimData, vd, correctCoordinates ); long time2 = System.currentTimeMillis(); benchmark.openFiles += time2 - time1; preSmooth( input ); final Image< FloatType > img = ImgLib2.wrapFloatToImgLib1( (Img<net.imglib2.type.numeric.real.FloatType>)input ); // // compute Difference-of-Gaussian // List< InterestPoint > ips = ProcessDOG.compute( cuda, deviceList, accurateCUDA, percentGPUMem, img, (Img<net.imglib2.type.numeric.real.FloatType>)input, (float)sigma[ c.getId() ], (float)threshold[ c.getId() ], localization, Math.min( imageSigmaX, (float)sigma[ c.getId() ] ), Math.min( imageSigmaY, (float)sigma[ c.getId() ] ), Math.min( imageSigmaZ, (float)sigma[ c.getId() ] ), findMin[ c.getId() ], findMax[ c.getId() ], minIntensity, maxIntensity, limitDetections ); img.close(); correctForDownsampling( ips, correctCoordinates ); if ( limitDetections ) ips = limitList( maxDetections, maxDetectionsTypeIndex, ips ); interestPoints.put( vd, ips ); benchmark.computation += System.currentTimeMillis() - time2; } catch ( Exception e ) { IOFunctions.println( "An error occured (DOG): " + e ); IOFunctions.println( "Failed to segment angleId: " + vd.getViewSetup().getAngle().getId() + " channelId: " + vd.getViewSetup().getChannel().getId() + " illumId: " + vd.getViewSetup().getIllumination().getId() + ". Continuing with next one." ); e.printStackTrace(); } } return interestPoints; } @Override protected boolean setDefaultValues( final Channel channel, final int brightness ) { final int channelId = channel.getId(); this.sigma[ channelId ] = defaultS; this.findMin[ channelId ] = false; this.findMax[ channelId ] = true; if ( brightness == 0 ) this.threshold[ channelId ] = 0.001; else if ( brightness == 1 ) this.threshold[ channelId ] = 0.008; else if ( brightness == 2 ) this.threshold[ channelId ] = 0.03; else if ( brightness == 3 ) this.threshold[ channelId ] = 0.1; else return false; return true; } @Override protected boolean setAdvancedValues( final Channel channel ) { final int channelId = channel.getId(); final GenericDialog gd = new GenericDialog( "Advanced values for channel " + channel.getName() ); String ch; if ( this.channelsToProcess.size() > 1 ) ch = "_" + channel.getName().replace( ' ', '_' ); else ch = ""; gd.addMessage( "Advanced values for channel " + channel.getName() ); gd.addNumericField( "Sigma" + ch, defaultSigma[ channelId ], 5 ); gd.addNumericField( "Threshold" + ch, defaultThreshold[ channelId ], 4 ); gd.addCheckbox( "Find_minima" + ch, defaultFindMin[ channelId ] ); gd.addCheckbox( "Find_maxima" + ch, defaultFindMax[ channelId ] ); gd.showDialog(); if ( gd.wasCanceled() ) return false; this.sigma[ channelId ] = defaultSigma[ channelId ] = gd.getNextNumber(); this.threshold[ channelId ] = defaultThreshold[ channelId ] = gd.getNextNumber(); this.findMin[ channelId ] = defaultFindMin[ channelId ] = gd.getNextBoolean(); this.findMax[ channelId ] = defaultFindMax[ channelId ] = gd.getNextBoolean(); return true; } @Override protected boolean setInteractiveValues( final Channel channel ) { final ViewId view = getViewSelection( "Interactive Difference-of-Gaussian", "Please select view to use for channel " + channel.getName(), channel ); if ( view == null ) return false; final ViewDescription viewDescription = spimData.getSequenceDescription().getViewDescription( view.getTimePointId(), view.getViewSetupId() ); if ( !viewDescription.isPresent() ) { IOFunctions.println( "You defined the view you selected as not present at this timepoint." ); IOFunctions.println( "timepoint: " + viewDescription.getTimePoint().getName() + " angle: " + viewDescription.getViewSetup().getAngle().getName() + " channel: " + viewDescription.getViewSetup().getChannel().getName() + " illum: " + viewDescription.getViewSetup().getIllumination().getName() ); return false; } RandomAccessibleInterval< net.imglib2.type.numeric.real.FloatType > img = openAndDownsample( spimData, viewDescription, new AffineTransform3D() ); if ( img == null ) { IOFunctions.println( "View not found: " + viewDescription ); return false; } preSmooth( img ); final ImagePlus imp = ImageJFunctions.wrapFloat( img, "" ).duplicate(); img = null; imp.setDimensions( 1, imp.getStackSize(), 1 ); imp.setTitle( "tp: " + viewDescription.getTimePoint().getName() + " viewSetup: " + viewDescription.getViewSetupId() ); imp.show(); imp.setSlice( imp.getStackSize() / 2 ); imp.setRoi( 0, 0, imp.getWidth()/3, imp.getHeight()/3 ); final InteractiveDoG idog = new InteractiveDoG( imp ); final int channelId = channel.getId(); idog.setSigma2isAdjustable( false ); idog.setInitialSigma( (float)defaultSigma[ channelId ] ); idog.setThreshold( (float)defaultThreshold[ channelId ] ); idog.setLookForMinima( defaultFindMin[ channelId ] ); idog.setLookForMaxima( defaultFindMax[ channelId ] ); idog.setMinIntensityImage( minIntensity ); // if is Double.NaN will be ignored idog.setMaxIntensityImage( maxIntensity ); // if is Double.NaN will be ignored idog.run( null ); while ( !idog.isFinished() ) { try { Thread.sleep( 100 ); } catch (InterruptedException e) {} } imp.close(); if ( idog.wasCanceled() ) return false; this.sigma[ channelId ] = defaultSigma[ channelId ] = idog.getInitialSigma(); this.threshold[ channelId ] = defaultThreshold[ channelId ] = idog.getThreshold(); this.findMin[ channelId ] = defaultFindMin[ channelId ] = idog.getLookForMinima(); this.findMax[ channelId ] = defaultFindMax[ channelId ] = idog.getLookForMaxima(); return true; } /** * This is only necessary to make static objects so that the ImageJ dialog remembers choices * for the right channel * * @param numChannels - the TOTAL number of channels (not only the ones to process) */ @Override protected void init( final int numChannels ) { this.sigma = new double[ numChannels ]; this.threshold = new double[ numChannels ]; this.findMin = new boolean[ numChannels ]; this.findMax = new boolean[ numChannels ]; if ( defaultSigma == null || defaultSigma.length != numChannels ) { defaultSigma = new double[ numChannels ]; defaultThreshold = new double[ numChannels ]; defaultFindMin = new boolean[ numChannels ]; defaultFindMax = new boolean[ numChannels ]; for ( int c = 0; c < numChannels; ++c ) { defaultSigma[ c ] = defaultS; defaultThreshold[ c ] = defaultT; defaultFindMin[ c ] = false; defaultFindMax[ c ] = true; } } } @Override public String getParameters( final int channelId ) { return "DOG s=" + sigma[ channelId ] + " t=" + threshold[ channelId ] + " min=" + findMin[ channelId ] + " max=" + findMax[ channelId ] + " imageSigmaX=" + imageSigmaX + " imageSigmaY=" + imageSigmaY + " imageSigmaZ=" + imageSigmaZ + " downsampleXY=" + downsampleXY + " downsampleZ=" + downsampleZ + " additionalSigmaX=" + additionalSigmaX + " additionalSigmaY=" + additionalSigmaY + " additionalSigmaZ=" + additionalSigmaZ + " minIntensity=" + minIntensity + " maxIntensity=" + maxIntensity; } @Override protected void addAddtionalParameters( final GenericDialog gd ) { gd.addChoice( "Compute_on", computationOnChoice, computationOnChoice[ defaultComputationChoiceIndex ] ); } @Override protected boolean queryAdditionalParameters( final GenericDialog gd ) { final int computationTypeIndex = defaultComputationChoiceIndex = gd.getNextChoiceIndex(); if ( computationTypeIndex == 1 ) accurateCUDA = false; else accurateCUDA = true; if ( computationTypeIndex >= 1 ) { final ArrayList< String > potentialNames = new ArrayList< String >(); potentialNames.add( "separable" ); cuda = NativeLibraryTools.loadNativeLibrary( potentialNames, CUDASeparableConvolution.class ); if ( cuda == null ) { IOFunctions.println( "Cannot load CUDA JNA library." ); deviceList = null; return false; } else { deviceList = new ArrayList< CUDADevice >(); } // multiple CUDA devices sometimes crashes, no idea why yet ... final ArrayList< CUDADevice > selectedDevices = CUDATools.queryCUDADetails( cuda, false, this ); if ( selectedDevices == null || selectedDevices.size() == 0 ) return false; else deviceList.addAll( selectedDevices ); // TODO: remove this, only for debug on non-CUDA machines >>>> if ( deviceList.get( 0 ).getDeviceName().startsWith( "CPU emulation" ) ) { for ( int i = 0; i < deviceList.size(); ++i ) { deviceList.set( i, new CUDADevice( -1-i, deviceList.get( i ).getDeviceName(), deviceList.get( i ).getTotalDeviceMemory(), deviceList.get( i ).getFreeDeviceMemory(), deviceList.get( i ).getMajorComputeVersion(), deviceList.get( i ).getMinorComputeVersion() ) ); IOFunctions.println( "Running on cpu emulation, added " + ( -1-i ) + " as device" ); } } // TODO: <<<< remove this, only for debug on non-CUDA machines } else { deviceList = null; } return true; } @Override public void addQuery( final GenericDialog gd ) { gd.addMessage( "" ); gd.addSlider( "Percent_of_GPU_Memory_to_use", 1, 100, defaultUseGPUMem ); } @Override public boolean parseDialog( final GenericDialog gd ) { this.percentGPUMem = defaultUseGPUMem = gd.getNextNumber(); return true; } }