EASI View · SDK guide

Your application. Your viewer.

Embed a complete DICOM workspace with a JavaScript SDK. Keep your brand, application shell and patient workflow while EASI View handles images, interaction and tools.

Install the published viewer SDK.

EASI View is an independent viewer built with EASI JS. Its browser ES module includes its own rendering and interaction layer; workers and bundled WebAssembly codecs handle supported pixel formats. No desktop viewer or browser plugin is required.

Terminal · Node.js 22 or 24bash
npm install @xinonix/easi-view@1.0.0-rc.1
npx easi-view-copy-assets public/easi-view

The copy command creates a new directory containing the SDK, module worker, codecs, build information and license notices. It rejects an existing destination. Serve public/easi-view at /easi-view/ through your application’s development or production HTTP server.

Published release candidate · 1.0.0-rc.1

This guide pins the current candidate. npm’s next and latest tags currently both resolve to it; neither makes it a stable release. The complete corpus campaign and review remain a stable 1.0.0 gate. The viewer uses the independently published EASI JS 1.0.1 core.

Mount a viewer. Open a generated CT or your own file.

Add this markup to your host page. The file input deliberately has no extension filter: valid DICOM files can have another extension or none.

Your host pagehtml
<link rel="stylesheet" href="/viewer-host.css">
<button id="open-demo" type="button" disabled>Open synthetic CT</button>
<label>
  Open local DICOM files
  <input id="dicom-files" type="file" multiple disabled>
</label>
<output id="viewer-status" aria-live="polite">Preparing viewer…</output>
<div id="imaging-viewer"></div>
<script type="module" src="/viewer-host.js"></script>
public/viewer-host.csscss
#imaging-viewer {
  width: 100%;
  min-width: 320px;
  height: 720px;
}

Save the following as public/viewer-host.js. The import is a served browser URL, so it works with the copied assets without configuring an npm import map.

public/viewer-host.jsJavaScript
import { createViewer } from '/easi-view/assets/easi-view.js';

const input = document.getElementById('dicom-files');
const demo = document.getElementById('open-demo');
const status = document.getElementById('viewer-status');
const viewer = createViewer(document.getElementById('imaging-viewer'), {
  branding: { name: 'Acme Imaging', showReleaseTag: false },
  theme: { accent: '#7aa9ff', panel: '#121b2b' },
  ui: { demos: false }
});

function describe(state) {
  status.textContent = state.image
    ? 'Image ' + (state.frameIndex + 1) + ' of ' + state.frameCount
    : 'Ready to open images';
}
function showError(error) {
  if (error.name !== 'AbortError') status.textContent = error.message;
}
const unsubscribe = [
  viewer.on('sourcechange', describe),
  viewer.on('framechange', describe),
  viewer.on('error', showError)
];

try {
  await viewer.ready;
  input.disabled = false;
  demo.disabled = false;
  describe(viewer.getState());
} catch (error) { showError(error); }

input.addEventListener('change', async () => {
  if (!input.files?.length) return;
  try { await viewer.openFiles(input.files); }
  catch (error) { showError(error); }
});
demo.addEventListener('click', async () => {
  try { await viewer.openDemo('ct'); }
  catch (error) { showError(error); }
});

// Use the same cleanup in your framework's component-unmount hook.
window.addEventListener('pagehide', () => {
  unsubscribe.forEach(stop => stop());
  viewer.dispose();
}, { once: true });

Start your application server and choose Open synthetic CT. You should see a generated, navigable 16-image stack and an image counter. mr, xr and us are also supported demo IDs. The ultrasound example includes 24 frames and playback.

Mounting starts empty and does not fetch a study. viewer.ready resolves when initialization completes. openFiles() reads browser-selected files locally; it does not upload them or modify the originals. Local viewing needs no imaging server. Explicit archive connections and any logo URLs you configure have their own network requests.

For React, Vue or another framework, call createViewer() after the container exists in the browser, and dispose it in the component’s unmount hook. Give each viewer its own container and mount only once per container.

