You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将3D NumPy数组写入DICOM时的PixelData问题求助

问题描述

我希望将灰度3D像素数组写入DICOM文件,但此前无该复杂文件格式的使用经验。使用pydicom库操作时出现疑似与PixelData属性相关的错误,不知如何修复。

最小复现代码

import os
import math
import numpy as np
import datetime as dt
from pydicom.dataset import FileDataset, FileMetaDataset
import pydicom._storage_sopclass_uids

dir_dcm = os.getcwd()
im_stack =  np.random.randint(0, (2**16)-1, size=(8406, 900, 900), dtype='uint16')
max_val = np.max(im_stack, axis=(0, 1, 2))
bts_stored =  int(math.log(max_val, 2))
max_val_hex = hex(max_val)
min_val_hex = hex(0)
dt = dt.datetime.now()
  
file_meta = FileMetaDataset()
file_meta.ImplementationClassUID = '1.2.3.4'
file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
file_meta.MediaStorageSOPClassUID = pydicom._storage_sopclass_uids.CTImageStorage
file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRBigEndian
  
pydicom.dataset.validate_file_meta(file_meta, enforce_standard=True)

path_dcm = dir_dcm + '.dcm'

mf_ds = FileDataset(path_dcm, {}, file_meta=file_meta)
mf_ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.14.1'
mf_ds.PatientName = "name^surname"
mf_ds.PatientID = "00-000-000"
mf_ds.ContentDate = dt.strftime('%Y%m%d')
mf_ds.ContentTime = dt.strftime('%H%M%S.%f')
mf_ds.is_little_endian = True
mf_ds.is_implicit_VR = False
mf_ds.SpecificCharacterSet = 'ISO_IR 100'
mf_ds.SpacingBetweenSlices = r"0.5" 
mf_ds.PixelSpacing = r"0.5\\0.5"
mf_ds.ImageOrientationPatient = r"1\\0\\0\\0\\1\\0"
mf_ds.PhotometricInterpretation = "MONOCHROME2"
mf_ds.PixelRepresentation = 0
mf_ds.PlanarConfiguration = 0
mf_ds.SmallestImagePixelValue = str.encode(min_val_hex)  # zero
mf_ds.LargestImagePixelValue = str.encode(max_val_hex)

mf_ds.TotalPixelMatrixRows = im_stack.shape[2]
mf_ds.TotalPixelMatrixColumns = im_stack.shape[1]
mf_ds.Rows = im_stack.shape[2]
mf_ds.Columns = im_stack.shape[1]
mf_ds.NumberOfFrames = im_stack.shape[0]

mf_ds.SamplesPerPixel = 1
mf_ds.BitsAllocated = 16
mf_ds.BitsStored = bts_stored
mf_ds.HighBit = bts_stored-1
mf_ds.PixelRepresentation = 0

mf_ds.PixelData = im_stack.tobytes()

mf_ds.save_as(dir_dcm+'.dcm', write_like_original=False)

错误信息

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\spyder_kernels\py3compat.py", line 356, in compat_exec
exec(code, globals, locals)

