Search in sources :

Example 1 with DLabel

use of ini.trakem2.display.DLabel in project TrakEM2 by trakem2.

the class Display method actionPerformed.

@Override
public void actionPerformed(final ActionEvent ae) {
    dispatcher.exec(new Runnable() {

        @Override
        public void run() {
            final String command = ae.getActionCommand();
            if (command.startsWith("Job")) {
                if (Utils.checkYN("Really cancel job?")) {
                    project.getLoader().quitJob(command);
                    repairGUI();
                }
                return;
            } else if (command.equals("Move to top")) {
                if (null == active)
                    return;
                canvas.setUpdateGraphics(true);
                getLayerSet().addUndoMoveStep(active);
                layer.getParent().move(LayerSet.TOP, active);
                getLayerSet().addUndoMoveStep(active);
                Display.repaint(layer.getParent(), active, 5);
            // Display.updatePanelIndex(layer, active);
            } else if (command.equals("Move up")) {
                if (null == active)
                    return;
                canvas.setUpdateGraphics(true);
                getLayerSet().addUndoMoveStep(active);
                layer.getParent().move(LayerSet.UP, active);
                getLayerSet().addUndoMoveStep(active);
                Display.repaint(layer.getParent(), active, 5);
            // Display.updatePanelIndex(layer, active);
            } else if (command.equals("Move down")) {
                if (null == active)
                    return;
                canvas.setUpdateGraphics(true);
                getLayerSet().addUndoMoveStep(active);
                layer.getParent().move(LayerSet.DOWN, active);
                getLayerSet().addUndoMoveStep(active);
                Display.repaint(layer.getParent(), active, 5);
            // Display.updatePanelIndex(layer, active);
            } else if (command.equals("Move to bottom")) {
                if (null == active)
                    return;
                canvas.setUpdateGraphics(true);
                getLayerSet().addUndoMoveStep(active);
                layer.getParent().move(LayerSet.BOTTOM, active);
                getLayerSet().addUndoMoveStep(active);
                Display.repaint(layer.getParent(), active, 5);
            // Display.updatePanelIndex(layer, active);
            } else if (command.equals("Duplicate, link and send to next layer")) {
                duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
            } else if (command.equals("Duplicate, link and send to previous layer")) {
                duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
            } else if (command.equals("Duplicate, link and send to...")) {
                // fix non-scrolling popup menu
                Utils.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        final GenericDialog gd = new GenericDialog("Send to");
                        gd.addMessage("Duplicate, link and send to...");
                        final String[] sl = new String[layer.getParent().size()];
                        int next = 0;
                        for (final Layer la : layer.getParent().getLayers()) {
                            sl[next++] = project.findLayerThing(la).toString();
                        }
                        gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
                        gd.showDialog();
                        if (gd.wasCanceled())
                            return;
                        final Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
                        if (layer == la) {
                            Utils.showMessage("Can't duplicate, link and send to the same layer.");
                            return;
                        }
                        duplicateLinkAndSendTo(active, 0, la);
                    }
                });
            } else if (-1 != command.indexOf("z = ")) {
                // this is an item from the "Duplicate, link and send to" menu of layer z's
                final Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') + 1)));
                Utils.log2("layer: __" + command.substring(command.lastIndexOf(' ') + 1) + "__");
                if (null == target_layer)
                    return;
                duplicateLinkAndSendTo(active, 0, target_layer);
            } else if (-1 != command.indexOf("z=")) {
                // WARNING the indexOf is very similar to the previous one
                // Send the linked group to the selected layer
                final int iz = command.indexOf("z=") + 2;
                Utils.log2("iz=" + iz + "  other: " + command.indexOf(' ', iz + 2));
                int end = command.indexOf(' ', iz);
                if (-1 == end)
                    end = command.length();
                final double lz = Double.parseDouble(command.substring(iz, end));
                final Layer target = layer.getParent().getLayer(lz);
                // TODO what happens when ZDisplayable are selected?
                layer.getParent().move(selection.getAffected(), active.getLayer(), target);
            } else if (command.equals("Unlink")) {
                if (null == active || active instanceof Patch)
                    return;
                active.unlink();
                // selection.update();
                updateSelection();
            } else if (command.equals("Unlink from images")) {
                if (null == active)
                    return;
                try {
                    for (final Displayable displ : selection.getSelected()) {
                        displ.unlinkAll(Patch.class);
                    }
                    // selection.update();
                    updateSelection();
                } catch (final Exception e) {
                    IJError.print(e);
                }
            } else if (command.equals("Unlink slices")) {
                final YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
                if (!yn.yesPressed())
                    return;
                final ArrayList<Patch> pa = ((Patch) active).getStackPatches();
                for (int i = pa.size() - 1; i > 0; i--) {
                    pa.get(i).unlink(pa.get(i - 1));
                }
            } else if (command.equals("Send to next layer")) {
                final Rectangle box = selection.getBox();
                try {
                    // unlink Patch instances
                    for (final Displayable displ : selection.getSelected()) {
                        displ.unlinkAll(Patch.class);
                    }
                    // selection.update();
                    updateSelection();
                } catch (final Exception e) {
                    IJError.print(e);
                }
                // layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
                selection.moveDown();
                repaint(layer.getParent(), box);
            } else if (command.equals("Send to previous layer")) {
                final Rectangle box = selection.getBox();
                try {
                    // unlink Patch instances
                    for (final Displayable displ : selection.getSelected()) {
                        displ.unlinkAll(Patch.class);
                    }
                    // selection.update();
                    updateSelection();
                } catch (final Exception e) {
                    IJError.print(e);
                }
                // layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
                selection.moveUp();
                repaint(layer.getParent(), box);
            } else if (command.equals("Show centered")) {
                if (active == null)
                    return;
                showCentered(active);
            } else if (command.equals("Delete...")) {
                // remove all selected objects
                selection.deleteAll();
            } else if (command.equals("Color...")) {
                IJ.doCommand("Color Picker...");
            } else if (command.equals("Revert")) {
                if (null == active || active.getClass() != Patch.class)
                    return;
                final Patch p = (Patch) active;
                if (!p.revert()) {
                    if (null == p.getOriginalPath())
                        Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
                    else
                        Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
                }
            } else if (command.equals("Remove alpha mask")) {
                Display.removeAlphaMasks(selection.get(Patch.class));
            } else if (command.equals("Undo")) {
                Bureaucrat.createAndStart(new Worker.Task("Undo") {

                    @Override
                    public void exec() {
                        layer.getParent().undoOneStep();
                        Display.repaint(layer.getParent());
                    }
                }, project);
            } else if (command.equals("Redo")) {
                Bureaucrat.createAndStart(new Worker.Task("Redo") {

                    @Override
                    public void exec() {
                        layer.getParent().redoOneStep();
                        Display.repaint(layer.getParent());
                    }
                }, project);
            } else if (command.equals("Apply transform")) {
                canvas.applyTransform();
            } else if (command.equals("Apply transform propagating to last layer")) {
                if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
                    final LayerSet ls = getLayerSet();
                    // +1 to exclude current layer
                    final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(ls.indexOf(Display.this.layer) + 1, ls.size() - 1));
                    if (mode.getClass() == AffineTransformMode.class)
                        ((AffineTransformMode) mode).applyAndPropagate(subset);
                    else if (mode.getClass() == NonLinearTransformMode.class)
                        ((NonLinearTransformMode) mode).apply(subset);
                    setMode(new DefaultMode(Display.this));
                }
            } else if (command.equals("Apply transform propagating to first layer")) {
                if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
                    final LayerSet ls = getLayerSet();
                    // -1 to exclude current layer
                    final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(0, ls.indexOf(Display.this.layer) - 1));
                    if (mode.getClass() == AffineTransformMode.class)
                        ((AffineTransformMode) mode).applyAndPropagate(subset);
                    else if (mode.getClass() == NonLinearTransformMode.class)
                        ((NonLinearTransformMode) mode).apply(subset);
                    setMode(new DefaultMode(Display.this));
                }
            } else if (command.equals("Cancel transform")) {
                // calls getMode().cancel()
                canvas.cancelTransform();
            } else if (command.equals("Specify transform...")) {
                if (null == active)
                    return;
                selection.specify();
            } else if (command.equals("Exit inspection")) {
                getMode().cancel();
                setMode(new DefaultMode(Display.this));
            } else if (command.equals("Inspect image mesh triangles")) {
                setMode(new InspectPatchTrianglesMode(Display.this));
            } else if (command.equals("Hide all but images")) {
                final ArrayList<Class<?>> type = new ArrayList<Class<?>>();
                type.add(Patch.class);
                type.add(Stack.class);
                final Collection<Displayable> col = layer.getParent().hideExcept(type, false);
                selection.removeAll(col);
                Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
                Display.update(layer.getParent(), false);
            } else if (command.equals("Unhide all")) {
                Display.updateCheckboxes(layer.getParent().setAllVisible(false), DisplayablePanel.VISIBILITY_STATE);
                Display.update(layer.getParent(), false);
            } else if (command.startsWith("Hide all ")) {
                // skip the ending plural 's'
                final String type = command.substring(9, command.length() - 1);
                final Collection<Displayable> col = layer.getParent().setVisible(type, false, true);
                selection.removeAll(col);
                Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
            } else if (command.startsWith("Unhide all ")) {
                // skip the ending plural 's'
                String type = command.substring(11, command.length() - 1);
                type = type.substring(0, 1).toUpperCase() + type.substring(1);
                updateCheckboxes(layer.getParent().setVisible(type, true, true), DisplayablePanel.VISIBILITY_STATE);
            } else if (command.equals("Hide deselected")) {
                hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
            } else if (command.equals("Hide deselected except images")) {
                hideDeselected(true);
            } else if (command.equals("Hide selected")) {
                // TODO should deselect them too? I don't think so.
                selection.setVisible(false);
                Display.updateCheckboxes(selection.getSelected(), DisplayablePanel.VISIBILITY_STATE);
            } else if (command.equals("Resize canvas/LayerSet...")) {
                resizeCanvas();
            } else if (command.equals("Autoresize canvas/LayerSet")) {
                layer.getParent().setMinimumDimensions();
            } else if (command.equals("Resize canvas/LayerSet to ROI")) {
                final Roi roi = canvas.getFakeImagePlus().getRoi();
                if (null == roi) {
                    Utils.log("No ROI present!");
                    return;
                }
                resizeCanvas(roi.getBounds());
            } else if (command.equals("Import image")) {
                importImage();
            } else if (command.equals("Import next image")) {
                importNextImage();
            } else if (command.equals("Import stack...")) {
                Display.this.getLayerSet().addChangeTreesStep();
                final Rectangle sr = getCanvas().getSrcRect();
                final Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width / 2, sr.y + sr.height / 2, null, true, null, false);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        Display.this.getLayerSet().addChangeTreesStep();
                    }
                });
            } else if (command.equals("Import stack with landmarks...")) {
                // 1 - Find out if there's any other project open
                final List<Project> pr = Project.getProjects();
                if (1 == pr.size()) {
                    Utils.logAll("Need another project open!");
                    return;
                }
                // 2 - Ask for a "landmarks" type
                final GenericDialog gd = new GenericDialog("Landmarks");
                gd.addStringField("landmarks type:", "landmarks");
                final String[] none = { "-- None --" };
                final Hashtable<String, Project> mpr = new Hashtable<String, Project>();
                for (final Project p : pr) {
                    if (p == project)
                        continue;
                    mpr.put(p.toString(), p);
                }
                final String[] project_titles = mpr.keySet().toArray(new String[0]);
                final Hashtable<String, ProjectThing> map_target = findLandmarkNodes(project, "landmarks");
                final String[] target_landmark_titles = map_target.isEmpty() ? none : map_target.keySet().toArray(new String[0]);
                gd.addChoice("Landmarks node in this project:", target_landmark_titles, target_landmark_titles[0]);
                gd.addMessage("");
                gd.addChoice("Source project:", project_titles, project_titles[0]);
                final Hashtable<String, ProjectThing> map_source = findLandmarkNodes(mpr.get(project_titles[0]), "landmarks");
                final String[] source_landmark_titles = map_source.isEmpty() ? none : map_source.keySet().toArray(new String[0]);
                gd.addChoice("Landmarks node in source project:", source_landmark_titles, source_landmark_titles[0]);
                final List<Patch> stacks = Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet());
                String[] stack_titles;
                if (stacks.isEmpty()) {
                    if (1 == mpr.size()) {
                        IJ.showMessage("Project " + project_titles[0] + " does not contain any Stack.");
                        return;
                    }
                    stack_titles = none;
                } else {
                    stack_titles = new String[stacks.size()];
                    int next = 0;
                    for (final Patch pa : stacks) stack_titles[next++] = pa.toString();
                }
                gd.addChoice("Stacks:", stack_titles, stack_titles[0]);
                final Vector<?> vc = gd.getChoices();
                final Choice choice_target_landmarks = (Choice) vc.get(0);
                final Choice choice_source_projects = (Choice) vc.get(1);
                final Choice choice_source_landmarks = (Choice) vc.get(2);
                final Choice choice_stacks = (Choice) vc.get(3);
                final TextField input = (TextField) gd.getStringFields().get(0);
                input.addTextListener(new TextListener() {

                    @Override
                    public void textValueChanged(final TextEvent te) {
                        final String text = input.getText();
                        update(choice_target_landmarks, Display.this.project, text, map_target);
                        update(choice_source_landmarks, mpr.get(choice_source_projects.getSelectedItem()), text, map_source);
                    }

                    private void update(final Choice c, final Project p, final String type, final Hashtable<String, ProjectThing> table) {
                        table.clear();
                        table.putAll(findLandmarkNodes(p, type));
                        c.removeAll();
                        if (table.isEmpty())
                            c.add(none[0]);
                        else
                            for (final String t : table.keySet()) c.add(t);
                    }
                });
                choice_source_projects.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(final ItemEvent e) {
                        final String item = (String) e.getItem();
                        final Project p = mpr.get(choice_source_projects.getSelectedItem());
                        // 1 - Update choice of landmark items
                        map_source.clear();
                        map_source.putAll(findLandmarkNodes(p, input.getText()));
                        choice_target_landmarks.removeAll();
                        if (map_source.isEmpty())
                            choice_target_landmarks.add(none[0]);
                        else
                            for (final String t : map_source.keySet()) choice_target_landmarks.add(t);
                        // 2 - Update choice of Stack items
                        stacks.clear();
                        choice_stacks.removeAll();
                        stacks.addAll(Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet()));
                        if (stacks.isEmpty())
                            choice_stacks.add(none[0]);
                        else
                            for (final Patch pa : stacks) choice_stacks.add(pa.toString());
                    }
                });
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final String type = gd.getNextString();
                if (null == type || 0 == type.trim().length()) {
                    Utils.log("Invalid landmarks node type!");
                    return;
                }
                final ProjectThing target_landmarks_node = map_target.get(gd.getNextChoice());
                final Project source = mpr.get(gd.getNextChoice());
                final ProjectThing source_landmarks_node = map_source.get(gd.getNextChoice());
                final Patch stack_patch = stacks.get(gd.getNextChoiceIndex());
                // Store current state
                Display.this.getLayerSet().addLayerContentStep(layer);
                // Insert stack
                insertStack(target_landmarks_node, source, source_landmarks_node, stack_patch);
                // Store new state
                Display.this.getLayerSet().addChangeTreesStep();
            } else if (command.equals("Import grid...")) {
                Display.this.getLayerSet().addLayerContentStep(layer);
                final Bureaucrat burro = project.getLoader().importGrid(layer);
                if (null != burro)
                    burro.addPostTask(new Runnable() {

                        @Override
                        public void run() {
                            Display.this.getLayerSet().addLayerContentStep(layer);
                        }
                    });
            } else if (command.equals("Import sequence as grid...")) {
                Display.this.getLayerSet().addChangeTreesStep();
                final Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
                if (null != burro)
                    burro.addPostTask(new Runnable() {

                        @Override
                        public void run() {
                            Display.this.getLayerSet().addChangeTreesStep();
                        }
                    });
            } else if (command.equals("Import from text file...")) {
                Display.this.getLayerSet().addChangeTreesStep();
                final Bureaucrat burro = project.getLoader().importImages(layer);
                if (null != burro)
                    burro.addPostTask(new Runnable() {

                        @Override
                        public void run() {
                            Display.this.getLayerSet().addChangeTreesStep();
                        }
                    });
            } else if (command.equals("Import labels as arealists...")) {
                Display.this.getLayerSet().addChangeTreesStep();
                final Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        Display.this.getLayerSet().addChangeTreesStep();
                    }
                });
            } else if (command.equals("Make flat image...")) {
                // if there's a ROI, just use that as cropping rectangle
                Rectangle srcRect = null;
                final Roi roi = canvas.getFakeImagePlus().getRoi();
                if (null != roi) {
                    srcRect = roi.getBounds();
                } else {
                    // otherwise, whatever is visible
                    // srcRect = canvas.getSrcRect();
                    // The above is confusing. That is what ROIs are for. So paint all:
                    srcRect = new Rectangle(0, 0, (int) Math.ceil(layer.getParent().getLayerWidth()), (int) Math.ceil(layer.getParent().getLayerHeight()));
                }
                double scale = 1.0;
                final String[] types = new String[] { "8-bit grayscale", "RGB Color" };
                int the_type = ImagePlus.GRAY8;
                final GenericDialog gd = new GenericDialog("Choose", frame);
                gd.addSlider("Scale: ", 1, 100, 100);
                gd.addNumericField("Width: ", srcRect.width, 0);
                gd.addNumericField("height: ", srcRect.height, 0);
                // connect the above 3 fields:
                final Vector<?> numfields = gd.getNumericFields();
                final UpdateDimensionField udf = new UpdateDimensionField(srcRect.width, srcRect.height, (TextField) numfields.get(1), (TextField) numfields.get(2), (TextField) numfields.get(0), (Scrollbar) gd.getSliders().get(0));
                for (final Object ob : numfields) ((TextField) ob).addTextListener(udf);
                gd.addChoice("Type: ", types, types[0]);
                if (layer.getParent().size() > 1) {
                    // / $#%! where are my lisp macros
                    Utils.addLayerRangeChoices(Display.this.layer, gd);
                    gd.addCheckbox("Include non-empty layers only", true);
                }
                gd.addMessage("Background color:");
                Utils.addRGBColorSliders(gd, Color.black);
                gd.addCheckbox("Best quality", false);
                gd.addMessage("");
                final String[] choices = new String[] { "Show", "Save to file", "Save for web (CATMAID)" };
                gd.addChoice("Export:", choices, choices[0]);
                final String[] formats = Saver.formats();
                gd.addChoice("Format:", formats, formats[0]);
                gd.addNumericField("Tile_side", 256, 0);
                final Choice cformats = (Choice) gd.getChoices().get(gd.getChoices().size() - 1);
                cformats.setEnabled(false);
                final Choice cchoices = (Choice) gd.getChoices().get(gd.getChoices().size() - 2);
                final TextField tf = (TextField) gd.getNumericFields().get(gd.getNumericFields().size() - 1);
                tf.setEnabled(false);
                cchoices.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(final ItemEvent e) {
                        cformats.setEnabled(cchoices.getSelectedIndex() > 0);
                        if (2 == cchoices.getSelectedIndex()) {
                            cformats.select(".jpg");
                            tf.setEnabled(true);
                        } else {
                            tf.setEnabled(false);
                        }
                    }
                });
                gd.addCheckbox("Use original images", true);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                scale = gd.getNextNumber() / 100;
                the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
                if (Double.isNaN(scale) || scale <= 0.0) {
                    Utils.showMessage("Invalid scale.");
                    return;
                }
                // consuming and ignoring width and height:
                gd.getNextNumber();
                gd.getNextNumber();
                Layer[] layer_array = null;
                boolean non_empty_only = false;
                if (layer.getParent().size() > 1) {
                    non_empty_only = gd.getNextBoolean();
                    final int i_start = gd.getNextChoiceIndex();
                    final int i_end = gd.getNextChoiceIndex();
                    final ArrayList<Layer> al = new ArrayList<Layer>();
                    final ArrayList<ZDisplayable> al_zd = layer.getParent().getZDisplayables();
                    final ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
                    al_zd.toArray(zd);
                    for (int i = i_start, j = 0; i <= i_end; i++, j++) {
                        final Layer la = layer.getParent().getLayer(i);
                        // checks both the Layer and the ZDisplayable objects in the parent LayerSet
                        if (!la.isEmpty() || !non_empty_only)
                            al.add(la);
                    }
                    if (0 == al.size()) {
                        Utils.showMessage("All layers are empty!");
                        return;
                    }
                    layer_array = new Layer[al.size()];
                    al.toArray(layer_array);
                } else {
                    layer_array = new Layer[] { Display.this.layer };
                }
                final Color background = new Color((int) gd.getNextNumber(), (int) gd.getNextNumber(), (int) gd.getNextNumber());
                final boolean quality = gd.getNextBoolean();
                final int choice = gd.getNextChoiceIndex();
                final boolean save_to_file = 1 == choice;
                final boolean save_for_web = 2 == choice;
                final String format = gd.getNextChoice();
                final Saver saver = new Saver(format);
                final int tile_side = (int) gd.getNextNumber();
                final boolean use_original_images = gd.getNextBoolean();
                // in its own thread
                if (save_for_web)
                    project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type, null, use_original_images, saver, tile_side);
                else
                    project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, format, quality, background);
            } else if (command.equals("Lock")) {
                selection.setLocked(true);
                Utils.revalidateComponent(tabs.getSelectedComponent());
            } else if (command.equals("Unlock")) {
                selection.setLocked(false);
                Utils.revalidateComponent(tabs.getSelectedComponent());
            } else if (command.equals("Properties...")) {
                switch(selection.getSelected().size()) {
                    case 0:
                        return;
                    case 1:
                        active.adjustProperties();
                        break;
                    default:
                        adjustGroupProperties(selection.getSelected());
                        break;
                }
                updateSelection();
            } else if (command.equals("Measurement options...")) {
                adjustMeasurementOptions();
            } else if (command.equals("Show current 2D position in 3D")) {
                final Point p = canvas.consumeLastPopupPoint();
                if (null == p)
                    return;
                Display3D.addFatPoint("Current 2D Position", getLayerSet(), p.x, p.y, layer.getZ(), 10, Color.magenta);
            } else if (command.equals("Show layers as orthoslices in 3D")) {
                final GenericDialog gd = new GenericDialog("Options");
                final Roi roi = canvas.getFakeImagePlus().getRoi();
                final Rectangle r = null == roi ? getLayerSet().get2DBounds() : roi.getBounds();
                gd.addMessage("ROI 2D bounds:");
                gd.addNumericField("x:", r.x, 0, 30, "pixels");
                gd.addNumericField("y:", r.y, 0, 30, "pixels");
                gd.addNumericField("width:", r.width, 0, 30, "pixels");
                gd.addNumericField("height:", r.height, 0, 30, "pixels");
                gd.addMessage("Layers to include:");
                Utils.addLayerRangeChoices(layer, gd);
                gd.addMessage("Constrain dimensions to:");
                gd.addNumericField("max width and height:", getLayerSet().getPixelsMaxDimension(), 0, 30, "pixels");
                gd.addMessage("Options:");
                final String[] types = { "Greyscale", "Color RGB" };
                gd.addChoice("Image type:", types, types[0]);
                gd.addCheckbox("Invert images", false);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final int x = (int) gd.getNextNumber(), y = (int) gd.getNextNumber(), width = (int) gd.getNextNumber(), height = (int) gd.getNextNumber();
                final int first = gd.getNextChoiceIndex(), last = gd.getNextChoiceIndex();
                final List<Layer> layers = getLayerSet().getLayers(first, last);
                final int max_dim = Math.min((int) gd.getNextNumber(), Math.max(width, height));
                float scale = 1;
                if (max_dim < Math.max(width, height)) {
                    scale = max_dim / (float) Math.max(width, height);
                }
                final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
                final boolean invert = gd.getNextBoolean();
                final LayerStack stack = new LayerStack(layers, new Rectangle(x, y, width, height), scale, type, Patch.class, max_dim, invert);
                Display3D.showOrthoslices(stack.getImagePlus(), "LayerSet [" + x + "," + y + "," + width + "," + height + "] " + first + "--" + last, x, y, scale, layers.get(0));
            } else if (command.equals("Align stack slices")) {
                if (getActive() instanceof Patch) {
                    final Patch slice = (Patch) getActive();
                    if (slice.isStack()) {
                        // check linked group
                        final HashSet hs = slice.getLinkedGroup(new HashSet());
                        for (final Iterator it = hs.iterator(); it.hasNext(); ) {
                            if (it.next().getClass() != Patch.class) {
                                // labels should be fine, need to check that
                                Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them.");
                                return;
                            }
                        }
                        final LayerSet ls = slice.getLayerSet();
                        final HashSet<Displayable> linked = slice.getLinkedGroup(null);
                        ls.addTransformStepWithData(linked);
                        // will repaint
                        final Bureaucrat burro = AlignTask.registerStackSlices((Patch) getActive());
                        burro.addPostTask(new Runnable() {

                            @Override
                            public void run() {
                                ls.enlargeToFit(linked);
                                // The current state when done
                                ls.addTransformStepWithData(linked);
                            }
                        });
                    } else {
                        Utils.log("Align stack slices: selected image is not part of a stack.");
                    }
                }
            } else if (command.equals("Align layers manually with landmarks")) {
                setMode(new ManualAlignMode(Display.this));
            } else if (command.equals("Align layers")) {
                Roi roi = canvas.getFakeImagePlus().getRoi();
                if (null != roi) {
                    final YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Use ROI?", "Snapshot layers using the ROI bounds?\n" + roi.getBounds());
                    if (yn.cancelPressed())
                        return;
                    if (!yn.yesPressed()) {
                        roi = null;
                    }
                }
                // caching, since scroll wheel may change it
                final Layer la = layer;
                la.getParent().addTransformStep(la.getParent().getLayers());
                final Bureaucrat burro = AlignLayersTask.alignLayersTask(la, null == roi ? null : roi.getBounds());
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
                        la.getParent().addTransformStep(la.getParent().getLayers());
                    }
                });
            } else if (command.equals("Align multi-layer mosaic")) {
                // caching, since scroll wheel may change it
                final Layer la = layer;
                la.getParent().addTransformStep();
                final Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask(la, active instanceof Patch ? (Patch) active : null);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
                        la.getParent().addTransformStep();
                    }
                });
            } else if (command.equals("Montage all images in this layer")) {
                final Layer la = layer;
                final List<Patch> patches = new ArrayList<Patch>((List<Patch>) (List) la.getDisplayables(Patch.class, true));
                if (patches.size() < 2) {
                    Utils.showMessage("Montage needs 2 or more visible images");
                    return;
                }
                final Collection<Displayable> col = la.getParent().addTransformStepWithDataForAll(Arrays.asList(new Layer[] { la }));
                // find any locked or selected patches
                final HashSet<Patch> fixed = new HashSet<Patch>();
                for (final Patch p : patches) {
                    if (p.isLocked2() || selection.contains(p))
                        fixed.add(p);
                }
                if (patches.size() == fixed.size()) {
                    Utils.showMessage("Can't do", "No montage possible: all images are selected,\nand hence all are considered locked.\nSelect only one image to be used as reference, or none.");
                    return;
                }
                Utils.log("Using " + fixed.size() + " image" + (fixed.size() == 1 ? "" : "s") + " as reference.");
                final Bureaucrat burro = AlignTask.alignPatchesTask(patches, fixed);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().enlargeToFit(patches);
                        la.getParent().addTransformStepWithData(col);
                    }
                });
            } else if (command.equals("Montage selected images")) {
                final Layer la = layer;
                if (selection.getSelected(Patch.class).size() < 2) {
                    Utils.showMessage("Montage needs 2 or more images selected");
                    return;
                }
                final Collection<Displayable> col = la.getParent().addTransformStepWithDataForAll(Arrays.asList(new Layer[] { la }));
                final Bureaucrat burro = AlignTask.alignSelectionTask(selection);
                if (null == burro)
                    return;
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        la.getParent().enlargeToFit(selection.getAffected());
                        la.getParent().addTransformStepWithData(col);
                    }
                });
            } else if (command.equals("Montage multiple layers")) {
                final GenericDialog gd = new GenericDialog("Choose range");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
                final Collection<Displayable> col = getLayerSet().addTransformStepWithDataForAll(layers);
                final Bureaucrat burro = AlignTask.montageLayersTask(layers);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        final Collection<Displayable> ds = new ArrayList<Displayable>();
                        for (final Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
                        getLayerSet().enlargeToFit(ds);
                        getLayerSet().addTransformStepWithData(col);
                    }
                });
            } else if (command.equals("Properties ...")) {
                // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
                adjustProperties();
            } else if (command.equals("Adjust snapping parameters...")) {
                AlignTask.p_snap.setup("Snap");
            } else if (command.equals("Adjust fast-marching parameters...")) {
                Segmentation.fmp.setup();
            } else if (command.equals("Adjust arealist paint parameters...")) {
                AreaWrapper.PP.setup();
            } else if (command.equals("Fill ROI in alpha mask")) {
                if (active.getClass() == Patch.class) {
                    ((Patch) active).keyPressed(new KeyEvent(getCanvas(), -1, System.currentTimeMillis(), 0, KeyEvent.VK_F, 'f'));
                }
            } else if (command.equals("Fill inverse ROI in alpha mask")) {
                if (active.getClass() == Patch.class) {
                    ((Patch) active).keyPressed(new KeyEvent(getCanvas(), -1, System.currentTimeMillis(), Event.SHIFT_MASK, KeyEvent.VK_F, 'f'));
                }
            } else if (command.equals("Search...")) {
                Search.showWindow();
            } else if (command.equals("Select all")) {
                selection.selectAll();
                repaint(Display.this.layer, selection.getBox(), 0);
            } else if (command.equals("Select all visible")) {
                selection.selectAllVisible();
                repaint(Display.this.layer, selection.getBox(), 0);
            } else if (command.equals("Select all that match...")) {
                final List<Displayable> ds = find();
                selection.selectAll(ds);
                Utils.showStatus("Added " + ds.size() + " to selection.");
            } else if (command.equals("Select none")) {
                final Rectangle box = selection.getBox();
                selection.clear();
                repaint(Display.this.layer, box, 0);
            } else if (command.equals("Restore selection")) {
                selection.restore();
            } else if (command.equals("Select under ROI")) {
                final Roi roi = canvas.getFakeImagePlus().getRoi();
                if (null == roi)
                    return;
                selection.selectAll(roi, true);
            } else if (command.equals("Merge") || command.equals("Split")) {
                final Bureaucrat burro = Bureaucrat.create(new Worker.Task(command + "ing AreaLists") {

                    @Override
                    public void exec() {
                        final ArrayList<Displayable> al_sel = selection.getSelected(AreaList.class);
                        // put active at the beginning, to work as the base on which other's will get merged
                        al_sel.remove(Display.this.active);
                        al_sel.add(0, Display.this.active);
                        final Set<DoStep> dataedits = new HashSet<DoStep>();
                        if (command.equals("Merge")) {
                            // Add data undo for active only, which will be edited
                            dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[] { "data" }));
                            getLayerSet().addChangeTreesStep(dataedits);
                            final AreaList ali = AreaList.merge(al_sel);
                            if (null != ali) {
                                // remove all but the first from the selection
                                for (int i = 1; i < al_sel.size(); i++) {
                                    final Object ob = al_sel.get(i);
                                    if (ob.getClass() == AreaList.class) {
                                        selection.remove((Displayable) ob);
                                    }
                                }
                                selection.updateTransform(ali);
                                repaint(ali.getLayerSet(), ali, 0);
                            }
                        } else if (command.equals("Split")) {
                            // Add data undo for every AreaList
                            for (final Displayable d : al_sel) {
                                if (d.getClass() != AreaList.class)
                                    continue;
                                dataedits.add(new Displayable.DoEdit(d).init(d, new String[] { "data" }));
                            }
                            getLayerSet().addChangeTreesStep(dataedits);
                            try {
                                List<AreaList> alis = AreaList.split(al_sel);
                                for (AreaList ali : alis) {
                                    if (selection.contains(ali))
                                        continue;
                                    selection.add(ali);
                                }
                            } catch (Exception e) {
                                IJError.print(e);
                                getLayerSet().undoOneStep();
                            }
                        }
                    }
                }, Display.this.project);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        final Set<DoStep> dataedits = new HashSet<DoStep>();
                        dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[] { "data" }));
                        getLayerSet().addChangeTreesStep(dataedits);
                    }
                });
                burro.goHaveBreakfast();
            } else if (command.equals("Reroot")) {
                if (!(active instanceof Tree<?>))
                    return;
                getLayerSet().addDataEditStep(active);
                if (((Tree) active).reRoot(((Tree) active).getLastVisited())) {
                    getLayerSet().addDataEditStep(active);
                    Display.repaint(getLayerSet());
                } else {
                    getLayerSet().removeLastUndoStep();
                }
            } else if (command.equals("Part subtree")) {
                if (!(active instanceof Tree<?>))
                    return;
                if (!Utils.check("Really part the subtree?"))
                    return;
                final LayerSet.DoChangeTrees step = getLayerSet().addChangeTreesStep();
                final Set<DoStep> deps = new HashSet<DoStep>();
                // I hate java
                deps.add(new Displayable.DoEdit(active).init(active, new String[] { "data" }));
                step.addDependents(deps);
                final List<ZDisplayable> ts = ((Tree) active).splitAt(((Tree) active).getLastVisited());
                if (null == ts) {
                    getLayerSet().removeLastUndoStep();
                    return;
                }
                final Displayable elder = Display.this.active;
                final HashSet<DoStep> deps2 = new HashSet<DoStep>();
                for (final ZDisplayable t : ts) {
                    deps2.add(new Displayable.DoEdit(t).init(t, new String[] { "data" }));
                    if (t == elder)
                        continue;
                    // will change Display.this.active !
                    getLayerSet().add(t);
                    project.getProjectTree().addSibling(elder, t);
                }
                selection.clear();
                selection.selectAll(ts);
                selection.add(elder);
                final LayerSet.DoChangeTrees step2 = getLayerSet().addChangeTreesStep();
                step2.addDependents(deps2);
                Display.repaint(getLayerSet());
            } else if (command.equals("Show tabular view")) {
                if (!(active instanceof Tree<?>))
                    return;
                ((Tree<?>) active).createMultiTableView();
            } else if (command.equals("Mark")) {
                if (!(active instanceof Tree<?>))
                    return;
                final Point p = canvas.consumeLastPopupPoint();
                if (null == p)
                    return;
                if (((Tree<?>) active).markNear(p.x, p.y, layer, canvas.getMagnification())) {
                    Display.repaint(getLayerSet());
                }
            } else if (command.equals("Clear marks (selected Trees)")) {
                for (final Tree<?> t : selection.get(Tree.class)) {
                    t.unmark();
                }
                Display.repaint(getLayerSet());
            } else if (command.equals("Join")) {
                if (!(active instanceof Tree<?>))
                    return;
                final List<Tree<?>> tlines = (List<Tree<?>>) selection.get(active.getClass());
                if (((Tree) active).canJoin(tlines)) {
                    final int nNodes_active = ((Tree) active).getRoot().getSubtreeNodes().size();
                    String warning = "";
                    for (final Tree<?> t : tlines) {
                        if (active == t)
                            continue;
                        if (null == t.getRoot()) {
                            Utils.log("Removed empty tree #" + t.getId() + " from those to join.");
                            tlines.remove(t);
                            continue;
                        }
                        if (t.getRoot().getSubtreeNodes().size() > nNodes_active) {
                            warning = "\nWARNING joining into a tree that is not the largest!";
                            break;
                        }
                    }
                    if (!Utils.check("Join these " + tlines.size() + " trees into the tree " + active + " ?" + warning))
                        return;
                    // Record current state
                    final Set<DoStep> dataedits = new HashSet<DoStep>(tlines.size());
                    for (final Tree<?> tl : tlines) {
                        dataedits.add(new Displayable.DoEdit(tl).init(tl, new String[] { "data" }));
                    }
                    getLayerSet().addChangeTreesStep(dataedits);
                    // 
                    ((Tree) active).join(tlines);
                    for (final Tree<?> tl : tlines) {
                        if (tl == active)
                            continue;
                        tl.remove2(false);
                    }
                    Display.repaint(getLayerSet());
                    // Again, to record current state (just the joined tree this time)
                    final Set<DoStep> dataedits2 = new HashSet<DoStep>(1);
                    dataedits2.add(new Displayable.DoEdit(active).init(active, new String[] { "data" }));
                    getLayerSet().addChangeTreesStep(dataedits2);
                } else {
                    Utils.showMessage("Can't do", "Only one tree is selected.\nSelect more than one tree to perform a join operation!");
                }
            } else if (command.equals("Previous branch node or start")) {
                if (!(active instanceof Tree<?>))
                    return;
                final Point p = canvas.consumeLastPopupPoint();
                if (null == p)
                    return;
                center(((Treeline) active).findPreviousBranchOrRootPoint(p.x, p.y, layer, canvas));
            } else if (command.equals("Next branch node or end")) {
                if (!(active instanceof Tree<?>))
                    return;
                final Point p = canvas.consumeLastPopupPoint();
                if (null == p)
                    return;
                center(((Tree<?>) active).findNextBranchOrEndPoint(p.x, p.y, layer, canvas));
            } else if (command.equals("Root")) {
                if (!(active instanceof Tree<?>))
                    return;
                final Point p = canvas.consumeLastPopupPoint();
                if (null == p)
                    return;
                center(((Tree) active).createCoordinate(((Tree<?>) active).getRoot()));
            } else if (command.equals("Last added node")) {
                if (!(active instanceof Tree<?>))
                    return;
                center(((Treeline) active).getLastAdded());
            } else if (command.equals("Last edited node")) {
                if (!(active instanceof Tree<?>))
                    return;
                center(((Treeline) active).getLastEdited());
            } else if (command.equals("Reverse point order")) {
                if (!(active instanceof Pipe))
                    return;
                getLayerSet().addDataEditStep(active);
                ((Pipe) active).reverse();
                Display.repaint(Display.this.layer);
                getLayerSet().addDataEditStep(active);
            } else if (command.equals("View orthoslices")) {
                if (!(active instanceof Patch))
                    return;
                Display3D.showOrthoslices(((Patch) active));
            } else if (command.equals("View volume")) {
                if (!(active instanceof Patch))
                    return;
                Display3D.showVolume(((Patch) active));
            } else if (command.equals("Show in 3D")) {
                for (final ZDisplayable zd : selection.get(ZDisplayable.class)) {
                    Display3D.show(zd.getProject().findProjectThing(zd));
                }
                // handle profile lists ...
                final HashSet<ProjectThing> hs = new HashSet<ProjectThing>();
                for (final Profile d : selection.get(Profile.class)) {
                    final ProjectThing profile_list = (ProjectThing) d.getProject().findProjectThing(d).getParent();
                    if (!hs.contains(profile_list)) {
                        Display3D.show(profile_list);
                        hs.add(profile_list);
                    }
                }
            } else if (command.equals("Snap")) {
                // Take the active if it's a Patch
                if (!(active instanceof Patch))
                    return;
                Display.snap((Patch) active);
            } else if (command.equals("Blend") || command.equals("Blend (selected images)...")) {
                final HashSet<Patch> patches = new HashSet<Patch>(selection.get(Patch.class));
                if (patches.size() > 1) {
                    final GenericDialog gd = new GenericDialog("Blending");
                    gd.addCheckbox("Respect current alpha mask", true);
                    gd.showDialog();
                    if (gd.wasCanceled())
                        return;
                    Blending.blend(patches, gd.getNextBoolean());
                } else {
                    IJ.log("Please select more than one overlapping image.");
                }
            } else if (command.equals("Blend (layer-wise)...")) {
                final GenericDialog gd = new GenericDialog("Blending");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.addCheckbox("Respect current alpha mask", true);
                gd.addMessage("Filter:");
                gd.addStringField("Use only images whose title matches:", "", 30);
                gd.addCheckbox("Blend visible patches only", true);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final boolean respect_alpha_mask = gd.getNextBoolean();
                final String toMatch = gd.getNextString().trim();
                final String regex = 0 == toMatch.length() ? null : ".*" + toMatch + ".*";
                final boolean visible_only = gd.getNextBoolean();
                Blending.blendLayerWise(getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()), respect_alpha_mask, new Filter<Patch>() {

                    @Override
                    public final boolean accept(final Patch patch) {
                        if (visible_only && !patch.isVisible())
                            return false;
                        if (null == regex)
                            return true;
                        return patch.getTitle().matches(regex);
                    }
                });
            } else if (command.equals("Match intensities (layer-wise)...")) {
                Bureaucrat.createAndStart(new Worker.Task("Match intensities") {

                    @Override
                    public void exec() {
                        final MatchIntensities matching = new MatchIntensities();
                        matching.invoke(getActive());
                    }
                }, project);
            } else if (command.equals("Remove intensity maps (layer-wise)...")) {
                final GenericDialog gd = new GenericDialog("Remove intensity maps");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                Bureaucrat.createAndStart(new Worker.Task("Match intensities") {

                    @Override
                    public void exec() {
                        for (final Layer layer : getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex())) {
                            for (final Displayable p : layer.getDisplayables(Patch.class)) {
                                final Patch patch = (Patch) p;
                                if (patch.clearIntensityMap()) {
                                    patch.updateMipMaps();
                                }
                            }
                        }
                    }
                }, project);
            } else if (command.equals("Montage")) {
                final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
                // make an undo step!
                final LayerSet ls = layer.getParent();
                ls.addTransformStepWithData(affected);
                final Bureaucrat burro = AlignTask.alignSelectionTask(selection);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        ls.enlargeToFit(affected);
                        ls.addTransformStepWithData(affected);
                    }
                });
            } else if (command.equals("Lens correction")) {
                final Layer la = layer;
                la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
                final Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection(selection);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        // no means to know which where modified and from which layers!
                        la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
                    }
                });
            } else if (command.equals("Link images...")) {
                final GenericDialog gd = new GenericDialog("Options");
                gd.addMessage("Linking images to images (within their own layer only):");
                final String[] options = { "all images to all images", "each image with any other overlapping image" };
                gd.addChoice("Link: ", options, options[1]);
                final String[] options2 = { "selected images only", "all images in this layer", "all images in all layers, within the layer only", "all images in all layers, within and across consecutive layers" };
                gd.addChoice("Apply to: ", options2, options2[0]);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final Layer lay = layer;
                final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
                lay.getParent().addDataEditStep(ds, new String[] { "data" });
                final boolean overlapping_only = 1 == gd.getNextChoiceIndex();
                Collection<Displayable> coll = null;
                switch(gd.getNextChoiceIndex()) {
                    case 0:
                        coll = selection.getSelected(Patch.class);
                        Patch.crosslink(coll, overlapping_only);
                        break;
                    case 1:
                        coll = lay.getDisplayables(Patch.class);
                        Patch.crosslink(coll, overlapping_only);
                        break;
                    case 2:
                        coll = new ArrayList<Displayable>();
                        for (final Layer la : lay.getParent().getLayers()) {
                            final Collection<Displayable> acoll = la.getDisplayables(Patch.class);
                            Patch.crosslink(acoll, overlapping_only);
                            coll.addAll(acoll);
                        }
                        break;
                    case 3:
                        final ArrayList<Layer> layers = lay.getParent().getLayers();
                        Collection<Displayable> lc1 = layers.get(0).getDisplayables(Patch.class);
                        if (lay == layers.get(0))
                            coll = lc1;
                        for (int i = 1; i < layers.size(); i++) {
                            final Collection<Displayable> lc2 = layers.get(i).getDisplayables(Patch.class);
                            if (null == coll && Display.this.layer == layers.get(i))
                                coll = lc2;
                            final Collection<Displayable> both = new ArrayList<Displayable>();
                            both.addAll(lc1);
                            both.addAll(lc2);
                            Patch.crosslink(both, overlapping_only);
                            lc1 = lc2;
                        }
                        break;
                }
                if (null != coll)
                    Display.updateCheckboxes(coll, DisplayablePanel.LINK_STATE, true);
                lay.getParent().addDataEditStep(ds);
            } else if (command.equals("Unlink all selected images")) {
                if (Utils.check("Really unlink selected images?")) {
                    final Collection<Displayable> ds = selection.getSelected(Patch.class);
                    for (final Displayable d : ds) {
                        d.unlink();
                    }
                    Display.updateCheckboxes(ds, DisplayablePanel.LINK_STATE);
                }
            } else if (command.equals("Unlink all")) {
                if (Utils.check("Really unlink all objects from all layers?")) {
                    final Collection<Displayable> ds = layer.getParent().getDisplayables();
                    for (final Displayable d : ds) {
                        d.unlink();
                    }
                    Display.updateCheckboxes(ds, DisplayablePanel.LINK_STATE);
                }
            } else if (command.equals("Calibration...")) {
                try {
                    IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
                    Display.updateTitle(getLayerSet());
                    // repaint layer names
                    project.getLayerTree().updateUILater();
                } catch (final RuntimeException re) {
                    Utils.log2("Calibration dialog canceled.");
                }
            } else if (command.equals("Grid overlay...")) {
                if (null == gridoverlay)
                    gridoverlay = new GridOverlay();
                gridoverlay.setup(canvas.getFakeImagePlus().getRoi());
                canvas.repaint(false);
            } else if (command.equals("Enhance contrast (selected images)...")) {
                final Layer la = layer;
                final ArrayList<Displayable> selected = selection.getSelected(Patch.class);
                final HashSet<Displayable> ds = new HashSet<Displayable>(selected);
                la.getParent().addDataEditStep(ds);
                final Displayable active = Display.this.getActive();
                final Patch ref = active.getClass() == Patch.class ? (Patch) active : null;
                final Bureaucrat burro = getProject().getLoader().enhanceContrast(selected, ref);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        la.getParent().addDataEditStep(ds);
                    }
                });
            } else if (command.equals("Enhance contrast layer-wise...")) {
                // ask for range of layers
                final GenericDialog gd = new GenericDialog("Choose range");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                // exclusive end
                final java.util.List<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() + 1);
                final HashSet<Displayable> ds = new HashSet<Displayable>();
                for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
                getLayerSet().addDataEditStep(ds);
                final Bureaucrat burro = project.getLoader().enhanceContrast(layers);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().addDataEditStep(ds);
                    }
                });
            } else if (command.equals("Adjust image filters (selected images)")) {
                if (selection.isEmpty() || !(active instanceof Patch))
                    return;
                FilterEditor.GUI(selection.get(Patch.class), (Patch) active);
            } else if (command.equals("Set Min and Max layer-wise...")) {
                final Displayable active = getActive();
                double min = 0;
                double max = 0;
                if (null != active && active.getClass() == Patch.class) {
                    min = ((Patch) active).getMin();
                    max = ((Patch) active).getMax();
                }
                final GenericDialog gd = new GenericDialog("Min and Max");
                gd.addMessage("Set min and max to all images in the layer range");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.addNumericField("min: ", min, 2);
                gd.addNumericField("max: ", max, 2);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                // 
                min = gd.getNextNumber();
                max = gd.getNextNumber();
                final ArrayList<Displayable> al = new ArrayList<Displayable>();
                for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() + 1)) {
                    // exclusive end
                    al.addAll(la.getDisplayables(Patch.class));
                }
                final HashSet<Displayable> ds = new HashSet<Displayable>(al);
                getLayerSet().addDataEditStep(ds);
                final Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().addDataEditStep(ds);
                    }
                });
            } else if (command.equals("Set Min and Max (selected images)...")) {
                final Displayable active = getActive();
                double min = 0;
                double max = 0;
                if (null != active && active.getClass() == Patch.class) {
                    min = ((Patch) active).getMin();
                    max = ((Patch) active).getMax();
                }
                final GenericDialog gd = new GenericDialog("Min and Max");
                gd.addMessage("Set min and max to all selected images");
                gd.addNumericField("min: ", min, 2);
                gd.addNumericField("max: ", max, 2);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                // 
                min = gd.getNextNumber();
                max = gd.getNextNumber();
                final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
                getLayerSet().addDataEditStep(ds);
                final Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().addDataEditStep(ds);
                    }
                });
            } else if (command.equals("Adjust min and max (selected images)...")) {
                adjustMinAndMaxGUI();
            } else if (command.equals("Mask image borders (layer-wise)...")) {
                final GenericDialog gd = new GenericDialog("Mask borders");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.addMessage("Borders:");
                gd.addNumericField("left: ", 6, 2);
                gd.addNumericField("top: ", 6, 2);
                gd.addNumericField("right: ", 6, 2);
                gd.addNumericField("bottom: ", 6, 2);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() + 1);
                final HashSet<Displayable> ds = new HashSet<Displayable>();
                for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
                getLayerSet().addDataEditStep(ds);
                final Bureaucrat burro = project.getLoader().maskBordersLayerWise(layers, (int) gd.getNextNumber(), (int) gd.getNextNumber(), (int) gd.getNextNumber(), (int) gd.getNextNumber());
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().addDataEditStep(ds);
                    }
                });
            } else if (command.equals("Mask image borders (selected images)...")) {
                final GenericDialog gd = new GenericDialog("Mask borders");
                gd.addMessage("Borders:");
                gd.addNumericField("left: ", 6, 2);
                gd.addNumericField("top: ", 6, 2);
                gd.addNumericField("right: ", 6, 2);
                gd.addNumericField("bottom: ", 6, 2);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final Collection<Displayable> patches = selection.getSelected(Patch.class);
                final HashSet<Displayable> ds = new HashSet<Displayable>(patches);
                getLayerSet().addDataEditStep(ds);
                final Bureaucrat burro = project.getLoader().maskBorders(patches, (int) gd.getNextNumber(), (int) gd.getNextNumber(), (int) gd.getNextNumber(), (int) gd.getNextNumber());
                burro.addPostTask(new Runnable() {

                    @Override
                    public void run() {
                        getLayerSet().addDataEditStep(ds);
                    }
                });
            } else if (command.equals("Remove alpha masks (layer-wise)...")) {
                final GenericDialog gd = new GenericDialog("Remove alpha masks");
                Utils.addLayerRangeChoices(Display.this.layer, gd);
                gd.addCheckbox("Visible only", true);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() + 1);
                final boolean visible_only = gd.getNextBoolean();
                final Collection<Patch> patches = new ArrayList<Patch>();
                for (final Layer l : layers) {
                    patches.addAll((Collection<Patch>) (Collection) l.getDisplayables(Patch.class, visible_only));
                }
                Display.removeAlphaMasks(patches);
            } else if (command.equals("Remove alpha masks (selected images)...")) {
                Display.removeAlphaMasks(selection.get(Patch.class));
            } else if (command.equals("Split images under polyline ROI")) {
                final Roi roi = canvas.getFakeImagePlus().getRoi();
                if (null == roi)
                    return;
                if (!(roi.getType() == Roi.POLYLINE || roi.getType() == Roi.FREELINE)) {
                    Utils.showMessage("Need a polyline or freeline ROI, not just any ROI!");
                    return;
                }
                if (!Utils.check("Really split images under the ROI?")) {
                    return;
                }
                // OK identify images whose contour intersects the ROI
                final Set<Displayable> col = new HashSet<Displayable>();
                // FreehandRoi is a subclass of PolygonRoi
                final PolygonRoi proi = (PolygonRoi) roi;
                final int[] x = proi.getXCoordinates(), y = proi.getYCoordinates();
                final Rectangle b = proi.getBounds();
                final Polygon[] pols = new Polygon[proi.getNCoordinates() - 1];
                for (int i = 0; i < pols.length; i++) {
                    pols[i] = new Polygon(new int[] { b.x + x[i], b.x + x[i] + 1, b.x + x[i + 1], b.x + x[i + 1] + 1 }, new int[] { b.y + y[i], b.y + y[i], b.y + y[i + 1], b.y + y[i + 1] }, 4);
                }
                for (final Patch p : getLayer().getAll(Patch.class)) {
                    if (!p.isVisible())
                        continue;
                    final Area a = p.getArea();
                    for (int i = 0; i < pols.length; i++) {
                        final Area c = new Area(pols[i]);
                        c.intersect(a);
                        if (M.isEmpty(c))
                            continue;
                        // Else, add it:
                        col.add(p);
                        break;
                    }
                }
                if (col.isEmpty()) {
                    Utils.showMessage("No images intersect the ROI!");
                    return;
                }
                for (int i = 1; i < proi.getNCoordinates(); i++) {
                    for (int k = i + 2; k < proi.getNCoordinates(); k++) {
                        // check if the two segments intersect
                        if (null != M.computeSegmentsIntersection(x[i - 1], y[i - 1], x[i], y[i], x[k - 1], y[k - 1], x[k], y[k])) {
                            Utils.showMessage("Cannot split images with a polygon ROI that intersects itself!");
                            return;
                        }
                    }
                }
                final Area[] as = M.splitArea(new Area(getLayerSet().get2DBounds()), proi, getLayerSet().get2DBounds());
                final Color[] c = new Color[] { Color.blue, Color.red };
                int i = 0;
                for (final Area a : as) {
                    // Utils.log2("Added overlay " + i + " with color " + c[i] + " and area " + AreaCalculations.area(a.getPathIterator(null)));
                    getLayer().getOverlay().add(a, c[i++], null, true, false, 0.4f);
                }
                Display.repaint(getLayer());
                final YesNoDialog yn = new YesNoDialog(frame, "Check", "Does the splitting match your expectations?\nPush 'yes' to split the images.", false);
                yn.setModal(false);
                for (final WindowListener wl : yn.getWindowListeners()) yn.removeWindowListener(wl);
                yn.setClosingTask(new Runnable() {

                    @Override
                    public void run() {
                        try {
                            // Remove overlay shapes
                            for (final Area a : as) {
                                getLayer().getOverlay().remove(a);
                            }
                            if (!yn.yesPressed()) {
                                Utils.log2("Pushed 'no'");
                                return;
                            }
                            // Split intersecting patches
                            // Duplicate each intersecting patch, and assign a[0] to the original and a[1] to the copy, as mask.
                            Bureaucrat.createAndStart(new Worker.Task("Spliting images") {

                                @Override
                                public void exec() {
                                    final Roi r1 = new ShapeRoi(as[0]), r2 = new ShapeRoi(as[1]);
                                    final ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
                                    for (final Patch p : (Collection<Patch>) (Collection) col) {
                                        final Patch copy = (Patch) p.clone(p.getProject(), false);
                                        p.addAlphaMask(r1, 0);
                                        copy.addAlphaMask(r2, 0);
                                        fus.add(p.updateMipMaps());
                                        fus.add(copy.updateMipMaps());
                                        // after submitting mipmaps, since it will get added to all Displays and repainted.
                                        p.getLayer().add(copy);
                                    }
                                    Utils.wait(fus);
                                }
                            }, project);
                        } catch (final Throwable t) {
                            IJError.print(t);
                        } finally {
                            yn.dispose();
                            Display.repaint(getLayer());
                        }
                    }
                });
                yn.setVisible(true);
            } else if (command.equals("Duplicate")) {
                // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
                final HashSet<Class> accepted = new HashSet<Class>();
                accepted.add(Patch.class);
                accepted.add(DLabel.class);
                accepted.add(Stack.class);
                final ArrayList<Displayable> originals = new ArrayList<Displayable>();
                final ArrayList<Displayable> selected = selection.getSelected();
                for (final Displayable d : selected) {
                    if (accepted.contains(d.getClass())) {
                        originals.add(d);
                    }
                }
                if (originals.size() > 0) {
                    getLayerSet().addChangeTreesStep();
                    for (final Displayable d : originals) {
                        if (d instanceof ZDisplayable) {
                            d.getLayerSet().add((ZDisplayable) d.clone());
                        } else {
                            d.getLayer().add(d.clone());
                        }
                    }
                    getLayerSet().addChangeTreesStep();
                } else if (selected.size() > 0) {
                    Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
                }
            } else if (command.equals("Create subproject")) {
                // Choose a 2D rectangle
                final Roi roi = canvas.getFakeImagePlus().getRoi();
                Rectangle bounds;
                if (null != roi) {
                    if (!Utils.check("Use bounds as defined by the ROI:\n" + roi.getBounds() + " ?"))
                        return;
                    bounds = roi.getBounds();
                } else
                    bounds = getLayerSet().get2DBounds();
                // Choose a layer range, and whether to ignore hidden images
                final GenericDialog gd = new GenericDialog("Choose layer range");
                Utils.addLayerRangeChoices(layer, gd);
                gd.addCheckbox("Ignore hidden images", true);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final Layer first = layer.getParent().getLayer(gd.getNextChoiceIndex());
                final Layer last = layer.getParent().getLayer(gd.getNextChoiceIndex());
                final boolean ignore_hidden_patches = gd.getNextBoolean();
                final Project sub = getProject().createSubproject(bounds, first, last, ignore_hidden_patches);
                if (null == sub) {
                    Utils.log("ERROR: failed to create subproject.");
                    return;
                }
                final LayerSet subls = sub.getRootLayerSet();
                Display.createDisplay(sub, subls.getLayer(0));
            } else if (command.startsWith("Image stack under selected Arealist")) {
                if (null == active || active.getClass() != AreaList.class)
                    return;
                final GenericDialog gd = new GenericDialog("Stack options");
                final String[] types = { "8-bit", "16-bit", "32-bit", "RGB" };
                gd.addChoice("type:", types, types[0]);
                gd.addSlider("Scale: ", 1, 100, 100);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final int type;
                switch(gd.getNextChoiceIndex()) {
                    case 0:
                        type = ImagePlus.GRAY8;
                        break;
                    case 1:
                        type = ImagePlus.GRAY16;
                        break;
                    case 2:
                        type = ImagePlus.GRAY32;
                        break;
                    case 3:
                        type = ImagePlus.COLOR_RGB;
                        break;
                    default:
                        type = ImagePlus.GRAY8;
                        break;
                }
                final ImagePlus imp = ((AreaList) active).getStack(type, gd.getNextNumber() / 100);
                if (null != imp)
                    imp.show();
            } else if (command.equals("Fly through selected Treeline/AreaTree")) {
                if (null == active || !(active instanceof Tree<?>))
                    return;
                Bureaucrat.createAndStart(new Worker.Task("Creating fly through", true) {

                    @Override
                    public void exec() {
                        final GenericDialog gd = new GenericDialog("Fly through");
                        gd.addNumericField("Width", 512, 0);
                        gd.addNumericField("Height", 512, 0);
                        final String[] types = new String[] { "8-bit gray", "Color RGB" };
                        gd.addChoice("Image type", types, types[0]);
                        gd.addSlider("scale", 0, 100, 100);
                        gd.addCheckbox("save to file", false);
                        gd.showDialog();
                        if (gd.wasCanceled())
                            return;
                        final int w = (int) gd.getNextNumber();
                        final int h = (int) gd.getNextNumber();
                        final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
                        final double scale = gd.getNextNumber();
                        if (w <= 0 || h <= 0) {
                            Utils.log("Invalid width or height: " + w + ", " + h);
                            return;
                        }
                        if (0 == scale || Double.isNaN(scale)) {
                            Utils.log("Invalid scale: " + scale);
                            return;
                        }
                        String dir = null;
                        if (gd.getNextBoolean()) {
                            final DirectoryChooser dc = new DirectoryChooser("Target directory");
                            dir = dc.getDirectory();
                            // canceled
                            if (null == dir)
                                return;
                            dir = Utils.fixDir(dir);
                        }
                        final ImagePlus imp = ((Tree<?>) active).flyThroughMarked(w, h, scale / 100, type, dir);
                        if (null == imp) {
                            Utils.log("Mark a node first!");
                            return;
                        }
                        imp.show();
                    }
                }, project);
            } else if (command.startsWith("Arealists as labels")) {
                final GenericDialog gd = new GenericDialog("Export labels");
                gd.addSlider("Scale: ", 1, 100, 100);
                final String[] options = { "All area list", "Selected area lists" };
                gd.addChoice("Export: ", options, options[0]);
                Utils.addLayerRangeChoices(layer, gd);
                gd.addCheckbox("Visible only", true);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final float scale = (float) (gd.getNextNumber() / 100);
                final java.util.List<Displayable> al = (java.util.List<Displayable>) (0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelectedSorted(AreaList.class));
                if (null == al) {
                    Utils.log("No area lists found to export.");
                    return;
                }
                // Generics are ... a pain? I don't understand them? They fail when they shouldn't? And so easy to workaround that they are a shame?
                final int first = gd.getNextChoiceIndex();
                final int last = gd.getNextChoiceIndex();
                final boolean visible_only = gd.getNextBoolean();
                if (-1 != command.indexOf("(amira)")) {
                    AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
                } else if (-1 != command.indexOf("(tif)")) {
                    AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
                }
            } else if (command.equals("Project properties...")) {
                project.adjustProperties();
            } else if (command.equals("Release memory...")) {
                Bureaucrat.createAndStart(new Worker("Releasing memory") {

                    @Override
                    public void run() {
                        startedWorking();
                        try {
                            final GenericDialog gd = new GenericDialog("Release Memory");
                            final int max = (int) (IJ.maxMemory() / 1000000);
                            gd.addSlider("Megabytes: ", 0, max, max / 2);
                            gd.showDialog();
                            if (!gd.wasCanceled()) {
                                final int n_mb = (int) gd.getNextNumber();
                                project.getLoader().releaseToFit((long) n_mb * 1000000);
                            }
                        } catch (final Throwable e) {
                            IJError.print(e);
                        } finally {
                            finishedWorking();
                        }
                    }
                }, project);
            } else if (command.equals("Create sibling project with retiled layers")) {
                final GenericDialog gd = new GenericDialog("Export flattened layers");
                gd.addNumericField("Tile_width", 2048, 0);
                gd.addNumericField("Tile_height", 2048, 0);
                final String[] types = new String[] { "16-bit", "RGB color" };
                gd.addChoice("Export_image_type", types, types[0]);
                gd.addCheckbox("Create mipmaps", true);
                gd.addNumericField("Number_of_threads_to_use", Runtime.getRuntime().availableProcessors(), 0);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final DirectoryChooser dc = new DirectoryChooser("Choose target folder");
                final String folder = dc.getDirectory();
                if (null == folder)
                    return;
                final int tileWidth = (int) gd.getNextNumber(), tileHeight = (int) gd.getNextNumber();
                if (tileWidth < 0 || tileHeight < 0) {
                    Utils.showMessage("Invalid tile sizes: " + tileWidth + ", " + tileHeight);
                    return;
                }
                if (tileWidth != tileHeight) {
                    if (!Utils.check("The tile width (" + tileWidth + ") differs from the tile height (" + tileHeight + ").\nContinue anyway?")) {
                        return;
                    }
                }
                final int imageType = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY16 : ImagePlus.COLOR_RGB;
                final boolean createMipMaps = gd.getNextBoolean();
                final int nThreads = (int) gd.getNextNumber();
                Bureaucrat.createAndStart(new Worker.Task("Export flattened sibling project") {

                    @Override
                    public void exec() {
                        try {
                            ProjectTiler.createRetiledSibling(project, folder, tileWidth, tileHeight, imageType, true, nThreads, createMipMaps);
                        } catch (final Throwable t) {
                            Utils.showMessage("ERROR: " + t);
                            IJError.print(t);
                        }
                    }
                }, project);
            } else if (command.equals("Flush image cache")) {
                Loader.releaseAllCaches();
            } else if (command.equals("Regenerate all mipmaps")) {
                project.getLoader().regenerateMipMaps(getLayerSet().getDisplayables(Patch.class));
            } else if (command.equals("Regenerate mipmaps (selected images)")) {
                project.getLoader().regenerateMipMaps(selection.getSelected(Patch.class));
            } else if (command.equals("Tags...")) {
                // get a file first
                final File f = Utils.chooseFile(null, "tags", ".xml");
                if (null == f)
                    return;
                if (!Utils.saveToFile(f, getLayerSet().exportTags())) {
                    Utils.logAll("ERROR when saving tags to file " + f.getAbsolutePath());
                }
            } else if (command.equals("Tags ...")) {
                final String[] ff = Utils.selectFile("Import tags");
                if (null == ff)
                    return;
                final GenericDialog gd = new GenericDialog("Import tags");
                final String[] modes = new String[] { "Append to current tags", "Replace current tags" };
                gd.addChoice("Import tags mode:", modes, modes[0]);
                gd.addMessage("Replacing current tags\nwill remove all tags\n from all nodes first!");
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                getLayerSet().importTags(new StringBuilder(ff[0]).append('/').append(ff[1]).toString(), 1 == gd.getNextChoiceIndex());
            } else if (command.equals("Connectivity graph...")) {
                Bureaucrat.createAndStart(new Worker.Task("Connectivity graph") {

                    @Override
                    public void exec() {
                        Graph.extractAndShowGraph(getLayerSet());
                    }
                }, getProject());
            } else if (command.equals("NeuroML...")) {
                final GenericDialog gd = new GenericDialog("Export NeuroML");
                final String[] a = new String[] { "NeuroML (arbors and synapses)", "MorphML (arbors)" };
                gd.addChoice("Type:", a, a[0]);
                final String[] b = new String[] { "All treelines and areatrees", "Selected treelines and areatrees" };
                gd.addChoice("Export:", b, b[0]);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                final int type = gd.getNextChoiceIndex();
                final int export = gd.getNextChoiceIndex();
                // 
                final SaveDialog sd = new SaveDialog("Choose .mml file", null, ".mml");
                final String filename = sd.getFileName();
                // canceled
                if (null == filename)
                    return;
                final File f = new File(sd.getDirectory() + filename);
                // 
                Bureaucrat.createAndStart(new Worker.Task("Export NeuroML") {

                    @Override
                    public void exec() {
                        OutputStreamWriter w = null;
                        try {
                            final Set<Tree<?>> trees = new HashSet<Tree<?>>();
                            Collection<? extends Displayable> ds = null;
                            switch(export) {
                                case 0:
                                    ds = getLayerSet().getZDisplayables();
                                    break;
                                case 1:
                                    ds = selection.getSelected();
                                    break;
                            }
                            for (final Displayable d : ds) {
                                if (d.getClass() == Treeline.class || d.getClass() == AreaTree.class) {
                                    trees.add((Tree<?>) d);
                                }
                            }
                            if (trees.isEmpty()) {
                                Utils.showMessage("No trees to export!");
                                return;
                            }
                            // 
                            // encoding in Latin 1 (for macosx not to mess around
                            w = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f), 65536), "8859_1");
                            // 
                            switch(type) {
                                case 0:
                                    NeuroML.exportNeuroML(trees, w);
                                    break;
                                case 1:
                                    NeuroML.exportMorphML(trees, w);
                                    break;
                            }
                            // 
                            w.flush();
                            w.close();
                        // 
                        } catch (final Throwable t) {
                            IJError.print(t);
                            try {
                                if (null != w)
                                    w.close();
                            } catch (final Exception ee) {
                                IJError.print(ee);
                            }
                        }
                    }
                }, getProject());
            } else if (command.equals("Measure")) {
                if (selection.isEmpty()) {
                    Utils.log("Nothing selected to measure!");
                    return;
                }
                selection.measure();
            } else if (command.equals("Area interpolation options...")) {
                final GenericDialog gd = new GenericDialog("Area interpolation");
                final boolean a = project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map);
                gd.addCheckbox("Always use distance map method", a);
                gd.showDialog();
                if (gd.wasCanceled())
                    return;
                project.setProperty(AreaUtils.always_interpolate_areas_with_distance_map, gd.getNextBoolean() ? "true" : null);
            } else {
                Utils.log2("Display: don't know what to do with command " + command);
            }
        }
    });
}
Also used : ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) Bureaucrat(ini.trakem2.utils.Bureaucrat) MatchIntensities(org.janelia.intensity.MatchIntensities) SaveDialog(ij.io.SaveDialog) Vector(java.util.Vector) TextListener(java.awt.event.TextListener) Project(ini.trakem2.Project) DBObject(ini.trakem2.persistence.DBObject) ItemEvent(java.awt.event.ItemEvent) Set(java.util.Set) HashSet(java.util.HashSet) GenericDialog(ij.gui.GenericDialog) InspectPatchTrianglesMode(ini.trakem2.display.inspect.InspectPatchTrianglesMode) Worker(ini.trakem2.utils.Worker) ProjectThing(ini.trakem2.tree.ProjectThing) TextEvent(java.awt.event.TextEvent) WindowListener(java.awt.event.WindowListener) Hashtable(java.util.Hashtable) ImagePlus(ij.ImagePlus) ItemListener(java.awt.event.ItemListener) OutputStreamWriter(java.io.OutputStreamWriter) DirectoryChooser(ij.io.DirectoryChooser) AlignTask(mpicbg.trakem2.align.AlignTask) AlignLayersTask(mpicbg.trakem2.align.AlignLayersTask) DistortionCorrectionTask(lenscorrection.DistortionCorrectionTask) Choice(java.awt.Choice) LayerStack(ini.trakem2.imaging.LayerStack) TextField(java.awt.TextField) ArrayList(java.util.ArrayList) List(java.util.List) CoordinateTransformList(mpicbg.trakem2.transform.CoordinateTransformList) Polygon(java.awt.Polygon) HashSet(java.util.HashSet) Color(java.awt.Color) PolygonRoi(ij.gui.PolygonRoi) ShapeRoi(ij.gui.ShapeRoi) Roi(ij.gui.Roi) Area(java.awt.geom.Area) FileOutputStream(java.io.FileOutputStream) Collection(java.util.Collection) File(java.io.File) Saver(ini.trakem2.utils.Saver) KeyEvent(java.awt.event.KeyEvent) PolygonRoi(ij.gui.PolygonRoi) ListIterator(java.util.ListIterator) Iterator(java.util.Iterator) YesNoCancelDialog(ij.gui.YesNoCancelDialog) BufferedOutputStream(java.io.BufferedOutputStream) ShapeRoi(ij.gui.ShapeRoi) Point(java.awt.Point) Point(java.awt.Point) NoninvertibleTransformException(java.awt.geom.NoninvertibleTransformException) Filter(ini.trakem2.utils.Filter) Future(java.util.concurrent.Future)

