当前位置: 首页 > 知识库问答 >
问题:

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

长孙正卿
2023-03-14

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

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

共有1个答案

朱高超
2023-03-14

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

第一个会比较慢,因为它涉及到将byte数组复制到short数组中。它还需要两倍的内存。好处是它创建了一个标准的type_ushort_grayBufferedImage,显示速度更快,兼容性更强。

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图像,显示速度可能较慢(但在我的测试中,它似乎执行得合理)。它要快得多,而且使用的额外内存很少,因为它在创建时不复制/转换输入数据。

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;
    }
}

如果您的图像在转换后看起来很奇怪,请尝试翻转endianness,如代码中所注释的那样。

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

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):

 类似资料:
  • 问题内容: 我有一个从主监视器截取的图像,因此我想将其添加到Java FX : 我正在尝试将设置为,但是类型不兼容,也无法强制转换。我该如何纠正? 问题答案: 您可以使用

  • 我有一个来自主监视器的图像截图,我想将其添加到Java FX ImageView中,如下所示: 我试图将BufferedImage捕获设置为javafx。场景形象Image Image但是类型不兼容,我也无法转换它。我该如何纠正?

  • 问题内容: 如果我的窗口处于32位色深模式,那么下面的代码将从窗口中获取漂亮的PIL图像: 但是,以16位模式运行时,出现错误: 我应该如何形成以16位模式工作的电话?另外,如何使该函数在任何位深度模式下都能工作,而不必说必须将其作为参数传递呢? 更新:从这个问题中,我了解到我必须对第二个模式参数使用“ BGR; 16”而不是“ BGRX”。它可以拍出正确的照片,无论是否指定步幅。问题是像素值在某

  • 当我以tiff格式打开一个16位图像时,它会以黑色图像的形式打开。16位tiff图像仅在程序ImageJ中打开;但是,它不会在预览中打开。我想知道我现在的选择是什么,以一种不降低分辨率的更简单的方式查看格式,而不是打开ImageJ查看它。我是否应该将其转换为8位格式,但当格式从16位减少到8位时,是否会丢失数据?另外,我正在考虑将tiff图像转换为jpeg,但这会导致分辨率降低吗?

  • 我试图从bufferedimage中获取像素数据,该bufferedimage将只包含灰度半透明图像(argb)。我从图像中得到一个WritableRaster,并使用光栅的setPixels方法设置它的像素。使用此方法,我得到ArrayIndexOutOfBounds异常。在做了一些研究后,我发现BufferedImages在每个频带每个像素存储一个整数,而不是每个像素存储一个整数。也就是说,对

  • 我是第一次使用openCV。我正在使用openCV3和XCode对其进行编码。我想创建一个16位的灰度图像,但我想我有的数据是这样定义的,4000是像素值为白色和0为黑色。我在int类型的数组中有这些像素的信息。如何创建Mat并将数组中的值分配给Mat?