Руководство по Carino DICOM

Carino DICOM — это шлюз DICOM и устройство непрерывности работы: принимает исследования, маршрутизирует и пересылает их, отдаёт хранимое по Query/Retrieve и DICOMweb и позволяет отделению лучевой диагностики работать, когда PACS или РИС перестают отвечать. Это руководство охватывает развёртывание, модель безопасности, каждую службу и реальные пределы программы.

Все снимки экрана в этом руководстве сделаны с работающего экземпляра этой программы, а исследования на них придуманы для съёмки. Ни один пациент в руководстве не показан.

Содержание

  1. Что это такое и какую роль играет
  2. Начало работы
  3. Модель безопасности и правило токена
  4. Службы, одна за другой
  5. Это не медицинское изделие
  6. Лицензия и где получить помощь

1. Что это такое и какую роль играет

Carino DICOM стоит между оборудованием, которое создаёт изображения, и системами, которые их хранят или читают. Он не пытается заменить ваш архив; он пытается быть тем звеном, которое их связывает, и тем, что останется на ногах, когда упадёт что-то из остальных.

У него две роли, и различие между ними важно: именно оно определяет, какие службы вы включите.

Всем управляет один config.json, и всё работает без графического интерфейса. Веб-панель удобна, но не обязательна.

Порты по умолчанию

СлужбаПортПротокол
Приём (Storage SCP)11112DICOM / DIMSE
Виртуальная печать11113DICOM Print
Modality Worklist11114DICOM C-FIND
Запрос/выдача11115DICOM C-FIND/C-MOVE/C-GET
Аварийная РИС2575HL7 поверх MLLP
Панель + DICOMweb8042HTTP

Почему 11112, а не 104. Зарегистрированный порт для DICOM — 104, а он привилегированный в Linux и macOS и требует root. Значение по умолчанию этого избегает. Настройте оборудование соответственно или пробросьте 104 на 11112 в контейнере или на брандмауэре.

2. Начало работы

Выберите вариант развёртывания

ВариантДля чего подходитПоднимается после отключения питания
Настольное приложение (в трее)Рабочая станция, за которой кто-то сидит: один кабинет, одна клиника, пробный запуск.Только с «запуском при входе»
Docker / PodmanСервер или любая машина, где его нужно изолировать и переносить целиком.Да (restart: unless-stopped)
Служба systemdПостоянный ПК в углу отделения лучевой диагностики, где никто не входит в систему.Да

Настольное приложение

Скачайте пакет для вашей системы со страницы releases и откройте его. Оно сворачивается в системный трей; по клику открывается панель. Приложение пока не подписано цифровой подписью, поэтому Windows и macOS предупреждают при первом запуске — на главной странице есть точные шаги, как всё-таки его открыть.

При первом запуске оно один раз спрашивает, можно ли проверять на GitHub, не вышла ли более новая версия. Ответ «нет» — или просто закрытое окно вопроса — оставляет проверку выключенной, и так она настроена по умолчанию. Если она включена, новый релиз появляется строкой в меню трея и рядом с версией на панели Обзор; клик по любой из них открывает страницу релиза в вашем браузере. Ничего не скачивается и ничего не устанавливается само. Флажок Проверять обновления в меню трея включает и выключает это в любой момент, и есть он только в настольной сборке — в контейнерных и systemd-развёртываниях его нет.

Docker

git clone https://github.com/MiguelCarino/Carino-DICOM
cd Carino-PACS
mkdir -p data && sudo chown -R $(id -u):$(id -g) data
docker compose up -d
docker compose logs -f pacs

При первом запуске создаётся и печатается в журнал токен доступа. Откройте http://127.0.0.1:8042/ и вставьте его, когда панель попросит. Всё — конфигурация, исследования, журналы, индекс — лежит в ./data, и именно с него делается резервная копия.

Самый частый сбой первого запуска — каталог ./data, принадлежащий root: контейнер никогда не работает от root и падает с EACCES. Если uid вашей учётной записи не 1000, укажите PACS_UID и PACS_GID в файле .env и пересоберите. На хостах с SELinux (Fedora, RHEL, Rocky) суффикс тома :z — не украшение: без него в примонтированный каталог нельзя писать, хотя владелец и права выглядят совершенно правильно.

Служба systemd (Linux)

git clone https://github.com/MiguelCarino/Carino-DICOM.git
cd Carino-PACS
sudo packaging/systemd/install.sh

Установщик создаёт системного пользователя carino-pacs, копирует код в /opt/carino-pacs, готовит /var/lib/carino-pacs и ставит юнит… но не запускает его. Это не недосмотр: запуск PACS открывает порты, принимающие данные пациентов, и такое решение принимается после чтения конфигурации, а не до. Отредактируйте /var/lib/carino-pacs/config.json, затем:

sudo systemctl enable --now carino-pacs
systemctl status carino-pacs
journalctl -u carino-pacs -f

Повторный запуск установщика обновляет код на месте и никогда не трогает существующий config.json.

Где лежат данные

ВариантКаталог
Настольное приложение / CLI~/CarinoDICOM/ (существующие установки сохраняют ~/CarinoPACS)
Docker/data в контейнере → ./data на хосте
systemd/var/lib/carino-pacs/ (режим 0750)

config.json лежит вместе с данными, а не в /etc, потому что относительные пути в конфигурации ("./received", "./logs", "./index.db") отсчитываются от каталога, в котором лежит сам файл конфигурации. Конфигурация в /etc разбросала бы исследования пациентов по /etc.

