Search in sources :

Example 1 with Align

use of mpicbg.trakem2.align.Align in project TrakEM2 by trakem2.

the class ElasticLayerAlignment method exec.

/**
 * @param param
 * @param layerRange
 * @param fixedLayers
 * @param emptyLayers
 * @param box
 * @param filter
 * @param useTps true if using TPS transforms, otherwise MLS
 * @throws Exception
 */
@SuppressWarnings("deprecation")
public final void exec(final Param param, final Project project, final List<Layer> layerRange, final Set<Layer> fixedLayers, final Set<Layer> emptyLayers, final Rectangle box, final boolean propagateTransformBefore, final boolean propagateTransformAfter, final Filter<Patch> filter) throws Exception {
    final ExecutorService service = ExecutorProvider.getExecutorService(1.0f);
    /* create tiles and models for all layers */
    final ArrayList<Tile<?>> tiles = new ArrayList<Tile<?>>();
    for (int i = 0; i < layerRange.size(); ++i) {
        switch(param.desiredModelIndex) {
            case 0:
                tiles.add(new Tile<TranslationModel2D>(new TranslationModel2D()));
                break;
            case 1:
                tiles.add(new Tile<RigidModel2D>(new RigidModel2D()));
                break;
            case 2:
                tiles.add(new Tile<SimilarityModel2D>(new SimilarityModel2D()));
                break;
            case 3:
                tiles.add(new Tile<AffineModel2D>(new AffineModel2D()));
                break;
            case 4:
                tiles.add(new Tile<HomographyModel2D>(new HomographyModel2D()));
                break;
            default:
                return;
        }
    }
    /* collect all pairs of slices for which a model could be found */
    final ArrayList<Triple<Integer, Integer, AbstractModel<?>>> pairs = new ArrayList<Triple<Integer, Integer, AbstractModel<?>>>();
    if (!param.isAligned) {
        preAlignStack(param, project, layerRange, box, filter, pairs);
    } else {
        for (int i = 0; i < layerRange.size(); ++i) {
            final int range = Math.min(layerRange.size(), i + param.maxNumNeighbors + 1);
            for (int j = i + 1; j < range; ++j) {
                pairs.add(new Triple<Integer, Integer, AbstractModel<?>>(i, j, new TranslationModel2D()));
            }
        }
    }
    /* Elastic alignment */
    /* Initialization */
    final TileConfiguration initMeshes = new TileConfiguration();
    final int meshWidth = (int) Math.ceil(box.width * param.layerScale);
    final int meshHeight = (int) Math.ceil(box.height * param.layerScale);
    final ArrayList<SpringMesh> meshes = new ArrayList<SpringMesh>(layerRange.size());
    for (int i = 0; i < layerRange.size(); ++i) {
        meshes.add(new SpringMesh(param.resolutionSpringMesh, meshWidth, meshHeight, param.stiffnessSpringMesh, param.maxStretchSpringMesh * param.layerScale, param.dampSpringMesh));
    }
    // final int blockRadius = Math.max( 32, meshWidth / p.resolutionSpringMesh / 2 );
    final int blockRadius = Math.max(16, mpicbg.util.Util.roundPos(param.layerScale * param.blockRadius));
    Utils.log("effective block radius = " + blockRadius);
    final ArrayList<Future<BlockMatchPairCallable.BlockMatchResults>> futures = new ArrayList<Future<BlockMatchPairCallable.BlockMatchResults>>(pairs.size());
    for (final Triple<Integer, Integer, AbstractModel<?>> pair : pairs) {
        /* free memory */
        project.getLoader().releaseAll();
        final SpringMesh m1 = meshes.get(pair.a);
        final SpringMesh m2 = meshes.get(pair.b);
        final ArrayList<Vertex> v1 = m1.getVertices();
        final ArrayList<Vertex> v2 = m2.getVertices();
        final Layer layer1 = layerRange.get(pair.a);
        final Layer layer2 = layerRange.get(pair.b);
        final boolean layer1Fixed = fixedLayers.contains(layer1);
        final boolean layer2Fixed = fixedLayers.contains(layer2);
        if (!(layer1Fixed && layer2Fixed)) {
            final BlockMatchPairCallable bmpc = new BlockMatchPairCallable(pair, layerRange, layer1Fixed, layer2Fixed, filter, param, v1, v2, box);
            futures.add(service.submit(bmpc));
        }
    }
    for (final Future<BlockMatchPairCallable.BlockMatchResults> future : futures) {
        final BlockMatchPairCallable.BlockMatchResults results = future.get();
        final Collection<PointMatch> pm12 = results.pm12, pm21 = results.pm21;
        final Triple<Integer, Integer, AbstractModel<?>> pair = results.pair;
        final Tile<?> t1 = tiles.get(pair.a);
        final Tile<?> t2 = tiles.get(pair.b);
        final SpringMesh m1 = meshes.get(pair.a);
        final SpringMesh m2 = meshes.get(pair.b);
        final double springConstant = 1.0 / (pair.b - pair.a);
        final boolean layer1Fixed = results.layer1Fixed;
        final boolean layer2Fixed = results.layer2Fixed;
        if (layer1Fixed) {
            initMeshes.fixTile(t1);
        } else {
            if (param.useLocalSmoothnessFilter) {
                Utils.log(pair.a + " > " + pair.b + ": " + pm12.size() + " candidates passed local smoothness filter.");
            } else {
                Utils.log(pair.a + " > " + pair.b + ": found " + pm12.size() + " correspondences.");
            }
            for (final PointMatch pm : pm12) {
                final Vertex p1 = (Vertex) pm.getP1();
                final Vertex p2 = new Vertex(pm.getP2());
                p1.addSpring(p2, new Spring(0, springConstant));
                m2.addPassiveVertex(p2);
            }
            /*
                * adding Tiles to the initialing TileConfiguration, adding a Tile
                * multiple times does not harm because the TileConfiguration is
                * backed by a Set.
                */
            if (pm12.size() > pair.c.getMinNumMatches()) {
                initMeshes.addTile(t1);
                initMeshes.addTile(t2);
                t1.connect(t2, pm12);
            }
        }
        if (layer2Fixed)
            initMeshes.fixTile(t2);
        else {
            if (param.useLocalSmoothnessFilter) {
                Utils.log(pair.a + " < " + pair.b + ": " + pm21.size() + " candidates passed local smoothness filter.");
            } else {
                Utils.log(pair.a + " < " + pair.b + ": found " + pm21.size() + " correspondences.");
            }
            for (final PointMatch pm : pm21) {
                final Vertex p1 = (Vertex) pm.getP1();
                final Vertex p2 = new Vertex(pm.getP2());
                p1.addSpring(p2, new Spring(0, springConstant));
                m1.addPassiveVertex(p2);
            }
            /*
                * adding Tiles to the initialing TileConfiguration, adding a Tile
                * multiple times does not harm because the TileConfiguration is
                * backed by a Set.
                */
            if (pm21.size() > pair.c.getMinNumMatches()) {
                initMeshes.addTile(t1);
                initMeshes.addTile(t2);
                t2.connect(t1, pm21);
            }
        }
        Utils.log(pair.a + " <> " + pair.b + " spring constant = " + springConstant);
    }
    /* pre-align by optimizing a piecewise linear model */
    initMeshes.optimize(param.maxEpsilon * param.layerScale, param.maxIterationsSpringMesh, param.maxPlateauwidthSpringMesh);
    for (int i = 0; i < layerRange.size(); ++i) meshes.get(i).init(tiles.get(i).getModel());
    /* optimize the meshes */
    try {
        final long t0 = System.currentTimeMillis();
        Utils.log("Optimizing spring meshes...");
        if (param.useLegacyOptimizer) {
            Utils.log("  ...using legacy optimizer...");
            SpringMesh.optimizeMeshes2(meshes, param.maxEpsilon * param.layerScale, param.maxIterationsSpringMesh, param.maxPlateauwidthSpringMesh, param.visualize);
        } else {
            SpringMesh.optimizeMeshes(meshes, param.maxEpsilon * param.layerScale, param.maxIterationsSpringMesh, param.maxPlateauwidthSpringMesh, param.visualize);
        }
        Utils.log("Done optimizing spring meshes. Took " + (System.currentTimeMillis() - t0) + " ms");
    } catch (final NotEnoughDataPointsException e) {
        Utils.log("There were not enough data points to get the spring mesh optimizing.");
        e.printStackTrace();
        return;
    }
    /* translate relative to bounding box */
    for (final SpringMesh mesh : meshes) {
        for (final PointMatch pm : mesh.getVA().keySet()) {
            final Point p1 = pm.getP1();
            final Point p2 = pm.getP2();
            final double[] l = p1.getL();
            final double[] w = p2.getW();
            l[0] = l[0] / param.layerScale + box.x;
            l[1] = l[1] / param.layerScale + box.y;
            w[0] = w[0] / param.layerScale + box.x;
            w[1] = w[1] / param.layerScale + box.y;
        }
    }
    /* free memory */
    project.getLoader().releaseAll();
    final Layer first = layerRange.get(0);
    final List<Layer> layers = first.getParent().getLayers();
    final LayerSet ls = first.getParent();
    final Area infArea = AreaUtils.infiniteArea();
    final List<VectorData> vectorData = new ArrayList<VectorData>();
    for (final Layer layer : ls.getLayers()) {
        vectorData.addAll(Utils.castCollection(layer.getDisplayables(VectorData.class, false, true), VectorData.class, true));
    }
    vectorData.addAll(Utils.castCollection(ls.getZDisplayables(VectorData.class, true), VectorData.class, true));
    /* transfer layer transform into patch transforms and append to patches */
    if (propagateTransformBefore || propagateTransformAfter) {
        if (propagateTransformBefore) {
            final ThinPlateSplineTransform tps = makeTPS(meshes.get(0).getVA().keySet());
            final int firstLayerIndex = first.getParent().getLayerIndex(first.getId());
            for (int i = 0; i < firstLayerIndex; ++i) {
                applyTransformToLayer(layers.get(i), tps, filter);
                for (final VectorData vd : vectorData) {
                    vd.apply(layers.get(i), infArea, tps);
                }
            }
        }
        if (propagateTransformAfter) {
            final Layer last = layerRange.get(layerRange.size() - 1);
            final CoordinateTransform ct;
            if (param.useTps)
                ct = makeTPS(meshes.get(meshes.size() - 1).getVA().keySet());
            else {
                final MovingLeastSquaresTransform2 mls = new MovingLeastSquaresTransform2();
                mls.setMatches(meshes.get(meshes.size() - 1).getVA().keySet());
                ct = mls;
            }
            final int lastLayerIndex = last.getParent().getLayerIndex(last.getId());
            for (int i = lastLayerIndex + 1; i < layers.size(); ++i) {
                applyTransformToLayer(layers.get(i), ct, filter);
                for (final VectorData vd : vectorData) {
                    vd.apply(layers.get(i), infArea, ct);
                }
            }
        }
    }
    for (int l = 0; l < layerRange.size(); ++l) {
        IJ.showStatus("Applying transformation to patches ...");
        IJ.showProgress(0, layerRange.size());
        final Layer layer = layerRange.get(l);
        final ThinPlateSplineTransform tps = makeTPS(meshes.get(l).getVA().keySet());
        applyTransformToLayer(layer, tps, filter);
        for (final VectorData vd : vectorData) {
            vd.apply(layer, infArea, tps);
        }
        if (Thread.interrupted()) {
            Utils.log("Interrupted during applying transformations to patches.  No all patches have been updated.  Re-generate mipmaps manually.");
        }
        IJ.showProgress(l + 1, layerRange.size());
    }
    /* update patch mipmaps */
    final int firstLayerIndex;
    final int lastLayerIndex;
    if (propagateTransformBefore)
        firstLayerIndex = 0;
    else {
        firstLayerIndex = first.getParent().getLayerIndex(first.getId());
    }
    if (propagateTransformAfter)
        lastLayerIndex = layers.size() - 1;
    else {
        final Layer last = layerRange.get(layerRange.size() - 1);
        lastLayerIndex = last.getParent().getLayerIndex(last.getId());
    }
    for (int i = firstLayerIndex; i <= lastLayerIndex; ++i) {
        final Layer layer = layers.get(i);
        if (!(emptyLayers.contains(layer) || fixedLayers.contains(layer))) {
            for (final Patch patch : AlignmentUtils.filterPatches(layer, filter)) patch.updateMipMaps();
        }
    }
    Utils.log("Done.");
}
Also used : AbstractModel(mpicbg.models.AbstractModel) BlockMatchPairCallable(mpicbg.trakem2.align.concurrent.BlockMatchPairCallable) ArrayList(java.util.ArrayList) RigidModel2D(mpicbg.models.RigidModel2D) SimilarityModel2D(mpicbg.models.SimilarityModel2D) LayerSet(ini.trakem2.display.LayerSet) HomographyModel2D(mpicbg.models.HomographyModel2D) Spring(mpicbg.models.Spring) Triple(mpicbg.trakem2.util.Triple) Area(java.awt.geom.Area) TranslationModel2D(mpicbg.models.TranslationModel2D) TileConfiguration(mpicbg.models.TileConfiguration) CoordinateTransform(mpicbg.trakem2.transform.CoordinateTransform) Patch(ini.trakem2.display.Patch) NotEnoughDataPointsException(mpicbg.models.NotEnoughDataPointsException) Vertex(mpicbg.models.Vertex) SpringMesh(mpicbg.models.SpringMesh) ThinPlateSplineTransform(mpicbg.trakem2.transform.ThinPlateSplineTransform) VectorData(ini.trakem2.display.VectorData) AffineModel2D(mpicbg.models.AffineModel2D) Tile(mpicbg.models.Tile) Point(mpicbg.models.Point) Layer(ini.trakem2.display.Layer) Point(mpicbg.models.Point) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) PointMatch(mpicbg.models.PointMatch) MovingLeastSquaresTransform2(mpicbg.trakem2.transform.MovingLeastSquaresTransform2) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future)

