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.
18 package ini
.trakem2
.display
;
22 import ij
.process
.ImageProcessor
;
23 import ini
.trakem2
.Project
;
24 import ini
.trakem2
.utils
.Utils
;
25 import ini
.trakem2
.utils
.IJError
;
27 import java
.awt
.Color
;
28 import java
.awt
.Composite
;
29 import java
.awt
.Graphics2D
;
30 import java
.awt
.Image
;
31 import java
.awt
.Polygon
;
32 import java
.awt
.Rectangle
;
33 import java
.awt
.geom
.AffineTransform
;
34 import java
.awt
.geom
.Area
;
35 import java
.util
.ArrayList
;
36 import java
.util
.HashMap
;
37 import java
.util
.HashSet
;
38 import java
.util
.Iterator
;
40 import java
.util
.concurrent
.Callable
;
41 import java
.util
.concurrent
.Future
;
43 import mpicbg
.ij
.stack
.InverseTransformMapping
;
44 import mpicbg
.ij
.util
.Filter
;
45 import mpicbg
.models
.AffineModel2D
;
46 import mpicbg
.models
.AffineModel3D
;
47 import mpicbg
.models
.Boundable
;
48 import mpicbg
.models
.InvertibleCoordinateTransformList
;
49 import mpicbg
.models
.TranslationModel3D
;
50 import mpicbg
.trakem2
.transform
.InvertibleCoordinateTransform
;
51 import mpicbg
.util
.Util
;
54 * @author Stephan Saalfeld <saalfeld@mpi-cbg.de>
56 public class Stack
extends ZDisplayable
implements ImageData
58 private String file_path
;
59 private double depth
= -1;
62 private InvertibleCoordinateTransform ict
;
63 final private float[] boundsMin
= new float[]{ 0, 0, 0 };
64 final private float[] boundsMax
= new float[]{ 0, 0, 0 };
65 private double ictScale
= 1.0;
68 * References cached images in Loader whose unique identifier from the
69 * prespective of the stack are double:magnification and double:z.
71 final private HashMap
< SliceViewKey
, Long
> cachedImages
= new HashMap
< SliceViewKey
, Long
>();
73 final private HashMap
< Long
, Future
< Image
> > futureImages
= new HashMap
< Long
, Future
<Image
> >();
75 private static class SliceViewKey
77 final double magnification
;
80 SliceViewKey( final double magnification
, final double z
)
82 this.magnification
= magnification
;
87 final public boolean equals( Object o
)
89 final SliceViewKey k
= ( SliceViewKey
)o
;
90 return k
.magnification
== magnification
&& k
.z
== z
;
94 final public int hashCode()
100 public Stack( final Project project
, final String title
, double x
, double y
, Layer initial_layer
, final String file_path
)
102 super( project
, title
, x
, y
);
103 this.file_path
= file_path
;
104 // ct ==> initial_layer;
105 final ImagePlus imp
= project
.getLoader().fetchImagePlus( this );
107 /* TODO scale regarding the Calibration and shift regarding x, y and the initial_layer */
108 depth
= imp
.getNSlices();
109 width
= imp
.getWidth();
110 height
= imp
.getHeight();
111 min
= imp
.getDisplayRangeMin();
112 max
= imp
.getDisplayRangeMax();
113 //at.translate( x, y ); // No need: the call to the super constructor already translated the affine transform.
117 boundsMin
[ 2 ] = ( float )initial_layer
.getZ();
119 boundsMax
[ 0 ] = ( float ) width
;
120 boundsMax
[ 1 ] = ( float ) height
;
121 boundsMax
[ 2 ] = ( float )( boundsMin
[ 2 ] + depth
);
126 /** For cloning purposes. */
127 private Stack(Project project
, long id
, String title
,
128 AffineTransform at
, double width
, double height
,
129 float alpha
, boolean visible
, Color color
, boolean locked
,
130 double depth
, double min
, double max
,
131 float[] boundsMin
, float[] boundsMax
,
132 InvertibleCoordinateTransform ict
, double ictScale
, String file_path
) {
133 super(project
, id
, title
, locked
, at
, 0, 0);
136 this.visible
= visible
;
138 this.boundsMin
[0] = boundsMin
[0];
139 this.boundsMin
[1] = boundsMin
[1];
140 this.boundsMin
[2] = boundsMin
[2];
141 this.boundsMax
[0] = boundsMax
[0];
142 this.boundsMax
[1] = boundsMax
[1];
143 this.boundsMax
[2] = boundsMax
[2];
145 this.height
= height
;
149 this.ict
= null == ict ?
null : this.ict
.clone();
150 this.file_path
= file_path
;
153 /** Construct a Stack from an XML entry. */
154 public Stack(Project project
, long id
, HashMap ht
, HashMap ht_links
) {
155 super(project
, id
, ht
, ht_links
);
156 // parse specific fields
158 final Iterator it
= ht
.entrySet().iterator();
159 while (it
.hasNext()) {
160 final Map
.Entry entry
= (Map
.Entry
)it
.next();
161 final String key
= (String
)entry
.getKey();
162 final String data
= (String
)entry
.getValue();
163 if (key
.equals("min")) {
164 this.min
= Double
.parseDouble(data
);
165 } else if (key
.equals("max")) {
166 this.max
= Double
.parseDouble(data
);
167 } else if (key
.equals("file_path")) {
168 this.file_path
= data
;
169 } else if (key
.equals("depth")) {
170 this.depth
= Double
.parseDouble(data
);
177 boundsMax
[ 0 ] = ( float )width
;
178 boundsMax
[ 1 ] = ( float )height
;
179 boundsMax
[ 2 ] = ( float )depth
;
182 public InvertibleCoordinateTransform
getInvertibleCoordinateTransform()
188 * @see ini.trakem2.display.ZDisplayable#getFirstLayer()
191 public Layer
getFirstLayer()
193 // TODO Auto-generated method stub
198 * @see ini.trakem2.display.ZDisplayable#intersects(java.awt.geom.Area, double, double)
201 public boolean intersects( Area area
, double z_first
, double z_last
)
203 // TODO Auto-generated method stub
208 * @see ini.trakem2.display.ZDisplayable#linkPatches()
211 public boolean linkPatches()
217 * @see ini.trakem2.display.Displayable#clone(ini.trakem2.Project, boolean)
220 public Displayable
clone( Project pr
, boolean copy_id
)
222 final long nid
= copy_id ?
this.id
: pr
.getLoader().getNextId();
223 final Stack copy
= new Stack( pr
, nid
, null != title ? title
.toString() : null,
224 new AffineTransform(at
), width
, height
,
225 alpha
, visible
, color
, locked
,
227 boundsMin
, boundsMax
,
228 ict
, ictScale
, file_path
);
229 // Copy references to cached images
230 copy
.cachedImages
.putAll(cachedImages
);
231 copy
.futureImages
.putAll(futureImages
);
232 copy
.addToDatabase();
237 * @see ini.trakem2.display.Displayable#isDeletable()
240 public boolean isDeletable()
242 return 0 == width
&& 0 == height
;
245 public String
getFilePath(){
246 return this.file_path
;
249 public long estimateImageFileSize()
252 return IJ
.maxMemory() / 2;
253 return (long) (width
* height
* depth
* 4);
257 * Estimate the scale of atp to apply the
258 * appropriate smoothing to the image.
260 final static private float estimateAffineScale( final AffineTransform atp
)
262 final float dxx
= ( float )atp
.getScaleX();
263 final float dxy
= ( float )atp
.getShearY();
264 final float dxs
= dxx
* dxx
+ dxy
* dxy
;
266 final float dyx
= ( float )atp
.getShearX();
267 final float dyy
= ( float )atp
.getScaleY();
268 final float dys
= dyx
* dyx
+ dyy
* dyy
;
270 return ( float )Math
.sqrt( Math
.max( dxs
, dys
) );
273 /** Slow paint: will wait until the image is generated and cached, then paint it. */
275 public void paint(final Graphics2D g
, final double magnification
, final boolean active
, final int channels
, final Layer active_layer
) {
277 Future
< Image
> fu
= null;
278 final SliceViewKey sliceViewKey
= new SliceViewKey( magnification
, active_layer
.getZ() );
279 synchronized ( cachedImages
)
282 Long imageIdL
= cachedImages
.get( sliceViewKey
);
283 if ( imageIdL
== null )
285 imageId
= project
.getLoader().getNextTempId();
286 cachedImages
.put( sliceViewKey
, imageId
);
290 /* fetch the image from cache---still, it may be that it is not there... */
292 image
= project
.getLoader().getCached( cachedImages
.get( sliceViewKey
), 0 );
296 /* image has to be generated */
297 fu
= fetchFutureImage( imageId
, magnification
, active_layer
, false ); // do not trigger repaint event
301 // Paint outside the synchronization block:
304 } else if (null != fu
) {
307 } catch (Throwable ie
) {
308 IJ
.log("Could not paint Stack " + this);
312 // If I put the fu.get() where image is, it fails to compile. I had to separate it!
313 paint(g
, image
); // will wait until present
315 Utils
.log2("Stack.paint ERROR: no image to paint!");
319 final private Future
< Image
> fetchFutureImage(final Long imageId
, final double magnification
, final Layer active_layer
, final boolean trigger_repaint_event
) {
320 synchronized ( futureImages
)
322 Future
< Image
> fu
= futureImages
.get( imageId
);
325 fu
= project
.getLoader().doLater( new Callable
< Image
>()
329 final InvertibleCoordinateTransformList
< mpicbg
.models
.InvertibleCoordinateTransform
> ictl
= new InvertibleCoordinateTransformList
< mpicbg
.models
.InvertibleCoordinateTransform
>();
332 // Utils.log2( "ictScale of " + getTitle() + " is " + ictScale );
335 /* Remove boundingBox shift ict ... */
336 final TranslationModel3D unShiftBounds
= new TranslationModel3D();
337 unShiftBounds
.set( -boundsMin
[ 0 ], -boundsMin
[ 1 ], 0 );
338 ictl
.add( unShiftBounds
);
340 if ( ictScale
!= 1.0 )
342 final AffineModel3D unScaleXY
= new AffineModel3D();
344 1.0f
/ ( float )ictScale
, 0, 0, 0,
345 0, 1.0f
/ ( float )ictScale
, 0, 0,
347 ictl
.add( unScaleXY
);
351 /* TODO remove that scale from ict and put it into atp */
353 final ImagePlus imp
= project
.getLoader().fetchImagePlus( Stack
.this );
354 final ImageProcessor ip
= imp
.getStack().getProcessor( 1 ).createProcessor( ( int )Math
.ceil( ( boundsMax
[ 0 ] - boundsMin
[ 0 ] ) / ictScale
), ( int )Math
.ceil( ( boundsMax
[ 1 ] - boundsMin
[ 1 ] ) / ictScale
) );
356 //Utils.log2( "ictScale is " + ictScale );
357 //Utils.log2( "rendering an image of " + ip.getWidth() + " x " + ip.getHeight() + " px" );
359 final double currentZ
= active_layer
.getZ();
361 final TranslationModel3D sliceShift
= new TranslationModel3D();
362 sliceShift
.set( 0, 0, ( float )-currentZ
);
363 ictl
.add( sliceShift
);
365 /* optimization: if ict is affine, reduce ictl into a single affine */
366 final InverseTransformMapping
< mpicbg
.models
.InvertibleCoordinateTransform
> mapping
;
367 if ( AffineModel3D
.class.isInstance( ict
) )
369 final AffineModel3D ictAffine
= new AffineModel3D();
370 boolean isAffine
= true;
371 for ( final mpicbg
.models
.InvertibleCoordinateTransform t
: ictl
.getList( null ) )
373 if ( AffineModel3D
.class.isInstance( t
) )
374 ictAffine
.preConcatenate( ( AffineModel3D
)t
);
375 else if ( TranslationModel3D
.class.isInstance( t
) )
376 ictAffine
.preConcatenate( ( TranslationModel3D
)t
);
384 mapping
= new InverseTransformMapping
< mpicbg
.models
.InvertibleCoordinateTransform
>( ictAffine
);
386 mapping
= new InverseTransformMapping
< mpicbg
.models
.InvertibleCoordinateTransform
>( ictl
);
389 mapping
= new InverseTransformMapping
< mpicbg
.models
.InvertibleCoordinateTransform
>( ictl
);
390 mapping
.mapInterpolated( imp
.getStack(), ip
);
392 final float s
= estimateAffineScale( new AffineTransform( at
) ); // wast: atp
394 final float smoothMag
= ( float )magnification
* s
* ( float )ictScale
;
395 if ( smoothMag
< 1.0f
)
397 Filter
.smoothForScale( ip
, smoothMag
, 0.5f
, 0.5f
);
400 final Image image
= ip
.createImage();
404 Utils
.log2( "Stack.paint: null image, returning" );
405 return null; // TEMPORARY from lazy
406 // repaints after closing a
410 project
.getLoader().cacheAWT( imageId
, image
);
412 synchronized ( futureImages
)
414 futureImages
.remove( imageId
);
417 if ( trigger_repaint_event
)
419 // Display.repaint( active_layer, Stack.this );
420 Display
.repaint( active_layer
);
427 // Utils.log2( "fu is not null" );
428 // // We don't do anything: we wait for itself to launch a
432 futureImages
.put( imageId
, fu
);
437 /** Will not paint but fork a task to create an image to paint later, when not already cached. */
439 public void prePaint(
441 final double magnification
,
442 final boolean active
,
444 final Layer active_layer
)
447 //final Image image = project.getLoader().fetchImage(this,0);
448 //Utils.log2("Patch " + id + " painted image " + image);
450 final double currentZ
= active_layer
.getZ();
452 synchronized ( cachedImages
)
454 final SliceViewKey sliceViewKey
= new SliceViewKey( magnification
, currentZ
);
456 Long imageIdL
= cachedImages
.get( sliceViewKey
);
457 if ( imageIdL
== null )
459 imageId
= project
.getLoader().getNextTempId();
460 cachedImages
.put( sliceViewKey
, imageId
);
464 /* fetch the image from cache---still, it may be that it is not there... */
466 image
= project
.getLoader().getCached( cachedImages
.get( sliceViewKey
), 0 );
470 /* image has to be generated */
471 fetchFutureImage( imageId
, magnification
, active_layer
, true );
476 if ( image
!= null) {
481 final private void paint( final Graphics2D g
, final Image image
)
483 final AffineTransform atp
= new AffineTransform( this.at
);
485 /* Put boundShift into atp */
486 final AffineTransform shiftBounds
= new AffineTransform( 1, 0, 0, 1, boundsMin
[ 0 ], boundsMin
[ 1 ] );
487 atp
.concatenate( shiftBounds
);
489 /* If available, incorporate the involved x,y-scale of ict in the AffineTransform */
490 final AffineTransform asict
= new AffineTransform( ictScale
, 0, 0, ictScale
, 0, 0 );
491 atp
.concatenate( asict
);
493 final Composite original_composite
= g
.getComposite();
494 // Fail gracefully for graphics cards that don't support custom composites, like ATI cards:
496 g
.setComposite( getComposite() );
497 g
.drawImage( image
, atp
, null );
498 } catch (Throwable t
) {
499 Utils
.log(new StringBuilder("Cannot paint Stack with composite type ").append(compositeModes
[getCompositeMode()]).append("\nReason:\n").append(t
.toString()).toString());
500 g
.setComposite( getComposite( COMPOSITE_NORMAL
) );
501 g
.drawImage( image
, atp
, null );
504 g
.setComposite( original_composite
);
507 static public final void exportDTD( final StringBuffer sb_header
, final HashSet hs
, final String indent
) {
508 String type
= "t2_stack";
509 if (hs
.contains(type
)) return;
511 sb_header
.append(indent
).append("<!ELEMENT t2_stack (").append(Displayable
.commonDTDChildren()).append(",(iict_transform|iict_transform_list)?)>\n");
512 Displayable
.exportDTD( type
, sb_header
, hs
, indent
);
513 sb_header
.append(indent
).append(TAG_ATTR1
).append(type
).append(" file_path CDATA #REQUIRED>\n")
514 .append(indent
).append(TAG_ATTR1
).append(type
).append(" depth CDATA #REQUIRED>\n");
517 /** Opens and closes the tag and exports data. The image is saved in the directory provided in @param any as a String. */
518 public void exportXML(StringBuffer sb_body
, String indent
, Object any
) { // TODO the Loader should handle the saving of images, not this class.
519 String in
= indent
+ "\t";
520 sb_body
.append(indent
).append("<t2_stack\n");
522 super.exportXML(sb_body
, in
, any
);
523 String
[] RGB
= Utils
.getHexRGBColor(color
);
525 sb_body
.append(in
).append("file_path=\"").append(file_path
).append("\"\n")
526 .append(in
).append("style=\"fill-opacity:").append(alpha
).append(";stroke:#").append(RGB
[0]).append(RGB
[1]).append(RGB
[2]).append(";\"\n")
527 .append(in
).append("depth=\"").append(depth
).append("\"\n")
528 .append(in
).append("min=\"").append(min
).append("\"\n")
529 .append(in
).append("max=\"").append(max
).append("\"\n")
532 sb_body
.append(indent
).append(">\n");
535 sb_body
.append(ict
.toXML(in
)).append('\n');
538 super.restXML(sb_body
, in
, any
);
540 sb_body
.append(indent
).append("</t2_stack>\n");
544 protected Rectangle
getBounds( final Rectangle rect
)
546 final AffineModel2D a
= new AffineModel2D();
549 final float[] rMin
= new float[]{ Float
.MAX_VALUE
, Float
.MAX_VALUE
};
550 final float[] rMax
= new float[]{ -Float
.MAX_VALUE
, -Float
.MAX_VALUE
};
552 final float[] l
= new float[]{ boundsMin
[ 0 ], boundsMin
[ 1 ] };
558 l
[ 0 ] = boundsMin
[ 0 ];
559 l
[ 1 ] = boundsMax
[ 1 ];
564 l
[ 0 ] = boundsMax
[ 0 ];
565 l
[ 1 ] = boundsMin
[ 1 ];
570 l
[ 0 ] = boundsMax
[ 0 ];
571 l
[ 1 ] = boundsMax
[ 1 ];
576 rect
.x
= ( int )rMin
[ 0 ];
577 rect
.y
= ( int )rMin
[ 1 ];
578 rect
.width
= ( int )Math
.ceil( rMax
[ 0 ] - rect
.x
);
579 rect
.height
= ( int )Math
.ceil( rMax
[ 1 ] - rect
.y
);
584 private void update()
590 boundsMax
[ 0 ] = ( float )width
;
591 boundsMax
[ 1 ] = ( float )height
;
592 boundsMax
[ 2 ] = ( float )depth
;
596 // Utils.log2( "ict is null" );
599 else if ( Boundable
.class.isInstance( ict
) )
601 // Utils.log2( ict + " is a boundable" );
602 ( ( Boundable
)ict
).estimateBounds( boundsMin
, boundsMax
);
603 // Utils.log2( ict + "its bounds are (" + boundsMin[ 0 ] + ", " + boundsMin[ 1 ] + ", " + boundsMin[ 2 ] + ") -> (" + boundsMax[ 0 ] + ", " + boundsMax[ 1 ] + ", " + boundsMax[ 2 ] + ")" );
607 Utils
.log2( ict
+ " is not a boundable" );
608 final ArrayList
< Layer
> layers
= layer_set
.getLayers();
609 boundsMax
[ 0 ] = ( float )layer_set
.width
;
610 boundsMax
[ 1 ] = ( float )layer_set
.height
;
611 boundsMax
[ 2 ] = ( float )( layers
.get( layers
.size() - 1 ).getZ() - layers
.get( 0 ).getZ() );
616 if ( AffineModel3D
.class.isInstance( ict
) )
618 final float[] m
= ( ( AffineModel3D
)ict
).getMatrix( null );
620 final float dxs
= m
[ 0 ] * m
[ 0 ] + m
[ 4 ] * m
[ 4 ];
621 final float dys
= m
[ 1 ] * m
[ 1 ] + m
[ 5 ] * m
[ 5 ];
622 final float dzs
= m
[ 2 ] * m
[ 2 ] + m
[ 6 ] * m
[ 6 ];
624 ictScale
= ( float )Math
.sqrt( Math
.max( dxs
, Math
.max( dys
, dzs
) ) );
630 * For now, just returns the bounding box---we can refine this later
632 public Polygon
getPerimeter()
634 final Rectangle r
= getBoundingBox();
636 new int[]{ r
.x
, r
.x
+ r
.width
, r
.x
+ r
.width
, r
.x
},
637 new int[]{ r
.y
, r
.y
, r
.y
+ r
.height
, r
.y
+ r
.height
},
641 /** For reconstruction purposes, overwrites the present InvertibleCoordinateTransform, if any, with the given one. */
642 public void setInvertibleCoordinateTransformSilently( final InvertibleCoordinateTransform ict
)
648 private void invalidateCache()
650 cachedImages
.clear();
653 public void setInvertibleCoordinateTransform( final InvertibleCoordinateTransform ict
)
656 setInvertibleCoordinateTransformSilently( ict
);
659 public void setAffineTransform( final AffineTransform at
)
662 super.setAffineTransform( at
);
665 /** Avoid calling the trees: the stack exists only in the LayerSet ZDisplayable's list. */
666 public boolean remove2(boolean check
) {
667 return remove(check
);