Первый запуск, по порядку

  1. Выберите службы. Всё поставляется выключенным. Окно выбора служб в панели спрашивает, что должна запускать эта машина; включите только это.
  2. Добавьте назначения (имя, хост, порт, AE title) на вкладке Назначения в разделе Конфигурация — именно этой вкладкой раздел и открывается.
  3. Проверьте связь через C-ECHO, прежде чем передавать хотя бы одно изображение: ./run.sh echo --name "Hospital PACS". Если C-ECHO не проходит, дело не в Carino — дело в сети, брандмауэре или AE title.
  4. Направьте сюда один аппарат, указав AE title приёмника, IP и порт, и отправьте тестовое исследование.
  5. Следите за журналом — вкладка Журнал в разделе Активность. Если что-то не сработало, это появится там; эта программа скорее закричит, чем тихо откажет.
Окно выбора служб в панели, озаглавленное «Выберите службы, которые должен запускать этот ПК»: по карточке на службу — Приём, Автоотправка, Приём печати, Аварийная РИС, MWL, Запрос/выдача — у каждой отметка «Использовать», и ни одна не отмечена; внизу подпись «Ничего не выбрано — этот ПК не будет ни принимать, ни отправлять» рядом с кнопками «Не сейчас» и «Включить выбранные».
Шаг 1. Ничего не отмечено и ничего не запущено: об этом говорит подпись внизу, и ни один порт не откроется, пока вы не примените выбор. Каждая карточка называет, для чего служба нужна, так что решение принимается по описанию, а не по названию протокола.
Вкладка Журнал в разделе Активность: поток записей с отметками времени. Строки запуска называют каждую службу и её порт; красный блок объясняет, что правило маршрутизации требует обезличивания, пока профиль выключен; янтарная строка сообщает об отклонённой ассоциации и о времени следующего повтора.
Шаг 5. Поток активности. Каждая значимая строка говорит, что с ней делать: красный блок здесь — удержание из-за обезличивания с указанием способа исправления, а янтарная строка — назначение, отклонившее подключение, с указанием следующего повтора.

Без графического интерфейса

./run.sh init            # создаёт config.json и его папки
./run.sh init --token    # и генерирует web.auth_token
./run.sh serve           # панель на http://127.0.0.1:8042
./run.sh receive         # только приём
./run.sh send            # только наблюдение за папкой / пересылка
./run.sh qr              # только Запрос/выдача
./run.sh mwl             # только рабочий список
./run.sh ris             # только приём заявок HL7
./run.sh print           # только виртуальная печать
./run.sh echo --host 10.0.0.5 --port 104 --aet REMOTEPACS

Каждая команда принимает -c / --config <путь>. В Windows — run.ps1.

3. Модель безопасности и правило токена

Экран входа: карточка с заголовком «Этому PACS нужен токен доступа», одно поле для ввода, кнопка «Войти» и примечание о том, что токен — это web.auth_token в файле конфигурации и что панель никогда не показывает сохранённый токен.
Это выдаётся вместо панели, а затем снова при каждом ответе 401 — перезапуск намеренно обрывает все сессии, поэтому повторный вход нормален, а не является неисправностью.

Начнём с главного: панель — это ключ от архива. Любой, кто её откроет, может прочитать каждое сохранённое исследование, скачать файлы DICOM, изменить любую настройку, запустить и остановить службы, удалить исследования и выключить сервер. Из коробки существует один общий секрет, и тот, у кого он есть, может всё — профили необязательны и выключены, пока вы их не включите. Как только вы это сделаете, каждый входит под собой — со своими разрешениями, со своим доступом к идентификаторам пациента и со своим именем в журнале аудита; общий токен продолжает работать как администратор, поэтому ничего из того, что уже им пользуется, не ломается.

Вкладка Люди раздела Конфигурация, под строкой о четырёх профилях: Администратор, ИТ, Рентгенолог и Регистратура. Карточка Администратора короткая — у неё отмечен пункт «Администратор (всё, включая будущие разрешения)», поэтому таблицы разрешений на ней нет вовсе. Карточка ИТ под ней показывает всю сетку отметок «Может делать», а снимок обрывается на середине её строки «Может видеть». Внизу справа закреплена кнопка «Добавить».
Профили, когда они включены. Разрешения задаются на человека и проверяются на каждой конечной точке, а не прячутся в браузере — и Может видеть отделено от Может делать: профилю можно разрешить перенаправить исследование по номеру исследования, ни разу не прочитав имени пациента. Всё неотмеченное показывается как *** везде, где оно появилось бы.

Разрешения определяют и то, что панель рисует. Профиль видит только те разделы боковой панели и только те вкладки внутри них, на которые у него есть разрешения: Рентгенолог, у которого есть routing.read, но нет config.read, получает Конфигурацию с Назначениями и Маршрутами и без вкладки Настройки, а Регистратура, не имеющая ни одного из трёх, вообще не видит раздела Конфигурация. Индикаторы служб в верхней шапке — намеренное исключение, см. Веб-панель ниже.

Тот же экран входа на устройстве с профилями: четыре кнопки — Администратор, ИТ, Рентгенолог, Регистратура — каждая с замком, и ссылка «Войти по токену доступа».
Экран входа устройства с профилями. Список рисуется, только пока включено users.list_profiles: публиковать состав сотрудников для всех, кто дотянется до порта, — это реальное раскрытие, и решать вам. Токен по-прежнему работает, спрятанный ниже, потому что это путь обратно, когда кто-то закрыл себе вход.

Правило

Пустой web.auth_token допустим, только пока web.host — это петлевой адрес. Если web.host — что-либо другое: 0.0.0.0, адрес в локальной сети, имя хоста — а токен пуст, сервер отказывается запускаться. Этот отказ — свойство программы, а не ошибка, которую надо обойти.

