ЛУЧ ИИ
Документация

Подключение Луч ИИ

Один движок — много способов подключения, платформ и интеграций. Готовое отмечено «Доступно», запланированное — «Скоро» (код приведён предварительно, до релиза может меняться).

Возможности

ДоступноMPR + косые срезыАксиал / сагиттал / коронал и oblique-реформации
Доступно3D-объём (DVR)Объёмный рендер на WebGPU прямо в браузере
ДоступноPT/CT fusionКо-регистрация ПЭТ в сетку КТ + палитры
ДоступноSEG-сегментацииЦветной overlay структур на всех плоскостях
ДоступноMultiframe cineУЗИ / XA / NM / OCT — все кадры, плей/скраб/fps
ДоступноИзмерения и инструментыЛинейка / угол / ROI с HU, окно-уровень, зум и панорама, снимок панели
ДоступноФорматы и отчётыCT/MR/PT/NM/US/XA/RF/CR/DX/MG/SC/CBCT/OCT · цветные фото · SR-отчёты + PDF · LE/BE, JPEG-LS, JPEG 2000, JPEG lossless, RLE
ДоступноDICOMweb / PACSИсследование или одна серия: QIDO/WADO-RS + static-wado, сжатие JPEG-LS / JPEG 2000
ДоступноWhite-labelТема, бренд-цвет, локализация, логотип
СкороИИ-слойНаложение находок поверх изображений

Установка

  • ДоступноnpmПакет @luchai/viewer для веба и встраивания.
  • СкороCapacitorСборка мобильного приложения (iOS / Android).
  • СкороElectronДесктоп-оболочка (Windows / macOS / Linux).
npm i @luchai/viewer

# The WASM core is bundled inline (nothing to host). Mount it CLIENT-SIDE:
# createLuchaiApp touches the DOM/WebGPU, so import it dynamically
# (Next.js: a 'use client' component + await import).
#
# Webpack / Next.js only: the inline core has a dead Node branch that
# references node builtins. It never runs in the browser; stub them so
# the bundle resolves:
#   // next.config.mjs
#   webpack: (config) => {
#     config.resolve.fallback = { ...config.resolve.fallback,
#       module: false, fs: false, path: false, url: false, crypto: false, os: false };
#     return config;
#   }
# (Vite / esbuild need no config.)
#
# Then mount the viewer and pass your license token — see Quickstart
# (createLuchaiApp's licenseKey) or React (the licenseKey prop).
# We issue the token bound to your domain.

Быстрый старт

Смонтируйте вьювер в любой DOM-элемент: Доступно

import { createLuchaiApp } from '@luchai/viewer';

const viewer = createLuchaiApp(
  document.getElementById('viewer'),
  {
    locale: 'ru',
    theme: 'auto',                 // 'dark' | 'light' | 'auto' (follows the OS)
    brand: { accent: '#2F80FF' },  // your brand colour → recolours the viewer
    onExit: () => history.back(),
    // The left Studies panel collapses (chevron) and drag-resizes from its right
    // edge; the width + collapsed state persist in localStorage. Set storageKey
    // to namespace those prefs when you embed more than one viewer on a page.
    storageKey: 'report',
    // License token, verified on-device. Optional in dev: localhost is
    // always free. On your production domain an absent/invalid token
    // gates the viewer (degraded image + "license not active" panel).
    licenseKey: 'eyJ2IjoxLC...<token>',
    // (optional) licenseServer / telemetry — see the License section.
  },
);

// The returned handle drives the running viewer:
await viewer.loadStudy(fileInput.files);   // load / replace the study
const off = viewer.on('studyLoaded', (s) => console.log(s.modality));
viewer.reset();    // clear back to the empty state
off();             // unsubscribe
viewer.destroy();  // tear down when no longer needed

createLuchaiApp возвращает handle для управления вьювером: loadStudy (загрузить/заменить исследование), reset (вернуть к пустому состоянию), on (подписка на события studyLoaded / error / license — возвращает функцию отписки) и destroy.

Лицензия

Для работы на боевом домене нужен лицензионный ключ — запросите его у Луч ИИ, указав домен(ы), на которых будет работать вьювер. На localhost и *.local ключ не требуется (свободно для разработки).

Куда вписать ключ

// How to license, in 3 steps:
//   1. We issue you a token (a string), bound to YOUR production domain(s) + an expiry.
//   2. Put it in the config: createLuchaiApp(el, { licenseKey }) — or the <LuchaiViewer licenseKey=…> prop.
//   3. Deploy. On your domain the viewer is licensed; localhost / *.local are always free in dev.
// The token is safe to ship in your frontend bundle (it only works on your domain), like a
// Mapbox / Stripe PUBLISHABLE key. Nothing else to wire — the rest below is optional.

