/*******************************************************************************
* 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.utils;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.operator.LookupDescriptor;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.image.PaletteColorModel;
import org.dcm4che3.imageio.codec.ImageReaderFactory;
import org.dcm4che3.imageio.codec.ImageReaderFactory.ImageReaderParam;
import org.weasis.core.api.image.LutShape;
import org.weasis.core.api.media.data.TagReadable;
import org.weasis.core.api.media.data.TagW;
import org.weasis.dicom.codec.TagD;
import org.weasis.dicom.codec.TransferSyntax;
/**
*
* @author Benoit Jacquemoud, Nicolas Roduit
*
* @version $Rev$ $Date$
*/
public class DicomImageUtils {
private DicomImageUtils() {
}
public static PlanarImage getRGBImageFromPaletteColorModel(RenderedImage source, Attributes ds) {
if (source == null) {
return null;
}
// Convert images with PaletteColorModel to RGB model
if (source.getColorModel() instanceof PaletteColorModel && ds != null) {
int[] rDesc = DicomImageUtils.lutDescriptor(ds, Tag.RedPaletteColorLookupTableDescriptor);
int[] gDesc = DicomImageUtils.lutDescriptor(ds, Tag.GreenPaletteColorLookupTableDescriptor);
int[] bDesc = DicomImageUtils.lutDescriptor(ds, Tag.BluePaletteColorLookupTableDescriptor);
byte[] r = DicomImageUtils.lutData(ds, rDesc, Tag.RedPaletteColorLookupTableData,
Tag.SegmentedRedPaletteColorLookupTableData);
byte[] g = DicomImageUtils.lutData(ds, gDesc, Tag.GreenPaletteColorLookupTableData,
Tag.SegmentedGreenPaletteColorLookupTableData);
byte[] b = DicomImageUtils.lutData(ds, bDesc, Tag.BluePaletteColorLookupTableData,
Tag.SegmentedBluePaletteColorLookupTableData);
LookupTableJAI lut = new LookupTableJAI(new byte[][] { r, g, b });
// Replace the original image with the RGB image.
return LookupDescriptor.create(source, lut, null);
}
return PlanarImage.wrapRenderedImage(source);
}
/**
* Minimum output is given for input value below (level - window/2)<br>
* Maximum output is given for input value above (level + window/2) <br>
* <br>
* These Minimum and Maximum values depends on bitsStored and signed given attributes. ie : <br>
* - when bitsStored=8 bits unsigned => minOutValue=0 and maxOutValue=255 <br>
* - when bitsStored=8 bits signed => minOutValue=-128 and maxOutValue=127 <br>
* - when bitsStored=16 bits unsigned => minOutValue= 0 and maxOutValue= 65535 <br>
* - when bitsStored=16 bits signed => minOutValue= -32768 and maxOutValue= 32767 <br>
*
* @param lutShape
* @param window
* @param level
* @param minValue
* @param maxValue
* @param bitsStored
* @param isSigned
* @param inverse
*
* @return a LookupTableJAI for data between minValue and maxValue according to all given parameters <br>
*/
public static LookupTableJAI createWindowLevelLut(LutShape lutShape, double window, double level, int minValue,
int maxValue, int bitsStored, boolean isSigned, boolean inverse) {
if (lutShape == null) {
return null;
}
int bStored = bitsStored > 16 ? 16 : (bitsStored < 1) ? 1 : bitsStored;
double win = window < 1.0 ? 1.0 : window;
int bitsAllocated = (bStored <= 8) ? 8 : 16;
int outRangeSize = (1 << bitsAllocated) - 1;
int maxOutValue = isSigned ? (1 << (bitsAllocated - 1)) - 1 : outRangeSize;
int minOutValue = isSigned ? -(maxOutValue + 1) : 0;
int minInValue = Math.min(maxValue, minValue);
int maxInValue = Math.max(maxValue, minValue);
int numEntries = maxInValue - minInValue + 1;
Object outLut = bStored <= 8 ? new byte[numEntries] : new short[numEntries];
if (lutShape.getFunctionType() != null) {
switch (lutShape.getFunctionType()) {
case LINEAR:
setWindowLevelLinearLut(win, level, minInValue, outLut, minOutValue, maxOutValue, inverse);
break;
case SIGMOID:
setWindowLevelSigmoidLut(win, level, minInValue, outLut, minOutValue, maxOutValue, inverse);
break;
case SIGMOID_NORM:
setWindowLevelSigmoidLut(win, level, minInValue, outLut, minOutValue, maxOutValue, inverse, true);
break;
case LOG:
setWindowLevelLogarithmicLut(win, level, minInValue, outLut, minOutValue, maxOutValue, inverse);
break;
case LOG_INV:
setWindowLevelExponentialLut(win, level, minInValue, outLut, minOutValue, maxOutValue, inverse);
break;
default:
return null;
}
} else {
setWindowLevelSequenceLut(win, level, lutShape.getLookup(), minInValue, maxInValue, outLut, minOutValue,
maxOutValue, inverse);
}
return (outLut instanceof byte[]) ? new LookupTableJAI((byte[]) outLut, minInValue) : //
new LookupTableJAI((short[]) outLut, minInValue, isSigned);
}
/**
* @return LookupTable with full range of possible input entries according to bitStored.<br>
* Note that isSigned is relevant for both input and output values
*/
public static LookupTableJAI createRescaleRampLut(LutParameters params) {
return createRescaleRampLut(params.getIntercept(), params.getSlope(), params.getBitsStored(), params.isSigned(),
params.isOutputSigned(), params.getBitsOutput());
}
public static LookupTableJAI createRescaleRampLut(double intercept, double slope, int bitsStored, boolean isSigned,
boolean outputSigned, int bitsOutput) {
return createRescaleRampLut(intercept, slope, Integer.MIN_VALUE, Integer.MAX_VALUE, bitsStored, isSigned, false,
outputSigned, bitsOutput);
}
public static LookupTableJAI createRescaleRampLut(double intercept, double slope, int minValue, int maxValue,
int bitsStored, boolean isSigned, boolean inverse, boolean outputSigned, int bitsOutput) {
int stored = (bitsStored > 16) ? 16 : ((bitsStored < 1) ? 1 : bitsStored);
int bitsOutLut = bitsOutput <= 8 ? 8 : 16;
int outRangeSize = (1 << bitsOutLut) - 1;
int maxOutValue = outputSigned ? (1 << (bitsOutLut - 1)) - 1 : outRangeSize;
int minOutValue = outputSigned ? -(maxOutValue + 1) : 0;
int minInValue = isSigned ? -(1 << (stored - 1)) : 0;
int maxInValue = isSigned ? (1 << (stored - 1)) - 1 : (1 << stored) - 1;
minInValue = Math.max(minInValue, maxValue < minValue ? maxValue : minValue);
maxInValue = Math.min(maxInValue, maxValue < minValue ? minValue : maxValue);
int numEntries = maxInValue - minInValue + 1;
Object outLut = (bitsOutLut == 8) ? new byte[numEntries] : new short[numEntries];
for (int i = 0; i < numEntries; i++) {
int value = (int) Math.round((i + minInValue) * slope + intercept);
value = (value >= maxOutValue) ? maxOutValue : ((value <= minOutValue) ? minOutValue : value);
value = inverse ? (maxOutValue + minOutValue - value) : value;
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
return (outLut instanceof byte[]) ? new LookupTableJAI((byte[]) outLut, minInValue) : //
new LookupTableJAI((short[]) outLut, minInValue, !outputSigned);
}
/**
* Apply the pixel padding to the modality LUT
*
* @see DICOM standard PS 3.3
*
* §C.7.5.1.1.2 Pixel Padding Value and Pixel Padding Range Limit If Photometric Interpretation
*
* * If a Pixel Padding Value (0028,0120) only is present in the image then image contrast manipulations shall
* be not be applied to those pixels with the value specified in Pixel Padding Value (0028,0120). If both Pixel
* Padding Value (0028,0120) and Pixel Padding Range Limit (0028,0121) are present in the image then image
* contrast manipulations shall not be applied to those pixels with values in the range between the values of
* Pixel Padding Value (0028,0120) and Pixel Padding Range Limit (0028,0121), inclusive."
*
*
* (0028,0004) is MONOCHROME2, Pixel Padding Value (0028,0120) shall be less than (closer to or equal to the
* minimum possible pixel value) or equal to Pixel Padding Range Limit (0028,0121). If Photometric
* Interpretation (0028,0004) is MONOCHROME1, Pixel Padding Value (0028,0120) shall be greater than (closer to
* or equal to the maximum possible pixel value) or equal to Pixel Padding Range Limit (0028,0121).
*
* When the relationship between pixel value and X-Ray Intensity is unknown, it is recommended that the
* following values be used to pad with black when the image is unsigned:
*
* 0 if Photometric Interpretation (0028,0004) is MONOCHROME2. 2BitsStored - 1 if Photometric Interpretation
* (0028,0004) is MONOCHROME1.
*
* and when the image is signed: -2BitsStored-1 if Photometric Interpretation (0028,0004) is MONOCHROME2.
* 2BitsStored-1 - 1 if Photometric Interpretation (0028,0004) is MONOCHROME1.
*
*
*/
public static void applyPixelPaddingToModalityLUT(LookupTableJAI modalityLookup, LutParameters lutparams) {
if (modalityLookup != null && lutparams.isApplyPadding() && lutparams.getPaddingMinValue() != null
&& modalityLookup.getDataType() <= DataBuffer.TYPE_SHORT) {
int paddingValue = lutparams.getPaddingMinValue();
Integer paddingLimit = lutparams.getPaddingMaxValue();
int paddingValueMin = (paddingLimit == null) ? paddingValue : Math.min(paddingValue, paddingLimit);
int paddingValueMax = (paddingLimit == null) ? paddingValue : Math.max(paddingValue, paddingLimit);
int numPaddingValues = paddingValueMax - paddingValueMin + 1;
int paddingValuesStartIndex = paddingValueMin - modalityLookup.getOffset();
if (paddingValuesStartIndex >= modalityLookup.getNumEntries()) {
return;
}
if (paddingValuesStartIndex < 0) {
numPaddingValues += paddingValuesStartIndex;
if (numPaddingValues < 1) {
// No padding value in the LUT range
return;
}
paddingValuesStartIndex = 0;
}
Object inLut;
// if FALSE DataBuffer Type is supposed to be either TYPE_SHORT or TYPE_USHORT
final boolean isDataTypeByte = modalityLookup.getDataType() == DataBuffer.TYPE_BYTE;
if (isDataTypeByte) {
inLut = modalityLookup.getByteData(0);
} else {
inLut = modalityLookup.getShortData(0);
}
Object outLut = inLut;
if (isDataTypeByte) {
byte fillVal = lutparams.isInversePaddingMLUT() ? (byte) 255 : (byte) 0;
byte[] data = (byte[]) outLut;
Arrays.fill(data, paddingValuesStartIndex, paddingValuesStartIndex + numPaddingValues, fillVal);
} else {
short[] data = (short[]) outLut;
short fillVal = lutparams.isInversePaddingMLUT() ? data[data.length - 1] : data[0];
Arrays.fill(data, paddingValuesStartIndex, paddingValuesStartIndex + numPaddingValues, fillVal);
}
}
}
private static void setWindowLevelLinearLutLegacy(double window, double level, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse) {
/**
* Pseudo code defined in Dicom Standard 2011 - PS 3.3 § C.11.2 VOI LUT Module
*/
double lowLevel = (level - 0.5) - (window - 1.0) / 2.0;
double highLevel = (level - 0.5) + (window - 1.0) / 2.0;
for (int i = 0; i < Array.getLength(outLut); i++) {
int value;
if ((i + minInValue) <= lowLevel) {
value = minOutValue;
} else if ((i + minInValue) > highLevel) {
value = maxOutValue;
} else {
value = (int) ((((i + minInValue) - (level - 0.5)) / (window - 1.0) + 0.5) * (maxOutValue - minOutValue)
+ minOutValue);
}
value = (value >= maxOutValue) ? maxOutValue : ((value <= minOutValue) ? minOutValue : value);
value = inverse ? (maxOutValue + minOutValue - value) : value;
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
}
private static void setWindowLevelLinearLut(double window, double level, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse) {
double slope = (maxOutValue - minOutValue) / window;
double intercept = maxOutValue - slope * (level + (window / 2.0));
for (int i = 0; i < Array.getLength(outLut); i++) {
int value = (int) ((i + minInValue) * slope + intercept);
value = (value >= maxOutValue) ? maxOutValue : ((value <= minOutValue) ? minOutValue : value);
value = inverse ? (maxOutValue + minOutValue - value) : value;
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
}
private static void setWindowLevelSigmoidLut(double width, double center, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse) {
setWindowLevelSigmoidLut(width, center, minInValue, outLut, minOutValue, maxOutValue, inverse, false);
}
private static void setWindowLevelSigmoidLut(double width, double center, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse, boolean normalize) {
double nFactor = -20d; // factor defined by default in Dicom standard ( -20*2/10 = -4 )
double outRange = maxOutValue - (double) minOutValue;
double minValue = 0;
double outRescaleRatio = 1;
if (normalize) {
double lowLevel = center - width / 2d;
double highLevel = center + width / 2d;
minValue = minOutValue + outRange / (1d + Math.exp((2d * nFactor / 10d) * (lowLevel - center) / width));
double maxValue =
minOutValue + outRange / (1d + Math.exp((2d * nFactor / 10d) * (highLevel - center) / width));
outRescaleRatio = (maxOutValue - minOutValue) / Math.abs(maxValue - minValue);
}
for (int i = 0; i < Array.getLength(outLut); i++) {
double value = outRange / (1d + Math.exp((2d * nFactor / 10d) * (i + minInValue - center) / width));
if (normalize) {
value = (value - minValue) * outRescaleRatio;
}
value = (int) Math.round(value + minOutValue);
value = (int) ((value > maxOutValue) ? maxOutValue : ((value < minOutValue) ? minOutValue : value));
value = (int) (inverse ? (maxOutValue + minOutValue - value) : value);
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
}
private static void setWindowLevelExponentialLut(double width, double center, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse) {
setWindowLevelExponentialLut(width, center, minInValue, outLut, minOutValue, maxOutValue, inverse, true);
}
private static void setWindowLevelExponentialLut(double width, double center, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse, boolean normalize) {
double nFactor = 20d;
double outRange = maxOutValue - (double) minOutValue;
double minValue = 0;
double outRescaleRatio = 1;
if (normalize) {
double lowLevel = center - width / 2d;
double highLevel = center + width / 2d;
minValue = minOutValue + outRange * Math.exp((nFactor / 10d) * (lowLevel - center) / width);
double maxValue = minOutValue + outRange * Math.exp((nFactor / 10d) * (highLevel - center) / width);
outRescaleRatio = (maxOutValue - minOutValue) / Math.abs(maxValue - minValue);
}
for (int i = 0; i < Array.getLength(outLut); i++) {
double value = outRange * Math.exp((nFactor / 10d) * (i + minInValue - center) / width);
if (normalize) {
value = (value - minValue) * outRescaleRatio;
}
value = (int) Math.round(value + minOutValue);
value = (int) ((value > maxOutValue) ? maxOutValue : ((value < minOutValue) ? minOutValue : value));
value = (int) (inverse ? (maxOutValue + minOutValue - value) : value);
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
}
private static void setWindowLevelLogarithmicLut(double width, double center, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse) {
setWindowLevelLogarithmicLut(width, center, minInValue, outLut, minOutValue, maxOutValue, inverse, true);
}
private static void setWindowLevelLogarithmicLut(double width, double center, int minInValue, Object outLut,
int minOutValue, int maxOutValue, boolean inverse, boolean normalize) {
double nFactor = 20d;
double outRange = maxOutValue - (double) minOutValue;
double minValue = 0;
double outRescaleRatio = 1;
if (normalize) {
double lowLevel = center - width / 2d;
double highLevel = center + width / 2d;
minValue = minOutValue + outRange * Math.log((nFactor / 10d) * (1 + (lowLevel - center) / width));
double maxValue = minOutValue + outRange * Math.log((nFactor / 10d) * (1 + (highLevel - center) / width));
outRescaleRatio = (maxOutValue - minOutValue) / Math.abs(maxValue - minValue);
}
for (int i = 0; i < Array.getLength(outLut); i++) {
double value = outRange * Math.log((nFactor / 10d) * (1 + (i + minInValue - center) / width));
if (normalize) {
value = (value - minValue) * outRescaleRatio;
}
value = (int) Math.round(value + minOutValue);
value = (int) ((value > maxOutValue) ? maxOutValue : ((value < minOutValue) ? minOutValue : value));
value = (int) (inverse ? (maxOutValue + minOutValue - value) : value);
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
}
private static Object getLutDataArray(LookupTableJAI lookup) {
Object lutDataArray = null;
if (lookup != null) {
if (lookup.getDataType() == DataBuffer.TYPE_BYTE) {
lutDataArray = lookup.getByteData(0);
} else if (lookup.getDataType() <= DataBuffer.TYPE_SHORT) {
lutDataArray = lookup.getShortData(0);
}
}
return lutDataArray;
}
/**
* @param width
* @param center
* @param lookupSequence
* @param minInValue
* @param maxInValue
* @param outLut
* @param minOutValue
* @param maxOutValue
* @param inverse
* @return a normalized LookupTableJAI based upon given lutSequence <br>
*/
private static void setWindowLevelSequenceLut(double width, double center, LookupTableJAI lookupSequence,
int minInValue, int maxInValue, Object outLut, int minOutValue, int maxOutValue, boolean inverse) {
final Object inLutDataArray = getLutDataArray(lookupSequence);
if (inLutDataArray == null) {
return;
}
// Use this mask to get positive value assuming inLutData value is always unsigned
final int lutDataValueMask = inLutDataArray instanceof byte[] ? 0x000000FF
: (inLutDataArray instanceof short[] ? 0x0000FFFF : 0xFFFFFFFF);
double lowLevel = center - width / 2.0;
double highLevel = center + width / 2.0;
int maxInLutIndex = Array.getLength(inLutDataArray) - 1;
int minLookupValue = Integer.MAX_VALUE;
int maxLookupValue = Integer.MIN_VALUE;
for (int i = 0; i < Array.getLength(inLutDataArray); i++) {
int val = lutDataValueMask & Array.getInt(inLutDataArray, i);
if (val < minLookupValue) {
minLookupValue = val;
}
if (val > maxLookupValue) {
maxLookupValue = val;
}
}
int lookupValueRange = Math.abs(maxLookupValue - minLookupValue);
double widthRescaleRatio = maxInLutIndex / width;
double outRescaleRatio = (maxOutValue - minOutValue) / (double) lookupValueRange;
for (int i = 0; i < Array.getLength(outLut); i++) {
int value;
double inValueRescaled;
if ((i + minInValue) <= lowLevel) {
inValueRescaled = 0;
} else if ((i + minInValue) > highLevel) {
inValueRescaled = maxInLutIndex;
} else {
inValueRescaled = (i + minInValue - lowLevel) * widthRescaleRatio;
}
int inValueRoundDown = Math.max(0, (int) Math.floor(inValueRescaled));
int inValueRoundUp = Math.min(maxInLutIndex, (int) Math.ceil(inValueRescaled));
int valueDown = lutDataValueMask & Array.getInt(inLutDataArray, inValueRoundDown);
int valueUp = lutDataValueMask & Array.getInt(inLutDataArray, inValueRoundUp);
// Linear Interpolation of the output value with respect to the rescaled ratio
value = (int) ((inValueRoundUp == inValueRoundDown) ? valueDown : Math.round(valueDown
+ (inValueRescaled - inValueRoundDown) * (valueUp - valueDown) / (inValueRoundUp - inValueRoundDown)));
value = (int) Math.round(value * outRescaleRatio);
value = (value >= maxOutValue) ? maxOutValue : ((value <= minOutValue) ? minOutValue : value);
value = inverse ? (maxOutValue + minOutValue - value) : value;
if (outLut instanceof byte[]) {
Array.set(outLut, i, (byte) value);
} else if (outLut instanceof short[]) {
Array.set(outLut, i, (short) value);
}
}
}
public static boolean hasImageReader(String tsuid) {
ImageReaderFactory factory = ImageReaderFactory.getDefault();
if (factory != null) {
List<ImageReaderParam> list = factory.get(tsuid);
if (list != null) {
synchronized (list) {
for (Iterator<ImageReaderParam> it = list.iterator(); it.hasNext();) {
ImageReaderParam imageParam = it.next();
String cl = imageParam.className;
Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName(imageParam.formatName);
while (iter.hasNext()) {
ImageReader reader = iter.next();
if (cl == null || reader.getClass().getName().equals(cl)) {
return true;
}
}
}
}
}
}
return false;
}
public static double pixel2rescale(TagReadable tagable, double pixelValue) {
if (tagable != null) {
LookupTableJAI lookup = (LookupTableJAI) tagable.getTagValue(TagW.ModalityLUTData);
if (lookup != null) {
if (pixelValue >= lookup.getOffset() && pixelValue <= lookup.getOffset() + lookup.getNumEntries() - 1) {
return lookup.lookup(0, (int) pixelValue);
}
} else {
// value = pixelValue * rescale slope + intercept value
Double slope = TagD.getTagValue(tagable, Tag.RescaleSlope, Double.class);
Double intercept = TagD.getTagValue(tagable, Tag.RescaleIntercept, Double.class);
if (slope != null || intercept != null) {
return pixelValue * (slope == null ? 1.0 : slope) + (intercept == null ? 0.0 : intercept);
}
}
}
return pixelValue;
}
// ////////////////////////////////////////////////////////////////////////////
// Take from dcm4che3, should be public
public static int[] lutDescriptor(Attributes ds, int descTag) {
int[] desc = DicomMediaUtils.getIntAyrrayFromDicomElement(ds, descTag, null);
if (desc == null) {
throw new IllegalArgumentException("Missing LUT Descriptor!"); //$NON-NLS-1$
}
if (desc.length != 3) {
throw new IllegalArgumentException("Illegal number of LUT Descriptor values: " + desc.length); //$NON-NLS-1$
}
if (desc[0] < 0) {
throw new IllegalArgumentException("Illegal LUT Descriptor: len=" + desc[0]); //$NON-NLS-1$
}
int bits = desc[2];
if (bits != 8 && bits != 16) {
throw new IllegalArgumentException("Illegal LUT Descriptor: bits=" + bits); //$NON-NLS-1$
}
return desc;
}
public static byte[] lutData(Attributes ds, int[] desc, int dataTag, int segmTag) {
int len = desc[0] == 0 ? 0x10000 : desc[0];
int bits = desc[2];
byte[] data = ds.getSafeBytes(dataTag);
if (data == null) {
int[] segm = DicomMediaUtils.getIntAyrrayFromDicomElement(ds, segmTag, null);
if (segm == null) {
throw new IllegalArgumentException("Missing LUT Data!"); //$NON-NLS-1$
}
if (bits == 8) {
throw new IllegalArgumentException("Segmented LUT Data with LUT Descriptor: bits=8"); //$NON-NLS-1$
}
data = new byte[len];
inflateSegmentedLut(segm, data);
} else if (bits == 16 || data.length != len) {
if (data.length != len << 1) {
lutLengthMismatch(data.length, len);
}
int hilo = ds.bigEndian() ? 0 : 1;
if (bits == 8) {
hilo = 1 - hilo; // padded high bits -> use low bits
}
byte[] bs = new byte[data.length >> 1];
for (int i = 0; i < bs.length; i++) {
bs[i] = data[(i << 1) | hilo];
}
data = bs;
}
return data;
}
private static void inflateSegmentedLut(int[] in, byte[] out) {
int x = 0;
try {
for (int i = 0; i < in.length;) {
int op = in[i++];
int n = in[i++];
switch (op) {
case 0:
while (n-- > 0) {
out[x++] = (byte) in[i++];
}
break;
case 1:
x = linearSegment(in[i++], out, x, n);
break;
case 2: {
int i2 = (in[i++] & 0xffff) | (in[i++] << 16);
while (n-- > 0) {
int op2 = in[i2++];
int n2 = in[i2++] & 0xffff;
switch (op2) {
case 0:
while (n2-- > 0) {
out[x++] = (byte) in[i2++];
}
break;
case 1:
x = linearSegment(in[i2++], out, x, n);
break;
default:
illegalOpcode(op, i2 - 2);
}
}
}
default:
illegalOpcode(op, i - 2);
}
}
} catch (IndexOutOfBoundsException e) {
if (x > out.length) {
exceedsLutLength(out.length);
} else {
endOfSegmentedLut();
}
}
if (x < out.length) {
lutLengthMismatch(x, out.length);
}
}
private static void endOfSegmentedLut() {
throw new IllegalArgumentException("Running out of data inflating segmented LUT"); //$NON-NLS-1$
}
private static int linearSegment(int y1, byte[] out, int x, int n) {
if (x == 0) {
throw new IllegalArgumentException("Linear segment cannot be the first segment"); //$NON-NLS-1$
}
try {
int y0 = out[x - 1];
int dy = y1 - y0;
for (int j = 1; j <= n; j++) {
out[x++] = (byte) ((y0 + dy * j / n) >> 8);
}
} catch (IndexOutOfBoundsException e) {
exceedsLutLength(out.length);
}
return x;
}
private static void exceedsLutLength(int descLen) {
throw new IllegalArgumentException("Number of entries in inflated segmented LUT exceeds specified value: " //$NON-NLS-1$
+ descLen + " in LUT Descriptor"); //$NON-NLS-1$
}
private static void lutLengthMismatch(int lutLen, int descLen) {
throw new IllegalArgumentException("Number of actual LUT entries: " + lutLen + " mismatch specified value: " //$NON-NLS-1$ //$NON-NLS-2$
+ descLen + " in LUT Descriptor"); //$NON-NLS-1$
}
private static void illegalOpcode(int op, int i) {
throw new IllegalArgumentException("illegal op code:" + op + ", index:" + i); //$NON-NLS-1$ //$NON-NLS-2$
}
}