Example 2 with Align

use of mpicbg.trakem2.align.Align in project TrakEM2 by trakem2.

the class Display method getPopupMenu.

/**
 * Return a context-sensitive popup menu.
 */
protected JPopupMenu getPopupMenu() {
    // get the job canceling dialog
    if (!canvas.isInputEnabled()) {
        return project.getLoader().getJobsPopup(this);
    }
    // create new
    this.popup = new JPopupMenu();
    JMenuItem item = null;
    JMenu menu = null;
    if (mode instanceof InspectPatchTrianglesMode) {
        item = new JMenuItem("Exit inspection");
        item.addActionListener(this);
        popup.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
        return popup;
    } else if (canvas.isTransforming()) {
        item = new JMenuItem("Apply transform");
        item.addActionListener(this);
        popup.add(item);
        // dummy, for I don't add a MenuKeyListener, but "works" through the normal key listener. It's here to provide a visual cue
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
        item = new JMenuItem("Apply transform propagating to last layer");
        item.addActionListener(this);
        popup.add(item);
        if (layer.getParent().indexOf(layer) == layer.getParent().size() - 1)
            item.setEnabled(false);
        if (!(getMode().getClass() == AffineTransformMode.class || getMode().getClass() == NonLinearTransformMode.class))
            item.setEnabled(false);
        item = new JMenuItem("Apply transform propagating to first layer");
        item.addActionListener(this);
        popup.add(item);
        if (0 == layer.getParent().indexOf(layer))
            item.setEnabled(false);
        if (!(getMode().getClass() == AffineTransformMode.class || getMode().getClass() == NonLinearTransformMode.class))
            item.setEnabled(false);
        item = new JMenuItem("Cancel transform");
        item.addActionListener(this);
        popup.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
        item = new JMenuItem("Specify transform...");
        item.addActionListener(this);
        popup.add(item);
        if (getMode().getClass() != AffineTransformMode.class)
            item.setEnabled(false);
        if (getMode().getClass() == ManualAlignMode.class) {
            final JMenuItem lexport = new JMenuItem("Export landmarks");
            popup.add(lexport);
            final JMenuItem limport = new JMenuItem("Import landmarks");
            popup.add(limport);
            final ActionListener a = new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent ae) {
                    final ManualAlignMode mam = (ManualAlignMode) getMode();
                    final Object source = ae.getSource();
                    if (lexport == source) {
                        mam.exportLandmarks();
                    } else if (limport == source) {
                        mam.importLandmarks();
                    }
                }
            };
            lexport.addActionListener(a);
            limport.addActionListener(a);
        }
        return popup;
    }
    final Class<?> aclass = null == active ? null : active.getClass();
    if (null != active) {
        if (Profile.class == aclass) {
            item = new JMenuItem("Duplicate, link and send to next layer");
            item.addActionListener(this);
            popup.add(item);
            Layer nl = layer.getParent().next(layer);
            if (nl == layer)
                item.setEnabled(false);
            item = new JMenuItem("Duplicate, link and send to previous layer");
            item.addActionListener(this);
            popup.add(item);
            nl = layer.getParent().previous(layer);
            if (nl == layer)
                item.setEnabled(false);
            menu = new JMenu("Duplicate, link and send to");
            int i = 1;
            for (final Layer la : layer.getParent().getLayers()) {
                // TODO should label which layers contain Profile instances linked to the one being duplicated
                item = new JMenuItem(i + ": z = " + la.getZ());
                // TODO should label which layers contain Profile instances linked to the one being duplicated
                item.addActionListener(this);
                // TODO should label which layers contain Profile instances linked to the one being duplicated
                menu.add(item);
                if (la == this.layer)
                    item.setEnabled(false);
                i++;
            }
            popup.add(menu);
            item = new JMenuItem("Duplicate, link and send to...");
            item.addActionListener(this);
            popup.add(item);
            popup.addSeparator();
            item = new JMenuItem("Unlink from images");
            item.addActionListener(this);
            popup.add(item);
            // isLinked() checks if it's linked to a Patch in its own layer
            if (!active.isLinked())
                item.setEnabled(false);
            item = new JMenuItem("Show in 3D");
            item.addActionListener(this);
            popup.add(item);
            popup.addSeparator();
        } else if (Patch.class == aclass) {
            final JMenu m = new JMenu("Patch");
            item = new JMenuItem("Fill ROI in alpha mask");
            item.addActionListener(this);
            m.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, 0));
            item.setEnabled(null != getRoi());
            item = new JMenuItem("Fill inverse ROI in alpha mask");
            item.addActionListener(this);
            m.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.SHIFT_MASK));
            item.setEnabled(null != getRoi());
            item = new JMenuItem("Remove alpha mask");
            item.addActionListener(this);
            m.add(item);
            if (!((Patch) active).hasAlphaMask())
                item.setEnabled(false);
            item = new JMenuItem("Unlink from images");
            item.addActionListener(this);
            m.add(item);
            if (!active.isLinked(Patch.class))
                item.setEnabled(false);
            if (((Patch) active).isStack()) {
                item = new JMenuItem("Unlink slices");
                item.addActionListener(this);
                m.add(item);
            }
            final int n_sel_patches = selection.getSelected(Patch.class).size();
            item = new JMenuItem("Snap");
            item.addActionListener(this);
            m.add(item);
            item.setEnabled(1 == n_sel_patches);
            item = new JMenuItem("Montage");
            item.addActionListener(this);
            m.add(item);
            item.setEnabled(n_sel_patches > 1);
            item = new JMenuItem("Lens correction");
            item.addActionListener(this);
            m.add(item);
            item.setEnabled(n_sel_patches > 1);
            item = new JMenuItem("Blend");
            item.addActionListener(this);
            m.add(item);
            item.setEnabled(n_sel_patches > 1);
            item = new JMenuItem("Open image");
            item.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent e) {
                    for (final Patch p : selection.get(Patch.class)) {
                        p.getImagePlus().show();
                    }
                }
            });
            m.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.SHIFT_MASK, true));
            item = new JMenuItem("Open original image");
            item.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent e) {
                    for (final Patch p : selection.get(Patch.class)) {
                        p.getProject().getLoader().releaseToFit(p.getOWidth(), p.getOHeight(), p.getType(), 5);
                        p.getProject().getLoader().openImagePlus(p.getImageFilePath()).show();
                    }
                }
            });
            item = new JMenuItem("View volume");
            item.addActionListener(this);
            m.add(item);
            final HashSet<Displayable> hs = active.getLinked(Patch.class);
            if (null == hs || 0 == hs.size())
                item.setEnabled(false);
            item = new JMenuItem("View orthoslices");
            item.addActionListener(this);
            m.add(item);
            // if no Patch instances among the directly linked, then it's not a stack
            if (null == hs || 0 == hs.size())
                item.setEnabled(false);
            popup.add(m);
            popup.addSeparator();
        } else {
            item = new JMenuItem("Unlink");
            item.addActionListener(this);
            popup.add(item);
            item = new JMenuItem("Show in 3D");
            item.addActionListener(this);
            popup.add(item);
            popup.addSeparator();
        }
        if (AreaList.class == aclass) {
            final ArrayList<?> al = selection.getSelected();
            int n = 0;
            for (final Iterator<?> it = al.iterator(); it.hasNext(); ) {
                if (it.next().getClass() == AreaList.class)
                    n++;
            }
            item = new JMenuItem("Merge");
            item.addActionListener(this);
            popup.add(item);
            if (n < 2)
                item.setEnabled(false);
            item = new JMenuItem("Split");
            item.addActionListener(this);
            popup.add(item);
            if (n < 1)
                item.setEnabled(false);
            addAreaListAreasMenu(popup, active);
            popup.addSeparator();
        } else if (Pipe.class == aclass) {
            item = new JMenuItem("Reverse point order");
            item.addActionListener(this);
            popup.add(item);
            popup.addSeparator();
        } else if (Treeline.class == aclass || AreaTree.class == aclass) {
            if (AreaTree.class == aclass)
                addAreaTreeAreasMenu(popup, (AreaTree) active);
            item = new JMenuItem("Reroot");
            item.addActionListener(this);
            popup.add(item);
            item = new JMenuItem("Part subtree");
            item.addActionListener(this);
            popup.add(item);
            item = new JMenuItem("Join");
            item.addActionListener(this);
            popup.add(item);
            item = new JMenuItem("Show tabular view");
            item.addActionListener(this);
            popup.add(item);
            final Collection<Tree> trees = selection.get(Tree.class);
            // 
            final JMenu nodeMenu = new JMenu("Nodes");
            item = new JMenuItem("Mark");
            item.addActionListener(this);
            nodeMenu.add(item);
            item = new JMenuItem("Clear marks (selected Trees)");
            item.addActionListener(this);
            nodeMenu.add(item);
            final JMenuItem nodeColor = new JMenuItem("Color...");
            nodeMenu.add(nodeColor);
            nodeColor.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_MASK, true));
            final JMenuItem nodePairColor = new JMenuItem("Color path between two nodes tagged as...");
            nodeMenu.add(nodePairColor);
            final JMenuItem nodeRadius = active instanceof Treeline ? new JMenuItem("Radius...") : null;
            if (null != nodeRadius) {
                nodeMenu.add(nodeRadius);
                nodeRadius.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0, true));
            }
            final JMenuItem removeAllTags = new JMenuItem("Drop all tags (selected trees)");
            nodeMenu.add(removeAllTags);
            final JMenuItem removeTag = new JMenuItem("Drop all occurrences of tag...");
            nodeMenu.add(removeTag);
            final JMenuItem colorizeByNodeCentrality = new JMenuItem("Colorize by node betweenness centrality");
            nodeMenu.add(colorizeByNodeCentrality);
            final JMenuItem colorizeByBranchCentrality = new JMenuItem("Colorize by branch betweenness centrality");
            nodeMenu.add(colorizeByBranchCentrality);
            popup.add(nodeMenu);
            final ActionListener ln = new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent ae) {
                    if (null == active) {
                        Utils.showMessage("No tree selected!");
                        return;
                    }
                    if (!(active instanceof Tree)) {
                        Utils.showMessage("The selected object is not a Tree!");
                        return;
                    }
                    final Tree tree = (Tree) active;
                    final Object src = ae.getSource();
                    // 
                    if (src == nodeColor) {
                        final Node nd = tree.getLastVisited();
                        if (null == nd) {
                            Utils.showMessage("Select a node first by clicking on it\nor moving the mouse over it and pushing 'g'.");
                            return;
                        }
                        // sets an undo step
                        tree.adjustNodeColors(nd);
                    } else if (src == nodePairColor) {
                        final TreeMap<String, Tag> sm = getTags(tree);
                        if (null == sm)
                            return;
                        if (1 == sm.size()) {
                            Utils.showMessage("Need at least two different tags in the tree!");
                            return;
                        }
                        final Color color = tree.getColor();
                        final GenericDialog gd = new GenericDialog("Node colors");
                        gd.addSlider("Red: ", 0, 255, color.getRed());
                        gd.addSlider("Green: ", 0, 255, color.getGreen());
                        gd.addSlider("Blue: ", 0, 255, color.getBlue());
                        final String[] stags = asStrings(sm);
                        sm.keySet().toArray(stags);
                        gd.addChoice("Upstream tag:", stags, stags[0]);
                        gd.addChoice("Downstream tag:", stags, stags[1]);
                        gd.showDialog();
                        if (gd.wasCanceled())
                            return;
                        final Color newColor = new Color((int) gd.getNextNumber(), (int) gd.getNextNumber(), (int) gd.getNextNumber());
                        final Tag upstreamTag = sm.get(gd.getNextChoice());
                        final Tag downstreamTag = sm.get(gd.getNextChoice());
                        final List<Tree<?>.NodePath> pairs = tree.findTaggedPairs(upstreamTag, downstreamTag);
                        if (null == pairs || pairs.isEmpty()) {
                            Utils.showMessage("No pairs found for '" + upstreamTag + "' and '" + downstreamTag + "'");
                            return;
                        }
                        getLayerSet().addDataEditStep(tree);
                        for (final Tree<?>.NodePath pair : pairs) {
                            for (final Node<?> nd : pair.path) {
                                nd.setColor(newColor);
                            }
                        }
                        getLayerSet().addDataEditStep(tree);
                        Display.repaint();
                    } else if (src == nodeRadius) {
                        if (!(tree instanceof Treeline))
                            return;
                        final Node nd = tree.getLastVisited();
                        if (null == nd) {
                            Utils.showMessage("Select a node first by clicking on it\nor moving the mouse over it and pushing 'g'.");
                            return;
                        }
                        // sets an undo step
                        ((Treeline) tree).askAdjustRadius(nd);
                    } else if (src == removeAllTags) {
                        if (!Utils.check("Really remove all tags from all selected trees?"))
                            return;
                        final List<Tree> sel = selection.get(Tree.class);
                        getLayerSet().addDataEditStep(new HashSet<Displayable>(sel));
                        try {
                            for (final Tree t : sel) {
                                t.dropAllTags();
                            }
                            // current state
                            getLayerSet().addDataEditStep(new HashSet<Displayable>(sel));
                        } catch (final Exception e) {
                            getLayerSet().undoOneStep();
                            IJError.print(e);
                        }
                        Display.repaint();
                    } else if (src == removeTag) {
                        final TreeMap<String, Tag> tags = getTags(tree);
                        final String[] ts = asStrings(tags);
                        final GenericDialog gd = new GenericDialog("Remove tags");
                        gd.addChoice("Tag:", ts, ts[0]);
                        final String[] c = new String[] { "Active tree", "All selected trees and connectors", "All trees and connectors" };
                        gd.addChoice("From: ", c, c[0]);
                        gd.showDialog();
                        if (gd.wasCanceled())
                            return;
                        final HashSet<Displayable> ds = new HashSet<Displayable>();
                        final Tag tag = tags.get(gd.getNextChoice());
                        switch(gd.getNextChoiceIndex()) {
                            case 0:
                                ds.add(tree);
                                break;
                            case 1:
                                ds.addAll(selection.get(Tree.class));
                            case 2:
                                ds.addAll(getLayerSet().getZDisplayables(Tree.class, true));
                        }
                        getLayerSet().addDataEditStep(ds);
                        try {
                            for (final Displayable d : ds) {
                                final Tree t = (Tree) d;
                                t.removeTag(tag);
                            }
                            getLayerSet().addDataEditStep(ds);
                        } catch (final Exception e) {
                            getLayerSet().undoOneStep();
                            IJError.print(e);
                        }
                        Display.repaint();
                    } else if (src == colorizeByNodeCentrality) {
                        final List<Tree> ts = selection.get(Tree.class);
                        final HashSet<Tree> ds = new HashSet<Tree>(ts);
                        getLayerSet().addDataEditStep(ds);
                        try {
                            for (final Tree t : ts) {
                                t.colorizeByNodeBetweennessCentrality();
                            }
                            getLayerSet().addDataEditStep(ds);
                            Display.repaint();
                        } catch (final Exception e) {
                            getLayerSet().undoOneStep();
                            IJError.print(e);
                        }
                    } else if (src == colorizeByBranchCentrality) {
                        final List<Tree> ts = selection.get(Tree.class);
                        final HashSet<Tree> ds = new HashSet<Tree>(ts);
                        getLayerSet().addDataEditStep(ds);
                        try {
                            for (final Tree t : ts) {
                                t.colorizeByBranchBetweennessCentrality(2);
                            }
                            getLayerSet().addDataEditStep(ds);
                            Display.repaint();
                        } catch (final Exception e) {
                            getLayerSet().undoOneStep();
                            IJError.print(e);
                        }
                    }
                }
            };
            for (final JMenuItem a : new JMenuItem[] { nodeColor, nodePairColor, nodeRadius, removeAllTags, removeTag, colorizeByNodeCentrality, colorizeByBranchCentrality }) {
                if (null == a)
                    continue;
                a.addActionListener(ln);
            }
            // 
            final JMenu review = new JMenu("Review");
            final JMenuItem tgenerate = new JMenuItem("Generate review stacks (selected Trees)");
            review.add(tgenerate);
            tgenerate.setEnabled(trees.size() > 0);
            final JMenuItem tslab = new JMenuItem("Generate review stack for current slab");
            review.add(tslab);
            final JMenuItem tsubtree = new JMenuItem("Generate review stacks for subtree");
            review.add(tsubtree);
            final JMenuItem tremove = new JMenuItem("Remove reviews (selected Trees)");
            review.add(tremove);
            tremove.setEnabled(trees.size() > 0);
            final JMenuItem tconnectors = new JMenuItem("View table of outgoing/incoming connectors");
            review.add(tconnectors);
            final ActionListener l = new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent ae) {
                    if (!Utils.check("Really " + ae.getActionCommand())) {
                        return;
                    }
                    dispatcher.exec(new Runnable() {

                        @Override
                        public void run() {
                            int count = 0;
                            for (final Tree<?> t : trees) {
                                Utils.log("Processing " + (++count) + "/" + trees.size());
                                Bureaucrat bu = null;
                                if (ae.getSource() == tgenerate)
                                    bu = t.generateAllReviewStacks();
                                else if (ae.getSource() == tremove)
                                    bu = t.removeReviews();
                                else if (ae.getSource() == tslab) {
                                    final Point po = canvas.consumeLastPopupPoint();
                                    Utils.log2(po, layer, 1.0);
                                    bu = t.generateReviewStackForSlab(po.x, po.y, Display.this.layer, 1.0);
                                } else if (ae.getSource() == tsubtree) {
                                    final Point po = canvas.consumeLastPopupPoint();
                                    bu = t.generateSubtreeReviewStacks(po.x, po.y, Display.this.layer, 1.0);
                                }
                                if (null != bu)
                                    try {
                                        bu.getWorker().join();
                                    } catch (final InterruptedException ie) {
                                        return;
                                    }
                            }
                        }
                    });
                }
            };
            for (final JMenuItem c : new JMenuItem[] { tgenerate, tslab, tsubtree, tremove }) c.addActionListener(l);
            tconnectors.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent ae) {
                    for (final Tree<?> t : trees) TreeConnectorsView.create(t);
                }
            });
            popup.add(review);
            final JMenu go = new JMenu("Go");
            item = new JMenuItem("Previous branch node or start");
            item.addActionListener(this);
            go.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0, true));
            item = new JMenuItem("Next branch node or end");
            item.addActionListener(this);
            go.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0, true));
            item = new JMenuItem("Root");
            item.addActionListener(this);
            go.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, true));
            go.addSeparator();
            item = new JMenuItem("Last added node");
            item.addActionListener(this);
            go.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, 0, true));
            item = new JMenuItem("Last edited node");
            item.addActionListener(this);
            go.add(item);
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, true));
            popup.add(go);
            final JMenu tmeasure = new JMenu("Measure");
            final JMenuItem dist_to_root = new JMenuItem("Distance from this node to root");
            tmeasure.add(dist_to_root);
            final JMenuItem dist_to_tag = new JMenuItem("Distance from this node to all nodes tagged as...");
            tmeasure.add(dist_to_tag);
            final JMenuItem dist_to_mark = new JMenuItem("Distance from this node to the marked node");
            tmeasure.add(dist_to_mark);
            final JMenuItem dist_pairs = new JMenuItem("Shortest distances between all pairs of nodes tagged as...");
            tmeasure.add(dist_pairs);
            final ActionListener tma = getTreePathMeasureListener((Tree<?>) active);
            for (final JMenuItem mi : new JMenuItem[] { dist_to_root, dist_to_tag, dist_to_mark, dist_pairs }) {
                mi.addActionListener(tma);
            }
            popup.add(tmeasure);
            final String[] name = new String[] { AreaTree.class.getSimpleName(), Treeline.class.getSimpleName() };
            if (Treeline.class == aclass) {
                final String a = name[0];
                name[0] = name[1];
                name[1] = a;
            }
            item = new JMenuItem("Duplicate " + name[0] + " as " + name[1]);
            item.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent e) {
                    Bureaucrat.createAndStart(new Worker.Task("Converting") {

                        @Override
                        public void exec() {
                            try {
                                getLayerSet().addChangeTreesStep();
                                final Map<Tree<?>, Tree<?>> m = Tree.duplicateAs(selection.getSelected(), (Class<Tree<?>>) (Treeline.class == aclass ? AreaTree.class : Treeline.class));
                                if (m.isEmpty()) {
                                    getLayerSet().removeLastUndoStep();
                                } else {
                                    getLayerSet().addChangeTreesStep();
                                }
                            } catch (final Exception e) {
                                IJError.print(e);
                            }
                        }
                    }, getProject());
                }
            });
            popup.add(item);
            popup.addSeparator();
        } else if (Connector.class == aclass) {
            item = new JMenuItem("Merge");
            item.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(final ActionEvent ae) {
                    if (null == getActive() || getActive().getClass() != Connector.class) {
                        Utils.log("Active object must be a Connector!");
                        return;
                    }
                    final List<Connector> col = selection.get(Connector.class);
                    if (col.size() < 2) {
                        Utils.log("Select more than one Connector!");
                        return;
                    }
                    if (col.get(0) != getActive()) {
                        if (col.remove(getActive())) {
                            col.add(0, (Connector) getActive());
                        } else {
                            Utils.log("ERROR: cannot find active object in selection list!");
                            return;
                        }
                    }
                    Bureaucrat.createAndStart(new Worker.Task("Merging connectors") {

                        @Override
                        public void exec() {
                            getLayerSet().addChangeTreesStep();
                            Connector base = null;
                            try {
                                base = Connector.merge(col);
                            } catch (final Exception e) {
                                IJError.print(e);
                            }
                            if (null == base) {
                                Utils.log("ERROR: could not merge connectors!");
                                getLayerSet().undoOneStep();
                            } else {
                                getLayerSet().addChangeTreesStep();
                            }
                            Display.repaint();
                        }
                    }, getProject());
                }
            });
            popup.add(item);
            item.setEnabled(selection.getSelected(Connector.class).size() > 1);
            popup.addSeparator();
        }
        item = new JMenuItem("Duplicate");
        item.addActionListener(this);
        popup.add(item);
        item = new JMenuItem("Color...");
        item.addActionListener(this);
        popup.add(item);
        if (active instanceof LayerSet)
            item.setEnabled(false);
        if (active.isLocked()) {
            item = new JMenuItem("Unlock");
            item.addActionListener(this);
            popup.add(item);
        } else {
            item = new JMenuItem("Lock");
            item.addActionListener(this);
            popup.add(item);
        }
        menu = new JMenu("Move");
        popup.addSeparator();
        final LayerSet ls = layer.getParent();
        item = new JMenuItem("Move to top");
        item.addActionListener(this);
        menu.add(item);
        // this is just to draw the key name by the menu; it does not incur on any event being generated (that I know if), and certainly not any event being listened to by TrakEM2.
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0, true));
        if (ls.isTop(active))
            item.setEnabled(false);
        item = new JMenuItem("Move up");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
        if (ls.isTop(active))
            item.setEnabled(false);
        item = new JMenuItem("Move down");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
        if (ls.isBottom(active))
            item.setEnabled(false);
        item = new JMenuItem("Move to bottom");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
        if (ls.isBottom(active))
            item.setEnabled(false);
        popup.add(menu);
        popup.addSeparator();
        item = new JMenuItem("Delete...");
        item.addActionListener(this);
        popup.add(item);
        try {
            if (Patch.class == aclass) {
                if (!active.isOnlyLinkedTo(Patch.class)) {
                    item.setEnabled(false);
                }
            }
        } catch (final Exception e) {
            IJError.print(e);
            item.setEnabled(false);
        }
        if (Patch.class == aclass) {
            item = new JMenuItem("Revert");
            item.addActionListener(this);
            popup.add(item);
            if (null == ((Patch) active).getOriginalPath())
                item.setEnabled(false);
            popup.addSeparator();
        }
        item = new JMenuItem("Properties...");
        item.addActionListener(this);
        popup.add(item);
        item = new JMenuItem("Show centered");
        item.addActionListener(this);
        popup.add(item);
        popup.addSeparator();
        if (!(active instanceof ZDisplayable)) {
            final int i_layer = layer.getParent().indexOf(layer);
            final int n_layers = layer.getParent().size();
            item = new JMenuItem("Send to previous layer");
            item.addActionListener(this);
            popup.add(item);
            if (1 == n_layers || 0 == i_layer || active.isLinked())
                item.setEnabled(false);
            else // check if the active is a profile and contains a link to another profile in the layer it is going to be sent to, or it is linked
            if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer)))
                item.setEnabled(false);
            item = new JMenuItem("Send to next layer");
            item.addActionListener(this);
            popup.add(item);
            if (1 == n_layers || n_layers - 1 == i_layer || active.isLinked())
                item.setEnabled(false);
            else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer)))
                item.setEnabled(false);
            menu = new JMenu("Send linked group to...");
            if (active.hasLinkedGroupWithinLayer(this.layer)) {
                int i = 1;
                for (final Layer la : ls.getLayers()) {
                    String layer_title = i + ": " + la.getTitle();
                    if (-1 == layer_title.indexOf(' '))
                        layer_title += " ";
                    item = new JMenuItem(layer_title);
                    item.addActionListener(this);
                    menu.add(item);
                    if (la == this.layer)
                        item.setEnabled(false);
                    i++;
                }
                popup.add(menu);
            } else {
                menu.setEnabled(false);
            // Utils.log("Active's linked group not within layer.");
            }
            popup.add(menu);
            popup.addSeparator();
        }
    }
    item = new JMenuItem("Undo");
    item.addActionListener(this);
    popup.add(item);
    if (!layer.getParent().canUndo() || canvas.isTransforming())
        item.setEnabled(false);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
    item = new JMenuItem("Redo");
    item.addActionListener(this);
    popup.add(item);
    if (!layer.getParent().canRedo() || canvas.isTransforming())
        item.setEnabled(false);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Utils.getControlModifier(), true));
    popup.addSeparator();
    try {
        menu = new JMenu("Hide/Unhide");
        item = new JMenuItem("Hide deselected");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
        boolean none = 0 == selection.getNSelected();
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Hide deselected except images");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK | Event.ALT_MASK, true));
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Hide selected");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().containsDisplayable(DLabel.class);
        item = new JMenuItem("Hide all labels");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all labels");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(AreaList.class);
        item = new JMenuItem("Hide all arealists");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all arealists");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.contains(Profile.class);
        item = new JMenuItem("Hide all profiles");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all profiles");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(Pipe.class);
        item = new JMenuItem("Hide all pipes");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all pipes");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(Polyline.class);
        item = new JMenuItem("Hide all polylines");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all polylines");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(Treeline.class);
        item = new JMenuItem("Hide all treelines");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all treelines");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(AreaTree.class);
        item = new JMenuItem("Hide all areatrees");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all areatrees");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(Ball.class);
        item = new JMenuItem("Hide all balls");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all balls");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().contains(Connector.class);
        item = new JMenuItem("Hide all connectors");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all connectors");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        none = !layer.getParent().containsDisplayable(Patch.class);
        item = new JMenuItem("Hide all images");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Unhide all images");
        item.addActionListener(this);
        menu.add(item);
        if (none)
            item.setEnabled(false);
        item = new JMenuItem("Hide all but images");
        item.addActionListener(this);
        menu.add(item);
        item = new JMenuItem("Unhide all");
        item.addActionListener(this);
        menu.add(item);
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
        popup.add(menu);
    } catch (final Exception e) {
        IJError.print(e);
    }
    // plugins, if any
    Utils.addPlugIns(popup, "Display", project, new Callable<Displayable>() {

        @Override
        public Displayable call() {
            return Display.this.getActive();
        }
    });
    final JMenu align_menu = new JMenu("Align");
    item = new JMenuItem("Align stack slices");
    item.addActionListener(this);
    align_menu.add(item);
    if (selection.isEmpty() || !(getActive().getClass() == Patch.class && ((Patch) getActive()).isStack()))
        item.setEnabled(false);
    item = new JMenuItem("Align layers");
    item.addActionListener(this);
    align_menu.add(item);
    if (1 == layer.getParent().size())
        item.setEnabled(false);
    item = new JMenuItem("Align layers manually with landmarks");
    item.addActionListener(this);
    align_menu.add(item);
    if (1 == layer.getParent().size())
        item.setEnabled(false);
    item = new JMenuItem("Align multi-layer mosaic");
    item.addActionListener(this);
    align_menu.add(item);
    if (1 == layer.getParent().size())
        item.setEnabled(false);
    item = new JMenuItem("Montage all images in this layer");
    item.addActionListener(this);
    align_menu.add(item);
    if (layer.getDisplayables(Patch.class).size() < 2)
        item.setEnabled(false);
    item = new JMenuItem("Montage selected images");
    item.addActionListener(this);
    align_menu.add(item);
    if (selection.getSelected(Patch.class).size() < 2)
        item.setEnabled(false);
    item = new JMenuItem("Montage multiple layers");
    item.addActionListener(this);
    align_menu.add(item);
    popup.add(align_menu);
    final JMenuItem st = new JMenu("Transform");
    final StartTransformMenuListener tml = new StartTransformMenuListener();
    item = new JMenuItem("Transform (affine)");
    item.addActionListener(tml);
    st.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Transform (non-linear)");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, Event.SHIFT_MASK, true));
    item = new JMenuItem("Cancel transform");
    st.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
    // just added as a self-documenting cue; no listener
    item.setEnabled(false);
    item = new JMenuItem("Remove rotation, scaling and shear (selected images)");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Remove rotation, scaling and shear layer-wise");
    item.addActionListener(tml);
    st.add(item);
    item = new JMenuItem("Remove coordinate transforms (selected images)");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Remove coordinate transforms layer-wise");
    item.addActionListener(tml);
    st.add(item);
    item = new JMenuItem("Adjust mesh resolution (selected images)");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Adjust mesh resolution layer-wise");
    item.addActionListener(tml);
    st.add(item);
    item = new JMenuItem("Set coordinate transform of selected image to other selected images");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Set coordinate transform of selected image layer-wise");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Set affine transform of selected image to other selected images");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    item = new JMenuItem("Set affine transform of selected image layer-wise");
    item.addActionListener(tml);
    st.add(item);
    if (null == active)
        item.setEnabled(false);
    popup.add(st);
    final JMenu link_menu = new JMenu("Link");
    item = new JMenuItem("Link images...");
    item.addActionListener(this);
    link_menu.add(item);
    item = new JMenuItem("Unlink all selected images");
    item.addActionListener(this);
    link_menu.add(item);
    item.setEnabled(selection.getSelected(Patch.class).size() > 0);
    item = new JMenuItem("Unlink all");
    item.addActionListener(this);
    link_menu.add(item);
    popup.add(link_menu);
    final JMenu adjust_menu = new JMenu("Adjust images");
    item = new JMenuItem("Enhance contrast layer-wise...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item = new JMenuItem("Enhance contrast (selected images)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Adjust image filters (selected images)");
    item.addActionListener(this);
    adjust_menu.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Set Min and Max layer-wise...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item = new JMenuItem("Set Min and Max (selected images)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Adjust min and max (selected images)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_J, 0));
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Mask image borders (layer-wise)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item = new JMenuItem("Mask image borders (selected images)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Remove alpha masks (layer-wise)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item = new JMenuItem("Remove alpha masks (selected images)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Split images under polyline ROI");
    item.addActionListener(this);
    adjust_menu.add(item);
    final Roi roi = canvas.getFakeImagePlus().getRoi();
    if (null == roi || !(roi.getType() == Roi.POLYLINE || roi.getType() == Roi.FREELINE))
        item.setEnabled(false);
    item = new JMenuItem("Blend (layer-wise)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item = new JMenuItem("Blend (selected images)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Match intensities (layer-wise)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    item = new JMenuItem("Remove intensity maps (layer-wise)...");
    item.addActionListener(this);
    adjust_menu.add(item);
    popup.add(adjust_menu);
    final JMenu script = new JMenu("Script");
    final MenuScriptListener msl = new MenuScriptListener();
    item = new JMenuItem("Set preprocessor script layer-wise...");
    item.addActionListener(msl);
    script.add(item);
    item = new JMenuItem("Set preprocessor script (selected images)...");
    item.addActionListener(msl);
    script.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    item = new JMenuItem("Remove preprocessor script layer-wise...");
    item.addActionListener(msl);
    script.add(item);
    item = new JMenuItem("Remove preprocessor script (selected images)...");
    item.addActionListener(msl);
    script.add(item);
    if (selection.isEmpty())
        item.setEnabled(false);
    popup.add(script);
    menu = new JMenu("Import");
    item = new JMenuItem("Import image");
    item.addActionListener(this);
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
    item = new JMenuItem("Import stack...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Import stack with landmarks...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Import grid...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Import sequence as grid...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Import from text file...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Import labels as arealists...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Tags ...");
    item.addActionListener(this);
    menu.add(item);
    popup.add(menu);
    menu = new JMenu("Export");
    final boolean has_arealists = layer.getParent().contains(AreaList.class);
    item = new JMenuItem("Make flat image...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Arealists as labels (tif)");
    item.addActionListener(this);
    menu.add(item);
    item.setEnabled(has_arealists);
    item = new JMenuItem("Arealists as labels (amira)");
    item.addActionListener(this);
    menu.add(item);
    item.setEnabled(has_arealists);
    item = new JMenuItem("Image stack under selected Arealist");
    item.addActionListener(this);
    menu.add(item);
    item.setEnabled(null != active && AreaList.class == active.getClass());
    item = new JMenuItem("Fly through selected Treeline/AreaTree");
    item.addActionListener(this);
    menu.add(item);
    item.setEnabled(null != active && Tree.class.isInstance(active));
    item = new JMenuItem("Tags...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Connectivity graph...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("NeuroML...");
    item.addActionListener(this);
    menu.add(item);
    popup.add(menu);
    menu = new JMenu("Display");
    item = new JMenuItem("Resize canvas/LayerSet...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Autoresize canvas/LayerSet");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Resize canvas/LayerSet to ROI");
    item.addActionListener(this);
    menu.add(item);
    item.setEnabled(null != canvas.getFakeImagePlus().getRoi());
    item = new JMenuItem("Properties ...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Calibration...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Grid overlay...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Adjust snapping parameters...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Adjust fast-marching parameters...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Adjust arealist paint parameters...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Show current 2D position in 3D");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Show layers as orthoslices in 3D");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Inspect image mesh triangles");
    item.addActionListener(this);
    menu.add(item);
    popup.add(menu);
    menu = new JMenu("Project");
    this.project.getLoader().setupMenuItems(menu, this.getProject());
    item = new JMenuItem("Project properties...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Create subproject");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Create sibling project with retiled layers");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Release memory...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Flush image cache");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Regenerate all mipmaps");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Regenerate mipmaps (selected images)");
    item.addActionListener(this);
    menu.add(item);
    menu.addSeparator();
    item = new JMenuItem("Measurement options...");
    item.addActionListener(this);
    menu.add(item);
    popup.add(menu);
    menu = new JMenu("Selection");
    item = new JMenuItem("Select all");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Select all visible");
    item.addActionListener(this);
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
    if (0 == layer.getDisplayableList().size() && 0 == layer.getParent().getDisplayableList().size())
        item.setEnabled(false);
    item = new JMenuItem("Select all that match...");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Select none");
    item.addActionListener(this);
    menu.add(item);
    if (0 == selection.getNSelected())
        item.setEnabled(false);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
    final JMenu bytype = new JMenu("Select all by type");
    item = new JMenuItem("AreaList");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("AreaTree");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Ball");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Connector");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Dissector");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Image");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Pipe");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Polyline");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Profile");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Text");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    item = new JMenuItem("Treeline");
    item.addActionListener(bytypelistener);
    bytype.add(item);
    menu.add(bytype);
    item = new JMenuItem("Restore selection");
    item.addActionListener(this);
    menu.add(item);
    item = new JMenuItem("Select under ROI");
    item.addActionListener(this);
    menu.add(item);
    if (canvas.getFakeImagePlus().getRoi() == null)
        item.setEnabled(false);
    final JMenu graph = new JMenu("Graph");
    final GraphMenuListener gl = new GraphMenuListener();
    item = new JMenuItem("Select outgoing Connectors");
    item.addActionListener(gl);
    graph.add(item);
    item = new JMenuItem("Select incoming Connectors");
    item.addActionListener(gl);
    graph.add(item);
    item = new JMenuItem("Select downstream targets");
    item.addActionListener(gl);
    graph.add(item);
    item = new JMenuItem("Select upstream targets");
    item.addActionListener(gl);
    graph.add(item);
    graph.setEnabled(!selection.isEmpty());
    menu.add(graph);
    item = new JMenuItem("Measure");
    item.addActionListener(this);
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0, true));
    item.setEnabled(!selection.isEmpty());
    popup.add(menu);
    menu = new JMenu("Tool");
    item = new JMenuItem("Rectangular ROI");
    item.addActionListener(new SetToolListener(Toolbar.RECTANGLE));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0, true));
    item = new JMenuItem("Polygon ROI");
    item.addActionListener(new SetToolListener(Toolbar.POLYGON));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0, true));
    item = new JMenuItem("Freehand ROI");
    item.addActionListener(new SetToolListener(Toolbar.FREEROI));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0, true));
    item = new JMenuItem("Text");
    item.addActionListener(new SetToolListener(Toolbar.TEXT));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0, true));
    item = new JMenuItem("Magnifier glass");
    item.addActionListener(new SetToolListener(Toolbar.MAGNIFIER));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0, true));
    item = new JMenuItem("Hand");
    item.addActionListener(new SetToolListener(Toolbar.HAND));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0, true));
    item = new JMenuItem("Select");
    item.addActionListener(new SetToolListener(ProjectToolbar.SELECT));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0, true));
    item = new JMenuItem("Pencil");
    item.addActionListener(new SetToolListener(ProjectToolbar.PENCIL));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0, true));
    item = new JMenuItem("Pen");
    item.addActionListener(new SetToolListener(ProjectToolbar.PEN));
    menu.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0, true));
    popup.add(menu);
    item = new JMenuItem("Search...");
    item.addActionListener(this);
    popup.add(item);
    item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Utils.getControlModifier(), true));
    // canvas.add(popup);
    return popup;
}
Also used : AlignTask(mpicbg.trakem2.align.AlignTask) AlignLayersTask(mpicbg.trakem2.align.AlignLayersTask) DistortionCorrectionTask(lenscorrection.DistortionCorrectionTask) ActionEvent(java.awt.event.ActionEvent) Bureaucrat(ini.trakem2.utils.Bureaucrat) ArrayList(java.util.ArrayList) List(java.util.List) CoordinateTransformList(mpicbg.trakem2.transform.CoordinateTransformList) HashSet(java.util.HashSet) Color(java.awt.Color) PolygonRoi(ij.gui.PolygonRoi) ShapeRoi(ij.gui.ShapeRoi) Roi(ij.gui.Roi) ActionListener(java.awt.event.ActionListener) DBObject(ini.trakem2.persistence.DBObject) InspectPatchTrianglesMode(ini.trakem2.display.inspect.InspectPatchTrianglesMode) GenericDialog(ij.gui.GenericDialog) Worker(ini.trakem2.utils.Worker) JMenuItem(javax.swing.JMenuItem) Point(java.awt.Point) TreeMap(java.util.TreeMap) JPopupMenu(javax.swing.JPopupMenu) Point(java.awt.Point) NoninvertibleTransformException(java.awt.geom.NoninvertibleTransformException) JMenu(javax.swing.JMenu)

