Использование библиотеки DCMTK для создания DICOM-файлов на C++

Ссылка на оригинал, попытался дополнить и объяснить некоторые не понятные детали.

Введение

Эта статья фокусируется на примере использование библиотеки DCMTK при создании DICOM-файлов. Как говорит Википедия, DICOM — Digital Imaging and Communications in Medicine, это стандарт создания, хранения, передачи и визуализации медицинских изображений. Стандарт включает в себя часть, которая описывает структуру DICOM-файла, и другую, описывающую передачу DICOM-данных по сети.

DCMTK обеспечивает строгую совместимость с DICOM-стандартом, предоставляя широкий спектр функциональности для обработки изображений, текстовой информации и метаданных. Библиотека поддерживает различные форматы изображений, унифицирует данные и обеспечивает эффективный обмен информацией в медицинском сообществе.

Современные МРТ и КТ устройства по умолчанию создают медицинские изображения и передают их на PACS-сервер для хранения, используя стандарт DICOM. Но цифровые медицинские изображения не обязательно должны быть топографическими, а могут быть обычными цветными или черно-белыми фотографиями, например, снимок сетчатки глаза. Такие снимки зачастую хранятся в виде: описание пациента + jpg снимок. Чтобы хранить такие изображения на PACS-серверах, их нужно преобразовать в DICOM.

В данной статье мы углубимся в практическую сторону вопроса, рассмотрев конкретный пример создания файла DICOM из изображения формата *.dcm на языке C++ для последующей его отправки на PACS-сервер.

Подготовка

Думаю, стоит упомянуть, что я использую для разработки:

  • Windows 10, x64

  • Visual Studio 2022

  • MSVC v. 14

  • CMake v. 3.28.2

  • DCMTK v. 3.6.6

Для начала нужно скачать и установить библиотеку DCMTK. После сборки библиотеки и подключения её к своему проекту, стоит уточнить, что необходимо всегда открыть Visual Studio 2022 с правами администратора — поможет избежать большей части ошибок.

Написание программы

