org.micromanager.plugins.magellan.imagedisplay.DisplayOverlayer.java Source code

Java tutorial

Introduction

Here is the source code for org.micromanager.plugins.magellan.imagedisplay.DisplayOverlayer.java

Source

///////////////////////////////////////////////////////////////////////////////
// AUTHOR:       Henry Pinkard, henry.pinkard@gmail.com
//
// COPYRIGHT:    University of California, San Francisco, 2015
//
// LICENSE:      This file is distributed under the BSD license.
//               License text is included with the source distribution.
//
//               This file is distributed in the hope that it will be useful,
//               but WITHOUT ANY WARRANTY; without even the implied warranty
//               of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
//               IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//               CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//               INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES.
//
package org.micromanager.plugins.magellan.imagedisplay;

import org.micromanager.plugins.magellan.acq.Acquisition;
import org.micromanager.plugins.magellan.acq.ExploreAcquisition;
import org.micromanager.plugins.magellan.acq.FixedAreaAcquisition;
import org.micromanager.plugins.magellan.coordinates.XYStagePosition;
import ij.IJ;
import ij.gui.ImageCanvas;
import ij.gui.Line;
import ij.gui.OvalRoi;
import ij.gui.Overlay;
import ij.gui.Roi;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import javax.swing.SwingUtilities;
import org.micromanager.plugins.magellan.misc.Log;
import org.micromanager.plugins.magellan.misc.LongPoint;
import org.micromanager.plugins.magellan.mmcloneclasses.graph.ContrastPanel;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.micromanager.plugins.magellan.surfacesandregions.SingleResolutionInterpolation;
import org.micromanager.plugins.magellan.surfacesandregions.MultiPosRegion;
import org.micromanager.plugins.magellan.surfacesandregions.Point3d;
import org.micromanager.plugins.magellan.surfacesandregions.SurfaceInterpolator;

/**
 * Class that encapsulates calculation of overlays for DisplayPlus
 */
public class DisplayOverlayer {