createLuchaiApp(el, {
  // The license token we issue you, bound to YOUR domain(s) + an expiry.
  // Keep it in your runtime config (like a Mapbox / Stripe publishable key).
  // localhost / *.local need no key (free for development).
  licenseKey: 'eyJ2IjoxLC...<your token>',

  // (optional) Online validation endpoint. On a production domain the token
  // is exchanged here for a short-lived signed grant (required to unlock the
  // image); the grant is refreshed in the background and a cached grant keeps
  // working offline for up to 30 days, so a transient outage never interrupts
  // the viewer. Defaults to our hosted control plane (api.луч-ии.рф) — reachable
  // from Russia and abroad, so you don't need to set this. Override only for an
  // on-prem deployment. Patient images never leave the browser. For a fully
  // air-gapped install we instead issue an OFFLINE token that needs no server.
  // licenseServer: 'https://api.xn----ptbaj4bya.xn--p1ai',

  // Serving to a Russian audience? The SDK's license check (small request to
  // api.луч-ии.рф) works inside Russia. But YOUR site's own hosting must be
  // reachable too: avoid *.pages.dev / *.workers.dev (blocked), and if you host
  // on Cloudflare, disable ECH on your zone or serve from RU-reachable infra —
  // otherwise browsers in Russia stall on the TLS handshake. The WASM core is
  // inlined in the bundle (no extra fetch), so it loads straight from your host.

  // (optional) Anonymous, PHI-free telemetry (import/error counters +
  // format / SDK version / browser context — never snapshots or patient
  // data). On by default; set false to opt out.
  // telemetry: false,
});

Ключ привязан к вашему домену и сроку и проверяется прямо на устройстве. На боевом домене обычного ключа недостаточно — он обменивается на короткоживущий подписанный grant, и только grant разблокирует изображение в движке; grant фоном обновляется, а его кэш позволяет работать офлайн до 30 дней, поэтому кратковременный сбой сети не прерывает работу. Для полностью изолированных (air-gapped) установок выдаётся отдельный офлайн-ключ, которому сервер не нужен. Снимки пациента никуда не уходят — наружу идут только данные лицензии (домен/компания) и анонимные счётчики. Без действующего ключа/grant'а на боевом домене вьювер показывает плашку «Лицензия не активирована» и не отдаёт изображение.

Способы встраивания

Три способа подключить вьювер к вашему продукту — выбираете под свою архитектуру.

npm / программный APIДоступно

Полный контроль из вашего кода — пример выше (createLuchaiApp).

React-компонентДоступно

Тот же пакет, импорт из подпути @luchai/viewer/react. Декларативные пропсы (locale / theme / brand / study), а ref даёт императивные действия { loadStudy, reset } для сценария «открыть по клику». Изменение пропса study загружает новое исследование.

import { useRef } from 'react';
import { LuchaiViewer, type LuchaiViewerHandle } from '@luchai/viewer/react';

function Report({ studyZipUrl }: { studyZipUrl: string }) {
  const ref = useRef<LuchaiViewerHandle>(null);
  return (
    <div style={{ height: 600 }}>
      <button onClick={() => ref.current?.reset()}>Clear</button>
      <LuchaiViewer
        ref={ref}
        licenseKey="eyJ2IjoxLC...<your token>"  {/* the token we issue you (bound to your domain). localhost is free. See License. */}
        locale="ru"
        theme="auto"
        brand={{ accent: '#2F80FF' }}
        study={studyZipUrl}                 {/* controlled: changing this loads it */}
        onStudyLoaded={(s) => console.log(s.modality)}
        onError={(e) => console.error(e)}
      />
    </div>
  );
}

Веб-компонентСкоро

Встраивание одним тегом — без сборки и фреймворка.

iframe + postMessageСкоро

Полная изоляция и кросс-доменное управление через сообщения.

White-label приложения

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

  • ДоступноВеб-приложениеГотовый вьювер в браузере, on-device, под ваш бренд.
  • СкороДесктоп (Windows / macOS / Linux)Electron-оболочка из того же ядра.
  • СкороМобильные (iOS / Android)Нативные приложения через Capacitor.
  • СкороРоссийские ОС (Astra / РЕД / Аврора)Нативная сборка под импортозамещение.