Example 2 with DLabel

use of ini.trakem2.display.DLabel in project TrakEM2 by trakem2.

the class Loader method getFlatAWTImage.

/**
 * @param layer The layer from which to collect visible Displayable instances that intersect the srcRect.
 * @param srcRect_ Rectangle in World coordinates representing the field of view to paint into the image, and defines the width and height of the image together with the scale.
 * @param scale Value between 0 and 1.
 * @param c_alphas Which color channels to include when painting Patch instances that hold an RGB image.
 * @param type Either ImagePlus.GRAY8 or ImagePlus.COLOR_RGB
 * @param clazz Include only Displayable instances of this class; use Displayable.class for all.
 * @param al_displ List of Displayable instances to include. Use null to include all visible intersected by srcRect.
 * @param quality Whether to attempt to create a larger image and then scale it down with area averaging for best quality.
 * @param background The color of the areas of the image where no Displayable paints.
 * @param active Whether to paint a particular Displayable instance in an active state (as if it was selected in the UI).
 * @return
 */
public Image getFlatAWTImage(final Layer layer, final Rectangle srcRect_, final double scale, final int c_alphas, final int type, final Class<?> clazz, List<? extends Displayable> al_displ, boolean quality, final Color background, final Displayable active) {
    try {
        // dimensions
        int w = 0;
        int h = 0;
        Rectangle srcRect = (null == srcRect_) ? null : (Rectangle) srcRect_.clone();
        if (null != srcRect) {
            w = srcRect.width;
            h = srcRect.height;
        } else {
            w = (int) Math.ceil(layer.getLayerWidth());
            h = (int) Math.ceil(layer.getLayerHeight());
            srcRect = new Rectangle(0, 0, w, h);
        }
        Utils.log2("Loader.getFlatImage: using rectangle " + srcRect);
        /*
			 * output size including excess space for not entirely covered
			 * pixels
			 */
        final int ww = (int) Math.ceil(w * scale);
        final int hh = (int) Math.ceil(h * scale);
        /* The size of the buffered image to be generated */
        final int biw, bih;
        final double scaleP, scalePX, scalePY;
        // on scaling down to output resolution.
        if (quality) {
            if (ww * hh >= Math.pow(2, 30)) {
                // 1 GB
                // While perhaps scale could be increased to an image size of up to 2 GB, it is not advisable
                scaleP = scalePX = scalePY = scale;
                quality = false;
                biw = ww;
                bih = hh;
                Utils.log("Can't use 'quality' flag for getFlatAWTImage: would be too large");
            // If the image is larger than 2 GB, it will thrown a NegativeArraySizeException below and stop.
            } else {
                // Max area: the smallest of the srcRect at 100x magnification and 1 GB
                final double max_area = Math.min(srcRect.width * srcRect.height, Math.pow(2, 30));
                final double ratio = ww / (double) hh;
                // area = w * h
                // ratio = w / h
                // w = ratio * h
                // area = ratio * h * h
                // h = sqrt(area / ratio)
                // scaleP is then the ratio between the real-world height and the target height
                // (And clamp to a maximum of 1.0: above makes no sense)
                scaleP = Math.min(1.0, srcRect.height / Math.sqrt(max_area / ratio));
                biw = (int) Math.ceil(ww / scale);
                bih = (int) Math.ceil(hh / scale);
                /* compensate for excess space due to ceiling */
                scalePX = (double) biw / (double) ww * scale;
                scalePY = (double) bih / (double) hh * scale;
                Utils.log("getFlatAWTImage -- scale: " + scale + "; quality scale: " + scaleP);
            }
        } else {
            scaleP = scalePX = scalePY = scale;
            biw = ww;
            bih = hh;
        }
        // estimate image size
        final long n_bytes = (long) ((biw * bih * (ImagePlus.GRAY8 == type ? 1.0 : /*byte*/
        4.0)));
        Utils.log2("Flat image estimated size in bytes: " + Long.toString(n_bytes) + "  w,h : " + (int) Math.ceil(biw) + "," + (int) Math.ceil(bih) + (quality ? " (using 'quality' flag: scaling to " + scale + " is done later with area averaging)" : ""));
        releaseToFit(n_bytes);
        // go
        BufferedImage bi = null;
        switch(type) {
            case ImagePlus.GRAY8:
                bi = new BufferedImage(biw, bih, BufferedImage.TYPE_BYTE_INDEXED, GRAY_LUT);
                break;
            case ImagePlus.COLOR_RGB:
                bi = new BufferedImage(biw, bih, BufferedImage.TYPE_INT_ARGB);
                break;
            default:
                Utils.log2("Left bi,icm as null");
                break;
        }
        final Graphics2D g2d = bi.createGraphics();
        g2d.setColor(background);
        g2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        // TODO Stephan, test if that introduces offset vs nearest neighbor
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        // to smooth edges of the images
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ArrayList<ZDisplayable> al_zdispl = null;
        if (null == al_displ) {
            al_displ = new ArrayList<Displayable>(layer.find(clazz, srcRect, true, true));
            al_zdispl = new ArrayList<ZDisplayable>((Collection) layer.getParent().findZDisplayables(clazz, layer, srcRect, true, true));
        } else {
            // separate ZDisplayables into their own array
            al_displ = new ArrayList<Displayable>(al_displ);
            final HashSet<ZDisplayable> az = new HashSet<ZDisplayable>();
            for (final Iterator<?> it = al_displ.iterator(); it.hasNext(); ) {
                final Object ob = it.next();
                if (ob instanceof ZDisplayable) {
                    it.remove();
                    az.add((ZDisplayable) ob);
                }
            }
            // order ZDisplayables by their stack order
            final ArrayList<ZDisplayable> al_zdispl2 = layer.getParent().getZDisplayables();
            for (final Iterator<ZDisplayable> it = al_zdispl2.iterator(); it.hasNext(); ) {
                if (!az.contains(it.next()))
                    it.remove();
            }
            al_zdispl = al_zdispl2;
        }
        // prepare the canvas for the srcRect and magnification
        final AffineTransform at_original = g2d.getTransform();
        final AffineTransform atc = new AffineTransform();
        atc.scale(scalePX, scalePY);
        atc.translate(-srcRect.x, -srcRect.y);
        at_original.preConcatenate(atc);
        g2d.setTransform(at_original);
        // Utils.log2("will paint: " + al_displ.size() + " displ and " + al_zdispl.size() + " zdispl");
        // int total = al_displ.size() + al_zdispl.size();
        int count = 0;
        boolean zd_done = false;
        final List<Layer> layers = layer.getParent().getColorCueLayerRange(layer);
        for (final Displayable d : al_displ) {
            // paint the ZDisplayables before the first label, if any
            if (!zd_done && d instanceof DLabel) {
                zd_done = true;
                for (final ZDisplayable zd : al_zdispl) {
                    if (!zd.isOutOfRepaintingClip(scaleP, srcRect, null)) {
                        zd.paint(g2d, srcRect, scaleP, active == zd, c_alphas, layer, layers);
                    }
                    count++;
                // Utils.log2("Painted " + count + " of " + total);
                }
            }
            if (!d.isOutOfRepaintingClip(scaleP, srcRect, null)) {
                d.paintOffscreen(g2d, srcRect, scaleP, active == d, c_alphas, layer, layers);
            // Utils.log("painted: " + d + "\n with: " + scaleP + ", " + c_alphas + ", " + layer);
            } else {
            // Utils.log2("out: " + d);
            }
            count++;
        // Utils.log2("Painted " + count + " of " + total);
        }
        if (!zd_done) {
            zd_done = true;
            for (final ZDisplayable zd : al_zdispl) {
                if (!zd.isOutOfRepaintingClip(scaleP, srcRect, null)) {
                    zd.paint(g2d, srcRect, scaleP, active == zd, c_alphas, layer, layers);
                }
                count++;
            // Utils.log2("Painted " + count + " of " + total);
            }
        }
        // ensure enough memory is available for the processor and a new awt from it
        // locks on its own
        releaseToFit((long) (n_bytes * 2.3));
        try {
            if (quality) {
                // need to scale back down
                Image scaled = null;
                if (!isMipMapsRegenerationEnabled() || scale >= 0.499) {
                    // there are no proper mipmaps above 50%, so there's need for SCALE_AREA_AVERAGING.
                    // very slow, but best by far
                    scaled = bi.getScaledInstance(ww, hh, Image.SCALE_AREA_AVERAGING);
                    if (ImagePlus.GRAY8 == type) {
                        // getScaledInstance generates RGB images for some reason.
                        final BufferedImage bi8 = new BufferedImage(ww, hh, BufferedImage.TYPE_BYTE_GRAY);
                        bi8.createGraphics().drawImage(scaled, 0, 0, null);
                        scaled.flush();
                        scaled = bi8;
                    }
                } else {
                    // faster, but requires gaussian blurred images (such as the mipmaps)
                    if (bi.getType() == BufferedImage.TYPE_BYTE_INDEXED) {
                        scaled = new BufferedImage(ww, hh, bi.getType(), GRAY_LUT);
                    } else {
                        scaled = new BufferedImage(ww, hh, bi.getType());
                    }
                    final Graphics2D gs = (Graphics2D) scaled.getGraphics();
                    // gs.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
                    gs.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                    gs.drawImage(bi, 0, 0, ww, hh, null);
                }
                bi.flush();
                return scaled;
            } else {
                // else the image was made scaled down already, and of the proper type
                return bi;
            }
        } catch (final OutOfMemoryError oome) {
            Utils.log("Not enough memory to create the ImagePlus. Try scaling it down or not using the 'quality' flag.");
        }
    } catch (final Exception e) {
        IJError.print(e);
    }
    return null;
}
Also used : ZDisplayable(ini.trakem2.display.ZDisplayable) Displayable(ini.trakem2.display.Displayable) Rectangle(java.awt.Rectangle) Image(java.awt.Image) BufferedImage(java.awt.image.BufferedImage) MipMapImage(ini.trakem2.display.MipMapImage) Layer(ini.trakem2.display.Layer) BufferedImage(java.awt.image.BufferedImage) IOException(java.io.IOException) FormatException(loci.formats.FormatException) Graphics2D(java.awt.Graphics2D) ZDisplayable(ini.trakem2.display.ZDisplayable) DLabel(ini.trakem2.display.DLabel) Collection(java.util.Collection) AffineTransform(java.awt.geom.AffineTransform) HashSet(java.util.HashSet)