    private final static int INTERP_POINT_DIAMETER = 12;
    private final static int INITIAL_NUM_INTERPOLATION_DIVISIONS = 10;
    private final static Color INTERP_POINT_COLOR = Color.GREEN;
    private final static Color CONVEX_HULL_COLOR = Color.GREEN;
    private static final Color NEW_GRID_COLOR = Color.red;
    private static final int[] ICE_RED = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 7, 9, 11, 14, 16, 19, 20, 21, 22, 24, 25, 26,
            27, 29, 31, 34, 36, 39, 42, 44, 47, 50, 49, 49, 49, 49, 48, 48, 48, 48, 51, 55, 59, 63, 67, 71, 75, 79,
            83, 87, 91, 95, 99, 103, 107, 112, 114, 117, 120, 123, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152,
            155, 158, 161, 165, 168, 172, 175, 179, 182, 186, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207,
            209, 211, 213, 215, 217, 218, 220, 221, 223, 224, 226, 227, 229, 230, 232, 233, 235, 237, 238, 240, 242,
            243, 244, 245, 246, 247, 248, 249, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250,
            250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 251, 250, 250,
            250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250,
            250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 251, 251, 251, 251, 251,
            251, 251, 251, 251, 250, 249, 248, 247, 246, 245, 244, 243, 241, 239, 238, 236, 234, 233, 231, 230, 230,
            230, 230, 230, 230, 230, 230 };
    private static final int[] ICE_GREEN = { 156, 157, 158, 159, 160, 161, 162, 163, 165, 166, 167, 169, 170, 171,
            173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 184, 185, 186, 187, 187, 188, 189, 190, 190, 191,
            192, 193, 193, 194, 195, 196, 195, 195, 194, 194, 194, 193, 193, 193, 191, 190, 189, 188, 187, 186, 185,
            184, 182, 180, 179, 177, 175, 174, 172, 171, 169, 168, 167, 166, 165, 164, 163, 162, 160, 158, 156, 154,
            152, 150, 148, 146, 143, 140, 138, 135, 132, 130, 127, 125, 122, 120, 118, 116, 113, 111, 109, 107, 105,
            103, 101, 100, 98, 96, 94, 93, 91, 90, 88, 87, 85, 84, 82, 81, 81, 82, 83, 84, 84, 85, 86, 87, 87, 88,
            88, 89, 90, 90, 91, 92, 92, 93, 93, 94, 95, 95, 96, 97, 96, 96, 96, 96, 95, 95, 95, 95, 94, 94, 94, 94,
            93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 92, 92, 91, 91, 91, 90, 90, 90, 89, 88, 88, 87, 86, 86,
            85, 85, 83, 81, 79, 77, 75, 73, 71, 69, 68, 67, 67, 66, 65, 65, 64, 64, 62, 61, 60, 59, 57, 56, 55, 54,
            53, 52, 51, 50, 49, 48, 47, 47, 45, 44, 42, 41, 39, 38, 36, 35, 33, 31, 29, 27, 25, 23, 21, 19, 16, 14,
            11, 9, 7, 4, 2, 0, 0, 1, 1, 2, 2, 3, 3, 4, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    private static final int[] ICE_BLUE = { 140, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 151, 152, 153,
            155, 156, 158, 159, 160, 161, 162, 163, 164, 165, 166, 166, 167, 167, 168, 168, 169, 169, 170, 170, 171,
            172, 173, 173, 174, 175, 176, 180, 184, 188, 192, 196, 200, 204, 209, 210, 211, 213, 214, 215, 217, 218,
            220, 221, 223, 225, 227, 228, 230, 232, 234, 232, 231, 230, 229, 228, 227, 226, 225, 226, 227, 229, 230,
            231, 233, 234, 236, 237, 238, 239, 241, 242, 243, 244, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250,
            250, 250, 250, 250, 250, 250, 251, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250,
            250, 250, 249, 248, 248, 247, 246, 246, 245, 245, 243, 241, 239, 237, 235, 233, 231, 230, 230, 230, 230,
            230, 230, 230, 230, 230, 229, 228, 227, 226, 225, 224, 223, 222, 219, 217, 214, 212, 209, 207, 204, 202,
            199, 196, 193, 191, 188, 185, 182, 180, 177, 175, 173, 171, 169, 167, 165, 163, 160, 157, 155, 152, 149,
            147, 144, 142, 139, 137, 134, 132, 130, 127, 125, 123, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112,
            111, 110, 109, 108, 107, 106, 104, 103, 101, 100, 98, 97, 95, 94, 92, 91, 90, 89, 87, 86, 85, 84, 81,
            79, 76, 74, 71, 69, 66, 64, 59, 54, 49, 45, 40, 35, 30, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27,
            27, 27, 27, 27 };
    private static final int[] VIRIDIS_RED = { 68, 68, 68, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 71,
            72, 72, 72, 72, 72, 72, 72, 72, 72, 71, 71, 71, 71, 71, 71, 71, 70, 70, 70, 70, 69, 69, 69, 69, 68, 68,
            67, 67, 67, 66, 66, 66, 65, 65, 64, 64, 63, 63, 62, 62, 61, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56,
            56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 49, 48, 48, 47, 47, 46, 46, 46, 45, 45, 44,
            44, 44, 43, 43, 42, 42, 42, 41, 41, 40, 40, 40, 39, 39, 39, 38, 38, 38, 37, 37, 36, 36, 36, 35, 35, 35,
            34, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 31,
            31, 31, 32, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 50, 51, 53, 54, 56,
            57, 59, 61, 62, 64, 66, 68, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 94, 96, 98, 100, 103, 105,
            107, 109, 112, 114, 116, 119, 121, 124, 126, 129, 131, 134, 136, 139, 141, 144, 146, 149, 151, 154, 157,
            159, 162, 165, 167, 170, 173, 175, 178, 181, 183, 186, 189, 191, 194, 197, 199, 202, 205, 207, 210, 212,
            215, 218, 220, 223, 225, 228, 231, 233, 236, 238, 241, 243, 246, 248, 250, 253 };
    private static final int[] VIRIDIS_GREEN = { 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, 22, 24, 25,
            26, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 42, 43, 44, 45, 47, 48, 49, 50, 52, 53, 54, 55, 57, 58,
            59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 86, 87,
            88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
            111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 122, 123, 124, 125, 126, 127, 128, 129, 130,
            131, 132, 133, 134, 135, 136, 137, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150,
            151, 152, 153, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 167, 168, 169,
            170, 171, 172, 173, 174, 175, 176, 177, 177, 178, 179, 180, 181, 182, 183, 184, 185, 185, 186, 187, 188,
            189, 190, 190, 191, 192, 193, 194, 194, 195, 196, 197, 198, 198, 199, 200, 201, 201, 202, 203, 204, 204,
            205, 206, 206, 207, 208, 208, 209, 210, 210, 211, 211, 212, 213, 213, 214, 214, 215, 215, 216, 216, 217,
            217, 218, 218, 219, 219, 220, 220, 221, 221, 221, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225,
            226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231 };
    private static final int[] VIRIDIS_BLUE = { 84, 85, 87, 88, 90, 91, 92, 94, 95, 97, 98, 99, 101, 102, 103, 105,
            106, 107, 108, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 124, 125, 126,
            127, 127, 128, 129, 129, 130, 131, 131, 132, 132, 133, 133, 134, 134, 135, 135, 135, 136, 136, 137, 137,
            137, 137, 138, 138, 138, 138, 139, 139, 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141,
            141, 141, 141, 141, 141, 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
            142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 141, 141, 141, 141, 141, 141, 141,
            141, 141, 141, 141, 140, 140, 140, 140, 140, 140, 139, 139, 139, 139, 138, 138, 138, 138, 137, 137, 137,
            136, 136, 136, 135, 135, 134, 134, 133, 133, 133, 132, 132, 131, 130, 130, 129, 129, 128, 127, 127, 126,
            125, 125, 124, 123, 122, 122, 121, 120, 119, 118, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108,
            107, 105, 104, 103, 102, 101, 100, 98, 97, 96, 95, 93, 92, 91, 89, 88, 86, 85, 84, 82, 81, 79, 78, 76,
            75, 73, 71, 70, 68, 67, 65, 63, 62, 60, 58, 56, 55, 53, 51, 50, 48, 46, 44, 43, 41, 39, 38, 36, 34, 33,
            31, 30, 29, 28, 27, 26, 25, 24, 24, 24, 24, 24, 25, 25, 26, 27, 28, 30, 31, 33, 34, 36 };
    private static final Color TRANSPARENT_BLUE = new Color(0, 0, 255, 100);
    private static final Color TRANSPARENT_GREEN = new Color(0, 255, 0, 100);
    private static final Color TRANSPARENT_MAGENTA = new Color(255, 0, 255, 100);
    private final DisplayPlus display_;
    private Acquisition acq_;
    private volatile boolean showSurface_ = true, showConvexHull_ = true, showStagePositionsBelow_ = true,
            showStagePositionsAbove_ = false;
    private ZoomableVirtualStack zoomableStack_;
    private ImageCanvas canvas_;
    private final int tileWidth_, tileHeight_;
    private ExecutorService taskExecutor_, overlayMakerExecutor_;
    private Future currentTask_;

    public DisplayOverlayer(DisplayPlus display, Acquisition acq, int tileWidth, int tileHeight,
            ZoomableVirtualStack stack) {
        display_ = display;
        tileWidth_ = tileWidth;
        tileHeight_ = tileHeight;
        acq_ = acq;
        canvas_ = display.getImagePlus().getCanvas();
        zoomableStack_ = stack;
        createExecutors();
    }

    private void createExecutors() {
        taskExecutor_ = Executors.newSingleThreadExecutor(new ThreadFactory() {

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, display_.getTitle() + " overalyer task thread");
            }
        });
        overlayMakerExecutor_ = Executors.newSingleThreadExecutor(new ThreadFactory() {

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, display_.getTitle() + " overaly maker thread");
            }
        });
    }

    public void setSurfaceDisplayParams(boolean convexHull, boolean stagePosAbove, boolean stagePosBelow,
            boolean surf) {
        showConvexHull_ = convexHull;
        showStagePositionsAbove_ = stagePosAbove;
        showStagePositionsBelow_ = stagePosBelow;
        showSurface_ = surf;
    }

    public void setStack(ZoomableVirtualStack stack) {
        zoomableStack_ = stack;
    }

    public void shutdown() {
        taskExecutor_.shutdownNow();
        overlayMakerExecutor_.shutdownNow();
    }

    //always try to cancel the previous task, assuming it is being replaced with a more current one
    public synchronized void redrawOverlay() {
        if (currentTask_ != null && !currentTask_.isDone()) {
            //cancel current surface calculation--this call does not block until complete
            currentTask_.cancel(true);
        }
        currentTask_ = taskExecutor_.submit(new Runnable() {

            @Override
            public void run() {
                createAndRenderOverlay();
            }
        });
    }

    /**
     * Calculate the surface on a different thread, and block until it returns an
     * overlay, then add the rendering of that overlay back onto EDT.
     *
     * needs to support interrupts when new rendering instructions come from GUI
     *
     * @return
     */
    private void createAndRenderOverlay() {
        //submit tasks for rendering the overlay at multiple levels of detail, drawing each as it becomes available
        final Future<Overlay> baseOverlayCreation = overlayMakerExecutor_.submit(createBaseOverlay());
        try {
            //block until overlay creation finished
            final Overlay baseOverlay = baseOverlayCreation.get();
            //now that drawing finished, update canvas with most current overlay
            //Computing overlays is going to be the limiting step in this process,
            //because the setOverlay method just sets a reference and calls repaint    
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    canvas_.setOverlay(baseOverlay);
                }
            });
            if (display_.getMode() == DisplayPlus.SURFACE) {
                //now finished base overlay, move on to more detailed surface renderings 
                //first draw convex hull
                if (showConvexHull_) {
                    final Overlay overlay = createBackgroundOverlay();
                    addInterpPoints(display_.getCurrentSurface(), overlay);
                    addConvexHull(overlay);
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            canvas_.setOverlay(overlay);
                        }
                    });
                }
                //then draw convex hull + stage positions
                if (showStagePositionsAbove_ || showStagePositionsBelow_) {
                    final Overlay overlay = createBackgroundOverlay();
                    addInterpPoints(display_.getCurrentSurface(), overlay);
                    if (showConvexHull_) {
                        addConvexHull(overlay);
                    }
                    addStagePositions(overlay, showStagePositionsAbove_);
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            canvas_.setOverlay(overlay);
                        }
                    });
                }

                //finally, draw surface at increasing high resolutions, blocking if interpolation hasn't progressed far enough to
                //supply the desired resolution yet
                if (showSurface_ && display_.getCurrentSurface() != null) {
                    renderSurfaceOverlay();
                }
            }
        } catch (InterruptedException ex) {
            //interrupted because overlay is out of date
            return;
        } catch (ExecutionException ex) {
            //sholdn't ever happen because createBaseOverlay throw exceptions
            Log.log("Exception when creating base overlay", true);
            Log.log(ex);
            ex.printStackTrace();
        }
    }

    /**
     * Draw an initial version of the overlay that can be calculated quickly
     * subsequent calls will draw more detailed surface overlay renderings
     *
     * @return
     */
    private Callable<Overlay> createBaseOverlay() {
        return new Callable<Overlay>() {

            @Override
            public Overlay call() throws InterruptedException {
                try {
                    //determine appropriate overlay
                    int mode = display_.getMode();
                    if (mode == DisplayPlus.EXPLORE) {
                        Overlay overlay = createBackgroundOverlay();
                        if (display_.getExploreEndTile() != null) {
                            //draw explore tiles waiting to be confirmed with a click
                            highlightTilesOnOverlay(overlay,
                                    Math.min(display_.getExploreEndTile().y, display_.getExploreStartTile().y),
                                    Math.max(display_.getExploreEndTile().y, display_.getExploreStartTile().y),
                                    Math.min(display_.getExploreEndTile().x, display_.getExploreStartTile().x),
                                    Math.max(display_.getExploreEndTile().x, display_.getExploreStartTile().x),
                                    TRANSPARENT_MAGENTA);
                        } else if (display_.getMouseDragStartPointLeft() != null) {
                            //highlight multiple tiles when mouse dragging    
                            Point mouseLoc = display_.getCurrentMouseLocation();
                            Point dragStart = display_.getMouseDragStartPointLeft();
                            Point p2Tiles = zoomableStack_.getTileIndicesFromDisplayedPixel(mouseLoc.x, mouseLoc.y),
                                    p1Tiles = zoomableStack_.getTileIndicesFromDisplayedPixel(dragStart.x,
                                            dragStart.y);
                            highlightTilesOnOverlay(overlay, Math.min(p1Tiles.y, p2Tiles.y),
                                    Math.max(p1Tiles.y, p2Tiles.y), Math.min(p1Tiles.x, p2Tiles.x),
                                    Math.max(p1Tiles.x, p2Tiles.x), TRANSPARENT_BLUE);
                        } else if (display_.getCurrentMouseLocation() != null) {
                            //draw single highlighted tile under mouse
                            Point coords = zoomableStack_.getTileIndicesFromDisplayedPixel(
                                    display_.getCurrentMouseLocation().x, display_.getCurrentMouseLocation().y);
                            highlightTilesOnOverlay(overlay, coords.y, coords.y, coords.x, coords.x,
                                    TRANSPARENT_BLUE); //highligth single tile

                        }
                        try {
                            if (acq_ instanceof ExploreAcquisition) {
                                //always draw tiles waiting to be acquired
                                LinkedBlockingQueue<ExploreAcquisition.ExploreTileWaitingToAcquire> tiles = ((ExploreAcquisition) acq_)
                                        .getTilesWaitingToAcquireAtSlice(display_.getVisibleSliceIndex()
                                                + ((ExploreAcquisition) acq_).getMinSliceIndex());
                                if (tiles != null) {
                                    for (ExploreAcquisition.ExploreTileWaitingToAcquire t : tiles) {
                                        highlightTilesOnOverlay(overlay, t.row, t.row, t.col, t.col,
                                                TRANSPARENT_GREEN);
                                    }
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                        return overlay;
                    } else if (mode == DisplayPlus.NEWGRID) {
                        return newGridOverlay();
                    } else if (mode == DisplayPlus.NONE) {
                        return createBackgroundOverlay();
                    } else if (mode == DisplayPlus.SURFACE) {
                        //Do only fast version of surface overlay rendering, which don't require 
                        //any progress in the interpolation
                        Overlay overlay = createBackgroundOverlay();
                        addInterpPoints(display_.getCurrentSurface(), overlay);
                        return overlay;
                    } else {
                        Log.log("Unkonwn display mode", true);
                        throw new RuntimeException();
                    }
                } catch (NullPointerException npe) {
                    Log.log("Null pointer exception while creating overlay. written to stack ", false);
                    npe.printStackTrace();
                    return null;
                }
            }
        };
    }

    /**
     * creates background overlay that other modes should build upon Includes
     * scale bar and zoom indicator
     */
    private Overlay createBackgroundOverlay() {
        Overlay overlay = new Overlay();
        ContrastPanel cp = ((DisplayWindow) display_.getHyperImage().getWindow()).getContrastPanel();
        boolean showScaleBar = cp.showScaleBar();
        Color color = cp.getScaleBarColor();

        if (showScaleBar) {
            MMScaleBar sizeBar = new MMScaleBar(display_.getHyperImage(), color);
            sizeBar.setPosition(cp.getScaleBarPosition());
            sizeBar.addToOverlay(overlay);
        }

        if (display_.getAcquisition() instanceof FixedAreaAcquisition || display_.getAcquisition() == null) {
            drawZoomIndicator(overlay);
        }
        return overlay;
    }

    private void addInterpPoints(SurfaceInterpolator newSurface, Overlay overlay) {
        if (newSurface == null) {
            return;
        }
        for (Point3d point : newSurface.getPoints()) {
            LongPoint displayLocation = display_.imageCoordsFromStageCoords(point.x, point.y);
            int displaySlice;
            displaySlice = zoomableStack_.getSliceIndexFromZCoordinate(point.z);

            if (displaySlice != display_.getVisibleSliceIndex()) {
                continue;
            }

            Roi circle = new OvalRoi(displayLocation.x_ - INTERP_POINT_DIAMETER / 2,
                    displayLocation.y_ - INTERP_POINT_DIAMETER / 2, INTERP_POINT_DIAMETER, INTERP_POINT_DIAMETER);
            circle.setFillColor(INTERP_POINT_COLOR);
            circle.setStrokeColor(INTERP_POINT_COLOR);
            overlay.add(circle);
        }
    }

    private void addConvexHull(Overlay overlay) throws InterruptedException {
        //draw convex hull
        Vector2D[] hullPoints = display_.getCurrentSurface().getConvexHullPoints();

        LongPoint lastPoint = null, firstPoint = null;
        for (Vector2D v : hullPoints) {
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            //convert to image coords
            LongPoint p = display_.imageCoordsFromStageCoords(v.getX(), v.getY());
            if (lastPoint != null) {
                Line l = new Line(p.x_, p.y_, lastPoint.x_, lastPoint.y_);
                l.setStrokeColor(CONVEX_HULL_COLOR);
                overlay.add(l);
            } else {
                firstPoint = p;
            }
            lastPoint = p;
        }
        //draw last connection         
        Line l = new Line(firstPoint.x_, firstPoint.y_, lastPoint.x_, lastPoint.y_);
        l.setStrokeColor(CONVEX_HULL_COLOR);
        overlay.add(l);
    }

    private void addStagePositions(Overlay overlay, boolean above) throws InterruptedException {
        double zPosition = zoomableStack_.getZCoordinateOfDisplayedSlice(display_.getVisibleSliceIndex());

        //this will block until interpolation detailed enough to show stage positions
        ArrayList<XYStagePosition> positionsAtSlice = display_.getCurrentSurface().getXYPositonsAtSlice(zPosition,
                above);
        for (XYStagePosition pos : positionsAtSlice) {
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            Point2D.Double[] corners = pos.getDisplayedTileCorners();
            LongPoint corner1 = display_.imageCoordsFromStageCoords(corners[0].x, corners[0].y);
            LongPoint corner2 = display_.imageCoordsFromStageCoords(corners[1].x, corners[1].y);
            LongPoint corner3 = display_.imageCoordsFromStageCoords(corners[2].x, corners[2].y);
            LongPoint corner4 = display_.imageCoordsFromStageCoords(corners[3].x, corners[3].y);
            //add lines connecting 4 corners
            Line l1 = new Line(corner1.x_, corner1.y_, corner2.x_, corner2.y_);
            Line l2 = new Line(corner2.x_, corner2.y_, corner3.x_, corner3.y_);
            Line l3 = new Line(corner3.x_, corner3.y_, corner4.x_, corner4.y_);
            Line l4 = new Line(corner4.x_, corner4.y_, corner1.x_, corner1.y_);
            l1.setStrokeColor(Color.red);
            l2.setStrokeColor(Color.red);
            l3.setStrokeColor(Color.red);
            l4.setStrokeColor(Color.red);
            overlay.add(l1);
            overlay.add(l2);
            overlay.add(l3);
            overlay.add(l4);
        }
    }

    private void renderSurfaceOverlay() throws InterruptedException {
        //start out with 10 interpolation points across the whole image 
        int displayPixPerInterpPoint = Math.max(display_.getImagePlus().getWidth(),
                display_.getImagePlus().getHeight()) / INITIAL_NUM_INTERPOLATION_DIVISIONS;
        //keep redrawing until surface full interpolated  
        final Overlay startingOverlay = createBackgroundOverlay();
        addInterpPoints(display_.getCurrentSurface(), startingOverlay);
        if (showConvexHull_) {
            addConvexHull(startingOverlay);
        }

        while (true) {
            final Overlay surfOverlay = new Overlay();
            //add all objects from starting overlay rather than recalculating them each time
            for (int i = 0; i < startingOverlay.size(); i++) {
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                surfOverlay.add(startingOverlay.get(i));
            }
            SingleResolutionInterpolation interp = display_.getCurrentSurface().waitForCurentInterpolation();
            //wait until surface is interpolated at sufficent resolution to draw
            while (displayPixPerInterpPoint * zoomableStack_.getDownsampleFactor() < interp
                    .getPixelsPerInterpPoint()) {
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                display_.getCurrentSurface().waitForHigherResolutionInterpolation();
                interp = display_.getCurrentSurface().waitForCurentInterpolation();
            }
            if (showStagePositionsAbove_ || showStagePositionsBelow_) {
                //these could concieveably change as function of interpolation detail
                addStagePositions(surfOverlay, showStagePositionsAbove_);
            }
            //add surface interpolation
            addSurfaceInterpolation(surfOverlay, interp, displayPixPerInterpPoint);
            if (Thread.interrupted()) {
                throw new InterruptedException();
            }
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    canvas_.setOverlay(surfOverlay);
                }
            });

            if (displayPixPerInterpPoint == 1 || displayPixPerInterpPoint
                    * zoomableStack_.getDownsampleFactor() <= SurfaceInterpolator.MIN_PIXELS_PER_INTERP_POINT) {
                //finished  
                return;
            }
            displayPixPerInterpPoint /= 2;
        }

    }

    //draw the surface itself by interpolating a grid over viewable area
    private void addSurfaceInterpolation(Overlay overlay, SingleResolutionInterpolation interp,
            int displayPixPerInterpTile) throws InterruptedException {
        int width = display_.getImagePlus().getWidth();
        int height = display_.getImagePlus().getHeight();
        ZoomableVirtualStack zStack = (ZoomableVirtualStack) display_.virtualStack_;
        double sliceZ = zStack.getZCoordinateOfDisplayedSlice(display_.getVisibleSliceIndex());
        double zStep = acq_.getZStep();

        //Make numTestPoints a factor of image size for clean display of surface
        int numTestPointsX = width / displayPixPerInterpTile;
        int numTestPointsY = height / displayPixPerInterpTile;
        double roiWidth = width / (double) numTestPointsX;
        double roiHeight = height / (double) numTestPointsY;

        for (int x = 0; x < numTestPointsX; x++) {
            for (int y = 0; y < numTestPointsY; y++) {
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                Point2D.Double stageCoord = display_.stageCoordFromImageCoords((int) ((x + 0.5) * roiWidth),
                        (int) ((y + 0.5) * roiHeight));
                if (!interp.isInterpDefined(stageCoord.x, stageCoord.y)) {
                    continue;
                }
                float interpZ = interp.getInterpolatedValue(stageCoord.x, stageCoord.y);

                if (Math.abs(sliceZ - interpZ) < zStep / 2) {
                    double roiX = roiWidth * x;
                    double roiY = roiHeight * y;
                    //calculate distance from last ROI for uninterrupted coverage of image
                    int displayWidth = (int) (roiWidth * (x + 1)) - (int) (roiX);
                    int displayHeight = (int) (roiHeight * (y + 1)) - (int) (roiY);
                    Roi rect = new Roi(roiX, roiY, displayWidth, displayHeight); //make ROI
                    int[] lutRed = VIRIDIS_RED;
                    int[] lutBlue = VIRIDIS_BLUE;
                    int[] lutGreen = VIRIDIS_GREEN;
                    double colorScale = ((sliceZ - interpZ) / (zStep / 2) + 1) / 2; //between 0 and 1
                    rect.setFillColor(new Color(lutRed[(int) (colorScale * lutRed.length)],
                            lutGreen[(int) (colorScale * lutGreen.length)],
                            lutBlue[(int) (colorScale * lutBlue.length)], 175));
                    overlay.add(rect);
                }
            }
        }
    }

    private Overlay newGridOverlay() {
        Overlay overlay = createBackgroundOverlay();
        double dsTileWidth, dsTileHeight;
        dsTileWidth = tileWidth_ / (double) zoomableStack_.getDownsampleFactor();
        dsTileHeight = tileHeight_ / (double) zoomableStack_.getDownsampleFactor();
        MultiPosRegion newGrid = display_.getCurrentRegion();
        if (newGrid == null) {
            return overlay;
        }
        int roiWidth = (int) ((newGrid.numCols() * dsTileWidth));
        int roiHeight = (int) ((newGrid.numRows() * dsTileHeight));
        LongPoint displayCenter = display_.imageCoordsFromStageCoords(newGrid.center().x, newGrid.center().y);
        Roi rectangle = new Roi(displayCenter.x_ - roiWidth / 2, displayCenter.y_ - roiHeight / 2, roiWidth,
                roiHeight);
        rectangle.setStrokeWidth(5f);
        rectangle.setStrokeColor(NEW_GRID_COLOR);

        Point displayTopLeft = new Point((int) (displayCenter.x_ - roiWidth / 2),
                (int) (displayCenter.y_ - roiHeight / 2));
        //draw boundries of tiles
        for (int row = 1; row < newGrid.numRows(); row++) {
            int yPos = (int) (displayTopLeft.y + row * dsTileHeight);
            Line l = new Line(displayTopLeft.x, yPos, displayTopLeft.x + roiWidth, yPos);
            l.setStrokeColor(NEW_GRID_COLOR);
            overlay.add(l);
        }
        for (int col = 1; col < newGrid.numCols(); col++) {
            int xPos = (int) (displayTopLeft.x + col * dsTileWidth);
            Line l = new Line(xPos, displayTopLeft.y, xPos, displayTopLeft.y + roiHeight);
            l.setStrokeColor(NEW_GRID_COLOR);
            overlay.add(l);
        }

        overlay.add(rectangle);
        return overlay;
    }

    private void highlightTilesOnOverlay(Overlay base, long row1, long row2, long col1, long col2, Color color) {
        LongPoint topLeft = zoomableStack_.getDisplayedPixel(row1, col1);
        int width = (int) (Math.round(tileWidth_ / (double) zoomableStack_.getDownsampleFactor() * (col2 + 1))
                - Math.round(tileWidth_ / (double) zoomableStack_.getDownsampleFactor() * (col1)));
        int height = (int) (Math.round(tileHeight_ / (double) zoomableStack_.getDownsampleFactor() * (row2 + 1))
                - Math.round(tileHeight_ / (double) zoomableStack_.getDownsampleFactor() * (row1)));
        Roi rect = new Roi(topLeft.x_, topLeft.y_, width, height);
        rect.setFillColor(color);
        base.add(rect);
    }

    private void drawZoomIndicator(Overlay overlay) {
        LongPoint zoomPos = zoomableStack_.getZoomLocation();
        int outerWidth = 100;
        long fullResHeight = display_.getStorage().getNumRows() * display_.getStorage().getTileHeight();
        long fullResWidth = display_.getStorage().getNumCols() * display_.getStorage().getTileWidth();
        int dsFactor = display_.getZoomableStack().getDownsampleFactor();
        Dimension displayImageSize = display_.getZoomableStack().getDisplayImageSize();
        int outerHeight = (int) ((double) fullResHeight / (double) fullResWidth * outerWidth);
        //draw outer rectangle representing full image
        Roi outerRect = new Roi(10, 10, outerWidth, outerHeight);
        outerRect.setStrokeColor(new Color(255, 0, 255)); //magenta
        int innerX = (int) Math.round(((double) outerWidth / (double) fullResWidth)
                * (zoomPos.x_ * dsFactor - zoomableStack_.getTopLeftPixel().x_));
        int innerY = (int) Math.round(((double) outerHeight / (double) fullResHeight)
                * (zoomPos.y_ * dsFactor - zoomableStack_.getTopLeftPixel().y_));
        //outer width * percentage of width of full images that is shown
        int innerWidth = (int) (outerWidth * ((double) displayImageSize.width / (fullResWidth / dsFactor)));
        int innerHeight = (int) (outerHeight * ((double) displayImageSize.height / (fullResHeight / dsFactor)));
        Roi innerRect = new Roi(10 + innerX, 10 + innerY, innerWidth, innerHeight);
        innerRect.setStrokeColor(new Color(255, 0, 255));
        if (outerWidth != innerWidth || outerHeight != innerHeight) { //dont draw if fully zoomed out
            overlay.add(outerRect);
            overlay.add(innerRect);
        }
        canvas_.setOverlay(overlay);
    }
}