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

Pixel data of a 16-bit DICOM image to BufferedImage

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

如果您有任何问题,或者如果有任何我能解决的问题,请告诉我。

编辑:预期图像和当前图像

最简单的方法是创建 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。我认为十二猴子项目可以更好地支持 imageio 的 TIFF,但你必须先检查一下。

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

  1. 创建一个新的 class DataBufferLongShort,它将使用不安全 class 分配所需的 array/DataBuffer。然后你可以使用 Long 索引而不是 Integer
  2. 创建一个新的 class DataBuffer 扩展 classical DataBuffer 以添加类型 TYPE_LONG_USHORT 然后您可以使用新的 DataBuffer 创建 ColorModel。

给定原始字节数组的输入数据,其中包含无符号的 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 图像,显示速度可能较慢(尽管它看起来确实表现合理,在我的测试中)。它要快得多,并且使用很少的额外内存,因为它在创建时不 copying/conversion 输入数据。

相反,它使用自定义示例模型,将 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):