16位DICOM图像到BufferedImage的像素数据

时间:2017-03-16 17:58:55

标签: java bufferedimage tiff

我有一个字节数组存储来自已经解构的DICOM文件的16位像素数据。我现在需要做的是将像素数据以某种方式转换/导出为TIFF文件格式。我正在使用imageio-tiff-3.3.2.jar插件来处理tiff转换/标头数据。但现在我需要将图像数据数组打包到原始图像尺寸的BufferedImage中,以便将其导出为TIFF。但似乎BufferedImage不支持16位图像。有没有办法解决这个问题,比如外部库?有没有其他方法可以将图像数据打包成原始DICOM尺寸的TIFF图像?请记住,这个过程必须是完全无损的。我在最近几天环顾四周试了一些东西,但到目前为止没有什么对我有用。

如果您有任何问题或者我有什么办法可以解决任何困惑,请告诉我。

编辑:预期和当前图像 enter image description here

2 个答案:

答案 0 :(得分:3)

给定原始字节数组的输入数据,包含无符号16位图像数据,这里有两种创建BufferedImage的方法。

第一个会慢一些,因为它涉及将byte数组复制到short数组中。它还需要两倍的内存量。好处是它创建了一个标准的TYPE_USHORT_GRAY BufferedImage,它可能更快显示并且可能更兼容。

private static BufferedImage createCopyUsingByteBuffer(int w, int h, byte[] rawBytes) {
    short[] rawShorts = new short[rawBytes.length / 2];

    ByteBuffer.wrap(rawBytes)
            // .order(ByteOrder.LITTLE_ENDIAN) // Depending on the data's endianness
            .asShortBuffer()
            .get(rawShorts);

    DataBuffer dataBuffer = new DataBufferUShort(rawShorts, rawShorts.length);
    int stride = 1;
    WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, w, h, w * stride, stride, new int[] {0}, null);
    ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);

    return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}

更快的变体(以前的版本需要花费4-5倍的时间)来创建,但会产生TYPE_CUSTOM图像,显示速度可能会慢一些(尽管在我看来它看起来很合理)测试)。它的速度要快得多,并且使用的内存非常少,因为它在创建时不会复制/转换输入数据。

相反,它使用自定义样本模型,其中DataBuffer.TYPE_USHORT作为传输类型,但使用DataBufferByte作为数据缓冲区。

private static BufferedImage createNoCopy(int w, int h, byte[] rawBytes) {
    DataBuffer dataBuffer = new DataBufferByte(rawBytes, rawBytes.length);

    int stride = 2;
    SampleModel sampleModel = new MyComponentSampleModel(w, h, stride);
    WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null);

    ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);

    return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}

private static class MyComponentSampleModel extends ComponentSampleModel {
    public MyComponentSampleModel(int w, int h, int stride) {
        super(DataBuffer.TYPE_USHORT, w, h, stride, w * stride, new int[] {0});
    }

    @Override
    public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
        if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) {
            throw new ArrayIndexOutOfBoundsException("Coordinate out of bounds!");
        }

        // Simplified, as we only support TYPE_USHORT
        int numDataElems = getNumDataElements();
        int pixelOffset = y * scanlineStride + x * pixelStride;

        short[] sdata;

        if (obj == null) {
            sdata = new short[numDataElems];
        }
        else {
            sdata = (short[]) obj;
        }

        for (int i = 0; i < numDataElems; i++) {
            sdata[i] = (short) (data.getElem(0, pixelOffset) << 8 | data.getElem(0, pixelOffset + 1));
            // If little endian, swap the element order, like this:
//            sdata[i] = (short) (data.getElem(0, pixelOffset + 1) << 8 | data.getElem(0, pixelOffset));
        }

        return sdata;
    }
}

如果您的图片在转换后看起来很奇怪,请尝试翻转字节序,如代码中所述。

最后,一些代码来练习上述内容:

public static void main(String[] args) {
    int w = 1760;
    int h = 2140;

    byte[] rawBytes = new byte[w * h * 2]; // This will be your input array, 7532800 bytes

    ShortBuffer buffer = ByteBuffer.wrap(rawBytes)
//            .order(ByteOrder.LITTLE_ENDIAN) // Try swapping the byte order to see sharp edges
            .asShortBuffer();

    // Let's make a simple gradient, from black UL to white BR
    int max = 65535; // Unsigned short max value
    for (int y = 0; y < h; y++) {
        double v = max * y / (double) h;

        for (int x = 0; x < w; x++) {
            buffer.put((short) Math.round((v + max * x / (double) w) / 2.0));
        }
    }

    final BufferedImage image = createNoCopy(w, h, rawBytes);
//    final BufferedImage image = createCopyUsingByteBuffer(w, h, rawBytes);

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            JFrame frame = new JFrame("Test");
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

            frame.add(new JScrollPane(new JLabel(new ImageIcon(image))));

            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    });
}

这里的输出应该是什么样子(按比例缩小到1/10):

enter image description here

答案 1 :(得分:0)

最简单的方法是创建TYPE_USHORT_GRAY类型的BufferedImage,这是用于16位编码的类型。

public BufferedImage Convert(short[] array, final int width, final int height)
    {
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY) ;
    short[] sb = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData() ;
    System.arraycopy(array, 0, sb, 0, array.length) ;
    return image ;
    }

然后,您可以使用Java.imageio将图像保存为TIFF或PNG。我认为Twelve Monkey Project可以为imageio提供更好的TIFF支持,但你必须先检查。

[编辑]在您的情况下,因为您处理无法存储到常规BufferedImage的巨大DICOM图像,您必须使用Unsafe类创建自己的类型以分配DataBuffer。

  1. 创建一个新类DataBufferLongShort,它将使用Unsafe类分配所需的数组/ DataBuffer。然后您可以使用Long索引而不是Integer
  2. 创建一个新类DataBuffer,扩展经典DataBuffer以添加类型TYPE_LONG_USHORT 然后,您可以使用新的DataBuffer创建ColorModel。