public DisplayPlus(
      final ImageCache stitchedCache,
      AcquisitionEngine eng,
      JSONObject summaryMD,
      boolean invX,
      boolean invY,
      boolean swapXY) {
    eng_ = eng;
    invertX_ = invX;
    invertY_ = invY;
    swapXY_ = swapXY;
    try {
      MultiStagePosition pos0 = MMStudioMainFrame.getInstance().getPositionList().getPosition(0);
      pos0x_ = pos0.getX();
      pos0y_ = pos0.getY();
      tileWidth_ = MDUtils.getHeight(summaryMD);
      tileHeight_ = MDUtils.getWidth(summaryMD);
    } catch (Exception e) {
      ReportingUtils.showError("Couldnt get grid info");
    }
    vad_ =
        new VirtualAcquisitionDisplay(stitchedCache, eng, WINDOW_TITLE) {
          public void showImage(final JSONObject tags, boolean waitForDisplay)
              throws InterruptedException, InvocationTargetException {
            // Since this is multichannel camera, only show when last channel arrives
            try {
              if (MDUtils.getChannelIndex(tags) == super.getNumChannels() - 1) {
                super.showImage(tags, waitForDisplay);
              } else {
                ImagePlus ip = super.getHyperImage();
                if (ip != null) {
                  // canvas never gets painted so need to set painpending false
                  ip.getCanvas().setPaintPending(false);
                }
              }
            } catch (JSONException ex) {
            }
          }
        };
    DisplayControls controls = new Controls();

    // Add in custom controls
    try {
      JavaUtils.setRestrictedFieldValue(
          vad_, VirtualAcquisitionDisplay.class, "controls_", controls);
    } catch (NoSuchFieldException ex) {
      ReportingUtils.showError("Couldn't create display controls");
    }
    vad_.show();
    // Zoom to 100%
    vad_.getImagePlus().getWindow().getCanvas().unzoom();

    // add mouse listeners for moving grids
    addMouseListeners();

    stitchedCache.addImageCacheListener(this);
  }
  private void addMouseListeners() {
    vad_.getImagePlus()
        .getCanvas()
        .addMouseMotionListener(
            new MouseMotionListener() {

              @Override
              public void mouseDragged(MouseEvent e) {
                Point finalPos = e.getPoint();
                ImageCanvas canvas = vad_.getImagePlus().getCanvas();
                int dx = (int) ((finalPos.x - clickStart_.x) / canvas.getMagnification());
                int dy = (int) ((finalPos.y - clickStart_.y) / canvas.getMagnification());
                vad_.getImagePlus()
                    .getOverlay()
                    .get(0)
                    .setLocation(gridStart_.x + dx, gridStart_.y + dy);
                if (!canvas.getPaintPending()) {
                  canvas.setPaintPending(true);
                  canvas.paint(canvas.getGraphics());
                }
              }

              @Override
              public void mouseMoved(MouseEvent e) {}
            });

    vad_.getImagePlus()
        .getCanvas()
        .addMouseListener(
            new MouseListener() {

              @Override
              public void mouseClicked(MouseEvent e) {}

              @Override
              public void mousePressed(MouseEvent e) {
                clickStart_ = e.getPoint();
                Roi rect = vad_.getImagePlus().getOverlay().get(0);
                Rectangle2D bounds = rect.getFloatBounds();
                gridStart_ = new Point((int) bounds.getX(), (int) bounds.getY());
                System.out.println("Pressed");
              }

              @Override
              public void mouseReleased(MouseEvent e) {}

              @Override
              public void mouseEntered(MouseEvent e) {}

              @Override
              public void mouseExited(MouseEvent e) {}
            });
  }
  private void makeGridOverlay(int centerX, int centerY) {
    IJ.setTool(Toolbar.SPARE2);
    Overlay overlay = vad_.getImagePlus().getOverlay();
    if (overlay == null || overlay.size() == 0) {
      overlay = new Overlay();
    } else {
      overlay.clear();
    }

    int gridWidth = (Integer) gridXSpinner_.getValue();
    int gridHeight = (Integer) gridYSpinner_.getValue();
    int roiWidth = gridWidth * tileWidth_;
    int roiHeight = gridHeight * tileHeight_;

    Roi rectangle = new Roi(centerX - roiWidth / 2, centerY - roiHeight / 2, roiWidth, roiHeight);
    rectangle.setStrokeWidth(20f);
    overlay.add(rectangle);
    vad_.getImagePlus().setOverlay(overlay);
  }
 private void gridSizeChanged() {
   // resize exisiting grid but keep centered on same area
   Overlay overlay = vad_.getImagePlus().getOverlay();
   if (overlay == null || overlay.get(0) == null) {
     return;
   }
   Rectangle2D oldBounds = overlay.get(0).getFloatBounds();
   int centerX = (int) oldBounds.getCenterX();
   int centerY = (int) oldBounds.getCenterY();
   makeGridOverlay(centerX, centerY);
 }
  @Override
  public void imageReceived(TaggedImage taggedImage) {
    try {
      // duplicate so image storage doesnt see incorrect tags
      JSONObject newTags = new JSONObject(taggedImage.tags.toString());
      MDUtils.setPositionIndex(newTags, 0);
      taggedImage = new TaggedImage(taggedImage.pix, newTags);
    } catch (JSONException ex) {
      ReportingUtils.showError("Couldn't manipulate image tags for display");
    }

    vad_.imageReceived(taggedImage);
  }
 @Override
 public void imagingFinished(String path) {
   vad_.imagingFinished(path);
 }