package mpicbg.trakem2.transform; import ij.ImagePlus; import ij.ImageStack; import ij.process.ByteProcessor; import ij.process.ImageProcessor; import ij.process.ShortProcessor; import ini.trakem2.display.Displayable; import ini.trakem2.display.Layer; import ini.trakem2.display.Patch; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import mpicbg.models.CoordinateTransform; import mpicbg.models.CoordinateTransformList; import mpicbg.models.CoordinateTransformMesh; import mpicbg.models.TranslationModel2D; import mpicbg.trakem2.util.Triple; public class ExportUnsignedShort { static protected class PatchIntensityRange { final public Patch patch; final public double a, min, max; PatchIntensityRange( final Patch patch ) { this.patch = patch; a = patch.getMax() - patch.getMin(); final ImageProcessor ip = patch.getImageProcessor(); ip.resetMinAndMax(); min = ( ip.getMin() - patch.getMin() ) / a; max = ( ip.getMax() - patch.getMin() ) / a; ip.setMinAndMax( patch.getMin(), patch.getMax() ); } } static protected class PatchTransform { final PatchIntensityRange pir; final CoordinateTransform ct; PatchTransform( final PatchIntensityRange pir ) { this.pir = pir; ct = pir.patch.getFullCoordinateTransform(); } } final static protected ShortProcessor mapIntensities( final PatchIntensityRange pir, final double min, final double max ) { final double a = 65535.0 / ( max - min ); final ImageProcessor source = pir.patch.getImageProcessor(); final short[] targetPixels = new short[ source.getWidth() * source.getHeight() ]; for ( int i = 0; i < targetPixels.length; ++i ) { targetPixels[ i ] = ( short )Math.max( 0, Math.min( 65535, Math.round( ( ( source.getf( i ) - pir.patch.getMin() ) / pir.a - min ) * a ) ) ); } final ShortProcessor target = new ShortProcessor( source.getWidth(), source.getHeight(), targetPixels, null ); target.setMinAndMax( -min * a, ( 1.0 - min ) * a ); return target; } final static protected void map( final PatchTransform pt, final double x, final double y, final ShortProcessor mappedIntensities, final ShortProcessor target ) { final TranslationModel2D t = new TranslationModel2D(); t.set( -x, -y ); final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >(); ctl.add( pt.ct ); ctl.add( t ); final CoordinateTransformMesh mesh = new CoordinateTransformMesh( ctl, pt.pir.patch.getMeshResolution(), pt.pir.patch.getOWidth(), pt.pir.patch.getOHeight() ); final TransformMeshMappingWithMasks< CoordinateTransformMesh > mapping = new TransformMeshMappingWithMasks< CoordinateTransformMesh >( mesh ); mappedIntensities.setInterpolationMethod( ImageProcessor.BILINEAR ); if ( pt.pir.patch.hasAlphaMask() ) { final ByteProcessor alpha = pt.pir.patch.getAlphaMask(); alpha.setInterpolationMethod( ImageProcessor.BILINEAR ); mapping.map( mappedIntensities, alpha, target ); } else { mapping.mapInterpolated( mappedIntensities, target ); } } final static public void exportTEST( final Layer layer, final int tileWidth, final int tileHeight ) { /* calculate intensity transfer */ final ArrayList< Displayable > patches = layer.getDisplayables( Patch.class ); final ArrayList< PatchIntensityRange > patchIntensityRanges = new ArrayList< PatchIntensityRange >(); double min = Double.MAX_VALUE; double max = -Double.MAX_VALUE; for ( final Displayable d : patches ) { final Patch patch = ( Patch )d; final PatchIntensityRange pir = new PatchIntensityRange( patch ); if ( pir.min < min ) min = pir.min; if ( pir.max > max ) max = pir.max; patchIntensityRanges.add( pir ); } /* render tiles */ /* TODO Do not render them into a stack but save them as files */ final ImageStack stack = new ImageStack( tileWidth, tileHeight ); ImagePlus imp = null; final double minI = -min * 65535.0 / ( max - min ); final double maxI = ( 1.0 - min ) * 65535.0 / ( max - min ); //ij.IJ.log("min, max: " + min + ", " + max + ", minI, maxI: " + minI + ", " + maxI); final int nc = ( int )Math.ceil( layer.getLayerWidth() / tileWidth ); final int nr = ( int )Math.ceil( layer.getLayerHeight() / tileHeight ); for ( int r = 0; r < nr; ++r ) { final int y0 = r * tileHeight; for ( int c = 0; c < nc; ++c ) { final int x0 = c * tileWidth; final Rectangle box = new Rectangle( x0, y0, tileWidth, tileHeight ); final ShortProcessor sp = new ShortProcessor( tileWidth, tileHeight ); sp.setMinAndMax( minI, maxI ); for ( final PatchIntensityRange pir : patchIntensityRanges ) { if ( pir.patch.getBoundingBox().intersects( box ) ) map( new PatchTransform( pir ), x0, y0, mapIntensities( pir, min, max ), sp ); } stack.addSlice( r + ", " + c , sp ); if ( null == imp && stack.getSize() > 1 ) { imp = new ImagePlus( "tiles", stack ); imp.show(); } if (null != imp) { imp.setSlice( stack.getSize() ); imp.updateAndDraw(); } } } if (null == imp) { new ImagePlus( "tiles", stack ).show(); // single-slice, non-StackWindow } } /** Create constant size tiles that carpet the areas of the {@code layer} where there are images; * these tiles are returned in a lazy sequence of {@link Callable} objects that create a tripled * consisting of the {@link ShortProcessor} and the X and Y pixel coordinates of that tile. * * @param layer The layer to export images for * @param tileWidth The width of the tiles to export * @param tileHeight * @return A lazy sequence of {@link Callable} instances, each holding a {@link Triple} that specifies the ShortProcessor, * the X and the Y (both in world pixel uncalibrated coordinates). */ final static public Iterable<Callable<ExportedTile>> exportTiles( final Layer layer, final int tileWidth, final int tileHeight, final boolean visible_only ) { final ArrayList< Displayable > patches = layer.getDisplayables( Patch.class, visible_only ); // If the Layer lacks images, return an empty sequence. if ( patches.isEmpty() ) { return Collections.emptyList(); } /* calculate intensity transfer */ final ArrayList< PatchIntensityRange > patchIntensityRanges = new ArrayList< PatchIntensityRange >(); double min_ = Double.MAX_VALUE; double max_ = -Double.MAX_VALUE; for ( final Displayable d : patches ) { final Patch patch = ( Patch )d; final PatchIntensityRange pir = new PatchIntensityRange( patch ); if ( pir.min < min_ ) min_ = pir.min; if ( pir.max > max_ ) max_ = pir.max; patchIntensityRanges.add( pir ); } final double min = min_; final double max = max_; /* Create lazy sequence that creates Callable instances. */ final Rectangle box = layer.getMinimalBoundingBox( Patch.class, visible_only ) .intersection( layer.getParent().get2DBounds() ); final int nCols = ( int )Math.ceil( box.width / (double)tileWidth ); final int nRows = ( int )Math.ceil( box.height / (double)tileHeight ); final double minI = -min * 65535.0 / ( max - min ); final double maxI = ( 1.0 - min ) * 65535.0 / ( max - min ); //ij.IJ.log("min, max: " + min + ", " + max + ", minI, maxI: " + minI + ", " + maxI); return new Iterable<Callable<ExportedTile>>() { @Override public Iterator<Callable<ExportedTile>> iterator() { return new Iterator<Callable<ExportedTile>>() { // Internal state private int row = 0, col = 0, x0 = box.x, y0 = box.y; private final ArrayList< PatchIntensityRange > ps = new ArrayList< PatchIntensityRange >(); { // Constructor body. Prepare to be able to answer "hasNext()" findNext(); } private final void findNext() { // Iterate until finding a tile that intersects one or more patches ps.clear(); while (true) { if (nRows == row) { // End of domain break; } x0 = box.x + col * tileWidth; y0 = box.y + row * tileHeight; final Rectangle tileBounds = new Rectangle( x0, y0, tileWidth, tileHeight ); for ( final PatchIntensityRange pir : patchIntensityRanges ) { if ( pir.patch.getBoundingBox().intersects( tileBounds ) ) { ps.add( pir ); } } // Prepare next iteration col += 1; if (nCols == col) { col = 0; row += 1; } if ( ps.size() > 0 ) { // Ready for next iteration break; } } } @Override public boolean hasNext() { return ps.size() > 0; } @Override public Callable<ExportedTile> next() { // Capture state locally final ArrayList< PatchIntensityRange > pirs = new ArrayList< PatchIntensityRange >( ps ); final int x = x0; final int y = y0; // Advance findNext(); return new Callable<ExportedTile>() { @Override public ExportedTile call() throws Exception { final ShortProcessor sp = new ShortProcessor( tileWidth, tileHeight ); sp.setMinAndMax( minI, maxI ); for ( final PatchIntensityRange pir : pirs ) { map( new PatchTransform( pir ), x, y, mapIntensities( pir, min, max ), sp ); } return new ExportedTile( sp, x, y, minI, maxI ); } }; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** Create a flat image into which Patch instances are transferred considering their min and max values. * * @param patches * @param roi * @return */ static public final ShortProcessor makeFlatImage(final List<Patch> patches, final Rectangle roi) { return makeFlatImage(patches, roi, 0); } static public final ShortProcessor makeFlatImage(final List<Patch> patches, final Rectangle roi, final double backgroundValue) { final ArrayList< PatchIntensityRange > patchIntensityRanges = new ArrayList< PatchIntensityRange >(); double min = Double.MAX_VALUE; double max = -Double.MAX_VALUE; for ( final Displayable d : patches ) { final Patch patch = ( Patch )d; final PatchIntensityRange pir = new PatchIntensityRange( patch ); if ( pir.min < min ) min = pir.min; if ( pir.max > max ) max = pir.max; patchIntensityRanges.add( pir ); } final double minI = -min * 65535.0 / ( max - min ); final double maxI = ( 1.0 - min ) * 65535.0 / ( max - min ); final ShortProcessor sp = new ShortProcessor( roi.width, roi.height ); sp.setMinAndMax( minI, maxI ); if (0 != backgroundValue) { sp.setValue(backgroundValue); sp.setRoi(0, 0, roi.width, roi.height); sp.fill(); } for ( final PatchIntensityRange pir : patchIntensityRanges ) { map( new PatchTransform( pir ), roi.x, roi.y, mapIntensities( pir, min, max ), sp ); } return sp; } /** * Returns a stack of ShortProcessor, with dimensions as in the {@code roi}. * @param layers * @param roi * @param backgroundValue * @return */ static public final ImageStack makeFlatImageStack(final List<Layer> layers, final Rectangle roi, final double backgroundValue) { final ImageStack stack = new ImageStack(roi.width, roi.height); for (final Layer layer : layers) { stack.addSlice("", makeFlatImage((List<Patch>)(List)layer.getDisplayables(Patch.class, true), roi, backgroundValue)); } return stack; } }