// Rebrand the turnkey viewer entirely via config — no fork.
const viewer = createLuchaiApp(document.getElementById('viewer'), {
  theme: 'dark',                     // 'dark' | 'light' | 'auto'
  brand: {
    name: 'РадиоПро',                // wordmark in the header (default «Луч ИИ»)
    accent: '#0A84FF',               // brand colour → recolours the whole viewer
    logoLight: '/logo-light.svg',    // logo per theme (optional; falls back to the name)
    logoDark:  '/logo-dark.svg',
    backLabel: 'К списку',           // text on the back button (default: icon-only chevron)
    backIcon:  '/icons/back.svg',    // optional custom back glyph (URL)
  },
  features: {                        // hide the surfaces you drive yourself
    import: false,                   //   e.g. your own study list opens studies →
    library: false,                  //   no built-in Import button / Studies sidebar
  },
  onExit: () => closeFullscreen(),   // setting onExit shows the back button → your handler
});

// Open studies from YOUR list into this ONE instance (no remount per click). Fullscreen is just
// your container going fullscreen (CSS / Fullscreen API) — the viewer fills whatever box it's in.
async function openStudy(study) {
  document.getElementById('viewer').requestFullscreen?.();
  await viewer.loadStudy(study.zipUrl);   // a .zip URL — or File[]/Blob/DICOMweb (see "Loading data")
}

Загрузка данных

  • ДоступноЛокальные файлыDrag-and-drop DICOM, папки или архива — обработка прямо в браузере. Поддерживаются ZIP (в т.ч. вложенный ZIP-в-ZIP), TAR / TAR.GZ / GZ, больничный CD с DICOMDIR (файл-индекс пропускается, изображения без расширения импортируются) и папки с подпапками. Архив с мусорными файлами (PDF/EXE/autorun) импортирует только DICOM. Защищённый паролем архив и неподдерживаемые контейнеры (ISO/7z/RAR) дают понятную ошибку, не падение. Архив без DICOM, но с изображениями (JPG/PNG/BMP/WEBP/GIF), открывается как 2D-галерея; форматы, которые браузер не декодирует (TIFF/HEIC/NIfTI/PDF), показывают понятное сообщение «формат изображения не поддерживается», а не падение или чёрный экран. Данные пациента и серии копируются в буфер одним кликом по карточке.
  • ДоступноDICOMweb — WADO-RSПолучение изображений по HTTP — всё исследование (loadFromDicomweb(studyUID)) или одна серия. Реальные PACS и static-wado (metadata-JSON + кадры).
  • ДоступноDICOMweb — QIDO-RSПоиск исследований и серий — listStudies(filters); в «PACS»-модалке исследование → серии → одна серия.
  • СкороDICOMweb — STOW-RSЗагрузка исследований в архив.
  • СкороDICOM-сеть — C-STORE / C-FIND / C-MOVEКлассический протокол DIMSE для подключения к PACS.
  • СкороModality Worklist / MPPSРабочий список и статусы исследований.

Загрузка из PACS / DICOMwebДоступно

Задайте config.dataSource — и вьювер ищет исследования через QIDO-RS и забирает их по WADO-RS прямо с PACS / DICOMweb-сервера, а не только из перетянутых файлов. listStudies(filters) возвращает список исследований, loadFromDicomweb(studyUID) открывает выбранное. headers внедряют авторизацию (Bearer-токен или кастомная аутентификация PACS) в каждый запрос. Полученные .dcm-инстансы проходят через тот же импорт. Работает с реальными PACS (dcm4chee / Orthanc / Google Healthcare); PACS должен отдавать CORS-заголовки для кросс-доменных запросов браузера.

Загрузка по одной серии и static-wado. Встроенная «PACS»-модалка показывает список исследований, затем раскрывает исследование → серии (QIDO), чтобы открыть только ОДНУ серию — забирается лишь она (WADO-RS на серию), это ~15× меньше данных, чем всё исследование. Поддерживаются и статические DICOMweb-серверы (static-wado / демо OHIF): если сервер отклоняет полный WADO-RS-ретрив инстанса, вьювер берёт его metadata-JSON по серии плюс пиксели по кадрам и собирает каждый инстанс в браузере — включая сжатые кадры (JPEG-LS / JPEG 2000 / JPEG baseline / JPEG lossless / RLE). Многокадровые инстансы (УЗИ / XA / NM / enhanced) забираются ЦЕЛИКОМ, все кадры, а не только первый.

// Load studies straight from a PACS / DICOMweb server (QIDO-RS query +
// WADO-RS retrieve) — not only drag-dropped files. Point the viewer at your
// server with config.dataSource; `headers` inject auth into every request
// (Authorization: Bearer … or a custom-auth PACS token). qidoRoot / wadoRoot
// are usually the same base (…/dicomweb). The retrieved .dcm instances run
// through the SAME import pipeline — no extra parsing on your side.
const viewer = createLuchaiApp(el, {
  locale: 'ru',
  dataSource: {
    type: 'dicomweb',
    qidoRoot: 'https://pacs.example.ru/dicomweb',
    wadoRoot: 'https://pacs.example.ru/dicomweb',
    headers: { Authorization: 'Bearer <token>' }, // optional
  },
});

