/*- * #%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.ImagePlus; import ij.ImageStack; import ij.gui.GenericDialog; import ij.io.Opener; import ij.process.ImageProcessor; import java.io.File; import java.util.Date; import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription; import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.io.IOFunctions; import net.imglib2.Cursor; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.RealUnsignedShortConverter; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImg; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.img.planar.PlanarImg; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.integer.UnsignedShortType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.view.Views; import spim.fiji.datasetmanager.StackListImageJ; import spim.fiji.plugin.resave.Generic_Resave_HDF5; import spim.fiji.plugin.resave.Generic_Resave_HDF5.Parameters; import spim.fiji.plugin.util.GUIHelper; import spim.process.fusion.export.ExportSpimData2HDF5; public class LegacyStackImgLoaderIJ extends LegacyStackImgLoader { Parameters params = null; public LegacyStackImgLoaderIJ( 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 ); } public static ImagePlus open( File file ) { final ImagePlus imp = new Opener().openImage( file.getAbsolutePath() ); if ( imp == null ) { IOFunctions.printlnSafe( "Could not open file with ImageJ TIFF reader: '" + file.getAbsolutePath() + "'" ); return null; } return imp; } /** * 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 + "'." ); IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Loading '" + file + "' ..." ); final ImagePlus imp = open( file ); if ( imp == null ) throw new RuntimeException( "Could not load '" + file + "'." ); final long[] dim = new long[]{ imp.getWidth(), imp.getHeight(), imp.getStack().getSize() }; final Img< FloatType > img = this.instantiateImg( dim, new FloatType() ); if ( img == null ) throw new RuntimeException( "Could not instantiate " + getImgFactory().getClass().getSimpleName() + " for '" + file + "', most likely out of memory." ); else IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Opened '" + file + "' [" + dim[ 0 ] + "x" + dim[ 1 ] + "x" + dim[ 2 ] + " image=" + img.getClass().getSimpleName() + "<FloatType>]" ); imagePlus2ImgLib2Img( imp, img, normalize ); // update the MetaDataCache of the AbstractImgLoader // this does not update the XML ViewSetup but has to be called explicitly before saving updateMetaDataCache( view, imp.getWidth(), imp.getHeight(), imp.getStack().getSize(), imp.getCalibration().pixelWidth, imp.getCalibration().pixelHeight, imp.getCalibration().pixelDepth ); imp.close(); return img; } public static void imagePlus2ImgLib2Img( final ImagePlus imp, final Img< FloatType > img, final boolean normalize ) { final ImageStack stack = imp.getStack(); final int sizeZ = imp.getStack().getSize(); if ( img instanceof ArrayImg || img instanceof PlanarImg ) { final Cursor< FloatType > cursor = img.cursor(); final int sizeXY = imp.getWidth() * imp.getHeight(); if ( normalize ) { float min = Float.MAX_VALUE; float max = -Float.MAX_VALUE; for ( int z = 0; z < sizeZ; ++z ) { final ImageProcessor ip = stack.getProcessor( z + 1 ); for ( int i = 0; i < sizeXY; ++i ) { final float v = ip.getf( i ); if ( v < min ) min = v; if ( v > max ) max = v; cursor.next().set( v ); } } for ( final FloatType t : img ) t.set( ( t.get() - min ) / ( max - min ) ); } else { for ( int z = 0; z < sizeZ; ++z ) { final ImageProcessor ip = stack.getProcessor( z + 1 ); for ( int i = 0; i < sizeXY; ++i ) cursor.next().set( ip.getf( i ) ); } } } else { final int width = imp.getWidth(); if ( normalize ) { float min = Float.MAX_VALUE; float max = -Float.MAX_VALUE; for ( int z = 0; z < sizeZ; ++z ) { final Cursor< FloatType > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor(); final ImageProcessor ip = stack.getProcessor( z + 1 ); while ( cursor.hasNext() ) { cursor.fwd(); final float v = ip.getf( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ); if ( v < min ) min = v; if ( v > max ) max = v; cursor.get().set( v ); } } for ( final FloatType t : img ) t.set( ( t.get() - min ) / ( max - min ) ); } else { for ( int z = 0; z < sizeZ; ++z ) { final Cursor< FloatType > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor(); final ImageProcessor ip = stack.getProcessor( z + 1 ); while ( cursor.hasNext() ) { cursor.fwd(); cursor.get().set( ip.getf( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ) ); } } } } } /** * 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 + "'." ); IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Loading '" + file + "' ..." ); final ImagePlus imp = open( file ); if ( imp == null ) throw new RuntimeException( "Could not load '" + file + "'." ); final boolean is32bit; final RealUnsignedShortConverter< FloatType > converter; if ( imp.getType() == ImagePlus.GRAY32 ) { is32bit = true; IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Image '" + file + "' is 32bit, opening as 16bit with scaling" ); if ( params == null ) params = queryParameters(); if ( params == null ) return null; final double[] minmax = ExportSpimData2HDF5.updateAndGetMinMax( ImageJFunctions.wrapFloat( imp ), params ); converter = new RealUnsignedShortConverter< FloatType >( minmax[ 0 ], minmax[ 1 ] ); } else { is32bit = false; converter = null; } final long[] dim = new long[]{ imp.getWidth(), imp.getHeight(), imp.getStack().getSize() }; final Img< UnsignedShortType > img = instantiateImg( dim, new UnsignedShortType() ); if ( img == null ) throw new RuntimeException( "Could not instantiate " + getImgFactory().getClass().getSimpleName() + " for '" + file + "', most likely out of memory." ); else IOFunctions.printlnSafe( new Date( System.currentTimeMillis() ) + ": Opened '" + file + "' [" + dim[ 0 ] + "x" + dim[ 1 ] + "x" + dim[ 2 ] + " image=" + img.getClass().getSimpleName() + "<UnsignedShortType>]" ); final ImageStack stack = imp.getStack(); final int sizeZ = imp.getStack().getSize(); if ( img instanceof ArrayImg || img instanceof PlanarImg ) { final Cursor< UnsignedShortType > cursor = img.cursor(); final int sizeXY = imp.getWidth() * imp.getHeight(); for ( int z = 0; z < sizeZ; ++z ) { final ImageProcessor ip = stack.getProcessor( z + 1 ); if( is32bit ) { final FloatType input = new FloatType(); final UnsignedShortType output = new UnsignedShortType(); for ( int i = 0; i < sizeXY; ++i ) { input.set( ip.getf( i ) ); converter.convert( input, output ); cursor.next().set( output.get() ); } } else { for ( int i = 0; i < sizeXY; ++i ) cursor.next().set( ip.get( i ) ); } } } else { final int width = imp.getWidth(); for ( int z = 0; z < sizeZ; ++z ) { final Cursor< UnsignedShortType > cursor = Views.iterable( Views.hyperSlice( img, 2, z ) ).localizingCursor(); final ImageProcessor ip = stack.getProcessor( z + 1 ); if ( is32bit ) { final FloatType input = new FloatType(); final UnsignedShortType output = new UnsignedShortType(); while ( cursor.hasNext() ) { cursor.fwd(); input.set( ip.getf( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ) ); converter.convert( input, output ); cursor.get().set( output ); } } else { while ( cursor.hasNext() ) { cursor.fwd(); cursor.get().set( ip.get( cursor.getIntPosition( 0 ) + cursor.getIntPosition( 1 ) * width ) ); } } } } // update the MetaDataCache of the AbstractImgLoader // this does not update the XML ViewSetup but has to be called explicitly before saving updateMetaDataCache( view, imp.getWidth(), imp.getHeight(), imp.getStack().getSize(), imp.getCalibration().pixelWidth, imp.getCalibration().pixelHeight, imp.getCalibration().pixelDepth ); imp.close(); return img; } @Override protected void loadMetaData( final ViewId view ) { final File file = getFile( view ); final ImagePlus imp = open( file ); if ( imp == 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, imp.getWidth(), imp.getHeight(), imp.getStack().getSize(), imp.getCalibration().pixelWidth, imp.getCalibration().pixelHeight, imp.getCalibration().pixelDepth ); imp.close(); } @Override public String toString() { return new StackListImageJ().getTitle() + ", ImgFactory=" + imgFactory.getClass().getSimpleName(); } protected static Parameters queryParameters() { final GenericDialog gd = new GenericDialog( "Opening 32bit TIFF as 16bit" ); gd.addMessage( "You are trying to open 32-bit images as 16-bit (resaving as HDF5 maybe). Please define how to convert to 16bit.", GUIHelper.mediumstatusfont ); gd.addMessage( "Note: This dialog will only show up once for the first image.", GUIHelper.mediumstatusfont ); gd.addChoice( "Convert_32bit", Generic_Resave_HDF5.convertChoices, Generic_Resave_HDF5.convertChoices[ Generic_Resave_HDF5.defaultConvertChoice ] ); gd.showDialog(); if ( gd.wasCanceled() ) return null; Generic_Resave_HDF5.defaultConvertChoice = gd.getNextChoiceIndex(); if ( Generic_Resave_HDF5.defaultConvertChoice == 2 ) { if ( Double.isNaN( Generic_Resave_HDF5.defaultMin ) ) Generic_Resave_HDF5.defaultMin = 0; if ( Double.isNaN( Generic_Resave_HDF5.defaultMax ) ) Generic_Resave_HDF5.defaultMax = 5; final GenericDialog gdMinMax = new GenericDialog( "Define min/max" ); gdMinMax.addNumericField( "Min_Intensity_for_16bit_conversion", Generic_Resave_HDF5.defaultMin, 1 ); gdMinMax.addNumericField( "Max_Intensity_for_16bit_conversion", Generic_Resave_HDF5.defaultMax, 1 ); gdMinMax.addMessage( "Note: the typical range for multiview deconvolution is [0 ... 10] & for fusion the same as the input intensities., ",GUIHelper.mediumstatusfont ); gdMinMax.showDialog(); if ( gdMinMax.wasCanceled() ) return null; Generic_Resave_HDF5.defaultMin = gdMinMax.getNextNumber(); Generic_Resave_HDF5.defaultMax = gdMinMax.getNextNumber(); } else { Generic_Resave_HDF5.defaultMin = Generic_Resave_HDF5.defaultMax = Double.NaN; } return new Parameters( false, null, null, null, null, false, false, 0, 0, false, 0, Generic_Resave_HDF5.defaultConvertChoice, Generic_Resave_HDF5.defaultMin, Generic_Resave_HDF5.defaultMax ); } }