Причина вполне конкретна. Без аутентификации на 127.0.0.1 контролем доступа служит операционная система: до API дотянется только процесс на этой машине. Это оправданно. Но web.host задаёт оператор, и в тот момент, когда кто-нибудь меняет его на 0.0.0.0, чтобы «зайти с другого ПК» — второпях, во вторник, не думая о безопасности, — тот же API выдаёт любому соседу по локальной сети список исследований, пути хранения, байты DICOM и /api/shutdown. Правило существует потому, что такое изменение занимает десять секунд, а последствия остаются на годы.

Любой контейнер всегда находится именно в этом положении: контейнер по своей природе публикует службу на 0.0.0.0. Поэтому образ при первом запуске создаёт 256-битный токен, если вы не задали свой, — и печатает его. А вот молча он токен не создаёт никогда: секрет, которого никто не видел, — это секрет, который никто не меняет.

Создание и предъявление токена

./run.sh init --token                                  # записывает его в config.json
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
openssl rand -base64 32

Сервер принимает учётные данные тремя способами:

Файл cookie существует для того, чтобы панель спрашивала токен один раз вместо того, чтобы держать его в JavaScript, где его прочитает любой XSS и любое расширение браузера. Он никогда не несёт сам токен: он несёт HMAC по секрету, созданному при старте и хранимому только в памяти. Поэтому перезапуск завершает все сессии — верный компромисс для устройства с одним оператором: никакого хранилища сессий на диске, нечему утечь, а в худшем случае токен придётся ввести заново раз в смену.

Панель говорит по обычному HTTP. Встроенного TLS для веб-уровня нет. Если вы открываете её за пределы петлевого адреса, поставьте перед ней обратный прокси, который завершает HTTPS. Токен, переданный по открытому HTTP в общей сети, — это токен, который вы отдали.

Сторона DICOM

Что эта модель не защищает

Резервные копии. Индекс sqlite — это кеш, и он восстанавливается сам; изображения — нет. Делайте резервные копии каталогов хранения — и хотя бы раз проверьте восстановление.

Никакой телеметрии — с одним исключением, которое включаете вы сами. Движок никуда ничего не отправляет: ни аналитики, ни отчётов о сбоях, ни счётчиков использования, ни сторонних скриптов, подгружаемых во время работы. Его единственные исходящие соединения — это ассоциации DICOM и подтверждения HL7, которые вы настроили, к названным вами узлам, и развёртывание в Docker, Podman или systemd — это ровно оно и ничего сверх того. Именно эта фраза здесь и важна: такая машина и стоит в отделении лучевой диагностики с данными пациентов на ней. Настольное приложение вдобавок умеет проверять, вышла ли более новая версия, — но только если вы ответили «да» на вопрос, который оно задаёт один раз, при первом запуске; по умолчанию проверка выключена, и в меню трея её можно выключить снова. Эта проверка — один HTTPS-запрос в сутки к списку релизов на GitHub, с заголовком User-Agent и ничем больше: без идентификаторов, без счётчиков, без конфигурации, без данных пациентов. Поэтому GitHub узнаёт IP-адрес этой машины и примерное время запуска приложения — и это всё. Ничего не скачивается и ничего не устанавливается само. Изменение, которое отправляло бы больше этого или отправляло бы то, на что никто не соглашался, считалось бы уязвимостью.

Вкладка Аудит раздела Активность: в шапке панели, над полосой вкладок, кнопки «Очистить вид», «Проверить целостность» и «Экспорт»; под вкладками строка с числом файлов, размером на диске и хешем вершины цепочки; ниже — пять строк с отметками времени, в каждой система входит по токену API, и каждая успешна.
Журнал аудита, а над ним напечатана вершина цепочки — именно этот хеш стоит скопировать туда, куда эта машина не дотянется. Три кнопки принадлежат панели, а не вкладке, поэтому Очистить вид, Проверить целостность и Экспорт одинаково доступны и на вкладке Журнал. Отказы записываются так же добросовестно, как и успехи: отклонённый запрос — такая же строка здесь, как и любая другая.

4. Службы, одна за другой

Всё, что открывает порт, поставляется выключенным. Правильный вопрос не «что оно умеет?», а «что должна делать эта машина?». Каждая включённая служба — это ещё один открытый порт. Индекс ниже — единственное исключение: он запускается сам, потому что это локальный кеш sqlite, который ничего не слушает.

Панель Службы: по карточке на службу — Приём, Автоотправка, Приём печати, Аварийная РИС, MWL, Запрос/выдача — у каждой зелёная точка, кнопка «Стоп», её AE title, что она слушает, и её счётчики.
Каждая служба запускается и останавливается сама по себе, и на каждой карточке есть два числа, за которыми стоит следить: сколько обработано и сколько не удалось. Панель — не движок: остановка здесь останавливает эту службу, а не устройство.
Панель Обзор: плитки со счётчиками служб запущено, принято, отправлено, застряло, ожидает, открытых заявок и свободного места, а под ними карточки с адресом этой машины, её приёмником, путями конфигурации и хранения, доступностью каждого назначения и последним принятым исследованием.
Обзор отвечает на вопрос «работает ли эта машина» одним экраном. Каждая плитка, считающая то, у чего есть своё место, — это кнопка в ту панель, которой это принадлежит — Застряли и Ожидают открывают свои вкладки в разделе Исследований, — поэтому неожиданное число находится в одном клике от подробностей. Свободно — исключение: оно никуда не ведёт, поэтому и остаётся простым показателем, а не кнопкой, которая ничего не делает.

Приём — Storage SCP

Порт 11112 · C-STORE, C-ECHO

