/*-
* #%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;
import ij.gui.GenericDialog;
import ij.plugin.PlugIn;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import mpicbg.spim.data.sequence.Channel;
import mpicbg.spim.data.sequence.ViewDescription;
import mpicbg.spim.data.sequence.ViewId;
import mpicbg.spim.data.sequence.VoxelDimensions;
import mpicbg.spim.io.IOFunctions;
import net.imglib2.KDTree;
import net.imglib2.RealPoint;
import net.imglib2.neighborsearch.KNearestNeighborSearchOnKDTree;
import spim.fiji.plugin.queryXML.LoadParseQueryXML;
import spim.fiji.plugin.thinout.ChannelProcessThinOut;
import spim.fiji.plugin.thinout.Histogram;
import spim.fiji.spimdata.SpimData2;
import spim.fiji.spimdata.interestpoints.InterestPoint;
import spim.fiji.spimdata.interestpoints.InterestPointList;
import spim.fiji.spimdata.interestpoints.ViewInterestPointLists;
import spim.fiji.spimdata.interestpoints.ViewInterestPoints;
public class ThinOut_Detections implements PlugIn
{
public static boolean[] defaultShowHistogram;
public static int[] defaultSubSampling;
public static String[] defaultNewLabels;
public static int[] defaultRemoveKeep;
public static double[] defaultCutoffThresholdMin, defaultCutoffThresholdMax;
public static String[] removeKeepChoice = new String[]{ "Remove Range", "Keep Range" };
public static double defaultThresholdMinValue = 0;
public static double defaultThresholdMaxValue = 5;
public static int defaultSubSamplingValue = 1;
public static String defaultNewLabelText = "thinned-out";
public static int defaultRemoveKeepValue = 0; // 0 == remove, 1 == keep
@Override
public void run( final String arg )
{
final LoadParseQueryXML xml = new LoadParseQueryXML();
if ( !xml.queryXML( "", true, false, true, true ) )
return;
final SpimData2 data = xml.getData();
final List< ViewId > viewIds = SpimData2.getAllViewIdsSorted( data, xml.getViewSetupsToProcess(), xml.getTimePointsToProcess() );
// ask which channels have the objects we are searching for
final List< ChannelProcessThinOut > channels = getChannelsAndLabels( data, viewIds );
if ( channels == null )
return;
// get the actual min/max thresholds for cutting out
if ( !getThinOutThresholds( data, viewIds, channels ) )
return;
// thin out detections and save the new interestpoint files
if ( !thinOut( data, viewIds, channels, true ) )
return;
// write new xml
SpimData2.saveXML( data, xml.getXMLFileName(), xml.getClusterExtension() );
}
public static boolean thinOut( final SpimData2 spimData, final List< ViewId > viewIds, final List< ChannelProcessThinOut > channels, final boolean save )
{
final ViewInterestPoints vip = spimData.getViewInterestPoints();
for ( final ChannelProcessThinOut channel : channels )
{
final double minDistance = channel.getMin();
final double maxDistance = channel.getMax();
final boolean keepRange = channel.keepRange();
for ( final ViewId viewId : viewIds )
{
final ViewDescription vd = spimData.getSequenceDescription().getViewDescription( viewId );
if ( !vd.isPresent() || vd.getViewSetup().getChannel().getId() != channel.getChannel().getId() )
continue;
final ViewInterestPointLists vipl = vip.getViewInterestPointLists( viewId );
final InterestPointList oldIpl = vipl.getInterestPointList( channel.getLabel() );
if ( oldIpl.getInterestPoints() == null )
oldIpl.loadInterestPoints();
final VoxelDimensions voxelSize = vd.getViewSetup().getVoxelSize();
// assemble the list of points (we need two lists as the KDTree sorts the list)
// we assume that the order of list2 and points is preserved!
final List< RealPoint > list1 = new ArrayList< RealPoint >();
final List< RealPoint > list2 = new ArrayList< RealPoint >();
final List< double[] > points = new ArrayList< double[] >();
for ( final InterestPoint ip : oldIpl.getInterestPoints() )
{
list1.add ( new RealPoint(
ip.getL()[ 0 ] * voxelSize.dimension( 0 ),
ip.getL()[ 1 ] * voxelSize.dimension( 1 ),
ip.getL()[ 2 ] * voxelSize.dimension( 2 ) ) );
list2.add ( new RealPoint(
ip.getL()[ 0 ] * voxelSize.dimension( 0 ),
ip.getL()[ 1 ] * voxelSize.dimension( 1 ),
ip.getL()[ 2 ] * voxelSize.dimension( 2 ) ) );
points.add( ip.getL() );
}
// make the KDTree
final KDTree< RealPoint > tree = new KDTree< RealPoint >( list1, list1 );
// Nearest neighbor for each point, populate the new list
final KNearestNeighborSearchOnKDTree< RealPoint > nn = new KNearestNeighborSearchOnKDTree< RealPoint >( tree, 2 );
final InterestPointList newIpl = new InterestPointList(
oldIpl.getBaseDir(),
new File(
oldIpl.getFile().getParentFile(),
"tpId_" + viewId.getTimePointId() + "_viewSetupId_" + viewId.getViewSetupId() + "." + channel.getNewLabel() ) );
newIpl.setInterestPoints( new ArrayList< InterestPoint >() );
int id = 0;
for ( int j = 0; j < list2.size(); ++j )
{
final RealPoint p = list2.get( j );
nn.search( p );
// first nearest neighbor is the point itself, we need the second nearest
final double d = nn.getDistance( 1 );
if ( ( keepRange && d >= minDistance && d <= maxDistance ) || ( !keepRange && ( d < minDistance || d > maxDistance ) ) )
{
newIpl.getInterestPoints().add( new InterestPoint( id++, points.get( j ).clone() ) );
}
}
if ( keepRange )
newIpl.setParameters( "thinned-out '" + channel.getLabel() + "', kept range from " + minDistance + " to " + maxDistance );
else
newIpl.setParameters( "thinned-out '" + channel.getLabel() + "', removed range from " + minDistance + " to " + maxDistance );
vipl.addInterestPointList( channel.getNewLabel(), newIpl );
IOFunctions.println( new Date( System.currentTimeMillis() ) + ": TP=" + vd.getTimePointId() + " ViewSetup=" + vd.getViewSetupId() +
", Detections: " + oldIpl.getInterestPoints().size() + " >>> " + newIpl.getInterestPoints().size() );
if ( save && !newIpl.saveInterestPoints() )
{
IOFunctions.println( "Error saving interest point list: " + new File( newIpl.getBaseDir(), newIpl.getFile().toString() + newIpl.getInterestPointsExt() ) );
return false;
}
}
}
return true;
}
public static boolean getThinOutThresholds( final SpimData2 spimData, final List< ViewId > viewIds, final List< ChannelProcessThinOut > channels )
{
for ( final ChannelProcessThinOut channel : channels )
if ( channel.showHistogram() )
plotHistogram( spimData, viewIds, channel );
if ( defaultCutoffThresholdMin == null || defaultCutoffThresholdMin.length != channels.size() ||
defaultCutoffThresholdMax == null || defaultCutoffThresholdMax.length != channels.size() )
{
defaultCutoffThresholdMin = new double[ channels.size() ];
defaultCutoffThresholdMax = new double[ channels.size() ];
for ( int i = 0; i < channels.size(); ++i )
{
defaultCutoffThresholdMin[ i ] = defaultThresholdMinValue;
defaultCutoffThresholdMax[ i ] = defaultThresholdMaxValue;
}
}
if ( defaultRemoveKeep == null || defaultRemoveKeep.length != channels.size() )
{
defaultRemoveKeep = new int[ channels.size() ];
for ( int i = 0; i < channels.size(); ++i )
defaultRemoveKeep[ i ] = defaultRemoveKeepValue;
}
final GenericDialog gd = new GenericDialog( "Define cut-off threshold" );
for ( int c = 0; c < channels.size(); ++c )
{
final ChannelProcessThinOut channel = channels.get( c );
gd.addChoice( "Channel_" + channel.getChannel().getName() + "_", removeKeepChoice, removeKeepChoice[ defaultRemoveKeep[ c ] ] );
gd.addNumericField( "Channel_" + channel.getChannel().getName() + "_range_lower_threshold", defaultCutoffThresholdMin[ c ], 2 );
gd.addNumericField( "Channel_" + channel.getChannel().getName() + "_range_upper_threshold", defaultCutoffThresholdMax[ c ], 2 );
gd.addMessage( "" );
}
gd.showDialog();
if ( gd.wasCanceled() )
return false;
for ( int c = 0; c < channels.size(); ++c )
{
final ChannelProcessThinOut channel = channels.get( c );
final int removeKeep = defaultRemoveKeep[ c ] = gd.getNextChoiceIndex();
if ( removeKeep == 1 )
channel.setKeepRange( true );
else
channel.setKeepRange( false );
channel.setMin( defaultCutoffThresholdMin[ c ] = gd.getNextNumber() );
channel.setMax( defaultCutoffThresholdMax[ c ] = gd.getNextNumber() );
if ( channel.getMin() >= channel.getMax() )
{
IOFunctions.println( "You selected the minimal threshold larger than the maximal threshold for channel " + channel.getChannel().getName() );
IOFunctions.println( "Stopping." );
return false;
}
else
{
if ( channel.keepRange() )
IOFunctions.println( "Channel " + channel.getChannel().getName() + ": keep only distances from " + channel.getMin() + " >>> " + channel.getMax() );
else
IOFunctions.println( "Channel " + channel.getChannel().getName() + ": remove distances from " + channel.getMin() + " >>> " + channel.getMax() );
}
}
return true;
}
public static Histogram plotHistogram( final SpimData2 spimData, final List< ViewId > viewIds, final ChannelProcessThinOut channel )
{
final ViewInterestPoints vip = spimData.getViewInterestPoints();
// list of all distances
final ArrayList< Double > distances = new ArrayList< Double >();
final Random rnd = new Random( System.currentTimeMillis() );
String unit = null;
for ( final ViewId viewId : viewIds )
{
final ViewDescription vd = spimData.getSequenceDescription().getViewDescription( viewId );
if ( !vd.isPresent() || vd.getViewSetup().getChannel().getId() != channel.getChannel().getId() )
continue;
final ViewInterestPointLists vipl = vip.getViewInterestPointLists( viewId );
final InterestPointList ipl = vipl.getInterestPointList( channel.getLabel() );
final VoxelDimensions voxelSize = vd.getViewSetup().getVoxelSize();
if ( ipl.getInterestPoints() == null )
ipl.loadInterestPoints();
if ( unit == null )
unit = vd.getViewSetup().getVoxelSize().unit();
// assemble the list of points
final List< RealPoint > list = new ArrayList< RealPoint >();
for ( final InterestPoint ip : ipl.getInterestPoints() )
{
list.add ( new RealPoint(
ip.getL()[ 0 ] * voxelSize.dimension( 0 ),
ip.getL()[ 1 ] * voxelSize.dimension( 1 ),
ip.getL()[ 2 ] * voxelSize.dimension( 2 ) ) );
}
// make the KDTree
final KDTree< RealPoint > tree = new KDTree< RealPoint >( list, list );
// Nearest neighbor for each point
final KNearestNeighborSearchOnKDTree< RealPoint > nn = new KNearestNeighborSearchOnKDTree< RealPoint >( tree, 2 );
for ( final RealPoint p : list )
{
// every n'th point only
if ( rnd.nextDouble() < 1.0 / (double)channel.getSubsampling() )
{
nn.search( p );
// first nearest neighbor is the point itself, we need the second nearest
distances.add( nn.getDistance( 1 ) );
}
}
}
final Histogram h = new Histogram( distances, 100, "Distance Histogram [Channel=" + channel.getChannel().getName() + "]", unit );
h.showHistogram();
IOFunctions.println( "Channel " + channel.getChannel().getName() + ": min distance=" + h.getMin() + ", max distance=" + h.getMax() );
return h;
}
public static ArrayList< ChannelProcessThinOut > getChannelsAndLabels(
final SpimData2 spimData,
final List< ViewId > viewIds )
{
// build up the dialog
final GenericDialog gd = new GenericDialog( "Choose segmentations to thin out" );
final List< Channel > channels = SpimData2.getAllChannelsSorted( spimData, viewIds );
final int nAllChannels = spimData.getSequenceDescription().getAllChannelsOrdered().size();
if ( Interest_Point_Registration.defaultChannelLabels == null || Interest_Point_Registration.defaultChannelLabels.length != nAllChannels )
Interest_Point_Registration.defaultChannelLabels = new int[ nAllChannels ];
if ( defaultShowHistogram == null || defaultShowHistogram.length != channels.size() )
{
defaultShowHistogram = new boolean[ channels.size() ];
for ( int i = 0; i < channels.size(); ++i )
defaultShowHistogram[ i ] = true;
}
if ( defaultSubSampling == null || defaultSubSampling.length != channels.size() )
{
defaultSubSampling = new int[ channels.size() ];
for ( int i = 0; i < channels.size(); ++i )
defaultSubSampling[ i ] = defaultSubSamplingValue;
}
if ( defaultNewLabels == null || defaultNewLabels.length != channels.size() )
{
defaultNewLabels = new String[ channels.size() ];
for ( int i = 0; i < channels.size(); ++i )
defaultNewLabels[ i ] = defaultNewLabelText;
}
// check which channels and labels are available and build the choices
final ArrayList< String[] > channelLabels = new ArrayList< String[] >();
int j = 0;
for ( final Channel channel : channels )
{
final String[] labels = Interest_Point_Registration.getAllInterestPointLabelsForChannel(
spimData,
viewIds,
channel,
"thin out" );
if ( Interest_Point_Registration.defaultChannelLabels[ j ] >= labels.length )
Interest_Point_Registration.defaultChannelLabels[ j ] = 0;
String ch = channel.getName().replace( ' ', '_' );
gd.addCheckbox( "Channel_" + ch + "_Display_distance_histogram", defaultShowHistogram[ j ] );
gd.addChoice( "Channel_" + ch + "_Interest_points", labels, labels[ Interest_Point_Registration.defaultChannelLabels[ j ] ] );
gd.addStringField( "Channel_" + ch + "_New_label", defaultNewLabels[ j ], 20 );
gd.addNumericField( "Channel_" + ch + "_Subsample histogram", defaultSubSampling[ j ], 0, 5, "times" );
channelLabels.add( labels );
++j;
}
gd.showDialog();
if ( gd.wasCanceled() )
return null;
// assemble which channels have been selected with with label
final ArrayList< ChannelProcessThinOut > channelsToProcess = new ArrayList< ChannelProcessThinOut >();
j = 0;
for ( final Channel channel : channels )
{
final boolean showHistogram = defaultShowHistogram[ j ] = gd.getNextBoolean();
final int channelChoice = Interest_Point_Registration.defaultChannelLabels[ j ] = gd.getNextChoiceIndex();
final String newLabel = defaultNewLabels[ j ] = gd.getNextString();
final int subSampling = defaultSubSampling[ j ] = (int)Math.round( gd.getNextNumber() );
if ( channelChoice < channelLabels.get( j ).length - 1 )
{
String label = channelLabels.get( j )[ channelChoice ];
if ( label.contains( Interest_Point_Registration.warningLabel ) )
label = label.substring( 0, label.indexOf( Interest_Point_Registration.warningLabel ) );
channelsToProcess.add( new ChannelProcessThinOut( channel, label, newLabel, showHistogram, subSampling ) );
}
++j;
}
return channelsToProcess;
}
public static void main( final String[] args )
{
new ThinOut_Detections().run( null );
}
}