/*-
* #%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.process.fusion.export;
import ij.gui.GenericDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import mpicbg.spim.data.SpimDataException;
import mpicbg.spim.data.registration.ViewRegistration;
import mpicbg.spim.data.registration.ViewRegistrations;
import mpicbg.spim.data.registration.ViewTransform;
import mpicbg.spim.data.registration.ViewTransformAffine;
import mpicbg.spim.data.sequence.SequenceDescription;
import mpicbg.spim.data.sequence.TimePoint;
import mpicbg.spim.data.sequence.TimePoints;
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.converter.RealUnsignedShortConverter;
import net.imglib2.converter.read.ConvertedRandomAccessibleInterval;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.util.Pair;
import net.imglib2.util.Util;
import net.imglib2.util.ValuePair;
import spim.fiji.plugin.queryXML.LoadParseQueryXML;
import spim.fiji.plugin.resave.Generic_Resave_HDF5;
import spim.fiji.plugin.resave.Generic_Resave_HDF5.Parameters;
import spim.fiji.plugin.resave.ProgressWriterIJ;
import spim.fiji.plugin.resave.Resave_HDF5;
import spim.fiji.spimdata.SpimData2;
import spim.fiji.spimdata.XmlIoSpimData2;
import spim.fiji.spimdata.boundingbox.BoundingBoxes;
import spim.fiji.spimdata.interestpoints.ViewInterestPointLists;
import spim.fiji.spimdata.interestpoints.ViewInterestPoints;
import spim.process.fusion.FusionHelper;
import spim.process.fusion.boundingbox.BoundingBoxGUI;
import bdv.export.ExportMipmapInfo;
import bdv.export.ProgressWriter;
import bdv.export.SubTaskProgressWriter;
import bdv.export.WriteSequenceToHdf5;
import bdv.img.hdf5.Hdf5ImageLoader;
import bdv.img.hdf5.Partition;
public class ExportSpimData2HDF5 implements ImgExport
{
private List< TimePoint > newTimepoints;
private List< ViewSetup > newViewSetups;
private Parameters params;
private SpimData2 spimData;
private Map< Integer, ExportMipmapInfo > perSetupExportMipmapInfo;
private HashMap< ViewId, Partition > viewIdToPartition;
private final ProgressWriter progressWriter = new ProgressWriterIJ();
@Override
public boolean finish()
{
System.out.println( "finish()" );
String path = params.getSeqFile().getAbsolutePath();
try
{
new XmlIoSpimData2( "" ).save( spimData, path );
IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Saved xml '" + path + "'." );
// this spimdata object was not modified, we just wrote a new one
return false;
}
catch ( SpimDataException e )
{
IOFunctions.println( "(" + new Date( System.currentTimeMillis() ) + "): Could not save xml '" + path + "'." );
e.printStackTrace();
return false;
}
}
@Override
public void setXMLData( final List< TimePoint > newTimepoints, final List< ViewSetup > newViewSetups )
{
System.out.println( "setXMLData()" );
this.newTimepoints = newTimepoints;
this.newViewSetups = newViewSetups;
}
@Override
public boolean queryParameters( SpimData2 spimData, final boolean is16bit )
{
System.out.println( "queryParameters()" );
if ( newTimepoints == null || newViewSetups == null )
{
IOFunctions.println( "new timepoints and new viewsetup list not set yet ... cannot continue" );
return false;
}
perSetupExportMipmapInfo = Resave_HDF5.proposeMipmaps( newViewSetups );
String fn = LoadParseQueryXML.defaultXMLfilename;
if ( fn.endsWith( ".xml" ) )
fn = fn.substring( 0, fn.length() - ".xml".length() );
for ( int i = 0;; ++i )
{
Generic_Resave_HDF5.lastExportPath = String.format( "%s-f%d.xml", fn, i );
if ( !new File( Generic_Resave_HDF5.lastExportPath ).exists() )
break;
}
final int firstviewSetupId = newViewSetups.get( 0 ).getId();
params = Generic_Resave_HDF5.getParameters( perSetupExportMipmapInfo.get( firstviewSetupId ), true, getDescription(), is16bit );
if ( params == null )
{
System.out.println( "abort " );
return false;
}
Pair< SpimData2, HashMap< ViewId, Partition > > init = initSpimData( newTimepoints, newViewSetups, params, perSetupExportMipmapInfo );
this.spimData = init.getA();
viewIdToPartition = init.getB();
return true;
}
protected static Pair< SpimData2, HashMap< ViewId, Partition > > initSpimData(
final List< TimePoint > newTimepoints,
final List< ViewSetup > newViewSetups,
final Parameters params,
final Map< Integer, ExportMipmapInfo > perSetupExportMipmapInfo )
{
// SequenceDescription containing the subset of viewsetups and timepoints. Does not have an ImgLoader yet.
final SequenceDescription seq = new SequenceDescription( new TimePoints( newTimepoints ), newViewSetups, null, null );
// Create identity ViewRegistration for all views.
final Map< ViewId, ViewRegistration > regMap = new HashMap< ViewId, ViewRegistration >();
for ( final ViewDescription vDesc : seq.getViewDescriptions().values() )
regMap.put( vDesc, new ViewRegistration( vDesc.getTimePointId(), vDesc.getViewSetupId() ) );
final ViewRegistrations viewRegistrations = new ViewRegistrations( regMap );
// Create empty ViewInterestPoints.
final ViewInterestPoints viewsInterestPoints = new ViewInterestPoints( new HashMap< ViewId, ViewInterestPointLists >() );
// base path is directory containing the XML file.
File basePath = params.getSeqFile().getParentFile();
ArrayList< Partition > hdf5Partitions = null;
HashMap< ViewId, Partition > viewIdToPartition = new HashMap< ViewId, Partition >();
if ( params.getSplit() )
{
String basename = params.getHDF5File().getAbsolutePath();
if ( basename.endsWith( ".h5" ) )
basename = basename.substring( 0, basename.length() - ".h5".length() );
hdf5Partitions = Partition.split( newTimepoints, newViewSetups, params.getTimepointsPerPartition(), params.getSetupsPerPartition(), basename );
for ( final ViewDescription vDesc : seq.getViewDescriptions().values() )
for ( Partition p : hdf5Partitions )
if ( p.contains( vDesc ) )
{
viewIdToPartition.put( vDesc, p );
break;
}
WriteSequenceToHdf5.writeHdf5PartitionLinkFile( seq, perSetupExportMipmapInfo, hdf5Partitions, params.getHDF5File() );
}
else
{
final HashMap< Integer, Integer > timepointIdSequenceToPartition = new HashMap< Integer, Integer >();
for ( final TimePoint timepoint : newTimepoints )
timepointIdSequenceToPartition.put( timepoint.getId(), timepoint.getId() );
final HashMap< Integer, Integer > setupIdSequenceToPartition = new HashMap< Integer, Integer >();
for ( final ViewSetup setup : newViewSetups )
setupIdSequenceToPartition.put( setup.getId(), setup.getId() );
final Partition partition = new Partition( params.getHDF5File().getAbsolutePath(), timepointIdSequenceToPartition, setupIdSequenceToPartition );
for ( final ViewDescription vDesc : seq.getViewDescriptions().values() )
viewIdToPartition.put( vDesc, partition );
}
seq.setImgLoader( new Hdf5ImageLoader( params.getHDF5File(), hdf5Partitions, seq, false ) );
SpimData2 spimData = new SpimData2( basePath, seq, viewRegistrations, viewsInterestPoints, new BoundingBoxes() );
return new ValuePair< SpimData2, HashMap<ViewId,Partition> >( spimData, viewIdToPartition );
}
@Override
public < T extends RealType< T > & NativeType< T >> boolean exportImage( RandomAccessibleInterval< T > img, BoundingBoxGUI bb, TimePoint tp, ViewSetup vs )
{
System.out.println( "exportImage1()" );
return exportImage( img, bb, tp, vs, Double.NaN, Double.NaN );
}
public static < T extends RealType< T > > double[] updateAndGetMinMax( final RandomAccessibleInterval< T > img, final Parameters params )
{
double min, max;
if ( params == null || params.getConvertChoice() == 0 || Double.isNaN( params.getMin() ) || Double.isNaN( params.getMin() ) )
{
final float[] minmax = FusionHelper.minMax( img );
min = minmax[ 0 ];
max = minmax[ 1 ];
min = Math.max( 0, min - ((min+max)/2.0) * 0.1 );
max = max + ((min+max)/2.0) * 0.1;
if ( params != null )
{
params.setMin( min );
params.setMax( max );
}
}
else
{
min = params.getMin();
max = params.getMax();
}
IOFunctions.println( "Min intensity for 16bit conversion: " + min );
IOFunctions.println( "Max intensity for 16bit conversion: " + max );
return new double[]{ min, max };
}
public static < T extends RealType< T > > RandomAccessibleInterval< UnsignedShortType > convert( final RandomAccessibleInterval< T > img, final Parameters params )
{
final double[] minmax = updateAndGetMinMax( img, params );
final RealUnsignedShortConverter< T > converter = new RealUnsignedShortConverter< T >( minmax[ 0 ], minmax[ 1 ] );
return new ConvertedRandomAccessibleInterval<T, UnsignedShortType>( img, converter, new UnsignedShortType() );
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public < T extends RealType< T > & NativeType< T > > boolean exportImage( RandomAccessibleInterval< T > img, BoundingBoxGUI bb, TimePoint tp, ViewSetup vs, double min, double max )
{
System.out.println( "exportImage2()" );
// write the image
final RandomAccessibleInterval< UnsignedShortType > ushortimg;
if ( ! UnsignedShortType.class.isInstance( Util.getTypeFromInterval( img ) ) )
ushortimg = convert( img, params );
else
ushortimg = ( RandomAccessibleInterval ) img;
final Partition partition = viewIdToPartition.get( new ViewId( tp.getId(), vs.getId() ) );
final ExportMipmapInfo mipmapInfo = perSetupExportMipmapInfo.get( vs.getId() );
final boolean writeMipmapInfo = true; // TODO: remember whether we already wrote it and write only once
final boolean deflate = params.getDeflate();
final ProgressWriter progressWriter = new SubTaskProgressWriter( this.progressWriter, 0.0, 1.0 ); // TODO
final int numThreads = Math.max( 1, Runtime.getRuntime().availableProcessors() - 2 );
WriteSequenceToHdf5.writeViewToHdf5PartitionFile( ushortimg, partition, tp.getId(), vs.getId(), mipmapInfo, writeMipmapInfo, deflate, null, null, numThreads, progressWriter );
// update the registrations
final ViewRegistration vr = spimData.getViewRegistrations().getViewRegistration( new ViewId( tp.getId(), vs.getId() ) );
final double scale = bb.getDownSampling();
final AffineTransform3D m = new AffineTransform3D();
m.set( scale, 0.0f, 0.0f, bb.min( 0 ),
0.0f, scale, 0.0f, bb.min( 1 ),
0.0f, 0.0f, scale, bb.min( 2 ) );
final ViewTransform vt = new ViewTransformAffine( "fusion bounding box", m );
vr.getTransformList().clear();
vr.getTransformList().add( vt );
return true;
}
@Override
public void queryAdditionalParameters( GenericDialog gd, SpimData2 spimData )
{
System.out.println( "queryAdditionalParameters()" );
}
@Override
public boolean parseAdditionalParameters( GenericDialog gd, SpimData2 spimData )
{
System.out.println( "parseAdditionalParameters()" );
return true;
}
@Override
public ImgExport newInstance()
{
System.out.println( "newInstance()" );
BoundingBoxGUI.defaultPixelType = 1; // set to 16 bit by default
return new ExportSpimData2HDF5();
}
@Override
public String getDescription()
{
System.out.println( "getDescription()" );
return "Save as new XML Project (HDF5)";
}
}