使用 tensorflow_io 将 DICOM 图像转换为 pixel_array 时出错

Error when converting DICOM image to pixel_array using tensorflow_io

我正在尝试使用 tf.data API 和 tensorflow_io 从 DICOM 图像创建 TensorFlow 数据集,我想使用图像中的 Hounsfield 单位执行一些预处理. DICOM 图像的形状为 (512,512)。我已经从图像中提取了 PixelData 并希望使用以下代码将其转换为适当形状的 numpy 数组:

image_bytes = tf.io.read_file(image_path)
PixelData = tfio.image.decode_dicom_data(image_bytes, tfio.image.dicom_tags.PixelData).numpy()
pixel_array = np.frombuffer(PixelData, dtype=tf.uint16)
pixel_array = np.reshape(pixel_array, (512,512))
print(pixel_array)

这段代码应该等同于

Image = pydicom.dcmread(image_path)
pixel_array = Image.pixel_array
print(pixel_array)

Image = pydicom.dcmread(image_path)
PixelData = Image.PixelData
pixel_array = np.frombuffer(PixelData, dtype=np.uint16)
pixel_array = np.reshape(pixel_array, (512,512))
print(pixel_array)

DICOM 标签与 pydicom 使用的标签相同,并给出 here。 PixelData 应该 return DICOM 图像的原始字节值。我已经通过 pydicom 确认原始像素数据存储为 np.uint16 值。但是,当我尝试使用 np.frombuffer 函数将 tensorflow 给出的字节数据转换为 numpy 数组时,出现缓冲区大小不能被元素长度整除的错误。

当我运行上述脚本时,这些是以下输出形状

  1. Tensorflow:不 运行 使用 tf.uint16,在使用 tf.uint8
  2. 时给出输出形状 (1310719,)
  3. Pydicom 直接 pixel_array:输出形状 (512,512)
  4. Pydicom PixelData 到 pixel_array:输出形状 (512,512)

pydicom 示例在两种情况下提供相同的输出,但是 tensorflow DICOM 标签似乎提供完全不同的结果。请查找随附的示例 DICOM 文件 here。库或我的实现有问题吗?

编辑: DICOM 图像实际上是有符号的 16 位整数,而不是无符号的。因此,以下三个代码片段产生相同的输出:

直接从 pydicompixel_array

import pydicom
import numpy as np

dcm = pydicom.dcmread("ID_000012eaf.dcm")
print(dcm.pixel_array)

手动将 PixelData 转换为 pixel_array

import pydicom
import numpy as np
dcm = pydicom.dcmread("ID_000012eaf.dcm")
PixelData = dcm.PixelData
pixel_array = np.frombuffer(PixelData, dtype=np.int16)
pixel_array = np.reshape(pixel_array, (512,512))
print(pixel_array)

使用tensorflow_io

直接获取pixel_array
import tensorflow as tf
import tensorflow_io as tfio
import numpy as np
image_bytes = tf.io.read_file("ID_000012eaf.dcm")
pixel_array = tfio.image.decode_dicom_image(image_bytes, on_error='lossy', scale='preserve', dtype=tf.float32).numpy()
pixel_array = pixel_array.astype('int16')
pixel_array /= 2.
pixel_array = np.reshape(pixel_array, (512,512))
print(pixel_array)

但是,由于某些原因,这最后一段代码仍然不起作用:

import tensorflow as tf
import tensorflow_io as tfio
import numpy as np

image_bytes = tf.io.read_file("ID_000012eaf.dcm")
PixelData = tfio.image.decode_dicom_data(image_bytes, tfio.image.dicom_tags.PixelData).numpy()
pixel_array = np.frombuffer(PixelData, dtype=np.int16)
print(pixel_array)

编辑 2:这两个代码片段理论上应该可以工作,但是它们显示错误,即字节字符串的长度不能被 int16 的大小整除:

import tensorflow as tf
import tensorflow_io as tfio
import numpy as np
image_bytes = tf.io.read_file("ID_000012eaf.dcm")
PixelData = tfio.image.decode_dicom_data(image_bytes, tfio.image.dicom_tags.PixelData).numpy()
pixel_array =  np.frombuffer(PixelData, dtype=np.int16)
pixel_array = np.reshape(pixel_array, (512,512))
print(pixel_array)

import tensorflow as tf
import tensorflow_io as tfio
import numpy as np
image_bytes = tf.io.read_file("ID_000012eaf.dcm")
PixelData = tfio.image.decode_dicom_data(image_bytes, tfio.image.dicom_tags.PixelData)
pixel_array = tf.io.decode_raw(PixelData, tf.int16)
pixel_array = tf.reshape(pixel_array, (512,512))
print(pixel_array)

编辑3:得到decode_dicom_data提供的字节串包含十六进制值的提示后,我找到了一种方法将我的数据转换成所需的pixel_array,但我很好奇为什么PixelData 以这种方式存储:

import tensorflow as tf
import tensorflow_io as tfio
import numpy as np
image_bytes = tf.io.read_file("ID_000012eaf.dcm")
PixelData = tfio.image.decode_dicom_data(image_bytes, tfio.image.dicom_tags.PixelData).numpy()
pixel_array = np.zeros(262144, dtype=np.int16)
start,stop = 0,4
for i in range(262144):
    pixel_array[i] = int(PixelData[start:stop], base=16)
    start+=5
    stop+=5
pixel_array = np.reshape(pixel_array, (512,512))
print(pixel_array)

来自 pydicom 的像素数据:

PixelData = b'0\xf80\xf80\xf80...'

来自 Tensorflow_io

的像素数据
PixelData = b'f830\f830\f830\...'

任何有关代码重构和 linting 的建议都将不胜感激。非常感谢@ai2ys 帮助我诊断这些问题。

函数tfio.image.decode_dicom_data解码标签信息而不是像素信息。

要读取像素数据,请改用 tfio.image.decode_dicom_image

import tensorflow_io as tfio

image_bytes = tf.io.read_file(image_path)
pixel_data = tfio.image.decode_dicom_image(
    image_bytes,
    dtype=tf.uint16)

# type conversion and reshaping is not required
# as can be checked with the print statement
print(pixel_data.dtype, pixel_data.shape)

# if required the pixel_data can be converted to a numpy array
# but calculations like scaling and offset correction can 
# be done on tensors as well
pixel_data_nparray = pixel_data.numpy()

# reading tag information, e.g. rescale intercept and slope
intersept = tfio.image.decode_dicom_data(
    image_bytes, 
    tfio.image.dicom_tags.RescaleIntercept)
slope = tfio.image.decode_dicom_data(
    image_bytes,
    tfio.image.dicom_tags.RescaleSlope)

print(intersept)
print(slope)

请查看文档以获取更多信息:

使用共享文件编辑 2021-02-01:

也可以使用 tfio.image.decode_dicom_data 并传递 tfio.image.dicom_tags.PixelData 来读取像素数据,但是必须对返回的字节字符串进行解码。

data = tfio.image.decode_dicom_data(image_bytes, tfio.image.dicom_tags.PixelData)
print(data)

输出(缩短):

tf.Tensor(b'f830\f830\f830\f830\ ...')

被解释为 int16 的十六进制值 f830-2000

我发现了问题: 我拥有的图像是带符号的 16 位整数数据类型,但 tensorflow_io 库中不存在此类选项。将数组值转换为 16 位有符号数后,问题就解决了。我必须在 decode_dicom_image 函数中将数据转换为更高的数据类型,如 float32,在 numpy 中重新转换为 signed int16,最后除以 2(不确定为什么最后一步),但我最终得到了 pixel_array 与 pydicom 的输出相同。

现在除了从 dicom_tag PixelData 转换数据外,一切都有意义了,它仍然显示无法解释的行为。我已经更新了这里的 python 脚本,为任何感兴趣的人展示了不同库 here 的不同 DICOM 图像转换方法。