Что делает
Принимает исследования, которые присылают аппараты, и раскладывает их на диск — при желании по схеме пациент / исследование / серия. Он принимает все синтаксисы передачи и сохраняет сжатые объекты как есть — без перекодирования, поэтому по пути внутрь ничего не меняется.
Включайте, когда
Сюда что-либо должно отправлять изображения: аппараты, другой PACS, рабочая станция. Это основная служба.
Оставьте выключенным, когда
Эта машина только пересылает то, что кто-то другой кладёт в папку.
Вкладка История раздела Исследований, полоса Принятые: по строке на исследование с пациентом, ID, датой, описанием, модальностью и числом изображений, под ней перечислены серии, а справа в каждой строке действия «Отправить», «Прикрепить», «Теги», «В папке» и «Удалить».
Что пришло, новое сверху, и из каких серий состоит каждое исследование. В папке открывает то место, куда оно действительно легло на диске: раскладка — это настоящее дерево папок, а не база данных, из которой кому-то надо выгружать.

Автоотправка — Storage SCU и правила маршрутизации

Исходящий клиент · C-STORE

Что делает
Наблюдает за папкой и пересылает каждый новый файл на те назначения, которые подходят. Файл уходит, только когда он стабилен (размер не изменился между двумя сканированиями), поэтому недописанный файл никогда не пересылается. Прогресс отслеживается по каждому назначению: файл считается обработанным, только когда его приняло каждое включённое назначение, а отправка на недоступные узлы повторяется на следующем сканировании. При успехе оригинал можно оставить, переместить или удалить.
Правила
При включённой маршрутизации правило выбирает назначения по модальности, вызывающему AE title, станции, ID пациента или описанию исследования (шаблоны * и ?, без учёта регистра). Например: CT из ER_* — в учебный архив, обезличенно.
Гарантия
Исследование никогда не может остаться неотправленным. Маршрутизация выключена, ни одно правило не совпало, заголовок не читается, правило называет назначение, которого больше нет, — в каждом из этих случаев файл уходит на все включённые назначения. Лишняя отправка раздражает оператора; недостаточная теряет изображение.
Единственное исключение
Правило, которое требует обезличивания для назначения, когда очистку выполнить нельзя, удерживает это назначение: ему не отправляется ничего, вместо того чтобы отправить с данными пациента. Очистке мешают две вещи — профиль в состоянии off или профиль включён, но из настроек не удаётся построить деидентификатор, — и исправляются они по-разному. Ничего не теряется ни в том, ни в другом случае; см. Обезличивание при пересылке ниже.
Включайте, когда
Эта машина должна доставлять изображения дальше: в центральный PACS, на рабочую станцию врача или копиями в учебный архив.
Вкладка Назначения раздела Конфигурация: три строки — Главный архив, Читальная комната и Учебный архив — у каждой хост, порт, AE title, отметки TLS, «Без РИС» и аварийного основного узла, а также кнопка Echo.
Назначения получают имя здесь, а везде далее упоминаются по имени, поэтому переименование узла не оставляет сиротами правила, которые на него ссылаются. Echo проверяет ассоциацию до того, как от неё будет зависеть хоть одно изображение.
Вкладка Маршруты раздела Конфигурация: отметка «Маршрутизировать по правилам», затем пронумерованные правила с полями сопоставления по модальности, вызывающему AE, станции, ID пациента и описанию исследования, и строка «Отправлять на» с отметками назначений плюс «Обезличить» и «Остановиться здесь».
Правила читаются сверху вниз, а пустые поля означают любое: правило — это фильтр, а не бланк, который надо заполнить. Порядок важен, поэтому у каждого правила есть стрелки для перемещения.
Вкладка Застряли раздела Исследований под баннером «5 файлов требуют внимания», разделённая надвое: «Повтор идёт автоматически» — с учебным архивом, двумя ожидающими объектами, его последней ошибкой красным, кнопкой «Повторить сейчас» и обратным отсчётом до следующей попытки; и «Удержано — ничего не отправляется» — с главным архивом, помеченным «профиль выключен», тремя ожидающими объектами, названными файлами .dcm, абзацем с правкой, которая их освободит, и двумя кнопками: «Настройки обезличивания» и «Правило, которое этого требует».
Две половины здесь — это разные проблемы, и вкладка их разделяет. Сверху — узел, который лежит и починится сам; снизу — удержание из-за обезличивания, которое не снимает никакой таймер, поэтому в каждой строке прямо названа правка, снимающая его, и стоят две кнопки, которые ведут туда, где её делают. Третий раздел, Не осталось назначения для повтора, рисуется, только когда ему есть что показать: это файлы, направленные на имя, которое больше не является включённым назначением. Их никто не повторяет, потому что обращаться уже не к кому — выход в том, чтобы вернуть назначение или смириться с потерей, и пока не произойдёт одно из двух, они лежат в папке исходящих, а не архивируются и не удаляются.

Обезличивание при пересылке

Профиль PS3.15 Annex E · применяется только к уходящей копии

Что делает
Применяет Basic Application Level Confidentiality Profile к отправляемому объекту и записывает в (0012,0064), какие именно опции сохранения были использованы, чтобы получатель видел, что осталось. Сохранённый оригинал никогда не переписывается — в этой асимметрии вся суть.
Профили
basic сохраняет даты (полностью или со сдвигом), характеристики пациента, сведения об устройстве и об учреждении. strict убирает сведения об устройстве и учреждении и удаляет частные атрибуты, даже если вы просили их сохранить.
Включайте, когда
Изображения покидают клиническую среду: обучение, научная работа, поставщик, внешнее второе мнение.
Выключение
Профиль решает, происходит ли очистка вообще; отметка Обезличить у правила решает, какие назначения получат очищенную копию. Поставьте профиль в off, пока правило всё ещё этого требует, — и Carino удержит это назначение, а не отправит копию с данными пациента на узел, владельцу которого сказали, что таких данных он не получает.