Example 3 with DLabel

use of ini.trakem2.display.DLabel in project TrakEM2 by trakem2.

the class ProjectTiler method createRetiledSibling.

/**
 * Take a {@link Project}, a size for the image tiles, and a target directory,
 * and create a new copy of the current project in that folder but with the underlying
 * images converted to tiles with a translation-only transform (saved as zipped TIFFs,
 * with extension ".tif.zip").
 * The new, returned {@link Project} represents the given project but with much
 * simpler transformations (just translation) for the images and a defined size for
 * the latter, which helps a lot regarding storage space of the XML (and parsing and
 * saving time) and performance when browsing layers (keep in mind that, for a 32k x 32k image,
 * at 100% zoom one would have to load a 32k x 32k image and render just a tiny bit
 * of it). The copied Project preserves the ID of the {@link Layer}s of the original
 * {@link Project}, as well as the dimensions; this means the copy is a sibling of
 * the original, and it is possible to send segmentations from one to the other "as is"
 * (directly, without having to transform along with the images which would not be possible).
 *
 * Image files are stored as
 *
 * The non-image objects of the given project are copied into the new project as well.
 *
 * @param srcProject The project to create a sibling of.
 * @param targetDirectory The directory in which to create all the necessary data and mipmap folders for the new Project.
 * @param tileWidth The width of the tiles to create for the data of the new project.
 * @param tileHeight The height of the tiles.
 * @param exportImageType Any of {@link ImagePlus#GRAY8}, {@link ImagePlus#GRAY16} or {@link ImagePlus#COLOR_RGB}, otherwise an {@link IllegalArgumentException} is thrown.
 * @param onlyVisibleImages Whether to consider visible images only.
 * @param nExportThreads Number of layers to export in parallel. Use a small number when original images are huge (such as larger than 4096 x 4096 pixels).
 * @param createMipMaps Whether to generate the mipmaps when done or not.
 *
 * @throws Exception IllegalArgumentException When {@code exportImageType} is not {@link ImagePlus#GRAY16} or {@link ImagePlus#COLOR_RGB}, or when the directory exists and cannot be written to.
 */
