/*-
* #%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 fiji.plugin;
import fiji.util.gui.GenericDialogPlus;
import ij.IJ;
import ij.gui.DialogListener;
import ij.gui.GenericDialog;
import ij.gui.MultiLineLabel;
import ij.plugin.PlugIn;
import java.awt.AWTEvent;
import java.awt.TextField;
import java.awt.event.TextEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import mpicbg.imglib.container.array.ArrayContainerFactory;
import mpicbg.imglib.container.cell.CellContainerFactory;
import mpicbg.spim.Reconstruction;
import mpicbg.spim.io.ConfigurationParserException;
import mpicbg.spim.io.IOFunctions;
import mpicbg.spim.io.SPIMConfiguration;
import mpicbg.spim.io.TextFileAccess;
import spim.fiji.plugin.util.GUIHelper;
import spimopener.SPIMExperiment;
public class Multi_View_Fusion implements PlugIn
{
final private String myURL = "http://fly.mpi-cbg.de/preibisch";
final private String paperURL = "http://www.nature.com/nmeth/journal/v7/n6/full/nmeth0610-418.html";
final static String fusionType[] = new String[] { "Single-channel", "Multi-channel" };
final static String outputType[] = new String[] { "Display only", "Save 2d-slices, all in one directory", "Save 2d-slices, one directory per time-point" };
public static int defaultFusionType = 0;
@Override
public void run(String arg0)
{
// output to IJ.log
IOFunctions.printIJLog = true;
final GenericDialog gd = new GenericDialog( "Multi-view fusion" );
gd.addChoice( "Select_channel type", fusionType, fusionType[ defaultFusionType ] );
gd.addMessage( "Please note that the Multi-view fusion is based on a publication.\n" +
"If you use it successfully for your research please be so kind to cite our work:\n" +
"Preibisch et al., Nature Methods (2010), 7(6):418-419\n" );
MultiLineLabel text = (MultiLineLabel) gd.getMessage();
GUIHelper.addHyperLinkListener( text, paperURL );
gd.showDialog();
if ( gd.wasCanceled() )
return;
final int channelChoice = gd.getNextChoiceIndex();
defaultFusionType = channelChoice;
final SPIMConfiguration conf;
if ( channelChoice == 0 )
conf = getParameters( false );
else
conf = getParameters( true );
// cancelled
if ( conf == null )
return;
conf.readSegmentation = true;
conf.readRegistration = true;
new Reconstruction( conf );
}
//public static String allChannels = "0, 1";
public static String[] fusionMethodList = { "Fuse into a single image", "Create independent registered images" };
public static int defaultFusionMethod = 0;
public static int defaultParalellViews = 0;
public static boolean fusionUseBlendingStatic = true;
public static boolean fusionUseContentBasedStatic = false;
public static boolean fusionUseContentBasedIntegralStatic = false;
//public static boolean displayFusedImageStatic = true;
//public static boolean saveFusedImageStatic = true;
public static int defaultOutputType = 1;
public static int outputImageScalingStatic = 1;
public static int cropOffsetXStatic = 0;
public static int cropOffsetYStatic = 0;
public static int cropOffsetZStatic = 0;
public static int cropSizeXStatic = 0;
public static int cropSizeYStatic = 0;
public static int cropSizeZStatic = 0;
protected SPIMConfiguration getParameters( final boolean multichannel )
{
final GenericDialogPlus gd = new GenericDialogPlus( "Multi-View Fusion" );
gd.addDirectoryOrFileField( "SPIM_data_directory", Bead_Registration.spimDataDirectory );
final TextField tfSpimDataDirectory = (TextField) gd.getStringFields().lastElement();
if ( multichannel )
gd.addStringField( "Pattern_of_SPIM files", Bead_Registration.fileNamePatternMC, 25 );
else
gd.addStringField( "Pattern_of_SPIM files", Bead_Registration.fileNamePattern, 25 );
final TextField tfFilePattern = (TextField) gd.getStringFields().lastElement();
gd.addStringField( "Timepoints_to_process", Bead_Registration.timepoints );
final TextField tfTimepoints = (TextField) gd.getStringFields().lastElement();
gd.addStringField( "Angles to process", Bead_Registration.angles );
final TextField tfAngles = (TextField) gd.getStringFields().lastElement();
final TextField tfChannels;
if ( multichannel )
{
gd.addStringField( "Channels to process", Bead_Registration.channelsBeadsMC );
tfChannels = (TextField) gd.getStringFields().lastElement();
}
else
tfChannels = null;
gd.addMessage( "" );
gd.addChoice( "ImgLib_container_(input files)", Multi_View_Deconvolution.imglibContainer, Multi_View_Deconvolution.imglibContainer[ Multi_View_Deconvolution.defaultContainerInput ] );
gd.addMessage("");
gd.addMessage("This Plugin is developed by Stephan Preibisch\n" + myURL);
MultiLineLabel text = (MultiLineLabel) gd.getMessage();
GUIHelper.addHyperLinkListener(text, myURL);
gd.addDialogListener( new DialogListener()
{
@Override
public boolean dialogItemChanged( GenericDialog dialog, AWTEvent e )
{
if ( e instanceof TextEvent && e.getID() == TextEvent.TEXT_VALUE_CHANGED && e.getSource() == tfSpimDataDirectory )
{
TextField tf = ( TextField ) e.getSource();
final String spimDataDirectory = tf.getText();
File f = new File( spimDataDirectory );
if ( f.exists() && f.isFile() && f.getName().endsWith( ".xml" ) )
{
SPIMExperiment exp = new SPIMExperiment( f.getAbsolutePath() );
// disable file pattern field
tfFilePattern.setEnabled( false );
// set timepoint string
String expTimepoints = "";
if ( exp.timepointStart == exp.timepointEnd )
expTimepoints = "" + exp.timepointStart;
else
expTimepoints = "" + exp.timepointStart + "-" + exp.timepointEnd;
tfTimepoints.setText( expTimepoints );
// set angles string
String expAngles = "";
for ( String angle : exp.angles )
{
int a = Integer.parseInt( angle.substring( 1, angle.length() ) );
if ( !expAngles.equals( "" ) )
expAngles += ",";
expAngles += a;
}
tfAngles.setText( expAngles );
if ( multichannel )
{
// set channels string
String expChannels = "";
for ( final String channel : exp.channels )
{
final int c = Integer.parseInt( channel.substring( 1, channel.length() ) );
if ( !expChannels.equals( "" ) )
expChannels += ",";
expChannels += c;
}
tfChannels.setText( expChannels );
}
}
else
{
// enable file pattern field
tfFilePattern.setEnabled( true );
}
}
return true;
}
} );
File f = new File( tfSpimDataDirectory.getText() );
if ( f.exists() && f.isFile() && f.getName().endsWith( ".xml" ) )
{
// disable file pattern field
tfFilePattern.setEnabled( false );
if ( multichannel )
{
// set channels string
final SPIMExperiment exp = new SPIMExperiment( f.getAbsolutePath() );
String expChannels = "";
for ( final String channel : exp.channels )
{
final int c = Integer.parseInt( channel.substring( 1, channel.length() ) );
if ( !expChannels.equals( "" ) )
expChannels += ",";
expChannels += c;
}
tfChannels.setText( expChannels );
}
}
gd.showDialog();
if ( gd.wasCanceled() )
return null;
Bead_Registration.spimDataDirectory = gd.getNextString();
if ( multichannel )
Bead_Registration.fileNamePatternMC = gd.getNextString();
else
Bead_Registration.fileNamePattern = gd.getNextString();
Bead_Registration.timepoints = gd.getNextString();
Bead_Registration.angles = gd.getNextString();
Multi_View_Deconvolution.defaultContainerInput = gd.getNextChoiceIndex();
int numViews = 0;
try
{
numViews = SPIMConfiguration.parseIntegerString( Bead_Registration.angles ).size();
}
catch (ConfigurationParserException e)
{
IOFunctions.printErr( "Cannot understand/parse the channels: " + Bead_Registration.angles );
return null;
}
int numChannels = 0;
ArrayList<Integer> channels;
// verify this part
if ( multichannel )
{
Bead_Registration.channelsBeadsMC = gd.getNextString();
try
{
channels = SPIMConfiguration.parseIntegerString( Bead_Registration.channelsBeadsMC );
numChannels = channels.size();
}
catch (ConfigurationParserException e)
{
IOFunctions.printErr( "Cannot understand/parse the channels: " + Bead_Registration.channelsBeadsMC );
return null;
}
if ( numChannels < 1 )
{
IOFunctions.printErr( "There are no channels given: " + Bead_Registration.channelsBeadsMC );
return null;
}
}
else
{
numChannels = 1;
channels = new ArrayList<Integer>();
channels.add( 0 );
}
// create the configuration object
final SPIMConfiguration conf = new SPIMConfiguration();
if ( Multi_View_Deconvolution.defaultContainerInput == 1 )
conf.inputImageFactory = new CellContainerFactory( 256 );
else
conf.inputImageFactory = new ArrayContainerFactory();
conf.timepointPattern = Bead_Registration.timepoints;
conf.anglePattern = Bead_Registration.angles;
if ( multichannel )
{
conf.channelPattern = Bead_Registration.channelsBeadsMC;
conf.channelsToRegister = Bead_Registration.channelsBeadsMC;
conf.channelsToFuse = Bead_Registration.channelsBeadsMC;
conf.inputFilePattern = Bead_Registration.fileNamePatternMC;
}
else
{
conf.channelPattern = "";
conf.channelsToRegister = "";
conf.channelsToFuse = "";
conf.inputFilePattern = Bead_Registration.fileNamePattern;
}
f = new File( Bead_Registration.spimDataDirectory );
if ( f.exists() && f.isFile() && f.getName().endsWith( ".xml" ) )
{
conf.spimExperiment = new SPIMExperiment( f.getAbsolutePath() );
conf.inputdirectory = f.getAbsolutePath().substring( 0, f.getAbsolutePath().length() - 4 );
System.out.println( "inputdirectory : " + conf.inputdirectory );
}
else
{
conf.inputdirectory = Bead_Registration.spimDataDirectory;
}
// get filenames and so on...
conf.fuseOnly = true;
if ( !Bead_Registration.init( conf ) )
return null;
// test which registration files are there for each channel
// file = new File[ timepoints.length ][ channels.length ][ angles.length ];
final ArrayList<ArrayList<Integer>> timepoints = new ArrayList<ArrayList<Integer>>();
int numChoices = 0;
conf.zStretching = -1;
for ( int c = 0; c < channels.size(); ++c )
{
timepoints.add( new ArrayList<Integer>() );
final String name = conf.file[ 0 ][ c ][ 0 ][ 0 ].getName();
final File regDir = new File( conf.registrationFiledirectory );
IJ.log( "name: " + name );
IJ.log( "dir: " + regDir.getAbsolutePath() );
if ( !regDir.isDirectory() )
{
IOFunctions.println( conf.registrationFiledirectory + " is not a directory. " );
return null;
}
final String entries[] = regDir.list( new FilenameFilter() {
@Override
public boolean accept(File directory, String filename)
{
if ( filename.contains( name ) && filename.contains( ".registration" ) )
return true;
else
return false;
}
});
for ( final String e : entries )
IJ.log( e );
for ( final String s : entries )
{
if ( s.endsWith( ".registration" ) )
{
if ( !timepoints.get( c ).contains( -1 ) )
{
timepoints.get( c ).add( -1 );
numChoices++;
}
}
else if ( s.contains( ".registration.to_" ) )
{
final int timepoint = Integer.parseInt( s.substring( s.indexOf( ".registration.to_" ) + 17, s.length() ) );
if ( !timepoints.get( c ).contains( timepoint ) )
{
timepoints.get( c ).add( timepoint );
numChoices++;
}
}
if ( conf.zStretching < 0 )
{
conf.zStretching = loadZStretching( conf.registrationFiledirectory + s );
IOFunctions.println( "Z-stretching = " + conf.zStretching );
}
}
}
if ( numChoices == 0 )
{
IOFunctions.println( "No registration files available." );
return null;
}
for ( int c = 0; c < channels.size(); ++c )
for ( final int i : timepoints.get( c ) )
IOFunctions.println( c + ": " + i );
final GenericDialog gd2 = new GenericDialog( "Multi-View Fusion" );
// build up choices
final String[] choices = new String[ numChoices ];
final int[] suggest = new int[ channels.size() ];
int firstSuggestion = -1;
int index = 0;
for ( int c = 0; c < channels.size(); ++c )
{
final ArrayList<Integer> tps = timepoints.get( c );
// no suggestion yet
suggest[ c ] = -1;
for ( int i = 0; i < tps.size(); ++i )
{
if ( tps.get( i ) == -1 )
choices[ index ] = "Individual registration of channel " + channels.get( c );
else
choices[ index ] = "Time-point registration (reference=" + tps.get( i ) + ") of channel " + channels.get( c );
if ( suggest[ c ] == -1 )
{
suggest[ c ] = index;
if ( firstSuggestion == -1 )
firstSuggestion = index;
}
index++;
}
}
for ( int c = 0; c < channels.size(); ++c )
if ( suggest[ c ] == -1 )
suggest[ c ] = firstSuggestion;
for ( int c = 0; c < channels.size(); ++c )
gd2.addChoice( "Registration for channel " + channels.get( c ), choices, choices[ suggest[ c ] ]);
gd2.addMessage( "" );
gd2.addChoice( "Fusion_method", fusionMethodList, fusionMethodList[ defaultFusionMethod ] );
final String[] views = new String[ numViews ];
views[ 0 ] = "All";
for ( int v = 1; v < numViews; v++ )
views[ v ] = "" + v;
if ( defaultParalellViews >= views.length )
defaultParalellViews = views.length - 1;
gd2.addChoice( "Process_views_in_paralell", views, views[ defaultParalellViews ] );
gd2.addMessage( "" );
gd2.addCheckbox( "Blending", fusionUseBlendingStatic );
gd2.addCheckbox( "Content_based weights", fusionUseContentBasedStatic );
gd2.addCheckbox( "Content_based_weights_(fast, approximate)", fusionUseContentBasedIntegralStatic );
gd2.addMessage( "" );
gd2.addNumericField( "Downsample_output image n-times", outputImageScalingStatic, 0 );
gd2.addNumericField( "Crop_output_image_offset_x", cropOffsetXStatic, 0 );
gd2.addNumericField( "Crop_output_image_offset_y", cropOffsetYStatic, 0 );
gd2.addNumericField( "Crop_output_image_offset_z", cropOffsetZStatic, 0 );
gd2.addNumericField( "Crop_output_image_size_x", cropSizeXStatic, 0 );
gd2.addNumericField( "Crop_output_image_size_y", cropSizeYStatic, 0 );
gd2.addNumericField( "Crop_output_image_size_z", cropSizeZStatic, 0 );
gd2.addMessage( "" );
//gd2.addCheckbox( "Display_fused_image", displayFusedImageStatic );
//gd2.addCheckbox( "Save_fused_image", saveFusedImageStatic );
gd2.addChoice( "Fused_image_output", outputType, outputType[ defaultOutputType ] );
gd2.addMessage("");
gd2.addMessage("This Plugin is developed by Stephan Preibisch\n" + myURL);
text = (MultiLineLabel) gd2.getMessage();
GUIHelper.addHyperLinkListener(text, myURL);
gd2.showDialog();
if ( gd2.wasCanceled() )
return null;
// which channel uses which registration file from which channel to be fused
final int[][] registrationAssignment = new int[ channels.size() ][ 2 ];
for ( int c = 0; c < channels.size(); ++c )
{
final int choice = gd2.getNextChoiceIndex();
index = 0;
for ( int c2 = 0; c2 < channels.size(); ++c2 )
{
final ArrayList<Integer> tps = timepoints.get( c2 );
for ( int i = 0; i < tps.size(); ++i )
{
if ( index == choice )
{
registrationAssignment[ c ][ 0 ] = tps.get( i );
registrationAssignment[ c ][ 1 ] = c2;
}
index++;
}
}
}
// test consistency
final int tp = registrationAssignment[ 0 ][ 0 ];
for ( int c = 1; c < channels.size(); ++c )
{
if ( tp != registrationAssignment[ c ][ 0 ] )
{
IOFunctions.println( "Inconsistent choice of reference timeseries, only same reference timepoints or individual registration are allowed.");
return null;
}
}
// save from which channel to load registration
conf.registrationAssignmentForFusion = new int[ channels.size() ];
for ( int c = 0; c < channels.size(); ++c )
{
IOFunctions.println( "channel " + c + " takes it from channel " + registrationAssignment[ c ][ 1 ] );
conf.registrationAssignmentForFusion[ c ] = registrationAssignment[ c ][ 1 ];
}
if ( tp >= 0 )
{
conf.timeLapseRegistration = true;
conf.referenceTimePoint = tp;
// if the reference is not part of the time series, add it but do not fuse it
ArrayList< Integer > tpList = null;
try
{
tpList = SPIMConfiguration.parseIntegerString( conf.timepointPattern );
}
catch (ConfigurationParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
IJ.log( "Cannot parse time-point pattern: " + conf.timepointPattern );
return null;
}
if ( !tpList.contains( tp ) )
{
conf.timepointPattern += ", " + tp;
conf.fuseReferenceTimepoint = false;
//System.out.println( "new tp: '" + conf.timepointPattern + "'" );
if ( !Bead_Registration.init( conf ) )
return null;
}
else
{
//System.out.println( "old tp: '" + conf.timepointPattern + "'" );
}
}
IOFunctions.println( "tp " + tp );
defaultFusionMethod = gd2.getNextChoiceIndex();
defaultParalellViews = gd2.getNextChoiceIndex(); // 0 = all
fusionUseBlendingStatic = gd2.getNextBoolean();
fusionUseContentBasedStatic = gd2.getNextBoolean();
fusionUseContentBasedIntegralStatic = gd2.getNextBoolean();
outputImageScalingStatic = (int)Math.round( gd2.getNextNumber() );
cropOffsetXStatic = (int)Math.round( gd2.getNextNumber() );
cropOffsetYStatic = (int)Math.round( gd2.getNextNumber() );
cropOffsetZStatic = (int)Math.round( gd2.getNextNumber() );
cropSizeXStatic = (int)Math.round( gd2.getNextNumber() );
cropSizeYStatic = (int)Math.round( gd2.getNextNumber() );
cropSizeZStatic = (int)Math.round( gd2.getNextNumber() );
defaultOutputType = gd2.getNextChoiceIndex();
//displayFusedImageStatic = gd2.getNextBoolean();
//saveFusedImageStatic = gd2.getNextBoolean();
conf.paralellFusion = false;
conf.sequentialFusion = false;
conf.multipleImageFusion = false;
if ( defaultFusionMethod == 0 && defaultParalellViews == 0 )
{
conf.paralellFusion = true;
}
else if ( defaultFusionMethod == 0 && defaultParalellViews > 0 )
{
conf.sequentialFusion = true;
conf.numParalellViews = defaultParalellViews;
}
else
{
conf.multipleImageFusion = true;
if ( defaultParalellViews == 0 )
conf.numParalellViews = views.length;
else
conf.numParalellViews = defaultParalellViews;
}
if ( defaultOutputType == 0 )
conf.showOutputImage = true;
else
conf.showOutputImage = false;
conf.writeOutputImage = defaultOutputType;
conf.useLinearBlening = fusionUseBlendingStatic;
conf.useGaussContentBased = fusionUseContentBasedStatic;
conf.useIntegralContentBased = fusionUseContentBasedIntegralStatic;
conf.scale = outputImageScalingStatic;
conf.cropOffsetX = cropOffsetXStatic;
conf.cropOffsetY = cropOffsetYStatic;
conf.cropOffsetZ = cropOffsetZStatic;
conf.cropSizeX = cropSizeXStatic;
conf.cropSizeY = cropSizeYStatic;
conf.cropSizeZ = cropSizeZStatic;
conf.processImageFactory = new CellContainerFactory( 256 );
conf.overrideImageZStretching = true;
return conf;
}
protected static double loadZStretching( final String file )
{
BufferedReader in = TextFileAccess.openFileRead( file );
double z = -1;
try
{
while ( in.ready() )
{
String line = in.readLine();
if ( line.contains( "z-scaling:") )
z = Double.parseDouble( line.substring( line.indexOf( "ing" ) + 4, line.length() ).trim() );
}
}
catch (IOException e)
{
}
return z;
}
}