-
Jean-Yves TINEVEZ authoredJean-Yves TINEVEZ authored
CCCT.java 9.23 KiB
/*-
* #%L
* Fiji distribution of ImageJ for the life sciences.
* %%
* Copyright (C) 2015 - 2022 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 3 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-3.0.html>.
* #L%
*/
package fr.pasteur.iah.ccct;
import static fr.pasteur.iah.ccct.trackmate.CellContactDetectorFactory.KEY_CHANNEL_1;
import static fr.pasteur.iah.ccct.trackmate.CellContactDetectorFactory.KEY_CHANNEL_2;
import static fr.pasteur.iah.ccct.trackmate.CellContactDetectorFactory.KEY_CONTACT_SENSITIVITY;
import static fr.pasteur.iah.ccct.trackmate.CellContactDetectorFactory.KEY_SIGMA_FILTER;
import static fr.pasteur.iah.ccct.trackmate.CellContactDetectorFactory.KEY_THRESHOLD_1;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.ImageIcon;
import org.scijava.util.VersionUtils;
import fiji.plugin.trackmate.gui.GuiUtils;
import fiji.plugin.trackmate.util.TMUtils;
import fr.pasteur.iah.ccct.util.Thresholder;
import ij.CompositeImage;
import ij.IJ;
import ij.ImagePlus;
import ij.WindowManager;
import ij.plugin.PlugIn;
import net.imagej.ImgPlus;
import net.imagej.axis.Axes;
import net.imglib2.IterableInterval;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.algorithm.MultiThreaded;
import net.imglib2.algorithm.labeling.ConnectedComponentAnalysis;
import net.imglib2.algorithm.neighborhood.CenteredRectangleShape;
import net.imglib2.img.Img;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.img.planar.PlanarImgFactory;
import net.imglib2.multithreading.SimpleMultiThreading;
import net.imglib2.type.NativeType;
import net.imglib2.type.logic.BitType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.util.Util;
import net.imglib2.view.IntervalView;
import net.imglib2.view.Views;
@SuppressWarnings( "deprecation" )
public class CCCT< T extends RealType< T > & NativeType< T > > implements PlugIn, CCCTProcessor, MultiThreaded
{
public static final String PLUGIN_VERSION = VersionUtils.getVersion( CCCT.class );
public static final String PLUGIN_NAME = "CCCT";
public static final ImageIcon ICON = new ImageIcon( CCCT.class.getResource( "LogoCellContactAnalyzer-128x128.png" ) );;
public static final ImageIcon ICON_SMALL = new ImageIcon( CCCT.class.getResource( "LogoCellContactAnalyzer-64x64.png" ) );;
ImagePlus imp;
private CCCTGui gui;
private int numThreads;
public CCCT()
{
setNumThreads( 10 );
}
@Override
public void run( final String arg )
{
if ( null != arg && arg.length() > 0 )
{
imp = new CompositeImage( new ImagePlus( arg ) );
}
else
{
imp = WindowManager.getCurrentImage();
}
if ( null == imp )
{
IJ.error( PLUGIN_NAME + " v" + PLUGIN_VERSION, "Please open a multi-channel image first." );
return;
}
if ( !imp.isVisible() )
{
imp.show();
}
gui = new CCCTGui( imp, this );
GuiUtils.positionWindow( gui, imp.getWindow() );
gui.setVisible( true );
}
@Override
public void process( final boolean showContactImage, final boolean contactMask, final boolean contactLabels, final boolean trackLabels )
{
@SuppressWarnings( "unchecked" )
final ImgPlus< T > img = TMUtils.rawWraps( imp );
final Map< String, Object > settings = gui.getSettings();
// In ImgLib2, dimensions are 0-based.
final int channel1 = ( Integer ) settings.get( KEY_CHANNEL_1 ) - 1;
final int channel2 = ( Integer ) settings.get( KEY_CHANNEL_2 ) - 1;
final int contactSensitivity = ( Integer ) settings.get( KEY_CONTACT_SENSITIVITY );
final double sigma = ( Double ) settings.get( KEY_SIGMA_FILTER );
final double thresholdC1 = ( Double ) settings.get( KEY_THRESHOLD_1 );
final double thresholdC2 = ( Double ) settings.get( KEY_THRESHOLD_1 );
final RandomAccessibleInterval< T > im1;
final RandomAccessibleInterval< T > im2;
final int cDim = img.dimensionIndex( Axes.CHANNEL );
if ( cDim < 0 )
{
im1 = img;
im2 = img;
}
else
{
im1 = Views.hyperSlice( img, cDim, channel1 );
im2 = Views.hyperSlice( img, cDim, channel2 );
}
final PlanarImgFactory< T > factory = new PlanarImgFactory<>( Util.getTypeFromInterval( img ) );
final Img< T > out = factory.create( im1 );
int timeDim = img.dimensionIndex( Axes.TIME );
if ( timeDim >= 0 )
if ( cDim >= 0 && timeDim > cDim )
timeDim--;
final int td = timeDim;
final int nFrames = imp.getNFrames();
{
gui.setProgressStatus( "Contact image" );
final AtomicInteger ai = new AtomicInteger( 0 );
final Thread[] threads = SimpleMultiThreading.newThreads( numThreads );
for ( int i = 0; i < threads.length; i++ )
{
threads[ i ] = new Thread( "CCCT Thread Contact Image" )
{
@Override
public void run()
{
for ( int frame = ai.getAndIncrement(); frame < nFrames; frame = ai.getAndIncrement() )
{
final ContactImgGenerator< T > algo = new ContactImgGenerator< T >(
Views.hyperSlice( im1, td, frame ),
Views.hyperSlice( im2, td, frame ),
Views.hyperSlice( out, out.numDimensions() - 1, frame ),
thresholdC1, thresholdC2, contactSensitivity, sigma );
if ( !algo.checkInput() || !algo.process() )
{
System.err.println( algo.getErrorMessage() );
return;
}
gui.setProgress( ( 1.0 + ai.get() ) / nFrames );
}
}
};
}
SimpleMultiThreading.startAndJoin( threads );
if ( showContactImage )
{
final ImagePlus contacts = ImageJFunctions.wrap( out, "Contacts" );
contacts.setCalibration( imp.getCalibration().copy() );
contacts.setDimensions( 1, imp.getNSlices(), imp.getNFrames() );
contacts.show();
contacts.resetDisplayRange();
IJ.run( "Fire" );
}
}
/*
* Generate binary mask.
*/
if ( contactMask || contactLabels )
{
final PlanarImgFactory< BitType > maskFactory = new PlanarImgFactory<>( new BitType() );
final Img< BitType > mask = maskFactory.create( im1 );
final T valTreshold = out.firstElement().createVariable();
valTreshold.setZero();
gui.setProgressStatus( "Contact masks" );
final AtomicInteger ai = new AtomicInteger( 0 );
final Thread[] threads = SimpleMultiThreading.newThreads( numThreads );
for ( int i = 0; i < threads.length; i++ )
{
threads[ i ] = new Thread( "CCCT Thread Contact Mask" )
{
@Override
public void run()
{
for ( int frame = ai.getAndIncrement(); frame < nFrames; frame = ai.getAndIncrement() )
{
final IterableInterval< BitType > maskSlice = Views.hyperSlice( mask, mask.numDimensions() - 1, frame );
final IntervalView< T > slice = Views.hyperSlice( out, out.numDimensions() - 1, frame );
Thresholder.threshold( slice, maskSlice, valTreshold, true, 1 );
gui.setProgress( ( 1.0 + ai.get() ) / nFrames );
}
}
};
}
SimpleMultiThreading.startAndJoin( threads );
if ( contactMask )
{
final ImagePlus masks = ImageJFunctions.wrap( mask, "ContactMasks" );
masks.setCalibration( imp.getCalibration() );
masks.setDimensions( 1, imp.getNSlices(), imp.getNFrames() );
masks.show();
}
if ( contactLabels )
{
gui.setProgressStatus( "Contact labels" );
gui.setProgress( 0. );
final Img< UnsignedShortType > lbl = Util.getArrayOrCellImgFactory( mask, new UnsignedShortType() ).create( mask );
if ( trackLabels && imp.getNFrames() > 1 )
/*
* Connect in all dimensions, including time. This will
* effectively track contacts, provided they overlap from
* one frame to another.
*/
ConnectedComponentAnalysis.connectedComponents( mask, lbl );
else
{
/*
* Skip the time dimension when connecting mask.
*/
final int numDimensions = mask.numDimensions();
final int[] span = new int[ numDimensions ];
Arrays.fill( span, 1 );
if ( imp.getNSlices() > 1 )
span[ 3 ] = 0;
else
span[ 2 ] = 0;
ConnectedComponentAnalysis.connectedComponents( mask, lbl, new CenteredRectangleShape( span, false ) );
}
// For display range.
int maxLbl = -1;
for ( final UnsignedShortType pixel : lbl )
if ( pixel.get() > maxLbl )
maxLbl = pixel.get();
final ImagePlus labels = ImageJFunctions.wrap( lbl, "ContactLabels" );
labels.setCalibration( imp.getCalibration().copy() );
labels.setDimensions( 1, imp.getNSlices(), imp.getNFrames() );
labels.setDisplayRange( 0, maxLbl );
labels.show();
IJ.run( "3-3-2 RGB" );
}
}
}
@Override
public void setNumThreads()
{
this.numThreads = Runtime.getRuntime().availableProcessors();
}
@Override
public void setNumThreads( final int numThreads )
{
this.numThreads = numThreads;
}
@Override
public int getNumThreads()
{
return numThreads;
}
}