public static final Project createRetiledSibling(final Project srcProject, final String targetDirectory, final int tileWidth, final int tileHeight, final int exportImageType, final boolean onlyVisibleImages, final int nExportThreads, final boolean createMipMaps) throws Exception {
    // Validate exportImageType
    switch(exportImageType) {
        case ImagePlus.GRAY8:
        case ImagePlus.GRAY16:
        case ImagePlus.COLOR_RGB:
            break;
        default:
            throw new IllegalArgumentException("Can only accept GRAY8, GRAY16 or COLOR_RGB as values for 'exportImageType'!");
    }
    // Validate targetDirectory
    final File fdir = new File(targetDirectory);
    if (fdir.exists()) {
        if (!fdir.isDirectory() || !fdir.canWrite())
            throw new IllegalArgumentException("Invalid directory: not a directory or cannot write to: " + targetDirectory);
    } else {
        if (!fdir.mkdirs()) {
            throw new IllegalArgumentException("Cannot create directory at: " + targetDirectory);
        }
    }
    final String targetDir = Utils.fixDir(targetDirectory);
    // Create "data" directory
    final String dataDir = new StringBuilder(targetDir).append("data/").toString();
    final File fDataDir = new File(dataDir);
    if (fDataDir.exists() && (!fDataDir.isDirectory() || !fDataDir.canWrite())) {
        throw new IllegalArgumentException("Cannot create or write to 'data' directory in the targetDirectory at: " + targetDir);
    } else {
        fDataDir.mkdir();
    }
    // Create new Project, plain, without any automatic creation of a Layer or a Display
    final Project newProject = Project.newFSProject("blank", null, targetDir, false);
    final LayerSet newLayerSet = newProject.getRootLayerSet();
    newLayerSet.setCalibration(srcProject.getRootLayerSet().getCalibrationCopy());
    if (!createMipMaps) {
        Utils.log("MipMaps are DISABLED:\n --> When done, right-click and choose 'Display - Properties...' and enable mipmaps,\n     and then run 'Project - Regenerate all mipmaps'\n");
        newProject.getLoader().setMipMapsRegeneration(false);
        Utils.log("mipmaps enabled? " + newProject.getLoader().isMipMapsRegenerationEnabled());
    }
    // Copy the Template Tree of types
    newProject.resetRootTemplateThing(srcProject.getRootTemplateThing().clone(newProject, true), null);
    for (final TemplateThing tt : newProject.getRootTemplateThing().getUniqueTypes(new HashMap<String, TemplateThing>()).values()) {
        newProject.addUniqueType(tt);
    }
    // Clone layers with the exact same IDs, so that the two projects are siblings at the layer-level:
    // (Being siblings allows for treelines, arealists, etc. to be transferred from one to another "as is").
    final List<Layer> srcLayers = srcProject.getRootLayerSet().getLayers();
    final List<Layer> newLayers = new ArrayList<Layer>();
    for (final Layer srcLayer : srcLayers) {
        final Layer newLayer = new Layer(newProject, srcLayer.getId(), srcLayer.getZ(), srcLayer.getThickness());
        // to update the ID generator in FSLoader
        newLayer.addToDatabase();
        newLayerSet.add(newLayer);
        newLayers.add(newLayer);
        newProject.getRootLayerThing().addChild(new LayerThing(newProject.getRootLayerThing().getChildTemplate("layer"), newProject, newLayer));
    }
    newProject.getLayerTree().rebuild();
    // Update the LayerSet
    newLayerSet.setDimensions(srcProject.getRootLayerSet().getLayerWidth(), srcProject.getRootLayerSet().getLayerHeight(), LayerSet.NORTHWEST);
    Display.updateLayerScroller(newLayerSet);
    Display.update(newLayerSet);
    // Copy template from the src Project
    // (It's done after creating layers so the IDs will not collide with those of the Layers)
    newProject.resetRootTemplateThing(srcProject.getRootTemplateThing().clone(newProject, false), null);
    // Export tiles as new Patch instances, creating new image files in disk
    final int numThreads = Math.max(1, Math.min(nExportThreads, Runtime.getRuntime().availableProcessors()));
    int i = 0;
    for (final Layer srcLayer : srcLayers) {
        Utils.log("Processing layer " + (i + 1) + "/" + srcLayers.size() + " -- " + new Date());
        final int layerIndex = i++;
        // Create subDirectory
        final String dir = dataDir + "/" + layerIndex + "/";
        new File(dir).mkdir();
        // Create a new Layer with the same Z and thickness
        final Layer newLayer = newLayers.get(layerIndex);
        // Export layer tiles
        final ArrayList<Patch> patches = new ArrayList<Patch>();
        if (ImagePlus.GRAY16 == exportImageType) {
            Process.progressive(ExportUnsignedShort.exportTiles(srcLayer, tileWidth, tileHeight, onlyVisibleImages), new CountingTaskFactory<Callable<ExportedTile>, Patch>() {

                public Patch process(final Callable<ExportedTile> c, final int index) {
                    try {
                        // Create the tile
                        final ExportedTile t = c.call();
                        // Store the file
                        final String title = layerIndex + "-" + index;
                        final String path = dir + title + ".tif.zip";
                        final ImagePlus imp = new ImagePlus(title, t.sp);
                        if (!new FileSaver(imp).saveAsZip(path)) {
                            throw new Exception("Could not save tile: " + path);
                        }
                        // Create a Patch
                        final Patch patch = new Patch(newProject, title, t.x, t.y, imp);
                        patch.setLocked(true);
                        newProject.getLoader().addedPatchFrom(path, patch);
                        return patch;
                    } catch (Exception e) {
                        IJError.print(e);
                        return null;
                    }
                }
            }, patches, numThreads);
        } else {
            // GRAY8 or COLOR_RGB: created from mipmaps
            Process.progressive(tileSequence(srcLayer, tileWidth, tileHeight, onlyVisibleImages), new CountingTaskFactory<Rectangle, Patch>() {

                @Override
                public Patch process(final Rectangle bounds, final int index) {
                    try {
                        // Create the tile
                        final ImagePlus imp = srcLayer.getProject().getLoader().getFlatImage(srcLayer, bounds, 1.0, -1, exportImageType, Patch.class, null, false, Color.black);
                        final String title = layerIndex + "-" + index;
                        imp.setTitle(title);
                        final String path = dir + title + ".tif.zip";
                        if (!new FileSaver(imp).saveAsZip(path)) {
                            throw new Exception("Could not save tile: " + path);
                        }
                        // Create a Patch
                        final Patch patch = new Patch(newProject, title, bounds.x, bounds.y, imp);
                        patch.setLocked(true);
                        newProject.getLoader().addedPatchFrom(path, patch);
                        return patch;
                    } catch (Exception e) {
                        IJError.print(e);
                        return null;
                    }
                }
            }, patches, numThreads);
        }
        // Add all Patches to the new Layer
        for (final Patch p : patches) {
            newLayer.add(p);
        }
    }
    // Copy all segmentations "As is"
    final ProjectThing root = srcProject.getRootProjectThing();
    if (null != root.getChildren() && !root.getChildren().isEmpty()) {
        final ProjectThing source_pt = srcProject.getRootProjectThing().getChildren().get(0);
        // "As is"
        final int transfer_mode = 0;
        final ProjectThing landing_parent = newProject.getRootProjectThing();
        srcProject.getProjectTree().rawSendToSiblingProject(source_pt, transfer_mode, newProject, landing_parent);
    }
    // Copy all floating text labels
    i = 0;
    for (final Layer srcLayer : srcLayers) {
        for (final DLabel srcLabel : srcLayer.getAll(DLabel.class)) {
            newLayers.get(i++).add(srcLabel.clone(newProject, false));
        }
    }
    if (createMipMaps) {
        final LinkedList<Future<?>> fus = new LinkedList<Future<?>>();
        final int batch = Runtime.getRuntime().availableProcessors();
        for (final Layer newLayer : newLayers) {
            for (final Patch p : newLayer.getAll(Patch.class)) {
                fus.add(p.updateMipMaps());
                // Don't build-up too much
                if (fus.size() > batch * 3) {
                    while (fus.size() > batch) {
                        try {
                            fus.removeFirst().get();
                        } catch (Exception e) {
                            IJError.print(e);
                        }
                    }
                }
            }
        }
        Utils.wait(fus);
    }
    // Save:
    newProject.saveAs(targetDir + "exported.xml", false);
    return newProject;
}
Also used : HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) Callable(java.util.concurrent.Callable) FileSaver(ij.io.FileSaver) ProjectThing(ini.trakem2.tree.ProjectThing) LayerSet(ini.trakem2.display.LayerSet) LayerThing(ini.trakem2.tree.LayerThing) Layer(ini.trakem2.display.Layer) ImagePlus(ij.ImagePlus) Date(java.util.Date) LinkedList(java.util.LinkedList) Project(ini.trakem2.Project) DLabel(ini.trakem2.display.DLabel) TemplateThing(ini.trakem2.tree.TemplateThing) Future(java.util.concurrent.Future) File(java.io.File) Patch(ini.trakem2.display.Patch) ExportedTile(mpicbg.trakem2.transform.ExportedTile)

