/*-
* #%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.spimdata.imgloaders;
import ij.IJ;
import ij.ImagePlus;
import ij.io.Opener;
import ij.process.ImageProcessor;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.formats.ChannelSeparator;
import loci.formats.FormatTools;
import loci.formats.IFormatReader;
import loci.formats.meta.IMetadata;
import loci.formats.meta.MetadataRetrieve;
import loci.formats.services.OMEXMLService;
import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
import mpicbg.spim.data.generic.sequence.BasicViewDescription;
import mpicbg.spim.data.sequence.Channel;
import mpicbg.spim.data.sequence.ViewId;
import mpicbg.spim.io.IOFunctions;
import net.imglib2.Cursor;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.Img;
import net.imglib2.img.ImgFactory;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;
import ome.units.quantity.Length;
import spim.fiji.datasetmanager.StackListLOCI;
public class LegacyStackImgLoaderLOCI extends LegacyStackImgLoader
{
public LegacyStackImgLoaderLOCI(
final File path, final String fileNamePattern, final ImgFactory< ? extends NativeType< ? > > imgFactory,
final int layoutTP, final int layoutChannels, final int layoutIllum, final int layoutAngles,
final AbstractSequenceDescription< ?, ?, ? > sequenceDescription )
{
super( path, fileNamePattern, imgFactory, layoutTP, layoutChannels, layoutIllum, layoutAngles, sequenceDescription );
}
/**
* Get {@link FloatType} image normalized to the range [0,1].
*
* @param view
* timepoint and setup for which to retrieve the image.
* @param normalize
* if the image should be normalized to [0,1] or not
* @return {@link FloatType} image normalized to range [0,1]
*/
@Override
public RandomAccessibleInterval< FloatType > getFloatImage( final ViewId view, final boolean normalize )
{
final File file = getFile( view );
if ( file == null )
throw new RuntimeException( "Could not find file '" + file + "'." );
try
{
final CalibratedImg< FloatType > img = openLOCI( file, new FloatType(), view );
if ( img == null )
throw new RuntimeException( "Could not load '" + file + "'" );
if ( normalize )
{
float min = Float.MAX_VALUE;
float max = -Float.MAX_VALUE;
for ( final FloatType t : img.getImg() )
{
final float v = t.get();
if ( v < min )
min = v;
if ( v > max )
max = v;
}
for ( final FloatType t : img.getImg() )
t.set( ( t.get() - min ) / ( max - min ) );
}
// update the MetaDataCache of the AbstractImgLoader
// this does not update the XML ViewSetup but has to be called explicitly before saving
updateMetaDataCache( view, (int)img.getImg().dimension( 0 ), (int)img.getImg().dimension( 1 ), (int)img.getImg().dimension( 2 ),
img.getCalX(), img.getCalY(), img.getCalZ() );
return img.getImg();
}
catch ( Exception e )
{
throw new RuntimeException( "Could not load '" + file + "':\n" + e );
}
}
/**
* Get {@link UnsignedShortType} un-normalized image.
*
* @param view
* timepoint and setup for which to retrieve the image.
* @return {@link UnsignedShortType} image.
*/
@Override
public RandomAccessibleInterval< UnsignedShortType > getImage( final ViewId view )
{
final File file = getFile( view );
if ( file == null )
throw new RuntimeException( "Could not find file '" + file + "'." );
try
{
final CalibratedImg< UnsignedShortType > img = openLOCI( file, new UnsignedShortType(), view );
if ( img == null )
throw new RuntimeException( "Could not load '" + file + "'" );
// update the MetaDataCache of the AbstractImgLoader
// this does not update the XML ViewSetup but has to be called explicitly before saving
updateMetaDataCache( view, (int)img.getImg().dimension( 0 ), (int)img.getImg().dimension( 1 ), (int)img.getImg().dimension( 2 ),
img.getCalX(), img.getCalY(), img.getCalZ() );
return img.getImg();
}
catch ( Exception e )
{
e.printStackTrace();
throw new RuntimeException( "Could not load '" + file + "': " + e );
}
}
protected < T extends RealType< T > & NativeType< T > > CalibratedImg< T > openLOCI( final File path, final T type, final ViewId view ) throws Exception
{
BasicViewDescription< ? > viewDescription = sequenceDescription.getViewDescriptions().get( view );
// read many 2d-images if it is a directory
if ( path.isDirectory() )
{
final String[] files = path.list( new FilenameFilter()
{
@Override
public boolean accept( final File dir, final String name)
{
final File newFile = new File( dir, name );
// ignore directories and hidden files
if ( newFile.isHidden() || newFile.isDirectory() )
return false;
else
return true;
}
});
Arrays.sort( files );
final int depth = files.length;
// get size of first image
final Opener io = new Opener();
ImagePlus imp2d = io.openImage( path.getAbsolutePath() + File.separator + files[ 0 ] );
if ( imp2d.getStack().getSize() > 1 )
{
IOFunctions.printlnSafe( "This is not a two-dimensional file: '" + path + "'" );
imp2d.close();
return null;
}
final Img< T > output = instantiateImg( new long[] { imp2d.getWidth(), imp2d.getHeight(), depth }, type );
if ( output == null )
throw new RuntimeException( "Could not instantiate " + getImgFactory().getClass().getSimpleName() + " for '" + path + "', most likely out of memory." );
IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Opening '" + path + "' [" + imp2d.getWidth() + "x" + imp2d.getHeight() + "x" + depth + " type=" +
imp2d.getProcessor().getClass().getSimpleName() + " image=" + output.getClass().getSimpleName() + "<" + type.getClass().getSimpleName() + ">]" );
for ( int z = 0; z < depth; ++z )
{
imp2d = io.openImage( path.getAbsolutePath() + File.separator + files[ z ] );
final ImageProcessor ip = imp2d.getProcessor();
final Cursor< T > cursor = Views.iterable( Views.hyperSlice( output, 2, z ) ).localizingCursor();
while ( cursor.hasNext() )
{
cursor.fwd();
cursor.get().setReal( ip.getPixelValue( cursor.getIntPosition( 0 ), cursor.getIntPosition( 1 ) ) );
}
}
return new CalibratedImg<T>( output );
}
final IFormatReader r = new ChannelSeparator();
if ( !createOMEXMLMetadata( r ) )
{
try
{
r.close();
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
final String id = path.getAbsolutePath();
r.setId( id );
final boolean isLittleEndian = r.isLittleEndian();
final int width = r.getSizeX();
final int height = r.getSizeY();
final int depth = r.getSizeZ();
int timepoints = r.getSizeT();
int channels = r.getSizeC();
final int pixelType = r.getPixelType();
final int bytesPerPixel = FormatTools.getBytesPerPixel( pixelType );
final String pixelTypeString = FormatTools.getPixelTypeString( pixelType );
final MetadataRetrieve retrieve = (MetadataRetrieve)r.getMetadataStore();
double calX, calY, calZ;
try
{
float cal = retrieve.getPixelsPhysicalSizeX( 0 ).value().floatValue();
if ( cal == 0 )
{
cal = 1;
IOFunctions.printlnSafe( "StackListLOCI: Warning, calibration for dimension X seems corrupted, setting to 1." );
}
calX = cal;
cal = retrieve.getPixelsPhysicalSizeY( 0 ).value().floatValue();
if ( cal == 0 )
{
cal = 1;
IOFunctions.printlnSafe( "StackListLOCI: Warning, calibration for dimension Y seems corrupted, setting to 1." );
}
calY = cal;
cal = retrieve.getPixelsPhysicalSizeZ( 0 ).value().floatValue();
if ( cal == 0 )
{
cal = 1;
IOFunctions.printlnSafe( "StackListLOCI: Warning, calibration for dimension Z seems corrupted, setting to 1." );
}
calZ = cal;
}
catch ( Exception e )
{
IOFunctions.printlnSafe( "Failed to read calibration, setting to 1x1x1um." );
calX = calY = calZ = 1;
}
// which channel and timepoint to load from this file
int t = 0;
int c = 0;
if ( layoutTP == 2 )
{
t = Integer.parseInt( viewDescription.getTimePoint().getName() );
if ( t >= timepoints )
{
r.close();
throw new RuntimeException( "File '" + path + "' has only timepoints [0 ... " + (timepoints-1) + "], but you want to open timepoint " + t + ". Stopping.");
}
}
if ( layoutChannels == 2 )
{
c = Integer.parseInt( viewDescription.getViewSetup().getAttribute( Channel.class ).getName() );
if ( c >= channels )
{
r.close();
throw new RuntimeException( "File '" + path + "' has only channels [0 ... " + (channels-1) + "], but you want to open channel " + c + ". Stopping.");
}
}
if (!(pixelType == FormatTools.UINT8 || pixelType == FormatTools.UINT16 || pixelType == FormatTools.UINT32 || pixelType == FormatTools.FLOAT))
{
IOFunctions.printlnSafe( "StackImgLoaderLOCI.openLOCI(): PixelType " + pixelTypeString + " not supported by " +
type.getClass().getSimpleName() + ", returning. ");
r.close();
return null;
}
final Img< T > img;
img = instantiateImg( new long[] { width, height, depth }, type );
if ( img == null )
{
r.close();
throw new RuntimeException( "Could not instantiate " + getImgFactory().getClass().getSimpleName() + " for '" + path + "', most likely out of memory." );
}
else
IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Opening '" + path + "' [" + width + "x" + height + "x" + depth + " ch=" + c + " tp=" + t + " type=" + pixelTypeString + " image=" + img.getClass().getSimpleName() + "<" + type.getClass().getSimpleName() + ">]" );
final byte[] b = new byte[width * height * bytesPerPixel];
final int planeX = 0;
final int planeY = 1;
for ( int z = 0; z < depth; ++z )
{
IJ.showProgress( (double)z / (double)depth );
final Cursor< T > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor();
r.openBytes( r.getIndex( z, c, t ), b );
if ( pixelType == FormatTools.UINT8 )
{
while( cursor.hasNext() )
{
cursor.fwd();
cursor.get().setReal( b[ cursor.getIntPosition( planeX )+ cursor.getIntPosition( planeY )*width ] & 0xff );
}
}
else if ( pixelType == FormatTools.UINT16 )
{
while( cursor.hasNext() )
{
cursor.fwd();
cursor.get().setReal( getShortValueInt( b, ( cursor.getIntPosition( planeX )+ cursor.getIntPosition( planeY )*width ) * 2, isLittleEndian ) );
}
}
else if ( pixelType == FormatTools.INT16 )
{
while( cursor.hasNext() )
{
cursor.fwd();
cursor.get().setReal( getShortValue( b, ( cursor.getIntPosition( planeX )+ cursor.getIntPosition( planeY )*width ) * 2, isLittleEndian ) );
}
}
else if ( pixelType == FormatTools.UINT32 )
{
//TODO: Untested
while( cursor.hasNext() )
{
cursor.fwd();
cursor.get().setReal( getIntValue( b, ( cursor.getIntPosition( planeX )+ cursor.getIntPosition( planeY )*width )*4, isLittleEndian ) );
}
}
else if ( pixelType == FormatTools.FLOAT )
{
while( cursor.hasNext() )
{
cursor.fwd();
cursor.get().setReal( getFloatValue( b, ( cursor.getIntPosition( planeX )+ cursor.getIntPosition( planeY )*width )*4, isLittleEndian ) );
}
}
}
r.close();
IJ.showProgress( 1 );
return new CalibratedImg<T>( img, calX, calY, calZ );
}
protected static final float getFloatValue( final byte[] b, final int i, final boolean isLittleEndian )
{
if ( isLittleEndian )
return Float.intBitsToFloat( ((b[i+3] & 0xff) << 24) + ((b[i+2] & 0xff) << 16) + ((b[i+1] & 0xff) << 8) + (b[i] & 0xff) );
else
return Float.intBitsToFloat( ((b[i] & 0xff) << 24) + ((b[i+1] & 0xff) << 16) + ((b[i+2] & 0xff) << 8) + (b[i+3] & 0xff) );
}
protected static final int getIntValue( final byte[] b, final int i, final boolean isLittleEndian )
{
if ( isLittleEndian )
return ( ((b[i+3] & 0xff) << 24) + ((b[i+2] & 0xff) << 16) + ((b[i+1] & 0xff) << 8) + (b[i] & 0xff) );
else
return ( ((b[i] & 0xff) << 24) + ((b[i+1] & 0xff) << 16) + ((b[i+2] & 0xff) << 8) + (b[i+3] & 0xff) );
}
protected static final short getShortValue( final byte[] b, final int i, final boolean isLittleEndian )
{
return (short)getShortValueInt( b, i, isLittleEndian );
}
protected static final int getShortValueInt( final byte[] b, final int i, final boolean isLittleEndian )
{
if ( isLittleEndian )
return ((((b[i+1] & 0xff) << 8)) + (b[i] & 0xff));
else
return ((((b[i] & 0xff) << 8)) + (b[i+1] & 0xff));
}
protected static String checkPath( String path )
{
if (path.length() > 1)
{
path = path.replace('\\', '/');
if (!path.endsWith("/"))
path = path + "/";
}
return path;
}
public static boolean createOMEXMLMetadata( final IFormatReader r )
{
try
{
final ServiceFactory serviceFactory = new ServiceFactory();
final OMEXMLService service = serviceFactory.getInstance( OMEXMLService.class );
final IMetadata omexmlMeta = service.createOMEXMLMetadata();
r.setMetadataStore(omexmlMeta);
}
catch (final ServiceException e)
{
e.printStackTrace();
return false;
}
catch (final DependencyException e)
{
e.printStackTrace();
return false;
}
return true;
}
@Override
protected void loadMetaData( final ViewId view )
{
final File file = getFile( view );
final Calibration cal = loadMetaData( file );
if ( cal != null )
{
// update the MetaDataCache of the AbstractImgLoader
// this does not update the XML ViewSetup but has to be called explicitly before saving
updateMetaDataCache( view, cal.getWidth(), cal.getHeight(), cal.getDepth(),
cal.getCalX(), cal.getCalY(), cal.getCalZ() );
}
}
public static Calibration loadMetaData( final File file )
{
final IFormatReader r = new ChannelSeparator();
if ( !LegacyStackImgLoaderLOCI.createOMEXMLMetadata( r ) )
{
try
{
r.close();
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
try
{
r.setId( file.getAbsolutePath() );
final MetadataRetrieve retrieve = (MetadataRetrieve)r.getMetadataStore();
float cal = 0;
Length f = retrieve.getPixelsPhysicalSizeX( 0 );
if ( f != null )
cal = f.value().floatValue();
if ( cal == 0 )
{
cal = 1;
IOFunctions.printlnSafe( "StackListLOCI: Warning, calibration for dimension X seems corrupted, setting to 1." );
}
final double calX = cal;
f = retrieve.getPixelsPhysicalSizeY( 0 );
if ( f != null )
cal = f.value().floatValue();
if ( cal == 0 )
{
cal = 1;
IOFunctions.printlnSafe( "StackListLOCI: Warning, calibration for dimension Y seems corrupted, setting to 1." );
}
final double calY = cal;
f = retrieve.getPixelsPhysicalSizeZ( 0 );
if ( f != null )
cal = f.value().floatValue();
if ( cal == 0 )
{
cal = 1;
IOFunctions.printlnSafe( "StackListLOCI: Warning, calibration for dimension Z seems corrupted, setting to 1." );
}
final double calZ = cal;
IOFunctions.printlnSafe( "Image stack size of first stack: " + r.getSizeX() + "x" + r.getSizeY() + "x" + r.getSizeZ() );
final Calibration calibration = new Calibration( r.getSizeX(), r.getSizeY(), r.getSizeZ(), calX, calY, calZ );
r.close();
return calibration;
}
catch ( Exception e)
{
IOFunctions.printlnSafe( "Could not open file: '" + file.getAbsolutePath() + "'" );
e.printStackTrace();
return null;
}
}
@Override
public String toString()
{
return new StackListLOCI().getTitle() + ", ImgFactory=" + imgFactory.getClass().getSimpleName();
}
}