// List studies (QIDO-RS). Filters are standard QIDO query params.
const studies = await viewer.listStudies({ PatientName: 'Иванов*', StudyDate: '20240101-20241231' });
// → [{ studyInstanceUID, patientName, patientID, studyDate, studyDescription,
//      accessionNumber, modalities, numberOfSeries, numberOfInstances }]

// Open one by its StudyInstanceUID — retrieved over WADO-RS, then rendered.
await viewer.loadFromDicomweb(studies[0].studyInstanceUID);

// A built-in "PACS" button + study browser also appears automatically when dataSource
// is set: it lists studies, then drills study → SERIES so the user can open just ONE
// series (browses with QIDO, retrieves that series with WADO-RS) — ~15× less to fetch
// than the whole study. The programmatic listStudies / loadFromDicomweb above is the
// integrator path. Errors (network / 401-403 auth / 404) surface via 'error' / onError.

// CORS note: the browser fetches QIDO + WADO cross-origin, so the PACS must send
// Access-Control-Allow-Origin and allow the Accept / Authorization request headers.
// Works against real PACS (dcm4chee / Orthanc / Google Healthcare) AND static DICOMweb
// servers (static-wado / the OHIF demo): if a whole-instance WADO-RS retrieve is refused,
// the viewer falls back to the server's per-series metadata-JSON + per-frame pixels and
// reassembles each instance client-side (incl. JPEG-LS / JPEG 2000 / JPEG-baseline /
// JPEG-lossless / RLE compressed frames). Multi-frame instances fetch ALL frames.

Что принимает loadStudyДоступно

И loadStudy, и пропс study принимают несколько форматов входа — все проходят через тот же импорт в браузере (URL сначала скачиваются в байты):

// loadStudy / the `study` prop accept several shapes — all run through the
// same in-browser import path (URLs are fetched to bytes first):
viewer.loadStudy(fileInput.files);          // File[] / FileList (picker or folder)
viewer.loadStudy(zipBlob);                  // a Blob / ArrayBuffer (a .zip you hold)
viewer.loadStudy('https://pacs/scan.zip');  // a URL to a .zip archive
viewer.loadStudy([                          // a list of DICOM file URLs
  'https://pacs/img1.dcm',
  'https://pacs/img2.dcm',
]);

Клик по превью → loadStudyДоступно

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

// One persistent viewer instance + a list of thumbnails: clicking a
// thumbnail loads that study into the SAME viewer (no remount).
const viewer = createLuchaiApp(document.getElementById('viewer'), { locale: 'ru' });

for (const study of studies) {
  thumb(study).addEventListener('click', () => {
    viewer.loadStudy(study.zipUrl);   // replaces the current study in place
  });
}

Экран предпросмотра импортаДоступно

При импорте пользователем (кнопка «Импорт» или drag-drop) перед добавлением в библиотеку показывается экран предпросмотра в стиле iOS: список найденных исследований и серий с чекбоксами (что импортировать) плюс «хвост» нераспознанного/неподдерживаемого с отметкой «Импортировать всё равно». Программная загрузка (config.study или handle.loadStudy от интегратора) открывает исследование сразу, без предпросмотра. Поведением управляет features.importPreview: true — показывать всегда, false — никогда, по умолчанию (не задано) — только при импорте пользователем.

Видимость пропущенногоДоступно

Объекты, которые вьювер не может отобразить, не пропадают молча: над списком исследований показывается уведомление «Не отображены: …» с типом и количеством. Сюда попадают RT-структуры/дозы/планы (RTSTRUCT/RTDOSE/RTPLAN), состояния представления (PR), ключевые объекты и регистрации (KO/REG), микроскопия целого слайда (SM, гигапиксель), а также неподдерживаемые форматы изображений (TIFF/HEIC/NIfTI/PDF). Многое теперь НЕ дропается, а показывается: SR-отчёты и encapsulated PDF открываются как отдельная строка-серия «Отчёт»; офтальмологические снимки (OP, OCT/OPT, внешняя камера VL/XC) рендерятся — OCT как многокадровое кино B-сканов, цветные фото в цвете. Сегментации (SEG) РЕНДЕРЯТСЯ как оверлей: вьювер связывает SEG с серией, на которую она ссылается, строит выровненный том меток и накладывает цветную сегментацию поверх КТ/МРТ на всех плоскостях (аксиал/сагиттал/коронал/наклон), с панелью структур (показ/скрытие каждой), общим ползунком прозрачности и выключателем. Так на исследовании «КТ + SEG + SR» пользователь видит КТ с цветной сегментацией и понятное объяснение, что ещё было в архиве.

