included match intensities plugin, build with SNAPSHOTS for testing
[trakem2.git] / TrakEM2_ / src / main / java / org / janelia / intensity / Render.java
blob9958fae78e5ab5a8585ba33e475053c33339c1c0
1 /**
2 * License: GPL
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License 2
6 * as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 package org.janelia.intensity;
19 import ij.ImageJ;
20 import ij.ImagePlus;
21 import ij.ImageStack;
22 import ij.process.ByteProcessor;
23 import ij.process.ColorProcessor;
24 import ij.process.FloatProcessor;
25 import ij.process.ImageProcessor;
26 import ini.trakem2.Project;
27 import ini.trakem2.display.Layer;
28 import ini.trakem2.display.Patch;
30 import java.awt.Rectangle;
31 import java.awt.image.BufferedImage;
32 import java.awt.image.WritableRaster;
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.ArrayList;
37 import javax.imageio.ImageIO;
39 import mpicbg.ij.TransformMeshMapping;
40 import mpicbg.models.AffineModel2D;
41 import mpicbg.models.CoordinateTransform;
42 import mpicbg.models.CoordinateTransformList;
43 import mpicbg.models.CoordinateTransformMesh;
44 import mpicbg.models.NotEnoughDataPointsException;
45 import mpicbg.models.Point;
46 import mpicbg.models.PointMatch;
47 import mpicbg.models.SimilarityModel2D;
48 import mpicbg.models.TransformMesh;
49 import mpicbg.trakem2.transform.TransformMeshMappingWithMasks;
50 import mpicbg.trakem2.transform.TransformMeshMappingWithMasks.ImageProcessorWithMasks;
51 import mpicbg.trakem2.util.Downsampler;
53 /**
54 * Render a patch.
56 * @author Stephan Saalfeld <saalfelds@janelia.hhmi.org>
58 public class Render
60 private Render() {}
62 /**
63 * Create a {@link BufferedImage} from an existing pixel array. Make sure
64 * that pixels.length == width * height.
66 * @param pixels
67 * @param width
68 * @param height
70 * @return BufferedImage
72 final static public BufferedImage createARGBImage( final int[] pixels, final int width, final int height )
74 assert( pixels.length == width * height ) : "The number of pixels is not equal to width * height.";
76 final BufferedImage image = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB );
77 final WritableRaster raster = image.getRaster();
78 raster.setDataElements( 0, 0, width, height, pixels );
79 return image;
83 final static void saveImage( final BufferedImage image, final String path, final String format ) throws IOException
85 ImageIO.write( image, format, new File( path ) );
88 /**
89 * Sample the average scaling of a given {@link CoordinateTransform} by transferring
90 * a set of point samples using the {@link CoordinateTransform} and then
91 * least-squares fitting a {@link SimilarityModel2D} to it.
93 * @param ct
94 * @param width of the samples set
95 * @param height of the samples set
96 * @param dx spacing between samples
98 * @return average scale factor
100 final static protected double sampleAverageScale( final CoordinateTransform ct, final int width, final int height, final double dx )
102 final ArrayList< PointMatch > samples = new ArrayList< PointMatch >();
103 for ( double y = 0; y < height; y += dx )
105 for ( double x = 0; x < width; x += dx )
107 final Point p = new Point( new double[]{ x, y } );
108 p.apply( ct );
109 samples.add( new PointMatch( p, p ) );
112 final SimilarityModel2D model = new SimilarityModel2D();
115 model.fit( samples );
117 catch ( final NotEnoughDataPointsException e )
119 e.printStackTrace( System.err );
120 return 1;
122 final double[] data = new double[ 6 ];
123 model.toArray( data );
124 return Math.sqrt( data[ 0 ] * data[ 0 ] + data[ 1 ] * data[ 1 ] );
128 final static protected int bestMipmapLevel( final double scale )
130 int invScale = ( int )( 1.0 / scale );
131 int scaleLevel = 0;
132 while ( invScale > 1 )
134 invScale >>= 1;
135 ++scaleLevel;
137 return scaleLevel;
141 * Create an affine transformation that compensates for both scale and
142 * pixel shift of a mipmap level that was generated by top-left pixel
143 * averaging.
145 * @param scaleLevel
146 * @return
148 final static protected AffineModel2D createScaleLevelTransform( final int scaleLevel )
150 final AffineModel2D a = new AffineModel2D();
151 final int scale = 1 << scaleLevel;
152 final double t = ( scale - 1 ) * 0.5;
153 a.set( scale, 0, 0, scale, t, t );
154 return a;
158 * Renders a patch, mapping its intensities [min, max] &rarr; [0, 1]
160 * @param patch the patch to be rendered
161 * @param targetImage target pixels, specifies the target box
162 * @param targetWeight target weight pixels, depending on alpha
163 * @param x target box offset in world coordinates
164 * @param y target box offset in world coordinates
165 * @param scale target scale
167 final static public void render(
168 final Patch patch,
169 final int coefficientsWidth,
170 final int coefficientsHeight,
171 final FloatProcessor targetImage,
172 final FloatProcessor targetWeight,
173 final ColorProcessor targetCoefficients,
174 final double x,
175 final double y,
176 final double scale )
178 /* assemble coordinate transformations and add bounding box offset */
179 final CoordinateTransformList< CoordinateTransform > ctl = new CoordinateTransformList< CoordinateTransform >();
180 ctl.add( patch.getFullCoordinateTransform() );
181 final AffineModel2D affineScale = new AffineModel2D();
182 affineScale.set( scale, 0, 0, scale, -x * scale, -y * scale );
183 ctl.add( affineScale );
185 /* estimate average scale and generate downsampled source */
186 final int width = patch.getOWidth(), height = patch.getOHeight();
187 final double s = sampleAverageScale( ctl, width, height, width / patch.getMeshResolution() );
188 final int mipmapLevel = bestMipmapLevel( s );
189 final ImageProcessor ipMipmap = Downsampler.downsampleImageProcessor( patch.getImageProcessor(), mipmapLevel );
191 /* create a target */
192 final ImageProcessor tp = ipMipmap.createProcessor( targetImage.getWidth(), targetImage.getHeight() );
194 /* prepare and downsample alpha mask if there is one */
195 final ByteProcessor bpMaskMipmap;
196 final ByteProcessor bpMaskTarget;
198 final ByteProcessor bpMask = patch.getAlphaMask();
200 if ( bpMask == null )
202 bpMaskMipmap = null;
203 bpMaskTarget = null;
205 else
207 bpMaskMipmap = bpMask == null ? null : Downsampler.downsampleByteProcessor( bpMask, mipmapLevel );
208 bpMaskTarget = new ByteProcessor( tp.getWidth(), tp.getHeight() );
211 /* create coefficients map */
212 final ColorProcessor cp = new ColorProcessor( ipMipmap.getWidth(), ipMipmap.getHeight() );
213 final int w = cp.getWidth();
214 final int h = cp.getHeight();
215 for ( int yi = 0; yi < h; ++yi )
217 final int yc = yi * coefficientsHeight / h;
218 final int ic = yc * coefficientsWidth;
219 final int iyi = yi * w;
220 for ( int xi = 0; xi < w; ++xi )
221 cp.set( iyi + xi, ic + ( xi * coefficientsWidth / w ) + 1 );
224 /* attach mipmap transformation */
225 final CoordinateTransformList< CoordinateTransform > ctlMipmap = new CoordinateTransformList< CoordinateTransform >();
226 ctlMipmap.add( createScaleLevelTransform( mipmapLevel ) );
227 ctlMipmap.add( ctl );
229 /* create mesh */
230 final CoordinateTransformMesh mesh = new CoordinateTransformMesh( ctlMipmap, patch.getMeshResolution(), ipMipmap.getWidth(), ipMipmap.getHeight() );
232 /* render */
233 final ImageProcessorWithMasks source = new ImageProcessorWithMasks( ipMipmap, bpMaskMipmap, null );
234 final ImageProcessorWithMasks target = new ImageProcessorWithMasks( tp, bpMaskTarget, null );
235 final TransformMeshMappingWithMasks< TransformMesh > mapping = new TransformMeshMappingWithMasks< TransformMesh >( mesh );
236 mapping.mapInterpolated( source, target );
238 final TransformMeshMapping< TransformMesh > coefficientsMapMapping = new TransformMeshMapping< TransformMesh >( mesh );
239 coefficientsMapMapping.map( cp, targetCoefficients );
241 /* set alpha channel */
242 final byte[] alphaPixels;
244 if ( bpMaskTarget != null )
245 alphaPixels = ( byte[] )bpMaskTarget.getPixels();
246 else
247 alphaPixels = ( byte[] )target.outside.getPixels();
249 /* convert */
250 final double min = patch.getMin();
251 final double max = patch.getMax();
252 final double a = 1.0 / ( max - min );
253 final double b = 1.0 / 255.0;
255 for ( int i = 0; i < alphaPixels.length; ++i )
256 targetImage.setf( i, ( float )( ( tp.getf( i ) - min ) * a ) );
258 for ( int i = 0; i < alphaPixels.length; ++i )
259 targetWeight.setf( i, ( float )( ( alphaPixels[ i ] & 0xff ) * b ) );
262 final static public void main( final String... args )
264 new ImageJ();
266 final double scale = 0.05;
267 // final double scale = 1;
268 final int numCoefficients = 4;
270 final Project project = Project.openFSProject( "/home/saalfeld/tmp/bock-lens-correction/subproject.xml", false );
271 final Layer layer = project.getRootLayerSet().getLayer( 0 );
272 final ArrayList< Patch > patches = ( ArrayList )layer.getDisplayables( Patch.class );
274 final Patch patch1 = patches.get( 0 );
275 final Patch patch2 = patches.get( 2 );
276 final Rectangle box = patch1.getBoundingBox().intersection( patch2.getBoundingBox() );
277 //final Rectangle box = patch1.getBoundingBox();
279 final int w = ( int )( box.width * scale + 0.5 );
280 final int h = ( int )( box.height * scale + 0.5 );
282 final FloatProcessor pixels1 = new FloatProcessor( w, h );
283 final FloatProcessor weights1 = new FloatProcessor( w, h );
284 final ColorProcessor coefficients1 = new ColorProcessor( w, h );
285 final FloatProcessor pixels2 = new FloatProcessor( w, h );
286 final FloatProcessor weights2 = new FloatProcessor( w, h );
287 final ColorProcessor coefficients2 = new ColorProcessor( w, h );
289 render( patch1, numCoefficients, numCoefficients, pixels1, weights1, coefficients1, box.x, box.y, scale );
290 render( patch2, numCoefficients, numCoefficients, pixels2, weights2, coefficients2, box.x, box.y, scale );
292 final ImageStack stack = new ImageStack( w, h );
293 stack.addSlice( pixels1 );
294 stack.addSlice( pixels2 );
295 stack.addSlice( weights1 );
296 stack.addSlice( weights2 );
297 stack.addSlice( coefficients1.convertToFloatProcessor() );
298 stack.addSlice( coefficients2.convertToFloatProcessor() );
300 new ImagePlus( "", stack ).show();