Example 4 with DLabel

use of ini.trakem2.display.DLabel in project TrakEM2 by trakem2.

the class TMLHandler method makeLayerThing.

private LayerThing makeLayerThing(String type, final HashMap<String, String> ht_attributes) {
    try {
        type = type.toLowerCase();
        if (0 == type.indexOf("t2_")) {
            type = type.substring(3);
        }
        // long id = -1;
        // final String sid = ht_attributes.get("id");
        // if (null != sid) id = Long.parseLong(sid);
        long oid = -1;
        final String soid = ht_attributes.get("oid");
        if (null != soid)
            oid = Long.parseLong(soid);
        if (type.equals("node")) {
            if (null == last_tree) {
                throw new NullPointerException("Can't create a node for null last_tree!");
            }
            final Node<?> node = last_tree.newNode(ht_attributes);
            taggables.add(node);
            // Put node into the list of nodes with that layer id, to update to proper Layer pointer later
            final long ndlid = Long.parseLong(ht_attributes.get("lid"));
            List<Node<?>> list = node_layer_table.get(ndlid);
            if (null == list) {
                list = new ArrayList<Node<?>>();
                node_layer_table.put(ndlid, list);
            }
            list.add(node);
            // Set node as root node or add as child to last node in the stack
            if (null == last_root_node) {
                last_root_node = node;
            } else {
                final String sconf = ht_attributes.get("c");
                nodes.getLast().add((Node) node, null == sconf ? Node.MAX_EDGE_CONFIDENCE : Byte.parseByte(sconf));
            }
            // color?
            final String scolor = ht_attributes.get("color");
            if (null != scolor) {
                final Color color = Utils.getRGBColorFromHex(scolor);
                Collection<Node<?>> nodes = node_colors.get(color);
                if (null == nodes) {
                    nodes = new ArrayList<Node<?>>();
                    node_colors.put(color, nodes);
                }
                nodes.add(node);
            }
            // Put node into stack of nodes (to be removed on closing the tag)
            nodes.add(node);
        } else if (type.equals("profile")) {
            Profile profile = new Profile(this.project, oid, ht_attributes, ht_links);
            profile.addToDatabase();
            ht_displayables.put(oid, profile);
            addToLastOpenLayer(profile);
            last_displayable = profile;
            return null;
        } else if (type.equals("pipe")) {
            Pipe pipe = new Pipe(this.project, oid, ht_attributes, ht_links);
            pipe.addToDatabase();
            ht_displayables.put(new Long(oid), pipe);
            ht_zdispl.put(new Long(oid), pipe);
            last_displayable = pipe;
            addToLastOpenLayerSet(pipe);
            return null;
        } else if (type.equals("polyline")) {
            Polyline pline = new Polyline(this.project, oid, ht_attributes, ht_links);
            pline.addToDatabase();
            last_displayable = pline;
            ht_displayables.put(new Long(oid), pline);
            ht_zdispl.put(new Long(oid), pline);
            addToLastOpenLayerSet(pline);
            return null;
        } else if (type.equals("connector")) {
            final Connector con = new Connector(this.project, oid, ht_attributes, ht_links);
            if (ht_attributes.containsKey("origin")) {
                legacy.add(new Runnable() {

                    public void run() {
                        con.readLegacyXML(al_layer_sets.get(al_layer_sets.size() - 1), ht_attributes, ht_links);
                    }
                });
            }
            con.addToDatabase();
            last_connector = con;
            last_tree = con;
            last_displayable = con;
            ht_displayables.put(new Long(oid), con);
            ht_zdispl.put(new Long(oid), con);
            addToLastOpenLayerSet(con);
            return null;
        } else if (type.equals("path")) {
            if (null != reca) {
                reca.add(ht_attributes.get("d"));
                return null;
            }
            return null;
        } else if (type.equals("area")) {
            reca = new ReconstructArea();
            if (null != last_area_list) {
                last_area_list_layer_id = Long.parseLong(ht_attributes.get("layer_id"));
            }
            return null;
        } else if (type.equals("area_list")) {
            AreaList area = new AreaList(this.project, oid, ht_attributes, ht_links);
            // why? This looks like an onion
            area.addToDatabase();
            last_area_list = area;
            last_displayable = area;
            ht_displayables.put(new Long(oid), area);
            ht_zdispl.put(new Long(oid), area);
            addToLastOpenLayerSet(area);
            return null;
        } else if (type.equals("tag")) {
            Taggable t = taggables.getLast();
            if (null != t) {
                Object ob = ht_attributes.get("key");
                // defaults to 't'
                int keyCode = KeyEvent.VK_T;
                // KeyEvent.VK_U is char U, not u
                if (null != ob)
                    keyCode = (int) ((String) ob).toUpperCase().charAt(0);
                Tag tag = al_layer_sets.get(al_layer_sets.size() - 1).putTag(ht_attributes.get("name"), keyCode);
                // could be null if name is not found
                if (null != tag)
                    t.addTag(tag);
            }
        } else if (type.equals("ball_ob")) {
            // add a ball to the last open Ball
            if (null != last_ball) {
                last_ball.addBall(Double.parseDouble(ht_attributes.get("x")), Double.parseDouble(ht_attributes.get("y")), Double.parseDouble(ht_attributes.get("r")), Long.parseLong(ht_attributes.get("layer_id")));
            }
            return null;
        } else if (type.equals("ball")) {
            Ball ball = new Ball(this.project, oid, ht_attributes, ht_links);
            ball.addToDatabase();
            last_ball = ball;
            last_displayable = ball;
            ht_displayables.put(new Long(oid), ball);
            ht_zdispl.put(new Long(oid), ball);
            addToLastOpenLayerSet(ball);
            return null;
        } else if (type.equals("stack")) {
            Stack stack = new Stack(this.project, oid, ht_attributes, ht_links);
            stack.addToDatabase();
            last_stack = stack;
            last_displayable = stack;
            ht_displayables.put(new Long(oid), stack);
            ht_zdispl.put(new Long(oid), stack);
            addToLastOpenLayerSet(stack);
        } else if (type.equals("treeline")) {
            Treeline tline = new Treeline(this.project, oid, ht_attributes, ht_links);
            tline.addToDatabase();
            last_treeline = tline;
            last_tree = tline;
            last_treeline_data = new StringBuilder();
            last_displayable = tline;
            ht_displayables.put(oid, tline);
            ht_zdispl.put(oid, tline);
            addToLastOpenLayerSet(tline);
        } else if (type.equals("areatree")) {
            AreaTree art = new AreaTree(this.project, oid, ht_attributes, ht_links);
            art.addToDatabase();
            last_areatree = art;
            last_tree = art;
            last_displayable = art;
            ht_displayables.put(oid, art);
            ht_zdispl.put(oid, art);
            addToLastOpenLayerSet(art);
        } else if (type.equals("dd_item")) {
            if (null != last_dissector) {
                last_dissector.addItem(Integer.parseInt(ht_attributes.get("tag")), Integer.parseInt(ht_attributes.get("radius")), ht_attributes.get("points"));
            }
        } else if (type.equals("label")) {
            DLabel label = new DLabel(project, oid, ht_attributes, ht_links);
            label.addToDatabase();
            ht_displayables.put(new Long(oid), label);
            addToLastOpenLayer(label);
            last_displayable = label;
            return null;
        } else if (type.equals("annot")) {
            last_annotation = new StringBuilder();
            return null;
        } else if (type.equals("patch")) {
            Patch patch = new Patch(project, oid, ht_attributes, ht_links);
            patch.addToDatabase();
            ht_displayables.put(new Long(oid), patch);
            addToLastOpenLayer(patch);
            last_patch = patch;
            last_displayable = patch;
            checkAlphaMasks(patch);
            return null;
        } else if (type.equals("filter")) {
            last_patch_filters.add(newFilter(ht_attributes));
        } else if (type.equals("dissector")) {
            Dissector dissector = new Dissector(this.project, oid, ht_attributes, ht_links);
            dissector.addToDatabase();
            last_dissector = dissector;
            last_displayable = dissector;
            ht_displayables.put(new Long(oid), dissector);
            ht_zdispl.put(new Long(oid), dissector);
            addToLastOpenLayerSet(dissector);
        } else if (type.equals("layer")) {
            // find last open LayerSet, if any
            for (int i = al_layer_sets.size() - 1; i > -1; ) {
                LayerSet set = al_layer_sets.get(i);
                Layer layer = new Layer(project, oid, ht_attributes);
                layer.addToDatabase();
                set.addSilently(layer);
                al_layers.add(layer);
                Object ot = ht_attributes.get("title");
                return new LayerThing(template_layer_thing, project, -1, (null == ot ? null : (String) ot), layer, null);
            }
        } else if (type.equals("layer_set")) {
            LayerSet set = new LayerSet(project, oid, ht_attributes, ht_links);
            last_displayable = set;
            set.addToDatabase();
            ht_displayables.put(new Long(oid), set);
            al_layer_sets.add(set);
            addToLastOpenLayer(set);
            Object ot = ht_attributes.get("title");
            return new LayerThing(template_layer_set_thing, project, -1, (null == ot ? null : (String) ot), set, null);
        } else if (type.equals("calibration")) {
            // find last open LayerSet if any
            for (int i = al_layer_sets.size() - 1; i > -1; ) {
                LayerSet set = al_layer_sets.get(i);
                set.restoreCalibration(ht_attributes);
                return null;
            }
        } else if (type.equals("prop")) {
            // Add property to last created Displayable
            if (null != last_displayable) {
                last_displayable.setProperty(ht_attributes.get("key"), ht_attributes.get("value"));
            }
        } else if (type.equals("linked_prop")) {
            // Add linked property to last created Displayable. Has to wait until the Displayable ids have been resolved to instances.
            if (null != last_displayable) {
                putLinkedProperty(last_displayable, ht_attributes);
            }
        } else {
            Utils.log2("TMLHandler Unknown type: " + type);
        }
    } catch (Exception e) {
        IJError.print(e);
    }
    // default:
    return null;
}
Also used : Ball(ini.trakem2.display.Ball) Connector(ini.trakem2.display.Connector) Node(ini.trakem2.display.Node) Profile(ini.trakem2.display.Profile) AreaTree(ini.trakem2.display.AreaTree) ReconstructArea(ini.trakem2.utils.ReconstructArea) Dissector(ini.trakem2.display.Dissector) LayerSet(ini.trakem2.display.LayerSet) LayerThing(ini.trakem2.tree.LayerThing) Color(java.awt.Color) Pipe(ini.trakem2.display.Pipe) AreaList(ini.trakem2.display.AreaList) Layer(ini.trakem2.display.Layer) SAXException(org.xml.sax.SAXException) SAXParseException(org.xml.sax.SAXParseException) Stack(ini.trakem2.display.Stack) Treeline(ini.trakem2.display.Treeline) DLabel(ini.trakem2.display.DLabel) Polyline(ini.trakem2.display.Polyline) Tag(ini.trakem2.display.Tag) Taggable(ini.trakem2.display.Taggable) Patch(ini.trakem2.display.Patch)