Удержано, а не отправлено, — и это ответ, когда исследования перестают двигаться. Назначение, для которого правило требует обезличивания, не получает ничего всякий раз, когда очистку выполнить нельзя. Доставка откладывается, личность не раскрывается: исследование, ждущее на диске, освобождается одной правкой, а имя, дошедшее до внешнего узла, никакой правкой не вернуть.

Очистка не может произойти по двум причинам, и исправляются они по-разному. Каждое удержание записывает, какая именно это причина, и вкладка Застряли раздела Исследований показывает способ исправления именно для записанной причины, а не гадает:

  • Профиль в состоянии off, а правило всё ещё требует очистки. Поставьте профиль в basic или strict — и следующий проход автоотправки их освободит.
  • Профиль включён, но из текущих настроек не удалось построить деидентификатор, поэтому очищать по-прежнему нечем. Ошибка, которая этому помешала, есть на вкладке Журнал раздела Активность, на канале отправки, — исправьте её. Выключение профиля это не освободит: оно не освобождает ничего и лишь меняет то, какая из двух половин мешает очистке.

Снятая у правила отметка Обезличить освобождает любое из этих удержаний — и освобождает исследования с данными пациента, то есть даёт ровно тот исход, ради предотвращения которого удержание и существует. Делайте эту правку, только если это назначение действительно больше не должно получать очищенные данные.

Какая бы из этих правок ни была верной, до неё один клик из строки, которая сообщает об удержании. В каждой удержанной строке есть кнопка Настройки обезличивания, открывающая вкладку Настройки раздела Конфигурация, и кнопка Правило, которое этого требует, открывающая вкладку Маршруты. Обе только открывают вкладку и не более: ни одна не прокручивает до правила, требующего очистки, и ни одна его не подсвечивает. И всё же строка, называющая проблему, ведёт в оба места, где она чинится, поэтому починка короче, чем была, когда каждое из них надо было искать в боковой панели.

Ничего не теряется, и молчания об этом нет. Исследования остаются в папке исходящих — не архивируются и не удаляются, — каждое другое назначение того же исследования всё равно их получает, вкладка Журнал раздела Активность поднимает ошибку с именем исследования, удержанного назначения и причины, набор полей обезличивания на вкладке Настройки тоже её называет, а значок ⚠ рядом с разделом Исследований считает эти файлы — и сам этот значок является кнопкой, открывающей вкладку Застряли. Само по себе удержание не снимается ничем: таймер его не отсчитывает, и никто его не повторяет.

Пиксели не очищаются. Данные пациента, впечатанные в изображение, — надпись, которую УЗИ или вторичный захват печатает внутри кадра, — переживают любой профиль, и именно так «анонимизированные» данные чаще всего уходят из больницы с именем. Опция Clean Pixel Data (113101) намеренно не заявлена, потому что она не выполняется. Внутри программы ничто не может обнаружить эту утечку за вас: изображения должен посмотреть человек. Текст внутри структурированных отчётов также не читается.

Индекс

sqlite · index.db

Что делает
Держит по одной записи на сохранённый файл и выводит из них ответы о пациенте, исследовании и серии, поэтому сводка исследования никогда не разойдётся с объектами, которые она сводит. Это слой запросов, стоящий за Запросом/выдачей и DICOMweb.
Включайте, когда
Вы пользуетесь Запросом/выдачей или DICOMweb. Это их единственная зависимость.
Что гарантирует
Индекс — это кеш, а не источник истины. Его потеря стоит пересканирования, но никогда — изображения.

Запрос/выдача — C-FIND, C-MOVE, C-GET

Порт 11115 · Patient Root и Study Root

Что делает
Позволяет другой системе спросить «какие исследования у вас есть по этому пациенту?», а затем «пришлите их мне / вон на ту рабочую станцию». Это та половина PACS, с которой старое оборудование действительно умеет разговаривать: УЗИ 2009 года или считыватель CR никогда не заговорит на DICOMweb, но на DIMSE — говорит.
Включайте, когда
Рабочие станции или аппараты должны забирать исследования отсюда, или вы выступаете временным архивом на время аварии основного PACS.
Что гарантирует
C-MOVE никогда не выдумывает список объектов — он разрешает его через индекс. Всё, о чём индекс знает, но что не читается с диска, засчитывается как неудавшаяся подоперация и называется в Failed SOP Instance UID List. Это никогда не замалчивается: C-MOVE, который отчитывается об успехе, отправив меньше изображений, чем нашёл, — худший отказ, какой эта программа может допустить.

DICOMweb — QIDO-RS, WADO-RS, STOW-RS

HTTP, по пути /dicom-web на порту панели

Что делает
Позволяет современным просмотрщикам (OHIF, Weasis и подобным) запрашивать, получать и сохранять по HTTP, не договариваясь об ассоциации DICOM. Загрузка по STOW-RS проходит через ту же раскладку, что и C-STORE, поэтому исследование, отправленное просмотрщиком, неотличимо от присланного аппаратом.
Включайте, когда
Вам нужен веб-просмотрщик поверх архива. Помните, что токен защищает и эти маршруты.
CORS
cors_origins сверяется точно (схема + хост + порт) и по умолчанию пуст, поэтому источнику не из списка ничего не отражается. Синтаксиса шаблонов нет — но буквальная * принимается, и только потому, что вы её набрали: с этого момента отражается любой источник, и любая страница, которую откроет оператор, сможет прочитать архив с этой машины. Лучше назовите просмотрщик.
Чего не делает
/rendered, /thumbnail, bulkdata URI и перекодирование между синтаксисами передачи не реализованы и отвечают 406. Наполовину работающий просмотрщик хуже отсутствующей возможности: то, что не может быть получено, никогда не подделывается.