Control the workspace from your application.

The following examples run in the same module after await viewer.ready. This first one presents the same generated CT in lung and soft-tissue windows, with compatible slice navigation linked.

Two panes, one stack, independent windowsJavaScript
// Run after viewer.ready. This uses only generated CT data.
await viewer.openDemo('ct');
await viewer.setLayout(2);
await viewer.assignViewportSeries(1, 0);

viewer.setActiveViewport(0);
viewer.setWindow({ center: -600, width: 1500 });
viewer.setActiveViewport(1);
viewer.setWindow({ center: 40, width: 400 });

// Same compatible stack; navigate together, keep windows independent.
await viewer.setSynchronization({ slice: true, camera: true, window: false });
await viewer.setFrame(7); // Zero-based: the eighth image.
APIUse
setLayout(1 | 2 | 4)Choose one, two or four panes. Await layout and source changes.
setActiveViewport(slot)Select the pane for subsequent controls. Pane, series and frame indices are zero-based.
setSeries(index), setFrame(index)Choose a compatible image stack or frame from the loaded source. Bounds come from getState().
setTool('window' | 'pan' | 'zoom' | 'probe' | 'distance')Select an interaction. The typed API also lists angle and ROI tools.
play(), pause(), reset()Control cine or reset presentation. Playing a CT stack scrolls spatial slices; it is not a motion acquisition.
getState(), getPerformance()Read detached operational state or bounded in-memory timings and resource counts.

Windowing requires a supported grayscale image; RGB images use their color presentation. Slice and camera links require compatible geometry. They do not register unrelated examinations. An unavailable profile or incompatible operation produces an explicit error.

Keep measurements in the same host workflow.

Users can draw distances, angles and rectangle, ellipse or polygon ROIs. Applications can also add image-coordinate annotations and read calibrated geometry or ROI statistics. Here, fixed points are used only on the generated CT:

Create and inspect a manual distance annotationJavaScript
await viewer.openDemo('ct');
const annotation = viewer.addAnnotation({
  tool: 'length',
  points: [{ x: 30, y: 50 }, { x: 90, y: 50 }]
});
const measurement = viewer.measureAnnotation(annotation.id);
console.log(measurement.geometry.value, measurement.geometry.unit);

// Source references are included: handle this as imaging metadata.
const savedMeasurements = viewer.exportAnnotations();
// Later: viewer.importAnnotations(savedMeasurements, { mode: 'merge' });

Coordinates refer to image edges, not screen pixels. Units depend on valid source calibration; inspect measurement.geometry.calibrated and unit. This API measures supplied points; it does not identify anatomy or choose measurement endpoints. Explicit annotation exports contain source references and need the same care as other imaging metadata.

getAnnotations(), removeAnnotation(), undoAnnotations() and redoAnnotations() support your own controls. See the annotation guide for exact geometry, statistics, editing and persistence rules.

Make it look and behave like your product.

The viewer mounts inside a Shadow DOM boundary. Its styles, element IDs and focused keyboard shortcuts stay within the component. Your app can own navigation and surrounding controls while the viewer owns the image workspace.

Change branding without reopening the imageJavaScript
viewer.setBranding({
  name: 'Orbit Research',
  logoUrl: '/images/orbit.svg',
  logoAlt: 'Orbit',
  showReleaseTag: false
});
viewer.setTheme({ accent: '#c2a6ff', panel: '#171427' });

Supported theme keys are accent, background, panel, text, muted and border, each using a three- or six-digit hex color. setBranding() and setTheme() replace their corresponding objects: omitted fields return to defaults. Labels are plain text.

At mount time, ui.header, ui.library, ui.inspector, ui.statusBar and ui.demos choose visible chrome. Hiding demo buttons does not disable programmatic openDemo(). Use the opening policy to enable or disable the built-in local, remote and test opening paths.

