Введение
Это часть серии статей о стандарте DICOM, в которых мы пытаемся получить краткое представление о стандарте, а также познакомиться с прикладными инструментами для применения на практике. Вы также можете просмотреть другие материалы, чтобы освоить ряд тем, включая кодирование DICOM, SOP и IOD. Руководство под названием «Основы DICOM с использованием .NET и C# — понимание проверки DICOM» также будет очень полезно для понимания нижеизложенного материала. Также здесь предполагается, что вы знаете основы C# или любого эквивалентного объектно-ориентированного языка, такого как Java или C++. Базовое понимание сетевых технологий также будет полезно, но не обязательно.
Сеть DICOM на первый взгляд выглядит очень загадочной. Благодаря уникальному жаргону с такими словами, как «Абстрактный синтаксис», «Единицы данных протокола», «Контекст приложения», «Контекст представления» и т. д., любой новичок в стандарте может поначалу его полностью отпугнуть. Но как только вы начнете понимать, что они на самом деле означают, и проявите некоторое терпение, все действительно обретет смысл, и вся область связи DICOM начнет выглядеть интересно и даже весело. Я расскажу о некоторых фундаментальных терминах, которые вам необходимо знать, прежде чем вы углубитесь в написание программных приложений, которые используют или предоставляют услуги DICOM из/в другие программные приложения, поддерживающие DICOM. В этом уроке я буду использовать множество примеров, чтобы помочь вам лучше понять эти термины.
Контекст представления, контекст приложения, абстрактный синтаксис и синтаксис передачи в DICOM
Если вы читали мои другие статьи из этой серии, вы помните, что стандарт DICOM помогает устройствам, которые могут работать в совершенно разных операционных системах, обмениваться друг с другом объектами DICOM, такими как изображения, формы сигналов (например, ЭКГ) и диагностические отчеты. . Мы также увидели, что прежде чем произойдет какой-либо фактический обмен данными, два устройства должны согласовать «диалект» DICOM, на котором они говорят, или, более формально, «синтаксис передачи» ("transfer syntax"), как его называет стандарт. Этот синтаксис передачи определяет порядок байтов, используемый в этой операционной системе (с прямым порядком байтов или с обратным порядком байтов), тип сжатия, если оно используется, а также тип кодирования VR используемого (явный или неявный). Вы также помните концепцию пользователей класса обслуживания (или SCU), а также поставщиков классов обслуживания (или SCP), а также то, что одно и то же устройство может выполнять разные роли при обмене информацией с другими устройствами. Например, устройство A может играть роль C-Find SCU, когда необходимо запросить набор результатов от другого устройства B (которое будет воспроизводить роль C-Find SCP), но устройство A также может играть роль C-Store SCP, когда ему передаются любые интересующие его результаты (в данном случае устройство B играет роль C-Store SCU).
DICOM-соединение является фактически сетевым соединением между двумя устройствами DICOM, во время которого происходит первоначальное согласование используемого диалекта, а также фактическая передача данных, которая происходит после ассоциации. Во время ассоциации может выполняться ряд операций. Каждая из этих операций может быть полностью независимой друг от друга и помогать приложениям DICOM обмениваться друг с другом различными типами объектов DICOM. Когда устройство (обычно SCU) пытается открыть соединение с другим устройством (обычно SCP), перед открытием соединения проверяется некоторая проверка входной информации. Это включает в себя такие проверки, как то, настроен ли уже «Вызывающий AE» в «Вызываемом AE». (АЕ, если помните, определяется как объект приложения). Другими словами - разрешено ли настройками вызываемого устройства (например, сервера) подключение вызывающего устройства (например, рабочей станции врача). Это функция безопасности, которая реализована во многих приложениях DICOM, чтобы гарантировать, что конфиденциальная информация не будет передана «незаконным» приложениям, запрашивающим данные.
Затем вызываемый AE также проверяет, может ли он обрабатывать запрашиваемый тип службы (определяемый UID класса SOP) (например, хранение изображений CT, запрос/извлечение или печать), и может ли он обрабатывать эту операцию службы с помощью синтаксиса передачи, который объявляется вызывающим AE. Вызываемый AE может также выполнить дополнительные проверки, чтобы убедиться, что он имеет достаточно ресурсов для обработки этой рабочей нагрузки, т.к. в момент запроса ассоциации может быть высокий трафик и запрос на ассоциацию может быть отклонен по этой причине, даже если все остальное хорошо. Если первоначальные проверки прошли успешно, происходит успешная передача фактических данных, соответствующих классу SOP, также известному как определение информационного объекта или IOD, как они называются в DICOM. После передачи всех данных, относящихся к операциям, ассоциация может быть прекращена либо инициирующей стороной (SCU), либо иногда также и SCP. Вот и все. Вот суть того, что происходит во время ассоциации. Но прежде чем мы углубимся в детали, я хочу осветить некоторые жаргоны, специфичные для процесса ассоциации. Это поможет вам понять последующие примеры кода.
Иллюстрация выше должна дать хорошее представление о том, как работает связь DICOM между двумя устройствами на очень высоком уровне. Весь процесс начинается с того, что инициирующая сторона (часто SCU, также известная как вызывающий AE — здесь это устройство A) устанавливает сокетное соединение с другой стороной (часто SCP, также известной как вызываемый AE — здесь это устройство B). Это делается путем предоставления IP-адреса, а также номера порта во время установления соединения через сокет. Некоторые проверки безопасности выполняются, чтобы гарантировать, что вызывающая сторона уже зарегистрирована в базе данных вызываемого AE, а если нет, то соединение здесь не разрешено. Если все в порядке, соединение с сокетом установлено.
Переговоры об ассоциации в DICOM
Затем происходит процесс, называемый согласованием ассоциации, во время которого вызывающий AE отправляет некоторые объекты, называемые контекстами представления другой стороне. Инициатор ассоциации может передать несколько контекстов представления, но стандарт DICOM ограничивает их число максимум 128. Каждый объект контекста представления сам по себе состоит из двух объектов. Один из них назывался «Абстрактный синтаксис» ("Abstract Syntax"), а другой — «Список синтаксисов передачи» (Transfer Syntax List). Абстрактный синтаксис определяет тип класса SOP (указанный через UID SOP), а также роль, которую он хочет играть — SCU или SCP. Вызываемый AE должен поддерживать этот абстрактный синтаксис, в противном случае он полностью отклоняет запрос на ассоциацию. Например, вызывающий AE может указать, что ему нужна услуга C-Find SCP от вызываемого AE. Если эта услуга предоставляется, вызываемый AE затем просматривает список синтаксисов передачи, который был ему отправлен. Это определяет диалект DICOM, на котором желает говорить вызывающий AE. Например, вызывающий AE может пожелать использовать явный VR с прямым порядком байтов, указанный через UID 1.2.840.10008.1.2.1. Вызываемый AE может не поддерживать этот синтаксис передачи и может просмотреть другие синтаксисы передачи в списке, чтобы увидеть, есть ли в списке что-нибудь, что он понимает. Если ни один из синтаксисов передачи в списке не поддерживается, запрос на ассоциацию отклоняется.
Здесь я хочу упомянуть, что должен быть хотя бы один синтаксис передачи, который должны поддерживать все приложения DICOM. Этот синтаксис передачи представляет собой неявный VR с прямым порядком байтов, обозначенный UID 1.2.840.10008.1.2. Это единственный обязательный синтаксис передачи DICOM, который должны поддерживать все приложения DICOM. Проблема с этим синтаксисом заключается в том, что, поскольку имя указывает, кодировка VR является неявной и, следовательно, требует, чтобы вызываемое приложение имело обновленный словарь DICOM, чтобы иметь хоть какой-то смысл входящих данных. Тем не менее рекомендуется всегда использовать какой-либо явный синтаксис передачи кодировки VR, где это возможно, поскольку тип VR можно понять из самих переданных данных. Обратите внимание, что стандарт также разрешает использование частных абстрактных синтаксисов, а также частных синтаксисов передачи между сторонами. Дополнительную информацию см. в официальной документации DICOM. На этом этапе переговоров ассоциация либо принимается с уведомлением вызывающего AE о контекстах представления, которые приемлемы в ответном сообщении, либо ассоциация отклоняется вызываемым AE. Также обратите внимание, что может быть более одного контекста представления, принимаемого как несколько классов SOP, или устройством могут поддерживаться «абстрактные синтаксисы». Наряду с этой информацией также указывается конкретный синтаксис передачи, который поддерживается для этого контекста представления.
Также нужно упомянуть здесь, что существует нечто, называемое расширенным согласованием (Extended Negotiation), во время которого между устройствами происходят дополнительные согласования для согласования конкретной семантики для определенного класса SOP, такого как C-FIND, C-GET. и т. д. Это когда такая информация, как роль, которую устройства хотят играть (SCU, SCP или оба), максимальное количество асинхронных операций, которые могут быть вызваны/выполнены устройством, идентификационная информация пользователя в виде имени пользователя, Kerberos Билеты аутентификации, утверждения SAML, веб-токены JSON и т. д. также могут передаваться во время этого процесса. Обмениваемая идентификационная информация пользователя, например, может использоваться для ведения контрольного журнала, предоставления доступа или ограничения результатов, возвращаемых на определенный организационный уровень, к которому принадлежит пользователь, и т. д. Просто по этой теме можно очень многое рассказать, и я на этом пока остановимся. Не все поставщики реализуют все эти возможности или могут игнорировать их при установлении ассоциации. В некоторых ситуациях ассоциации по-прежнему могут устанавливаться с некоторыми значениями по умолчанию, принятыми поставщиком услуг (например, если роль не указана), а в других ситуациях ассоциации также отклоняются. Этот аспект DICOM в некоторых случаях определенно может вызвать проблемы совместимости, поэтому перед покупкой продукта проведите некоторое тестирование. Пожалуйста, посмотри Документация DICOM содержит подробные сведения об этом .
После завершения основных, а также любых расширенных переговоров, вызывающий AE знает, какие услуги ожидать от вызываемого AE, и это завершает то, что называется установлением ассоциации в сети DICOM. На этом этапе вызывающий AE может начать отправлять команды DICOM вместе со всеми связанными данными вызываемому AE. На диаграмме ниже показаны различные команды DIMSE, которые передаются туда и обратно во время согласования ассоциации.
Копаем глубже в DIMSE
При попытке установления связи два объекта DICOM передают друг другу серию DIMSE (элементов службы сообщений DICOM). DIMSE для операций являются тем же, чем IOD для данных. Команды DIMSE состоят из той же модульной группы элементов DICOM, что и IOD DICOM, определяющей такую информацию, как уникальный идентификатор сообщения, тип команды или операции, приоритет команды (используется очень редко), DICOM AE, запрашивающий эту операцию, а также а также индикатор, указывающий, есть ли данные, сопровождающие эту команду. Если в команде флаг данных установлен в значение true, то сразу после команды передается один и только один объект данных IOD. Вызываемый AE или SCP затем отвечает на эту команду (и любые сопровождающие ее данные) ответным сообщением, указывающим результаты операции.
«A-ASSOCIATE-RQ» — это команда DIMSE, передаваемая запрашивающей стороной удаленному узлу при инициировании ассоциации. Принимающая сторона может просмотреть контекст представления и принять его, ответив сообщением «A-ASSOCIATE-AC», или она может отклонить ассоциацию, используя сообщение «A-ASSOCIATE-RJ». Если/когда ассоциация успешно установлена, два объекта DICOM передают друг другу другие DIMSE на основе задействованной операции. Например, когда сканер КТ решает передать серию изображений на сервер хранения PACS, он сначала устанавливает ассоциацию, а затем отправляет команду DIMSE «C-STORE-RQ», за которой следует экземпляр IOD изображения КТ, который необходимо сохранить. SCP C-Store отвечает ответом об успехе или сбое (с использованием объекта C-STORE-RSP), указывающим результат операции. C-Store SCU может продолжать передавать серию дополнительных команд для каждого изображения, которое ему необходимо сохранить, и процесс продолжается между двумя устройствами по той же схеме. После завершения операций считается хорошей практикой «освободить» ассоциацию, чтобы освободить вычислительные ресурсы и сетевые соединения. Это можно сделать, отправив сообщение A-RELEASE-RQ. Ассоциации также могут быть «прерваны» в любой момент любой стороной в любой момент всего процесса (с использованием сообщения «A-ABORT»), и одноранговые устройства должны иметь возможность корректно обрабатывать такие сценарии.
Пример согласования ассоциации с использованием Fellow Oak (набор инструментов fo-dicom)
Данный материал является моим (Александр Кузнецов) творческим переводом статьи "DICOM Basics using .NET and C# - Understanding Association/ Negotiations", автором которой является Saravanan Subramanian. Приведенные примеры кода основаны на первоисточнике, но переработаны мной в связи с существенными изменениями определений процедур используемой библиотеки.
Как вы можете видеть из моего небольшого примера кода ниже, я сначала создаю контекст представления с одним абстрактным синтаксисом (класс Verification SOP) вместе со списком синтаксисов передачи, с которыми я могу справиться на своей стороне. Затем я формирую ассоциацию с удаленным AE и в это время передаю контексты презентации. Несмотря на то, что в этом примере я передаю только один, ничто не мешает вам запросить длинный список контекстов представления, каждый из которых включает совершенно другой класс SOP или абстрактный синтаксис вместе со всеми связанными синтаксисами передачи. Затем я спрашиваю удаленный AE, поддерживает ли он определенный идентификатор контекста представления (вот почему нам нужно отслеживать переданные нами идентификаторы). Если да, то я также проверяю, какой тип синтаксиса передачи он предпочитает использовать при обработке этого конкретного абстрактного синтаксиса или класса SOP. Большинство приложений DICOM должны поддерживать явный синтаксис передачи VR, поскольку они не требуют от приложения наличия огромного словаря DICOM. Как вы помните, явное кодирование VR включает в себя включение типа VR вместе с номером группы и тега, чтобы принимающее приложение могло быстрее анализировать данные.
В коде, показанном ниже, я регистрирую три дополнительных метода обратного вызова в клиенте DICOM, которые помогают отслеживать различные события, связанные с установлением и завершением ассоциации. Если ассоциация не может быть установлена по какой-либо причине, мы можем узнать причину, используя параметр AssociationRejectedEventArgs , который передается в обратный вызов отклонения ассоциации. С другой стороны, если ассоциация была установлена, мы можем получить дополнительные сведения об установленной ассоциации, используя параметр AssociationAcceptedEventArgs , который передается в обратный вызов, принятый ассоциацией. Подробности установленной ассоциации, такие как абстрактный синтаксис, а также согласованные синтаксисы передачи, выводятся в консоль программы.
using FellowOakDicom.Network; using FellowOakDicom.Network.Client; using FellowOakDicom.Network.Client.EventArguments; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AssociationDicom { class Program { static async Task Main(string[] args) { try { //replace these with your settings //Here, I am using Dr.Dave Harvey's public server //please be careful not to send any confidential info as all traffic is logged var dicomRemoteHost = "www.dicomserver.co.uk"; var dicomRemoteHostPort = 104; var useTls = false; var ourDotNetTestClientDicomAeTitle = "TESTAET"; var remoteDicomHostAeTitle = "DAVEHARVEYS"; //создаем клиент эхо-проверки DICOM с обработчиками var client = DicomClientFactory.Create(dicomRemoteHost, dicomRemoteHostPort, useTls, ourDotNetTestClientDicomAeTitle, remoteDicomHostAeTitle); var dicomCEchoRequest = new DicomCEchoRequest(); dicomCEchoRequest.OnResponseReceived += OnEchoResponseReceivedFromRemoteHost; //добавляем обработчики событий для общей информации о подключении ассоциации client.AssociationAccepted += ClientOnAssociationAccepted; client.AssociationRejected += ClientOnAssociationRejected; client.AssociationReleased += ClientOnAssociationReleased; await client.AddRequestAsync(dicomCEchoRequest).ConfigureAwait(true); //отправляем запрос на проверку на удаленный сервер DICOM await client.SendAsync().ConfigureAwait(true); Console.ReadLine(); } catch (Exception e) { //In real life, do something about this exception LogToDebugConsole($"Error occured during DICOM association request -> {e.StackTrace}"); } } private static void OnEchoResponseReceivedFromRemoteHost(DicomCEchoRequest request, DicomCEchoResponse response) { LogToDebugConsole($"DICOM Echo Verification request was received by remote host"); LogToDebugConsole($"Response was received from remote host..."); LogToDebugConsole($"Verification response status returned was:{response.Status.ToString()}"); } private static void ClientOnAssociationReleased(object sender, EventArgs e) { LogToDebugConsole("Assoacition was released"); } private static void ClientOnAssociationRejected(object sender, AssociationRejectedEventArgs e) { LogToDebugConsole($"Association was rejected. Rejected Reason:{e.Reason}"); } private static void ClientOnAssociationAccepted(object sender, AssociationAcceptedEventArgs e) { var association = e.Association; LogToDebugConsole($"Association was accepted by remote host: {association.RemoteHost} running on port: {association.RemotePort}"); foreach (var presentationContext in association.PresentationContexts) { if (presentationContext.Result == DicomPresentationContextResult.Accept) { LogToDebugConsole($"\t {presentationContext.AbstractSyntax} was accepted"); LogToDebugConsole($"\t Negotiation result was: {presentationContext.GetResultDescription()}"); LogToDebugConsole($"\t Abstract syntax accepted: {presentationContext.AbstractSyntax}"); LogToDebugConsole($"\t Transfer syntax accepted: {presentationContext.AcceptedTransferSyntax.ToString()}"); } else { LogToDebugConsole($"\t Presentation context with proposed abstract syntax of '{presentationContext.AbstractSyntax}' was not accepted"); LogToDebugConsole($"\t Reject reason was {presentationContext.GetResultDescription()}"); } } } private static void LogToDebugConsole(string informationToLog) { Console.WriteLine(informationToLog); } } }
Вывод, отображаемый в консоли программы при запуске приведенного выше примера кода, показан ниже. Из ответа видно, что сначала устанавливается связь между двумя устройствами DICOM, а затем следует ответ удаленного хоста о том, что он поддерживает службу проверки DICOM (здесь она действует как «Verification SCP»). Затем он освобождает ассоциацию, поскольку клиент не запрашивал никаких других операций.
Заключение
На этом завершается краткое вводное руководство по установлению/согласованию ассоциации DICOM. Это чрезвычайно важный аспект сети DICOM, и в этом коротком руководстве я постарался передать саму суть этого сложного аспекта связи DICOM так, чтобы это имело для вас смысл.