МИС / RIS

  • СкороHL7 v2Направления на исследование и результаты.
  • СкороFHIRСовременный REST-обмен с медицинскими системами.
  • СкороIHE-профилиScheduled Workflow и AI Results (AIR) — часто требуют госзаказчики.

ИИ-слой

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

  • СкороAI plug-in APIМаска или heatmap модели → real-time оверлей на устройстве.
  • СкороDICOM SR / SEG / Parametric MapsСтандартный перенос результатов ИИ — совместимость со всей экосистемой.
  • СкороIHE AIR (AI Results)Госпитальный профиль для ИИ-результатов.
  • СкороOn-device inferenceЗапуск модели на устройстве (ONNX Runtime / WebGPU).

Control API

Управление вьювером с хоста — методы и события. Низкоуровневый API вьюпорта. Готовый вьювер уже вызывает всё это из своего UI; для программного управления получите вьюпорт из handle — createLuchaiApp(...).getViewer() (без отдельного пакета; null до монтирования, зовите после первого «studyLoaded»). Частично

// The Control API below is the low-level viewport surface. The turnkey viewer already drives
// all of it from its own UI — reach for it only for PROGRAMMATIC control. No extra package: the
// handle returned by createLuchaiApp exposes it via getViewer() (returns null until mounted, so
// call it after the first 'studyLoaded', or guard with ?.).
import { createLuchaiApp } from '@luchai/viewer';
const app = createLuchaiApp(document.getElementById('viewer'), { /* config */ });
const viewer = app.getViewer();        // the low-level viewport (or null before it mounts)

// Actions
await viewer.loadStudy(input);         // load / replace the current study (see "Loading data")
viewer.reset();                        // clear back to the empty state, incl. measurements

// Window/level — set the grayscale window directly, or apply a clinical preset.
viewer.setWindowLevel(2000, 500);      // window width / window centre (HU for CT)
viewer.setPreset('bone');              // 'bone' | 'dental' | 'brain' | 'lung' | 'softTissue' | 'abdomen'

// Display colormap for monochrome 2D/MPR planes. NM SPECT / PET auto-render in a hot-iron palette
// (bright uptake on black — the nuclear-medicine convention) with the window floored at 0; CT/MR stay
// grayscale. Force any palette: gray, hot (hot-iron), pet (rainbow), jet. The demo shows a palette picker
// centred in each NM/PT panel header. Cross-reference lines / annotations are drawn on a display-resolution
// layer, so they stay crisp even on a low-res slice (e.g. a 128² NM frame).
viewer.setColormap('auto');            // 'auto' (hot for NM/PT, else gray — default) | 'gray' | 'hot' | 'pet' | 'jet'

// 3D volume rendering (DVR) uses vtk-style TRANSFER-FUNCTION presets — each bakes a per-tissue colour +
// opacity curve over HU (bone = ivory, soft tissue = flesh, lung, angio = vessel-red, MR = grayscale ramp),
// not a flat grayscale ramp. The 3D panel header has the preset picker; a depth (front-clip) slider peels
// the near part of the volume to look inside. (DVR needs a GPU/WebGPU; without one the 3D panel is disabled
// and 2D/MPR still work.)

// Measurement tools (ruler / angle / ROI) live on the top toolbar; you can also
// drive them from the host. Toggle a tool, then the user draws on the MPR panels;
// a length reads in mm, an angle in degrees, a rectangle/ellipse ROI in mm² with
// HU mean/SD/min-max (CT) or raw-intensity stats. Set 'crosshair' to return to navigation.
viewer.setMeasureTool('length');       // 'crosshair' | 'length' | 'angle' | 'rect' | 'ellipse'
viewer.getMeasureTool();               // the currently-armed tool
viewer.getMeasurements();              // [{ id, kind, axis, slice, points, label }] — persisted per slice
viewer.removeMeasurement(id);          // remove a SINGLE measurement by id
viewer.clearMeasurements();            // remove every overlay
const png = viewer.capturePanel(0);    // an MPR panel as a PNG data-URL (0 = axial); snapshot3D() for 3D

// Sidebar thumbnails — decode a slice to auto-windowed 8-bit grey on the viewer's licensed
// core (so previews don't spin up a second, unlicensed core). null if the core isn't ready.
const thumb = viewer.decodeThumb(bytes); // { width, height, gray } | null