Modality Worklist (MWL)

Порт 11114 · C-FIND рабочего списка

Что делает
Выдаёт заявки аппаратам, чтобы лаборант не набирал данные пациента вручную. В каждой заявке есть заранее созданный Study Instance UID, который записывается в исследование, поэтому вернувшееся с аппарата исследование сопоставляется со своей заявкой точно. Поле AE целевого аппарата направляет заявку на одну станцию; пустое означает, что её видят все станции.
Включайте, когда
РИС не доводит заявки до аппаратов: потому что она недоступна, потому что на площадке РИС просто нет, или чтобы проверить поток РИС→PACS без живой РИС.

Зарегистрированные модальности

Без порта · Конфигурация → Модальности

Что делает
Список кабинетов, которые обслуживает этот узел: имя, понятное человеку, AE title станции, который рабочий список сверяет посимвольно, код модальности и, если нужно, имя станции. Это не список разрешений: аппарату не обязательно быть здесь, чтобы отправлять снимки, и его присутствие здесь ничего ему не разрешает. Выигрыш в том, что адресат заявки выбирается из списка, а не набирается руками, и что именно отсюда проверка рабочего списка одалживает AE title.
Заполняйте, когда
Вы раздаёте рабочий список или вводите заявки вручную. Пока не зарегистрирована ни одна станция, адресат заявки набирается руками, и опечатку нечем поймать.

Проверка рабочего списка — спросить чужую РИС

Исходящий клиент · C-FIND рабочего списка · Конфигурация → Модальности

Что делает
Отвечает на вопрос «почему этот аппарат не видит свой список», не трогая сам аппарат. Задайте адрес настоящей больничной РИС — или её брокера — в Настройках и нажмите Проверить рабочий список в строке модальности: Carino задаёт этой РИС тот вопрос, который задала бы сама модальность, её же AE title, и складывает каждый ответ в АктивностьПолученные. Один прогон задаёт один и тот же вопрос пять раз, снимая по одному ключу — станция, дата и модальность; затем без модальности; без даты; без станции, — потому что место неисправности показывает именно разница между ответами. Не вернулось ничего ни на один — у чужой РИС попросту нет записей; пустым вернулся только самый узкий — неверен AE title станции.
Прежде чем нажать
Сначала снимите модальность с сети. Проверка обращается как эта модальность, а две вещи, отвечающие на один AE title в одной РИС, — это путаница, которую потом придётся идти и разбирать.
Чего не делает
Это не служба, не расписание и не очередь. В Полученных лежит запись о том, что ответил кто-то другой: эти заявки никогда не раздаются в собственном рабочем списке этого узла, и он ни на одну из них не претендует. Очистить опустошает эту запись и только её.

Аварийная РИС — заявки HL7

Порт 2575 · HL7 ORM^O01 поверх MLLP

Что делает
Принимает заявки HL7 по MLLP, а также позволяет вводить их вручную в панели, когда выше по цепочке ничего живого не осталось. Когда исследование возвращается по C-STORE, оно сопоставляется со своей заявкой по номеру исследования (запасной вариант — ID пациента), и заявка закрывается и уходит в архив для аудита — но не стирается.
Включайте, когда
РИС недоступна или вы проверяете интеграцию.
Что гарантирует
Доставка изображений никогда не зависит от совпадения с заявкой. Исследование без подходящей заявки всё равно сохраняется и пересылается; заявка просто остаётся открытой для ручного сопоставления.
Панель заявок РИС: раскрывающийся блок «Новая заявка (ручной ввод)», вкладки «Открытые» и «Закрытые» и три открытые заявки, в каждой номер исследования, пациент, модальность, запрошенная процедура, отправитель HL7 и время постановки в очередь, с действиями «Захват», «Отменить» и «Удалить».
Заявки, пришедшие по HL7, и раскрывающийся блок сверху для ввода заявки вручную, когда выше по цепочке ничего живого не осталось. Каждая закрывается сама, когда придёт соответствующее исследование, — и уходит в архив, а не стирается.

Ни TLS, ни учётных данных. См. раздел о безопасности: любой, кто может открыть сокет на этот порт, может внедрить заявки. Доверенная клиническая сеть и брандмауэр — или совсем не включать.

Аварийное переключение

Наблюдение по C-ECHO за назначением, помеченным основным

Что делает
Пометьте назначение основным и включите наблюдение: Carino периодически шлёт ему C-ECHO и следит за неудачами пересылки. Если он остаётся недоступным дольше порога, вы получаете предложение включить аварийную РИС. Включение запускает локальный рабочий список, чтобы лаборанты продолжали снимать, удерживает каждое исследование, принятое во время аварии, и пересылает его, как только основной узел вернётся. Когда выходить из режима — решаете вы.
Включайте, когда
Эта машина — шлюз к PACS, от которого вы зависите. Именно поэтому слово «непрерывность» стоит в первой строке этого руководства.

Оповещения — webhook и почта

Только исходящие · HTTP webhook, SMTP

