/*******************************************************************************
* 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.codec.display;
import java.awt.event.KeyEvent;
import java.awt.image.DataBuffer;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import javax.media.jai.LookupTableJAI;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.dcm4che3.data.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.core.api.image.LutShape;
import org.weasis.core.api.image.LutShape.eFunction;
import org.weasis.core.api.media.data.TagReadable;
import org.weasis.core.api.media.data.TagUtil;
import org.weasis.core.api.media.data.TagW;
import org.weasis.core.api.util.FileUtil;
import org.weasis.core.api.util.ResourceUtil;
import org.weasis.core.api.util.StringUtil;
import org.weasis.dicom.codec.DicomImageElement;
import org.weasis.dicom.codec.Messages;
import org.weasis.dicom.codec.PRSpecialElement;
import org.weasis.dicom.codec.TagD;
public class PresetWindowLevel {
private static final Logger LOGGER = LoggerFactory.getLogger(PresetWindowLevel.class);
private static final Map<String, List<PresetWindowLevel>> presetListByModality = getPresetListByModality();
private final String name;
private final Double window;
private final Double level;
private final LutShape shape;
private int keyCode = 0;
public PresetWindowLevel(String name, Double window, Double level, LutShape shape) {
this.name = Objects.requireNonNull(name);
this.window = Objects.requireNonNull(window);
this.level = Objects.requireNonNull(level);
this.shape = Objects.requireNonNull(shape);
}
public String getName() {
return name;
}
public Double getWindow() {
return window;
}
public Double getLevel() {
return level;
}
public LutShape getLutShape() {
return shape;
}
public int getKeyCode() {
return keyCode;
}
public double getMinBox() {
return level - window / 2.0;
}
public double getMaxBox() {
return level + window / 2.0;
}
public void setKeyCode(int keyCode) {
this.keyCode = keyCode;
}
public LutShape getShape() {
return shape;
}
@Override
public String toString() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + level.hashCode();
result = prime * result + name.hashCode();
result = prime * result + shape.hashCode();
result = prime * result + window.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PresetWindowLevel other = (PresetWindowLevel) obj;
return name.equals(other.name) && level.equals(other.level) && window.equals(other.window)
&& shape.equals(other.shape);
}
public static List<PresetWindowLevel> getPresetCollection(DicomImageElement image, TagReadable tagable,
boolean pixelPadding, String type) {
if (image == null || tagable == null) {
return null;
}
String dicomKeyWord = " " + type; //$NON-NLS-1$
ArrayList<PresetWindowLevel> presetList = new ArrayList<>();
double[] levelList = TagD.getTagValue(tagable, Tag.WindowCenter, double[].class);
double[] windowList = TagD.getTagValue(tagable, Tag.WindowWidth, double[].class);
// optional attributes
String[] wlExplanationList = TagD.getTagValue(tagable, Tag.WindowCenterWidthExplanation, String[].class);
String lutFunctionDescriptor = TagD.getTagValue(tagable, Tag.VOILUTFunction, String.class);
LutShape defaultLutShape = LutShape.LINEAR; // Implicitly defined as default function in DICOM standard
if (lutFunctionDescriptor != null) {
if ("SIGMOID".equalsIgnoreCase(lutFunctionDescriptor)) { //$NON-NLS-1$
defaultLutShape = new LutShape(eFunction.SIGMOID, eFunction.SIGMOID + dicomKeyWord);
} else if ("LINEAR".equalsIgnoreCase(lutFunctionDescriptor)) { //$NON-NLS-1$
defaultLutShape = new LutShape(eFunction.LINEAR, eFunction.LINEAR + dicomKeyWord);
}
}
if (levelList != null && windowList != null) {
int windowLevelDefaultCount = (levelList.length == windowList.length) ? levelList.length : 0;
String defaultExplanation = Messages.getString("PresetWindowLevel.default"); //$NON-NLS-1$
int k = 1;
for (int i = 0; i < windowLevelDefaultCount; i++) {
String explanation = defaultExplanation + " " + k; //$NON-NLS-1$
if (wlExplanationList != null && i < wlExplanationList.length) {
if (StringUtil.hasText(wlExplanationList[i])) {
explanation = wlExplanationList[i]; // optional attribute
}
}
PresetWindowLevel preset =
new PresetWindowLevel(explanation + dicomKeyWord, windowList[i], levelList[i], defaultLutShape);
// Only set shortcuts for the two first presets
if (k == 1) {
preset.setKeyCode(KeyEvent.VK_1);
} else if (k == 2) {
preset.setKeyCode(KeyEvent.VK_2);
}
if (!presetList.contains(preset)) {
presetList.add(preset);
k++;
}
}
}
LookupTableJAI[] voiLUTsData = (LookupTableJAI[]) tagable.getTagValue(TagW.VOILUTsData);
String[] voiLUTsExplanation = (String[]) tagable.getTagValue(TagW.VOILUTsExplanation); // optional attribute
if (voiLUTsData != null) {
String defaultExplanation = Messages.getString("PresetWindowLevel.voi_lut"); //$NON-NLS-1$
for (int i = 0; i < voiLUTsData.length; i++) {
String explanation = defaultExplanation + " " + i; //$NON-NLS-1$
if (voiLUTsExplanation != null && i < voiLUTsExplanation.length) {
if (StringUtil.hasText(voiLUTsExplanation[i])) {
explanation = voiLUTsExplanation[i];
}
}
PresetWindowLevel preset =
buildPresetFromLutData(voiLUTsData[i], image, tagable, pixelPadding, explanation + dicomKeyWord);
if (preset == null) {
continue;
}
// Only set shortcuts for the two first presets
int k = presetList.size();
if (k == 0) {
preset.setKeyCode(KeyEvent.VK_1);
} else if (k == 1) {
preset.setKeyCode(KeyEvent.VK_2);
}
presetList.add(preset);
}
}
PresetWindowLevel autoLevel = new PresetWindowLevel(Messages.getString("PresetWindowLevel.full"), //$NON-NLS-1$
image.getFullDynamicWidth(tagable, pixelPadding), image.getFullDynamicCenter(tagable, pixelPadding),
defaultLutShape);
// Set O shortcut for auto levels
autoLevel.setKeyCode(KeyEvent.VK_0);
presetList.add(autoLevel);
// Exclude Secondary Capture CT
if (image.getBitsStored() > 8) {
List<PresetWindowLevel> modPresets = presetListByModality.get(TagD.getTagValue(image, Tag.Modality));
if (modPresets != null) {
presetList.addAll(modPresets);
}
}
return presetList;
}
public static PresetWindowLevel buildPresetFromLutData(LookupTableJAI voiLUTsData, DicomImageElement image,
TagReadable tagable, boolean pixelPadding, String explanation) {
if (voiLUTsData == null || explanation == null) {
return null;
}
Object inLut;
if (voiLUTsData.getDataType() == DataBuffer.TYPE_BYTE) {
inLut = voiLUTsData.getByteData(0);
} else if (voiLUTsData.getDataType() <= DataBuffer.TYPE_SHORT) {
inLut = voiLUTsData.getShortData(0);
} else {
return null;
}
int minValueLookup = voiLUTsData.getOffset();
int maxValueLookup = voiLUTsData.getOffset() + Array.getLength(inLut) - 1;
minValueLookup = Math.min(minValueLookup, maxValueLookup);
maxValueLookup = Math.max(minValueLookup, maxValueLookup);
int minAllocatedValue = image.getMinAllocatedValue(tagable, pixelPadding);
if (minValueLookup < minAllocatedValue) {
minValueLookup = minAllocatedValue;
}
int maxAllocatedValue = image.getMaxAllocatedValue(tagable, pixelPadding);
if (maxValueLookup > maxAllocatedValue) {
maxValueLookup = maxAllocatedValue;
}
double fullDynamicWidth = maxValueLookup - minValueLookup;
double fullDynamicCenter = minValueLookup + fullDynamicWidth / 2f;
LutShape newLutShape = new LutShape(voiLUTsData, explanation);
return new PresetWindowLevel(newLutShape.toString(), fullDynamicWidth, fullDynamicCenter, newLutShape);
}
public static Map<String, List<PresetWindowLevel>> getPresetListByModality() {
Map<String, List<PresetWindowLevel>> presets = new TreeMap<>();
XMLStreamReader xmler = null;
InputStream stream = null;
try {
File file = ResourceUtil.getResource("presets.xml"); //$NON-NLS-1$
if (!file.canRead()) {
return Collections.emptyMap();
}
XMLInputFactory xmlif = XMLInputFactory.newInstance();
stream = new FileInputStream(file); // $NON-NLS-1$
xmler = xmlif.createXMLStreamReader(stream);
int eventType;
while (xmler.hasNext()) {
eventType = xmler.next();
switch (eventType) {
case XMLStreamConstants.START_ELEMENT:
if ("presets".equals(xmler.getName().getLocalPart())) { //$NON-NLS-1$
while (xmler.hasNext()) {
readPresetListByModality(xmler, presets);
}
}
break;
default:
break;
}
}
}
catch (Exception e) {
LOGGER.error("Cannot read presets file! " + e); //$NON-NLS-1$
} finally {
FileUtil.safeClose(xmler);
FileUtil.safeClose(stream);
}
return presets;
}
private static void readPresetListByModality(XMLStreamReader xmler, Map<String, List<PresetWindowLevel>> presets)
throws XMLStreamException {
int eventType = xmler.next();
String key;
if (eventType == XMLStreamConstants.START_ELEMENT) {
key = xmler.getName().getLocalPart();
if ("preset".equals(key) && xmler.getAttributeCount() >= 4) { //$NON-NLS-1$
String name = xmler.getAttributeValue(null, "name");//$NON-NLS-1$
try {
String modality = xmler.getAttributeValue(null, "modality");//$NON-NLS-1$
double window = Double.parseDouble(xmler.getAttributeValue(null, "window")); //$NON-NLS-1$
double level = Double.parseDouble(xmler.getAttributeValue(null, "level")); //$NON-NLS-1$
String shape = xmler.getAttributeValue(null, "shape");//$NON-NLS-1$
Integer keyCode = TagUtil.getIntegerTagAttribute(xmler, "key", null);//$NON-NLS-1$
LutShape lutShape = LutShape.getLutShape(shape);
PresetWindowLevel preset =
new PresetWindowLevel(name, window, level, lutShape == null ? LutShape.LINEAR : lutShape);
if (keyCode != null) {
preset.setKeyCode(keyCode);
}
List<PresetWindowLevel> presetList = presets.get(modality);
if (presetList == null) {
presetList = new ArrayList<>();
presets.put(modality, presetList);
}
presetList.add(preset);
} catch (Exception e) {
LOGGER.error("Preset {} cannot be read from xml file", name, e); //$NON-NLS-1$
}
}
}
}
}