Moved general geometry function findPath from AreaList to M
[trakem2.git] / ini / trakem2 / imaging / PatchStack.java
blob9368049123cedeb42ab4b387e1a230978e7cfcd4
1 /**
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.
21 **/
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;
37 import ij.*;
38 import ij.process.*;
39 import ij.gui.*;
40 import ij.measure.*;
41 import ij.io.*;
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.
54 * */
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) {
64 this.patch = patch;
65 //this.currentSlice = currentSlice;
66 setSlice(currentSlice);
67 Rectangle b = patch[0].getBoundingBox(null);
68 this.width = b.width;
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
73 this.changes = false;
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())
88 return true;
89 return false;
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)
98 return patch[i];
99 return null;
102 /** From 0 to getNSlices() -1, otherwise null. */
103 public Patch getPatch(int i) {
104 if (i<0 || i>patch.length) return null;
105 return patch[i];
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()) {
117 currentSlice = i+1;
118 break;
123 public void revert(Patch p) {
124 for (int i=0; i<patch.length; i++) {
125 if (patch[i].getId() == p.getId()) {
126 revert2(p);
127 Display.repaint(p.getLayer(), p, 0);
128 //p.getProject().getLoader().vacuum();
129 break;
134 public void revertAll() {
135 Utils.showProgress(0);
136 for (int i=0; i<patch.length; i++) {
137 revert2(patch[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) {
145 /* // TODO
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!");
156 return;
157 } else if (original.equals(current)) {
158 Utils.log("PatchStack.rever2: original equals current. Not reverting.");
159 return;
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;
185 called[i] = false;
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:
190 ip.reset(); break;
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");
204 if (!this.changes) {
205 Utils.log2("PatchStack.saveImages: nothing changed.");
206 return;
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);
221 // reset
222 imp.changes = false;
223 called[i] = false;
225 Utils.showProgress((i+1.0) / patch.length);
227 this.changes = false;
228 //patch[0].getProject().getLoader().vacuum();
229 Utils.showProgress(1);
232 public void draw() {
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:
247 if (changes) {
248 saveImages(); //only those perhaps affected (can't really tell)
249 changes = false;
250 } else {
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);
274 public void hide() {
275 Utils.log("PatchStack: can't hide.");
278 public void close() {
279 Utils.log("PatchStack: can't close.");
282 public void show() {
283 Utils.log("PatchStack: can't show.");
286 public void show(String statusMessage) {
287 this.show();
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");
315 //loader.vacuum();
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;
345 return ip;
348 public synchronized void trimProcessor() {
349 if (!locked) {
350 try {
351 ImagePlus imp = patch[currentSlice-1].getProject().getLoader().fetchImagePlus(patch[currentSlice-1]);
352 imp.trimProcessor();
353 } catch (Exception e) {
354 e.printStackTrace();
359 public ImageProcessor getMask() {
360 ImagePlus imp = patch[currentSlice-1].getProject().getLoader().fetchImagePlus(patch[currentSlice-1]);
361 ImageProcessor ip = imp.getProcessor();
362 Roi roi = getRoi();
363 if (null == roi) {
364 ip.resetRoi();
365 return null;
367 ImageProcessor mask = roi.getMask();
368 if (null==mask) return null;
369 ip.setMask(mask);
370 ip.setRoi(roi.getBounds());
371 return mask;
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() {
390 return patch.length;
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() {
399 return 0;
402 public int getNSlices() {
403 return patch.length;
406 /** Override to return zero */
407 public int getNFrames() {
408 return 0;
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();
426 switch(type) {
427 case GRAY8:
428 case COLOR_256:
429 return 8;
430 case GRAY16:
431 return 16;
432 case GRAY32:
433 return 32;
434 case COLOR_RGB:
435 return 24;
437 return 8;
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) {
472 try {
473 double mag = Display.getFront().getCanvas().getMagnification();
474 return patch[currentSlice-1].getPixel(x, y, mag);
475 } catch (Exception e) {
476 IJError.print(e);
478 return new int[4];
481 public ImageStack createEmptyStack() {
482 Utils.log("PatchStack: can't createEmptyStack");
483 return null;
486 public ImageStack getStack() { return stack; } // the virtual one
488 public ImageStack getImageStack() { return stack; } // idem
490 public int getCurrentSlice() {
491 return currentSlice;
494 public synchronized void setSlice(int index) {
495 if (index == currentSlice) {
496 //no need//updateAndRepaintWindow();
497 return;
499 if (index>= 1 && index <= patch.length) {
500 Roi roi = getRoi();
501 if (null != roi) roi.endPaste();
502 currentSlice = index;
503 //no need//updateAndRepaintWindow();
507 public Roi getRoi() {
508 return roi;
510 private Roi getImpRoi() {
511 return roi;
515 public void setRoi(Roi roi) {
516 killRoi();
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);
524 roi = roi2;
525 if (roi.isVisible()) roi = (Roi)roi.clone();
526 Rectangle bounds = roi.getBounds();
527 if (0==bounds.width && 0==bounds.height) return;
528 this.roi = roi;
529 ImagePlus imp = patch[currentSlice-1].getProject().getLoader().fetchImagePlus(patch[currentSlice-1]);
530 imp.setRoi(roi);
531 ImageProcessor ip = imp.getProcessor();
532 if (null!=ip) {
533 ip.setMask(null);
534 ip.setRoi(bounds);
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; }
543 killRoi();
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();
548 if (null != ip) {
549 ip.setMask(null);
550 ip.setRoi(r);
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() {
561 if (null!=roi) {
562 saveRoi();
563 roi = null;
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() {
571 if (null != roi) {
572 roi.endPaste();
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) {
618 super.copy(cut);
621 public void paste() {
622 super.paste();
623 ImagePlus imp = patch[currentSlice-1].getProject().getLoader().fetchImagePlus(patch[currentSlice-1]);
624 imp.changes = true;
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);
638 loader.decache(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)
700 return ob;
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();
708 return labels;
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);
725 return shortLabel;
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);
740 called[n-1] = true;
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
745 return ip;
748 /** Override: always false. */
749 public boolean isRGB() {
750 return false;
753 /** Override: always false. */
754 public boolean isHSB() {
755 return false;
758 public boolean isVirtual() {
759 return true; // TODO got to test this
762 /** Override: does nothing. */
763 public void trim() {
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();
788 return imp;
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();
815 return imp;