Subscribe with viewer.on(event, handler); the returned function unsubscribes. Listen to both sourcechange and framechange for an image counter, plus annotationchange, layoutchange or error as needed. Operational events omit patient identifiers and pixel arrays; explicit catalog and annotation APIs expose the metadata their task requires.

Branding does not remove the software’s redistribution and notice obligations. Commercial embedding and OEM distribution are available through Xinonix licensing.

Choose files, a patient timeline, or a configured archive.

openFiles() replaces the direct viewing source. For additive patient/study navigation, use importLocalFiles() and the session APIs, or open the built-in workflow with viewer.open('images') or viewer.open('study').

A local study folder includes DICOMDIR and its referenced subfolders. The browser must grant that complete selection; the viewer cannot discover neighboring files from DICOMDIR alone. Files stay on the device, while the session library organizes admitted patients, studies, series and instances.

Register a host-managed DICOMweb repositoryJavaScript
// The host operates a same-origin DICOMweb proxy at this URL.
viewer.registerSource({
  id: 'main-archive',
  name: 'Main imaging archive',
  type: 'dicomweb',
  baseUrl: new URL('/dicom-web/', location.origin).href,
  credentials: 'same-origin'
});

// Opens repository → patient criteria → patient → checked studies.
// Registration and opening this dialog do not themselves query the archive.
viewer.open('study');

Repository configurations can also be supplied through sources at mount time. Your application owns sign-in and authorization. Use the documented async headers callback for host-managed request headers; keep long-lived service credentials on your server. Archive CORS, cookie policy, HTTPS and host connect-src must permit the intended requests.

For a traditional PACS, register type: 'dimse' with the HTTP URL of a separately operated DIMSE gateway. The browser talks HTTP to that gateway; EASI JS runs the C-FIND/C-GET connection on the server. Gateway peer configuration and authentication belong to the deployment.

The opening guide covers named sources, patient search, checked study admission, local identity rules and session navigation. The DICOMweb guide covers retrieval and paging; the workspace guide covers comparison and links.

Keep the runtime together. Release it when you leave.

Serve the copied tree intact. assets/easi-view.js, assets/decode-worker.js and assets/dicom-codecs.wasm must keep their relative locations. Serve JavaScript as a module and WASM as application/wasm. Copying only the SDK JavaScript leaves compressed-image decoding incomplete.

For browser bundlers, the package exports createViewer and includes TypeScript declarations. Bundling does not automatically copy workers or WASM: retain the copied assets and emit your host ESM bundle beside them as described in the bundler integration instructions. The served-URL example above avoids that extra path configuration.

A strict CSP must allow the SDK’s scoped stylesheet using either a server-issued styleNonce mount option or the exact inlineStyleSha256 from the copied build-info.json. Add that digest as a 'sha256-…' source in style-src; it can change between builds. Allow your module worker in worker-src, WASM compilation with 'wasm-unsafe-eval' in script-src, and only intended archive/logo origins in connect-src/img-src.

When your host component unmountsJavaScript
unsubscribe.forEach(stop => stop());
viewer.dispose();
await viewer.whenDisposed(); // Wait for pending allocation cleanup.

Removing the container alone is insufficient. dispose() cancels work and removes the component synchronously; whenDisposed() waits for pending allocation cleanup. Repeated disposal is safe. A new viewer can then mount in the same container. Treat AbortError from superseded work as cancellation.

Use the full SDK, with its boundaries in view.

The public SDK extends beyond these first steps: typed reading layouts, bounded reconstruction, supplied overlays and DICOM derived objects, and host enterprise integration have separate guides and declared profiles.

Research release candidate, with explicit supported profiles.

Automated, browser and package-consumer checks qualify the documented behavior. The complete corpus campaign is still in progress; clinical/regulatory validation and stable 1.0.0 remain separate gates. Consult the supported-profile guide before relying on a particular object or transfer syntax.

EASI View and its EASI JS dependency retain independent source-available licenses. Preserve the copied legal files and notices, and review community eligibility and commercial embedding terms.