/*-
* #%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.segmentation;
import fiji.tool.SliceListener;
import fiji.tool.SliceObserver;
import ij.IJ;
import ij.ImageJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.WindowManager;
import ij.gui.OvalRoi;
import ij.gui.Overlay;
import ij.gui.Roi;
import ij.io.Opener;
import ij.plugin.PlugIn;
import ij.process.ByteProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import ij.process.ShortProcessor;
import java.awt.Button;
import java.awt.Checkbox;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Label;
import java.awt.Rectangle;
import java.awt.Scrollbar;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import mpicbg.imglib.algorithm.gauss.GaussianConvolutionReal;
import mpicbg.imglib.algorithm.math.LocalizablePoint;
import mpicbg.imglib.algorithm.scalespace.DifferenceOfGaussianPeak;
import mpicbg.imglib.algorithm.scalespace.DifferenceOfGaussianReal1;
import mpicbg.imglib.algorithm.scalespace.SubpixelLocalization;
import mpicbg.imglib.container.array.ArrayContainerFactory;
import mpicbg.imglib.cursor.LocalizableByDimCursor;
import mpicbg.imglib.cursor.LocalizableCursor;
import mpicbg.imglib.image.Image;
import mpicbg.imglib.image.ImageFactory;
import mpicbg.imglib.image.display.imagej.ImageJFunctions;
import mpicbg.imglib.multithreading.SimpleMultiThreading;
import mpicbg.imglib.outofbounds.OutOfBoundsStrategyMirrorFactory;
import mpicbg.imglib.outofbounds.OutOfBoundsStrategyValueFactory;
import mpicbg.imglib.type.numeric.real.FloatType;
import mpicbg.imglib.util.Util;
import mpicbg.spim.io.IOFunctions;
import mpicbg.spim.registration.ViewStructure;
import mpicbg.spim.registration.detection.DetectionSegmentation;
import net.imglib2.RandomAccess;
import net.imglib2.exception.ImgLibException;
import net.imglib2.img.ImagePlusAdapter;
import net.imglib2.img.imageplus.FloatImagePlus;
import net.imglib2.view.Views;
import spim.process.fusion.FusionHelper;
/**
* An interactive tool for determining the required sigma and peak threshold
*
* @author Stephan Preibisch
*/
public class InteractiveDoG implements PlugIn
{
final int extraSize = 40;
final int scrollbarSize = 1000;
float sigma = 0.5f;
float sigma2 = 0.5f;
float threshold = 0.0001f;
// steps per octave
public static int standardSensitivity = 4;
int sensitivity = standardSensitivity;
float imageSigma = 0.5f;
float sigmaMin = 0.5f;
float sigmaMax = 10f;
int sigmaInit = 300;
float thresholdMin = 0.0001f;
float thresholdMax = 1f;
int thresholdInit = 500;
double minIntensityImage = Double.NaN;
double maxIntensityImage = Double.NaN;
SliceObserver sliceObserver;
RoiListener roiListener;
ImagePlus imp;
int channel = 0;
Rectangle rectangle;
Image<FloatType> img;
FloatImagePlus< net.imglib2.type.numeric.real.FloatType > source;
ArrayList<DifferenceOfGaussianPeak<FloatType>> peaks;
Color originalColor = new Color( 0.8f, 0.8f, 0.8f );
Color inactiveColor = new Color( 0.95f, 0.95f, 0.95f );
public Rectangle standardRectangle;
boolean isComputing = false;
boolean isStarted = false;
boolean enableSigma2 = false;
boolean sigma2IsAdjustable = true;
boolean lookForMinima = false;
boolean lookForMaxima = true;
public static enum ValueChange { SIGMA, THRESHOLD, SLICE, ROI, MINMAX, ALL }
boolean isFinished = false;
boolean wasCanceled = false;
public boolean isFinished() { return isFinished; }
public boolean wasCanceled() { return wasCanceled; }
public double getInitialSigma() { return sigma; }
public void setInitialSigma( final float value )
{
sigma = value;
sigmaInit = computeScrollbarPositionFromValue( sigma, sigmaMin, sigmaMax, scrollbarSize );
}
public double getSigma2() { return sigma2; }
public double getThreshold() { return threshold; }
public void setThreshold( final float value )
{
threshold = value;
final double log1001 = Math.log10( scrollbarSize + 1);
thresholdInit = (int)Math.round( 1001-Math.pow(10, -(((threshold - thresholdMin)/(thresholdMax-thresholdMin))*log1001) + log1001 ) );
}
public boolean getSigma2WasAdjusted() { return enableSigma2; }
public boolean getLookForMaxima() { return lookForMaxima; }
public boolean getLookForMinima() { return lookForMinima; }
public void setLookForMaxima( final boolean lookForMaxima ) { this.lookForMaxima = lookForMaxima; }
public void setLookForMinima( final boolean lookForMinima ) { this.lookForMinima = lookForMinima; }
public void setSigmaMax( final float sigmaMax ) { this.sigmaMax = sigmaMax; }
public void setSigma2isAdjustable( final boolean state ) { sigma2IsAdjustable = state; }
// for the case that it is needed again, we can save one conversion
public FloatImagePlus< net.imglib2.type.numeric.real.FloatType > getConvertedImage() { return source; }
public InteractiveDoG( final ImagePlus imp, final int channel )
{
this.imp = imp;
this.channel = channel;
}
public InteractiveDoG( final ImagePlus imp ) { this.imp = imp; }
public InteractiveDoG() {}
public void setMinIntensityImage( final double min ) { this.minIntensityImage = min; }
public void setMaxIntensityImage( final double max ) { this.maxIntensityImage = max; }
@Override
public void run( String arg )
{
if ( imp == null )
imp = WindowManager.getCurrentImage();
standardRectangle = new Rectangle( imp.getWidth()/4, imp.getHeight()/4, imp.getWidth()/2, imp.getHeight()/2 );
if ( imp.getType() == ImagePlus.COLOR_RGB || imp.getType() == ImagePlus.COLOR_256 )
{
IJ.log( "Color images are not supported, please convert to 8, 16 or 32-bit grayscale" );
return;
}
Roi roi = imp.getRoi();
if ( roi == null )
{
//IJ.log( "A rectangular ROI is required to define the area..." );
imp.setRoi( standardRectangle );
roi = imp.getRoi();
}
if ( roi.getType() != Roi.RECTANGLE )
{
IJ.log( "Only rectangular rois are supported..." );
return;
}
// copy the ImagePlus into an ArrayImage<FloatType> for faster access
source = convertToFloat( imp, channel, 0, minIntensityImage, maxIntensityImage );
// show the interactive kit
displaySliders();
// add listener to the imageplus slice slider
sliceObserver = new SliceObserver( imp, new ImagePlusListener() );
// compute first version
updatePreview( ValueChange.ALL );
isStarted = true;
// check whenever roi is modified to update accordingly
roiListener = new RoiListener();
imp.getCanvas().addMouseListener( roiListener );
}
/**
* Updates the Preview with the current parameters (sigma, threshold, roi, slicenumber)
*
* @param change - what did change
*/
protected void updatePreview( final ValueChange change )
{
// check if Roi changed
boolean roiChanged = false;
Roi roi = imp.getRoi();
if ( roi == null || roi.getType() != Roi.RECTANGLE )
{
imp.setRoi( new Rectangle( standardRectangle ) );
roi = imp.getRoi();
roiChanged = true;
}
final Rectangle rect = roi.getBounds();
if ( roiChanged || img == null || change == ValueChange.SLICE ||
rect.getMinX() != rectangle.getMinX() || rect.getMaxX() != rectangle.getMaxX() ||
rect.getMinY() != rectangle.getMinY() || rect.getMaxY() != rectangle.getMaxY() )
{
rectangle = rect;
img = extractImage( source, rectangle, extraSize );
roiChanged = true;
}
// if we got some mouse click but the ROI did not change we can return
if ( !roiChanged && change == ValueChange.ROI )
{
isComputing = false;
return;
}
// compute the Difference Of Gaussian if necessary
if ( peaks == null || roiChanged || change == ValueChange.SIGMA || change == ValueChange.SLICE || change == ValueChange.ALL )
{
//
// Compute the Sigmas for the gaussian folding
//
final float k, K_MIN1_INV;
final float[] sigma, sigmaDiff;
if ( enableSigma2 )
{
sigma = new float[ 2 ];
sigma[ 0 ] = this.sigma;
sigma[ 1 ] = this.sigma2;
k = sigma[ 1 ] / sigma[ 0 ];
K_MIN1_INV = DetectionSegmentation.computeKWeight( k );
sigmaDiff = DetectionSegmentation.computeSigmaDiff( sigma, imageSigma );
}
else
{
k = (float)DetectionSegmentation.computeK( sensitivity );
K_MIN1_INV = DetectionSegmentation.computeKWeight( k );
sigma = DetectionSegmentation.computeSigma( k, this.sigma );
sigmaDiff = DetectionSegmentation.computeSigmaDiff( sigma, imageSigma );
}
// the upper boundary
this.sigma2 = sigma[ 1 ];
final DifferenceOfGaussianReal1<FloatType> dog = new DifferenceOfGaussianReal1<FloatType>( img, new OutOfBoundsStrategyValueFactory<FloatType>(), sigmaDiff[ 0 ], sigmaDiff[ 1 ], thresholdMin/4, K_MIN1_INV );
dog.setKeepDoGImage( true );
dog.process();
final SubpixelLocalization<FloatType> subpixel = new SubpixelLocalization<FloatType>( dog.getDoGImage(), dog.getPeaks() );
subpixel.process();
peaks = dog.getPeaks();
}
// extract peaks to show
Overlay o = imp.getOverlay();
if ( o == null )
{
o = new Overlay();
imp.setOverlay( o );
}
o.clear();
for ( final DifferenceOfGaussianPeak<FloatType> peak : peaks )
{
if ( ( peak.isMax() && lookForMaxima ) || ( peak.isMin() && lookForMinima ) )
{
final float x = peak.getPosition( 0 );
final float y = peak.getPosition( 1 );
if ( Math.abs( peak.getValue().get() ) > threshold &&
x >= extraSize/2 && y >= extraSize/2 &&
x < rect.width+extraSize/2 && y < rect.height+extraSize/2 )
{
final OvalRoi or = new OvalRoi( Util.round( x - sigma ) + rect.x - extraSize/2, Util.round( y - sigma ) + rect.y - extraSize/2, Util.round( sigma+sigma2 ), Util.round( sigma+sigma2 ) );
if ( peak.isMax() )
or.setStrokeColor( Color.green );
else if ( peak.isMin() )
or.setStrokeColor( Color.red );
o.add( or );
}
}
}
imp.updateAndDraw();
isComputing = false;
}
public static float computeSigma2( final float sigma1, final int sensitivity )
{
final float k = (float)DetectionSegmentation.computeK( sensitivity );
final float[] sigma = DetectionSegmentation.computeSigma( k, sigma1 );
return sigma[ 1 ];
}
/**
* Extract the current 2d region of interest from the souce image
*
* @param source - the source image, a {@link Image} which is a copy of the {@link ImagePlus}
* @param rectangle - the area of interest
* @param extraSize - the extra size around so that detections at the border of the roi are not messed up
* @return
*/
protected Image<FloatType> extractImage( final FloatImagePlus< net.imglib2.type.numeric.real.FloatType > source, final Rectangle rectangle, final int extraSize )
{
final Image<FloatType> img = new ImageFactory<FloatType>( new FloatType(), new ArrayContainerFactory() ).createImage( new int[]{ rectangle.width+extraSize, rectangle.height+extraSize } );
final int offsetX = rectangle.x - extraSize/2;
final int offsetY = rectangle.y - extraSize/2;
final int[] location = new int[ source.numDimensions() ];
if ( location.length > 2 )
location[ 2 ] = (imp.getCurrentSlice()-1)/imp.getNChannels();
final LocalizableCursor<FloatType> cursor = img.createLocalizableCursor();
final RandomAccess<net.imglib2.type.numeric.real.FloatType> positionable;
if ( offsetX >= 0 && offsetY >= 0 &&
offsetX + img.getDimension( 0 ) < source.dimension( 0 ) &&
offsetY + img.getDimension( 1 ) < source.dimension( 1 ) )
{
// it is completely inside so we need no outofbounds for copying
positionable = source.randomAccess();
}
else
{
positionable = Views.extendMirrorSingle( source ).randomAccess();
}
while ( cursor.hasNext() )
{
cursor.fwd();
cursor.getPosition( location );
location[ 0 ] += offsetX;
location[ 1 ] += offsetY;
positionable.setPosition( location );
cursor.getType().set( positionable.get().get() );
}
return img;
}
/**
* Normalize and make a copy of the {@link ImagePlus} into an {@link Image}>FloatType< for faster access when copying the slices
*
* @param imp - the {@link ImagePlus} input image
* @return - the normalized copy [0...1]
*/
public static FloatImagePlus< net.imglib2.type.numeric.real.FloatType > convertToFloat( final ImagePlus imp, int channel, int timepoint )
{
return convertToFloat( imp, channel, timepoint, Double.NaN, Double.NaN );
}
public static FloatImagePlus< net.imglib2.type.numeric.real.FloatType > convertToFloat( final ImagePlus imp, int channel, int timepoint, final double min, final double max )
{
// stupid 1-offset of imagej
channel++;
timepoint++;
final int h = imp.getHeight();
final int w = imp.getWidth();
final ArrayList< float[] > img = new ArrayList< float[] >();
if ( imp.getProcessor() instanceof FloatProcessor )
{
for ( int z = 0; z < imp.getNSlices(); ++z )
img.add( ( (float[])imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) ).getPixels() ).clone() );
}
else if ( imp.getProcessor() instanceof ByteProcessor )
{
for ( int z = 0; z < imp.getNSlices(); ++z )
{
final byte[] pixels = (byte[])imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) ).getPixels();
final float[] pixelsF = new float[ pixels.length ];
for ( int i = 0; i < pixels.length; ++i )
pixelsF[ i ] = pixels[ i ] & 0xff;
img.add( pixelsF );
}
}
else if ( imp.getProcessor() instanceof ShortProcessor )
{
for ( int z = 0; z < imp.getNSlices(); ++z )
{
final short[] pixels = (short[])imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) ).getPixels();
final float[] pixelsF = new float[ pixels.length ];
for ( int i = 0; i < pixels.length; ++i )
pixelsF[ i ] = pixels[ i ] & 0xffff;
img.add( pixelsF );
}
}
else // some color stuff or so
{
for ( int z = 0; z < imp.getNSlices(); ++z )
{
final ImageProcessor ip = imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) );
final float[] pixelsF = new float[ w * h ];
int i = 0;
for ( int y = 0; y < h; ++y )
for ( int x = 0; x < w; ++x )
pixelsF[ i++ ] = ip.getPixelValue( x, y );
img.add( pixelsF );
}
}
final FloatImagePlus< net.imglib2.type.numeric.real.FloatType > i = createImgLib2( img, w, h );
if ( Double.isNaN( min ) || Double.isNaN( max ) || Double.isInfinite( min ) || Double.isInfinite( max ) || min == max )
FusionHelper.normalizeImage( i );
else
FusionHelper.normalizeImage( i, (float)min, (float)max );
return i;
}
public static FloatImagePlus< net.imglib2.type.numeric.real.FloatType > createImgLib2( final List< float[] > img, final int w, final int h )
{
final ImagePlus imp;
if ( img.size() > 1 )
{
final ImageStack stack = new ImageStack( w, h );
for ( int z = 0; z < img.size(); ++z )
stack.addSlice( new FloatProcessor( w, h, img.get( z ) ) );
imp = new ImagePlus( "ImgLib2 FloatImagePlus (3d)", stack );
}
else
{
imp = new ImagePlus( "ImgLib2 FloatImagePlus (2d)", new FloatProcessor( w, h, img.get( 0 ) ) );
}
return ImagePlusAdapter.wrapFloat( imp );
}
/**
* Instantiates the panel for adjusting the paramters
*/
protected void displaySliders()
{
final Frame frame = new Frame("Adjust Difference-of-Gaussian Values");
frame.setSize( 400, 330 );
/* Instantiation */
final GridBagLayout layout = new GridBagLayout();
final GridBagConstraints c = new GridBagConstraints();
final Scrollbar sigma1 = new Scrollbar ( Scrollbar.HORIZONTAL, sigmaInit, 10, 0, 10 + scrollbarSize );
this.sigma = computeValueFromScrollbarPosition( sigmaInit, sigmaMin, sigmaMax, scrollbarSize);
final Scrollbar threshold = new Scrollbar ( Scrollbar.HORIZONTAL, thresholdInit, 10, 0, 10 + scrollbarSize );
final float log1001 = (float)Math.log10( scrollbarSize + 1);
this.threshold = thresholdMin + ( (log1001 - (float)Math.log10(1001-thresholdInit))/log1001 ) * (thresholdMax-thresholdMin);
this.sigma2 = computeSigma2( this.sigma, this.sensitivity );
final int sigma2init = computeScrollbarPositionFromValue( this.sigma2, sigmaMin, sigmaMax, scrollbarSize );
final Scrollbar sigma2 = new Scrollbar ( Scrollbar.HORIZONTAL, sigma2init, 10, 0, 10 + scrollbarSize );
final Label sigmaText1 = new Label( "Sigma 1 = " + this.sigma, Label.CENTER );
final Label sigmaText2 = new Label( "Sigma 2 = " + this.sigma2, Label.CENTER );
final Label thresholdText = new Label( "Threshold = " + this.threshold, Label.CENTER );
final Button apply = new Button( "Apply to Stack (will take some time)" );
final Button button = new Button( "Done" );
final Button cancel = new Button( "Cancel" );
final Checkbox sigma2Enable = new Checkbox( "Enable Manual Adjustment of Sigma 2 ", enableSigma2 );
final Checkbox min = new Checkbox( "Look for Minima (red)", lookForMinima );
final Checkbox max = new Checkbox( "Look for Maxima (green)", lookForMaxima );
/* Location */
frame.setLayout( layout );
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
c.weightx = 1;
frame.add ( sigma1, c );
++c.gridy;
frame.add( sigmaText1, c );
++c.gridy;
frame.add ( sigma2, c );
++c.gridy;
frame.add( sigmaText2, c );
++c.gridy;
c.insets = new Insets(0,65,0,65);
frame.add( sigma2Enable, c );
++c.gridy;
c.insets = new Insets(10,0,0,0);
frame.add ( threshold, c );
c.insets = new Insets(0,0,0,0);
++c.gridy;
frame.add( thresholdText, c );
++c.gridy;
c.insets = new Insets(0,130,0,75);
frame.add( min, c );
++c.gridy;
c.insets = new Insets(0,125,0,75);
frame.add( max, c );
++c.gridy;
c.insets = new Insets(0,75,0,75);
frame.add( apply, c );
++c.gridy;
c.insets = new Insets(10,150,0,150);
frame.add( button, c );
++c.gridy;
c.insets = new Insets(10,150,0,150);
frame.add( cancel, c );
/* Configuration */
sigma1.addAdjustmentListener( new SigmaListener( sigmaText1, sigmaMin, sigmaMax, scrollbarSize, sigma1, sigma2, sigmaText2 ) );
sigma2.addAdjustmentListener( new Sigma2Listener( sigmaMin, sigmaMax, scrollbarSize, sigma2, sigmaText2 ) );
threshold.addAdjustmentListener( new ThresholdListener( thresholdText, thresholdMin, thresholdMax ) );
button.addActionListener( new FinishedButtonListener( frame, false ) );
cancel.addActionListener( new FinishedButtonListener( frame, true ) );
apply.addActionListener( new ApplyButtonListener() );
min.addItemListener( new MinListener() );
max.addItemListener( new MaxListener() );
sigma2Enable.addItemListener( new EnableListener( sigma2, sigmaText2 ) );
if ( !sigma2IsAdjustable )
sigma2Enable.setEnabled( false );
frame.addWindowListener( new FrameListener( frame ) );
frame.setVisible( true );
originalColor = sigma2.getBackground();
sigma2.setBackground( inactiveColor );
sigmaText1.setFont( sigmaText1.getFont().deriveFont( Font.BOLD ) );
thresholdText.setFont( thresholdText.getFont().deriveFont( Font.BOLD ) );
}
protected class EnableListener implements ItemListener
{
final Scrollbar sigma2;
final Label sigmaText2;
public EnableListener( final Scrollbar sigma2, final Label sigmaText2 )
{
this.sigmaText2 = sigmaText2;
this.sigma2 = sigma2;
}
@Override
public void itemStateChanged( final ItemEvent arg0 )
{
if ( arg0.getStateChange() == ItemEvent.DESELECTED )
{
sigmaText2.setFont( sigmaText2.getFont().deriveFont( Font.PLAIN ) );
sigma2.setBackground( inactiveColor );
enableSigma2 = false;
}
else if ( arg0.getStateChange() == ItemEvent.SELECTED )
{
sigmaText2.setFont( sigmaText2.getFont().deriveFont( Font.BOLD ) );
sigma2.setBackground( originalColor );
enableSigma2 = true;
}
}
}
protected class MinListener implements ItemListener
{
@Override
public void itemStateChanged( final ItemEvent arg0 )
{
boolean oldState = lookForMinima;
if ( arg0.getStateChange() == ItemEvent.DESELECTED )
lookForMinima = false;
else if ( arg0.getStateChange() == ItemEvent.SELECTED )
lookForMinima = true;
if ( lookForMinima != oldState )
{
while ( isComputing )
SimpleMultiThreading.threadWait( 10 );
updatePreview( ValueChange.MINMAX );
}
}
}
protected class MaxListener implements ItemListener
{
@Override
public void itemStateChanged( final ItemEvent arg0 )
{
boolean oldState = lookForMaxima;
if ( arg0.getStateChange() == ItemEvent.DESELECTED )
lookForMaxima = false;
else if ( arg0.getStateChange() == ItemEvent.SELECTED )
lookForMaxima = true;
if ( lookForMaxima != oldState )
{
while ( isComputing )
SimpleMultiThreading.threadWait( 10 );
updatePreview( ValueChange.MINMAX );
}
}
}
/**
* Tests whether the ROI was changed and will recompute the preview
*
* @author Stephan Preibisch
*/
protected class RoiListener implements MouseListener
{
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased( final MouseEvent e )
{
// here the ROI might have been modified, let's test for that
final Roi roi = imp.getRoi();
if ( roi == null || roi.getType() != Roi.RECTANGLE )
return;
while ( isComputing )
SimpleMultiThreading.threadWait( 10 );
updatePreview( ValueChange.ROI );
}
}
protected class ApplyButtonListener implements ActionListener
{
@Override
public void actionPerformed( final ActionEvent arg0 )
{
ImagePlus imp;
try
{
imp = source.getImagePlus();
}
catch (ImgLibException e)
{
imp = null;
e.printStackTrace();
}
// convert ImgLib2 image to ImgLib1 image via the imageplus
final Image< FloatType > source = ImageJFunctions.wrapFloat( imp );
IOFunctions.println( "Computing DoG ... " );
// test the parameters on the complete stack
final ArrayList<DifferenceOfGaussianPeak<FloatType>> peaks =
DetectionSegmentation.extractBeadsLaPlaceImgLib(
source,
new OutOfBoundsStrategyMirrorFactory<FloatType>(),
imageSigma,
sigma,
sigma2,
threshold,
threshold/4,
lookForMaxima,
lookForMinima,
ViewStructure.DEBUG_MAIN );
IOFunctions.println( "Drawing DoG result ... " );
// display as extra image
Image<FloatType> detections = source.createNewImage();
final LocalizableByDimCursor<FloatType> c = detections.createLocalizableByDimCursor();
for ( final DifferenceOfGaussianPeak<FloatType> peak : peaks )
{
final LocalizablePoint p = new LocalizablePoint( new float[]{ peak.getSubPixelPosition( 0 ), peak.getSubPixelPosition( 1 ), peak.getSubPixelPosition( 2 ) } );
c.setPosition( p );
c.getType().set( 1 );
}
IOFunctions.println( "Convolving DoG result ... " );
final GaussianConvolutionReal<FloatType> gauss = new GaussianConvolutionReal<FloatType>( detections, new OutOfBoundsStrategyValueFactory<FloatType>(), 2 );
gauss.process();
detections = gauss.getResult();
IOFunctions.println( "Showing DoG result ... " );
ImageJFunctions.show( detections );
}
}
protected class FinishedButtonListener implements ActionListener
{
final Frame parent;
final boolean cancel;
public FinishedButtonListener( Frame parent, final boolean cancel )
{
this.parent = parent;
this.cancel = cancel;
}
@Override
public void actionPerformed( final ActionEvent arg0 )
{
wasCanceled = cancel;
close( parent, sliceObserver, imp, roiListener );
}
}
protected class FrameListener extends WindowAdapter
{
final Frame parent;
public FrameListener( Frame parent )
{
super();
this.parent = parent;
}
@Override
public void windowClosing (WindowEvent e)
{
close( parent, sliceObserver, imp, roiListener );
}
}
protected final void close( final Frame parent, final SliceObserver sliceObserver, final ImagePlus imp, final RoiListener roiListener )
{
if ( parent != null )
parent.dispose();
if ( sliceObserver != null )
sliceObserver.unregister();
if ( imp != null )
{
if ( roiListener != null )
imp.getCanvas().removeMouseListener( roiListener );
imp.getOverlay().clear();
imp.updateAndDraw();
}
isFinished = true;
}
protected class Sigma2Listener implements AdjustmentListener
{
final float min, max;
final int scrollbarSize;
final Scrollbar sigmaScrollbar2;
final Label sigma2Label;
public Sigma2Listener( final float min, final float max, final int scrollbarSize, final Scrollbar sigmaScrollbar2, final Label sigma2Label )
{
this.min = min;
this.max = max;
this.scrollbarSize = scrollbarSize;
this.sigmaScrollbar2 = sigmaScrollbar2;
this.sigma2Label = sigma2Label;
}
@Override
public void adjustmentValueChanged( final AdjustmentEvent event )
{
if ( enableSigma2 )
{
sigma2 = computeValueFromScrollbarPosition( event.getValue(), min, max, scrollbarSize );
if ( sigma2 < sigma )
{
sigma2 = sigma + 0.001f;
sigmaScrollbar2.setValue( computeScrollbarPositionFromValue( sigma2, min, max, scrollbarSize ) );
}
sigma2Label.setText( "Sigma 2 = " + sigma2 );
if ( !event.getValueIsAdjusting() )
{
while ( isComputing )
{
SimpleMultiThreading.threadWait( 10 );
}
updatePreview( ValueChange.SIGMA );
}
}
else
{
// if no manual adjustment simply reset it
sigmaScrollbar2.setValue( computeScrollbarPositionFromValue( sigma2, min, max, scrollbarSize ) );
}
}
}
protected class SigmaListener implements AdjustmentListener
{
final Label label;
final float min, max;
final int scrollbarSize;
final Scrollbar sigmaScrollbar1;
final Scrollbar sigmaScrollbar2;
final Label sigmaText2;
public SigmaListener( final Label label, final float min, final float max, final int scrollbarSize, final Scrollbar sigmaScrollbar1, final Scrollbar sigmaScrollbar2, final Label sigmaText2 )
{
this.label = label;
this.min = min;
this.max = max;
this.scrollbarSize = scrollbarSize;
this.sigmaScrollbar1 = sigmaScrollbar1;
this.sigmaScrollbar2 = sigmaScrollbar2;
this.sigmaText2 = sigmaText2;
}
@Override
public void adjustmentValueChanged( final AdjustmentEvent event )
{
sigma = computeValueFromScrollbarPosition( event.getValue(), min, max, scrollbarSize );
if ( !enableSigma2 )
{
sigma2 = computeSigma2( sigma, sensitivity );
sigmaText2.setText( "Sigma 2 = " + sigma2 );
sigmaScrollbar2.setValue( computeScrollbarPositionFromValue( sigma2, min, max, scrollbarSize ) );
}
else if ( sigma > sigma2 )
{
sigma = sigma2 - 0.001f;
sigmaScrollbar1.setValue( computeScrollbarPositionFromValue( sigma, min, max, scrollbarSize ) );
}
label.setText( "Sigma 1 = " + sigma );
//if ( !event.getValueIsAdjusting() )
{
while ( isComputing )
{
SimpleMultiThreading.threadWait( 10 );
}
updatePreview( ValueChange.SIGMA );
}
}
}
protected static float computeValueFromScrollbarPosition( final int scrollbarPosition, final float min, final float max, final int scrollbarSize )
{
return min + (scrollbarPosition/(float)scrollbarSize) * (max-min);
}
protected static int computeScrollbarPositionFromValue( final float sigma, final float min, final float max, final int scrollbarSize )
{
return Util.round( ((sigma - min)/(max-min)) * scrollbarSize );
}
protected class ThresholdListener implements AdjustmentListener
{
final Label label;
final float min, max;
final float log1001 = (float)Math.log10(1001);
public ThresholdListener( final Label label, final float min, final float max )
{
this.label = label;
this.min = min;
this.max = max;
}
@Override
public void adjustmentValueChanged( final AdjustmentEvent event )
{
threshold = min + ( (log1001 - (float)Math.log10(1001-event.getValue()))/log1001 ) * (max-min);
label.setText( "Threshold = " + threshold );
if ( !isComputing )
{
updatePreview( ValueChange.THRESHOLD );
}
else if ( !event.getValueIsAdjusting() )
{
while ( isComputing )
{
SimpleMultiThreading.threadWait( 10 );
}
updatePreview( ValueChange.THRESHOLD );
}
}
}
protected class ImagePlusListener implements SliceListener
{
@Override
public void sliceChanged(ImagePlus arg0)
{
if ( isStarted )
{
while ( isComputing )
{
SimpleMultiThreading.threadWait( 10 );
}
updatePreview( ValueChange.SLICE );
}
}
}
public static void main( String[] args )
{
new ImageJ();
ImagePlus imp = new Opener().openImage( "/Users/spreibi/Documents/Microscopy/SPIM/HisYFP-SPIM/spim_TL18_Angle0.tif" );
//ImagePlus imp = new Opener().openImage( "D:/Documents and Settings/Stephan/My Documents/Downloads/1-315--0.08-isotropic-subvolume/1-315--0.08-isotropic-subvolume.tif" );
imp.show();
imp.setSlice( 27 );
imp.setRoi( imp.getWidth()/4, imp.getHeight()/4, imp.getWidth()/2, imp.getHeight()/2 );
new InteractiveDoG().run( null );
}
}