Example 3 with Align

use of mpicbg.trakem2.align.Align 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 4 with Align

use of mpicbg.trakem2.align.Align in project TrakEM2 by trakem2.

the class AlignTask method alignMultiLayerMosaicTask.

@SuppressWarnings({ "unchecked", "rawtypes" })
public static final void alignMultiLayerMosaicTask(final List<Layer> layerRange, final Patch nail, final Align.Param cp, final Align.ParamOptimize p, final Align.ParamOptimize pcp, final boolean tilesAreInPlaceIn, final boolean largestGraphOnlyIn, final boolean hideDisconnectedTilesIn, final boolean deleteDisconnectedTilesIn, final boolean deformIn) {
    /* register */
    final List<AbstractAffineTile2D<?>> allTiles = new ArrayList<AbstractAffineTile2D<?>>();
    final List<AbstractAffineTile2D<?>> allFixedTiles = new ArrayList<AbstractAffineTile2D<?>>();
    final List<AbstractAffineTile2D<?>> previousLayerTiles = new ArrayList<AbstractAffineTile2D<?>>();
    final HashMap<Patch, PointMatch> tileCenterPoints = new HashMap<Patch, PointMatch>();
    final Collection<Patch> fixedPatches = new HashSet<Patch>();
    if (null != nail)
        fixedPatches.add(nail);
    Layer previousLayer = null;
    for (final Layer layer : layerRange) {
        /* align all tiles in the layer */
        final List<Patch> patches = new ArrayList<Patch>();
        for (// ignore hidden tiles
        final Displayable a : // ignore hidden tiles
        layer.getDisplayables(Patch.class, true)) if (a instanceof Patch)
            patches.add((Patch) a);
        final List<AbstractAffineTile2D<?>> currentLayerTiles = new ArrayList<AbstractAffineTile2D<?>>();
        final List<AbstractAffineTile2D<?>> fixedTiles = new ArrayList<AbstractAffineTile2D<?>>();
        Align.tilesFromPatches(p, patches, fixedPatches, currentLayerTiles, fixedTiles);
        // Will consider graphs and hide/delete tiles when all cross-layer graphs are found.
        alignTiles(p, currentLayerTiles, fixedTiles, tilesAreInPlaceIn, false, false, false);
        if (Thread.currentThread().isInterrupted())
            return;
        /* connect to the previous layer */
        /* generate tiles with the cross-section model from the current layer tiles */
        /* ------------------------------------------------------------------------ */
        /* TODO step back and make tiles bare containers for a patch and a model such that by changing the model the tile can be reused */
        final HashMap<Patch, AbstractAffineTile2D<?>> currentLayerPatchTiles = new HashMap<Patch, AbstractAffineTile2D<?>>();
        for (final AbstractAffineTile2D<?> t : currentLayerTiles) currentLayerPatchTiles.put(t.getPatch(), t);
        final List<AbstractAffineTile2D<?>> csCurrentLayerTiles = new ArrayList<AbstractAffineTile2D<?>>();
        final Set<AbstractAffineTile2D<?>> csFixedTiles = new HashSet<AbstractAffineTile2D<?>>();
        Align.tilesFromPatches(cp, patches, fixedPatches, csCurrentLayerTiles, csFixedTiles);
        final HashMap<Tile<?>, AbstractAffineTile2D<?>> tileTiles = new HashMap<Tile<?>, AbstractAffineTile2D<?>>();
        for (final AbstractAffineTile2D<?> t : csCurrentLayerTiles) tileTiles.put(currentLayerPatchTiles.get(t.getPatch()), t);
        for (final AbstractAffineTile2D<?> t : currentLayerTiles) {
            final AbstractAffineTile2D<?> csLayerTile = tileTiles.get(t);
            csLayerTile.addMatches(t.getMatches());
            for (final Tile<?> ct : t.getConnectedTiles()) csLayerTile.addConnectedTile(tileTiles.get(ct));
        }
        /* add a fixed tile only if there was a Patch selected */
        allFixedTiles.addAll(csFixedTiles);
        /* first, align connected graphs to each other */
        /* graphs in the current layer */
        final List<Set<Tile<?>>> currentLayerGraphs = AbstractAffineTile2D.identifyConnectedGraphs(csCurrentLayerTiles);
        if (Thread.currentThread().isInterrupted())
            return;
        // /* TODO just for visualization */
        // for ( final Set< Tile< ? > > graph : currentLayerGraphs )
        // {
        // Display.getFront().getSelection().clear();
        // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
        // 
        // for ( final Tile< ? > tile : graph )
        // {
        // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
        // Display.repaint();
        // }
        // Utils.showMessage( "OK" );
        // }
        /* graphs from the whole system that are present in the previous layer */
        final List<Set<Tile<?>>> graphs = AbstractAffineTile2D.identifyConnectedGraphs(allTiles);
        final HashMap<Set<Tile<?>>, Set<Tile<?>>> graphGraphs = new HashMap<Set<Tile<?>>, Set<Tile<?>>>();
        for (final Set<Tile<?>> graph : graphs) {
            if (Thread.currentThread().isInterrupted())
                return;
            final Set<Tile<?>> previousLayerGraph = new HashSet<Tile<?>>();
            for (final Tile<?> tile : previousLayerTiles) {
                if (graph.contains(tile)) {
                    graphGraphs.put(graph, previousLayerGraph);
                    previousLayerGraph.add(tile);
                }
            }
        }
        final Collection<Set<Tile<?>>> previousLayerGraphs = graphGraphs.values();
        // /* TODO just for visualization */
        // for ( final Set< Tile< ? > > graph : previousLayerGraphs )
        // {
        // Display.getFront().getSelection().clear();
        // Display.getFront().setLayer( ( ( AbstractAffineTile2D< ? > )graph.iterator().next() ).getPatch().getLayer() );
        // 
        // for ( final Tile< ? > tile : graph )
        // {
        // Display.getFront().getSelection().add( ( ( AbstractAffineTile2D< ? > )tile ).getPatch() );
        // Display.repaint();
        // }
        // Utils.showMessage( "OK" );
        // }
        /* generate snapshots of the graphs and preregister them using the parameters defined in cp */
        final List<AbstractAffineTile2D<?>[]> crossLayerTilePairs = new ArrayList<AbstractAffineTile2D<?>[]>();
        for (final Set<Tile<?>> currentLayerGraph : currentLayerGraphs) {
            for (final Set<Tile<?>> previousLayerGraph : previousLayerGraphs) {
                if (Thread.currentThread().isInterrupted())
                    return;
                alignGraphs(cp, layer, previousLayer, currentLayerGraph, previousLayerGraph);
                /* TODO this is pointless data shuffling just for type incompatibility---fix this at the root */
                final ArrayList<AbstractAffineTile2D<?>> previousLayerGraphTiles = new ArrayList<AbstractAffineTile2D<?>>();
                previousLayerGraphTiles.addAll((Set) previousLayerGraph);
                final ArrayList<AbstractAffineTile2D<?>> currentLayerGraphTiles = new ArrayList<AbstractAffineTile2D<?>>();
                currentLayerGraphTiles.addAll((Set) currentLayerGraph);
                AbstractAffineTile2D.pairOverlappingTiles(previousLayerGraphTiles, currentLayerGraphTiles, crossLayerTilePairs);
            }
        }
        /* ------------------------------------------------------------------------ */
        /* this is without the affine/rigid approximation per graph */
        // AbstractAffineTile2D.pairTiles( previousLayerTiles, csCurrentLayerTiles, crossLayerTilePairs );
        Align.connectTilePairs(cp, csCurrentLayerTiles, crossLayerTilePairs, Runtime.getRuntime().availableProcessors());
        if (Thread.currentThread().isInterrupted())
            return;
        // for ( final AbstractAffineTile2D< ? >[] tilePair : crossLayerTilePairs )
        // {
        // Display.getFront().setLayer( tilePair[ 0 ].getPatch().getLayer() );
        // Display.getFront().getSelection().clear();
        // Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
        // Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
        // 
        // Utils.showMessage( "1: OK?" );
        // 
        // Display.getFront().setLayer( tilePair[ 1 ].getPatch().getLayer() );
        // Display.getFront().getSelection().clear();
        // Display.getFront().getSelection().add( tilePair[ 0 ].getPatch() );
        // Display.getFront().getSelection().add( tilePair[ 1 ].getPatch() );
        // 
        // Utils.showMessage( "2: OK?" );
        // }
        /* prepare the next loop */
        allTiles.addAll(csCurrentLayerTiles);
        previousLayerTiles.clear();
        previousLayerTiles.addAll(csCurrentLayerTiles);
        /* optimize */
        Align.optimizeTileConfiguration(pcp, allTiles, allFixedTiles);
        if (Thread.currentThread().isInterrupted())
            return;
        for (final AbstractAffineTile2D<?> t : allTiles) t.getPatch().setAffineTransform(t.getModel().createAffine());
        previousLayer = layer;
    }
    final List<Set<Tile<?>>> graphs = AbstractAffineTile2D.identifyConnectedGraphs(allTiles);
    final List<AbstractAffineTile2D<?>> interestingTiles = new ArrayList<AbstractAffineTile2D<?>>();
    if (largestGraphOnlyIn && (hideDisconnectedTilesIn || deleteDisconnectedTilesIn)) {
        if (Thread.currentThread().isInterrupted())
            return;
        /* find largest graph. */
        Set<Tile<?>> largestGraph = null;
        for (final Set<Tile<?>> graph : graphs) if (largestGraph == null || largestGraph.size() < graph.size())
            largestGraph = graph;
        final Set<AbstractAffineTile2D<?>> tiles_to_keep = new HashSet<AbstractAffineTile2D<?>>();
        for (final Tile<?> t : largestGraph) tiles_to_keep.add((AbstractAffineTile2D<?>) t);
        if (hideDisconnectedTilesIn)
            for (final AbstractAffineTile2D<?> t : allTiles) if (!tiles_to_keep.contains(t))
                t.getPatch().setVisible(false);
        if (deleteDisconnectedTilesIn)
            for (final AbstractAffineTile2D<?> t : allTiles) if (!tiles_to_keep.contains(t))
                t.getPatch().remove(false);
        interestingTiles.addAll(tiles_to_keep);
    } else
        interestingTiles.addAll(allTiles);
    if (deformIn) {
        /* ############################################ */
        /* experimental: use the center points of all tiles to define a MLS deformation from the pure intra-layer registration to the globally optimal */
        Utils.log("deforming...");
        /* store the center location of each single tile for later deformation */
        for (final AbstractAffineTile2D<?> t : interestingTiles) {
            final double[] c = new double[] { t.getWidth() / 2.0, t.getHeight() / 2.0 };
            t.getModel().applyInPlace(c);
            final Point q = new Point(c);
            tileCenterPoints.put(t.getPatch(), new PointMatch(q.clone(), q));
        }
        for (final Layer layer : layerRange) {
            Utils.log("layer" + layer);
            if (Thread.currentThread().isInterrupted())
                return;
            /* again, align all tiles in the layer */
            final List<Patch> patches = new ArrayList<Patch>();
            for (final Displayable a : layer.getDisplayables(Patch.class)) if (a instanceof Patch)
                patches.add((Patch) a);
            final List<AbstractAffineTile2D<?>> currentLayerTiles = new ArrayList<AbstractAffineTile2D<?>>();
            final List<AbstractAffineTile2D<?>> fixedTiles = new ArrayList<AbstractAffineTile2D<?>>();
            Align.tilesFromPatches(p, patches, fixedPatches, currentLayerTiles, fixedTiles);
            /* add a fixed tile only if there was a Patch selected */
            allFixedTiles.addAll(fixedTiles);
            // will consider graphs and hide/delete tiles when all cross-layer graphs are found
            alignTiles(p, currentLayerTiles, fixedTiles, true, false, false, false);
            /* for each independent graph do an independent transform */
            final List<Set<Tile<?>>> currentLayerGraphs = AbstractAffineTile2D.identifyConnectedGraphs(currentLayerTiles);
            for (final Set<Tile<?>> graph : currentLayerGraphs) {
                /* update the tile-center pointmatches */
                final Collection<PointMatch> matches = new ArrayList<PointMatch>();
                final Collection<AbstractAffineTile2D<?>> toBeDeformedTiles = new ArrayList<AbstractAffineTile2D<?>>();
                for (final AbstractAffineTile2D<?> t : (Collection<AbstractAffineTile2D<?>>) (Collection) graph) {
                    final PointMatch pm = tileCenterPoints.get(t.getPatch());
                    if (pm == null)
                        continue;
                    final double[] pl = pm.getP1().getL();
                    pl[0] = t.getWidth() / 2.0;
                    pl[1] = t.getHeight() / 2.0;
                    t.getModel().applyInPlace(pl);
                    matches.add(pm);
                    toBeDeformedTiles.add(t);
                }
                for (final AbstractAffineTile2D<?> t : toBeDeformedTiles) {
                    if (Thread.currentThread().isInterrupted())
                        return;
                    try {
                        final Patch patch = t.getPatch();
                        final Rectangle pbox = patch.getCoordinateTransformBoundingBox();
                        final AffineTransform pat = new AffineTransform();
                        pat.translate(-pbox.x, -pbox.y);
                        pat.preConcatenate(patch.getAffineTransform());
                        final mpicbg.trakem2.transform.AffineModel2D toWorld = new mpicbg.trakem2.transform.AffineModel2D();
                        toWorld.set(pat);
                        final MovingLeastSquaresTransform2 mlst = Align.createMLST(matches, 1.0f);
                        final CoordinateTransformList<CoordinateTransform> ctl = new CoordinateTransformList<CoordinateTransform>();
                        ctl.add(toWorld);
                        ctl.add(mlst);
                        ctl.add(toWorld.createInverse());
                        patch.appendCoordinateTransform(ctl);
                        patch.getProject().getLoader().regenerateMipMaps(patch);
                    } catch (final Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    layerRange.get(0).getParent().setMinimumDimensions();
    IJ.log("Done: register multi-layer mosaic.");
    return;
}
Also used : Set(java.util.Set) HashSet(java.util.HashSet) LayerSet(ini.trakem2.display.LayerSet) HashMap(java.util.HashMap) CoordinateTransformList(mpicbg.trakem2.transform.CoordinateTransformList) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) AbstractAffineModel2D(mpicbg.models.AbstractAffineModel2D) AffineModel2D(mpicbg.models.AffineModel2D) HashSet(java.util.HashSet) Displayable(ini.trakem2.display.Displayable) Tile(mpicbg.models.Tile) Point(mpicbg.models.Point) Layer(ini.trakem2.display.Layer) NotEnoughDataPointsException(mpicbg.models.NotEnoughDataPointsException) NoninvertibleModelException(mpicbg.models.NoninvertibleModelException) NoninvertibleTransformException(java.awt.geom.NoninvertibleTransformException) PointMatch(mpicbg.models.PointMatch) MovingLeastSquaresTransform2(mpicbg.trakem2.transform.MovingLeastSquaresTransform2) Collection(java.util.Collection) AffineTransform(java.awt.geom.AffineTransform) Patch(ini.trakem2.display.Patch) CoordinateTransform(mpicbg.trakem2.transform.CoordinateTransform) InvertibleCoordinateTransform(mpicbg.trakem2.transform.InvertibleCoordinateTransform)

Example 5 with Align

use of mpicbg.trakem2.align.Align in project TrakEM2 by trakem2.

the class Align method alignLayersLinearly.

/**
 * Align a range of layers by accumulating pairwise alignments of contiguous layers.
 *
 * @param layers The range of layers to align pairwise.
 * @param numThreads The number of threads to use.
 * @param filter The {@link Filter} to decide which {@link Patch} instances to use in each {@link Layer}. Can be null.
 */
public static final void alignLayersLinearly(final List<Layer> layers, final int numThreads, final Filter<Patch> filter) {
    param.sift.maxOctaveSize = 1600;
    if (!param.setup("Align layers linearly"))
        return;
    final Rectangle box = layers.get(0).getParent().getMinimalBoundingBox(Patch.class);
    final double scale = Math.min(1.0, Math.min((double) param.sift.maxOctaveSize / box.width, (double) param.sift.maxOctaveSize / box.height));
    final Param p = param.clone();
    p.maxEpsilon *= scale;
    final FloatArray2DSIFT sift = new FloatArray2DSIFT(p.sift);
    final SIFT ijSIFT = new SIFT(sift);
    Rectangle box1 = null;
    Rectangle box2 = null;
    final Collection<Feature> features1 = new ArrayList<Feature>();
    final Collection<Feature> features2 = new ArrayList<Feature>();
    final List<PointMatch> candidates = new ArrayList<PointMatch>();
    final List<PointMatch> inliers = new ArrayList<PointMatch>();
    final AffineTransform a = new AffineTransform();
    int i = 0;
    for (final Layer l : layers) {
        long s = System.currentTimeMillis();
        features1.clear();
        features1.addAll(features2);
        features2.clear();
        final Rectangle box3 = l.getMinimalBoundingBox(Patch.class);
        if (box3 == null)
            continue;
        box1 = box2;
        box2 = box3;
        final List<Patch> patches = l.getAll(Patch.class);
        if (null != filter) {
            for (final Iterator<Patch> it = patches.iterator(); it.hasNext(); ) {
                if (!filter.accept(it.next()))
                    it.remove();
            }
        }
        ijSIFT.extractFeatures(l.getProject().getLoader().getFlatImage(l, box2, scale, 0xffffffff, ImagePlus.GRAY8, Patch.class, patches, true).getProcessor(), features2);
        Utils.log(features2.size() + " features extracted in layer \"" + l.getTitle() + "\" (took " + (System.currentTimeMillis() - s) + " ms).");
        if (features1.size() > 0) {
            s = System.currentTimeMillis();
            candidates.clear();
            FeatureTransform.matchFeatures(features2, features1, candidates, p.rod);
            final AbstractAffineModel2D<?> model;
            switch(p.expectedModelIndex) {
                case 0:
                    model = new TranslationModel2D();
                    break;
                case 1:
                    model = new RigidModel2D();
                    break;
                case 2:
                    model = new SimilarityModel2D();
                    break;
                case 3:
                    model = new AffineModel2D();
                    break;
                default:
                    return;
            }
            boolean modelFound;
            boolean again = false;
            try {
                do {
                    again = false;
                    modelFound = model.filterRansac(candidates, inliers, 1000, p.maxEpsilon, p.minInlierRatio, p.minNumInliers, 3);
                    if (modelFound && p.rejectIdentity) {
                        final ArrayList<Point> points = new ArrayList<Point>();
                        PointMatch.sourcePoints(inliers, points);
                        if (Transforms.isIdentity(model, points, p.identityTolerance)) {
                            Utils.log("Identity transform for " + inliers.size() + " matches rejected.");
                            candidates.removeAll(inliers);
                            inliers.clear();
                            again = true;
                        }
                    }
                } while (again);
            } catch (final NotEnoughDataPointsException e) {
                modelFound = false;
            }
            if (modelFound) {
                Utils.log("Model found for layer \"" + l.getTitle() + "\" and its predecessor:\n  correspondences  " + inliers.size() + " of " + candidates.size() + "\n  average residual error  " + (model.getCost() / scale) + " px\n  took " + (System.currentTimeMillis() - s) + " ms");
                final AffineTransform b = new AffineTransform();
                b.translate(box1.x, box1.y);
                b.scale(1.0f / scale, 1.0f / scale);
                b.concatenate(model.createAffine());
                b.scale(scale, scale);
                b.translate(-box2.x, -box2.y);
                a.concatenate(b);
                l.apply(Displayable.class, a);
                Display.repaint(l);
            } else {
                Utils.log("No model found for layer \"" + l.getTitle() + "\" and its predecessor:\n  correspondence candidates  " + candidates.size() + "\n  took " + (System.currentTimeMillis() - s) + " ms");
                a.setToIdentity();
            }
        }
        IJ.showProgress(++i, layers.size());
    }
}
Also used : NotEnoughDataPointsException(mpicbg.models.NotEnoughDataPointsException) SIFT(mpicbg.ij.SIFT) FloatArray2DSIFT(mpicbg.imagefeatures.FloatArray2DSIFT) Rectangle(java.awt.Rectangle) ArrayList(java.util.ArrayList) Feature(mpicbg.imagefeatures.Feature) RigidModel2D(mpicbg.trakem2.transform.RigidModel2D) AbstractAffineModel2D(mpicbg.models.AbstractAffineModel2D) AffineModel2D(mpicbg.models.AffineModel2D) InterpolatedAffineModel2D(mpicbg.models.InterpolatedAffineModel2D) SimilarityModel2D(mpicbg.models.SimilarityModel2D) Point(mpicbg.models.Point) Layer(ini.trakem2.display.Layer) Point(mpicbg.models.Point) FloatArray2DSIFT(mpicbg.imagefeatures.FloatArray2DSIFT) PointMatch(mpicbg.models.PointMatch) AffineTransform(java.awt.geom.AffineTransform) TranslationModel2D(mpicbg.trakem2.transform.TranslationModel2D) Patch(ini.trakem2.display.Patch)

Aggregations

ArrayList (java.util.ArrayList)6 Layer (ini.trakem2.display.Layer)4 Patch (ini.trakem2.display.Patch)4 Rectangle (java.awt.Rectangle)4 AffineTransform (java.awt.geom.AffineTransform)3 NoninvertibleTransformException (java.awt.geom.NoninvertibleTransformException)3 HashSet (java.util.HashSet)3 AbstractAffineModel2D (mpicbg.models.AbstractAffineModel2D)3 AffineModel2D (mpicbg.models.AffineModel2D)3 NotEnoughDataPointsException (mpicbg.models.NotEnoughDataPointsException)3 Point (mpicbg.models.Point)3 PointMatch (mpicbg.models.PointMatch)3 CoordinateTransformList (mpicbg.trakem2.transform.CoordinateTransformList)3 GenericDialog (ij.gui.GenericDialog)2 PolygonRoi (ij.gui.PolygonRoi)2 Roi (ij.gui.Roi)2 ShapeRoi (ij.gui.ShapeRoi)2 LayerSet (ini.trakem2.display.LayerSet)2 InspectPatchTrianglesMode (ini.trakem2.display.inspect.InspectPatchTrianglesMode)2 DBObject (ini.trakem2.persistence.DBObject)2