如何为DICOM文件生成SOPInstance UID?

7
我正在开发一个系统,它可以为PACS创建结构化报告。显然,为了创建包含报告数据的DICOM实例文件,我需要三个用于研究、系列和实例的UID。StudyUID和SeriesUID必须与创建报告的研究和系列相同。但是对于SOPInstanceUID,我需要生成新的UID。
我看过Pixelmed文档中的getNewSOPInstanceUID方法,但我不熟悉Pixelmed源代码。我需要一个算法或Python源代码。

5
你需要的不仅仅是一个算法。DICOM UID必须是全球唯一的,因此,您将需要向指定机构之一注册以获取用于生成所有UID的根词干。 - Matt
4个回答

12

在DICOM中创建UID有两种方式。一种是基于注册的UID根,另一种是基于UUID。后一种方法是在2012年通过CP-1156添加到DICOM标准中的。例如Study UID、Series UID和SOP Instance UID等的UID可以通过将UUID转换为DICOM UID来创建。

大多数编程语言都内置了创建UUID的支持。下面的示例代码基于GUID值在C#中创建一个有效的DICOM UID。

public static string GuidToUidStringUsingStringAndParse(Guid value)
{
    var guidBytes = string.Format("0{0:N}", value);
    var bigInteger = BigInteger.Parse(guidBytes, NumberStyles.HexNumber);
    return string.Format(CultureInfo.InvariantCulture, "2.25.{0}", bigInteger);
}
以下方法执行相同操作,但速度快约5倍:
public static string ConvertGuidToUuidInteger(ref Guid value)
{
    // ISO/IEC 9834-8, paragraph 6.3 (referenced by DICOM PS 3.5, B.2) defines how
    // to convert a UUID to a single integer value that can be converted back into a UUID.

    // The Guid.ToByteArray Method returns the array in a strange order (see .NET docs),
    // BigInteger expects the input array in little endian order.
    // The last byte controls the sign, add an additional zero to ensure
    // the array is parsed as a positive number.
    var octets = value.ToByteArray();
    var littleEndianOrder = new byte[]
    { octets[15], octets[14], octets[13], octets[12], octets[11], octets[10], octets[9], octets[8],
        octets[6], octets[7], octets[4], octets[5], octets[0], octets[1], octets[2], octets[3], 0 };

    return "2.25." + new BigInteger(littleEndianOrder).ToString(CultureInfo.InvariantCulture);
}

1
虽然我喜欢这个答案的简洁性,但应该注意到你只是“限制”(这么说)自己只使用UUID的128位部分。而DICOM UID提供了更广泛的范围... - malat
1
DICOM标准要求,如果使用UUID作为DICOM UID,则必须使用第5部分B.2段指定的格式。 这是有道理的,128位UUID已经是静态唯一的(参见维基百科),添加更多字节并不会使其更加唯一。添加额外的字节对于其他系统从接收到的DICOM对象中提取UUID部分也会带来问题。 - Victor Derks
@VictorDerks:我在我的另一个答案这里中引用了第一个代码块。核心逻辑是相同的,只是添加了一些改进和解释。 - Amit Joshi

8
请参考此答案以获取有关DICOM UID的更多细节。 A] 计数器递增(不建议使用) 一个简单的方法是获取SeriesInstanceUID并将其增加1。 例如,如果你的SeriesInstanceUID为“1.1.1.1.1”,则SOPInstanceUID可以是“1.1.1.1.2”或“1.1.1.1.1.1”。
问题:
  • 当实例被删除并创建下一个实例时,早期的计数器不应该被使用。
  • 在多线程环境中,需要足够的注意。
  • 不能保证在不同系统/应用程序之间唯一性。
B] 日期时间(不建议使用) 通常使用的另一种技术是将时间戳(带有ticks)附加到组织根。
问题:
  • 多线程环境是个问题。
  • 系统时钟可能出错。
  • 不能保证在不同系统/应用程序之间的唯一性。
C] 更复杂的方法(推荐使用)
1.2.840.xxxxx.30.152.99999.235.20.100.yyyyMMddHHmmss.zzzzzz

具体含义:

1.2.840.xxxxx: 组织根
30: 应用程序ID
152: 应用程序版本
99999: 安装/位置ID
235: 研究ID
20: 系列号
100: 图像号
yyyyMMddHHmmss: 日期时间
zzzzzz: 线程安全计数器/随机数

问题:

  • 如果系统时钟出现故障,则算法可能会失败;这通过线程安全计数器/随机数进行保护。这种情况很少见,但需要小心对待。

D] 基于UUID的UID [建议使用]

UID可以由根"2.25."后跟通用唯一标识符(UUID)的十进制表示形式生成。

问题:

  • 这可能适用于动态创建的UID(例如SOP实例UID),但不适用于在设计期间确定的UID,例如私有SOP类或传输语法UID或实施类UID。
  • UID仅限于128位。 DICOM UID支持更广泛的范围。

DICOM标准允许使用基于UUID的UID用于“设计”UID,例如传输语法,但是使用“组织根”格式更受欢迎,因为它可以更轻松地检测到谁设计了新的传输语法。软件工程师需要一些时间来创建这样的新UID,运行应用程序只需要动态创建UID即可。 - Victor Derks

2
根据DICOM标准(PS 3.5-2011第61页),您需要一个orgroot和一个后缀。示例可以在此处找到(PS 3.5-2011第83页)。
另外,请不要忘记如果UI字段长度不是偶数,则必须使用'\0'字节而不是空格进行填充。
我建议按以下方式创建UID:

YOUR_ORGROOT.Year.Month.Day.Hour.Minute.Second.Milliseconds.StaticCounter

注意,限制为64个字符!

1
您缺少时间上唯一性之前的空间条件,请参阅RFC 4122。应更类似于:YOUR_ORGROOT.SPATIAL.TIME。 - malat

-1

我真的建议你不要自己实现它。现在大多数语言都提供了UUID库,不要重复造轮子。特别是如果你要编写提取MAC地址的代码,这可能非常复杂,需要用可移植的C语言编写。

UUID并不完全符合DICOM定义,因此您需要注册自己的组织根UID,然后只需使用生成的UUID填充即可满足空间和时间唯一性条件。

YOUR_ORG_ROOT.CONVERTED_UUID

请注意,您在Value Representation UI中有64个字节的存储空间(已经足够了,请参见这里):

  • 将UUID的十六进制表示转换为VR:UI定义([0-9.]+)
  • 非常小心地修剪(在此操作期间可能会引入冗余)
  • 选择一个短的Org Root
  • 如果需要,请使用\0(0二进制)进行填充。

最后,由于您正在使用Python,请使用uuid库python-uuid


上述内容应被视为标准中官方定义的替代实现:

当直接将UUID转换为UID时,必须使用“2.25.”根。


1
不要自己创建UUID算法的建议非常明智。将UUID转换为DICOM UID的步骤是不正确的。DICOM标准对该转换有特定的规则(参见第5部分,第b.2段)。 - Victor Derks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接