02 · Pipeline concepts

One input. The output your application needs.

A DICOM workflow can produce an instance, a small attribute set, a mapped resource, or serialized bytes. Choose deliberately.

Separate how data arrives from what you produce.

A pipeline has three main decisions: its source, its input format, and its output product. That separation lets you reuse a selection or mapping as you move from synthetic metadata to files and network responses.

The execution modeltext
source → input format → output product → optional destination → build → process
If your application needs…Choose…The returned product
A DICOM object modeltoInstances()An Instance with a dataSet
A few chosen attributestoSelection(selection)An AttributeSet
FHIR imaging metadatatoFHIRImagingStudy(options)An ImagingStudy model
Native DICOM bytestoDicomData(options)A Uint8Array, or a writer receipt with a destination

The input format follows the actual bytes: ofDicomData() for native DICOM, ofDicomMetadata() for DICOM JSON, and ofDicomXmlMetadata() for Native DICOM Model XML.

Keep only the attributes you need.

An ingestion index might need study, series, and instance UIDs without a complete instance object. Build a selection, then use it as the pipeline’s target. This standalone example uses the same synthetic metadata as the first guide.

select.mjsJavaScript
import EASI, { Tag } from '@xinonix/easi-js';

const metadata = {
  '00080016': { vr: 'UI', Value: ['1.2.840.10008.5.1.4.1.1.7'] },
  '00080018': { vr: 'UI', Value: ['2.25.123.1.1'] },
  '00080050': { vr: 'SH', Value: ['SYNTHETIC-ACCESSION'] },
  '00080060': { vr: 'CS', Value: ['OT'] },
  '00100020': { vr: 'LO', Value: ['SYNTHETIC-PATIENT'] },
  '0020000D': { vr: 'UI', Value: ['2.25.123'] },
  '0020000E': { vr: 'UI', Value: ['2.25.123.1'] }
};
const source = new TextEncoder().encode(JSON.stringify(metadata));

const selection = EASI.selectionBuilder()
  .include(Tag.StudyInstanceUID)
  .include('SeriesInstanceUID')
  .include('(0008,0018)')
  .build();

const result = await EASI.pipelineBuilder()
  .fromByteStream()
  .ofDicomMetadata()
  .toSelection(selection)
  .build()
  .process({ source });

console.log(JSON.stringify(result.toArray().map(attributes => ({
  studyUid: attributes.value(Tag.StudyInstanceUID),
  seriesUid: attributes.value(Tag.SeriesInstanceUID),
  instanceUid: attributes.value(Tag.SOPInstanceUID)
})), null, 2));

Save it as select.mjs and run node select.mjs. The result contains one attribute set with the three requested UIDs.

Access the selected set directly.

A selection returns an AttributeSet, so use attributes.value(Tag.StudyInstanceUID). It has no dataSet wrapper. Missing selected attributes remain absent; selection does not invent values.

include() accepts a Tag object, a recognized keyword, an eight-digit hexadecimal tag, or a parenthesized tag. These identify DICOM attributes, rather than arbitrary nested JavaScript property paths.

Serialize, then read the bytes back.

Changing the target to toDicomData() produces bytes. The example below writes our metadata as a native DICOM dataset and reads that dataset back. Both sides use ordinary in-memory bytes.

roundtrip.mjsJavaScript
import EASI, { Tag } from '@xinonix/easi-js';

const metadata = {
  '00080016': { vr: 'UI', Value: ['1.2.840.10008.5.1.4.1.1.7'] },
  '00080018': { vr: 'UI', Value: ['2.25.123.1.1'] },
  '00080050': { vr: 'SH', Value: ['SYNTHETIC-ACCESSION'] },
  '00080060': { vr: 'CS', Value: ['OT'] },
  '00100020': { vr: 'LO', Value: ['SYNTHETIC-PATIENT'] },
  '0020000D': { vr: 'UI', Value: ['2.25.123'] },
  '0020000E': { vr: 'UI', Value: ['2.25.123.1'] }
};
const source = new TextEncoder().encode(JSON.stringify(metadata));

const written = await EASI.pipelineBuilder()
  .fromByteStream()
  .ofDicomMetadata()
  .toDicomData()
  .build()
  .process({ source });
const bytes = written.first();

const readBack = await EASI.pipelineBuilder()
  .fromByteStream()
  .ofDicomData()
  .toInstances()
  .build()
  .process({ source: bytes });

console.log(JSON.stringify({
  writtenBytes: bytes.byteLength,
  instances: readBack.count,
  studyUid: readBack.first().dataSet.value(Tag.StudyInstanceUID)
}, null, 2));

Save it as roundtrip.mjs and run node roundtrip.mjs. The output reports a positive byte count, one instance, and the same study UID.

This small dataset is for learning. It has no pixel data and is not a complete clinical image or Part 10 file. The writer preserves available file headers; it does not create missing file metadata or make an arbitrary dataset into a complete storage object.

A destination such as intoFileStream(path) sends serialized output to a file and returns a write receipt. For incremental output, toDicomData({ collectOutput: false, onChunk }) lets an async chunk callback consume bytes with backpressure. The writer guide includes a complete file-writing recipe.

Streaming input is one part of a memory plan.

DICOM instances can contain large pixel values. A stream reader avoids loading the entire source file first, while the chosen output determines what the application retains. An instance target retains attributes, a collected byte target retains serialized output, and FHIR mapping retains the resource hierarchy it builds.

The native parser’s default bulk policy is auto. It can forward large bulk values in chunks. An instance handler does not collect those streamed chunks, so finding a PixelData attribute does not guarantee PixelData.access() contains its bytes.

Use a materialize policy only for bounded inputs whose bulk bytes you need to retain. Its hardSafetyCap applies to known-length nonstructural values; it is not an overall application memory limit. Plan bulk retention, output collection, and network retrieval limits together.

Built pipelines reset per-call results and serialize repeated process() calls. Build separate pipeline instances for independent inputs that must run concurrently. Input streams are consumed: create a fresh stream for another run.

When your output needs a domain-specific object, custom mappings extend the same parser and handler lifecycle.

Keep going

Turn imaging metadata into FHIR

Keep the source and parser, then choose a target that creates an ImagingStudy resource.