File "c:\users\kahy\skogforsk\ct-tör - general\kod\untitled0.py", line 65, in
mf_ds.save_as(dir_dcm+'.dcm', write_like_original=False)

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\dataset.py", line 2061, in save_as
pydicom.dcmwrite(filename, self, write_like_original)

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\filewriter.py", line 1153, in dcmwrite
_write_dataset(fp, dataset, write_like_original)

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\filewriter.py", line 889, in _write_dataset
write_dataset(fp, get_item(dataset, slice(0x00010000, None)))

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\filewriter.py", line 645, in write_dataset
dataset = correct_ambiguous_vr(dataset, fp.is_little_endian)

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\filewriter.py", line 242, in correct_ambiguous_vr
correct_ambiguous_vr_element(elem, ds, is_little_endian)

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\filewriter.py", line 198, in correct_ambiguous_vr_element
_correct_ambiguous_vr_element(elem, ds, is_little_endian)

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\filewriter.py", line 119, in _correct_ambiguous_vr_element
elem.value = convert_numbers(

File "C:\Program Files\Miniconda3\envs\ct_env2\lib\site-packages\pydicom\values.py", line 389, in convert_numbers
raise BytesLengthException(

BytesLengthException: Expected total bytes to be an even multiple of bytes per value. Instead received b'0x0' with length 3 and struct format 'H' which corresponds to bytes per value of 2.


解决方法

错误原因

错误并非来自PixelData,而是SmallestImagePixelValue和LargestImagePixelValue字段。你用hex()把数值转成十六进制字符串(比如0x0)再编码成字节,但DICOM要求这两个字段是16位无符号整数,不是字符串格式。pydicom解析时把b'0x0'当成要转成16位整数的字节流,但其长度为3,不符合2字节的要求,因此报错。

另外还有几个潜在问题:

  1. BitsStored计算错误:math.log(max_val,2)得到浮点数,转整数会丢失精度,比如max_val=65535时,计算结果≈15.9999,转int后为15,实际应为16。
  2. SOPClassUID与MediaStorageSOPClassUID不匹配:前者用了增强CT存储UID,后者用了普通CT存储UID,需要统一。
  3. 部分属性赋值冗余:SpacingBetweenSlices、PixelSpacing等字段不需要手动加双反斜杠,pydicom会自动处理格式。

修正后的代码

import os
import numpy as np
import datetime as dt
from pydicom.dataset import FileDataset, FileMetaDataset
import pydicom._storage_sopclass_uids

dir_dcm = os.getcwd()
# 生成测试用3D像素数组
im_stack = np.random.randint(0, (2**16)-1, size=(8406, 900, 900), dtype='uint16')
max_val = np.max(im_stack)
min_val = np.min(im_stack)
# 正确计算BitsStored:取最高有效位长度
bts_stored = max_val.bit_length() if max_val !=0 else 1
current_dt = dt.datetime.now()
  
# 构建文件元数据
file_meta = FileMetaDataset()
file_meta.ImplementationClassUID = '1.2.3.4'
file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
# 统一使用支持多帧的增强CT存储UID
enhanced_ct_uid = pydicom._storage_sopclass_uids.EnhancedCTImageStorage
file_meta.MediaStorageSOPClassUID = enhanced_ct_uid
file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRBigEndian
  
pydicom.dataset.validate_file_meta(file_meta, enforce_standard=True)

path_dcm = os.path.join(dir_dcm, "output.dcm")

mf_ds = FileDataset(path_dcm, {}, file_meta=file_meta, preamble=b"\x00"*128)
mf_ds.SOPClassUID = enhanced_ct_uid
mf_ds.PatientName = "name^surname"
mf_ds.PatientID = "00-000-000"
mf_ds.ContentDate = current_dt.strftime('%Y%m%d')
mf_ds.ContentTime = current_dt.strftime('%H%M%S.%f')[:12]  # DICOM时间最多12位
mf_ds.is_little_endian = True
mf_ds.is_implicit_VR = False
mf_ds.SpecificCharacterSet = 'ISO_IR 100'
# 直接用数值或列表赋值,无需手动加反斜杠
mf_ds.SpacingBetweenSlices = 0.5
mf_ds.PixelSpacing = [0.5, 0.5]
mf_ds.ImageOrientationPatient = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
mf_ds.PhotometricInterpretation = "MONOCHROME2"
mf_ds.PixelRepresentation = 0  # 无符号整数
mf_ds.PlanarConfiguration = 0
# 直接赋值整数,符合DICOM的US类型要求
mf_ds.SmallestImagePixelValue = min_val
mf_ds.LargestImagePixelValue = max_val

# 多帧图像维度信息
mf_ds.TotalPixelMatrixRows = im_stack.shape[2]
mf_ds.TotalPixelMatrixColumns = im_stack.shape[1]
mf_ds.Rows = im_stack.shape[2]
mf_ds.Columns = im_stack.shape[1]
mf_ds.NumberOfFrames = im_stack.shape[0]

mf_ds.SamplesPerPixel = 1
mf_ds.BitsAllocated = 16
mf_ds.BitsStored = bts_stored
mf_ds.HighBit = bts_stored - 1
mf_ds.PixelRepresentation = 0

# 直接传入数组字节流
mf_ds.PixelData = im_stack.tobytes()

mf_ds.save_as(path_dcm, write_like_original=False)

关键修正点说明

  1. 像素最值字段修正:将十六进制编码字符串改为直接赋值整数,符合DICOM对这两个字段的VR(US,无符号16位整数)定义。
  2. BitsStored计算优化:用max_val.bit_length()替代math.log,确保得到准确的存储位数,避免精度丢失。
  3. SOP类UID统一:使用EnhancedCTImageStorage,适配多帧3D图像的存储需求,与NumberOfFrames字段匹配。
  4. 属性赋值简化:PixelSpacing、ImageOrientationPatient直接用列表赋值,pydicom会自动转换为DICOM要求的字符串格式。
  5. 时间格式修正:截取ContentTime前12位,符合DICOM对时间字段的长度限制。

内容的提问来源于stack exchange,提问作者user8188435

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 20:12:32