【问题标题】:Error when converting DICOM image to pixel_array using tensorflow_io使用 tensorflow_io 将 DICOM 图像转换为 pixel_array 时出错
【发布时间】:2022-01-30 00:46:59
【问题描述】:

我正在尝试使用 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 应该返回 DICOM 图像的原始字节值。我已通过 pydicom 确认原始像素数据存储为 np.uint16 值。但是,当我尝试使用 np.frombuffer 函数将 tensorflow 给出的字节数据转换为 numpy 数组时,出现缓冲区大小不能被元素长度整除的错误。

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

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

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

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

直接来自 pydicom 的 pixel_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)

但是,由于某种原因,这个最终的 sn-p 代码仍然不起作用:

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:这两个 sn-ps 代码理论上应该可以工作,但是它们显示错误,即字节字符串的长度不能被 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 帮助我诊断这些问题。

【问题讨论】:

  • 如果我在下面的回答回答了你的问题,你会点击“复选标记”。谢谢

标签: python numpy tensorflow pydicom medical-imaging


【解决方案1】:

函数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\\ ...')

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

【讨论】:

  • @KumareshBalaji 您是否尝试过文档链接中引用的 Colab 示例? tensorflow.org/io/tutorials/dicom
  • 我已经尝试过您建议的解决方案。对于我的大多数文件,尽管可以使用 pydicom 读取,但该函数仍会出现错误。当我设置参数 on_error='lossy' 和 scale='auto' 时,我能够将它们转换为接近 pydicom PixelData 提供的值的值,但仍然不同于 pydicom pixel_array 值。我的示例 DICOM 图像和代码的链接是 here
  • 是的,我已经试用了here 提供的协作笔记本。我还链接了我在之前评论中使用的确切文件和代码。
【解决方案2】:

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

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

【讨论】:

  • PixelData 并没有真正无法解释的行为。它也包含数据,但必须从包含十六进制值的字符串转换,并且还必须根据尺寸重新调整形状。
  • @ai2ys 太好了,谢谢你告诉我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多