3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005, 2006 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.imaging
;
25 import java
.awt
.Rectangle
;
26 import java
.awt
.Image
;
27 import java
.awt
.Window
;
28 import java
.awt
.Color
;
29 import java
.awt
.geom
.Point2D
;
30 import java
.awt
.geom
.AffineTransform
;
31 import java
.awt
.image
.PixelGrabber
;
32 import java
.awt
.image
.BufferedImage
;
33 import java
.awt
.image
.IndexColorModel
;
34 import java
.awt
.Graphics2D
;
35 import java
.util
.Properties
;
36 import java
.util
.HashSet
;
42 import ini
.trakem2
.display
.Display
;
43 import ini
.trakem2
.display
.Layer
;
44 import ini
.trakem2
.display
.Patch
;
45 import ini
.trakem2
.persistence
.Loader
;
46 import ini
.trakem2
.utils
.Utils
;
47 import ini
.trakem2
.utils
.IJError
;
51 /** Assumed is all Patch instances in the array are of the same size, live in consecutive layers of the same thickness.
53 * The superclass ImagePlus uses the 'ip' pointer for a single ImageProcessor whose pixels getr replaced every time a new slice is selected from the stack, when there is a stack. Here the 'ip' pointer is used for the current slice, which is the currently active Patch.
55 public class PatchStack
extends ImagePlus
{
57 /** Contains at least 1 patch.*/
58 private Patch
[] patch
;
59 //private int currentSlice = 1; // from 1 to n, as in ImageStack
60 private VirtualStack stack
;
61 private boolean[] called
;
63 public PatchStack(Patch
[] patch
, int currentSlice
) {
65 //this.currentSlice = currentSlice;
66 setSlice(currentSlice
);
67 Rectangle b
= patch
[0].getBoundingBox(null);
69 this.height
= b
.height
;
70 if (patch
.length
> 1) this.stack
= new VirtualStack(width
, height
);
71 else this.stack
= null;
72 this.ip
= null; // will be retrieved on the fly when necessary
74 called
= new boolean[patch
.length
];
75 for (int i
=0; i
<patch
.length
; i
++) called
[i
] = false;
77 super.setCalibration(patch
[0].getLayer().getParent().getCalibrationCopy());
80 /** Assumes all patches are of the same dimensions. */
81 public Rectangle
getBounds() {
82 return patch
[0].getBoundingBox();
85 public boolean contains(Patch p
) {
86 for (int i
=0; i
<patch
.length
; i
++)
87 if (patch
[i
].getId() == p
.getId())
92 /** If the 'p' is contained in this PatchStack, get the first Patch found to be contained here and in the given 'layer'*/
93 public Patch
getPatch(Layer layer
, Patch p
) {
94 if (!contains(p
)) return null;
95 long layer_id
= layer
.getId();
96 for (int i
=0; i
<patch
.length
; i
++)
97 if (patch
[i
].getLayer().getId() == layer_id
)
102 /** From 0 to getNSlices() -1, otherwise null. */
103 public Patch
getPatch(int i
) {
104 if (i
<0 || i
>patch
.length
) return null;
108 /** Returns the Patch corresponding to the current slice. */
109 public Patch
getCurrentPatch() {
110 return patch
[currentSlice
-1];
113 public void setCurrentSlice(Patch p
) {
114 // checks that it exists here first
115 for (int i
=0; i
<patch
.length
; i
++) {
116 if (patch
[i
].getId() == p
.getId()) {
123 public void revert(Patch p
) {
124 for (int i
=0; i
<patch
.length
; i
++) {
125 if (patch
[i
].getId() == p
.getId()) {
127 Display
.repaint(p
.getLayer(), p
, 0);
128 //p.getProject().getLoader().vacuum();
134 public void revertAll() {
135 Utils
.showProgress(0);
136 for (int i
=0; i
<patch
.length
; i
++) {
138 Utils
.showProgress((i
+1.0) / patch
.length
);
140 Utils
.showProgress(0);
141 //patch[0].getProject().getLoader().vacuum();
144 private void revert2(Patch p
) {
146 * needs rewrite: should just flush away and swap paths, and perhaps remove copy of image.
149 // TODO this is overkill. If only the lut and so on has changed, one could just undo that by calling ip.reset etc. as in ContrastAdjuster
150 Loader loader = p.getProject().getLoader();
151 ImagePlus current = loader.fetchImagePlus(p);
152 p.updateInDatabase("remove_tiff_working"); // remove the copy, no longer needed
153 ImagePlus original = loader.fetchOriginal(p);
154 if (null == original) {
155 Utils.log("PatchStack.revert2: null original!");
157 } else if (original.equals(current)) {
158 Utils.log("PatchStack.rever2: original equals current. Not reverting.");
161 Loader.flush(current);
162 loader.updateCache(p, original); //flush awt, remake awt, flush snap, remake snap
163 Rectangle box = p.getBoundingBox(null);
165 // ideally, all I want is to remove the scaling and shear components only
166 // but unfortunately the m02, m12 are edited to correct for shear and rotation
167 // and scaling -induced translations. So this is PARTIAL TODO
168 p.getAffineTransform().setToIdentity();
169 p.getAffineTransform().translate(box.x, box.y);
171 box = p.getBoundingBox(null);
173 // TODO this method needs heavy revision and updating
174 Display.repaint(p.getLayer(), box, 5); // the previous dimensions
178 /** Reset temporary changes such as from dragging B&C sliders and so on, in the current slice (the current Patch). */
179 public void resetNonActive() {
180 Utils
.log2("PatchStack: calling reset");
181 // remake the awt for the patch, flush the previous awt
182 Loader loader
= patch
[currentSlice
-1].getProject().getLoader();
183 for (int i
=0; i
<patch
.length
; i
++) {
184 if (currentSlice
-1 == i
|| !called
[i
]) continue;
186 ImagePlus imp
= loader
.fetchImagePlus(patch
[i
]);
187 ImageProcessor ip
= imp
.getProcessor();
188 switch (imp
.getType()) { // as in ij.plugin.frame.ContrastAdjuster.reset(ImagePlus, ImageProcessor)
189 case ImagePlus
.COLOR_RGB
:
191 case ImagePlus
.GRAY16
:
192 case ImagePlus
.GRAY32
:
193 ip
.resetMinAndMax(); break;
195 patch
[i
].setMinAndMax(ip
.getMin(), ip
.getMax());
196 patch
[i
].getProject().getLoader().decacheAWT(patch
[i
].getId());
197 Display
.repaint(patch
[i
].getLayer(), patch
[i
], null, 0, true);
201 /** Store working copies and remake the awts and repaint. */
202 public void saveImages() {
203 Utils
.log2("PatchStack: calling saveImages");
205 Utils
.log2("PatchStack.saveImages: nothing changed.");
208 Loader loader
= patch
[currentSlice
-1].getProject().getLoader();
209 Utils
.showProgress(0);
210 for (int i
=0; i
<patch
.length
; i
++) {
211 ImagePlus imp
= loader
.fetchImagePlus(patch
[i
]);
212 Utils
.log2("PatchStack.saveImages: patch imp " + i
+ " has the imp.changes=" + imp
.changes
+ " and the called[i]=" + called
[i
]);
213 if (imp
.changes
|| called
[i
]) {
214 patch
[i
].updateInDatabase("tiff_working"); // may be doing it twice, check TODO
216 patch[i].createImage(); //flushes the old awt, and creates the new one, and stores it in the cache.
218 // just flush away all dependent images, will be recreated when needed on repaint
219 patch
[i
].getProject().getLoader().decache(imp
);
220 Display
.repaint(patch
[i
].getLayer(), patch
[i
], 0);
225 Utils
.showProgress((i
+1.0) / patch
.length
);
227 this.changes
= false;
228 //patch[0].getProject().getLoader().vacuum();
229 Utils
.showProgress(1);
233 // repaint all Displays that show any layer of the parent LayerSet (this is overkill, but not so bad, almost never there'll be that many Displays)
234 Display
.repaint(patch
[currentSlice
-1].getLayer().getParent());
237 public void draw(int x
, int y
, int width
, int height
) {
238 Rectangle r
= new Rectangle(x
, y
, width
, height
);
239 Display
.repaint(patch
[currentSlice
-1].getLayer(), r
, 0);
242 public void updateAndDraw() {
243 Utils
.log2("PatchStack: calling updateAndDraw");
244 //Display.repaint(patch[currentSlice-1].getLayer(), patch[currentSlice-1], 0);
245 // TODO : notify listeners ?
246 //No, instead do it directly:
248 saveImages(); //only those perhaps affected (can't really tell)
251 Utils
.log2("PatchStack.updateAndDraw 'else'");
252 // decache (to force remaking) and redraw
253 patch
[currentSlice
-1].getProject().getLoader().decacheAWT(patch
[currentSlice
-1].getId());
254 Display
.repaint(patch
[currentSlice
-1].getLayer(), patch
[currentSlice
-1], null, 0, true);
255 // reset the others if necessary
256 //resetNonActive(); // TODO there must to be a better way, this is overkill because not all images over which a getProcessor() has been called will have been modified. It would be solved if imp.changes was accessed through a method instead, because then I could flag the proper imp as changed
260 public void repaintWindow() {
261 Display
.repaint(patch
[currentSlice
-1].getLayer(), patch
[currentSlice
-1], 0);
264 public void updateAndRepaintWindow() {
265 Display
.repaint(patch
[currentSlice
-1].getLayer(), patch
[currentSlice
-1], 0);
268 public void updateImage() {
269 Utils
.log2("PS: Update image");
270 patch
[currentSlice
-1].createImage(); //flushes the old awt, and creates the new one.
271 Display
.repaint(patch
[currentSlice
-1].getLayer(), patch
[currentSlice
-1], 0);
275 Utils
.log("PatchStack: can't hide.");
278 public void close() {
279 Utils
.log("PatchStack: can't close.");
283 Utils
.log("PatchStack: can't show.");
286 public void show(String statusMessage
) {
290 public void invertLookupTable() {
291 Loader loader
= patch
[currentSlice
-1].getProject().getLoader();
292 ImagePlus imp
= loader
.fetchImagePlus(patch
[currentSlice
-1]);
293 imp
.getProcessor().invert();
294 Display
.repaint(patch
[currentSlice
-1].getLayer(), patch
[currentSlice
-1], 0);
295 patch
[currentSlice
-1].updateInDatabase("tiff_working"); // TODO if the database updates are too much, then one could put a "save" button somewhere that shows as "unsaved" (red?) when there are unsaved changes.
296 imp
.changes
= false; // just saved
299 public Image
getImage() {
300 return patch
[currentSlice
-1].getProject().getLoader().fetchImage(patch
[currentSlice
-1]);
301 // TODO: is this safe? Can be flushed!
304 public void setImage(Image img
) {
305 Utils
.log("PatchStack: can't setImage");
308 public void setProcessor(String title
, ImageProcessor ip
) {
309 if (1 != patch
.length
) return; //so not applied to stacks
310 Loader loader
= patch
[currentSlice
-1].getProject().getLoader();
311 ImagePlus imp
= loader
.fetchImagePlus(patch
[currentSlice
-1]);
312 if (ip
.getWidth() != imp
.getWidth() || ip
.getHeight() != imp
.getHeight()) throw new IllegalArgumentException("PatchStack: ip wrong size");
313 imp
.setProcessor(null, ip
); // null means don't touch title
314 patch
[currentSlice
-1].updateInDatabase("tiff_working");
316 // repainting elsewhere
319 public void setStack(String title
, ImageStack stack
) {
320 Utils
.log("PatchStack: can't setStack");
323 public void setFileInfo(FileInfo fi
) {
324 Utils
.log("PatchStack: can't setFileInfo");
327 public void setWindow(Window win
) {
328 Utils
.log("PatchStack: can't setWindow");
331 public void setColor(Color c
) {
332 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
333 imp
.getProcessor().setColor(c
);
336 public boolean isProcessor() {
337 return true; // TODO needs testing. This function simply creates an ImageProcessor in the superclass, to hold the pixels object from the stack array
340 public ImageProcessor
getProcessor() {
341 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
342 ImageProcessor ip
= imp
.getProcessor();
343 if (null!=this.roi
) imp
.setRoi(this.roi
);
344 called
[currentSlice
-1] = true;
348 public synchronized void trimProcessor() {
351 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
353 } catch (Exception e
) {
359 public ImageProcessor
getMask() {
360 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
361 ImageProcessor ip
= imp
.getProcessor();
367 ImageProcessor mask
= roi
.getMask();
368 if (null==mask
) return null;
370 ip
.setRoi(roi
.getBounds());
374 // only need to override one, as the others point to this method
375 public ImageStatistics
getStatistics(int nOptions
, int nBins
, double histMin
, double histMax
) {
376 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
377 if (null!=this.roi
) imp
.setRoi(this.roi
); // to be sure!
378 return imp
.getStatistics(nOptions
, nBins
, histMin
, histMax
);
381 public String
getTitle() {
382 return patch
[currentSlice
-1].getTitle();
385 public void setTitle(String title
) {
386 patch
[currentSlice
-1].setTitle(title
);
389 public int getStackSize() {
393 public void setDimensions(int nChannels
, int nSlices
, int nFrames
) {
394 Utils
.log("PatchStack: Can't setDimensions.");
397 /** Override to return zero */
398 public int getNChannels() {
402 public int getNSlices() {
406 /** Override to return zero */
407 public int getNFrames() {
411 /** Override to return width, height, 1, patch.length, 1*/
412 public int[] getDimensions() {
413 return new int[]{width
, height
, 1, patch
.length
, 1};
416 public int getType() {
418 ImagePlus imp = patch[currentSlice-1].getProject().getLoader().fetchImagePlus(patch[currentSlice-1]);
419 return imp.getType();
421 return patch
[currentSlice
-1].getType();
424 public int getBitDepth() {
425 int type
= getType();
440 protected void setType(int type
) {
441 Utils
.log("PatchStack: Can't set type");
444 public void setProperty(String key
, Object value
) {
445 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
446 imp
.setProperty(key
, value
);
447 patch
[currentSlice
-1].updateInDatabase("tiff_working"); // TODO same as above, may be too much saving
448 //patch[currentSlice-1].getProject().getLoader().vacuum();
451 public Object
getProperty(String key
) {
452 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
453 return imp
.getProperty(key
);
456 public Properties
getProperties() {
457 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
458 return imp
.getProperties();
461 public LookUpTable
createLut() {
462 Image awt
= patch
[currentSlice
-1].getProject().getLoader().fetchImage(patch
[currentSlice
-1]);
463 return new LookUpTable(awt
);
466 public boolean isInvertedLut() {
467 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
468 return imp
.getProcessor().isInvertedLut();
471 public int[] getPixel(int x
, int y
) {
473 double mag
= Display
.getFront().getCanvas().getMagnification();
474 return patch
[currentSlice
-1].getPixel(x
, y
, mag
);
475 } catch (Exception e
) {
481 public ImageStack
createEmptyStack() {
482 Utils
.log("PatchStack: can't createEmptyStack");
486 public ImageStack
getStack() { return stack
; } // the virtual one
488 public ImageStack
getImageStack() { return stack
; } // idem
490 public int getCurrentSlice() {
494 public synchronized void setSlice(int index
) {
495 if (index
== currentSlice
) {
496 //no need//updateAndRepaintWindow();
499 if (index
>= 1 && index
<= patch
.length
) {
501 if (null != roi
) roi
.endPaste();
502 currentSlice
= index
;
503 //no need//updateAndRepaintWindow();
507 public Roi
getRoi() {
510 private Roi
getImpRoi() {
515 public void setRoi(Roi roi
) {
517 //super.setRoi(roi); // needed ? If roi is protected, no
518 if (null==roi
) { this.roi
= null; return; }
519 // translate roi to Patch coordinate system
520 Rectangle b
= getBounds();
521 Roi roi2
= (Roi
)roi
.clone();
522 Rectangle r
= roi2
.getBounds();
523 roi2
.setLocation(r
.x
- b
.x
, r
.y
- b
.y
);
525 if (roi
.isVisible()) roi
= (Roi
)roi
.clone();
526 Rectangle bounds
= roi
.getBounds();
527 if (0==bounds
.width
&& 0==bounds
.height
) return;
529 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
531 ImageProcessor ip
= imp
.getProcessor();
536 this.roi
.setImage(this);
537 //draw(); //not needed
538 //stack.setRoi(roi.getBounds()); // doesn't make a difference because I'm setting the Roi at VirtualStack.getProcessor(int) as well.
541 public void setRoi(Rectangle r
) {
542 if (null==r
) { killRoi(); return; }
544 this.roi
= new Roi(r
.x
, r
.y
, r
.width
, r
.height
);
545 this.roi
.setImage(this);
546 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
547 ImageProcessor ip
= imp
.getProcessor();
552 //draw(); // not needed
555 public void createNewRoi(int sx
, int sy
) {
556 super.createNewRoi(sx
, sy
);
557 this.roi
= getRoi(); // stupid privates
560 public void killRoi() {
565 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().getCachedImagePlus(patch
[currentSlice
-1].getId()); // .fetchImagePlus(patch[currentSlice-1]);
566 if (null != imp
) imp
.killRoi();
567 //draw() // not needed
570 public void saveRoi() {
573 Rectangle r
= roi
.getBounds();
574 if (r
.width
>0 && r
.height
>0) {
575 Roi
.previousRoi
= (Roi
)roi
.clone();
580 public FileInfo
getFileInfo() {
581 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
582 return imp
.getFileInfo();
585 public synchronized void flush() {}
587 public Calibration
getCalibration() {
589 if (null != getGlobalCalibration()) {
590 return getGlobalCalibration();
593 return getLocalCalibration();
596 public void setCalibration(Calibration cal
) {
597 // pass it to the LayerSet
598 patch
[0].getLayer().getParent().setCalibration(cal
);
599 // and the super of course
600 super.setCalibration(cal
);
601 // set it locally to the ImagePlus'es
602 for (int i
=0; i
<patch
.length
; i
++) {
603 ImagePlus imp
= patch
[i
].getProject().getLoader().fetchImagePlus(patch
[i
]);
604 imp
.setCalibration(cal
);
605 if (imp
.getStackSize() > 1) return; // done, for real stacks
609 public Calibration
getLocalCalibration() {
610 /* // never ! The calibration of the active image is ignored. The LayerSet calibration is what counts. Plus this method ends up loading the image when selecting it!
611 ImagePlus imp = patch[currentSlice-1].getProject().getLoader().fetchImagePlus(patch[currentSlice-1]);
612 return imp.getCalibration();
614 return patch
[currentSlice
-1].getLayer().getParent().getCalibrationCopy();
617 public void copy(boolean cut
) {
621 public void paste() {
623 ImagePlus imp
= patch
[currentSlice
-1].getProject().getLoader().fetchImagePlus(patch
[currentSlice
-1]);
625 // TODO: make sure that the srcRect that the super method gets makes any sense to it when the Canvas is larger than this image, or viceversa.
627 patch
[currentSlice
-1].updateInDatabase("tiff_working");
630 /** Remove all awts and snaps from the loader's cache, and repaint (which will remake as many as needed) */
631 public void decacheAll() {
632 final HashSet hs
= new HashSet(); // record already flushed imps, since there can be shared imps among Patch instances (for example in stacks)
633 final Loader loader
= patch
[currentSlice
-1].getProject().getLoader();
634 for (int i
=0; i
<patch
.length
; i
++) {
635 ImagePlus imp
= loader
.fetchImagePlus(patch
[i
]);
636 if (hs
.contains(imp
)) continue;
637 else if (null != imp
) hs
.add(imp
);
639 loader
.flushMipMaps(patch
[i
].getId());
640 Display
.repaint(patch
[i
].getLayer(), patch
[i
], 0);
644 private class VirtualStack
extends ImageStack
{
646 VirtualStack(int width
, int height
) {
647 super(width
, height
);
649 public void addSlice(String label
, ImageProcessor ip
) {
650 Utils
.log("PatchStack: Can't add a slice.");
653 public void addSlice(String label
, Object pixels
) {
654 Utils
.log("PatchStack: Can't add a slice.");
657 public void addSlice(String label
, Object pixels
, int n
) {
658 Utils
.log("PatchStack: Can't add a slice.");
661 public void addUInsignedShortSlice(String label
, Object pixels
) {
662 Utils
.log("PatchStack: Can't add a slice.");
665 public void deleteSlice(int n
) {
666 Utils
.log("PatchStack: Can't delete a slice.");
669 public void deleteLastSlice() {
670 Utils
.log("PatchStack: Can't delete last slice.");
673 /** Must override, for superclass fields are private! */
674 public int getWidth() { return width
; }
676 /** Must override, for superclass fields are private! */
677 public int getHeight() { return height
; }
679 public Object
getPixels(int n
) {
680 if (n
<1 || n
>patch
.length
) throw new IllegalArgumentException("PatchStack: out of range " + n
);
681 ImagePlus imp
= patch
[n
-1].getProject().getLoader().fetchImagePlus(patch
[n
-1]);
682 return imp
.getProcessor().getPixels(); // TODO should clone?
685 public void setPixels(Object pixels
, int n
) {
686 if (n
<1 || n
>patch
.length
) throw new IllegalArgumentException("PatchStack: out of range " + n
);
687 if (null == pixels
) throw new IllegalArgumentException("PatchStack: 'pixels' is null!");
688 ImagePlus imp
= patch
[n
-1].getProject().getLoader().fetchImagePlus(patch
[n
-1]);
689 imp
.getProcessor().setPixels(pixels
);
690 patch
[n
-1].updateInDatabase("tiff_working");
691 //patch[n-1].getProject().getLoader().vacuum();
694 public Object
[] getImageArray() {
695 Object
[] ob
= new Object
[patch
.length
];
696 for (int i
=0; i
<ob
.length
; i
++) {
697 ImagePlus imp
= patch
[i
].getProject().getLoader().fetchImagePlus(patch
[i
]);
698 ob
[i
] = imp
.getProcessor().getPixels(); // TODO should clone? This is mostly for saving files, and it's safe because the database is untouched (but then, it may look inconsistent if the images are updated in some way)
703 public int getSize() { return patch
.length
; }
705 public String
[] getSliceLabels() {
706 String
[] labels
= new String
[patch
.length
];
707 for (int i
=0; i
<labels
.length
; i
++) labels
[i
] = patch
[i
].getTitle();
711 public String
getSliceLabel(int n
) { // using 'n' for index is not a good idea, Wayne!
712 if (n
<1 || n
>patch
.length
) throw new IllegalArgumentException("PatchStack: out of range " + n
);
713 return patch
[n
-1].getTitle();
716 public String
getShortSliceLabel(int n
) {
717 String shortLabel
= getSliceLabel(n
);
718 if (shortLabel
==null) return null;
719 int newline
= shortLabel
.indexOf('\n');
720 if (0 == newline
) return null;
721 if (newline
>0) shortLabel
= shortLabel
.substring(0, newline
);
722 int len
= shortLabel
.length();
723 if (len
>4 && '.' == shortLabel
.charAt(len
-4) && Character
.isDigit(shortLabel
.charAt(len
-1))) shortLabel
= shortLabel
.substring(0,len
-4);
724 if (shortLabel
.length()>60) shortLabel
= shortLabel
.substring(0, 60);
728 public void setSliceLabel(String label
, int n
) {
729 if (n
<1 || n
>patch
.length
) throw new IllegalArgumentException("PatchStack: out of range " + n
);
730 if (null != label
) patch
[n
-1].setTitle(label
);
733 public ImageProcessor
getProcessor(int n
) {
734 //Utils.log("VirtualStack: called getProcessor " + n);
735 if (n
<1 || n
>patch
.length
) throw new IllegalArgumentException("PatchStack: out of range " + n
);
736 ImagePlus imp
= patch
[n
-1].getProject().getLoader().fetchImagePlus(patch
[n
-1]);
737 ImageProcessor ip
= imp
.getProcessor();
738 Roi roi
= getImpRoi(); // oddities of private classes
739 if (null!=roi
) imp
.setRoi(roi
);
741 /* // no, instead reload the ImagePlus from the database if flushed (setup at the Loader.fetchImagePlus and fetchImage methods)
742 patch[n-1].getProject().getLoader().releaseToFit(ip.getWidth() * ip.getHeight() * imp.getBitDepth() / 1024L);
743 return ip.duplicate(); // let's give a copy to ImageJ
748 /** Override: always false. */
749 public boolean isRGB() {
753 /** Override: always false. */
754 public boolean isHSB() {
758 public boolean isVirtual() {
759 return true; // TODO got to test this
762 /** Override: does nothing. */
764 Utils
.log("PatchStack.VirtualStack: can't trim");
767 public String
toString() {
768 return "Virtual Patch Stack: width=" + width
+ ", height=" + height
+ ", nSlices: " + patch
.length
;
772 // WARNING This method will fail if the stack has slices of different dimensions
773 /** Does not respect local transform of the patches, this is intended for confocal stacks. */
774 public ImagePlus
createGray8Copy() {
775 final Rectangle box
= patch
[0].getBoundingBox();
776 final int width
= box
.width
;
777 final int height
= box
.height
;
778 // compute minimum bounding box
779 ImageStack st
= new ImageStack(width
, height
);
780 Loader loader
= patch
[0].getProject().getLoader();
781 for (int i
=1; i
<patch
.length
; i
++) {
782 loader
.releaseToFit(width
* height
);
783 st
.addSlice(Integer
.toString(i
), this.stack
.getProcessor(i
).convertToByte(true));
785 ImagePlus imp
= new ImagePlus("byte", st
);
786 imp
.setCalibration(patch
[0].getLayer().getParent().getCalibrationCopy());
787 //imp.getCalibration().pixelDepth = patch[0].getLayer().getThickness();
791 // WARNING This method will fail if the stack has slices of different dimensions
792 /** Does not respect local transform of the patches, this is intended for confocal stacks. */
793 public ImagePlus
createColor256Copy() {
794 final Rectangle box
= patch
[0].getBoundingBox();
795 final int width
= box
.width
;
796 final int height
= box
.height
;
797 Loader loader
= patch
[0].getProject().getLoader();
798 patch
[0].getProject().getLoader().releaseToFit(4 * patch
.length
* width
* height
); // the montage, in RGB
799 final ColorProcessor montage
= new ColorProcessor(width
*patch
.length
, height
);
800 for (int i
=0; i
<patch
.length
; i
++) {
801 montage
.insert(this.stack
.getProcessor(i
+1), i
*width
, 0);
803 final MedianCut mc
= new MedianCut(montage
);
804 loader
.releaseToFit(patch
.length
* width
* height
);
805 ImageProcessor m2
= mc
.convertToByte(256);
806 final ImageStack st
= new ImageStack(width
, height
);
807 for (int i
=0; i
<patch
.length
; i
++) {
808 m2
.setRoi(i
*width
, 0, width
, height
);
809 loader
.releaseToFit(width
* height
);
810 st
.addSlice(null, m2
.crop());
812 ImagePlus imp
= new ImagePlus("color256", st
);
813 imp
.setCalibration(patch
[0].getLayer().getParent().getCalibrationCopy());
814 //imp.getCalibration().pixelDepth = patch[0].getLayer().getThickness();