/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.dicom.viewer2d; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.Paint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.font.TextAttribute; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import javax.media.jai.Histogram; import javax.media.jai.LookupTableJAI; import javax.media.jai.PlanarImage; import javax.swing.Icon; import javax.vecmath.Vector3d; import org.dcm4che3.data.Tag; import org.weasis.core.api.explorer.model.TreeModelNode; import org.weasis.core.api.gui.util.ActionW; import org.weasis.core.api.gui.util.DecFormater; import org.weasis.core.api.gui.util.Filter; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.image.FlipOp; import org.weasis.core.api.image.ImageOpNode; import org.weasis.core.api.image.LutShape; import org.weasis.core.api.image.OpManager; import org.weasis.core.api.image.PseudoColorOp; import org.weasis.core.api.image.RotationOp; import org.weasis.core.api.image.WindowOp; import org.weasis.core.api.image.op.ByteLut; import org.weasis.core.api.image.util.Unit; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.MediaSeriesGroup; import org.weasis.core.api.media.data.Series; import org.weasis.core.api.media.data.TagView; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.util.FontTools; import org.weasis.core.api.util.StringUtil; import org.weasis.core.api.util.StringUtil.Suffix; import org.weasis.core.ui.editor.image.PixelInfo; import org.weasis.core.ui.editor.image.SynchData; import org.weasis.core.ui.editor.image.ViewButton; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.model.layer.LayerAnnotation; import org.weasis.core.ui.model.layer.LayerType; import org.weasis.core.ui.model.utils.imp.DefaultGraphicLabel; import org.weasis.core.ui.model.utils.imp.DefaultUUID; import org.weasis.dicom.codec.DicomImageElement; import org.weasis.dicom.codec.DicomSeries; import org.weasis.dicom.codec.PresentationStateReader; import org.weasis.dicom.codec.RejectedKOSpecialElement; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.codec.display.CornerDisplay; import org.weasis.dicom.codec.display.CornerInfoData; import org.weasis.dicom.codec.display.Modality; import org.weasis.dicom.codec.display.ModalityInfoData; import org.weasis.dicom.codec.display.ModalityView; import org.weasis.dicom.codec.geometry.ImageOrientation; import org.weasis.dicom.codec.geometry.ImageOrientation.Label; import org.weasis.dicom.explorer.DicomModel; /** * The Class InfoLayer. * * @author Nicolas Roduit */ public class InfoLayer extends DefaultUUID implements LayerAnnotation { private static final long serialVersionUID = 3234560631747133075L; private static final Color highlight = new Color(255, 153, 153); private final HashMap<String, Boolean> displayPreferences = new HashMap<>(); private Boolean visible = Boolean.TRUE; private static final int BORDER = 10; private final ViewCanvas<DicomImageElement> view2DPane; private PixelInfo pixelInfo = null; private final Rectangle pixelInfoBound; private final Rectangle preloadingProgressBound; private int border = BORDER; private double thickLength = 15.0; private boolean showBottomScale = true; private String name; public InfoLayer(ViewCanvas<DicomImageElement> view2DPane) { this.view2DPane = view2DPane; displayPreferences.put(ANNOTATIONS, true); displayPreferences.put(ANONYM_ANNOTATIONS, false); displayPreferences.put(IMAGE_ORIENTATION, true); displayPreferences.put(SCALE, true); displayPreferences.put(LUT, false); displayPreferences.put(PIXEL, true); displayPreferences.put(WINDOW_LEVEL, true); displayPreferences.put(ZOOM, true); displayPreferences.put(ROTATION, false); displayPreferences.put(FRAME, true); displayPreferences.put(PRELOADING_BAR, true); displayPreferences.put(MIN_ANNOTATIONS, false); this.pixelInfoBound = new Rectangle(); this.preloadingProgressBound = new Rectangle(); } @Override public LayerAnnotation getLayerCopy(ViewCanvas view2DPane) { InfoLayer layer = new InfoLayer(view2DPane); HashMap<String, Boolean> prefs = layer.displayPreferences; prefs.put(ANNOTATIONS, getDisplayPreferences(ANNOTATIONS)); prefs.put(ANONYM_ANNOTATIONS, getDisplayPreferences(ANONYM_ANNOTATIONS)); prefs.put(IMAGE_ORIENTATION, getDisplayPreferences(IMAGE_ORIENTATION)); prefs.put(SCALE, getDisplayPreferences(SCALE)); prefs.put(LUT, getDisplayPreferences(LUT)); prefs.put(PIXEL, getDisplayPreferences(PIXEL)); prefs.put(WINDOW_LEVEL, getDisplayPreferences(WINDOW_LEVEL)); prefs.put(ZOOM, getDisplayPreferences(ZOOM)); prefs.put(ROTATION, getDisplayPreferences(ROTATION)); prefs.put(FRAME, getDisplayPreferences(FRAME)); prefs.put(PRELOADING_BAR, getDisplayPreferences(PRELOADING_BAR)); prefs.put(MIN_ANNOTATIONS, getDisplayPreferences(MIN_ANNOTATIONS)); return layer; } @Override public Integer getBorder() { return border; } @Override public void setBorder(Integer border) { this.border = border; } @Override public Boolean isShowBottomScale() { return showBottomScale; } @Override public void setShowBottomScale(Boolean showBottomScale) { this.showBottomScale = showBottomScale; } @Override public Boolean getVisible() { return visible; } @Override public void setVisible(Boolean visible) { this.visible = Optional.ofNullable(visible).orElse(getType().getVisible()); } @Override public Integer getLevel() { return getType().getLevel(); } @Override public void setLevel(Integer i) { // Do Nothing } @Override public LayerType getType() { return LayerType.IMAGE_ANNOTATION; } @Override public void setType(LayerType type) { // Cannot change this type } @Override public void setName(String layerName) { this.name = layerName; } @Override public String getName() { return name; } @Override public String toString() { return Optional.ofNullable(getName()).orElse(getType().getDefaultName()); } @Override public Boolean getDisplayPreferences(String item) { return Optional.ofNullable(displayPreferences.get(item)).orElse(Boolean.FALSE); } @Override public Boolean setDisplayPreferencesValue(String displayItem, Boolean selected) { boolean selected2 = getDisplayPreferences(displayItem); displayPreferences.put(displayItem, selected); return !Objects.equals(selected, selected2); } @Override public Rectangle getPreloadingProgressBound() { return preloadingProgressBound; } @Override public Rectangle getPixelInfoBound() { return pixelInfoBound; } @Override public void setPixelInfo(PixelInfo pixelInfo) { this.pixelInfo = pixelInfo; } @Override public PixelInfo getPixelInfo() { return pixelInfo; } @Override public void paint(Graphics2D g2) { ImageElement image = view2DPane.getImage(); if (!visible || image == null) { return; } OpManager disOp = view2DPane.getDisplayOpManager(); ModalityInfoData modality; Modality mod = Modality.getModality(TagD.getTagValue(view2DPane.getSeries(), Tag.Modality, String.class)); modality = ModalityView.getModlatityInfos(mod); final Rectangle bound = view2DPane.getJComponent().getBounds(); float midx = bound.width / 2f; float midy = bound.height / 2f; thickLength = g2.getFont().getSize() * 1.5f; // font 10 => 15 pixels thickLength = thickLength < 5.0 ? 5.0 : thickLength; g2.setPaint(Color.BLACK); boolean hideMin = !getDisplayPreferences(MIN_ANNOTATIONS); final float fontHeight = FontTools.getAccurateFontHeight(g2); final float midfontHeight = fontHeight * FontTools.getMidFontHeightFactor(); float drawY = bound.height - border - 1.5f; // -1.5 for outline DicomImageElement dcm = null; if (image instanceof DicomImageElement) { dcm = (DicomImageElement) image; } if (!image.isReadable()) { String message = Messages.getString("InfoLayer.msg_not_read"); //$NON-NLS-1$ float y = midy; DefaultGraphicLabel.paintColorFontOutline(g2, message, midx - g2.getFontMetrics().stringWidth(message) / 2, y, Color.RED); String tsuid = TagD.getTagValue(image, Tag.TransferSyntaxUID, String.class); if (StringUtil.hasText(tsuid)) { tsuid = Messages.getString("InfoLayer.tsuid") + StringUtil.COLON_AND_SPACE + tsuid; //$NON-NLS-1$ y += fontHeight; DefaultGraphicLabel.paintColorFontOutline(g2, tsuid, midx - g2.getFontMetrics().stringWidth(tsuid) / 2, y, Color.RED); } String[] desc = image.getMediaReader().getReaderDescription(); if (desc != null) { for (String str : desc) { if (StringUtil.hasText(str)) { y += fontHeight; DefaultGraphicLabel.paintColorFontOutline(g2, str, midx - g2.getFontMetrics().stringWidth(str) / 2, y, Color.RED); } } } } if (image.isReadable() && getDisplayPreferences(SCALE)) { drawScale(g2, bound, fontHeight); } if (image.isReadable() && getDisplayPreferences(LUT) && hideMin) { drawLUT(g2, bound, midfontHeight); } if (dcm != null) { /* * IHE BIR RAD TF-­‐2: 4.16.4.2.2.5.8 * * Whether or not lossy compression has been applied, derived from Lossy Image 990 Compression (0028,2110), * and if so, the value of Lossy Image Compression Ratio (0028,2112) and Lossy Image Compression Method * (0028,2114), if present (as per FDA Guidance for the Submission Of Premarket Notifications for Medical * Image Management Devices, July 27, 2000). */ drawY -= fontHeight; if ("01".equals(TagD.getTagValue(dcm, Tag.LossyImageCompression))) { //$NON-NLS-1$ double[] rates = TagD.getTagValue(dcm, Tag.LossyImageCompressionRatio, double[].class); StringBuilder buf = new StringBuilder(Messages.getString("InfoLayer.lossy") + StringUtil.COLON_AND_SPACE);//$NON-NLS-1$ if (rates != null && rates.length > 0) { for (int i = 0; i < rates.length; i++) { if (i > 0) { buf.append(","); //$NON-NLS-1$ } buf.append(" ["); //$NON-NLS-1$ buf.append( Math.round(rates[i])); buf.append(":1"); //$NON-NLS-1$ buf.append(']'); } } else { String val = TagD.getTagValue(dcm, Tag.DerivationDescription, String.class); if (val != null) { buf.append(StringUtil.getTruncatedString(val, 25, Suffix.THREE_PTS)); } } DefaultGraphicLabel.paintColorFontOutline(g2, buf.toString(), border, drawY, Color.RED); drawY -= fontHeight; } Object key = dcm.getKey(); RejectedKOSpecialElement koElement = DicomModel.getRejectionKoSpecialElement(view2DPane.getSeries(), TagD.getTagValue(dcm, Tag.SOPInstanceUID, String.class), key instanceof Integer ? (Integer) key + 1 : null); if (koElement != null) { float y = midy; String message = "Not a valid image: " + koElement.getDocumentTitle(); //$NON-NLS-1$ DefaultGraphicLabel.paintColorFontOutline(g2, message, midx - g2.getFontMetrics().stringWidth(message) / 2, y, Color.RED); } } if (getDisplayPreferences(PIXEL) && hideMin) { StringBuilder sb = new StringBuilder(Messages.getString("InfoLayer.pixel")); //$NON-NLS-1$ sb.append(StringUtil.COLON_AND_SPACE); if (pixelInfo != null) { sb.append(pixelInfo.getPixelValueText()); sb.append(" - "); //$NON-NLS-1$ sb.append(pixelInfo.getPixelPositionText()); } String str = sb.toString(); DefaultGraphicLabel.paintFontOutline(g2, str, border, drawY - 1); drawY -= fontHeight + 2; pixelInfoBound.setBounds(border - 2, (int) drawY + 3, g2.getFontMetrics(view2DPane.getLayerFont()).stringWidth(str) + 4, (int) fontHeight + 2); } if (getDisplayPreferences(WINDOW_LEVEL) && hideMin) { StringBuilder sb = new StringBuilder(); Number window = (Number) disOp.getParamValue(WindowOp.OP_NAME, ActionW.WINDOW.cmd()); Number level = (Number) disOp.getParamValue(WindowOp.OP_NAME, ActionW.LEVEL.cmd()); boolean outside = false; if (window != null && level != null) { sb.append(ActionW.WINLEVEL.getTitle()); sb.append(StringUtil.COLON_AND_SPACE); sb.append(DecFormater.oneDecimal(window)); sb.append("/");//$NON-NLS-1$ sb.append(DecFormater.oneDecimal(level)); if (dcm != null) { PresentationStateReader prReader = (PresentationStateReader) view2DPane.getActionValue(PresentationStateReader.TAG_PR_READER); boolean pixelPadding = (Boolean) disOp.getParamValue(WindowOp.OP_NAME, ActionW.IMAGE_PIX_PADDING.cmd()); double minModLUT = image.getMinValue(prReader, pixelPadding); double maxModLUT = image.getMaxValue(prReader, pixelPadding); double minp = level.doubleValue() - window.doubleValue() / 2.0; double maxp = level.doubleValue() + window.doubleValue() / 2.0; if (minp > maxModLUT || maxp < minModLUT) { outside = true; sb.append(" - "); //$NON-NLS-1$ sb.append(Messages.getString("InfoLayer.msg_outside_levels")); //$NON-NLS-1$ } } } if (outside) { DefaultGraphicLabel.paintColorFontOutline(g2, sb.toString(), border, drawY, Color.RED); } else { DefaultGraphicLabel.paintFontOutline(g2, sb.toString(), border, drawY); } drawY -= fontHeight; } if (getDisplayPreferences(ZOOM) && hideMin) { DefaultGraphicLabel.paintFontOutline(g2, Messages.getString("InfoLayer.zoom") + StringUtil.COLON_AND_SPACE //$NON-NLS-1$ + DecFormater.percentTwoDecimal(view2DPane.getViewModel().getViewScale()), border, drawY); drawY -= fontHeight; } if (getDisplayPreferences(ROTATION) && hideMin) { DefaultGraphicLabel.paintFontOutline(g2, Messages.getString("InfoLayer.angle") + StringUtil.COLON_AND_SPACE //$NON-NLS-1$ + disOp.getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE) + " " //$NON-NLS-1$ + Messages.getString("InfoLayer.angle_symb"), //$NON-NLS-1$ border, drawY); drawY -= fontHeight; } if (getDisplayPreferences(FRAME) && hideMin) { String instance = StringUtil.COLON_AND_SPACE; if (dcm != null) { Integer inst = TagD.getTagValue(dcm, Tag.InstanceNumber, Integer.class); if (inst != null) { instance += "[" + inst + "] "; //$NON-NLS-1$ //$NON-NLS-2$ } } DefaultGraphicLabel.paintFontOutline(g2, Messages.getString("InfoLayer.frame") + instance + (view2DPane.getFrameIndex() + 1) + " / " //$NON-NLS-1$ //$NON-NLS-2$ + view2DPane.getSeries() .size((Filter<DicomImageElement>) view2DPane.getActionValue(ActionW.FILTERED_SERIES.cmd())), border, drawY); drawY -= fontHeight; Double imgProgression = (Double) view2DPane.getActionValue(ActionW.PROGRESSION.cmd()); if (imgProgression != null) { drawY -= 13; int pColor = (int) (510 * imgProgression); g2.setPaint(new Color(510 - pColor > 255 ? 255 : 510 - pColor, pColor > 255 ? 255 : pColor, 0)); g2.fillOval(border, (int) drawY, 13, 13); } } Point2D.Float[] positions = new Point2D.Float[4]; positions[3] = new Point2D.Float(border, drawY - 5); if (getDisplayPreferences(ANNOTATIONS) && dcm != null) { Series series = (Series) view2DPane.getSeries(); MediaSeriesGroup study = getParent(series, DicomModel.study); MediaSeriesGroup patient = getParent(series, DicomModel.patient); CornerInfoData corner = modality.getCornerInfo(CornerDisplay.TOP_LEFT); boolean anonymize = getDisplayPreferences(ANONYM_ANNOTATIONS); drawY = fontHeight; TagView[] infos = corner.getInfos(); for (int j = 0; j < infos.length; j++) { if (infos[j] != null) { if (hideMin || infos[j].containsTag(TagD.get(Tag.PatientName))) { for (TagW tag : infos[j].getTag()) { if (!anonymize || tag.getAnonymizationType() != 1) { Object value = getTagValue(tag, patient, study, series, dcm); if (value != null) { String str = tag.getFormattedTagValue(value, infos[j].getFormat()); if (StringUtil.hasText(str)) { DefaultGraphicLabel.paintFontOutline(g2, str, border, drawY); drawY += fontHeight; } break; } } } } } } positions[0] = new Point2D.Float(border, drawY - fontHeight + 5); corner = modality.getCornerInfo(CornerDisplay.TOP_RIGHT); drawY = fontHeight; infos = corner.getInfos(); for (int j = 0; j < infos.length; j++) { if (infos[j] != null) { if (hideMin || infos[j].containsTag(TagD.get(Tag.SeriesDate))) { Object value; for (TagW tag : infos[j].getTag()) { if (!anonymize || tag.getAnonymizationType() != 1) { value = getTagValue(tag, patient, study, series, dcm); if (value != null) { String str = tag.getFormattedTagValue(value, infos[j].getFormat()); if (StringUtil.hasText(str)) { DefaultGraphicLabel.paintFontOutline(g2, str, bound.width - g2.getFontMetrics().stringWidth(str) - (float) border, drawY); drawY += fontHeight; } break; } } } } } } positions[1] = new Point2D.Float(bound.width - border, drawY - fontHeight + 5); drawY = bound.height - border - 1.5f; // -1.5 for outline if (hideMin) { corner = modality.getCornerInfo(CornerDisplay.BOTTOM_RIGHT); infos = corner.getInfos(); for (int j = infos.length - 1; j >= 0; j--) { if (infos[j] != null) { Object value; for (TagW tag : infos[j].getTag()) { if (!anonymize || tag.getAnonymizationType() != 1) { value = getTagValue(tag, patient, study, series, dcm); if (value != null) { String str = tag.getFormattedTagValue(value, infos[j].getFormat()); if (StringUtil.hasText(str)) { DefaultGraphicLabel.paintFontOutline(g2, str, bound.width - g2.getFontMetrics().stringWidth(str) - border, drawY); drawY -= fontHeight; } break; } } } } } drawY -= 5; drawSeriesInMemoryState(g2, view2DPane.getSeries(), bound.width - border, (int) (drawY)); } positions[2] = new Point2D.Float(bound.width - border, drawY - 5); // Boolean synchLink = (Boolean) view2DPane.getActionValue(ActionW.SYNCH_LINK); // String str = synchLink != null && synchLink ? "linked" : "unlinked"; //$NON-NLS-1$ //$NON-NLS-2$ // paintFontOutline(g2, str, bound.width - g2.getFontMetrics().stringWidth(str) - BORDER, drawY); double[] v = TagD.getTagValue(dcm, Tag.ImageOrientationPatient, double[].class); Integer columns = TagD.getTagValue(dcm, Tag.Columns, Integer.class); Integer rows = TagD.getTagValue(dcm, Tag.Rows, Integer.class); StringBuilder orientation = new StringBuilder(mod.name()); if (rows != null && columns != null) { orientation.append(" (");//$NON-NLS-1$ orientation.append(columns); orientation.append("x");//$NON-NLS-1$ orientation.append(rows); orientation.append(")");//$NON-NLS-1$ } String colLeft = null; String rowTop = null; if (getDisplayPreferences(IMAGE_ORIENTATION) && v != null && v.length == 6) { orientation.append(" - ");//$NON-NLS-1$ Label imgOrientation = ImageOrientation.makeImageOrientationLabelFromImageOrientationPatient(v[0], v[1], v[2], v[3], v[4], v[5]); orientation.append(imgOrientation); // Set the opposite vector direction (otherwise label should be placed in mid-right and mid-bottom Vector3d vr = new Vector3d(-v[0], -v[1], -v[2]); Vector3d vc = new Vector3d(-v[3], -v[4], -v[5]); Integer rotationAngle = (Integer) disOp.getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE); if (rotationAngle != null && rotationAngle != 0) { double rad = Math.toRadians(rotationAngle); double[] normal = ImageOrientation.computeNormalVectorOfPlan(v); if (normal != null && normal.length == 3) { Vector3d result = new Vector3d(0.0, 0.0, 0.0); Vector3d axis = new Vector3d(normal); rotate(vr, axis, -rad, result); vr = result; result = new Vector3d(0.0, 0.0, 0.0); rotate(vc, axis, -rad, result); vc = result; } } if (JMVUtils.getNULLtoFalse(disOp.getParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP))) { vr.x = -vr.x; vr.y = -vr.y; vr.z = -vr.z; } colLeft = ImageOrientation.makePatientOrientationFromPatientRelativeDirectionCosine(vr.x, vr.y, vr.z); rowTop = ImageOrientation.makePatientOrientationFromPatientRelativeDirectionCosine(vc.x, vc.y, vc.z); } else { String[] po = TagD.getTagValue(dcm, Tag.PatientOrientation, String[].class); Integer rotationAngle = (Integer) disOp.getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE); if (po != null && po.length == 2 && (rotationAngle == null || rotationAngle == 0)) { // Do not display if there is a transformation if (JMVUtils.getNULLtoFalse(disOp.getParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP))) { colLeft = po[0]; } else { StringBuilder buf = new StringBuilder(); for (char c : po[0].toCharArray()) { buf.append(getImageOrientationOposite(c)); } colLeft = buf.toString(); } StringBuilder buf = new StringBuilder(); for (char c : po[1].toCharArray()) { buf.append(getImageOrientationOposite(c)); } rowTop = buf.toString(); } } if (rowTop != null && colLeft != null) { if (colLeft.length() < 1) { colLeft = " "; //$NON-NLS-1$ } if (rowTop.length() < 1) { rowTop = " "; //$NON-NLS-1$ } Font oldFont = g2.getFont(); Font bigFont = oldFont.deriveFont(oldFont.getSize() + 5.0f); g2.setFont(bigFont); Map<TextAttribute, Object> map = new HashMap<>(1); map.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB); String fistLetter = rowTop.substring(0, 1); DefaultGraphicLabel.paintColorFontOutline(g2, fistLetter, midx, fontHeight + 5f, highlight); int shiftx = g2.getFontMetrics().stringWidth(fistLetter); Font subscriptFont = bigFont.deriveFont(map); if (rowTop.length() > 1) { g2.setFont(subscriptFont); DefaultGraphicLabel.paintColorFontOutline(g2, rowTop.substring(1, rowTop.length()), midx + shiftx, fontHeight + 5f, highlight); g2.setFont(bigFont); } DefaultGraphicLabel.paintColorFontOutline(g2, colLeft.substring(0, 1), (float) (border + thickLength), midy + fontHeight / 2.0f, highlight); if (colLeft.length() > 1) { g2.setFont(subscriptFont); DefaultGraphicLabel.paintColorFontOutline(g2, colLeft.substring(1, colLeft.length()), (float) (border + thickLength + shiftx), midy + fontHeight / 2.0f, highlight); } g2.setFont(oldFont); } DefaultGraphicLabel.paintFontOutline(g2, orientation.toString(), border, bound.height - border - 1.5f); // -1.5 // for // outline } else { positions[0] = new Point2D.Float(border, border); positions[1] = new Point2D.Float(bound.width - border, border); positions[2] = new Point2D.Float(bound.width - border, bound.height - border); } drawExtendedActions(g2, positions); } private MediaSeriesGroup getParent(Series series, TreeModelNode node) { if (series != null) { Object tagValue = series.getTagValue(TagW.ExplorerModel); if (tagValue instanceof DicomModel) { return ((DicomModel) tagValue).getParent(series, node); } } return null; } private static void rotate(Vector3d vSrc, Vector3d axis, double angle, Vector3d vDst) { axis.normalize(); vDst.x = axis.x * (axis.x * vSrc.x + axis.y * vSrc.y + axis.z * vSrc.z) * (1 - Math.cos(angle)) + vSrc.x * Math.cos(angle) + (-axis.z * vSrc.y + axis.y * vSrc.z) * Math.sin(angle); vDst.y = axis.y * (axis.x * vSrc.x + axis.y * vSrc.y + axis.z * vSrc.z) * (1 - Math.cos(angle)) + vSrc.y * Math.cos(angle) + (axis.z * vSrc.x - axis.x * vSrc.z) * Math.sin(angle); vDst.z = axis.z * (axis.x * vSrc.x + axis.y * vSrc.y + axis.z * vSrc.z) * (1 - Math.cos(angle)) + vSrc.z * Math.cos(angle) + (-axis.y * vSrc.x + axis.x * vSrc.y) * Math.sin(angle); } private void drawSeriesInMemoryState(Graphics2D g2d, MediaSeries series, int x, int y) { if (getDisplayPreferences(PRELOADING_BAR) && series instanceof DicomSeries) { DicomSeries s = (DicomSeries) series; boolean[] list = s.getImageInMemoryList(); int length = list.length > 120 ? 120 : list.length; x -= length; preloadingProgressBound.setBounds(x - 1, y - 1, length + 1, 5 + 1); g2d.fillRect(x, y, length, 5); g2d.setPaint(Color.BLACK); g2d.draw(preloadingProgressBound); double factorResize = list.length > 120 ? 120.0 / list.length : 1; for (int i = 0; i < list.length; i++) { if (!list[i]) { int val = x + (int) (i * factorResize); g2d.drawLine(val, y, val, y + 3); } } } } private Object getTagValue(TagW tag, MediaSeriesGroup patient, MediaSeriesGroup study, Series series, ImageElement image) { if (image.containTagKey(tag)) { return image.getTagValue(tag); } if (series.containTagKey(tag)) { return series.getTagValue(tag); } if (study != null && study.containTagKey(tag)) { return study.getTagValue(tag); } if (patient != null && patient.containTagKey(tag)) { return patient.getTagValue(tag); } return null; } public Rectangle2D getOutLine(Line2D l) { Rectangle2D r = l.getBounds2D(); r.setFrame(r.getX() - 1.0, r.getY() - 1.0, r.getWidth() + 2.0, r.getHeight() + 2.0); return r; } public void drawLUT(Graphics2D g2, Rectangle bound, float midfontHeight) { OpManager disOp = view2DPane.getDisplayOpManager(); ImageOpNode pseudoColorOp = disOp.getNode(PseudoColorOp.OP_NAME); ByteLut lut = null; if (pseudoColorOp != null) { lut = (ByteLut) pseudoColorOp.getParam(PseudoColorOp.P_LUT); } if (lut != null && bound.height > 350) { if (lut.getLutTable() == null) { lut = ByteLut.grayLUT; } byte[][] table = JMVUtils.getNULLtoFalse(pseudoColorOp.getParam(PseudoColorOp.P_LUT_INVERSE)) ? lut.getInvertedLutTable() : lut.getLutTable(); float length = table[0].length; int width = 0; for (ViewButton b : view2DPane.getViewButtons()) { if (b.isVisible() && b.getPosition() == GridBagConstraints.EAST) { int w = b.getIcon().getIconWidth() + 5; if (w > width) { width = w; } } } float x = bound.width - 30f - width; float y = bound.height / 2f - length / 2f; g2.setPaint(Color.BLACK); Rectangle2D.Float rect = new Rectangle2D.Float(x - 11f, y - 2f, 12f, 2f); g2.draw(rect); int separation = 4; float step = length / separation; for (int i = 1; i < separation; i++) { float posY = y + i * step; rect.setRect(x - 6f, posY - 1f, 7f, 2f); g2.draw(rect); } rect.setRect(x - 11f, y + length, 12f, 2f); g2.draw(rect); rect.setRect(x - 2f, y - 2f, 23f, length + 4f); g2.draw(rect); g2.setPaint(Color.WHITE); Line2D.Float line = new Line2D.Float(x - 10f, y - 1f, x - 1f, y - 1f); g2.draw(line); Double ww = (Double) disOp.getParamValue(WindowOp.OP_NAME, ActionW.WINDOW.cmd()); Double wl = (Double) disOp.getParamValue(WindowOp.OP_NAME, ActionW.LEVEL.cmd()); if (ww != null && wl != null) { int stepWindow = (int) (ww / separation); int firstlevel = (int) (wl - stepWindow * 2.0); String str = Integer.toString(firstlevel); // $NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2, str, x - g2.getFontMetrics().stringWidth(str) - 12f, y + midfontHeight); for (int i = 1; i < separation; i++) { float posY = y + i * step; line.setLine(x - 5f, posY, x - 1f, posY); g2.draw(line); str = Integer.toString(firstlevel + i * stepWindow); // $NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2, str, x - g2.getFontMetrics().stringWidth(str) - 7, posY + midfontHeight); } line.setLine(x - 10f, y + length + 1f, x - 1f, y + length + 1f); g2.draw(line); str = Integer.toString(firstlevel + 4 * stepWindow); // $NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2, str, x - g2.getFontMetrics().stringWidth(str) - 12, y + length + midfontHeight); rect.setRect(x - 1f, y - 1f, 21f, length + 2f); g2.draw(rect); } for (int k = 0; k < length; k++) { g2.setPaint(new Color(table[0][k] & 0xff, table[1][k] & 0xff, table[2][k] & 0xff)); rect.setRect(x, y + k, 19f, 1f); g2.draw(rect); } } } // TODO must be implemented as component of the layout (must inherit Jcomponent and implements SeriesViewerListener) public void drawLUTgraph(Graphics2D g2d, Rectangle viewPaneBound, float midfontHeight) { final Paint oldPaint = g2d.getPaint(); final RenderingHints oldRenderingHints = g2d.getRenderingHints(); final Stroke oldStroke = g2d.getStroke(); // ///////////////////////////////////////////////////////////////////////////////////// final DicomImageElement image = view2DPane.getImage(); // Min/Max out Lut pixel values defined as unsigned 8 bits data final int minOutputValue = 0; final int maxOutputValue = 255; OpManager dispOp = view2DPane.getDisplayOpManager(); ImageOpNode wlOp = dispOp.getNode(ImageOpNode.Param.NAME); if (wlOp == null) { return; } boolean pixelPadding = (Boolean) wlOp.getParam(ActionW.IMAGE_PIX_PADDING.cmd()); final double window = (Double) wlOp.getParam(ActionW.WINDOW.cmd()); final double level = (Double) wlOp.getParam(ActionW.LEVEL.cmd()); final double lowLevel = Math.round(level - window / 2); final double highLevel = Math.round(level + window / 2); PresentationStateReader prReader = (PresentationStateReader) view2DPane.getActionValue(PresentationStateReader.TAG_PR_READER); int lowInputValue = (int) (image.getMinValue(prReader, pixelPadding) < lowLevel ? lowLevel : image.getMinValue(prReader, pixelPadding)); int highInputValue = (int) (image.getMaxValue(prReader, pixelPadding) > highLevel ? highLevel : image.getMaxValue(prReader, pixelPadding)); final boolean inverseLut = (Boolean) wlOp.getParam(ActionW.INVERT_LUT.cmd()); LutShape lutShape = (LutShape) wlOp.getParam(ActionW.LUT_SHAPE.cmd()); LookupTableJAI lookup = image.getVOILookup(prReader, window, level, null, null, lutShape, true, pixelPadding); // Note : when fillLutOutside argument is true lookupTable returned is full range allocated final byte[] fullRangeVoiLUT = lookup.getByteData(0); final int lutInputRange = fullRangeVoiLUT.length - 1; final int minInputValue = lookup.getOffset(); final int maxInputValue = minInputValue + lutInputRange; lowInputValue = (lowInputValue < minInputValue) ? minInputValue : lowInputValue; highInputValue = (highInputValue > maxInputValue) ? maxInputValue : highInputValue; // ///////////////////////////////////////////////////////////////////////////////////// // Size in pixel of Input/Ouput LUT Range final float xAxisCoordinateSystemRange = 511; final float yAxisCoordinateSystemRange = 255; // Offset in pixel for the Left/Down side of the coordinate system boolean isMinInputValueNegative = minInputValue < 0; final float xOffsetCoordinateSystemOrigin = isMinInputValueNegative ? (-xAxisCoordinateSystemRange / 2f) : -5f; final float yOffsetCoordinateSystemOrigin = -5f; final float xAxisCoordinateSystemMinValue = isMinInputValueNegative ? (-xAxisCoordinateSystemRange / 2f) : 0; final float xAxisCoordinateSystemMaxValue = xAxisCoordinateSystemRange + xOffsetCoordinateSystemOrigin; // TODO - better to use a scaleTransform instead of scale ratio with many variables!!! final float xAxisRescaleRatio = xAxisCoordinateSystemRange / lutInputRange; final float yAxisRescaleRatio = yAxisCoordinateSystemRange / maxOutputValue; // ///////////////////////////////////////////////////////////////////////////////////// // Coordinate system arrows and lines defined in a CW system final Path2D upArrow = new Path2D.Float(); upArrow.moveTo(0, 0); upArrow.lineTo(0, 3); upArrow.lineTo(-3, 3); upArrow.lineTo(0, 10); upArrow.lineTo(3, 3); upArrow.lineTo(0, 3); final Path2D rightArrow = (Path2D) upArrow.clone(); rightArrow.transform(AffineTransform.getQuadrantRotateInstance(3)); final Shape upArrowCoordinateSystemPath = AffineTransform.getTranslateInstance(0, yAxisCoordinateSystemRange + 1).createTransformedShape(upArrow); final Shape rightArrowCoordinateSystemPath = AffineTransform .getTranslateInstance(xAxisCoordinateSystemMaxValue + 1, 0).createTransformedShape(rightArrow); final Path2D coordinateSystemPath = new Path2D.Float(); coordinateSystemPath.moveTo(0, yOffsetCoordinateSystemOrigin); coordinateSystemPath.lineTo(0, yAxisCoordinateSystemRange); coordinateSystemPath.append(upArrowCoordinateSystemPath, false); coordinateSystemPath.moveTo(xOffsetCoordinateSystemOrigin, 0); coordinateSystemPath.lineTo(xAxisCoordinateSystemMaxValue, 0); coordinateSystemPath.append(rightArrowCoordinateSystemPath, false); // ///////////////////////////////////////////////////////////////////////////////// // LUT graph bounding rectangle defined in a CCW system final float lutGraphMargin = 30f; final float lutGraphWidth = (float) coordinateSystemPath.getBounds2D().getWidth() + 2f * lutGraphMargin; final float lutGraphHeight = (float) coordinateSystemPath.getBounds2D().getHeight() + 2f * lutGraphMargin; final Path2D lutGraphBoundingRect = new Path2D.Float(new Rectangle2D.Float(0, 0, lutGraphWidth, lutGraphHeight)); // ///////////////////////////////////////////////////////////////////////////////// // Selected LUT defined in a CW system with the full range input. // Note : two path are distinct from the inside and ouside range part of lowInput and highInput values final Path2D insideRangeLutPath = new Path2D.Float(); final Path2D outsideRangeLutPath = new Path2D.Float(); boolean isOutsideRangeLutPathMoveToDefined = false; boolean isRealValuesLutPathMoveToDefined = false; for (int i = 0; i < fullRangeVoiLUT.length; i++) { int xVal = Math.round((minInputValue + i) * xAxisRescaleRatio); int yVal = fullRangeVoiLUT[i] & 0x000000FF; // Mask because byte is signed by default yVal = Math.round(yAxisRescaleRatio * (inverseLut ? (maxOutputValue - yVal) : yVal)); // if (yVal == maxOutputValue || yVal == minOutputValue) { // isRealValuesLutPathMoveToDefined = false; // isOutsideRangeLutPathMoveToDefined = false; // } else { if ((minInputValue + i) < lowInputValue || (minInputValue + i) > highInputValue) { if (isOutsideRangeLutPathMoveToDefined) { outsideRangeLutPath.lineTo(xVal, yVal); isRealValuesLutPathMoveToDefined = false; } else { outsideRangeLutPath.moveTo(xVal, yVal); isOutsideRangeLutPathMoveToDefined = true; } } else { if (isRealValuesLutPathMoveToDefined) { insideRangeLutPath.lineTo(xVal, yVal); isOutsideRangeLutPathMoveToDefined = false; } else { insideRangeLutPath.moveTo(xVal, yVal); isRealValuesLutPathMoveToDefined = true; } } } // ///////////////////////////////////////////////////////////////////////////////// // Path of Interest defined in a CW system final Path2D xAxisMaxOutValueLine = new Path2D.Float(); xAxisMaxOutValueLine.moveTo(xAxisCoordinateSystemMinValue, yAxisCoordinateSystemRange); xAxisMaxOutValueLine.lineTo(xAxisCoordinateSystemMaxValue, yAxisCoordinateSystemRange); int xLowLevel = (int) Math.round(xAxisRescaleRatio * lowLevel); int xHighLevel = (int) Math.round(xAxisRescaleRatio * highLevel); int xLevel = (int) Math.round(xAxisRescaleRatio * level); final Path2D yAxisOnLowLevelLine = new Path2D.Float(); if (lowLevel >= lowInputValue) { yAxisOnLowLevelLine.moveTo(xLowLevel, 0); yAxisOnLowLevelLine.lineTo(xLowLevel, yAxisCoordinateSystemRange); } final Path2D yAxisOnHighLevelLine = new Path2D.Float(); if (highLevel <= highInputValue) { yAxisOnHighLevelLine.moveTo(xHighLevel, 0); yAxisOnHighLevelLine.lineTo(xHighLevel, yAxisCoordinateSystemRange); } // final Path2D yAxisOnLevelLine = new Path2D.Float(); // yAxisOnLevelLine.moveTo(xLevel, 0); // yAxisOnLevelLine.lineTo(xLevel, yAxisCoordinateSystemRange); // // final Path2D xAxisOnLevelLine = new Path2D.Float(); // int yLevel = lookup.lookup(0, (int) level) & 0x000000FF; // yLevel = Math.round(yAxisRescaleRatio * (inverseLut ? (maxOutputValue - yLevel) : yLevel)); // xAxisOnLevelLine.moveTo(0, yLevel); // xAxisOnLevelLine.lineTo(xLevel, yLevel); // if (((int) level >= 0 && (int) level < fullRangeVoiLUT.length)) { // int yLevel = fullRangeVoiLUT[(int) level] & 0x000000FF; // yLevel = Math.round(yAxisRescaleRatio * (inverseLut ? (maxOutputValue - yLevel) : yLevel)); // // xAxisOnLevelLine.moveTo(0, yLevel); // xAxisOnLevelLine.lineTo(xLevel, yLevel); // } final Path2D xAxisOnMinValueLine = new Path2D.Float(); int xMinVal = lowInputValue; // int yMinVal = fullRangeVoiLUT[lowInputValue] & 0x000000FF; int yMinVal = lookup.lookup(0, lowInputValue) & 0x000000FF; yMinVal = inverseLut ? maxOutputValue - yMinVal : yMinVal; if (yMinVal != minOutputValue && yMinVal != maxOutputValue) { xAxisOnMinValueLine.moveTo(0, Math.round(yAxisRescaleRatio * yMinVal)); xAxisOnMinValueLine.lineTo(Math.round(xAxisRescaleRatio * xMinVal), Math.round(yAxisRescaleRatio * yMinVal)); } final Path2D yAxisOnMinValueLine = new Path2D.Float(); // if (xMinVal != xLowLevel && xMinVal != xLevel && xMinVal != xHighLevel) { yAxisOnMinValueLine.moveTo(Math.round(xAxisRescaleRatio * xMinVal), 0); yAxisOnMinValueLine.lineTo(Math.round(xAxisRescaleRatio * xMinVal), Math.round(yAxisRescaleRatio * yMinVal)); // } int xMaxVal = highInputValue; // int yMaxVal = fullRangeVoiLUT[highInputValue] & 0x000000FF; int yMaxVal = lookup.lookup(0, highInputValue) & 0x000000FF; yMaxVal = inverseLut ? maxOutputValue - yMaxVal : yMaxVal; final Path2D xAxisOnMaxValueLine = new Path2D.Float(); if (yMaxVal != minOutputValue && yMaxVal != maxOutputValue) { xAxisOnMaxValueLine.moveTo(0, Math.round(yAxisRescaleRatio * yMaxVal)); xAxisOnMaxValueLine.lineTo(Math.round(xAxisRescaleRatio * xMaxVal), Math.round(yAxisRescaleRatio * yMaxVal)); } final Path2D yAxisOnMaxValue = new Path2D.Float(); // if (xMaxVal != xLowLevel && xMaxVal != xLevel && xMaxVal != xHighLevel) { yAxisOnMaxValue.moveTo(Math.round(xAxisRescaleRatio * xMaxVal), 0); yAxisOnMaxValue.lineTo(Math.round(xAxisRescaleRatio * xMaxVal), Math.round(yAxisRescaleRatio * yMaxVal)); // } // ///////////////////////////////////////////////////////////////////////////////// // ViewPane transform in a CCW system final float lutGraphXPos = (viewPaneBound.width - lutGraphWidth) / 2; final float lutGraphYPos = (viewPaneBound.height - lutGraphHeight) / 2; final AffineTransform lutGraphViewPaneTranslate = AffineTransform.getTranslateInstance(lutGraphXPos, lutGraphYPos); final AffineTransform coordinateSystemViewPaneTransform = AffineTransform.getTranslateInstance(-xOffsetCoordinateSystemOrigin, coordinateSystemPath.getBounds2D().getHeight() + yOffsetCoordinateSystemOrigin); coordinateSystemViewPaneTransform.translate(lutGraphMargin, lutGraphMargin); coordinateSystemViewPaneTransform.concatenate(lutGraphViewPaneTranslate); final AffineTransform flipVerticalTransform = AffineTransform.getScaleInstance(1, -1); // Note : this flipVertical transform has to be used when drawing is defined in a CW system knowing that // graphics2D coordinate system is CCW coordinateSystemViewPaneTransform.concatenate(flipVerticalTransform); // ///////////////////////////////////////////////////////////////////////////////// // Transform all path lutGraphBoundingRect.transform(lutGraphViewPaneTranslate); coordinateSystemPath.transform(coordinateSystemViewPaneTransform); xAxisMaxOutValueLine.transform(coordinateSystemViewPaneTransform); insideRangeLutPath.transform(coordinateSystemViewPaneTransform); outsideRangeLutPath.transform(coordinateSystemViewPaneTransform); xAxisOnMinValueLine.transform(coordinateSystemViewPaneTransform); yAxisOnMinValueLine.transform(coordinateSystemViewPaneTransform); xAxisOnMaxValueLine.transform(coordinateSystemViewPaneTransform); yAxisOnMaxValue.transform(coordinateSystemViewPaneTransform); yAxisOnLowLevelLine.transform(coordinateSystemViewPaneTransform); yAxisOnHighLevelLine.transform(coordinateSystemViewPaneTransform); // yAxisOnLevelLine.transform(coordinateSystemViewPaneTransform); // xAxisOnLevelLine.transform(coordinateSystemViewPaneTransform); // ///////////////////////////////////////////////////////////////////////////////// // Draw Background float alphaReal = 0.75f; // [0.0 ; 1.0] int alphaMask = 0x00FFFFFF | (Math.round(alphaReal * 255) << 24); g2d.setPaint(new Color(Color.GRAY.getRGB() & alphaMask, true)); g2d.fill(lutGraphBoundingRect); // Handles background transparency inside bounding rectangle g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // ///////////////////////////////////////////////////////////////////////////////// // Draw Histogram boolean showHistogram = true; Histogram histogram = showHistogram ? image.getHistogram(view2DPane.getSourceImage(), null, pixelPadding) : null; if (histogram != null) { boolean logarithmRescale = true; final int[] histoData = histogram.getBins(0); int histoCount = Integer.MIN_VALUE; for (int c : histoData) { histoCount = Math.max(histoCount, c); } double maxHistoCount = logarithmRescale ? Math.log1p(histoCount) : histoCount; final float yAxisHistoRescaleRatio = (float) (yAxisCoordinateSystemRange / maxHistoCount); // final float xAxisHistoRescaleRatio = xAxisCoordinateSystemRange / histogram.getNumBins(0); final float xAxisHistoRescaleRatio = xAxisCoordinateSystemRange / lutInputRange; // assert histogram.getNumBins(0) == lutInputRange; final Point2D pt0 = new Point2D.Float(); final Point2D pt1 = new Point2D.Float(); g2d.setPaint(Color.DARK_GRAY); g2d.setStroke(new BasicStroke(1.0F)); // for (int i = 0; i < histogram.getNumBins(0); i++) { for (int i = 0; i < lutInputRange; i++) { double xVal = (minInputValue + i) * xAxisHistoRescaleRatio; double yVal = (logarithmRescale ? Math.log1p(histoData[i]) : (double) histoData[i]) * yAxisHistoRescaleRatio; pt0.setLocation(xVal, 0); pt1.setLocation(xVal, yVal); coordinateSystemViewPaneTransform.transform(pt0, pt0); coordinateSystemViewPaneTransform.transform(pt1, pt1); g2d.drawLine((int) Math.round(pt0.getX()), (int) Math.round(pt0.getY()), (int) Math.round(pt1.getX()), (int) Math.round(pt1.getY())); } } // ///////////////////////////////////////////////////////////////////////////////// // Draw Path g2d.setPaint(Color.ORANGE); g2d.setStroke(new BasicStroke(2.0F)); g2d.draw(lutGraphBoundingRect); g2d.setPaint(Color.RED); g2d.setStroke(new BasicStroke(1.0F)); g2d.draw(coordinateSystemPath); g2d.setStroke( new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10f, new float[] { 5.0f }, 0.0f)); g2d.draw(xAxisMaxOutValueLine); g2d.setPaint(Color.BLUE); g2d.setStroke(new BasicStroke(1.0F)); g2d.draw(insideRangeLutPath); g2d.setStroke( new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 5f, new float[] { 5.0f }, 0.0f)); g2d.draw(outsideRangeLutPath); g2d.draw(xAxisOnMinValueLine); g2d.draw(yAxisOnMinValueLine); g2d.draw(xAxisOnMaxValueLine); g2d.draw(yAxisOnMaxValue); g2d.setPaint(Color.CYAN); g2d.draw(yAxisOnLowLevelLine); g2d.draw(yAxisOnHighLevelLine); // g2d.draw(yAxisOnLevelLine); // g2d.draw(xAxisOnLevelLine); // ///////////////////////////////////////////////////////////////////////////////// // Draw Strings String str = Integer.toString(maxOutputValue); int strWidth = g2d.getFontMetrics().stringWidth(str); float xStrPos = -strWidth - 8; float yStrPos = Math.round(maxOutputValue * yAxisRescaleRatio) - midfontHeight; Point2D ptStr = new Point2D.Float(xStrPos, yStrPos); coordinateSystemViewPaneTransform.transform(ptStr, ptStr); DefaultGraphicLabel.paintFontOutline(g2d, str, Math.round(ptStr.getX()), Math.round(ptStr.getY())); str = Integer.toString(minOutputValue); strWidth = g2d.getFontMetrics().stringWidth(str); xStrPos = -strWidth - 8; yStrPos = Math.round(minOutputValue * yAxisRescaleRatio) - midfontHeight; ptStr.setLocation(xStrPos, yStrPos); coordinateSystemViewPaneTransform.transform(ptStr, ptStr); DefaultGraphicLabel.paintFontOutline(g2d, str, Math.round(ptStr.getX()), Math.round(ptStr.getY())); str = Integer.toString(yMinVal); strWidth = g2d.getFontMetrics().stringWidth(str); xStrPos = -strWidth - 8; yStrPos = Math.round(yAxisRescaleRatio * yMinVal) - midfontHeight; ptStr.setLocation(xStrPos, yStrPos); coordinateSystemViewPaneTransform.transform(ptStr, ptStr); DefaultGraphicLabel.paintFontOutline(g2d, str, Math.round(ptStr.getX()), Math.round(ptStr.getY())); str = Integer.toString(yMaxVal); strWidth = g2d.getFontMetrics().stringWidth(str); xStrPos = -strWidth - 8; yStrPos = Math.round(yAxisRescaleRatio * yMaxVal) - midfontHeight; ptStr.setLocation(xStrPos, yStrPos); coordinateSystemViewPaneTransform.transform(ptStr, ptStr); DefaultGraphicLabel.paintFontOutline(g2d, str, Math.round(ptStr.getX()), Math.round(ptStr.getY())); str = Integer.toString(xMinVal); strWidth = g2d.getFontMetrics().stringWidth(str); xStrPos = Math.round(xAxisRescaleRatio * xMinVal) - strWidth / 2; yStrPos = -midfontHeight - 8; ptStr.setLocation(xStrPos, yStrPos); coordinateSystemViewPaneTransform.transform(ptStr, ptStr); DefaultGraphicLabel.paintFontOutline(g2d, str, Math.round(ptStr.getX()), Math.round(ptStr.getY())); str = Integer.toString(xMaxVal); strWidth = g2d.getFontMetrics().stringWidth(str); xStrPos = Math.round(xAxisRescaleRatio * xMaxVal) - strWidth / 2; yStrPos = -midfontHeight - 8; ptStr.setLocation(xStrPos, yStrPos); coordinateSystemViewPaneTransform.transform(ptStr, ptStr); DefaultGraphicLabel.paintFontOutline(g2d, str, Math.round(ptStr.getX()), Math.round(ptStr.getY())); // /////////////////////////////////////////////////////////////////////////////// g2d.setPaint(oldPaint); g2d.setStroke(oldStroke); g2d.setRenderingHints(oldRenderingHints); } public void drawScale(Graphics2D g2d, Rectangle bound, float fontHeight) { ImageElement image = view2DPane.getImage(); PlanarImage source = image.getImage(); if (source == null) { return; } double zoomFactor = view2DPane.getViewModel().getViewScale(); double scale = image.getPixelSize() / zoomFactor; double scaleSizex = ajustShowScale(scale, (int) Math.min(zoomFactor * source.getWidth() * image.getRescaleX(), bound.width / 2.0)); if (showBottomScale && scaleSizex > 50.0d) { Unit[] unit = { image.getPixelSpacingUnit() }; String str = ajustLengthDisplay(scaleSizex * scale, unit); g2d.setStroke(new BasicStroke(1.0F)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setPaint(Color.BLACK); double posx = bound.width / 2.0 - scaleSizex / 2.0; double posy = bound.height - border - 1.5; // - 1.5 is for outline Line2D line = new Line2D.Double(posx, posy, posx + scaleSizex, posy); g2d.draw(getOutLine(line)); line.setLine(posx, posy - thickLength, posx, posy); g2d.draw(getOutLine(line)); line.setLine(posx + scaleSizex, posy - thickLength, posx + scaleSizex, posy); g2d.draw(getOutLine(line)); int divisor = str.indexOf("5") == -1 ? str.indexOf("2") == -1 ? 10 : 2 : 5; //$NON-NLS-1$ //$NON-NLS-2$ double midThick = thickLength * 2.0 / 3.0; double smallThick = thickLength / 3.0; double divSquare = scaleSizex / divisor; for (int i = 1; i < divisor; i++) { line.setLine(posx + divSquare * i, posy, posx + divSquare * i, posy - midThick); g2d.draw(getOutLine(line)); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posx + divSquare * i + secondSquare * k; line.setLine(secBar, posy, secBar, posy - smallThick); g2d.draw(getOutLine(line)); } } } g2d.setPaint(Color.white); line.setLine(posx, posy, posx + scaleSizex, posy); g2d.draw(line); line.setLine(posx, posy - thickLength, posx, posy); g2d.draw(line); line.setLine(posx + scaleSizex, posy - thickLength, posx + scaleSizex, posy); g2d.draw(line); for (int i = 0; i < divisor; i++) { line.setLine(posx + divSquare * i, posy, posx + divSquare * i, posy - midThick); g2d.draw(line); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posx + divSquare * i + secondSquare * k; line.setLine(secBar, posy, secBar, posy - smallThick); g2d.draw(line); } } } g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); String pixSizeDesc = image.getPixelSizeCalibrationDescription(); if (StringUtil.hasText(pixSizeDesc)) { DefaultGraphicLabel.paintFontOutline(g2d, pixSizeDesc, (float) (posx + scaleSizex + 5), (float) posy - fontHeight); } str += " " + unit[0].getAbbreviation(); //$NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2d, str, (float) (posx + scaleSizex + 5), (float) posy); } double scaleSizeY = ajustShowScale(scale, (int) Math.min(zoomFactor * source.getHeight() * image.getRescaleY(), bound.height / 2.0)); if (scaleSizeY > 30.0d) { Unit[] unit = { image.getPixelSpacingUnit() }; String str = ajustLengthDisplay(scaleSizeY * scale, unit); float strokeWidth = g2d.getFont().getSize() / 15.0f; strokeWidth = strokeWidth < 1.0f ? 1.0f : strokeWidth; g2d.setStroke(new BasicStroke(strokeWidth)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setPaint(Color.black); double posx = border - 1.5f; // -1.5 for outline double posy = bound.height / 2.0 - scaleSizeY / 2.0; Line2D line = new Line2D.Double(posx, posy, posx, posy + scaleSizeY); g2d.draw(getOutLine(line)); line.setLine(posx, posy, posx + thickLength, posy); g2d.draw(getOutLine(line)); line.setLine(posx, posy + scaleSizeY, posx + thickLength, posy + scaleSizeY); g2d.draw(getOutLine(line)); int divisor = str.indexOf("5") == -1 ? str.indexOf("2") == -1 ? 10 : 2 : 5; //$NON-NLS-1$ //$NON-NLS-2$ double divSquare = scaleSizeY / divisor; double midThick = thickLength * 2.0 / 3.0; double smallThick = thickLength / 3.0; for (int i = 0; i < divisor; i++) { line.setLine(posx, posy + divSquare * i, posx + midThick, posy + divSquare * i); g2d.draw(getOutLine(line)); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posy + divSquare * i + secondSquare * k; line.setLine(posx, secBar, posx + smallThick, secBar); g2d.draw(getOutLine(line)); } } } g2d.setPaint(Color.WHITE); line.setLine(posx, posy, posx, posy + scaleSizeY); g2d.draw(line); line.setLine(posx, posy, posx + thickLength, posy); g2d.draw(line); line.setLine(posx, posy + scaleSizeY, posx + thickLength, posy + scaleSizeY); g2d.draw(line); for (int i = 0; i < divisor; i++) { line.setLine(posx, posy + divSquare * i, posx + midThick, posy + divSquare * i); g2d.draw(line); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posy + divSquare * i + secondSquare * k; line.setLine(posx, secBar, posx + smallThick, secBar); g2d.draw(line); } } } g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); DefaultGraphicLabel.paintFontOutline(g2d, str + " " + unit[0].getAbbreviation(), (int) posx, //$NON-NLS-1$ (int) (posy - 5 * strokeWidth)); } } private double ajustShowScale(double ratio, int maxLength) { int digits = (int) ((Math.log(maxLength * ratio) / Math.log(10)) + 1); double scaleLength = Math.pow(10, digits); double scaleSize = scaleLength / ratio; int loop = 0; while ((int) scaleSize > maxLength) { scaleLength /= findGeometricSuite(scaleLength); scaleSize = scaleLength / ratio; loop++; if (loop > 50) { return 0.0; } } return scaleSize; } public double findGeometricSuite(double length) { int shift = (int) ((Math.log(length) / Math.log(10)) + 0.1); int firstDigit = (int) (length / Math.pow(10, shift) + 0.5); if (firstDigit == 5) { return 2.5; } return 2.0; } public String ajustLengthDisplay(double scaleLength, Unit[] unit) { double ajustScaleLength = scaleLength; Unit ajustUnit = unit[0]; if (scaleLength < 1.0) { Unit down = ajustUnit; while ((down = down.getDownUnit()) != null) { double length = scaleLength * down.getConversionRatio(unit[0].getConvFactor()); if (length > 1) { ajustUnit = down; ajustScaleLength = length; break; } } } else if (scaleLength > 10.0) { Unit up = ajustUnit; while ((up = up.getUpUnit()) != null) { double length = scaleLength * up.getConversionRatio(unit[0].getConvFactor()); if (length < 1) { break; } ajustUnit = up; ajustScaleLength = length; } } // Trick to keep the value as a return parameter unit[0] = ajustUnit; if (ajustScaleLength < 1.0) { return ajustScaleLength < 0.001 ? DecFormater.scientificFormat(ajustScaleLength) : DecFormater.fourDecimal(ajustScaleLength); } return ajustScaleLength > 50000.0 ? DecFormater.scientificFormat(ajustScaleLength) : DecFormater.twoDecimal(ajustScaleLength); } public static final char getImageOrientationOposite(char c) { switch (c) { case 'L': return 'R'; case 'R': return 'L'; case 'P': return 'A'; case 'A': return 'P'; case 'H': return 'F'; case 'F': return 'H'; } return ' '; } public static final char getMajorAxisFromPatientRelativeDirectionCosine(double x, double y, double z) { char axis = ' '; char orientationX = x < 0 ? 'L' : 'R'; char orientationY = y < 0 ? 'P' : 'A'; char orientationZ = z < 0 ? 'H' : 'F'; double absX = Math.abs(x); double absY = Math.abs(y); double absZ = Math.abs(z); // The tests here really don't need to check the other dimensions, // just the threshold, since the sum of the squares should be == 1.0 // but just in case ... if (absX > 0.8 && absX > absY && absX > absZ) { axis = orientationX; } else if (absY > 0.8 && absY > absX && absY > absZ) { axis = orientationY; } else if (absZ > 0.8 && absZ > absX && absZ > absY) { axis = orientationZ; } return axis; } protected void drawExtendedActions(Graphics2D g2d, Point2D.Float[] positions) { if (view2DPane.getViewButtons().size() > 0) { int space = 5; int height = 0; for (ViewButton b : view2DPane.getViewButtons()) { if (b.isVisible() && b.getPosition() == GridBagConstraints.EAST) { height += b.getIcon().getIconHeight() + 5; } } Point2D.Float midy = new Point2D.Float(positions[1].x, (float) (view2DPane.getJComponent().getHeight() * 0.5 - (height - space) * 0.5)); SynchData synchData = (SynchData) view2DPane.getActionValue(ActionW.SYNCH_LINK.cmd()); boolean tile = synchData != null && SynchData.Mode.TILE.equals(synchData.getMode()); for (ViewButton b : view2DPane.getViewButtons()) { if (b.isVisible() && (tile && b.getIcon() == View2d.KO_ICON) == false) { Icon icon = b.getIcon(); int p = b.getPosition(); if (p == GridBagConstraints.EAST) { b.x = midy.x - icon.getIconWidth(); b.y = midy.y; midy.y += icon.getIconHeight() + 5; } else if (p == GridBagConstraints.NORTHEAST) { b.x = positions[1].x - icon.getIconWidth(); b.y = positions[1].y; positions[1].x -= icon.getIconWidth() + 5; } else if (p == GridBagConstraints.SOUTHEAST) { b.x = positions[2].x - icon.getIconWidth(); b.y = positions[2].y - icon.getIconHeight(); positions[2].x -= icon.getIconWidth() + 5; } else if (p == GridBagConstraints.NORTHWEST) { b.x = positions[0].x; b.y = positions[0].y; positions[0].x += icon.getIconWidth() + 5; } else if (p == GridBagConstraints.SOUTHWEST) { b.x = positions[3].x; b.y = positions[3].y - icon.getIconHeight(); positions[3].x += icon.getIconWidth() + 5; } else { b.x = midy.x - icon.getIconWidth(); b.y = midy.y; midy.y += icon.getIconHeight() + 5; } icon.paintIcon(view2DPane.getJComponent(), g2d, (int) b.x, (int) b.y); } } } return; } }