Java 缓冲图像的16位DICOM图像的像素数据
我有一个字节数组,存储一个已经解构的DICOM文件中的16位像素数据。我现在需要做的是以某种方式将像素数据转换/导出为TIFF文件格式。我正在使用imageio-tiff-3.3.2.jar插件来处理tiff转换/头数据。但现在我需要将该图像数据数组打包到原始图像维度的BuffereImage中,以便将其导出到TIFF。但BuffereImage似乎不支持16位图像。有没有办法解决这个问题,比如外部库?有没有其他方法可以将图像数据打包成原始DICOM尺寸的TIFF图像?记住,这个过程必须是完全无损的。在过去的几天里,我环顾四周,尝试了一些东西,但到目前为止,没有任何东西对我有效 如果您有任何问题或者我可以做些什么来澄清任何困惑,请告诉我 编辑:预期图像和当前图像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图像?记住,这个过程必须是完全无
最简单的方法是创建类型为_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类似。要正确显示值,只需反转值即可。但是您需要一些外部标志来告诉这些值是否要反转,因为您无法从数据中看到这一点。