(https://brandres.medium.com/building-a-simple-dicom-application-with-c-and-dcmtk-in-visual-studio-2019–5aacc1e0854e)

#include 
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/libi2d/i2d.h"
#include "dcmtk/dcmdata/libi2d/i2djpgs.h"
#include "dcmtk/dcmdata/libi2d/i2dplsc.h"
#include "dcmtk/dcmdata/dctk.h"
using namespace std;

Немного о библиотеках:

  • dcmtk/config/osconfig.h — содержит настройки конфигурации для операционной системы;

  • dcfilefo.h — определяет класс «DcmFileFormat», который представляет DICOM-файл в формате File Format (включая метаданные и изображения). Содержит функции и методы для чтения, записи и манипуляций с данными DICOM в рамках файла формата File Format. Пример использования «DcmFileFormat» может включать создание нового DICOM-файла, добавление метаданных и изображений, а также сохранение или чтение DICOM-файла.

  • i2d.h — Предоставляет средства для кодирования изображений в формат DICOM, чтобы они могли быть интегрированы в DICOM-формат;

  • i2djpgs.h — Предоставляет средства для преобразования изображений в формат JPEG и интеграции их в DICOM-датасет для последующего сохранения в DICOM-файл с использованием JPEG-сжатия;

  • dctk.h — Основной заголовочный файл для компонентов, относящихся к обработке данных.

Объявляем переменные:

char uid[100];
I2DImgSource* inputPlug = new I2DJpegSource();
I2DOutputPlug* outPlug = new I2DOutputPlugSC();
Image2Dcm i2d;
E_TransferSyntax writeXfer;
DcmDataset* resultDset = NULL;
  1. Массив символов длиной в 100 элементов. Зачем он нам нужен поговорим подробней далее.

  2. Создается объект «I2DImgSource» с именем «inputPlug», который инициализируется конструктором «I2DJpegSource». Этот объект представляет источник изображения в формате JPEG.

  3. Все точно так же, как и в переменной, указанной выше, только этот объект представляет собой выходной плагин для DICOM.

  4. Класс, реализующий движок image2dcm. Преобразование происходит путем объединения входного плагина, считывающего формат изображения общего назначения, и выходных плагинов для преобразования в определенные классы DICOM SOP

  5. Перечисление (enum), представляющие различные синтаксисы передачи данных. Этот перечислитель определяет различные стандартные синтаксисы. хранилище метаданных пациентов и их изображений. (чуть подробнее на этой переменной остановимся дальше)

  6. Класс, работающий с форматом набора данных DICOM (файлы без мета-заголовка)

После объявления всех переменных необходимо загрузить изображение, к которому и будут цепляться метаданные. В нашем случае — test.jpg.

ca634f6800da4b8856ddc8e11a9f8185.jpgacca59f9748987bac66c8376c8a498ba.JPG

inputPlug->setImageFile("test.jpg");

Подробнее о E_TransferSyntax

E_TransferSyntax — представляет собой перечисление (enum) в библиотеке DCMTK, которое определяет различные форматы передачи данных (transfer syntax) в стандарте DICOM. Формат передачи данных определяет, как данные закодированы и передаются между устройствами или программами. Этот тег необходим для передачи DICOM файла по сети, например, для хранения на PACS-сервер. Без него в принципе возможно создать DICOM файл, но невозможно передать его по сети, PACS-сервер не примет передачу.

По умолчанию, для DICOM изображений используется синтаксис VR Little Endian, который задается следующей строкой:»1.2.840.10008.1.2». В нашем случае, для передачи JPEG существует отдельный набор правил кодирования — JPEG Baseline (Process 1), что означает использование базового профиля JPEG для сжатия изображений, который задается такой строкой:»1.2.840.10008.1.2.4.50».

Теперь все готово для преобразования изображения в формат DICOM:

i2d.convert(inputPlug, outPlug, resultDset, writeXfer)

Функция i2d.convert предназначена для конвертации данных в формат DICOM. Рассмотрим аргументы:

  • inputPlug: Переданный в функцию входной плагин, представляет собой полностью настроенный компонент, способный читать данные пикселей из исходного формата (.jpg).

  • outPlug: Переданный в функцию выходной плагин, представляет собой полностью настроенный компонент, определяющий конечный формат-DICOM.

  • resultDset: Этот параметр представляет собой выходной набор данных, созданный в результате конвертации.

  • writeXfer: Этот параметр предоставляет предложенный формат передачи данных, необходимый, например, при использовании JPEG входного плагина. Он возвращается из функции.

Таким образом, функция i2d.convert выполняет конвертацию изображения из одного формата в другой.

Добавляем данные пациента

В этой части кода добавляем имя и пол пациента. Первым аргументом идет название DICOM тега, вторым — значение, которое в этот тег записывается.

resultDset->putAndInsertString(DCM_PatientName, "Marina Shimth");
resultDset->putAndInsertString(DCM_PatientSex, "female");

Причем данные могут быть любых типов, например, «string», как показано у меня в примере. Также могут передаваться числа (целые и с плавающей точкой), дата, время и так далее. Как правило, добавляется еще и дата рождения пациента и/или ID для упрощения последующей идентификации изображения.

resultDset ->putAndInsertString(DCM_PatientsBirthDate, PatientBirthDate);
resultDset ->putAndInsertString(DCM_PatientID, PatientID);

Далее необходимо будет добавить ряд тегов, без которых не получится отправить наш DICOM файл на сервер:

  • SOPClassUID = '1.2.840.10008.5.1.4.1.1.77.1.4'

  • StudyID — задать (номер исследования у пациента)

  • StudyInstanceUID — сгенерировать (уникальный номер исследования)

  • SeriesInstanceUID — сгенерировать (уникальный номер серии)

  • SOPInstanceUID — сгенерировать (уникальный номер файла)

Логика DICOM следующая: есть пациент с уникальным ID (PatientID), у пациента проводится исследование с уникальным ID (StudyInstanceUID) под номером StudyID, в данном исследовании была серия изображений с уникальным ID (SeriesInstanceUID), в которой находится файл с уникальным ID (SOPInstanceUID).

Рассмотрим, как сгенерировать UID на примере SOPInstanceUID, который задает глобально уникальное значение (больше нигде в мире он не должен повторяться) для нашего DICOM файла. Запишем в него значение с помощью генератора уникального идентификатора (uid):

resultDset->putAndInsertString(DCM_SOPInstanceUID, 
                               dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));

Подробнее о UID

UID — это уникальные строки, которые используются в стандарте DICOM для однозначной идентификации различных ресурсов, таких как изображения или серии. Функция dcmGenerateUniqueIdentifier является частью библиотеки DCMTK и предназначена для генерации уникальных идентификаторов объектов DICOM (UID).

const char* dcmGenerateUniqueIdentifier(char* uid, const char* root);
  • uid: это выходной параметр, в который будет записан сгенерированный уникальный идентификатор.

  • root: это корневая часть UID. Это может быть ваша уникальная строка, например, идентификатор вашей организации. Корневая часть обычно представляет собой префикс UID.

