Подключение Луч ИИ
Один движок — много способов подключения, платформ и интеграций. Готовое отмечено «Доступно», запланированное — «Скоро» (код приведён предварительно, до релиза может меняться).
Возможности
Установка
- Доступно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 neededcreateLuchaiApp возвращает 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