package ij.plugin.frame; import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.*; import java.awt.List; import java.util.zip.*; import ij.*; import ij.process.*; import ij.gui.*; import ij.io.*; import ij.plugin.filter.*; import ij.util.*; import ij.macro.*; import ij.measure.*; /** This plugin implements the Analyze/Tools/ROI Manager command. */ public class RoiManager extends PlugInFrame implements ActionListener, ItemListener, MouseListener, MouseWheelListener { public static final String LOC_KEY = "manager.loc"; static final int BUTTONS = 10; static final int DRAW=0, FILL=1, LABEL=2; static final int MENU=0, COMMAND=1, MULTI=2; static int rows = 15; static boolean allowMultipleSelections = true; static String moreButtonLabel = "More "+'\u00bb'; Panel panel; static Frame instance; java.awt.List list; Hashtable rois = new Hashtable(); Roi roiCopy; boolean canceled; boolean macro; boolean ignoreInterrupts; PopupMenu pm; Button moreButton, colorButton; static boolean measureAll = true; static boolean onePerSlice = true; static boolean restoreCentered; int prevID; public RoiManager() { super("ROI Manager"); if (instance!=null) { instance.toFront(); return; } instance = this; list = new List(rows, allowMultipleSelections); showWindow(); } public RoiManager(boolean hideWindow) { super("ROI Manager"); list = new List(rows, allowMultipleSelections); } void showWindow() { ImageJ ij = IJ.getInstance(); addKeyListener(ij); addMouseListener(this); addMouseWheelListener(this); WindowManager.addWindow(this); setLayout(new FlowLayout(FlowLayout.CENTER,5,5)); list.add("012345678901234"); list.addItemListener(this); list.addKeyListener(ij); list.addMouseListener(this); list.addMouseWheelListener(this); if (IJ.isLinux()) list.setBackground(Color.white); add(list); panel = new Panel(); int nButtons = BUTTONS; panel.setLayout(new GridLayout(nButtons, 1, 5, 0)); addButton("Add [t]"); addButton("Update"); addButton("Delete"); addButton("Rename"); addButton("Open"); addButton("Save"); addButton("Measure"); addButton("Deselect"); addButton("Show All"); addButton(moreButtonLabel); add(panel); addPopupMenu(); pack(); list.remove(0); Point loc = Prefs.getLocation(LOC_KEY); if (loc!=null) setLocation(loc); else GUI.center(this); show(); } void addButton(String label) { Button b = new Button(label); b.addActionListener(this); b.addKeyListener(IJ.getInstance()); b.addMouseListener(this); if (label.equals(moreButtonLabel)) moreButton = b; panel.add(b); } void addPopupMenu() { pm=new PopupMenu(); //addPopupItem("Select All"); addPopupItem("Draw"); addPopupItem("Fill"); addPopupItem("Label"); pm.addSeparator(); addPopupItem("Combine"); addPopupItem("Split"); addPopupItem("Add Particles"); addPopupItem("Multi Measure"); addPopupItem("Sort"); addPopupItem("Specify..."); addPopupItem("Remove Slice Info"); addPopupItem("Help"); addPopupItem("Options..."); add(pm); } void addPopupItem(String s) { MenuItem mi=new MenuItem(s); mi.addActionListener(this); pm.add(mi); } public void actionPerformed(ActionEvent e) { int modifiers = e.getModifiers(); boolean altKeyDown = (modifiers&ActionEvent.ALT_MASK)!=0 || IJ.altKeyDown(); boolean shiftKeyDown = (modifiers&ActionEvent.SHIFT_MASK)!=0 || IJ.shiftKeyDown(); IJ.setKeyUp(KeyEvent.VK_ALT); IJ.setKeyUp(KeyEvent.VK_SHIFT); String label = e.getActionCommand(); if (label==null) return; String command = label; if (command.equals("Add [t]")) add(shiftKeyDown, altKeyDown); else if (command.equals("Update")) update(); else if (command.equals("Delete")) delete(false); else if (command.equals("Rename")) rename(null); else if (command.equals("Open")) open(null); else if (command.equals("Save")) save(); else if (command.equals("Measure")) measure(MENU); else if (command.equals("Show All")) showAll(); else if (command.equals("Draw")) drawOrFill(DRAW); else if (command.equals("Fill")) drawOrFill(FILL); else if (command.equals("Label")) drawOrFill(LABEL); else if (command.equals("Deselect")) select(-1); else if (command.equals(moreButtonLabel)) { Point ploc = panel.getLocation(); Point bloc = moreButton.getLocation(); pm.show(this, ploc.x, bloc.y); } else if (command.equals("Select All")) selectAll(); else if (command.equals("Combine")) combine(); else if (command.equals("Split")) split(); else if (command.equals("Add Particles")) addParticles(); else if (command.equals("Multi Measure")) multiMeasure(); else if (command.equals("Sort")) sort(); else if (command.equals("Specify...")) specify(); else if (command.equals("Remove Slice Info")) removeSliceInfo(); else if (command.equals("Help")) help(); else if (command.equals("Options...")) options(); else if (command.equals("\"Show All\" Color...")) setShowAllColor(); } public void itemStateChanged(ItemEvent e) { if (e.getStateChange()==ItemEvent.SELECTED && !ignoreInterrupts) { int index = 0; try {index = Integer.parseInt(e.getItem().toString());} catch (NumberFormatException ex) {} if (index<0) index = 0; if (!IJ.shiftKeyDown() && !IJ.isMacintosh()) { int[] indexes = list.getSelectedIndexes(); for (int i=0; i0 && !IJ.isMacro()) { // check for duplicate String label = list.getItem(n-1); Roi roi2 = (Roi)rois.get(label); if (roi2!=null) { int slice2 = getSliceNumber(label); if (roi.equals(roi2) && (slice2==-1||slice2==imp.getCurrentSlice()) && imp.getID()==prevID && !Interpreter.isBatchMode()) return false; } } prevID = imp.getID(); String name = roi.getName(); if (isStandardName(name)) name = null; String label = name!=null?name:getLabel(imp, roi, -1); if (promptForName) label = promptForName(label); else label = getUniqueName(label); if (label==null) return false; list.add(label); roi.setName(label); roiCopy = (Roi)roi.clone(); Calibration cal = imp.getCalibration(); if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0) { Rectangle r = roiCopy.getBounds(); roiCopy.setLocation(r.x-(int)cal.xOrigin, r.y-(int)cal.yOrigin); } rois.put(label, roiCopy); updateShowAll(); if (Recorder.record) Recorder.record("roiManager", "Add"); return true; } /** Adds the specified ROI to the list. The third argument ('n') will be used to form the first part of the ROI lable if it is >= 0. */ public void add(ImagePlus imp, Roi roi, int n) { if (roi==null) return; String label = getLabel(imp, roi, n); if (label==null) return; list.add(label); roi.setName(label); roiCopy = (Roi)roi.clone(); Calibration cal = imp.getCalibration(); if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0) { Rectangle r = roiCopy.getBounds(); roiCopy.setLocation(r.x-(int)cal.xOrigin, r.y-(int)cal.yOrigin); } rois.put(label, roiCopy); } boolean isStandardName(String name) { if (name==null) return false; boolean isStandard = false; int len = name.length(); if (len>=14 && name.charAt(4)=='-' && name.charAt(9)=='-' ) isStandard = true; else if (len>=17 && name.charAt(5)=='-' && name.charAt(11)=='-' ) isStandard = true; else if (len>=9 && name.charAt(4)=='-') isStandard = true; else if (len>=11 && name.charAt(5)=='-') isStandard = true; return isStandard; } String getLabel(ImagePlus imp, Roi roi, int n) { Rectangle r = roi.getBounds(); int xc = r.x + r.width/2; int yc = r.y + r.height/2; if (n>=0) {xc = yc; yc=n;} if (xc<0) xc = 0; if (yc<0) yc = 0; int digits = 4; String xs = "" + xc; if (xs.length()>digits) digits = xs.length(); String ys = "" + yc; if (ys.length()>digits) digits = ys.length(); xs = "000000" + xc; ys = "000000" + yc; String label = ys.substring(ys.length()-digits) + "-" + xs.substring(xs.length()-digits); if (imp.getStackSize()>1) { String zs = "000000" + imp.getCurrentSlice(); label = zs.substring(zs.length()-digits) + "-" + label; } return label; } void addAndDraw(boolean altKeyDown) { if (altKeyDown) { if (!add(true)) return; } else if (!add(false)) return; ImagePlus imp = WindowManager.getCurrentImage(); Undo.setup(Undo.COMPOUND_FILTER, imp); IJ.run("Draw"); Undo.setup(Undo.COMPOUND_FILTER_DONE, imp); if (Recorder.record) Recorder.record("roiManager", "Add & Draw"); } boolean delete(boolean replacing) { int count = list.getItemCount(); if (count==0) return error("The list is empty."); int index[] = list.getSelectedIndexes(); if (index.length==0 || (replacing&&count>1)) { String msg = "Delete all items on the list?"; if (replacing) msg = "Replace items on the list?"; canceled = false; if (!IJ.isMacro() && !macro) { YesNoCancelDialog d = new YesNoCancelDialog(this, "ROI Manager", msg); if (d.cancelPressed()) {canceled = true; return false;} if (!d.yesPressed()) return false; } index = getAllIndexes(); } for (int i=count-1; i>=0; i--) { boolean delete = false; for (int j=0; j=0) { String name = list.getItem(index); rois.remove(name); rois.put(name, (Roi)roi.clone()); } if (Recorder.record) Recorder.record("roiManager", "Update"); if (showingAll) imp.draw(); return true; } boolean rename(String name2) { int index = list.getSelectedIndex(); if (index<0) return error("Exactly one item in the list must be selected."); String name = list.getItem(index); if (name2==null) name2 = promptForName(name); if (name2==null) return false; Roi roi = (Roi)rois.get(name); rois.remove(name); roi.setName(name2); rois.put(name2, roi); list.replaceItem(name2, index); list.select(index); if (Recorder.record) Recorder.record("roiManager", "Rename", name2); return true; } String promptForName(String name) { GenericDialog gd = new GenericDialog("ROI Manager"); gd.addStringField("Rename As:", name, 20); gd.showDialog(); if (gd.wasCanceled()) return null; String name2 = gd.getNextString(); name2 = getUniqueName(name2); return name2; } boolean restore(int index, boolean setSlice) { String label = list.getItem(index); Roi roi = (Roi)rois.get(label); ImagePlus imp = getImage(); if (imp==null || roi==null) return false; if (setSlice) { int n = getSliceNumber(label); if (n>=1 && n<=imp.getStackSize()) { if (imp.isHyperStack()) imp.setPosition(n); else imp.setSlice(n); } } Roi roi2 = (Roi)roi.clone(); Calibration cal = imp.getCalibration(); Rectangle r = roi2.getBounds(); if (cal.xOrigin!=0.0 || cal.yOrigin!=0.0) roi2.setLocation(r.x+(int)cal.xOrigin, r.y+(int)cal.yOrigin); int width= imp.getWidth(), height=imp.getHeight(); if (restoreCentered) { ImageCanvas ic = imp.getCanvas(); if (ic!=null) { Rectangle r1 = ic.getSrcRect(); Rectangle r2 = roi2.getBounds(); roi2.setLocation(r1.x+r1.width/2-r2.width/2, r1.y+r1.height/2-r2.height/2); } } if (r.x>=width || r.y>=height || (r.x+r.width)<=0 || (r.y+r.height)<=0) roi2.setLocation((width-r.width)/2, (height-r.height)/2); imp.setRoi(roi2); return true; } /** Returns the slice number associated with the specified name, or -1 if the name does not include a slice number. */ public int getSliceNumber(String label) { int slice = -1; if (label.length()>=14 && label.charAt(4)=='-' && label.charAt(9)=='-') slice = (int)Tools.parseDouble(label.substring(0,4),-1); else if (label.length()>=17 && label.charAt(5)=='-' && label.charAt(11)=='-') slice = (int)Tools.parseDouble(label.substring(0,5),-1); else if (label.length()>=20 && label.charAt(6)=='-' && label.charAt(13)=='-') slice = (int)Tools.parseDouble(label.substring(0,6),-1); return slice; } void open(String path) { Macro.setOptions(null); String name = null; if (path==null || path.equals("")) { OpenDialog od = new OpenDialog("Open Selection(s)...", ""); String directory = od.getDirectory(); name = od.getFileName(); if (name==null) return; path = directory + name; } if (Recorder.record) Recorder.record("roiManager", "Open", path); if (path.endsWith(".zip")) { openZip(path); return; } Opener o = new Opener(); if (name==null) name = o.getName(path); Roi roi = o.openRoi(path); if (roi!=null) { if (name.endsWith(".roi")) name = name.substring(0, name.length()-4); name = getUniqueName(name); list.add(name); rois.put(name, roi); } updateShowAll(); } // Modified on 2005/11/15 by Ulrik Stervbo to only read .roi files and to not empty the current list void openZip(String path) { ZipInputStream in = null; ByteArrayOutputStream out; int nRois = 0; try { in = new ZipInputStream(new FileInputStream(path)); byte[] buf = new byte[1024]; int len; ZipEntry entry = in.getNextEntry(); while (entry!=null) { String name = entry.getName(); if (name.endsWith(".roi")) { out = new ByteArrayOutputStream(); while ((len = in.read(buf)) > 0) out.write(buf, 0, len); out.close(); byte[] bytes = out.toByteArray(); RoiDecoder rd = new RoiDecoder(bytes, name); Roi roi = rd.getRoi(); if (roi!=null) { name = name.substring(0, name.length()-4); name = getUniqueName(name); list.add(name); rois.put(name, roi); nRois++; } } entry = in.getNextEntry(); } in.close(); } catch (IOException e) {error(e.toString());} if(nRois==0) error("This ZIP archive does not appear to contain \".roi\" files"); updateShowAll(); } String getUniqueName(String name) { String name2 = name; int n = 1; Roi roi2 = (Roi)rois.get(name2); while (roi2!=null) { roi2 = (Roi)rois.get(name2); if (roi2!=null) { int lastDash = name2.lastIndexOf("-"); if (lastDash!=-1 && name2.length()-lastDash<5) name2 = name2.substring(0, lastDash); name2 = name2+"-"+n; n++; } roi2 = (Roi)rois.get(name2); } return name2; } boolean save() { if (list.getItemCount()==0) return error("The selection list is empty."); int[] indexes = list.getSelectedIndexes(); if (indexes.length==0) indexes = getAllIndexes(); if (indexes.length>1) return saveMultiple(indexes, null); String name = list.getItem(indexes[0]); Macro.setOptions(null); SaveDialog sd = new SaveDialog("Save Selection...", name, ".roi"); String name2 = sd.getFileName(); if (name2 == null) return false; String dir = sd.getDirectory(); Roi roi = (Roi)rois.get(name); rois.remove(name); if (!name2.endsWith(".roi")) name2 = name2+".roi"; String newName = name2.substring(0, name2.length()-4); rois.put(newName, roi); roi.setName(newName); list.replaceItem(newName, indexes[0]); RoiEncoder re = new RoiEncoder(dir+name2); try { re.write(roi); } catch (IOException e) { IJ.error("ROI Manager", e.getMessage()); } return true; } boolean saveMultiple(int[] indexes, String path) { Macro.setOptions(null); if (path==null) { SaveDialog sd = new SaveDialog("Save ROIs...", "RoiSet", ".zip"); String name = sd.getFileName(); if (name == null) return false; if (!(name.endsWith(".zip") || name.endsWith(".ZIP"))) name = name + ".zip"; String dir = sd.getDirectory(); path = dir+name; } try { ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(path)); DataOutputStream out = new DataOutputStream(new BufferedOutputStream(zos)); RoiEncoder re = new RoiEncoder(out); for (int i=0; i1) allSliceOne = false; Roi roi = (Roi)rois.get(label); } int nSlices = 1; if (mode==MULTI) nSlices = imp.getStackSize(); int measurements = Analyzer.getMeasurements(); if (imp.getStackSize()>1) Analyzer.setMeasurements(measurements|Measurements.SLICE); int currentSlice = imp.getCurrentSlice(); for (int slice=1; slice<=nSlices; slice++) { if (nSlices>1) imp.setSlice(slice); for (int i=0; i1) IJ.run("Select None"); if (Recorder.record) Recorder.record("roiManager", "Measure"); return true; } /* void showIndexes(int[] indexes) { for (int i=0; i1) measureAll = true; onePerSlice = true; } else { GenericDialog gd = new GenericDialog("Multi Measure"); if (nSlices>1) gd.addCheckbox("Measure All "+nSlices+" Slices", measureAll); gd.addCheckbox("One Row Per Slice", onePerSlice); int columns = getColumnCount(imp, measurements)*indexes.length; String str = nSlices==1?"this option":"both options"; gd.setInsets(10, 25, 0); gd.addMessage( "Enabling "+str+" will result\n"+ "in a table with "+columns+" columns." ); gd.showDialog(); if (gd.wasCanceled()) return false; if (nSlices>1) measureAll = gd.getNextBoolean(); onePerSlice = gd.getNextBoolean(); } if (!measureAll) nSlices = 1; int currentSlice = imp.getCurrentSlice(); if (!onePerSlice) return measure(MULTI); Analyzer aSys = new Analyzer(imp); //System Analyzer ResultsTable rtSys = Analyzer.getResultsTable(); ResultsTable rtMulti = new ResultsTable(); Analyzer aMulti = new Analyzer(imp, measurements, rtMulti); //Private Analyzer for (int slice=1; slice<=nSlices; slice++) { int sliceUse = slice; if(nSlices == 1)sliceUse = currentSlice; imp.setSlice(sliceUse); rtMulti.incrementCounter(); int roiIndex = 0; for (int i=0; i0 && (name.length()<9||!Character.isDigit(name.charAt(0)))) suffix = "("+name+")"; } if (head!=null && col!=null && !head.equals("Slice")) rtMulti.addValue(head+suffix,rtSys.getValue(j,rtSys.getCounter()-1)); } } else break; } aMulti.displayResults(); aMulti.updateHeadings(); } imp.setSlice(currentSlice); if (indexes.length>1) IJ.run("Select None"); if (Recorder.record) Recorder.record("roiManager", "Multi Measure"); return true; } int getColumnCount(ImagePlus imp, int measurements) { ImageStatistics stats = imp.getStatistics(measurements); ResultsTable rt = new ResultsTable(); Analyzer analyzer = new Analyzer(imp, measurements, rt); analyzer.saveResults(stats, null); int count = 0; for (int i=0; i<=rt.getLastColumn(); i++) { float[] col = rt.getColumn(i); String head = rt.getColumnHeading(i); if (head!=null && col!=null) count++; } return count; } boolean drawOrFill(int mode) { int[] indexes = list.getSelectedIndexes(); if (indexes.length==0) indexes = getAllIndexes(); ImagePlus imp = WindowManager.getCurrentImage(); imp.killRoi(); ImageProcessor ip = imp.getProcessor(); ip.setColor(Toolbar.getForegroundColor()); ip.snapshot(); Undo.setup(Undo.FILTER, imp); Filler filler = mode==LABEL?new Filler():null; int slice = imp.getCurrentSlice(); for (int i=0; i=1 && slice2<=imp.getStackSize()) { imp.setSlice(slice2); ip = imp.getProcessor(); ip.setColor(Toolbar.getForegroundColor()); if (slice2!=slice) Undo.reset(); } switch (mode) { case DRAW: roi.drawPixels(ip); break; case FILL: ip.fillPolygon(roi.getPolygon()); break; case LABEL: roi.drawPixels(ip); filler.drawLabel(imp, ip, i+1, roi.getBounds()); break; } } ImageCanvas ic = imp.getCanvas(); if (ic!=null) ic.setShowAllROIs(false); imp.updateAndDraw(); String str=null; switch (mode) { case DRAW: str="Draw"; break; case FILL: str="Fill"; break; case LABEL: str="Label"; imp.updateAndDraw(); break; } if (Recorder.record) Recorder.record("roiManager", str); return true; } void combine() { ImagePlus imp = getImage(); if (imp==null) return; int[] indexes = list.getSelectedIndexes(); if (indexes.length==1) { error("More than one item must be selected, or none"); return; } if (indexes.length==0) indexes = getAllIndexes(); int nPointRois = 0; for (int i=0; i0) error(err); } void sort() { int n = rois.size(); if (n==0) return; String[] labels = new String[n]; int index = 0; for (Enumeration en=rois.keys(); en.hasMoreElements();) labels[index++] = (String)en.nextElement(); list.removeAll(); StringSorter.sort(labels); for (int i=0; icall("ij.plugin.frame.RoiManager.getName", index) Returns "null" if the Roi Manager is not open or index is out of range. */ public static String getName(String index) { int i = (int)Tools.parseDouble(index, -1); RoiManager instance = getInstance(); if (instance!=null && i>=0 && icmd is not one of these strings. */ public boolean runCommand(String cmd) { cmd = cmd.toLowerCase(); macro = true; boolean ok = true; if (cmd.equals("add")) add(IJ.shiftKeyDown(), IJ.altKeyDown()); else if (cmd.equals("add & draw")) addAndDraw(false); else if (cmd.equals("update")) update(); else if (cmd.equals("delete")) delete(false); else if (cmd.equals("measure")) measure(COMMAND); else if (cmd.equals("draw")) drawOrFill(DRAW); else if (cmd.equals("fill")) drawOrFill(FILL); else if (cmd.equals("label")) drawOrFill(LABEL); else if (cmd.equals("combine")) combine(); else if (cmd.equals("split")) split(); else if (cmd.equals("sort")) sort(); else if (cmd.startsWith("multi")) multiMeasure(); else if (cmd.equals("deselect")||cmd.indexOf("all")!=-1) { if (IJ.isMacOSX()) ignoreInterrupts = true; select(-1); } else if (cmd.equals("reset")) { if (IJ.isMacOSX() && IJ.isMacro()) ignoreInterrupts = true; list.removeAll(); rois.clear(); } else if (cmd.equals("debug")) { //IJ.log("Debug: "+debugCount); //for (int i=0; icmd is not "Open", "Save" or "Rename", or if an error occurs. */ public boolean runCommand(String cmd, String name) { cmd = cmd.toLowerCase(); macro = true; if (cmd.equals("open")) { open(name); macro = false; return true; } else if (cmd.equals("save")) { if (!name.endsWith(".zip") && !name.equals("")) return error("Name must end with '.zip'"); if (list.getItemCount()==0) return error("The selection list is empty."); int[] indexes = getAllIndexes(); boolean ok = false; if (name.equals("")) ok = saveMultiple(indexes, null); else ok = saveMultiple(indexes, name); macro = false; return ok; } else if (cmd.equals("rename")) { rename(name); macro = false; return true; } return false; } //public static String[] debug = new String[100000]; //public static int debugCount; public void select(int index) { int n = list.getItemCount(); if (index<0) { for (int i=0; i=n) return; boolean mm = list.isMultipleMode(); if (mm) list.setMultipleMode(false); int delay = 1; long start = System.currentTimeMillis(); while (true) { list.select(index); if (delay>1) IJ.wait(delay); if (list.isIndexSelected(index)) break; for (int i=0; i32) delay=32; if ((System.currentTimeMillis()-start)>1000L) error("Failed to select ROI "+index); } restore(index, true); if (mm) list.setMultipleMode(true); } public void select(int index, boolean shiftKeyDown, boolean altKeyDown) { if (!(shiftKeyDown||altKeyDown)) select(index); ImagePlus imp = IJ.getImage(); if (imp==null) return; Roi previousRoi = imp.getRoi(); if (previousRoi==null) {select(index); return;} Roi.previousRoi = (Roi)previousRoi.clone(); String label = list.getItem(index); Roi roi = (Roi)rois.get(label); if (roi!=null) { roi.setImage(imp); roi.update(shiftKeyDown, altKeyDown); } } void selectAll() { boolean allSelected = true; int count = list.getItemCount(); for (int i=0; i1) rot = 1; index += rot; if (index<0) index = 0; if (index>=list.getItemCount()) index = list.getItemCount(); //IJ.log(index+" "+rot); select(index); if (IJ.isWindows()) list.requestFocusInWindow(); } } public void mouseReleased (MouseEvent e) {} public void mouseClicked (MouseEvent e) {} public void mouseEntered (MouseEvent e) {} public void mouseExited (MouseEvent e) {} }