Что делает
Достаёт до тех, у кого панель не открыта. Плашки на экране хватает тому, кто и так смотрит на передачу, и она бесполезна ровно в том случае, ради которого всё это есть: основной узел падает в 03:00, а дежурный рентгенолог не сидит ни перед каким экраном. Два канала, независимые друг от друга, и оба мертвы, пока не включён общий рубильник.
Что отправляет
Только события переключения: сработало, включено, восстановлено. Никакая другая часть узла оповещений не порождает. Это не система оповещений общего назначения и не становится ею оттого, что её включили.
Кому приходит почта
Профилям, а не адресам. Письмо уходит каждому включённому профилю, который указан в оповещениях переключения и у которого заполнен адрес почты. Настроенный SMTP без единого адреса ни в одном профиле не отправляет ничего и молча — это первое, что стоит проверить, когда «почта включена, а никого не предупредили». Формулировки зависят от роли, а адрес отказавшего назначения попадает в письмо только тому, кому и так позволено видеть таблицу назначений: письмо уходит за пределы здания, и отозвать его нельзя.
Чего не сделает никогда
Не задержит и не сломает то, о чём сообщает. Каждая отправка передаётся рабочему потоку, очередь ограничена — самое старое отбрасывается, и отброшенное считается, потому что «основной узел лежит», доставленное после восстановления, хуже молчания, — а сбой становится счётчиком и строкой журнала, а не исключением. Отправлено, не удалось, отброшено, в очереди и последняя ошибка — всё это можно прочитать, поэтому «включено и ни разу ничего не доставило» видно до той аварии, которая на это рассчитывает. Проверяйте спокойным днём, а не во время аварии.

Виртуальная печать

Порт 11113 · DICOM Print, вывод в PDF

Что делает
Представляется принтером DICOM и захватывает всё присланное на него в PDF. Для оборудования, единственный вывод которого — плёнка, это способ сохранить хоть что-то, что можно подшить.
Включайте, когда
У вас есть аппарат, умеющий только печатать, чей вывод вы хотите спасти.
Вкладка Ожидают раздела Исследований: два элемента в очереди, PDF и JPEG, у каждого уже заполнены ФИО пациента, ID пациента, номер исследования, дата и описание исследования, взятые из исследования, рядом с которым они пришли, ссылка «Просмотр» и кнопки «Утвердить» и «Отбросить».
Снятая плёнка — и любой PDF или изображение, найденные рядом с исследованием, — ждут здесь с уже заполненными данными самого исследования, а не превращаются в DICOM наугад. Вы подтверждаете личность, а затем утверждаете.

Веб-панель

Порт 8042 · HTTP, по умолчанию 127.0.0.1

Что делает
Состояние каждой службы, назначения, заявки, живой журнал активности, настройки и встроенный редактор DICOM. Всё, что делает панель, есть и в CLI.
Как ориентироваться
В боковой панели шесть разделов: Обзор, Службы, Исследований, Заявки, Конфигурация и Активность — и седьмой, Тестовый узел, только в процессе, запущенном с --dev-peer. Три вопроса, задаваемые одной и той же куче файлов, — это вкладки раздела Исследований: История, Ожидают и Застряли; то, что задаётся при вводе машины в работу, — это вкладки Конфигурации: Назначения, Маршруты, Настройки, Модальности и Люди, и открывается она на Назначениях, а не на Настройках; а три записи — это вкладки Активности: Журнал, Аудит и Полученные, последняя — то, что ответила чужая РИС, когда этот узел спросил её как одну из ваших модальностей. У Заявок своя полоса открытых и закрытых. Раздел Исследований несёт два счётчика, которые сами по себе являются кнопками, 📎 ожидающих и ⚠ застрявших, и каждый открывает свою вкладку: поэтому попасть в Застряли — один клик, пока что-то застряло, и два (раздел, затем вкладка), когда не застряло ничего, потому что нулевой счётчик прячется. Счётчик, постоянно показывающий 0, — это вечно включённая сигнализация, а на такие никто не смотрит. У Заявок свой счётчик на тех же условиях. У каждой панели и вкладки есть свой адрес — #studies/stuck, #configuration/routing, #activity/logs, — поэтому место в панели можно занести в закладки, продиктовать по телефону во время аварии и покинуть кнопкой «Назад» в браузере; старые ссылки вида #dlgStuck по-прежнему работают. Обзор — единственное исключение: он откликается на #overview, если его набрать, но никогда не пишется в адресную строку и не оставляет записи в истории, потому что он печатает имя пациента, а оставленный без присмотра экран не должен возвращаться к нему после перезагрузки.
Индикаторы служб в шапке
Индикаторы служб вдоль верхнего края не меняются для тех, кому можно запускать и останавливать службы. Профиль без services.control по-прежнему их видит — как индикаторы, — но они неактивны: состояние службы не является привилегированным (регистратура должна видеть, что приём не работает), а переключатель, который сервер всё равно отклонит, предлагать незачем.
Уводите с петлевого адреса, когда
Только при конкретной причине — и тогда с токеном (обязательно) и с HTTPS впереди. См. правило токена.
Вкладка Настройки раздела Конфигурация, её двенадцать наборов полей в три колонки на широком экране: AE title приёмника, адрес прослушивания, порт, папка хранения, порог свободного места, список разрешённых AE и поля TLS; вызывающий AE автоотправки, наблюдаемая папка, интервал опроса, действие после отправки файла и её настройки доверия TLS; а рядом наборы полей приёма печати, аварийного переключения, рабочего списка, РИС, Запроса/выдачи, DICOMweb, индекса, обезличивания, доступа к API и интеграций.
Всё, что есть в config.json, в виде формы. Файл остаётся источником истины: правка здесь записывается в него, а конфигурация, которую панель отказалась бы сохранить, отклоняется с указанием причины, а не переписывается молча.
Встроенный редактор DICOM с открытым примером исследования: слева изображение с ползунками центра и ширины окна, справа панель «Основное» (клиническое) с пациентом, модальностью и описанием исследования, где имя пациента скрыто за кнопкой «Показать», а под ней панель «Техническое» с SOP Instance UID, синтаксисом передачи, фотометрической интерпретацией и SHA-256 файла.
Встроенный редактор, открываемый из исследования на вкладке История или сам по себе. Он работает целиком в браузере — никуда ничего не загружается, — и имя пациента изначально скрыто, потому что чаще всего заголовок открывают, чтобы прочитать UID.
Вкладка правки в редакторе: панель инструментов с кнопками «Анонимизировать всё», «Рандомизировать всё», «+ Тег», JSON, CSV, «Печать» и «Скачать всё»; полоса из пяти загруженных серий; фильтры по пациенту, исследованию, серии, изображению, оборудованию, UID и приватным тегам; сама таблица тегов, по строке на атрибут с номерами группы и элемента, описанием, VR и редактируемым значением; и в правой трети панель правки изображения — поворот, отражение, инверсия и закрашивание впечатанного текста, записываемые в сохранённые пиксели — над предпросмотром, свёрнутым значением окна/уровня и областью для перетаскивания файлов.
Таблица тегов, где заголовок исправляют перед повторной отправкой исследования: неверный номер исследования или опечатка в ID пациента, поправленные на шлюзе, а не на аппарате, который уже занят другим.

