/*========================================================================= Program: FusionViewer Module: $RCSfile: ImageSliceView.java,v $ Language: Java Date: $Date: 2008/01/11 21:37:29 $ Version: $Revision: 1.9 $ Copyright (c) Insightful Corporation. All rights reserved. See Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ package org.fusionviewer.display; import java.awt.Cursor; import java.awt.Point; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.LinkedList; import javax.swing.JOptionPane; import org.fusionviewer.display.composite.AlphaBlendCompositor; import org.fusionviewer.display.composite.CheckerboardCompositor; import org.fusionviewer.display.composite.SplitWindowCompositor; import org.fusionviewer.model.FusionDisplayModel; import org.fusionviewer.model.ImageDisplayModel; import org.fusionviewer.model.ImageDisplayModelListener; import org.fusionviewer.ui.CursorControl; import org.fusionviewer.ui.FloatFormatter; import javax.media.opengl.GL; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCanvas; import javax.media.opengl.GLEventListener; import com.sun.opengl.util.GLUT; /**\class ImageSliceView *\brief Displays image(s) in a GLCanvas using ImageTextureRenderer and AlphaCompositor. */ public class ImageSliceView implements GLEventListener, ImageDisplayModelListener, MouseListener, MouseMotionListener { private ImageDisplayModel m_viewModel; // image display parameters private float[] m_pixelSpacing = new float[3]; // space between pixel centers in mm private int m_viewPortWidth, m_viewPortHeight; // dimensions of the area where the slice should be drawn private Point m_viewPortOrigin = new Point(); // x, y coordinates of the lower left corner of the // slice drawing area in GLCanvas coordinates private float m_aspectRatio; // ratio of image height to width private int m_xOrigin, m_yOrigin; // only non-zero when zoomed in--coordinates of the // panned image origin private GLCanvas m_parent; // the canvas to draw into private SliceConfiguration m_configuration; // axes identifiersof a 3D image used in extracting slices private int m_xAxis, m_yAxis; // x, y axis under current configuration private LinkedList m_renderers = new LinkedList(); // renderers used to draw the images private LinkedList m_images = new LinkedList(); //integer array representing the image private int m_firstImageId, m_refImageId; // the first and reference image id in image arrary of viewModel private LinkedList m_pixelInfoListeners = new LinkedList(); private MouseTool m_currentTool; // active mouse tool private int m_reloadSlices; // set of image slices to reload on the next redraw private boolean m_initialReshape = false; private boolean m_copyRenderFlag = false; private GLUT m_glut = new GLUT(); private LinkedList m_lineProfiles = new LinkedList(); private static final int NUM_PIXEL_COMPONENTS = 4; // number of components in a pixel (RGBA) private float[] m_offset = new float[2]; // Mouse handlers private NavigatorMouseTool m_navigatorTool; private WindowLevelMouseTool m_windowLevelTool; private SplitWindowMouseTool m_splitWindowTool; private ZoomMouseTool m_zoomTool; private PanMouseTool m_panTool; private RectangleROIMouseTool m_rectROITool; // Temporary variable used for updating the pixel position display. This array might need to // be populated several times a second, so we allocate it once here. private float[] m_positionTemp = new float[3]; // Compositing modes private AlphaBlendCompositor m_alphaCompositor = new AlphaBlendCompositor(); private CheckerboardCompositor m_checkerboardCompositor = new CheckerboardCompositor(); private SplitWindowCompositor m_splitWindowCompositor = new SplitWindowCompositor(this); /** * Create an image view * * @param parent GLCanvas this view can draw in * @param configuration configuration of the 2D slice to display */ public ImageSliceView(GLCanvas parent, SliceConfiguration configuration) { m_parent = parent; m_configuration = configuration; parent.addMouseListener(this); parent.addMouseMotionListener(this); // Instantiate tools m_navigatorTool = new NavigatorMouseTool(this); m_windowLevelTool = new WindowLevelMouseTool(this); m_zoomTool = new ZoomMouseTool(this); m_panTool = new PanMouseTool(this); m_rectROITool = new RectangleROIMouseTool(this); } /** * Replace this view's data model * * @param model view model to use */ public void setModel(ImageDisplayModel model) { if (m_viewModel != null) m_viewModel.removeListener(this); m_viewModel = model; m_refImageId = model.getReferenceImgId(); if (model != null) { model.addListener(this); if (model instanceof FusionDisplayModel) m_splitWindowTool = new SplitWindowMouseTool(this, m_splitWindowCompositor); else m_splitWindowTool = null; } } /** * Add an image index from the ImageDisplayModel to the list of displayed images * * @param id image to add */ public void addImage(int id) { if (m_viewModel == null) throw new IllegalStateException("View model must be set before adding images."); ImageTextureRenderer renderer = new ImageTextureRenderer(this); m_renderers.addFirst(renderer); m_images.addFirst(new Integer(id)); m_firstImageId = id; Image image = m_viewModel.getImage(id); renderer.setImage(m_configuration, image, m_viewModel); m_xAxis = m_configuration.getAxisIndex(0); m_yAxis = m_configuration.getAxisIndex(1); m_xOrigin = m_yOrigin = 0; m_reloadSlices = ImageDisplayModel.ALL_IMAGES; int numDiagonalPixels = (int) Math.sqrt(image.getDimension(m_yAxis) * image.getDimension(m_yAxis) + (image.getDimension(m_xAxis) * image.getDimension(m_xAxis))); m_lineProfiles.addFirst(new LineProfileMeasurement(numDiagonalPixels)); } /** * Dispose all image renderers and remove event listeners from the parent GLCanvas */ public void release() { Iterator it = m_renderers.iterator(); while (it.hasNext()) ((ImageTextureRenderer) it.next()).release(); m_parent.removeMouseListener(this); m_parent.removeMouseMotionListener(this); } /** * Called by GLCanvas when the GL context is intialized. Setup context parameters. */ public void init(GLAutoDrawable drawable) { GL gl = drawable.getGL(); m_parent.setEnabled(true); m_parent.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); gl.glShadeModel(GL.GL_FLAT); // no shading gl.glDisable(GL.GL_DEPTH_TEST); gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); } /** * Called by GLCanvas when this view should draw. */ public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); try { int viewPortOriginX = (int) m_viewPortOrigin.getX(); int viewPortOriginY = (int) m_viewPortOrigin.getY(); if (m_renderers.size() == 0) { gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.glClear(GL.GL_COLOR_BUFFER_BIT); } else { drawBorders(drawable); // Mask the back layer to the view port rectangle so that rounding errors // in the view plane size will not show through the edges of the front layer gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f); gl.glTranslated(0.375, 0.375, 0.0); gl.glRecti(viewPortOriginX, viewPortOriginY, viewPortOriginX + m_viewPortWidth, viewPortOriginY + m_viewPortHeight); gl.glTranslated(-0.375, -0.375, 0.0); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_DST_ALPHA, GL.GL_ONE_MINUS_DST_ALPHA); // Draw back image Iterator it = m_renderers.iterator(); Iterator imageIt = m_images.iterator(); int imageId = ((Integer) imageIt.next()).intValue(); ImageTextureRenderer renderer = (ImageTextureRenderer) it.next(); float[] offset = {0,0}; if (m_images.size() == 2 && imageId != m_refImageId){ setOffset(imageId); offset = getOffset(); } renderer.drawImage(drawable, viewPortOriginX + (int)offset[0], viewPortOriginY + (int)offset[1], imageId, m_viewModel.setIncludesImage(imageId, m_reloadSlices)); gl.glDisable(GL.GL_BLEND); if (it.hasNext()) { gl.glColorMask(false, false, false, true); // Replace the alpha layer in the back buffer FusionDisplayModel model = (FusionDisplayModel) m_viewModel; switch (model.getFusionMode()) { case FusionDisplayModel.ALPHABLEND_MODE: m_alphaCompositor.draw(drawable, model, viewPortOriginX, viewPortOriginY, m_viewPortWidth, m_viewPortHeight); break; case FusionDisplayModel.CHECKERBOARD_MODE: m_checkerboardCompositor.draw(drawable, model, viewPortOriginX, viewPortOriginY, m_viewPortWidth, m_viewPortHeight); break; case FusionDisplayModel.SPLITWINDOW_MODE: m_splitWindowCompositor.draw(drawable, model, viewPortOriginX, viewPortOriginY, m_viewPortWidth, m_viewPortHeight); break; } gl.glColorMask(true, true, true, true); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_DST_ALPHA, GL.GL_ONE_MINUS_DST_ALPHA); // Draw front image renderer = (ImageTextureRenderer) it.next(); imageId = ((Integer) imageIt.next()).intValue(); offset[0] = 0; offset[1] = 0; if (imageId != m_refImageId){ setOffset(imageId); offset = getOffset(); } renderer.drawImage(drawable, viewPortOriginX + (int)offset[0], viewPortOriginY + (int)offset[1], imageId, m_viewModel.setIncludesImage(imageId, m_reloadSlices)); gl.glDisable(GL.GL_BLEND); } m_reloadSlices = 0; drawCrosshairs(drawable, renderer, imageId); if (m_currentTool == m_rectROITool) { if (m_viewModel.getMouseMode() == ImageDisplayModel.MOUSE_MODE_MEASURE_LINE) { gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_DST_ALPHA, GL.GL_ONE_MINUS_DST_ALPHA); drawLineMeasurement(drawable); gl.glDisable(GL.GL_BLEND); } else { drawRectangleROI(drawable); } } } gl.glFlush(); // Copy image to the clipboard if (m_copyRenderFlag) { m_copyRenderFlag = false; drawable.swapBuffers(); // swap back buffer to front drawable.display(); // draw the identical image into the back buffer // Invert the display as feedback to the user invertViewport(drawable); m_parent.swapBuffers(); long startTime = System.currentTimeMillis(); // Invert the image back to its original state. This should not be necessary since // an identical non-inverted copy should be in the back buffer. However, the back // buffer is read as inverted to the clipboard and the display remains inverted if // we remove the next line. This is possibly a JOGL bug. invertViewport(drawable); gl.glReadBuffer(GL.GL_BACK); copyToClipboard(drawable); // Make sure the user sees the screen flash final int delayTime = 250; long time = System.currentTimeMillis() - startTime; if (time < delayTime) { try { Thread.sleep(delayTime - time); } catch (InterruptedException e1) { // Continue if interrupted } } } } catch (OutOfMemoryError e) { gl.glClear(GL.GL_COLOR_BUFFER_BIT); m_renderers.clear(); m_images.clear(); JOptionPane.showMessageDialog(getParent(), "Out of memory.", "Error", JOptionPane.ERROR_MESSAGE); } } /** * Draw a line and measure it. */ private void drawLineMeasurement(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glPushMatrix(); // Add 0.375 to ensure primitives are rendered at predictable pixel locations // See the OpenGL Programming Guide section on OpenGL Correctness gl.glTranslated(m_viewPortOrigin.getX() + 0.375, m_viewPortOrigin.getY() + 0.375, 0.0); Point anchor1 = m_rectROITool.getAnchor1(); Point anchor2 = m_rectROITool.getAnchor2(); // Draw text gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f); float dx = (anchor2.x - anchor1.x) * m_pixelSpacing[0]; float dy = (anchor2.y - anchor1.y) * m_pixelSpacing[1]; gl.glRasterPos2f(10, 15); m_glut.glutBitmapString(GLUT.BITMAP_HELVETICA_10, "Length: " + FloatFormatter.formatFloat(Math.sqrt(dx * dx + dy * dy)) + " mm"); gl.glBegin(GL.GL_LINES); // Draw the line gl.glVertex2f(anchor1.x, m_viewPortHeight - anchor1.y - 1); gl.glVertex2f(anchor2.x, m_viewPortHeight - anchor2.y - 1); gl.glEnd(); drawLineValues(drawable); gl.glPopMatrix(); } /** * Draw graphs of pixel values along a line. */ private void drawLineValues(GLAutoDrawable drawable) { Point anchor1 = m_rectROITool.getAnchor1(); Point anchor2 = m_rectROITool.getAnchor2(); // Get pixel values along the line anchor1.y = m_viewPortHeight - anchor1.y - 1 + m_yOrigin; anchor2.y = m_viewPortHeight - anchor2.y - 1 + m_yOrigin; if (!m_configuration.getAxisFlipped(1)) { anchor1.y = getScaledHeight() - anchor1.y - 1; anchor2.y = getScaledHeight() - anchor2.y - 1; } anchor1.x += m_xOrigin; anchor2.x += m_xOrigin; float x1 = Math.min(anchor1.x, anchor2.x) * m_pixelSpacing[0]; float y1 = Math.min(anchor1.y, anchor2.y) * m_pixelSpacing[1]; float x2 = Math.max(anchor1.x, anchor2.x) * m_pixelSpacing[0]; float y2 = Math.max(anchor1.y, anchor2.y) * m_pixelSpacing[1]; int xAxis = m_configuration.getAxisIndex(0); int yAxis = m_configuration.getAxisIndex(1); int zAxis = m_configuration.getAxisIndex(2); int[] index1 = new int[3]; int[] index2 = new int[3]; String[] imageLabels = { "Background", "Foreground" }; StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); for (int i = 0; i < m_images.size(); i++) { Image img = m_viewModel.getImage(((Integer) m_images.get(i)).intValue()); if (img == null) continue; LineProfileMeasurement measurement = (LineProfileMeasurement) m_lineProfiles.get(i); index1[xAxis] = (int) (x1 / img.getPixelSpacing(xAxis) + 0.5f); index1[yAxis] = (int) (y1 / img.getPixelSpacing(yAxis) + 0.5f); index1[zAxis] = (int) (m_viewModel.getPosition(i, zAxis) / img.getPixelSpacing(zAxis)); index2[xAxis] = (int) (x2 / img.getPixelSpacing(xAxis) + 0.5f); index2[yAxis] = (int) (y2 / img.getPixelSpacing(yAxis) + 0.5f); index2[zAxis] = index1[zAxis]; img.measureLineProfile(index1[0], index1[1], index1[2], index2[0], index2[1], index2[2], measurement); drawLineProfile(drawable, measurement, i); if (m_images.size() > 1) pw.println(imageLabels[i]); pw.print("Start: "); pw.println(index1[0] + " " + index1[1] + " " + index1[2]); pw.print("End: "); pw.println(index2[0] + " " + index2[1] + " " + index2[2]); pw.println(); measurement.print(pw); pw.println(); } StringSelection str = new StringSelection(sw.toString()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(str, null); } /** * Draw the graph for a line profile measurement. */ private void drawLineProfile(GLAutoDrawable drawable, LineProfileMeasurement measurement, int index) { if (measurement.numValues < 2) return; GL gl = drawable.getGL(); final double GRAPH_WIDTH = 100.0; final double GRAPH_HEIGHT = 60.0; final double PADDING = 2.0; double startX = m_viewPortWidth - GRAPH_WIDTH - PADDING; double startY; if (m_images.size() > 1) startY = PADDING + (1 - index) * (GRAPH_HEIGHT + PADDING); else startY = PADDING; // Find range of values in the data double maxValue = measurement.values[0]; double minValue = measurement.values[0]; for (int i = 0; i < measurement.numValues; i++) { double val = measurement.values[i]; if (val < minValue) minValue = val; if (val > maxValue) maxValue = val; } if (minValue > 0) minValue = 0; if (maxValue < 0) maxValue = 0; double xScale = GRAPH_WIDTH / (double) measurement.numValues; double yScale = GRAPH_HEIGHT / (maxValue - minValue); if (minValue < 0) minValue *= -1.0; // Draw transparent background gl.glColor4f(0.4f, 0.4f, 0.4f, 0.4f); gl.glRectd(startX, startY, startX + GRAPH_WIDTH + 1, startY + GRAPH_HEIGHT + 1); gl.glBegin(GL.GL_LINES); // Draw y = 0 gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); gl.glVertex2d(startX, yScale * minValue + startY); gl.glVertex2d(startX + GRAPH_WIDTH, yScale * minValue + startY); // Draw x = 0 gl.glVertex2d(startX, startY); gl.glVertex2d(startX, startY + GRAPH_HEIGHT); // Plot the pixel values gl.glColor4f(0.0f, 1.0f, 0.0f, 1.0f); for (int i = 1; i < measurement.numValues; i++) { gl.glVertex2d(startX + ((double) (i - 1)) * xScale, startY + (measurement.values[i - 1] + minValue) * yScale); gl.glVertex2d(startX + ((double) i) * xScale, startY + (measurement.values[i] + minValue) * yScale); } gl.glEnd(); // Label the figure if (m_images.size() > 1) { String[] imageLabels = { "BG", "FG" }; gl.glRasterPos2d(startX - 16, startY + PADDING); m_glut.glutBitmapString(GLUT.BITMAP_HELVETICA_10, imageLabels[index]); } } /** * Draw rectangle ROI. */ private void drawRectangleROI(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glPushMatrix(); // Add 0.375 to ensure primitives are rendered at predictable pixel locations // See the OpenGL Programming Guide section on OpenGL Correctness gl.glTranslated(m_viewPortOrigin.getX() + 0.375, m_viewPortOrigin.getY() + 0.375, 0.0); Point anchor1 = m_rectROITool.getAnchor1(); Point anchor2 = m_rectROITool.getAnchor2(); float left = Math.min(anchor1.x, anchor2.x); float top = Math.min(anchor1.y, anchor2.y); float right = Math.max(anchor1.x, anchor2.x); float bottom = Math.max(anchor1.y, anchor2.y); bottom = m_viewPortHeight - bottom - 1; top = m_viewPortHeight - top - 1; drawRectangleROIMeasurement(drawable); gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f); gl.glLineStipple(4, (short) 0x4AAAA); gl.glEnable(GL.GL_LINE_STIPPLE); gl.glLogicOp(GL.GL_INVERT); gl.glEnable(GL.GL_COLOR_LOGIC_OP); gl.glBegin(GL.GL_LINE_LOOP); gl.glVertex2f(left, bottom); gl.glVertex2f(right, bottom); gl.glVertex2f(right, top); gl.glVertex2f(left, top); gl.glEnd(); gl.glLogicOp(GL.GL_COPY); gl.glDisable(GL.GL_COLOR_LOGIC_OP); gl.glDisable(GL.GL_LINE_STIPPLE); gl.glPopMatrix(); } /** * Rectangle ROI measurement. */ private void drawRectangleROIMeasurement(GLAutoDrawable drawable) { GL gl = drawable.getGL(); Point anchor1 = m_rectROITool.getAnchor1(); Point anchor2 = m_rectROITool.getAnchor2(); anchor1.y = m_viewPortHeight - anchor1.y - 1 + m_yOrigin; anchor2.y = m_viewPortHeight - anchor2.y - 1 + m_yOrigin; if (!m_configuration.getAxisFlipped(1)) { anchor1.y = getScaledHeight() - anchor1.y - 1; anchor2.y = getScaledHeight() - anchor2.y - 1; } anchor1.x += m_xOrigin; anchor2.x += m_xOrigin; float x1 = Math.min(anchor1.x, anchor2.x); float y1 = Math.min(anchor1.y, anchor2.y); float x2 = Math.max(anchor1.x, anchor2.x); float y2 = Math.max(anchor1.y, anchor2.y); int xAxis = m_configuration.getAxisIndex(0); int yAxis = m_configuration.getAxisIndex(1); int zAxis = m_configuration.getAxisIndex(2); // Get statistics for the ROI float x = x1 * m_pixelSpacing[0]; float y = y1 * m_pixelSpacing[1]; float width = (x2 - x1) * m_pixelSpacing[0]; float height = (y2 - y1) * m_pixelSpacing[1]; ROIMeasurement measurement = new ROIMeasurement(); gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f); for (int i = 0; i < m_images.size(); i++) { Image img = m_viewModel.getImage(((Integer) m_images.get(i)).intValue()); if (img == null) continue; int imgWidth = (int) (width / img.getPixelSpacing(xAxis) + 0.5f); int imgHeight = (int) (height / img.getPixelSpacing(yAxis) + 0.5f); if ((imgWidth != 0) && (imgHeight != 0)) { int z = (int) (m_viewModel.getPosition(i, zAxis) / img.getPixelSpacing(zAxis)); img.measureROI((int) (x / img.getPixelSpacing(xAxis) + 0.5f), (int) (y / img.getPixelSpacing(yAxis) + 0.5f), imgWidth, imgHeight, xAxis, yAxis, z, measurement); gl.glRasterPos2f(10, 10 + (m_viewModel.getNumImages() - i - 1) * 15); m_glut.glutBitmapString(GLUT.BITMAP_HELVETICA_10, "Mean: " + FloatFormatter.formatFloat(measurement.mean) + " " + "Std Dev: " + FloatFormatter.formatFloat(measurement.standardDeviation) + " " + "#Samples: " + measurement.numPixels); } } } /** * Draw crosshairs on the image to show the current position. */ private void drawCrosshairs(GLAutoDrawable drawable, ImageTextureRenderer renderer, int id) { if (!m_viewModel.getCrosshairsVisible()) return; GL gl = drawable.getGL(); gl.glPushMatrix(); // Add 0.375 to ensure primitives are rendered at predictable pixel locations // See the OpenGL Programming Guide section on OpenGL Correctness gl.glTranslated(m_viewPortOrigin.getX() + 0.375, m_viewPortOrigin.getY() + 0.375, 0.0); // We manually scale instead of using OpenGL due to some accuracy problems. // With certain scales, the crosshairs do not draw directly underneath the cursor. // gl.glScaled(1.0 / m_pixelSpacing[0], 1.0 / m_pixelSpacing[1], 1.0f); float[] offset = {0,0}; if ( m_images.size() == 1 && (id != m_refImageId)){ offset = getOffset(); } double xCross = m_viewModel.getPosition(0, m_configuration.getAxisIndex(0)) / m_pixelSpacing[0] - m_xOrigin - ((double) offset[0]); double yCross; if (!m_configuration.getAxisFlipped(1)) { yCross = (getScaledHeight() - (m_viewModel.getPosition(0, m_configuration.getAxisIndex(1)) / m_pixelSpacing[1]) -1) - m_yOrigin + ((double)offset[1]); } else { yCross = m_viewModel.getPosition(0, m_configuration.getAxisIndex(1)) / m_pixelSpacing[1] - m_yOrigin - ((double)offset[1]); } final double CROSSHAIR_GAP = 8.0; gl.glBegin(GL.GL_LINES); gl.glColor4f(0.0f, 1.0f, 0.0f, 1.0f); // Draw horizontal line if (xCross > CROSSHAIR_GAP) { gl.glVertex2d(0.0, yCross); gl.glVertex2d(xCross - CROSSHAIR_GAP, yCross); } if (xCross + CROSSHAIR_GAP < m_viewPortWidth) { gl.glVertex2d(xCross + CROSSHAIR_GAP, yCross); gl.glVertex2d(m_viewPortWidth, yCross); } // Draw vertical line if (yCross > CROSSHAIR_GAP) { gl.glVertex2d(xCross, 0.0); gl.glVertex2d(xCross, yCross - CROSSHAIR_GAP); } if (yCross + CROSSHAIR_GAP < m_viewPortHeight) { gl.glVertex2d(xCross, yCross + CROSSHAIR_GAP); gl.glVertex2d(xCross, m_viewPortHeight); } gl.glEnd(); gl.glPopMatrix(); } /* * Draw borders around the image area */ private void drawBorders(GLAutoDrawable drawable) { GL gl = drawable.getGL(); // Translate to 0.375 to ensure primitives are rendered at predictable pixel locations // See the OpenGL Programming Guide section on OpenGL Correctness gl.glTranslated(0.375, 0.375, 0.0); gl.glColor4f(0.0f, 0.0f, 0.0f, 0.0f); int originX = (int) m_viewPortOrigin.getX(); int originY = (int) m_viewPortOrigin.getY(); int canvasWidth = m_parent.getWidth(); int canvasHeight = m_parent.getHeight(); // Draw top and bottom borders gl.glRecti(0, 0, canvasWidth, originY); gl.glRecti(0, originY + m_viewPortHeight, canvasWidth, canvasHeight); // Draw left and right borders gl.glRecti(0, 0, originX, canvasHeight); gl.glRecti(originX + m_viewPortWidth, 0, canvasWidth, canvasHeight); gl.glTranslated(-0.375, -0.375, 0.0); } /* * Invert the displayed image */ private void invertViewport(GLAutoDrawable drawable) { GL gl = drawable.getGL(); int viewPortOriginX = (int) m_viewPortOrigin.getX(); int viewPortOriginY = (int) m_viewPortOrigin.getY(); gl.glEnable(GL.GL_COLOR_LOGIC_OP); gl.glLogicOp(GL.GL_XOR); gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f); gl.glRecti(viewPortOriginX, viewPortOriginY, viewPortOriginX + m_viewPortWidth, viewPortOriginY + m_viewPortHeight); gl.glLogicOp(GL.GL_COPY); gl.glDisable(GL.GL_COLOR_LOGIC_OP); } /** * Called when the GLCanvas is resized */ public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { if (m_initialReshape) { zoomToFit(width, height); m_initialReshape = false; } else { correctViewForResize(width, height); } GL gl = drawable.getGL(); gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); gl.glOrtho(0.0, width, 0.0, height, -1.0, 1.0); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glLoadIdentity(); } /** * Calculate the size and location of the image display area within * the canvas such that the image is zoomed to fit the display */ public void zoomToFit(int surfaceWidth, int surfaceHeight) { if (m_renderers.size() == 0) return; Image image = null; if (m_images.size() == 1){ image = m_viewModel.getImage(m_firstImageId); }else{ image = m_viewModel.getImage(m_refImageId); } m_aspectRatio = ((float) image.getDimension(m_yAxis) * image.getPixelSpacing(m_yAxis)) / ((float) image.getDimension(m_xAxis) * image.getPixelSpacing(m_xAxis)); // Fit the image to the surface width or height depending on the a // comparison of the aspect ratios float surfaceAspectRatio = (float) surfaceHeight / (float) surfaceWidth; if (surfaceAspectRatio > m_aspectRatio) { m_viewPortWidth = surfaceWidth; m_viewPortHeight = (int) (m_aspectRatio * surfaceWidth); } else { m_viewPortHeight = surfaceHeight; m_viewPortWidth = (int) (surfaceHeight / m_aspectRatio); } m_pixelSpacing[0] = (float) image.getDimension(m_xAxis) / (float) m_viewPortWidth * (float) image.getPixelSpacing(m_xAxis); m_pixelSpacing[1] = (float) image.getDimension(m_yAxis) / (float) m_viewPortHeight * (float) image.getPixelSpacing(m_yAxis); m_pixelSpacing[2] = image.getPixelSpacing(m_configuration.getAxisIndex(2)); m_viewPortOrigin.setLocation((int) ((float) (surfaceWidth - m_viewPortWidth) / 2.0f), (int) ((float) (surfaceHeight - m_viewPortHeight) / 2.0f)); m_splitWindowCompositor.viewResized(m_viewPortWidth, m_viewPortHeight); } /* * Called when the GL context is resized to surfaceWidth by surfaceHeight. Correct the * view port size members to account for the resized GL context while maintaining the * same zoom and origin. */ private void correctViewForResize(int surfaceWidth, int surfaceHeight) { if (m_renderers.size() == 0) return; Image image = null; if (m_images.size() == 1) image = m_viewModel.getImage(m_firstImageId); else image = m_viewModel.getImage(m_refImageId); m_aspectRatio = ((float) image.getDimension(m_yAxis) * image.getPixelSpacing(m_yAxis)) / ((float) image.getDimension(m_xAxis) * image.getPixelSpacing(m_xAxis)); float scaleX = image.getPixelSpacing(m_xAxis) / m_pixelSpacing[0]; // Calculate the size of the zoomed image int viewWidth = (int) (image.getDimension(m_xAxis) * scaleX); int viewHeight = (int) (m_aspectRatio * viewWidth); // Resize the display area so that the zoomed image fills the view // as much as possible m_viewPortWidth = (viewWidth < surfaceWidth? viewWidth : surfaceWidth); m_viewPortHeight = (viewHeight < surfaceHeight? viewHeight : surfaceHeight); m_viewPortOrigin.setLocation((int) ((float) (surfaceWidth - m_viewPortWidth) / 2.0), (int) ((float) (surfaceHeight - m_viewPortHeight) / 2.0)); m_splitWindowCompositor.viewResized(m_viewPortWidth, m_viewPortHeight); } /* * Set the pixel spacing of the view. This will change the zoom level of the display. */ public void setSpacing(float newSpacingX, float newSpacingY, boolean redraw) { if (m_renderers.size() == 0) return; Image image = null; if (m_images.size() == 1) image = m_viewModel.getImage(m_firstImageId); else image = m_viewModel.getImage(m_refImageId); m_aspectRatio = ((float) image.getDimension(m_yAxis) * image.getPixelSpacing(m_yAxis)) / ((float) image.getDimension(m_xAxis) * image.getPixelSpacing(m_xAxis)); float newScaleX = image.getPixelSpacing(m_xAxis) / newSpacingX; float newScaleY = image.getPixelSpacing(m_yAxis) / newSpacingY; float oldScaleX = image.getPixelSpacing(m_xAxis) / m_pixelSpacing[0]; float oldScaleY = image.getPixelSpacing(m_yAxis) / m_pixelSpacing[1]; // Calculate the size of the zoomed image int viewWidth = (int) (image.getDimension(m_xAxis) * newScaleX); int viewHeight = (int) (m_aspectRatio * viewWidth); int surfaceWidth = m_parent.getWidth(); int surfaceHeight = m_parent.getHeight(); // Don't let the view get too big or too small final int MAX_SCALE = 32; if ((viewWidth <= 1) || (viewHeight <= 1) || (viewWidth >= surfaceWidth * MAX_SCALE) || (viewHeight >= surfaceHeight * MAX_SCALE)) return; float centerX = m_xOrigin + (m_viewPortWidth / 2); float centerY = m_yOrigin + (m_viewPortHeight / 2); // Resize the display area so that the zoomed image fills the view // as much as possible if (viewWidth < surfaceWidth) m_viewPortWidth = viewWidth; else m_viewPortWidth = surfaceWidth; if (viewHeight < surfaceHeight) m_viewPortHeight = viewHeight; else m_viewPortHeight = surfaceHeight; m_viewPortOrigin.setLocation((int) ((float) (surfaceWidth - m_viewPortWidth) / 2.0), (int) ((float) (surfaceHeight - m_viewPortHeight) / 2.0)); m_xOrigin = (int) (centerX / oldScaleX * newScaleX) - (m_viewPortWidth / 2); m_yOrigin = (int) (centerY / oldScaleY * newScaleY) - (m_viewPortHeight / 2); if (m_xOrigin < 0) m_xOrigin = 0; if (m_yOrigin < 0) m_yOrigin = 0; if (m_xOrigin + m_viewPortWidth > viewWidth) m_xOrigin = viewWidth - m_viewPortWidth; if (m_yOrigin + m_viewPortHeight > viewHeight) m_yOrigin = viewHeight - m_viewPortHeight; m_pixelSpacing[0] = newSpacingX; m_pixelSpacing[1] = newSpacingY; m_splitWindowCompositor.viewResized(m_viewPortWidth, m_viewPortHeight); if (m_firstImageId != m_refImageId){ setOffset(m_firstImageId); } if (redraw) m_parent.display(); } /* * set the x, y coordinate offset */ private void setOffset(int id){ Image firstImage = m_viewModel.getImage(id); float imageWidth = firstImage.getPixelSpacing(m_xAxis) * firstImage.getDimension(m_xAxis); float imageHeight = firstImage.getDimension(m_yAxis) * firstImage.getPixelSpacing(m_yAxis); int refImageId = m_viewModel.getReferenceImgId(); Image refImage = m_viewModel.getImage(refImageId); float refWidth = refImage.getPixelSpacing(m_xAxis) * refImage.getDimension(m_xAxis); float refHeight = refImage.getPixelSpacing(m_yAxis) * refImage.getDimension(m_yAxis); m_offset[0] = (refWidth - imageWidth)/m_pixelSpacing[0] / 2.0f; m_offset[1] = (refHeight - imageHeight)/m_pixelSpacing[1] / 2.0f; } /* * Return true if the x,y coordinates are within the boundaries of * the displayed image. */ private boolean pointInImage(int x, int y) { if (m_images.size() ==1 && m_firstImageId != m_refImageId){ if (x < m_viewPortOrigin.x + m_offset[0] || x > m_viewPortOrigin.x + m_viewPortWidth +m_offset[0]) return false; if (y < m_viewPortOrigin.y + m_offset[1] || y > m_viewPortOrigin.y + m_viewPortHeight + m_offset[1]) return false; } else { if (x < m_viewPortOrigin.x) return false; if (y < m_viewPortOrigin.y) return false; if (x > m_viewPortOrigin.x + m_viewPortWidth) return false; if (y > m_viewPortOrigin.y + m_viewPortHeight) return false; } return true; } /** * OpenGL display mode change notification */ public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { } /** * Mouse has been pressed and released */ public void mouseClicked(MouseEvent e) { } /** * A mouse button has been pressed. */ public void mousePressed(MouseEvent e) { // If there is a tool, forward the event to the tool. Otherwise, // try to find a tool that can handle the event. if (m_currentTool != null) m_currentTool.mousePressed(e); else { int mode = m_viewModel.getMouseMode(); if (m_windowLevelTool.canHandle(e)) m_currentTool = m_windowLevelTool; if ((m_currentTool == null) && ((mode == ImageDisplayModel.MOUSE_MODE_ZOOM) || e.isAltDown() || (e.getButton() == MouseEvent.BUTTON2)) && m_zoomTool.canHandle(e)) m_currentTool = m_zoomTool; if (((mode == ImageDisplayModel.MOUSE_MODE_PAN) || (e.getButton() == MouseEvent.BUTTON3)) && m_panTool.canHandle(e)) m_currentTool = m_panTool; if ((m_splitWindowTool != null) && (m_renderers.size() > 1) && m_splitWindowTool.canHandle(e)) m_currentTool = m_splitWindowTool; if ((m_currentTool == null) && (mode == ImageDisplayModel.MOUSE_MODE_NAVIGATE) && m_navigatorTool.canHandle(e)) m_currentTool = m_navigatorTool; if ((m_currentTool == null) && (mode == ImageDisplayModel.MOUSE_MODE_RECTANGLE_ROI) && m_rectROITool.canHandle(e)) m_currentTool = m_rectROITool; if ((m_currentTool == null) && (mode == ImageDisplayModel.MOUSE_MODE_MEASURE_LINE) && m_rectROITool.canHandle(e)) m_currentTool = m_rectROITool; if ((m_currentTool == null) && (mode == ImageDisplayModel.MOUSE_MODE_COPYIMAGE)) { if (pointInImage(e.getX(), e.getY())) { // To copy pixels from the GL color buffer, a GL context must be active. // To trigger this condition, we call display on the parent GLCanvas. m_copyRenderFlag = true; m_parent.display(); } } if (m_currentTool != null) m_currentTool.mousePressed(e); } } /** * A mouse button has been released. */ public void mouseReleased(MouseEvent e) { boolean mouseInImage = pointInImage(e.getX(), e.getY()); if (m_currentTool != null) m_currentTool.mouseReleased(e); if (!mouseInImage) { CursorControl.setArrowCursor(m_parent); clearPixelInfo(); } } /** * The mouse has entered the display area. */ public void mouseEntered(MouseEvent e) { } /** * The mouse has left the display area. */ public void mouseExited(MouseEvent e) { if (m_currentTool == null) CursorControl.setArrowCursor(m_parent); clearPixelInfo(); } /** * The mouse has been dragged. */ public void mouseDragged(MouseEvent e) { if (m_currentTool != null) m_currentTool.mouseDragged(e); if (pointInImage(e.getX(), e.getY())) updatePixelInfo(e); else clearPixelInfo(); } /** * The mouse has moved. */ public void mouseMoved(MouseEvent e) { // Record whether the mouse is inside the image display area // before calling methods that might change the MouseEvent boolean mouseInImage = pointInImage(e.getX(), e.getY()); if (m_currentTool != null) { m_currentTool.mouseMoved(e); } else if ((m_splitWindowTool != null) && (m_renderers.size() > 1) && m_splitWindowTool.canHandle(e)) { m_currentTool = m_splitWindowTool; m_currentTool.mouseMoved(e); } else if (mouseInImage) { CursorControl.setCrossCursor(m_parent); } else { CursorControl.setArrowCursor(m_parent); } if (mouseInImage) updatePixelInfo(e); else clearPixelInfo(); } /* * Update the pixel coordinate and value display. */ private void updatePixelInfo(MouseEvent e) { if (m_viewModel == null) return; float x = e.getX() - m_viewPortOrigin.x + m_xOrigin; float y = getViewHeight() - (e.getY() - m_viewPortOrigin.y) - 1 + m_yOrigin; if (!m_configuration.getAxisFlipped(1)) y = getScaledHeight() - y - 1; m_positionTemp[m_configuration.getAxisIndex(0)] = x * m_pixelSpacing[0]; m_positionTemp[m_configuration.getAxisIndex(1)] = y * m_pixelSpacing[1]; m_positionTemp[m_configuration.getAxisIndex(2)] = m_viewModel.getPosition(0, m_configuration.getAxisIndex(2)); Iterator listeners = m_pixelInfoListeners.iterator(); while (listeners.hasNext()) ((PixelInfoListener) listeners.next()). displayPixelInfo(m_viewModel, m_positionTemp[0], m_positionTemp[1], m_positionTemp[2]); } /* * Erase pixel coordinate and value display. */ private void clearPixelInfo() { Iterator listeners = m_pixelInfoListeners.iterator(); while (listeners.hasNext()) ((PixelInfoListener) listeners.next()).clearPixelInfo(); } /** * Clears the mouse tool that is handling events. For example, a tool * that is responding to mouse clicks and drags might call this method * when the mouse button is released at the end of a drag. */ public void releaseMouseTool() { m_currentTool = null; CursorControl.setCrossCursor(m_parent); } /** * Translate the click point in the mouse event to account for the origin offset. */ public void transformPoint(Point pt) { pt.setLocation(pt.x - m_viewPortOrigin.getX(), pt.y - m_viewPortOrigin.getY()); } /** * Respond to a change in the state of the image model. */ public void modelChanged(int type, int imageSet, ImageDisplayModel source) { switch (type) { case ImageDisplayModel.POSITION_CHANGED: m_reloadSlices = 0; m_parent.display(); break; case ImageDisplayModel.WINDOWLEVEL_CHANGED: case ImageDisplayModel.COLORMAP_CHANGED: // Force an image reload no the next draw for the new // window and level or colormap m_reloadSlices = imageSet; m_parent.display(); break; case FusionDisplayModel.FUSIONMODE_CHANGED: case FusionDisplayModel.ALPHA_CHANGED: case FusionDisplayModel.CHECKERBOARD_CHANGED: case FusionDisplayModel.SPLITWINDOW_CHANGED: case ImageDisplayModel.CROSSHAIRS_CHANGED: m_reloadSlices = 0; m_parent.display(); break; } } /* * Copy the displayed image to the clipboard. */ private void copyToClipboard(GLAutoDrawable drawable) { // Copy the back buffer int imageSize = m_viewPortWidth * m_viewPortHeight * NUM_PIXEL_COMPONENTS; ByteBuffer pixels = ByteBuffer.allocate(imageSize); drawable.getGL().glReadPixels(m_viewPortOrigin.x, m_viewPortOrigin.y, m_viewPortWidth, m_viewPortHeight, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, pixels); BufferedImage image = new BufferedImage(m_viewPortWidth, m_viewPortHeight, BufferedImage.TYPE_INT_RGB); WritableRaster raster = image.getRaster(); int k = 0; for (int j = 0; j < m_viewPortHeight; j++) for (int i = 0; i < m_viewPortWidth; i++) { int h = m_viewPortHeight - j - 1; raster.setSample(i, h, 2, pixels.get(k++)); raster.setSample(i, h, 1, pixels.get(k++)); raster.setSample(i, h, 0, pixels.get(k++)); k++; } ImageDataTransfer imageTransfer = new ImageDataTransfer(image); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imageTransfer, null); } /* ------------------ * Field accessors * ------------------ */ /** * Returns the GLCanvas this view is drawing in. */ public GLCanvas getParent() { return m_parent; } /** * Returns the image(s) drawn to GLCanvas */ public LinkedList getImages() { return m_images; } /** * Returns the configuration of the 2D slice in this view. */ public SliceConfiguration getConfiguration() { return m_configuration; } /** * return the x, y coordinate offset */ public float[] getOffset(){ return m_offset; } /** * Returns the reference image size. */ public float getReferenceSize(int idx){ int imgId= m_viewModel.getReferenceImgId(); Image img = m_viewModel.getImage(imgId); int dim =img.getDimensions().length; float[] referenceSize = {0,0,0}; for (int i = 0; i < dim; i++) referenceSize[i] = img.getPixelSpacing(i) * img.getDimension(i); return referenceSize[idx]; } /** * Returns the height of this view. */ public int getViewHeight() { return m_viewPortHeight; } /** * Returns the width of this view. */ public int getViewWidth() { return m_viewPortWidth; } /** * Get the height of the zoomed image in pixels. * * @return zoomed image height */ public int getScaledHeight() { if (m_renderers.size() == 0) return 0; Image image = null; if (m_images.size() == 1){ image = m_viewModel.getImage(m_firstImageId); }else{ image = m_viewModel.getImage(m_refImageId); } return (int) (image.getDimension(m_configuration.getAxisIndex(1)) * image.getPixelSpacing(m_configuration.getAxisIndex(1)) / m_pixelSpacing[1]); } /** * Get the width of the zoomed image in pixels. * * @return zoomed image width */ public int getScaledWidth() { if (m_renderers.size() == 0) return 0; Image image = null; if (m_images.size() == 1){ image = m_viewModel.getImage(m_firstImageId); }else{ image = m_viewModel.getImage(m_refImageId); } return (int) (image.getDimension(m_configuration.getAxisIndex(0)) * image.getPixelSpacing(m_configuration.getAxisIndex(0)) / m_pixelSpacing[0]); } /** * Returns the data model this view is rendering. */ public ImageDisplayModel getViewModel() { return m_viewModel; } /** * Returns the view origin. */ public int getXOrigin() { return m_xOrigin; } /** * Returns the view origin. */ public int getYOrigin() { return m_yOrigin; } /** * Returns the view pixel spacing. */ public float getPixelSpacing(int i) { return m_pixelSpacing[i]; } /** * Sets the view origin. This will pan a zoomed-in image. */ public void setOrigin(int x, int y) { if (m_renderers.size() == 0) return; if ((m_viewPortWidth < m_parent.getWidth()) && (m_viewPortHeight < m_parent.getHeight())) return; Image image = m_viewModel.getImage(m_refImageId); // Calculate the size of the zoomed image int viewWidth = (int) (image.getDimension(m_xAxis) * image.getPixelSpacing(m_xAxis) / m_pixelSpacing[0]); int viewHeight = (int) (m_aspectRatio * viewWidth); m_xOrigin = x; m_yOrigin = y; if (m_xOrigin < 0) m_xOrigin = 0; if (m_yOrigin < 0) m_yOrigin = 0; if (m_xOrigin + m_viewPortWidth > viewWidth) m_xOrigin = viewWidth - m_viewPortWidth; if (m_yOrigin + m_viewPortHeight > viewHeight) m_yOrigin = viewHeight - m_viewPortHeight; m_parent.display(); } /** * Add a listener for pixel coordinate and value update events. */ public void addPixelInfoListener(PixelInfoListener listener) { m_pixelInfoListeners.add(listener); } /** * Remove a listener for pixel coordinate and value update events. */ public void removePixelInfoListener(PixelInfoListener listener) { m_pixelInfoListeners.remove(listener); } }