// Layout & MPR mode. A volumetric series opens in the NATIVE acquired plane (a 1×1 scroll over its
// real slices — fast, NO volume built). The "MPR" toolbar button builds the volume on demand and
// switches to MPR; the Layout switcher (⊞) picks a grid. The single-plane / 3D layouts also build the
// volume on demand. Reference lines are solid and coloured per 3D-Slicer convention (axial = red,
// sagittal = yellow, coronal = green); each MPR panel carries a matching corner colour chip.
viewer.setLayout('native');            // 'native' (acquired 1×1, no volume) — the default after open
viewer.setLayout('mpr-2x2');           // build volume → 2×2 (axial / sagittal / coronal / 3D)
viewer.setLayout('mpr-1x2');           // axial | sagittal side-by-side
viewer.setLayout('mpr-3up');           // one large axial + sagittal/coronal stacked
viewer.setLayout('axial');             // a single MPR plane: 'axial' | 'sagittal' | 'coronal' | '3d'
viewer.getLayout();                    // the current layout name
viewer.loadStudy({ files, forceVolume: true }); // skip native, build the MPR volume up front
viewer.on('study-loaded', (i) => {});  // i.native === true ⇒ opened in the native (deferred-build) view
viewer.on('build-start', () => {});    // a deferred volume build STARTED (native view → first MPR/3D, any
                                       // entry point inc. a panel double-tap) → show your loading overlay;
                                       // 'study-loaded' / 'error' clear it. 'progress' carries phase/value.

// Oblique MPR — rotate a reformat plane by dragging a reference-line ENDPOINT (a tilted cut).
// 'linked' rotates the other planes around the focus; 'single' rotates only the dragged plane.
viewer.setObliqueLinkage('linked');    // 'single' | 'linked'
viewer.setRotationSnap(15);            // 0 = off, 15 = snap to 15° on commit
viewer.setShowReferenceLines(true);    // show/hide the cross-reference lines + rotation handles
viewer.resetOblique();                 // clear all tilt → back to orthogonal planes
viewer.getObliqueSettings();           // { active, linkage, snapDeg, showRefLines }
viewer.on('oblique', (s) => {});       // fires when a plane is tilted/reset or a setting changes

// Multiframe cine — a single instance with NumberOfFrames > 1 (US / XA / NM / enhanced) is a
// TEMPORAL frame stack and plays as cine (colour US shows in correct colour). Controls appear
// automatically; you can also drive playback from the host. No-op for an ordinary single/volume series.
viewer.setCinePlayback(true);          // play / pause
viewer.setCineFrameRate(20);           // playback speed in frames/s (clamped 1..60; default = the
                                       // DICOM RecommendedDisplayFrameRate / CineRate, else ~15)
viewer.setCineLoop(true);              // loop at the end vs play once and stop
viewer.setCineFrame(0);                // scrub to a frame (0-based; pauses playback)
viewer.getCineState();                 // { available, playing, frame, frameCount, fps, loop }
viewer.on('cine', (s) => {});          // fires on play/pause, frame advance, fps/loop change

// Segmentation (SEG) overlay — render a DICOM Segmentation as a coloured wash over the image on
// ALL planes (axial / sagittal / coronal / oblique). The importer associates a SEG with the series
// it references and loads it automatically; you can also drive it from the host. The label volume is
// built in the engine, aligned to the source volume, and sampled on the same MPR planes as the image.
const ok = await viewer.loadSegmentation(segBytes); // Uint8Array of a SEG .dcm; false if it doesn't align
viewer.setSegOverlayEnabled(true);     // master on/off for the whole overlay
viewer.setSegOpacity(0.45);            // global wash strength, 0..1
viewer.setSegmentVisible(6, false);    // per-segment show/hide (by SegmentNumber)
viewer.clearSegmentation();            // drop the overlay
viewer.getSegOverlay();                // { available, enabled, opacity, segments:[{number,label,r,g,b,visible}] }
viewer.on('seg', (s) => {});           // fires when a segmentation loads / toggles / opacity changes

// PT/CT fusion — overlay a PET series on a CT series of the same study (same FrameOfReferenceUID).
// The engine resamples the PET into the CT voxel grid (patient-coordinate co-registration), so both
// sample the SAME MPR plane and the fusion follows on ALL planes (axial / sagittal / coronal / oblique).
// The importer pairs the CT with its PET partner and loads it automatically when you open the CT; you
// can also drive it from the host. PET is windowed + colour-mapped in the engine and blended over the CT.
const ok2 = await viewer.loadFusion(petBytesArray); // Uint8Array[] of the PET series; false if it doesn't overlap
viewer.setFusionEnabled(true);         // master on/off for the fusion overlay
viewer.setFusionOpacity(0.5);          // global blend strength, 0..1
viewer.setFusionColormap('hot');       // 'hot' (default) | 'pet' | 'jet' | 'gray'
viewer.setFusionWindow(0, 0);          // PET window/level (ww<=0 ⇒ the engine's auto PET W/L)
viewer.clearFusion();                  // drop the fusion overlay
viewer.getFusion();                    // { available, enabled, opacity, colormap, petWW, petWC }
viewer.on('fusion', (s) => {});        // fires when fusion loads / toggles / colormap / opacity changes

