Java 缓冲图像的16位DICOM图像的像素数据

Java 缓冲图像的16位DICOM图像的像素数据,java,bufferedimage,tiff,Java,Bufferedimage,Tiff,我有一个字节数组,存储一个已经解构的DICOM文件中的16位像素数据。我现在需要做的是以某种方式将像素数据转换/导出为TIFF文件格式。我正在使用imageio-tiff-3.3.2.jar插件来处理tiff转换/头数据。但现在我需要将该图像数据数组打包到原始图像维度的BuffereImage中,以便将其导出到TIFF。但BuffereImage似乎不支持16位图像。有没有办法解决这个问题,比如外部库?有没有其他方法可以将图像数据打包成原始DICOM尺寸的TIFF图像?记住,这个过程必须是完全无

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

如果您有任何问题或者我可以做些什么来澄清任何困惑,请告诉我

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

最简单的方法是创建类型为_USHORT_GRAY的BuffereImage,该类型用于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,但您必须首先检查

[编辑]在您的情况下,因为您处理的是无法存储到常规BuffereImage中的巨大DICOM映像,因此您必须使用分配给DataBuffer的不安全类创建自己的类型

创建一个新的类DataBufferLongShort,该类将使用不安全的类分配所需的数组/DataBuffer。然后可以使用长索引而不是整数 创建一个新的类DataBuffer,该类扩展了经典的DataBuffer,以便添加类型“LONG”USHORT 然后可以使用新的DataBuffer创建ColorModel。
最简单的方法是创建一个类型为_USHORT_GRAY的BuffereImage,该类型用于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,但您必须首先检查

[编辑]在您的情况下,因为您处理的是无法存储到常规BuffereImage中的巨大DICOM映像,因此您必须使用分配给DataBuffer的不安全类创建自己的类型

创建一个新的类DataBufferLongShort,该类将使用不安全的类分配所需的数组/DataBuffer。然后可以使用长索引而不是整数 创建一个新的类DataBuffer,该类扩展了经典的DataBuffer,以便添加类型“LONG”USHORT 然后可以使用新的DataBuffer创建ColorModel。
给定原始字节数组的输入数据,其中包含无符号16位图像数据,这里有两种创建BuffereImage的方法

第一个比较慢,因为它需要将字节数组复制到一个短数组中。它还需要两倍的内存量。好处是它创建了一个标准类型的_USHORT_GRAY buffereImage,它可以更快地显示,并且可能更兼容

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_自定义图像,显示速度可能会慢一些。不过,在我的测试中,它的性能似乎是合理的。它的速度更快,并且使用很少的额外内存,因为它不会在创建时复制/转换输入数据

相反,它使用一个定制的示例模型,该模型将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时的输出:


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

第一个比较慢,因为它需要将字节数组复制到一个短数组中。它还需要两倍的内存量。好处是它创建了一个标准类型的_USHORT_GRAY buffereImage,它可以更快地显示,并且可能更兼容

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_自定义图像,显示速度可能会慢一些。不过,在我的测试中,它的性能似乎是合理的。它的速度更快,并且使用很少的额外内存,因为它不会在创建时复制/转换输入数据

相反,它使用一个定制的示例模型,该模型将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时的输出:


我以前尝试过使用这种方法,但是图像数据不够大。用corr

