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
;
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
;
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
;
56 * @author Stephan Saalfeld <saalfelds@janelia.hhmi.org>
63 * Create a {@link BufferedImage} from an existing pixel array. Make sure
64 * that pixels.length == width * 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
);
83 final static void saveImage( final BufferedImage image
, final String path
, final String format
) throws IOException
85 ImageIO
.write( image
, format
, new File( path
) );
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.
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
} );
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
);
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
);
132 while ( invScale
> 1 )
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
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
);
158 * Renders a patch, mapping its intensities [min, max] → [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(
169 final int coefficientsWidth
,
170 final int coefficientsHeight
,
171 final FloatProcessor targetImage
,
172 final FloatProcessor targetWeight
,
173 final ColorProcessor targetCoefficients
,
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 )
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
);
230 final CoordinateTransformMesh mesh
= new CoordinateTransformMesh( ctlMipmap
, patch
.getMeshResolution(), ipMipmap
.getWidth(), ipMipmap
.getHeight() );
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();
247 alphaPixels
= ( byte[] )target
.outside
.getPixels();
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
)
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();