// RTSTRUCT (RT Structure Set) — overlay an RT plan's ROI contours as coloured outlines over the image
// on EVERY MPR plane (axial / sagittal / coronal / oblique). The engine keeps the contours in patient
// coordinates and projects them onto each reformat plane (so a contour stays aligned as you scroll /
// tilt). The importer loads an RTSTRUCT from the study automatically; you can also drive it from the host.
const ok3 = await viewer.loadRtstruct(rtBytes); // Uint8Array of an RTSTRUCT .dcm; false if it isn't one / no ROIs
viewer.setRtstructEnabled(true);       // master on/off for the contour overlay
viewer.setRoiVisible(2, false);        // per-ROI show/hide (by ROINumber)
viewer.clearRtstruct();                // drop the contour overlay
viewer.getRtstruct();                  // { available, enabled, rois:[{number,name,type,r,g,b,visible}] }
viewer.on('rtstruct', (s) => {});      // fires when an RT Structure Set loads / toggles / an ROI show/hides

// Structured Report (SR) & Encapsulated PDF — a "report" object in a study is now a FIRST-CLASS,
// openable series row (an «Отчёт»/SR badge next to the image series, same cell). Open it and the main
// area renders the report instead of the image canvas:
//   • a DICOM SR (.88.*) → its ContentSequence parsed into a formatted, localized content tree
//     (sections as headings, "Concept: Value" lines, measurements as "value units");
//   • an Encapsulated PDF (.104.1) → the embedded PDF, shown inline with a download button.
// No API call needed — the importer surfaces report series automatically and the viewer renders them.

// Plain images — a standalone X-ray exported as PNG/JPEG (or GIF/BMP/WebP) can be imported directly
// (drag-drop or the file picker), not just DICOM/zip. It opens as a flat 2D RGB image series (no
// window/level, MPR or mm — it carries no DICOM geometry). DICOM is still detected by content, so a
// DICOM file with no .dcm extension is never mis-routed as a plain image.

// Events — on() returns an unsubscribe. On the LuchaiViewer the load event is 'study-loaded'
// and carries { ok, width, height, depth, modality, ww, wc, native, ... }. (The turnkey
// createLuchaiApp handle instead exposes 'studyLoaded' — see Quickstart.)
const off = viewer.on('study-loaded', (s) => { /* { modality, width, height, depth, ww, wc, native } */ });
viewer.on('error',   (e) => { /* { message } */ });
viewer.on('license', (l) => { /* { ok, reason, customer } — gate your UI when !l.ok */ });
viewer.on('viewport', (v) => { /* { layout, ww, wc } — reflect the active layout + W/L */ });
off();

viewer.destroy();                      // tear down

Навигация по MPR — как в радиологических станциях: колесо / слайдер листают срез активной панели, а перетаскивание «крестика» (мышью или пальцем) перемещает общую 3D-точку фокуса — две другие плоскости тут же переслайсываются в эту точку, и все три перекрестия остаются согласованными. Тянете за линию перекрестия — двигается только одна ось (курсор col-/row-resize); тянете за центр или пустое место — обе. Точка фокуса всегда зажата в границах тома и работает для наклонных (oblique) серий. Тянете за КОНЕЦ линии перекрестия — плоскость реформата поворачивается (наклонный oblique-срез); в настройках MPR: поворот «одна / все связанные плоскости», привязка угла (выкл / 15°), показ опорных линий и сброс к ортогональным. Многокадровое исследование (УЗИ-кино, ангио XA, ОФЭКТ NM, enhanced-multiframe — один объект с NumberOfFrames > 1) распознаётся как временной стек кадров и воспроизводится как кино: панель управления (плей/пауза, слайдер кадров, скорость в кадрах/с, цикл) появляется автоматически; цветное УЗИ-кино показывается в правильном цвете. Колесо / слайдер листают кадры, ручной скрабинг ставит на паузу. ПЭТ/КТ слияние: при открытии КТ из исследования с парной ПЭТ-серией (та же система координат, FrameOfReferenceUID) движок ко-регистрирует ПЭТ в воксельную сетку КТ (трилинейная передискретизация по координатам пациента) и накладывает её цветной палитрой поверх КТ на всех плоскостях — аксиал/сагиттал/коронал/наклон; панель слияния даёт выбор палитры (hot/pet/jet/серая), ползунок смешивания и общий выключатель.