В DCMTK строка »1.2.840.10008.1.2.4.50» синтаксиса передачи данных DICOM (Transfer Syntax UID) для JPEG Baseline (Process 1) также представляет собой уникальный, но не глобально идентификатор. Это означает, что другие DICOM файлы могут также содержать в себе эту строку, в случае, если используют тот же синтаксис передачи.

Итак, мы все добавили, всё вписали. Мы почти молодцы, остался один момент — сохранить файл с расширением *.dcm. Как это сделать?! В этом нам помогут следующие две строки.

DcmFileFormat dcmff(resultDset);
dcmff.saveFile("test.dcm", 
                writeXfer, 
                EET_ExplicitLength, 
                EGL_recalcGL, 
                EPD_noChange, 
                OFstatic_cast(Uint32, 0), 
                OFstatic_cast(Uint32, 0), 
                EWM_fileformat);

Первая строка создает объект типа »DcmFileFormat» с именем »dcmff» и ему передает все наши данные, записанные в »resultDset». Вторая строка вызывает метод »SAVEFILE» объекта »dcmff» для сохранения DICOM-изображения в файл с названием «test.dcm» с использованием заданных параметров.

Получившийся .dcm файл можно открыть одним из множества бесплатных просмотрщиков, например, MicroDicom. В итоге должно получится нечто подобное:

5a01f4201c5d08f7afa81b952f1dc4ce.png

Для проверки возможности передачи файла по сети использовался локальный PACS-сервер Orthanc 23.2, который после добавления всех указанных тегов успешно сохранил DICOM файл. Хотя добавление тегов *UID и Transfer Syantax не гарантирует того, что сервер примет файл. В конечном итоге конфигурация сервера может запрещать сохранять некоторые типы SOPClassUID, или некоторые Transfer Syntaxes.

Полный код

#include 
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/libi2d/i2d.h"
#include "dcmtk/dcmdata/libi2d/i2djpgs.h"
#include "dcmtk/dcmdata/libi2d/i2dplsc.h"
#include "dcmtk/dcmdata/dctk.h"

using namespace std;

void createDicomImage() {
char uid[100];
I2DImgSource* inputPlug = new I2DJpegSource();
I2DOutputPlug* outPlug = new I2DOutputPlugSC();
Image2Dcm i2d;
E_TransferSyntax writeXfer;
DcmDataset* resultDset = NULL;

inputPlug->setImageFile("result.jpg");
i2d.convert(inputPlug, outPlug, resultDset, writeXfer);

//данные из базы данных пациентов

resultDset->putAndInsertString(DCM_PatientName, "Marina Shimth");
resultDset->putAndInsertString(DCM_PatientSex, "female");
resultDset->putAndInsertString(DCM_PatientBirthDate, "PatientBirthDate");
resultDset->putAndInsertString(DCM_PatientID, "PatientID");
resultDset->putAndInsertString(DCM_SOPClassUID, '1.2.840.10008.5.1.4.1.1.77.1.4');
resultDset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT));
resultDset->putAndInsertString(DCM_StudyID, "StudyID");
//общие настройки DICOM 
resultDset->putAndInsertString(DCM_TransferSyntaxUID, "'1.2.840.10008.5.1.4.1.1.77.1.4 UID_JPEGBaseline");
//сохранения файла

DcmFileFormat dcmff(resultDset);
 dcmff.saveFile("test_result_2.dcm", writeXfer, EET_ExplicitLength, EGL_recalcGL, EPD_noChange, OFstatic_cast(Uint32, 0), OFstatic_cast(Uint32, 0), EWM_fileformat);
}

Заключение

Для знакомства и написания этой программы я пользовался источником. В отличие от оригинальной статьи, хотелось привнести дополнительную информацию о генерации uid, детальнее рассмотреть функции и возможности DCMTK, а также сделать акцент на некоторых важных тегах при создании своего DICOM файла.  В итоге получилась программа, создающая файл формата .dcm из изображения .jpg, который можно послать на хранение в PACS-сервер. 

В общем, использование стандарта DICOM в сочетании с библиотекой DCMTK позволило нам превратить обычное фото в медицинское изображение, что благотворно способствует стандартизации процессов в медицинском программировании

Ссылка на оригинал, попытался дополнить и объяснить некоторые не понятные детали.

Habrahabr.ru прочитано 9147 раз