/*-
* #%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.resave;
import static mpicbg.spim.data.generic.sequence.ImgLoaderHints.LOAD_COMPLETELY;
import fiji.util.gui.GenericDialogPlus;
import ij.plugin.PlugIn;
import java.awt.Font;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mpicbg.spim.data.SpimData;
import mpicbg.spim.data.SpimDataException;
import mpicbg.spim.data.registration.ViewRegistration;
import mpicbg.spim.data.registration.ViewRegistrations;
import mpicbg.spim.data.sequence.MissingViews;
import mpicbg.spim.data.sequence.SequenceDescription;
import mpicbg.spim.data.sequence.TimePoint;
import mpicbg.spim.data.sequence.TimePoints;
import mpicbg.spim.data.sequence.TimePointsPattern;
import mpicbg.spim.data.sequence.ViewDescription;
import mpicbg.spim.data.sequence.ViewId;
import mpicbg.spim.data.sequence.ViewSetup;
import mpicbg.spim.io.IOFunctions;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.ImgFactory;
import net.imglib2.img.array.ArrayImgFactory;
import net.imglib2.img.cell.CellImgFactory;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.real.FloatType;
import spim.fiji.ImgLib2Temp.Pair;
import spim.fiji.ImgLib2Temp.ValuePair;
import spim.fiji.datasetmanager.StackList;
import spim.fiji.plugin.queryXML.LoadParseQueryXML;
import spim.fiji.spimdata.SpimData2;
import spim.fiji.spimdata.imgloaders.StackImgLoaderIJ;
import spim.fiji.spimdata.interestpoints.InterestPointList;
import spim.fiji.spimdata.interestpoints.ViewInterestPointLists;
import spim.fiji.spimdata.interestpoints.ViewInterestPoints;
import spim.process.fusion.export.Save3dTIFF;
import bdv.export.ProgressWriter;
public class Resave_TIFF implements PlugIn
{
public static String defaultPath = null;
public static int defaultContainer = 0;
public static boolean defaultCompress = false;
public static void main( final String[] args )
{
new Resave_TIFF().run( null );
}
public static class Parameters
{
public ImgFactory< ? extends NativeType< ? > > imgFactory;
public String xmlFile;
public boolean compress;
public boolean compress() { return compress; }
public String getXMLFile() { return xmlFile; }
public ImgFactory< ? extends NativeType< ? > > getImgFactory() { return imgFactory; }
}
@Override
public void run( final String arg0 )
{
final LoadParseQueryXML lpq = new LoadParseQueryXML();
if ( !lpq.queryXML( "Resaving as TIFF", "Resave", true, true, true, true ) )
return;
final ProgressWriter progressWriter = new ProgressWriterIJ();
progressWriter.out().println( "starting export..." );
final Parameters params = getParameters();
if ( params == null )
return;
final SpimData2 data = lpq.getData();
final List< ViewId > viewIds = SpimData2.getAllViewIdsSorted( data, lpq.getViewSetupsToProcess(), lpq.getTimePointsToProcess() );
// write the TIFF's
writeTIFF( data, viewIds, new File( params.xmlFile ).getParent(), params.compress, progressWriter );
// write the XML
try
{
final Pair< SpimData2, List< String > > result = createXMLObject( data, viewIds, params );
progressWriter.setProgress( 0.95 );
// write the XML
lpq.getIO().save( result.getA(), new File( params.xmlFile ).getAbsolutePath() );
// copy the interest points if they exist
copyInterestPoints( data.getBasePath(), new File( params.xmlFile ).getParentFile(), result.getB() );
}
catch ( SpimDataException e )
{
IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Could not save xml '" + params.xmlFile + "'." );
e.printStackTrace();
}
finally
{
progressWriter.setProgress( 1.00 );
IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Saved xml '" + params.xmlFile + "'." );
}
}
public static void copyInterestPoints( final File srcBase, final File destBase, final List< String > filesToCopy )
{
// test if source and target directory are identical, if so stop
String from = srcBase.getAbsolutePath();
String to = destBase.getAbsolutePath();
from = from.replace( "/./", "/" );
to = to.replace( "/./", "/" );
if ( from.endsWith( "/." ) )
from = from.substring( 0, from.length() - 2 );
if ( to.endsWith( "/." ) )
to = to.substring( 0, to.length() - 2 );
if ( new File( from ).getAbsolutePath().equals( new File( to ).getAbsolutePath() ) )
return;
final File src = new File( srcBase, "interestpoints" );
if ( src.exists() )
{
final File target = new File( destBase, "interestpoints" );
IOFunctions.println( new Date( System.currentTimeMillis() ) + ": Interestpoint directory exists. Copying '" + src + "' >>> '" + target + "'" );
try
{
copyFolder( src, target, filesToCopy );
}
catch (IOException e)
{
IOFunctions.println( new Date( System.currentTimeMillis() ) + ": FAILED to copying '" + src + "' >>> '" + target + "': " + e );
e.printStackTrace();
}
}
}
public static Parameters getParameters()
{
final GenericDialogPlus gd = new GenericDialogPlus( "Resave dataset as TIFF" );
if ( defaultPath == null )
defaultPath = LoadParseQueryXML.defaultXMLfilename;
PluginHelper.addSaveAsFileField( gd, "Select new XML", defaultPath, 80 );
gd.addChoice( "ImgLib2_data_container", StackList.imglib2Container, StackList.imglib2Container[ defaultContainer ] );
gd.addCheckbox( "Lossless compression of TIFF files (ZIP)", defaultCompress );
gd.addMessage( "Use ArrayImg if -ALL- input views are smaller than ~2048x2048x500 px (2^31 px), or if the\n" +
"program throws an OutOfMemory exception while processing. CellImg is slower, but more\n" +
"memory efficient and supports much larger file sizes only limited by the RAM of the machine.",
new Font( Font.SANS_SERIF, Font.ITALIC, 11 ) );
gd.showDialog();
if ( gd.wasCanceled() )
return null;
final Parameters params = new Parameters();
params.xmlFile = gd.getNextString();
if ( !params.xmlFile.endsWith( ".xml" ) )
params.xmlFile += ".xml";
params.compress = defaultCompress = gd.getNextBoolean();
defaultPath = LoadParseQueryXML.defaultXMLfilename = params.xmlFile;
if ( ( defaultContainer = gd.getNextChoiceIndex() ) == 0 )
params.imgFactory = new ArrayImgFactory< FloatType >();
else
params.imgFactory = new CellImgFactory< FloatType >();
return params;
}
public static void writeTIFF( final SpimData spimData, final List< ViewId > viewIds, final String path, final boolean compress, final ProgressWriter progressWriter )
{
if ( compress )
IOFunctions.println( new Date( System.currentTimeMillis() ) + ": Saving compressed TIFFS to directory '" + path + "'" );
else
IOFunctions.println( new Date( System.currentTimeMillis() ) + ": Saving TIFFS to directory '" + path + "'" );
final Save3dTIFF save = new Save3dTIFF( path, compress );
final int numAngles = SpimData2.getAllAnglesSorted( spimData, viewIds ).size();
final int numChannels = SpimData2.getAllChannelsSorted( spimData, viewIds ).size();
final int numIlluminations = SpimData2.getAllIlluminationsSorted( spimData, viewIds ).size();
final int numTimepoints = SpimData2.getAllTimePointsSorted( spimData, viewIds ).size();
int i = 0;
for ( final ViewId viewId : viewIds )
{
i++;
final ViewDescription viewDescription = spimData.getSequenceDescription().getViewDescription(
viewId.getTimePointId(), viewId.getViewSetupId() );
if ( !viewDescription.isPresent() )
continue;
final RandomAccessibleInterval img = spimData.getSequenceDescription().getImgLoader().getSetupImgLoader( viewId.getViewSetupId() ).getImage( viewId.getTimePointId(), LOAD_COMPLETELY );
String filename = "img";
if ( numTimepoints > 1 )
filename += "_TL" + viewId.getTimePointId();
if ( numChannels > 1 )
filename += "_Ch" + viewDescription.getViewSetup().getChannel().getName();
if ( numIlluminations > 1 )
filename += "_Ill" + viewDescription.getViewSetup().getIllumination().getName();
if ( numAngles > 1 )
filename += "_Angle" + viewDescription.getViewSetup().getAngle().getName();
save.exportImage( img, filename );
progressWriter.setProgress( ((i-1) / (double)viewIds.size()) * 95.00 );
}
}
public static Pair< SpimData2, List< String > > createXMLObject( final SpimData2 spimData, final List< ViewId > viewIds, final Parameters params )
{
int layoutTP = 0, layoutChannels = 0, layoutIllum = 0, layoutAngles = 0;
String filename = "img";
final int numAngles = SpimData2.getAllAnglesSorted( spimData, viewIds ).size();
final int numChannels = SpimData2.getAllChannelsSorted( spimData, viewIds ).size();
final int numIlluminations = SpimData2.getAllIlluminationsSorted( spimData, viewIds ).size();
final int numTimepoints = SpimData2.getAllTimePointsSorted( spimData, viewIds ).size();
if ( numTimepoints > 1 )
{
filename += "_TL{t}";
layoutTP = 1;
}
if ( numChannels > 1 )
{
filename += "_Ch{c}";
layoutChannels = 1;
}
if ( numIlluminations > 1 )
{
filename += "_Ill{i}";
layoutIllum = 1;
}
if ( numAngles > 1 )
{
filename += "_Angle{a}";
layoutAngles = 1;
}
filename += ".tif";
if ( params.compress )
filename += ".zip";
// Re-assemble a new SpimData object containing the subset of viewsetups and timepoints selected
final List< String > filesToCopy = new ArrayList< String >();
final SpimData2 newSpimData = assemblePartialSpimData2( spimData, viewIds, new File( params.xmlFile ).getParentFile(), filesToCopy );
final StackImgLoaderIJ imgLoader = new StackImgLoaderIJ(
new File( params.xmlFile ).getParentFile(),
filename, params.imgFactory,
layoutTP, layoutChannels, layoutIllum, layoutAngles, newSpimData.getSequenceDescription() );
newSpimData.getSequenceDescription().setImgLoader( imgLoader );
return new ValuePair< SpimData2, List< String > >( newSpimData, filesToCopy );
}
public static String listAllTimePoints( final List<TimePoint> timePointsToProcess )
{
String t = "" + timePointsToProcess.get( 0 ).getId();
for ( int i = 1; i < timePointsToProcess.size(); ++i )
t += ", " + timePointsToProcess.get( i ).getId();
return t;
}
public static void copyFolder( final File src, final File dest, final List< String > filesToCopy ) throws IOException
{
if ( src.isDirectory() )
{
if( !dest.exists() )
dest.mkdir();
for ( final String file : src.list() )
copyFolder( new File( src, file ), new File( dest, file ), filesToCopy );
}
else
{
boolean contains = false;
for ( int i = 0; i < filesToCopy.size() && !contains; ++i )
if ( src.getName().contains( filesToCopy.get( i ) ) )
contains = true;
if ( contains )
{
final InputStream in = new FileInputStream( src );
final OutputStream out = new FileOutputStream( dest );
final byte[] buffer = new byte[ 65535 ];
int length;
while ( ( length = in.read(buffer) ) > 0 )
out.write(buffer, 0, length);
in.close();
out.close();
}
}
}
/**
* Assembles a new SpimData2 based on the subset of timepoints and viewsetups as selected by the user.
* The imgloader is still not set here.
*
* It also fills up a list of filesToCopy from the interestpoints directory if it is not null.
*
* @param filesToCopy
* @return
*/
public static SpimData2 assemblePartialSpimData2( final SpimData2 spimData, final List< ViewId > viewIds, final File basePath, final List< String > filesToCopy )
{
final TimePoints timepoints;
try
{
timepoints = new TimePointsPattern( listAllTimePoints( SpimData2.getAllTimePointsSorted( spimData, viewIds ) ) );
}
catch (ParseException e)
{
IOFunctions.println( "Automatically created list of timepoints failed to parse. This should not happen, really :) -- " + e );
IOFunctions.println( "Here is the list: " + listAllTimePoints( SpimData2.getAllTimePointsSorted( spimData, viewIds ) ) );
e.printStackTrace();
return null;
}
final List< ViewSetup > setups = SpimData2.getAllViewSetupsSorted( spimData, viewIds );
// a hashset for all viewsetups that remain
final Set< ViewId > views = new HashSet< ViewId >();
for ( final ViewId viewId : viewIds )
views.add( new ViewId( viewId.getTimePointId(), viewId.getViewSetupId() ) );
final MissingViews oldMissingViews = spimData.getSequenceDescription().getMissingViews();
final HashSet< ViewId > missingViews = new HashSet< ViewId >();
if ( oldMissingViews != null && oldMissingViews.getMissingViews() != null )
for ( final ViewId id : oldMissingViews.getMissingViews() )
if ( views.contains( id ) )
missingViews.add( id );
// add the new missing views!!!
for ( final TimePoint t : timepoints.getTimePointsOrdered() )
for ( final ViewSetup v : setups )
{
final ViewId viewId = new ViewId( t.getId(), v.getId() );
if ( !views.contains( viewId ) )
missingViews.add( viewId );
}
// instantiate the sequencedescription
final SequenceDescription sequenceDescription = new SequenceDescription( timepoints, setups, null, new MissingViews( missingViews ) );
// re-assemble the registrations
final Map< ViewId, ViewRegistration > oldRegMap = spimData.getViewRegistrations().getViewRegistrations();
final Map< ViewId, ViewRegistration > newRegMap = new HashMap< ViewId, ViewRegistration >();
for ( final ViewId viewId : oldRegMap.keySet() )
if ( views.contains( viewId ) )
newRegMap.put( viewId, oldRegMap.get( viewId ) );
final ViewRegistrations viewRegistrations = new ViewRegistrations( newRegMap );
// re-assemble the interestpoints and a list of filenames to copy
final Map< ViewId, ViewInterestPointLists > oldInterestPoints = spimData.getViewInterestPoints().getViewInterestPoints();
final Map< ViewId, ViewInterestPointLists > newInterestPoints = new HashMap< ViewId, ViewInterestPointLists >();
for ( final ViewId viewId : oldInterestPoints.keySet() )
if ( views.contains( viewId ) )
{
final ViewInterestPointLists ipLists = oldInterestPoints.get( viewId );
newInterestPoints.put( viewId, ipLists );
if ( filesToCopy != null )
{
// get also all the filenames that we need to copy
for ( final InterestPointList ipl : ipLists.getHashMap().values() )
filesToCopy.add( ipl.getFile().getName() );
}
}
final ViewInterestPoints viewsInterestPoints = new ViewInterestPoints( newInterestPoints );
final SpimData2 newSpimData = new SpimData2(
basePath,
sequenceDescription,
viewRegistrations,
viewsInterestPoints,
spimData.getBoundingBoxes() );
return newSpimData;
}
}