04 · Node.js DIMSE

Bring a DICOM archive into your JavaScript workflow.

Verify a peer, query studies, and retrieve instances through Node.js DIMSE transports. Give network configuration and completion status the same care as your data mapping.

DIMSE starts with a configured DICOM peer.

Traditional DICOM archives communicate using DIMSE operations over DICOM associations. EASI JS adds Node.js transports for Verification, Store, and a bounded Query/Retrieve profile. The core parser and output targets still do the application-facing work.

These examples require a reachable archive, such as an Orthanc instance you control. They are separate from the offline tutorials. Before running them:

  • Confirm the peer’s DICOM listener address and port. This is its DICOM port, rather than its HTTP or DICOMweb port.
  • Set calledAeTitle to the peer’s AE title, and callingAeTitle to your application’s AE title.
  • Configure the peer to allow your application’s AE and host where its policy requires that.
  • Use a test archive with synthetic data, and replace sample queries and identifiers with values at that peer.
The DIMSE package entry point requires Node.js.

Import transports from @xinonix/easi-js/dimse/node. A browser interface needs a Node backend for these connections. Browser-compatible DICOM processing remains available through the core package.

Start with C-ECHO.

Verification checks whether a DICOM association and echo operation can complete with your configured peer. Replace the example address, port, and AE titles before running it.

verify-peer.mjs · requires a configured DICOM peerJavaScript
import {
  DimseClientBuilder,
  NodeDimseQueryRetrieveSourceTransport
} from '@xinonix/easi-js/dimse/node';

const association = {
  host: '127.0.0.1',
  port: 4242,
  calledAeTitle: 'ORTHANC',
  callingAeTitle: 'EASI_JS',
  associationTimeoutMs: 30000
};

const client = new DimseClientBuilder()
  .withAssociation(association)
  .withTransport(new NodeDimseQueryRetrieveSourceTransport())
  .build();

const verification = await client.echo({ operationTimeoutMs: 10000 });
console.log(verification.ok, verification.status); // Success: true, 0

A successful response has ok: true and status 0. If this fails, check the listener, routing, firewall, and AE policy before moving to Query/Retrieve. A successful echo does not establish that the peer supports every other operation.

Query once, then choose the result shape.

C-FIND returns matching identifiers. The pipeline below asks for study-level metadata and maps each returned study into a FHIR summary. Its default subject mode uses a contained Patient; use configured references when integrating with your own patient identity system.

find-studies.mjs · requires a configured DICOM peerJavaScript
import EASI from '@xinonix/easi-js';
import {
  NodeDimseQueryRetrieveSourceTransport
} from '@xinonix/easi-js/dimse/node';

const association = {
  host: '127.0.0.1',
  port: 4242,
  calledAeTitle: 'ORTHANC',
  callingAeTitle: 'EASI_JS',
  associationTimeoutMs: 30000
};

const pipeline = EASI.pipelineBuilder()
  .fromDimseAssociation(association, new NodeDimseQueryRetrieveSourceTransport())
  .ofDicomData()
  .toFHIRImagingStudy('study-summary')
  .build();

const studies = await pipeline.process({
  sourceOptions: {
    operation: 'c-find',
    queryRetrieveModel: 'study-root',
    queryRetrieveLevel: 'STUDY',
    keys: { PatientID: 'RESEARCH-*' },
    returnKeys: [
      'StudyInstanceUID', 'PatientID', 'PatientName', 'StudyDate',
      'ModalitiesInStudy', 'NumberOfStudyRelatedSeries',
      'NumberOfStudyRelatedInstances'
    ],
    operationTimeoutMs: 30000
  }
});

console.log(studies.toArray().map(study => study.toJSON()));
console.log(pipeline.reader.lastMetadata.dimse.finalResponse);

keys constrain the query. returnKeys request fields your output needs. A peer may omit unavailable optional attributes or reject unsupported keys. Use toInstances() if you want identifier datasets rather than FHIR summaries.

A successful query with no matches returns an empty result collection. The transport’s finalResponse preserves status and counters separately from the mapped output.

Choose how instances return to your application.

OperationWhere incoming Store objects arriveWhat to configure
C-GETOn the same associationSupported storage SOP classes and transfer syntaxes
C-MOVEOn a separate Store listenerA destination AE route at the archive, plus a reachable local listener

This GET recipe targets a single identified instance. Replace all three UIDs with an existing study, series, and instance at your peer. performFind: false skips the preliminary FIND because the identifiers are already known.

retrieve-instance.mjs · replace UIDs with identifiers from your peerJavaScript
import EASI from '@xinonix/easi-js';
import {
  NodeDimseQueryRetrieveSourceTransport
} from '@xinonix/easi-js/dimse/node';

const association = {
  host: '127.0.0.1',
  port: 4242,
  calledAeTitle: 'ORTHANC',
  callingAeTitle: 'EASI_JS',
  associationTimeoutMs: 30000
};

const pipeline = EASI.pipelineBuilder()
  .fromDimseAssociation(association, new NodeDimseQueryRetrieveSourceTransport())
  .ofDicomData()
  .toInstances()
  .build();

try {
  const instances = await pipeline.process({
    sourceOptions: {
      operation: 'c-get',
      performFind: false,
      queryRetrieveModel: 'study-root',
      queryRetrieveLevel: 'IMAGE',
      keys: {
        StudyInstanceUID: '2.25.123',
        SeriesInstanceUID: '2.25.123.1',
        SOPInstanceUID: '2.25.123.1.1'
      },
      operationTimeoutMs: 60000
    }
  });
  console.log(instances.count, pipeline.reader.lastMetadata.dimse.finalResponse);
} catch (error) {
  console.error(error.message, error.dimse);
  process.exitCode = 1;
}

To use MOVE, the archive must already map moveDestinationAeTitle to your listener’s reachable address and fixed moveStorePort. The local moveStoreHost is a bind address; configuring it does not register a route at the archive. The complete retrieval guide shows that configuration.

For outgoing C-STORE, provide a complete Part 10 object with file metadata. The small raw dataset from our writing tutorial is not that input. The transport preserves bytes and does not transcode them to a peer-selected transfer syntax.

Inspect completion and bound the work.

Warning statuses can retain partial results. Failure and cancellation reject with peer information in error.dimse. Inspect the final status, completed/failed/warning counters, and failed instance UIDs before treating a retrieval as complete.

associationTimeoutMs is a socket idle timeout. operationTimeoutMs is an optional absolute operation deadline. Use a deadline for peers that could keep sending pending responses; a local abort closes the association rather than sending a graceful C-CANCEL exchange.

The Node transports buffer complete datasets and retrieved output before the pipeline consumes them. Memory includes retained datasets, Part 10 wrappers, and multipart copies. Query a smaller series or instance and lower the transport limits to match your deployment.

Limitv1 default
maxIncomingPduLength16 MiB
maxCommandBytes1 MiB
maxDataSetBytes512 MiB per dataset
maxTotalDataSetBytes512 MiB retained datasets per operation

These dataset limits are not total process-memory limits. Transport acceptance also does not establish pixel-decoder support.

The independent Orthanc validation matrix covers Echo, Store send/receive, and Study Root FIND/GET/MOVE with synthetic inputs. Query/Retrieve SCP services, worklist, normalized services, and asynchronous multiplexing are outside that matrix. Review the v1 profile against your peer and deployment requirements.

Keep going

Read the complete DIMSE v1 profile

Explore Store send/receive, MOVE routing, transport options, and the independent-peer validation matrix.