Example 5 with DLabel

use of ini.trakem2.display.DLabel in project TrakEM2 by trakem2.

the class DBLoader method fetchLayer.

/**
 * Load all objects into the Layer: Profile and Pipe from the hs_pt (full of ProjectThing wrapping them), and Patch, LayerSet, DLabel, etc from the database.
 */
private Layer fetchLayer(Project project, long id, HashMap hs_pt) throws Exception {
    ResultSet r = connection.prepareStatement("SELECT * FROM ab_layers WHERE id=" + id).executeQuery();
    Layer layer = null;
    if (r.next()) {
        long layer_id = r.getLong("id");
        layer = new Layer(project, layer_id, r.getDouble("z"), r.getDouble("thickness"));
        // find the Layer's parent
        long parent_id = r.getLong("layer_set_id");
        Object set = hs_pt.get(new Long(parent_id));
        if (null != set) {
            ((LayerSet) set).addSilently(layer);
        } else {
            Utils.log("Loader.fetchLayer: WARNING no parent for layer " + layer);
        }
        // add the displayables from hs_pt that correspond to this layer (and all other objects that belong to the layer)
        HashMap hs_d = new HashMap();
        ResultSet rd = connection.prepareStatement("SELECT ab_displayables.id, ab_profiles.id, layer_id, stack_index FROM ab_displayables,ab_profiles WHERE ab_displayables.id=ab_profiles.id AND layer_id=" + layer_id).executeQuery();
        while (rd.next()) {
            Long idd = new Long(rd.getLong("id"));
            Object ob = hs_pt.get(idd);
            // Utils.log("Found profile with id=" + idd + " and ob = " + ob);
            if (null != ob) {
                hs_d.put(new Integer(rd.getInt("stack_index")), ob);
            }
        }
        rd.close();
        // fetch LayerSet objects (which are also Displayable), and put them in the hs_pt (this is hackerous)
        ResultSet rls = connection.prepareStatement("SELECT * FROM ab_layer_sets, ab_displayables WHERE ab_layer_sets.id=ab_displayables.id AND ab_layer_sets.parent_layer_id=" + id).executeQuery();
        while (rls.next()) {
            long ls_id = rls.getLong("id");
            LayerSet layer_set = new LayerSet(project, ls_id, rls.getString("title"), (float) rls.getDouble("width"), (float) rls.getDouble("height"), rls.getDouble("rot_x"), rls.getDouble("rot_y"), rls.getDouble("rot_z"), (float) rls.getDouble("layer_width"), (float) rls.getDouble("layer_height"), rls.getBoolean("locked"), rls.getInt("snapshots_mode"), new AffineTransform(rls.getDouble("m00"), rls.getDouble("m10"), rls.getDouble("m01"), rls.getDouble("m11"), rls.getDouble("m02"), rls.getDouble("m12")));
            hs_pt.put(new Long(ls_id), layer_set);
            hs_d.put(new Integer(rls.getInt("stack_index")), layer_set);
            layer_set.setLayer(layer, false);
            // find the pipes (or other possible ZDisplayable objects) in the hs_pt that belong to this LayerSet and add them silently
            ResultSet rpi = connection.prepareStatement("SELECT ab_displayables.id, ab_zdisplayables.id, layer_id, layer_set_id, stack_index FROM ab_displayables,ab_zdisplayables WHERE ab_displayables.id=ab_zdisplayables.id AND layer_set_id=" + ls_id + " ORDER BY stack_index ASC").executeQuery();
            while (rpi.next()) {
                Long idd = new Long(rpi.getLong("id"));
                Object ob = hs_pt.get(idd);
                if (null != ob && ob instanceof ZDisplayable) {
                    layer_set.addSilently((ZDisplayable) ob);
                } else {
                    Utils.log("fetchLayer: failed to add a ZDisplayable to the layer_set. zdispl id = " + idd);
                }
            }
            rpi.close();
        }
        rls.close();
        // add Patch objects from ab_patches joint-called with ab_displayables
        ResultSet rp = connection.prepareStatement("SELECT ab_patches.id, ab_displayables.id, layer_id, title, width, height, stack_index, imp_type, locked, min, max, m00, m10, m01, m11, m02, m12 FROM ab_patches,ab_displayables WHERE ab_patches.id=ab_displayables.id AND ab_displayables.layer_id=" + layer_id).executeQuery();
        while (rp.next()) {
            long patch_id = rp.getLong("id");
            Patch patch = new Patch(project, patch_id, rp.getString("title"), (float) rp.getDouble("width"), (float) rp.getDouble("height"), rp.getInt("o_width"), rp.getInt("o_height"), rp.getInt("imp_type"), rp.getBoolean("locked"), rp.getDouble("min"), rp.getDouble("max"), new AffineTransform(rp.getDouble("m00"), rp.getDouble("m10"), rp.getDouble("m01"), rp.getDouble("m11"), rp.getDouble("m02"), rp.getDouble("m12")));
            // collecting all Displayable objects to reconstruct links
            hs_pt.put(new Long(patch_id), patch);
            hs_d.put(new Integer(rp.getInt("stack_index")), patch);
        }
        rp.close();
        // add DLabel objects
        ResultSet rl = connection.prepareStatement("SELECT ab_labels.id, ab_displayables.id, layer_id, title, width, height, m00, m10, m01, m11, m02, m12, stack_index, font_name, font_style, font_size, ab_labels.type, locked FROM ab_labels,ab_displayables WHERE ab_labels.id=ab_displayables.id AND ab_displayables.layer_id=" + layer_id).executeQuery();
        while (rl.next()) {
            long label_id = rl.getLong("id");
            DLabel label = new DLabel(project, label_id, rl.getString("title"), (float) rl.getDouble("width"), (float) rl.getDouble("height"), rl.getInt("type"), rl.getString("font_name"), rl.getInt("font_style"), rl.getInt("font_size"), rl.getBoolean("locked"), new AffineTransform(rl.getDouble("m00"), rl.getDouble("m10"), rl.getDouble("m01"), rl.getDouble("m11"), rl.getDouble("m02"), rl.getDouble("m12")));
            // collecting all Displayable objects to reconstruct links
            hs_pt.put(new Long(label_id), label);
            hs_d.put(new Integer(rl.getInt("stack_index")), label);
        }
        rl.close();
        // Add silently to the Layer ordered by stack index
        Set e = hs_d.keySet();
        Object[] si = new Object[hs_d.size()];
        si = e.toArray(si);
        // will it sort an array of integers correctly? Who knows!
        Arrays.sort(si);
        for (int i = 0; i < si.length; i++) {
            // Utils.log("Loader layer.addSilently: adding " + (DBObject)hs_d.get(si[i]));
            layer.addSilently((DBObject) hs_d.get(si[i]));
        }
        // find displays and open later, when fully loaded.
        ResultSet rdi = connection.prepareStatement("SELECT * FROM ab_displays WHERE layer_id=" + layer.getId()).executeQuery();
        while (rdi.next()) {
            fetchDisplay(rdi, layer);
        }
        rdi.close();
    }
    r.close();
    return layer;
}
Also used : ResultSet(java.sql.ResultSet) Set(java.util.Set) LayerSet(ini.trakem2.display.LayerSet) LayerSet(ini.trakem2.display.LayerSet) HashMap(java.util.HashMap) Layer(ini.trakem2.display.Layer) Point(java.awt.Point) PGpoint(org.postgresql.geometric.PGpoint) ZDisplayable(ini.trakem2.display.ZDisplayable) DLabel(ini.trakem2.display.DLabel) ResultSet(java.sql.ResultSet) AffineTransform(java.awt.geom.AffineTransform) Patch(ini.trakem2.display.Patch)

Aggregations

DLabel (ini.trakem2.display.DLabel)4 Layer (ini.trakem2.display.Layer)4 LayerSet (ini.trakem2.display.LayerSet)3 Patch (ini.trakem2.display.Patch)3 Rectangle (java.awt.Rectangle)3 ImagePlus (ij.ImagePlus)2 Project (ini.trakem2.Project)2 ZDisplayable (ini.trakem2.display.ZDisplayable)2 LayerThing (ini.trakem2.tree.LayerThing)2 ProjectThing (ini.trakem2.tree.ProjectThing)2 Color (java.awt.Color)2 Point (java.awt.Point)2 AffineTransform (java.awt.geom.AffineTransform)2 File (java.io.File)2 ArrayList (java.util.ArrayList)2 Collection (java.util.Collection)2 HashMap (java.util.HashMap)2 GenericDialog (ij.gui.GenericDialog)1 PolygonRoi (ij.gui.PolygonRoi)1 Roi (ij.gui.Roi)1