Инструменты замеров — на верхней панели (OHIF-стиль): линейка (длина в мм через PixelSpacing; при отсутствии спейсинга — в пикселях с пометкой), угол (3 точки, в градусах), ROI прямоугольник/эллипс (площадь в мм² плюс статистика интенсивности — для КТ это HU mean/SD/min/max через RescaleSlope/Intercept, для модальностей без HU — сырая интенсивность). Активный инструмент подсвечен; измерения рисуются оверлеями с подвижными ручками, сохраняются по срезу и убираются одним действием. Те же действия доступны из Control API. Захват панели в PNG — capturePanel (2D) и snapshot3D (3D).

Платформы и форматы

  • ДоступноВебБраузер, on-device через WASM.
  • СкороWindows · macOS · LinuxДесктоп из того же ядра.
  • СкороiOS · AndroidМобильные приложения.
  • СкороAstra · РЕД · Аврора ОСНативно на российских ОС.
  • ДоступноФорматы и кодекиМодальности КТ / МРТ / ПЭТ / ОФЭКТ / УЗИ / XA / RF / CR / DX / маммо / SC / CBCT / OCT, цветные фото (офтальмология / дерматоскопия), multi-frame cine, SR-отчёты и encapsulated PDF. DICOM Part-10 (explicit / implicit VR LE), кодеки JPEG2000 / JPEG-LS / JPEG baseline / JPEG lossless / RLE. Фотометрия MONOCHROME1 (инверсия) / MONOCHROME2 / RGB / YBR / PALETTE COLOR (через LUT); знаковые пиксели и 12-бит-в-16. Имена пациентов в любой кодировке (UTF-8 ISO_IR 192, кириллица ISO_IR 100 / Windows-1251, GB18030, японская) читаются корректно.

Безопасность и госконтур

  • ДоступноOn-device обработкаСнимки пациента не покидают устройство — без выгрузки в облако.
  • ДоступноБезопасный импортПарсер DICOM и чтение архивов закалены против вредоносных файлов: битые/обрезанные файлы, гигантские поля длины, zip-бомбы (огромный коэффициент распаковки), path-traversal и симлинки в архивах, абсурдная геометрия (Rows×Cols) — отклоняются с понятной ошибкой и лимитом памяти, без зависания, переполнения буфера или падения вкладки. Импорт устойчив к частичным сбоям: битый слайс в середине серии или нечитаемый кадр пропускается (серия строится из остальных, не теряется целиком), DICOM без 128-байтной преамбулы / без «DICM» распознаётся как валидный поток, а большой том на слабом устройстве укладывается в бюджет памяти через LOD-даунсэмпл. Повторный импорт во время текущего — корректно вытесняет предыдущий без рассинхрона.
  • СкороDICOM-TLSШифрование DICOM-сети для 152-ФЗ и защищённого контура.
  • СкороOn-prem развёртываниеРабота полностью внутри контура заказчика.
  • СкороРеестр российского ПОДопуск к госзакупкам.

Изменения

История версий @luchai/viewer. Закрепите версию в package.json — она не меняется, пока вы её не обновите сами. Полные заметки и теги по версиям — на GitHub.

// @luchai/viewer — release history. Full notes + per-version tags:
//   https://github.com/luchai-med/luchai-sdk/releases   ·   CHANGELOG.md in the repo.
// Pin a version in package.json; it never moves until you bump it.

0.9.4  Default license server → api.луч-ии.рф — reachable from Russia AND abroad (the legacy
       *.workers.dev endpoint is blocked inside Russia). Integrators get a working verify endpoint
       out of the box; no licenseServer override needed.
0.9.3  Bundler fix — neutralize the core glue's new URL("…wasm") reference (the core is inlined).
0.9.2  WASM core inlined as base64 — survives webpack/Next production re-minify; nothing to host.
0.9.1  Header dropdowns render above panels (z-index); toolbar wraps on narrow viewports.
0.9.0  First public npm release. 3D DVR rework (transfer-function presets, MIP/MinIP, depth clip),
       RTSTRUCT overlay, 2D drag tools (zoom / W-L / pan), mobile layout, getViewer() Control API,
       brand back-button. Baseline: license gate, DICOMweb/PACS, PT/CT fusion, SEG overlay,
       MPR/oblique, cine, measurements, SR/PDF, React <LuchaiViewer> adapter.

Что читать дальше

Нужен ранний доступ к чему-то из «Скоро», конкретный способ встраивания или интеграция под ваш сценарий — напишите нам. justry24@gmail.com