/*-
* #%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.WindowManager;
import ij.gui.OvalRoi;
import ij.gui.Overlay;
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.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import spim.Threads;
import mpicbg.imglib.algorithm.integral.IntegralImageLong;
import mpicbg.imglib.algorithm.scalespace.DifferenceOfGaussian.SpecialPoint;
import mpicbg.imglib.container.array.ArrayContainerFactory;
import mpicbg.imglib.cursor.LocalizableByDimCursor;
import mpicbg.imglib.cursor.LocalizableCursor;
import mpicbg.imglib.cursor.array.ArrayCursor;
import mpicbg.imglib.cursor.special.LocalNeighborhoodCursor;
import mpicbg.imglib.cursor.special.LocalNeighborhoodCursorFactory;
import mpicbg.imglib.function.Converter;
import mpicbg.imglib.image.Image;
import mpicbg.imglib.image.ImageFactory;
import mpicbg.imglib.multithreading.Chunk;
import mpicbg.imglib.multithreading.SimpleMultiThreading;
import mpicbg.imglib.type.numeric.integer.LongType;
import mpicbg.imglib.type.numeric.real.FloatType;
import mpicbg.imglib.util.Util;
import mpicbg.spim.registration.detection.DetectionSegmentation;
/**
* An interactive tool for determining the required radius and peak threshold
*
* @author Stephan Preibisch
*/
public class InteractiveIntegral implements PlugIn
{
final int scrollbarSize = 1000;
int radius1 = 1;
int radius2 = 3;
float threshold = 0.0001f;
double min, max;
int radiusMin = 1;
int radiusMax = 29;
int radiusInit1 = 300;
int radiusInit2 = 400;
float thresholdMin = 0.0001f;
float thresholdMax = 1f;
int thresholdInit = 500;
SliceObserver sliceObserver;
ImagePlus imp;
int channel = 0;
Rectangle rectangle;
Image<FloatType> source, sliceImage;
Image<LongType> integralImage;
ArrayList<SimplePeak> peaks;
Color originalColor = new Color( 0.8f, 0.8f, 0.8f );
Color inactiveColor = new Color( 0.95f, 0.95f, 0.95f );
boolean isComputing = false;
boolean isStarted = false;
boolean enableRadius2 = false;
boolean lookForMinima = false;
boolean lookForMaxima = true;
public static enum ValueChange { RADIUS, THRESHOLD, SLICE, MINMAX, ALL }
boolean isFinished = false;
boolean wasCanceled = false;
public boolean isFinished() { return isFinished; }
public boolean wasCanceld() { return wasCanceled; }
public void setInitialRadii( int r1, int r2 )
{
if ( r2 <= r1 )
r2 = r1 + 2;
radius1 = r1;
radiusInit1 = computeScrollbarPositionFromValue( radius1, radiusMin, radiusMax, scrollbarSize );
radius2 = r2;
radiusInit2 = computeScrollbarPositionFromValue( radius2, radiusMin, radiusMax, scrollbarSize );
}
public void setInitialRadius( final int r1 )
{
setInitialRadii( r1, computeRadius2( r1 ) );
}
public int getRadius1() { return radius1; }
public int getRadius2() { return radius2; }
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 getRadius2WasAdjusted() { return enableRadius2; }
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 setRadiusMax( final int radiusMax ) { this.radiusMax = radiusMax; }
// for the case that it is needed again, we can save one conversion
public Image<FloatType> getConvertedImage() { return source; }
public InteractiveIntegral( final ImagePlus imp, final int channel )
{
this.imp = imp;
this.channel = channel;
}
public InteractiveIntegral( final ImagePlus imp ) { this.imp = imp; }
public InteractiveIntegral() {}
public void setMinIntensityImage( final double min ) { this.min = min; }
public void setMaxIntensityImage( final double max ) { this.max = max; }
@Override
public void run( String arg )
{
if ( imp == null )
imp = WindowManager.getCurrentImage();
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;
}
// copy the ImagePlus into an ArrayImage<FloatType> for faster access
source = convertToFloat( imp, channel, 0 );
sliceImage = source.getImageFactory().createImage( new int[]{ source.getDimension( 0 ), source.getDimension( 1 ) } );
// compute min/max
if ( Double.isNaN( min ) || Double.isNaN( max ) || Double.isInfinite( min ) || Double.isInfinite( max ) || min == max )
{
FloatType min = new FloatType();
FloatType max = new FloatType();
DOM.computeMinMax( source, min, max );
this.min = min.get();
this.max = max.get();
}
// compute the integral image
integralImage = computeIntegralImage( source );
// 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;
}
public Image<LongType> computeIntegralImage( final Image<FloatType> img )
{
IntegralImageLong< FloatType > intImg = new IntegralImageLong<FloatType>( img, new Converter< FloatType, LongType >()
{
@Override
public void convert( final FloatType input, final LongType output ) { output.set( Util.round( input.get() ) ); }
} );
intImg.process();
final Image< LongType > integralImg = intImg.getResult();
return integralImg;
}
final public static void computeDifferencOfMeanSlice( final Image< LongType> integralImg, final Image< FloatType > sliceImg, final int z, final int sx1, final int sy1, final int sz1, final int sx2, final int sy2, final int sz2, final float min, final float max )
{
final float sumPixels1 = sx1 * sy1 * sz1;
final float sumPixels2 = sx2 * sy2 * sz2;
final int sx1Half = sx1 / 2;
final int sy1Half = sy1 / 2;
final int sz1Half = sz1 / 2;
final int sx2Half = sx2 / 2;
final int sy2Half = sy2 / 2;
final int sz2Half = sz2 / 2;
final int sxHalfMax = Math.max( sx1Half, sx2Half );
final int syHalfMax = Math.max( sy1Half, sy2Half );
final int szHalfMax = Math.max( sz1Half, sz2Half );
final int w = sliceImg.getDimension( 0 ) - ( Math.max( sx1, sx2 ) / 2 ) * 2;
final int h = sliceImg.getDimension( 1 ) - ( Math.max( sy1, sy2 ) / 2 ) * 2;
final int d = (integralImg.getDimension( 2 ) - 1) - ( Math.max( sz1, sz2 ) / 2 ) * 2;
final long imageSize = w * h;
final AtomicInteger ai = new AtomicInteger(0);
final Thread[] threads = SimpleMultiThreading.newThreads();
final Vector<Chunk> threadChunks = SimpleMultiThreading.divideIntoChunks( imageSize, threads.length );
for (int ithread = 0; ithread < threads.length; ++ithread)
threads[ithread] = new Thread(new Runnable()
{
public void run()
{
// Thread ID
final int myNumber = ai.getAndIncrement();
// get chunk of pixels to process
final Chunk myChunk = threadChunks.get( myNumber );
final long loopSize = myChunk.getLoopSize();
final LocalizableCursor< FloatType > cursor = sliceImg.createLocalizableCursor();
final LocalizableByDimCursor< LongType > randomAccess = integralImg.createLocalizableByDimCursor();
cursor.fwd( myChunk.getStartPosition() );
// do as many pixels as wanted by this thread
for ( long j = 0; j < loopSize; ++j )
{
final FloatType result = cursor.next();
final int x = cursor.getPosition( 0 );
final int y = cursor.getPosition( 1 );
//final int z = cursor.getPosition( 2 );
final int xt = x - sxHalfMax;
final int yt = y - syHalfMax;
final int zt = z - szHalfMax;
if ( xt >= 0 && yt >= 0 && zt >= 0 && xt < w && yt < h && zt < d )
{
final float s1 = DOM.computeSum( x - sx1Half, y - sy1Half, z - sz1Half, sx1, sy1, sz1, randomAccess ) / sumPixels1;
final float s2 = DOM.computeSum( x - sx2Half, y - sy2Half, z - sz2Half, sx2, sy2, sz2, randomAccess ) / sumPixels2;
result.set( ( s2 - s1 ) / ( max - min ) );
}
}
}
});
SimpleMultiThreading.startAndJoin( threads );
}
/**
* Updates the Preview with the current parameters (radius, threshold, roi, slicenumber)
*
* @param change - what did change
*/
protected void updatePreview( final ValueChange change )
{
// compute the Difference Of Mean if necessary
if ( peaks == null || change == ValueChange.RADIUS || change == ValueChange.SLICE || change == ValueChange.ALL )
{
int slice = (imp.getCurrentSlice()-1)/imp.getNChannels();
final int s1 = radius1*2 + 1;
final int s2 = radius2*2 + 1;
computeDifferencOfMeanSlice( integralImage, sliceImage, slice, s1, s1, s1, s2, s2, s2, (float)min, (float)max );
//ImageJFunctions.show( sliceImage );
peaks = findPeaks( sliceImage, thresholdMin );
//System.out.println("t=" + threshold + " -> " + peaks.size() );
}
// extract peaks to show
Overlay o = imp.getOverlay();
if ( o == null )
{
o = new Overlay();
imp.setOverlay( o );
}
o.clear();
if ( peaks != null )
{
int avgSize = (radius1 + radius2 + 1)/2;
for ( final SimplePeak peak : peaks )
{
if ( ( peak.isMax && lookForMaxima ) || ( peak.isMin && lookForMinima ) )
{
final float x = peak.location[ 0 ];
final float y = peak.location[ 1 ];
if ( Math.abs( peak.intensity ) > threshold )
{
final OvalRoi or = new OvalRoi( Util.round( x - avgSize ), Util.round( y - avgSize ), radius1+radius2+1, radius1+radius2+1 );
if ( peak.isMax )
or.setStrokeColor( Color.green );
else if ( peak.isMin )
or.setStrokeColor( Color.red );
o.add( or );
}
}
}
}
imp.updateAndDraw();
isComputing = false;
}
public static int computeRadius2( final int radius1 )
{
final float sensitivity = 1.25f;
final float k = (float)DetectionSegmentation.computeK( sensitivity );
final float[] radius = DetectionSegmentation.computeSigma( k, radius1 );
int radius2 = Math.round( radius[ 1 ] );
if ( radius2 <= radius1 + 1 )
radius2 = radius1 + 1;
return radius2;
}
public static ArrayList<SimplePeak> findPeaks( final Image<FloatType> laPlace, final float minValue )
{
final AtomicInteger ai = new AtomicInteger( 0 );
final Thread[] threads = SimpleMultiThreading.newThreads( Threads.numThreads() );
final int nThreads = threads.length;
final int numDimensions = laPlace.getNumDimensions();
final Vector< ArrayList<SimplePeak> > threadPeaksList = new Vector< ArrayList<SimplePeak> >();
for ( int i = 0; i < nThreads; ++i )
threadPeaksList.add( new ArrayList<SimplePeak>() );
for (int ithread = 0; ithread < threads.length; ++ithread)
threads[ithread] = new Thread(new Runnable()
{
public void run()
{
final int myNumber = ai.getAndIncrement();
final ArrayList<SimplePeak> myPeaks = threadPeaksList.get( myNumber );
final LocalizableByDimCursor<FloatType> cursor = laPlace.createLocalizableByDimCursor();
final LocalNeighborhoodCursor<FloatType> neighborhoodCursor = LocalNeighborhoodCursorFactory.createLocalNeighborhoodCursor( cursor );
final int[] position = new int[ numDimensions ];
final int[] dimensionsMinus2 = laPlace.getDimensions();
for ( int d = 0; d < numDimensions; ++d )
dimensionsMinus2[ d ] -= 2;
MainLoop: while ( cursor.hasNext() )
{
cursor.fwd();
cursor.getPosition( position );
if ( position[ 0 ] % nThreads == myNumber )
{
for ( int d = 0; d < numDimensions; ++d )
{
final int pos = position[ d ];
if ( pos < 1 || pos > dimensionsMinus2[ d ] )
continue MainLoop;
}
// if we do not clone it here, it might be moved along with the cursor
// depending on the container type used
final float currentValue = cursor.getType().get();
// it can never be a desired peak as it is too low
if ( Math.abs( currentValue ) < minValue )
continue;
// update to the current position
neighborhoodCursor.update();
// we have to compare for example 26 neighbors in the 3d case (3^3 - 1) relative to the current position
final SpecialPoint specialPoint = isSpecialPoint( neighborhoodCursor, currentValue );
if ( specialPoint == SpecialPoint.MIN )
myPeaks.add( new SimplePeak( position, Math.abs( currentValue ), true, false ) ); //( position, currentValue, specialPoint ) );
else if ( specialPoint == SpecialPoint.MAX )
myPeaks.add( new SimplePeak( position, Math.abs( currentValue ), false, true ) ); //( position, currentValue, specialPoint ) );
// reset the position of the parent cursor
neighborhoodCursor.reset();
}
}
cursor.close();
}
});
SimpleMultiThreading.startAndJoin( threads );
// put together the list from the various threads
final ArrayList<SimplePeak> dogPeaks = new ArrayList<SimplePeak>();
for ( final ArrayList<SimplePeak> peakList : threadPeaksList )
dogPeaks.addAll( peakList );
return dogPeaks;
}
final protected static SpecialPoint isSpecialPoint( final LocalNeighborhoodCursor<FloatType> neighborhoodCursor, final float centerValue )
{
boolean isMin = true;
boolean isMax = true;
while ( (isMax || isMin) && neighborhoodCursor.hasNext() )
{
neighborhoodCursor.fwd();
final double value = neighborhoodCursor.getType().getRealDouble();
// it can still be a minima if the current value is bigger/equal to the center value
isMin &= (value >= centerValue);
// it can still be a maxima if the current value is smaller/equal to the center value
isMax &= (value <= centerValue);
}
// this mixup is intended, a minimum in the 2nd derivation is a maxima in image space and vice versa
if ( isMin )
return SpecialPoint.MAX;
else if ( isMax )
return SpecialPoint.MIN;
else
return SpecialPoint.INVALID;
}
/**
* 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 Image<FloatType> convertToFloat( final ImagePlus imp, int channel, int timepoint )
{
// stupid 1-offset of imagej
channel++;
timepoint++;
final Image<FloatType> img;
if ( imp.getNSlices() > 1 )
img = new ImageFactory<FloatType>( new FloatType(), new ArrayContainerFactory() ).createImage( new int[]{ imp.getWidth(), imp.getHeight(), imp.getNSlices() } );
else
img = new ImageFactory<FloatType>( new FloatType(), new ArrayContainerFactory() ).createImage( new int[]{ imp.getWidth(), imp.getHeight() } );
final int sliceSize = imp.getWidth() * imp.getHeight();
int z = 0;
ImageProcessor ip = imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) );
if ( ip instanceof FloatProcessor )
{
final ArrayCursor<FloatType> cursor = (ArrayCursor<FloatType>)img.createCursor();
float[] pixels = (float[])ip.getPixels();
int i = 0;
while ( cursor.hasNext() )
{
// only get new imageprocessor if necessary
if ( i == sliceSize )
{
++z;
pixels = (float[])imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) ).getPixels();
i = 0;
}
cursor.next().set( pixels[ i++ ] );
}
}
else if ( ip instanceof ByteProcessor )
{
final ArrayCursor<FloatType> cursor = (ArrayCursor<FloatType>)img.createCursor();
byte[] pixels = (byte[])ip.getPixels();
int i = 0;
while ( cursor.hasNext() )
{
// only get new imageprocessor if necessary
if ( i == sliceSize )
{
++z;
pixels = (byte[])imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) ).getPixels();
i = 0;
}
cursor.next().set( pixels[ i++ ] & 0xff );
}
}
else if ( ip instanceof ShortProcessor )
{
final ArrayCursor<FloatType> cursor = (ArrayCursor<FloatType>)img.createCursor();
short[] pixels = (short[])ip.getPixels();
int i = 0;
while ( cursor.hasNext() )
{
// only get new imageprocessor if necessary
if ( i == sliceSize )
{
++z;
pixels = (short[])imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) ).getPixels();
i = 0;
}
cursor.next().set( pixels[ i++ ] & 0xffff );
}
}
else // some color stuff or so
{
final LocalizableCursor<FloatType> cursor = img.createLocalizableCursor();
final int[] location = new int[ img.getNumDimensions() ];
while ( cursor.hasNext() )
{
cursor.fwd();
cursor.getPosition( location );
// only get new imageprocessor if necessary
if ( location[ 2 ] != z )
{
z = location[ 2 ];
ip = imp.getStack().getProcessor( imp.getStackIndex( channel, z + 1, timepoint ) );
}
cursor.getType().set( ip.getPixelValue( location[ 0 ], location[ 1 ] ) );
}
}
// we do not want to normalize here ... otherwise the integral image will not work
//ViewDataBeads.normalizeImage( img );
return img;
}
/**
* Instantiates the panel for adjusting the paramters
*/
protected void displaySliders()
{
final Frame frame = new Frame( "Adjust Difference-of-Mean Values" );
frame.setSize( 400, 330 );
/* Instantiation */
final GridBagLayout layout = new GridBagLayout();
final GridBagConstraints c = new GridBagConstraints();
final Scrollbar radius1 = new Scrollbar ( Scrollbar.HORIZONTAL, radiusInit1, 10, 0, 10 + scrollbarSize );
this.radius1 = Math.round( computeValueFromScrollbarPosition( radiusInit1, radiusMin, radiusMax, 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.radius2 = computeRadius2( this.radius1 );
final int radius2init = computeScrollbarPositionFromValue( this.radius2, radiusMin, radiusMax, scrollbarSize );
final Scrollbar radius2 = new Scrollbar ( Scrollbar.HORIZONTAL, radius2init, 10, 0, 10 + scrollbarSize );
final Label radiusText1 = new Label( "Radius 1 = " + this.radius1, Label.CENTER );
final Label radiusText2 = new Label( "Radius 2 = " + this.radius2, Label.CENTER );
final Label thresholdText = new Label( "Threshold = " + this.threshold, Label.CENTER );
final Button button = new Button( "Done" );
final Button cancel = new Button( "Cancel" );
final Checkbox radius2Enable = new Checkbox( "Enable Manual Adjustment of Radius 2 ", enableRadius2 );
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 ( radius1, c );
++c.gridy;
frame.add( radiusText1, c );
++c.gridy;
frame.add ( radius2, c );
++c.gridy;
frame.add( radiusText2, c );
++c.gridy;
c.insets = new Insets(0,65,0,65);
frame.add( radius2Enable, 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(10,150,0,150);
frame.add( button, c );
++c.gridy;
c.insets = new Insets(10,150,0,150);
frame.add( cancel, c );
/* Configuration */
radius1.addAdjustmentListener( new Radius1Listener( radiusText1, radiusMin, radiusMax, scrollbarSize, radius1, radius2, radiusText2 ) );
radius2.addAdjustmentListener( new Radius2Listener( radiusMin, radiusMax, scrollbarSize, radius2, radiusText2 ) );
threshold.addAdjustmentListener( new ThresholdListener( thresholdText, thresholdMin, thresholdMax ) );
button.addActionListener( new FinishButtonListener( frame, false ) );
cancel.addActionListener( new FinishButtonListener( frame, true ) );
min.addItemListener( new MinListener() );
max.addItemListener( new MaxListener() );
radius2Enable.addItemListener( new EnableListener( radius2, radiusText2 ) );
if ( !enableRadius2 )
radius2Enable.setEnabled( false );
frame.addWindowListener( new FrameListener( frame ) );
frame.setVisible( true );
originalColor = radius2.getBackground();
radius2.setBackground( inactiveColor );
radiusText1.setFont( radiusText1.getFont().deriveFont( Font.BOLD ) );
thresholdText.setFont( thresholdText.getFont().deriveFont( Font.BOLD ) );
}
protected class EnableListener implements ItemListener
{
final Scrollbar radius2;
final Label radiusText2;
public EnableListener( final Scrollbar radius2, final Label radiusText2 )
{
this.radiusText2 = radiusText2;
this.radius2 = radius2;
}
@Override
public void itemStateChanged( final ItemEvent arg0 )
{
if ( arg0.getStateChange() == ItemEvent.DESELECTED )
{
radiusText2.setFont( radiusText2.getFont().deriveFont( Font.PLAIN ) );
radius2.setBackground( inactiveColor );
enableRadius2 = false;
}
else if ( arg0.getStateChange() == ItemEvent.SELECTED )
{
radiusText2.setFont( radiusText2.getFont().deriveFont( Font.BOLD ) );
radius2.setBackground( originalColor );
enableRadius2 = 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 );
}
}
}
protected class FinishButtonListener implements ActionListener
{
final Frame parent;
final boolean cancel;
public FinishButtonListener( final Frame parent, final boolean cancel )
{
this.parent = parent;
this.cancel = cancel;
}
@Override
public void actionPerformed( final ActionEvent arg0 )
{
wasCanceled = cancel;
close( parent, sliceObserver, imp );
}
}
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 );
}
}
protected final void close( final Frame parent, final SliceObserver sliceObserver, final ImagePlus imp )
{
if ( parent != null )
parent.dispose();
if ( sliceObserver != null )
sliceObserver.unregister();
if ( imp != null )
{
imp.getOverlay().clear();
imp.updateAndDraw();
}
isFinished = true;
}
protected class Radius2Listener implements AdjustmentListener
{
final float min, max;
final int scrollbarSize;
final Scrollbar radiusScrollbar2;
final Label radius2Label;
public Radius2Listener( final float min, final float max, final int scrollbarSize, final Scrollbar radiusScrollbar2, final Label radius2Label )
{
this.min = min;
this.max = max;
this.scrollbarSize = scrollbarSize;
this.radiusScrollbar2 = radiusScrollbar2;
this.radius2Label = radius2Label;
}
@Override
public void adjustmentValueChanged( final AdjustmentEvent event )
{
if ( enableRadius2 )
{
radius2 = Math.round( computeValueFromScrollbarPosition( event.getValue(), min, max, scrollbarSize ) );
if ( radius2 <= radius1 )
{
radius2 = radius1 + 1;
radiusScrollbar2.setValue( computeScrollbarPositionFromValue( radius2, min, max, scrollbarSize ) );
}
radius2Label.setText( "Radius 2 = " + radius2 );
if ( !event.getValueIsAdjusting() )
{
while ( isComputing )
{
SimpleMultiThreading.threadWait( 10 );
}
updatePreview( ValueChange.RADIUS );
}
}
else
{
// if no manual adjustment simply reset it
radiusScrollbar2.setValue( computeScrollbarPositionFromValue( radius2, min, max, scrollbarSize ) );
}
}
}
protected class Radius1Listener implements AdjustmentListener
{
final Label label;
final float min, max;
final int scrollbarSize;
final Scrollbar radiusScrollbar1;
final Scrollbar radiusScrollbar2;
final Label radiusText2;
public Radius1Listener( final Label label, final float min, final float max, final int scrollbarSize, final Scrollbar radiusScrollbar1, final Scrollbar radiusScrollbar2, final Label radiusText2 )
{
this.label = label;
this.min = min;
this.max = max;
this.scrollbarSize = scrollbarSize;
this.radiusScrollbar1 = radiusScrollbar1;
this.radiusScrollbar2 = radiusScrollbar2;
this.radiusText2 = radiusText2;
}
@Override
public void adjustmentValueChanged( final AdjustmentEvent event )
{
radius1 = Math.round( computeValueFromScrollbarPosition( event.getValue(), min, max, scrollbarSize ) );
if ( !enableRadius2 )
{
radius2 = computeRadius2( radius1 );
radiusText2.setText( "Radius 2 = " + radius2 );
radiusScrollbar2.setValue( computeScrollbarPositionFromValue( radius2, min, max, scrollbarSize ) );
}
else if ( radius1 >= radius2 )
{
radius1 = radius2 - 2;
radiusScrollbar1.setValue( computeScrollbarPositionFromValue( radius1, min, max, scrollbarSize ) );
}
label.setText( "Radius 1 = " + radius1 );
if ( !event.getValueIsAdjusting() )
{
while ( isComputing )
{
SimpleMultiThreading.threadWait( 10 );
}
updatePreview( ValueChange.RADIUS );
}
}
}
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 radius, final float min, final float max, final int scrollbarSize )
{
return Util.round( ((radius - 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 InteractiveDoG().displaySliders();
new ImageJ();
ImagePlus imp = new ImagePlus( "/Users/preibischs/Documents/Microscopy/SPIM/HisYFP-SPIM/spim_TL18_Angle0_cropped.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 );
InteractiveIntegral ii = new InteractiveIntegral();
ii.setInitialRadius( 2 );
ii.run( null );
}
}