ect宽度和高度,光栅缓冲区对于我的总像素数据来说似乎太小了50%。我将对此进行进一步调查,但在此期间,您是否有任何初步想法?不幸的是,您正面临BuffereImage的限制:-。唯一的解决方案是创建一个TYPE_自定义映像,您可以在其中使用类safe创建自己的数据缓冲区。为了简化这种创建,您可以使用与库JTransform关联的包JLargeArrays。您必须这样做,因为DataBuffer长度可能大于最大整数值。您有任何示例代码来说明如何做到这一点吗?我一直在尝试制作我自己的16位像素深度的定制颜色模型,但我仍然无法让整个shebang正常工作。我已经更新了我的答案。不幸的是,我以前没有这样做过,但根据我在代码中看到的内容,我会这样做。我以前尝试过使用这种方法,但图像数据不够大。如果宽度和高度正确,光栅缓冲区对于我的总像素数据来说似乎太小了50%。我将对此进行进一步调查,但在此期间,您是否有任何初步想法?不幸的是,您正面临BuffereImage的限制:-。唯一的解决方案是创建一个TYPE_自定义映像,您可以在其中使用类safe创建自己的数据缓冲区。为了简化这种创建,您可以使用与库JTransform关联的包JLargeArrays。您必须这样做,因为DataBuffer长度可能大于最大整数值。您有任何示例代码来说明如何做到这一点吗?我一直在尝试制作我自己的16位像素深度的定制颜色模型,但我仍然无法让整个shebang正常工作。我已经更新了我的答案。不幸的是,我以前没有这样做过,但根据我在代码中看到的情况,我会这样做。BuffereImage支持16位/样本图像的许多不同变体,即使除了灰色数据类型之外没有类型常数。您的数据有多少个通道?是灰色的吗?RGB?另外尺寸w*h是多少?…数组的长度是多少?它真的是一个具有16位值的字节数组吗?还是短数组?数据应该被视为有符号值还是无符号值?@haraldK我使用的是灰度无符号16位图像。宽度x高度为1760 x 2140。字节[]的长度为7532800。它包含16位balues.BufferedImage支持16位/样本图像的许多不同变体,即使除了灰色数据类型之外没有类型常数。您的数据有多少个通道?是灰色的吗?RGB?另外尺寸w*h是多少?…数组的长度是多少?它真的是一个具有16位值的字节数组吗?还是短数组?数据应该被视为有符号值还是无符号值?@haraldK我使用的是灰度无符号16位图像。宽度x高度为1760 x 2140。字节[]的长度为7532800。它包含16位平衡块,工作非常出色。非常感谢你!我现在有1000%的兴奋,因为这段代码在我的两个项目中解决了多少问题。事实上,这张图片看起来确实有点奇怪。看起来明暗通道已经切换。你能用代码解释一下翻转端点是什么意思吗?我试过我认为你的意思,但它给了我同样的结果。非常感谢。嗯,在你给我的新例子中起作用了。我要挖一些。看看主项目代码中发生了什么,知道吗?Neeevermind!结果表明,您/我的代码没有问题,这是示例图像的问题。我的同事正在matlab中做一个类似的项目,他对这个特定的图像有着完全相同的问题。考虑到这一点,这个问题就结束了。非常感谢你的帮助@Sarah是的,它现在看起来是正确的,不同的是,与预期的图像相比,值是反向的。可能是这些值的存储方式与TIFF PhotometricInterpretation MiniWhite又名WhiteIsZero类似。要正确显示值,只需反转值即可。但是您需要一些外部标志来告诉这些值是否要反转,因为您无法从数据中看到这一点。这非常有效。非常感谢你!我现在有1000%的兴奋,因为这段代码在我的两个项目中解决了多少问题。事实上,这张图片看起来确实有点奇怪。看起来明暗通道已经切换。你能用代码解释一下翻转端点是什么意思吗?我试过我认为你的意思,但它给了我同样的结果。非常感谢。嗯,在你给我的新例子中起作用了。我要挖一些。看看主项目代码中发生了什么,知道吗?Neeevermind!结果表明,您/我的代码没有问题,这是示例图像的问题。我的同事正在matlab中做一个类似的项目,他对这个特定的图像有着完全相同的问题。考虑到这一点,这个问题就结束了。非常感谢你的帮助@ Sarah是的,它现在看起来是正确的,不同的是,与预期的图像相比,值是反向的。可能是这些值的存储方式与TIFF PhotometricInterpretation MiniWhite又名WhiteIsZero类似。要正确显示值,只需反转值即可。但是您需要一些外部标志来告诉这些值是否要反转,因为您无法从数据中看到这一点。