Тестовый узел — одноразовый второй архив

Только петлевой адрес · pacs serve --dev-peer · никогда в поставляемой сборке

Что делает
Создаёт внутри этого же процесса целый второй архив — только на 127.0.0.1, со своим AE title, своими портами приёма и Query/Retrieve и своим хранилищем во временной папке, — а на этом узле сразу появляется назначение, нацеленное на него. Это настоящее место, куда исследованию есть куда уйти, пока вы проверяете, что пересылка, правило маршрутизации, обезличивание при пересылке или C-MOVE делают то, что вы думаете. Рядом создаётся выключенное назначение-«чёрная дыра»: включите его — и отправки начнут падать нарочно, а это и есть способ заполнить вкладку «Застряли» по требованию и увидеть, как выглядит затор, до того как он случится по-настоящему.
Как открыть
Запустите движок с pacs serve --dev-peer — он пишет об этом строкой ниже адреса панели, — затем выберите в боковой панели 🧪 Тестовый узел и нажмите Создать узел. Дальше панель показывает его AE title, оба порта, папку и сколько он принял. Без этого ключа строки просто нет, API отвечает 404, и никакая настройка в config.json её не вернёт. Так и задумано: настройку может изменить любой, у кого есть токен панели, а до аргумента запуска по HTTP не дотянуться вовсе.
Кому можно
Профилю, которому позволено создавать и удалять одноразовый тестовый архив. Из встроенных профилей это ИТ и администратор: доказать, где умирает пересылка, — работа стенда. Регистратуре и рентгенологу это не даётся: второй архив — инструмент стенда, а не клиники. И создание, и удаление записываются в журнал аудита.
Что станет с тем, что он сохранил
Уходит всё — и папка, и обе строки назначений на этом узле — когда вы нажимаете Удалить, когда процесс останавливается и при следующем запуске после аварийного завершения или отключения питания. Не остаётся ничего, и ровно ради этого им пользуются вместо того, чтобы написать вторую конфигурацию руками и забыть про папку. Одно стоит иметь в виду: удалите узел, пока против него ещё стоят отправки, — и этим отправкам будет некуда повторять; разберите их в разделе ИсследованийЗастряли.

Это инструмент стенда, и ключ запуска — то, что удерживает его в этой роли. Узел слушает только петлевой адрес, без исключений: тестовый архив, который могла бы найти модальность из сети, был бы вторым, никем не проверяемым хранилищем чьих-то снимков, и никто бы не заметил, что он там есть. Не передавайте этот ключ на узле с настоящими исследованиями. Настольная и контейнерная сборки никогда не передают его за вас.

5. Это не медицинское изделие

Carino DICOM не является медицинским изделием. Он не сертифицирован, не допущен и не зарегистрирован ни одним регулятором ни в одной стране. У него нет ни маркировки CE как медицинского изделия, ни допуска FDA, ни регистрации в ANVISA, COFEPRIS или любом равнозначном органе. Никто не проводил в отношении него валидацию клинического применения.

Он не предназначен для первичной диагностики. Диагностического просмотрщика здесь нет: ни оконного преобразования, ни измерений, ни калиброванной цепочки отображения, ни контроля над монитором, на котором он показан. Читайте исследования на валидированной рабочей станции, которая у вас уже есть.

Сказано прямо, потому что это полезнее сноски:

Всё это не пессимизм. Проект всерьёз исходит из того, что изображение, которое молча так и не пришло, хуже, чем сбой, — поэтому он скорее откажется запускаться, будет явно считать неудавшиеся подоперации и отправит лишнее, чем недоотправит. Он придерживает доставку ровно в одном случае, громко и обратимо: назначение, для которого правило требует обезличивания, когда обещанную очистку выполнить нельзя, — потому что профиль выключен или потому что профиль включён, но деидентификатор построить не удалось. И он говорит, что именно из двух, потому что чинятся они по-разному. Но программа может отвечать только за то, что делает сама; остальное — на вас.

6. Лицензия и где получить помощь

Carino DICOM выпускается под AGPL-3.0-or-later. Поскольку это сетевой сервер, применяется §13: если вы запускаете изменённую версию как сервис, вы обязаны предложить её исходный код тем, кто им пользуется. Сохраняйте файл LICENSE и ссылку на исходный код при любом изменённом развёртывании.

Нашли ошибку в этом руководстве — или перевод, который врач-рентгенолог никогда не произнёс бы? Заведите issue. Документация на русском, испанском и португальском — часть проекта, а не дополнение.

Carino DICOM · главная страница · часть мастерской carino.systems · AGPL-3.0-or-later.