Logo Search packages:      
Sourcecode: pixelmed version File versions  Download package

MammoImageViewer.java

/* Copyright (c) 2001-2009, David A. Clunie DBA Pixelmed Publishing. All rights reserved. */

package com.pixelmed.display;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*; 
import java.awt.color.*; 
import java.awt.geom.*; 
import java.util.*; 
import java.io.*; 
import javax.swing.*; 
import javax.swing.event.*;

import com.pixelmed.display.event.*; 
import com.pixelmed.dicom.*;
import com.pixelmed.event.EventContext;

/**
 * <p>This class is an entire application for displaying and viewing mammography images and
 * CAD objects.</p>
 * 
 * <p>It detects the screen size and scales the images to fit the available screen real estate,
 * using up to four columns of images and multiple rows as necessary.</p>
 * 
 * <p>Images are scaled to the same physical size based on the detected breast area.</p>
 * 
 * <p>Images are flipped into the correct orientation for the view.</p>
 * 
 * <p>It is invoked using a main method with a list of DICOM image and CAD file names.</p>
 * 
 * @author  dclunie
 */
00034 public class MammoImageViewer {

      private static final String identString = "@(#) $Header: /userland/cvs/pixelmed/imgbook/com/pixelmed/display/MammoImageViewer.java,v 1.74 2009/04/06 16:13:49 dclunie Exp $";

      protected JFrame frame;
      protected JPanel multiPanel;
      protected int frameWidth;
      protected int frameHeight;
      
      protected boolean doNotFlipOrRotate = false;;
      
      /**
       * <p>Suppress the normal flipping or rotation of images into the preferred orientation based on view and laterality.</p>
       * 
       * @param   doNotFlipOrRotate if true, supresses
       */
00050       public void setDoNotFlipOrRotate(boolean doNotFlipOrRotate) {
            this.doNotFlipOrRotate = doNotFlipOrRotate;
      }

      protected boolean forceFitEntireMatrixToWindow = false;
      
      /**
       * <p>Suppress the examination of breast extent and same sizing of images with different pixel spacing.</p>
       * 
       * @param   forceFitEntireMatrixToWindow  if true, supresses
       */
00061       public void setForceFitEntireMatrixToWindow(boolean forceFitEntireMatrixToWindow) {
            this.forceFitEntireMatrixToWindow = forceFitEntireMatrixToWindow;
      }

      protected boolean doNotJustify = false;
      
      /**
       * <p>Suppress the justification to the chest wall and axilla.</p>
       * 
       * @param   doNotJustify      if true, supresses
       */
00072       public void setDoNotJustify(boolean doNotJustify) {
            this.doNotJustify = doNotJustify;
      }

      
      /**
       * @param   list
       * @return              array of two String values, row then column orientation, else array of two nulls if cannot be obtained
       */
00081       private static String[] getPatientOrientation(AttributeList list) {
            String[] vPatientOrientation = null;
            Attribute aPatientOrientation = list.get(TagFromName.PatientOrientation);
            if (aPatientOrientation != null && aPatientOrientation.getVM() == 2) {
                  try {
                        vPatientOrientation = aPatientOrientation.getStringValues();
                        if (vPatientOrientation != null && vPatientOrientation.length != 2) {
                              vPatientOrientation=null;
                        }
                  }
                  catch (DicomException e) {
                        vPatientOrientation=null;
                  }
            }
            if (vPatientOrientation == null) {
                  vPatientOrientation = new String[2];
            }
            return vPatientOrientation;
      }
      
      /**
       * @param   list
       * @return              a single String value, null if cannot be obtained
       */
00105       private static String getView(AttributeList list) {
            String view = null;
            CodedSequenceItem csiViewCodeSequence = CodedSequenceItem.getSingleCodedSequenceItemOrNull(list,TagFromName.ViewCodeSequence);
            if (csiViewCodeSequence != null) {
                  //view = decipherSNOMEDCodeSequence(csiViewCodeSequence,standardViewCodes);
                  view = MammoDemographicAndTechniqueAnnotations.getViewAbbreviationFromViewCodeSequenceAttributes(csiViewCodeSequence.getAttributeList());
            }
//System.err.println("getView(): view="+view);
            return view;
      }
      
      /**
       * @param   list
       * @return              a single String value, null if cannot be obtained
       */
00120       private static String getImageLateralityViewModifierAndViewModifier(AttributeList list) {
            return MammoDemographicAndTechniqueAnnotations.getAbbreviationFromImageLateralityViewModifierAndViewModifierCodeSequenceAttributes(list);
      }
      
      /**
       * @param   list
       * @return              a single String value, null if cannot be obtained
       */
00128       private static String getLaterality(AttributeList list) {
            return Attribute.getSingleStringValueOrNull(list,TagFromName.ImageLaterality);
      }
      
      /**
       * @param   list
       * @return              a single String value, null if cannot be obtained
       */
00136       private static String getDate(AttributeList list) {
            return Attribute.getSingleStringValueOrNull(list,TagFromName.StudyDate);
      }
      
      /**
       * @param   dates
       * @return              a single String value, null if cannot be obtained
       */
00144       private static String getDateOfCurrentStudy(String dates[]) {
            int n = dates.length;
            int latestDateValue = 0;
            String latestDateString = null;
            for (int i=0; i<n; ++i) {
                  if (dates[i] != null) {
                        int testDateValue = 0;
                        try {
                              testDateValue = Integer.parseInt(dates[i]);
                        }
                        catch (NumberFormatException e) {
                              e.printStackTrace(System.err);
                        }
                        if (testDateValue > latestDateValue) {
                              latestDateValue = testDateValue;
                              latestDateString = dates[i];
                        }
                  }
            }
            return latestDateString;
      }
      
      /**
       * @param   n                       number of images ... 8 for prior and current 4 view, 2 for left/right or prior/current same view comparison
       * @param   views
       * @param   lateralities
       * @param   dates
       * @return                          an array of indexes in the correct sequence
       */
00173       private static int[] determineOrderForDisplay(int n,String views[],String lateralities[],String lateralityViewAndModifiers[],String dates[]) {
            // assert n == 8 || n == 2;
            // views.length == n;
            // lateralities.length == n;
            // dates.length == n;
            
            int order[] = null;

            if (n == 2) {
                  boolean sameStudy = (dates[0] != null && dates[1] != null && dates[0].equals(dates[1])) /*|| (dates[0] == null && dates[1] == null)*/;
                  boolean sameView  = (views[0] != null && views[1] != null && views[0].equals(views[1]));
                  boolean sameSide  = (lateralities[0] != null && lateralities[1] != null && lateralities[0].equals(lateralities[1]));
                  if (sameView) {
                        if (sameStudy) {
                              if (!sameSide) {
                                    if (lateralities[0] != null) {
                                          order = new int[n];
System.err.println("Hanging two of same view and same study with right side on left of display");
                                          // hang right side on left of display
                                          if (lateralities[0].equals("R")) {
                                                order[0] = 0;
                                                order[1] = 1;     // assume lateralities[1] is present and "L"
                                          }
                                          else {                        // assume lateralities[0] is "L"
                                                order[0] = 1;
                                                order[1] = 0;     // assume lateralities[1] is present and "R"
                                          }
                                    }
                              }
                              // else same view and side and study, give up trying to hang any particular way
                        }
                        else {      // different study
                              if (sameSide) {
                                    if (dates[0] != null && dates[1] != null) {
                                          order = new int[n];
System.err.println("Hanging two of same view and same side different study with earlier study on left of display");
                                          // hang earlier study on left of display
                                          if (Integer.parseInt(dates[0]) < Integer.parseInt(dates[1])) {
                                                order[0] = 0;
                                                order[1] = 1;
                                          }
                                          else {
                                                order[0] = 1;
                                                order[1] = 0;
                                          }
                                    }
                              }
                              // else same view but different side and different study, give up trying to hang any particular way
                        }
                  }
                  // else different view, give up trying to hang any particular way
            }
            if (order == null && n == 4) {
System.err.println("Hanging four views");
                  order = new int[n];
                  for (int i=0; i<n; ++i) {
                        order[i]=-1;
                  }
                  for (int i=0; i<n; ++i) {
                        // assume all with same date
                        // left laterality displayed on right side
                        int lateralityOffset = (lateralities[i] != null && lateralities[i].equals("L")) ? 1 : 0;
                        // CC displayed on right side
                        int viewOffset = (views[i] != null && views[i].equals("CC")) ? 2 : 0;
                        int offset = lateralityOffset + viewOffset;
                        if (order[offset] == -1 ) {
//System.err.println("spot "+offset+" used by image "+i);
                              order[offset] = i;
                        }
                        else {
System.err.println("spot "+offset+" wanted by "+i+" but already used by "+order[offset]);
                              order = null;
                              break;
                        }
                  }
            }
            if (order == null && n == 8) {
System.err.println("Hanging eight views - looking for prior and current four views");
                  order = new int[n];
                  for (int i=0; i<n; ++i) {
                        order[i]=-1;
                  }
                  String currentStudyDate = getDateOfCurrentStudy(dates);
//System.err.println("currentStudyDate "+currentStudyDate);
                  for (int i=0; i<n; ++i) {
                        // assume all with latest date and the same date are current
                        // current goes on bottom row (4,5,6,7)
                        // assume prior, regardless of what date actually is or whether all the same or whether even present
                        // prior goes on top row (0,1,2,3)
                        int dateOffset =  (currentStudyDate != null && dates[i] != null && currentStudyDate.equals(dates[i])) ? 4 : 0;
//System.err.println("dates[i] "+dates[i]+" used by image "+i);
//System.err.println("dateOffset "+dateOffset+" used by image "+i);
                        // left laterality displayed on right side
                        int lateralityOffset = (lateralities[i] != null && lateralities[i].equals("L")) ? 1 : 0;
//System.err.println("lateralityOffset "+lateralityOffset+" used by image "+i);
                        // CC displayed on right side
                        int viewOffset = (views[i] != null && views[i].equals("CC")) ? 2 : 0;
//System.err.println("views[i] "+views[i]+" used by image "+i);
//System.err.println("viewOffset "+viewOffset+" used by image "+i);
                        int offset = dateOffset + lateralityOffset + viewOffset;
                        if (order[offset] == -1 ) {
//System.err.println("spot "+offset+" used by image "+i);
                              order[offset] = i;
                        }
                        else {
System.err.println("spot "+offset+" wanted by "+i+" but already used by "+order[offset]);
                              order = null;
                              break;
                        }
                  }
            }
            if (order == null && n == 8) {
System.err.println("Hanging eight views - looking for views with and without modifiers");
                  order = new int[n];
                  for (int i=0; i<n; ++i) {
                        order[i]=-1;
                  }
                  for (int i=0; i<n; ++i) {
                        // no modifier goes on bottom row (4,5,6,7)
                        // with modifier goes on top row (0,1,2,3)
                        int viewModifierOffset = (lateralityViewAndModifiers[i] != null && lateralities[i] != null && views[i] != null
                              && lateralityViewAndModifiers[i].equals(lateralities[i]+views[i])) ? 0 : 4;
//System.err.println("lateralityViewAndModifiers[i] "+lateralityViewAndModifiers[i]+" used by image "+i);
//System.err.println("viewModifierOffset "+viewModifierOffset+" used by image "+i);
                        // left laterality displayed on right side
                        int lateralityOffset = (lateralities[i] != null && lateralities[i].equals("L")) ? 1 : 0;
//System.err.println("lateralityOffset "+lateralityOffset+" used by image "+i);
                        // CC displayed on right side
                        int viewOffset = (views[i] != null && views[i].equals("CC")) ? 2 : 0;
//System.err.println("views[i] "+views[i]+" used by image "+i);
//System.err.println("viewOffset "+viewOffset+" used by image "+i);
                        int offset = lateralityOffset + viewOffset + viewModifierOffset;
                        if (order[offset] == -1 ) {
//System.err.println("spot "+offset+" used by image "+i);
                              order[offset] = i;
                        }
                        else {
System.err.println("spot "+offset+" wanted by "+i+" but already used by "+order[offset]);
                              order = null;
                              break;
                        }
                  }
            }
            
            return order;
      }
      
      private static char invertOrientation(char orientation) {
            switch (orientation) {
                  case 'A':   return 'P';
                  case 'P':   return 'A';
                  case 'R':   return 'L';
                  case 'L':   return 'R';
                  case 'H':   return 'F';
                  case 'F':   return 'H';
            }
            return orientation;
      }
      
      private static String invertOrientation(String orientation) {
            StringBuffer b = new StringBuffer();
            if (orientation != null && orientation.length() > 0) {
                  char [] array = orientation.toCharArray();
                  for (int i=0; i<array.length; ++i) {
                        b.append(invertOrientation(array[i]));
                  }
            }
            return b.toString();
      }
      
      private class FlipAsNecessary {
            private BufferedImage img;
            private String rowOrientation;
            private String columnOrientation;
            private AffineTransform transform;
            
            FlipAsNecessary(BufferedImage img,String orientation[],String view,String laterality,boolean suppressFlipAndReturnIdentity) {
                  this.img = img;
                  rowOrientation = orientation != null ? orientation[0] : null;
                  columnOrientation = orientation != null ? orientation[1] : null;
                  transform = new AffineTransform();  // identity transformation
                  
                  if (suppressFlipAndReturnIdentity) {
                        return;
                  }
                  
                  // identity translation matrix and the AffineTransform(m00 m10 m01 m11 m02 m12) constructor arguments are ...
                  //
                  // 1  0  Tx       m00 m01 m02
                  // 0  1  Ty       m10 m11 m12
                  // 0  0  1        na  na  na

                  if (rowOrientation != null && rowOrientation.indexOf("P") == -1 && rowOrientation.indexOf("A") == -1) {
                        // if row is not AP, then we should rotate and flip rows and columns, and deal with the consequences next
                        String tmp = rowOrientation;
                        rowOrientation = columnOrientation;
                        columnOrientation = tmp;
System.err.println("FlipAsNecessary(): swapping rows and columns");
                        this.img = BufferedImageUtilities.rotateAndFlipSwappingRowsAndColumns(img);
                        //
                        // to just flip x and y use ...
                        //
                        // 0  1  0        m00 m01 m02
                        // 1  0  0        m10 m11 m12
                        // 0  0  1        na  na  na
                        //
                        transform.concatenate(new AffineTransform(0,1,1,0,0,0));
                  }
            
                  if (rowOrientation != null && laterality != null) {
                        if (laterality.equals("R") && rowOrientation.indexOf("P") == -1
                         || laterality.equals("L") && rowOrientation.indexOf("A") == -1
                        ) {
System.err.println("FlipAsNecessary(): flipping horizontally");
                              BufferedImageUtilities.flipHorizontally(img);
                              rowOrientation = invertOrientation(rowOrientation);
                              //
                              // to flip horizontally use ...
                              //
                              // -1 0  w-1      m00 m01 m02
                              // 0  1  0        m10 m11 m12
                              // 0  0  1        na  na  na
                              //
                              transform.concatenate(new AffineTransform(-1,0,0,1,img.getWidth()-1,0));
                        }
                  }

                  if (columnOrientation != null && laterality != null && view != null) {
                        boolean flipVertically=false;
                        if (view.equals("MLO") || view.equals("ML") || view.equals("LM") || view.equals("LMO") || view.equals("SIO")) {
                              // for lateral and lateral obliques, feet are always at the bottom (axilla at the top), regardless of side
                              if (columnOrientation.indexOf("F") == -1) {
System.err.println("FlipAsNecessary(): flipping vertically for lateral or oblique to put feet at bottom");
                                    flipVertically=true;
                              }
                        }
                        else {
                              // for all other views, the laterality value must be opposite to the column orientation (axilla at top)
                              if (columnOrientation.indexOf(laterality) != -1) {
System.err.println("FlipAsNecessary(): flipping vertically to put laterality in axilla");
                                    flipVertically=true;
                              }
                        }
                        if (flipVertically) {
                              BufferedImageUtilities.flipVertically(img);
                              columnOrientation = invertOrientation(columnOrientation);
                              //
                              // to flip vertically use ...
                              //
                              // 1  0  0        m00 m01 m02
                              // 0  -1 h-1      m10 m11 m12
                              // 0  0  1        na  na  na
                              //
System.err.println("FlipAsNecessary(): WARNING: flipping vertically but supressing coordinate transform because does not work - check CAD locations, which may be wrong");
                              //transform.concatenate(new AffineTransform(1,0,0,-1,0,img.getHeight()-1));         // :(
                        }
                  }
            }
            
            public BufferedImage getImage() { return img;}
            public String getRowOrientation() { return rowOrientation; }
            public String getColumnOrientation() { return columnOrientation; }
            public AffineTransform getTransform() { return transform; }
      }


      private class BreastExtentFinder {
            double samplingFactorFractionOfDimension = 0.05;                              // don't want this too small, else gets faked out by CR white edges
            double indentFromChestWallFactorFractionOfDimension = 0.1;
            double thresholdFractionOfPixelRangeAboveBlackToBeBreast = 0.01;
            //double thresholdFractionOfPixelRangeAboveBlackToBeBreast = 0.25;
      
            int upperBound;
            int lowerBound;
            int leftBound;
            int rightBound;
            double horizontalExtentInMm;
            double verticalExtentInMm;;
            
            public int getUpperBound() { return upperBound; }
            public int getLowerBound() { return lowerBound; }
            public int getLeftBound()  { return leftBound; }
            public int getRightBound() { return rightBound; }
            public double getHorizontalExtentInMm() { return horizontalExtentInMm; }
            public double getVerticalExtentInMm() { return verticalExtentInMm; }
            
            public int getVerticalCenter() { return (lowerBound - upperBound)/2; }
            
            BreastExtentFinder(BufferedImage img,double spacing) {
                  int imageWidth = img.getWidth();
                  int imageHeight = img.getHeight();
                  upperBound = 0;
                  lowerBound = imageHeight-1;
                  leftBound = 0;
                  rightBound = imageWidth-1;
                  horizontalExtentInMm = (rightBound - leftBound) * spacing;
                  verticalExtentInMm = (lowerBound - upperBound) * spacing;
            }

            BreastExtentFinder(BufferedImage img,String rowOrientation,int minimumPixel,int maximumPixel,boolean isInverted,double spacing) {
//System.err.println("BreastExtentFinder: rowOrientation = "+rowOrientation);
//System.err.println("BreastExtentFinder: minimumPixel = "+minimumPixel);
//System.err.println("BreastExtentFinder: maximumPixel = "+maximumPixel);
//System.err.println("BreastExtentFinder: isInverted = "+isInverted);
                  // assume that image has already been orientated correctly such that A-P is horizontal
                  boolean chestWallIsRightSide = rowOrientation != null && rowOrientation.indexOf("P") != -1;
//System.err.println("BreastExtentFinder: chestWallIsRightSide = "+chestWallIsRightSide);
                  int imageWidth = img.getWidth();
//System.err.println("BreastExtentFinder: imageWidth = "+imageWidth);
                  int imageHeight = img.getHeight();
//System.err.println("BreastExtentFinder: imageHeight = "+imageHeight);
                  int threshold = (int)((maximumPixel-minimumPixel) * thresholdFractionOfPixelRangeAboveBlackToBeBreast + minimumPixel);
//System.err.println("BreastExtentFinder: threshold = "+threshold);
                  if (isInverted) {
                        threshold = maximumPixel - threshold;
//System.err.println("BreastExtentFinder: inverted threshold = "+threshold);
                  }
                  int verticalSamplingInterval = (int)(imageHeight * samplingFactorFractionOfDimension);
//System.err.println("BreastExtentFinder: verticalSamplingInterval = "+verticalSamplingInterval);
                  int verticalSamplingStart = verticalSamplingInterval / 2;         // don't want this too small or zero, else gets faked out by CR white edges
//System.err.println("BreastExtentFinder: verticalSamplingStart = "+verticalSamplingStart);
                  int offsetFromChestWall = (int)(imageWidth * indentFromChestWallFactorFractionOfDimension);
//System.err.println("BreastExtentFinder: offsetFromChestWall = "+offsetFromChestWall);
                  int columnToSample = chestWallIsRightSide ? imageWidth-offsetFromChestWall-1 : offsetFromChestWall;
//System.err.println("BreastExtentFinder: columnToSample = "+columnToSample);
                  upperBound = 0;
                  int testY = verticalSamplingStart;
                  while (testY < imageHeight) {
                        int pixelValues[] = img.getRaster().getPixel(columnToSample,testY,(int[])null);
                        int pixelValue = pixelValues[0];
//System.err.println("BreastExtentFinder: upperBound ("+columnToSample+","+testY+") pixel = "+pixelValue);
                        if (( isInverted && pixelValue < threshold)
                         || (!isInverted && pixelValue > threshold)) {
                              break;
                        }
                        upperBound=testY;
                        testY+=verticalSamplingInterval;
                  }
//System.err.println("BreastExtentFinder: upperBound = "+upperBound);
                  lowerBound = imageHeight - 1;
                  testY = imageHeight - verticalSamplingStart - 1;
                  while (testY > 0) {
                        int pixelValues[] = img.getRaster().getPixel(columnToSample,testY,(int[])null);
                        int pixelValue = pixelValues[0];
//System.err.println("BreastExtentFinder: lowerBound ("+columnToSample+","+testY+") pixel = "+pixelValue);
                        if (( isInverted && pixelValue < threshold)
                         || (!isInverted && pixelValue > threshold)) {
                              break;
                        }
                        lowerBound=testY;
                        testY-=verticalSamplingInterval;
                  }
//System.err.println("BreastExtentFinder: lowerBound = "+lowerBound);

                  if (upperBound > lowerBound) {
                        // then we crossed over, i.e., failed to find a boundary, so give up
                        upperBound = 0;
                        lowerBound = imageHeight - 1;
                  }

                  // Now find lateral extent between upper and lower bounds ...
                  
                  int horizontalSamplingInterval = (int)(imageWidth * samplingFactorFractionOfDimension);
//System.err.println("BreastExtentFinder: horizontalSamplingInterval = "+horizontalSamplingInterval);

                  leftBound = 0;
                  rightBound = imageWidth-1;
                  int startYForLateralCheck = upperBound > verticalSamplingStart ? upperBound : verticalSamplingStart;  // don't get caught up in white CR edges
                  int endYForLateralCheck = lowerBound < imageHeight-verticalSamplingStart-1 ? lowerBound : imageHeight-verticalSamplingStart-1;
                  if (startYForLateralCheck < endYForLateralCheck) {
                        int lastActualX = chestWallIsRightSide ? imageWidth-offsetFromChestWall-1 : offsetFromChestWall;
                        int testX = offsetFromChestWall;
                        while (testX < imageWidth) {
                              boolean columnContainsSupraThresholdPixels = false;
                              int actualX = chestWallIsRightSide ? imageWidth-testX-1 : testX;
                              for (testY=startYForLateralCheck; testY <= endYForLateralCheck && !columnContainsSupraThresholdPixels; testY+=verticalSamplingInterval) {
                                    int pixelValues[] = img.getRaster().getPixel(actualX,testY,(int[])null);
                                    int pixelValue = pixelValues[0];
//System.err.println("BreastExtentFinder: lateral bound ("+actualX+","+testY+") pixel = "+pixelValue);
                                    if (( isInverted && pixelValue < threshold)
                                     || (!isInverted && pixelValue > threshold)) {
                                          columnContainsSupraThresholdPixels = true;;
                                    }
                              }
                              if (!columnContainsSupraThresholdPixels) {
//System.err.println("BreastExtentFinder: found empty column");
                                    // found "empty" column
                                    if (chestWallIsRightSide) {
                                          leftBound = lastActualX;
                                    }
                                    else {
                                          rightBound = lastActualX;
                                    }
                                    break;
                              }
                              lastActualX=actualX;
                              testX+=horizontalSamplingInterval;
                        }
                  }
//System.err.println("BreastExtentFinder: leftBound = "+leftBound);
//System.err.println("BreastExtentFinder: rightBound = "+rightBound);

                  // use a fudge factor to compensate for sampling interval. amongst other things :)
                  
                  if (upperBound > verticalSamplingInterval) {
                        upperBound-=verticalSamplingInterval;
                  }
                  if (lowerBound < (imageHeight - verticalSamplingInterval - 1)) {
                        lowerBound+=verticalSamplingInterval;
                  }
                  if (chestWallIsRightSide) {
                        if (leftBound > horizontalSamplingInterval) {
                              leftBound-=horizontalSamplingInterval;
                        }
                  }
                  else {
                        if (rightBound < (imageHeight - verticalSamplingInterval - 1)) {
                              rightBound+=verticalSamplingInterval;
                        }
                  }

                  horizontalExtentInMm = (rightBound - leftBound) * spacing;
                  verticalExtentInMm = (lowerBound - upperBound) * spacing;
//System.err.println("BreastExtentFinder: horizontalExtentInMm = "+horizontalExtentInMm);
//System.err.println("BreastExtentFinder: verticalExtentInMm = "+verticalExtentInMm);
            }
      }
      
      /**
       * @param   list
       * @return              a single value, or null if cannot be obtained (absent or empty)
       */
00605       private static String getSingleSourceImageSequenceReferencedSOPInstanceUIDOrNull(AttributeList list) {
            String uid = null;
            Attribute aSpatialLocationsPreserved = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(list,TagFromName.SourceImageSequence,TagFromName.SpatialLocationsPreserved);
//System.err.println("getSingleSourceImageSequenceReferencedSOPInstanceUIDOrNull(): aSpatialLocationsPreserved = "+aSpatialLocationsPreserved);
            if (aSpatialLocationsPreserved == null                                                                            // in absence (legacy images) assume YES
             || aSpatialLocationsPreserved.getSingleStringValueOrEmptyString().equals("YES")) {       // if present, must be YES
                  Attribute aReferencedSOPInstanceUID = SequenceAttribute.getNamedAttributeFromWithinSequenceWithSingleItem(list,TagFromName.SourceImageSequence,TagFromName.ReferencedSOPInstanceUID);
//System.err.println("getSingleSourceImageSequenceReferencedSOPInstanceUIDOrNull(): aReferencedSOPInstanceUID = "+aReferencedSOPInstanceUID);
                  if (aReferencedSOPInstanceUID != null) {
                        uid = aReferencedSOPInstanceUID.getSingleStringValueOrNull();
                        if (uid != null && uid.length() == 0) {
                              uid = null;
                        }
                  }
            }
            return uid;
      }
      
      private static final int crossSize = 50;  // actually just one arm of the cross
      private static final int crossGap  = 15;  // is included in crossSize
      
      /**
       * @param   coord
       * @param   preDefinedShapes
       */
00630       private static void addCADMarkToPreDefinedShapes(SpatialCoordinateAndImageReference coord,Vector preDefinedShapes) {
//System.err.println("addCADMarkToPreDefinedShapes(): given:"+coord);
            String graphicType=coord.getGraphicType();
            float[] graphicData=coord.getGraphicData();
            if (graphicType !=null && graphicData != null
             && (coord.getRenderingIntent() == SpatialCoordinateAndImageReference.RenderingRequired
              || coord.getRenderingIntent() == SpatialCoordinateAndImageReference.RenderingOptional)
            ) {
                  if (graphicType.equals("POINT") && graphicData.length == 2) {
//System.err.println("addCADMarkToPreDefinedShapes(): adding POINT");
                        int x = (int)graphicData[0];
                        int y = (int)graphicData[1];
                        if (coord.getCoordinateCategory() == SpatialCoordinateAndImageReference.CoordinateCategoryMammoBreastDensity) {
//System.err.println("addCADMarkToPreDefinedShapes(): adding vertical cross for density");
                              DrawingUtilities.addVerticalCross(preDefinedShapes,x,y,crossSize,crossGap);
                        }
                        else {
//System.err.println("addCADMarkToPreDefinedShapes(): adding diagonal cross for calcifications");
                              DrawingUtilities.addDiagonalCross(preDefinedShapes,x,y,crossSize,crossGap);
                        }
                  }
                  else if (graphicType.equals("POLYLINE") && graphicData.length >= 4) {
//System.err.println("addCADMarkToPreDefinedShapes(): adding POLYLINE");
                        int x1 = (int)graphicData[0];
                        int y1 = (int)graphicData[1];
                        for (int p=2; p+1 < graphicData.length; p+=2) {
                              int x2 = (int)graphicData[p];
                              int y2 = (int)graphicData[p+1];
                              preDefinedShapes.add(new Line2D.Float(new Point(x1,y1),new Point(x2,y2)));
                              x1=x2;
                              y1=y2;
                        }
                  }
                  else if (graphicType.equals("ELLIPSE") && graphicData.length == 8) {
//System.err.println("addCADMarkToPreDefinedShapes(): adding ELLIPSE");
                        int xmajorstart = (int)graphicData[0];
                        int ymajorstart = (int)graphicData[1];
                        int xmajorend = (int)graphicData[2];
                        int ymajorend = (int)graphicData[3];
                        int xminorstart = (int)graphicData[4];
                        int yminorstart = (int)graphicData[5];
                        int xminorend = (int)graphicData[6];
                        int yminorend = (int)graphicData[7];
                        if (ymajorstart == ymajorend && xminorstart == xminorend) {
                              // we have an ellipse with major axis along x axis without rotation
                              preDefinedShapes.add(new Ellipse2D.Float(xmajorstart,yminorstart,xmajorend-xmajorstart,yminorend-yminorstart));
                        }
                        else if (xmajorstart == xmajorend && yminorstart == yminorend) {
                              // we have an ellipse with major axis along y axis without rotation
                              preDefinedShapes.add(new Ellipse2D.Float(xminorstart,ymajorstart,xminorend-xminorstart,ymajorend-ymajorstart));
                        }
                        else {
System.err.println("addCADMarkToPreDefinedShapes(): NOT adding ELLIPSE that is not parallel to row");
                        }
                  }
                  else {
System.err.println("addCADMarkToPreDefinedShapes(): NOT adding "+graphicType);
                  }
            }
      }

      class OurSingleImagePanel extends /*SingleImagePanelWithRegionDrawing*/ SingleImagePanelWithLineDrawing  {
            public OurSingleImagePanel(SourceImage sImg,EventContext typeOfPanelEventContext) {
                  super(sImg,typeOfPanelEventContext);
            }
            protected Shape makeNewDrawingShape(int tlhcX,int tlhcY,int width,int height) {
                  return new Ellipse2D.Double(tlhcX,tlhcY,width,height);
            }
      }
      
      protected SingleImagePanel makeNewImagePanel(SourceImage sImg,EventContext typeOfPanelEventContext) {
            return new OurSingleImagePanel(sImg,typeOfPanelEventContext);
      }
      
      /**
       * @param         filenames
       * @exception     Exception         if internal error
       */
00708       public void loadMultiPanelFromSpecifiedFiles(String filenames[]) throws Exception {
      
            int nFiles = filenames.length;
            
            SingleImagePanel imagePanels[] = new SingleImagePanel[nFiles];
            
            String orientations[][] = new String[nFiles][];
            String views[] = new String[nFiles];
            String lateralityViewAndModifiers[] = new String[nFiles];
            String lateralities[] = new String[nFiles];
            String dates[] = new String[nFiles];
            PixelSpacing spacing[] = new PixelSpacing[nFiles];
            int widths[] = new int[nFiles];
            int heights[] = new int[nFiles];
            double horizontalExtentsInMm[] = new double[nFiles];
            double verticalExtentsInMm[] = new double[nFiles];
            //int verticalCenter[] = new int[nFiles];
            int upperBounds[] = new int[nFiles];
            int lowerBounds[] = new int[nFiles];
            int leftBounds[] = new int[nFiles];
            int rightBounds[] = new int[nFiles];
            
            String rowOrientations[] = new String[nFiles];
            String columnOrientations[] = new String[nFiles];
            AffineTransform coordinateTransform[] = new AffineTransform[nFiles];
            
            int justifyRightOrLeft[] = new int[nFiles];
            int justifyTopOrBottom[] = new int[nFiles];
            
            String imageSOPClassUID[] = new String[nFiles];
            String imageSOPInstanceUID[] = new String[nFiles];
            String imageSourceSOPInstanceUID[] = new String[nFiles];

            HashMap eventContexts = new HashMap();
            
            double maximumHorizontalExtentInMm = 0;
            double maximumVerticalExtentInMm = 0;
            
            StructuredReport sr[] = new StructuredReport[nFiles];

            int nImages = 0;
            int nCAD = 0;
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            for (int f=0; f<nFiles; ++f) {
                  try {
                        String filename = filenames[f];
                        DicomInputStream distream = null;
                        InputStream in = classLoader.getResourceAsStream(filename);
                        if (in != null) {
                              distream = new DicomInputStream(in);
                        }
                        else {
                              distream = new DicomInputStream(new File(filename));
                        }
                        AttributeList list = new AttributeList();
                        list.read(distream);
                        if (list.isImage()) {
                              int i = nImages++;
System.err.println("IMAGE ["+i+"] is file "+f+" ("+filenames[f]+")");

                              imageSOPClassUID[i] = Attribute.getSingleStringValueOrNull(list,TagFromName.SOPClassUID);
//System.err.println("IMAGE ["+i+"] imageSOPClassUID="+imageSOPClassUID[i]);
                              imageSOPInstanceUID[i] = Attribute.getSingleStringValueOrNull(list,TagFromName.SOPInstanceUID);
//System.err.println("IMAGE ["+i+"] imageSOPInstanceUID="+imageSOPInstanceUID[i]);
                              imageSourceSOPInstanceUID[i] = getSingleSourceImageSequenceReferencedSOPInstanceUIDOrNull(list);
//System.err.println("IMAGE ["+i+"] imageSourceSOPInstanceUID="+imageSourceSOPInstanceUID[i]);

                              orientations[i] = getPatientOrientation(list);
//System.err.println("IMAGE ["+i+"] orientation="+(orientations[i] == null && orientations[i].length == 2 ? "" : (orientations[i][0] + " " + orientations[i][1])));
                              views[i] = getView(list);
//System.err.println("IMAGE ["+i+"] view="+views[i]);
                              lateralityViewAndModifiers[i] = getImageLateralityViewModifierAndViewModifier(list);
//System.err.println("IMAGE ["+i+"] lateralityViewAndModifiers="+lateralityViewAndModifiers[i]);
//System.err.println("File "+filenames[f]+": "+lateralityViewAndModifiers[i]);
                              lateralities[i] = getLaterality(list);
//System.err.println("IMAGE ["+i+"] laterality="+lateralities[i]);
                              dates[i] = getDate(list);
//System.err.println("IMAGE ["+i+"] date="+dates[i]);
                              spacing[i] = new PixelSpacing(list,null,false/*preferCalibratedValue - as per current IHE mammo profile, do NOT use Pixel Spacing to override*/,true/*useMagnificationFactorIfPresent*/);
//System.err.println("IMAGE ["+i+"] spacing="+spacing[i]);
                        
                              SourceImage sImg = new SourceImage(list);
                              BufferedImage img = sImg.getBufferedImage();
                        
                              widths[i] = sImg.getWidth();
                              heights[i] = sImg.getHeight();
                        
                              FlipAsNecessary flipper = new FlipAsNecessary(img,orientations[i],views[i],lateralities[i],doNotFlipOrRotate);
                              img = flipper.getImage();
                              rowOrientations[i] = flipper.getRowOrientation();
//System.err.println("IMAGE ["+i+"] rowOrientations="+rowOrientations[i]);
                              columnOrientations[i] = flipper.getColumnOrientation();
//System.err.println("IMAGE ["+i+"] columnOrientations="+columnOrientations[i]);
                              coordinateTransform[i] = flipper.getTransform();

                              if (doNotJustify) {
                                    justifyTopOrBottom[i] = 0;
                                    justifyTopOrBottom[i] = 0;
                              }
                              else {
                                    justifyRightOrLeft[i] = rowOrientations[i]    == null ? 0 : (rowOrientations[i].indexOf("P")    == -1 ? -1 : 1);  // justify to chest wall, which is P (-1 is left)
                                    justifyTopOrBottom[i] = columnOrientations[i] == null ? 0 : (columnOrientations[i].indexOf("F") == -1 ? 0 : -1);  // justify to axilla, which is !F (-1 is top)
                              }
                  
                              BreastExtentFinder extentFinder = new BreastExtentFinder(img,rowOrientations[i],sImg.getMinimum(),sImg.getMaximum(),sImg.isInverted(),spacing[i].getSpacing());
                              //BreastExtentFinder extentFinder = new BreastExtentFinder(img,spacing[i].getSpacing());

                              horizontalExtentsInMm[i] = extentFinder.getHorizontalExtentInMm();
                              verticalExtentsInMm[i] = extentFinder.getVerticalExtentInMm();
                              
                              if (horizontalExtentsInMm[i] > maximumHorizontalExtentInMm) {
                                    maximumHorizontalExtentInMm = horizontalExtentsInMm[i];
                              }
                              if (verticalExtentsInMm[i] > maximumVerticalExtentInMm) {
                                    maximumVerticalExtentInMm = verticalExtentsInMm[i];
                              }
                        
                              //verticalCenter[i] = extentFinder.getVerticalCenter();
                              upperBounds[i] = extentFinder.getUpperBound();
                              lowerBounds[i] = extentFinder.getLowerBound();
                              leftBounds [i] = extentFinder.getLeftBound();
                              rightBounds[i] = extentFinder.getRightBound();
                  
                              boolean shareVOIEventsInStudy = false;          // does not seem to work anyway, since adding VOITransform to panel constructor :(
                        
                              EventContext eventContext = null;
                              if (shareVOIEventsInStudy) {
                                    eventContext = (EventContext)(eventContexts.get(dates[i])); // use same context for studies with same date
                                    if (eventContext == null) {
                                          eventContext = new EventContext(dates[i]);
                                          eventContexts.put(dates[i],eventContext);
                                    }
                              }
                              if (eventContext == null) {
                                    eventContext = new EventContext(Integer.toString(i));
                              }
                        
                              SingleImagePanel imagePanel = makeNewImagePanel(sImg,eventContext);
                              boolean leftSide = justifyRightOrLeft[i] >= 0;
                              imagePanel.setDemographicAndTechniqueAnnotations(new MammoDemographicAndTechniqueAnnotations(list,!leftSide),"SansSerif",Font.PLAIN,10,Color.pink);
                              imagePanel.setOrientationAnnotations(
                                    new OrientationAnnotations(leftSide ? invertOrientation(rowOrientations[i]) : rowOrientations[i],columnOrientations[i]),
                                    "SansSerif",Font.PLAIN,20,Color.pink,leftSide);
                              imagePanel.setSideAndViewAnnotationString(
                                    MammoDemographicAndTechniqueAnnotations.getAbbreviationFromImageLateralityViewModifierAndViewModifierCodeSequenceAttributes(list),
                                    60/*verticalOffset*/,
                                    "SansSerif",Font.PLAIN,20,Color.pink,leftSide);
                              imagePanel.setShowZoomFactor(true,leftSide,spacing[i].getSpacing(),spacing[i].getDescription());
                              if (Attribute.getSingleStringValueOrEmptyString(list,TagFromName.VOILUTFunction).equals("SIGMOID")) {
                                    imagePanel.setVOIFunctionToLogistic();
                              }
                              imagePanels[i] = imagePanel;
                        }
                        else if (list.isSRDocument()) {
                              int i = nCAD++;
System.err.println("CAD ["+i+"] is file "+f+" ("+filenames[f]+")");
                              sr[i] = new StructuredReport(list);
                        }
                        else {
                              throw new DicomException("Unsupported SOP Class in file "+filenames[f]);
                        }
                  }
                  catch (Exception e) {   // FileNotFoundException,IOException,DicomException
                        e.printStackTrace(System.err);
                  }
            }

            int imagesPerRow = nImages >= 4 ? 4 : nImages;              // i.e., 1 -> 1, 2 -> 1, 4 -> 4, 5 -> 4, 8 -> 4
            int imagesPerCol = (nImages - 1) / imagesPerRow + 1;  // i.e., 1 -> 1, 2 -> 2, 4 -> 1, 5 -> 2, 8 -> 2
//System.err.println("nImages="+nImages);
//System.err.println("imagesPerRow="+imagesPerRow);
//System.err.println("imagesPerCol="+imagesPerCol);

            int singleWidth  = frameWidth /imagesPerRow;
            int singleHeight = frameHeight/imagesPerCol;
//System.err.println("singleWidth="+singleWidth);
//System.err.println("singleHeight="+singleHeight);

            if (nImages == 1 && singleWidth > singleHeight) {
                  singleWidth = singleWidth/2;              // use only half the screen for a single view and a landscape monitor
            }

            double requestedDisplaySpacing;
            {
                  double hSpacing = maximumHorizontalExtentInMm / singleWidth;
                  double vSpacing = maximumVerticalExtentInMm / singleHeight;
                  requestedDisplaySpacing = hSpacing < vSpacing ? vSpacing : hSpacing;
            }
//System.err.println("requestedDisplaySpacing="+requestedDisplaySpacing);
            
            int[] displayOrder = determineOrderForDisplay(nImages,views,lateralities,lateralityViewAndModifiers,dates);
            
            for (int i=0; i<nImages; ++i) {
                  DisplayedAreaSelection displayedAreaSelection = null;
                  if (forceFitEntireMatrixToWindow) {
                        displayedAreaSelection = new DisplayedAreaSelection(widths[i],heights[i],0,0,widths[i],heights[i],
                              true, // in case spacing was not supplied
                              0,0,0,justifyRightOrLeft[i],justifyTopOrBottom[i],false/*crop*/);
                  }
                  else {
                        displayedAreaSelection = new DisplayedAreaSelection(widths[i],heights[i],leftBounds[i],upperBounds[i],rightBounds[i],lowerBounds[i],
                              requestedDisplaySpacing <= 0.0/*fitToWindow*/,  // in case spacing was not supplied
                              requestedDisplaySpacing,spacing[i].getSpacing(),spacing[i].getSpacing(),justifyRightOrLeft[i],justifyTopOrBottom[i],false/*crop*/);
                  }
                  imagePanels[i].setDisplayedAreaSelection(displayedAreaSelection);
                  imagePanels[i].setPreTransformImageRelativeCoordinates(coordinateTransform[i]);           // null unless was flipped or rotated
            }
            
            Vector preDefinedShapes[] = new Vector[nImages];

            for (int i=0; i<nCAD; ++i) {
                  ContentItem root = (ContentItem)(sr[i].getRoot());
                  ContentItem findings = root.getNamedChild("DCM","111017");        // "CAD Processing and Findings Summary"
//System.err.println("CAD ["+i+"] findings="+findings);
                  if (findings != null) {
                        Vector coords = StructuredReport.findAllContainedSOPInstances(root,findings);
                        if (coords == null || coords.size() == 0) {
//System.err.println("CAD ["+i+"] contains no SCOORDS");
                        }
                        else {
                              Iterator si = coords.iterator();
                              while (si.hasNext()) {
                                    SpatialCoordinateAndImageReference coord = (SpatialCoordinateAndImageReference)(si.next());
//System.err.println("CAD ["+i+"] coord="+coord);
                                    if (coord != null && coord.getRenderingIntent() != SpatialCoordinateAndImageReference.RenderingForbidden) {
                                          String referencedUID = coord.getSOPInstanceUID();
//System.err.println("CAD ["+i+"] referencedUID="+referencedUID);
                                          if (referencedUID != null && referencedUID.length() > 0) {
                                                int referencedImageNumber = -1;
                                                for (int j=0; j<nImages; ++j) {
//System.err.println("CAD ["+i+"] checking image UID ="+imageSOPInstanceUID[j]);
//System.err.println("CAD ["+i+"] checking image source UID ="+imageSourceSOPInstanceUID[j]);
                                                      if (imageSOPInstanceUID[j] != null && imageSOPInstanceUID[j].equals(referencedUID)
                                                       || imageSourceSOPInstanceUID[j] != null && imageSourceSOPInstanceUID[j].equals(referencedUID)) {
//System.err.println("CAD ["+i+"] found match to referencedImageNumber="+j);
                                                            referencedImageNumber = j;
                                                            break;
                                                      }
                                                }
                                                if (referencedImageNumber != -1) {
//System.err.println("CAD ["+i+"] apply to Image # "+referencedImageNumber);
                                                      Vector shapes = preDefinedShapes[referencedImageNumber];
                                                      if (shapes == null) {
                                                            shapes = new Vector();
                                                            preDefinedShapes[referencedImageNumber] = shapes;
                                                      }
                                                      addCADMarkToPreDefinedShapes(coord,shapes);
                                                }
                                                else {
System.err.println("CAD ["+i+"] could not find match for referencedUID="+referencedUID);
                                                }
                                          }
                                    }
                              }
                        }
                  }
            }

            for (int i=0; i<nImages; ++i) {
                  if (preDefinedShapes[i] != null) {
//System.err.println("IMAGE ["+i+"] setting preDefinedShapes");
                        imagePanels[i].setPreDefinedShapes(preDefinedShapes[i]);
                  }
            }
            
            SingleImagePanel.deconstructAllSingleImagePanelsInContainer(multiPanel);
            multiPanel.removeAll();
            multiPanel.setLayout(new GridLayout(imagesPerCol,imagesPerRow));
            multiPanel.setBackground(Color.black);
            
            for (int x=0; x<imagesPerCol; ++x) {
                  for (int y=0; y<imagesPerRow; ++y) {
                        int i = x*imagesPerRow+y;
                        if (i < nImages) {
                              imagePanels[i].setPreferredSize(new Dimension(singleWidth,singleHeight));
                              multiPanel.add(imagePanels[displayOrder == null ? i : displayOrder[i]]);
                        }
                  }
            }
            frame.getContentPane().validate();
            frame.getContentPane().repaint();
      }
      
      /**
       * @param         frame
       * @exception     Exception         if internal error
       */
00995       public MammoImageViewer(JFrame frame) throws Exception {
            this.frame = frame;
            doCommonConstructorStuff();
      }
      
      /**
       * @param         frame
       * @param         filenames
       * @exception     Exception         if internal error
       */
01005       public MammoImageViewer(JFrame frame,String filenames[]) throws Exception {
            this.frame = frame;
            doCommonConstructorStuff();
            loadMultiPanelFromSpecifiedFiles(filenames);
      }
      
      /**
       * @param         filenames
       * @exception     Exception         if internal error
       */
01015       public MammoImageViewer(String filenames[]) throws Exception {
            frame = new ApplicationFrame();
            GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
            boolean isFullScreen = devices.length == 1 && devices[0].isFullScreenSupported();
            frame.setUndecorated(isFullScreen);
            frame.setResizable(!isFullScreen);
            if (isFullScreen) {
                  devices[0].setFullScreenWindow(frame);
                  frame.validate();
            }
            else {
                  frame.pack();
                  frame.setVisible(true);
            }
            doCommonConstructorStuff();
            loadMultiPanelFromSpecifiedFiles(filenames);
      }
      
      /**
       * @exception     Exception         if internal error
       */
01036       protected void doCommonConstructorStuff() throws Exception {
            Container content = frame.getContentPane();
            content.setLayout(new GridLayout(1,1));
            multiPanel = new JPanel();
            //multiPanel.setBackground(Color.black);
            frameWidth  = (int)frame.getWidth();
            frameHeight = (int)frame.getHeight();
            Dimension d = new Dimension(frameWidth,frameHeight);
            //multiPanel.setSize(d);
            multiPanel.setPreferredSize(d);
            multiPanel.setBackground(Color.black);
            content.add(multiPanel);
            //frame.pack();
            content.validate();
      }
      
      /**
       */
      public void clear() {
            SingleImagePanel.deconstructAllSingleImagePanelsInContainer(multiPanel);
            multiPanel.removeAll();
            frame.getContentPane().validate();
            frame.getContentPane().repaint();
      }

      /**
       * <p>The method to invoke the application.</p>
       *
       * @param   arg         a list of DICOM files which may contain mammography images or mammography CAD SR objects
       */
01066       public static void main(String arg[]) {
            try {
                  new MammoImageViewer(arg);
            }
            catch (Exception e) {
                  e.printStackTrace(System.err);
            }
      }
}


Generated by  Doxygen 1.6.0   Back to index