DICOM Source

Receive DICOM images and structured data via the DICOM protocol using a C-STORE SCP (Service Class Provider).

Overview

The DICOM source implements a DICOM C-STORE SCP that receives medical images and associated metadata from modalities, PACS systems, and other DICOM-capable devices. When a DICOM object is received, its metadata is extracted and passed into the channel pipeline for transformation and routing.

DICOM (Digital Imaging and Communications in Medicine) is the universal standard for medical imaging. Radiology departments, cardiology labs, pathology scanners, and many other clinical systems produce DICOM data. This source enables intu to participate in imaging workflows by receiving studies as they are acquired or forwarded.

Note The DICOM source processes metadata (patient demographics, study details, series information) by default. Image pixel data is stored separately and a reference path is included in the message body.

Configuration

yaml
listener:
  type: dicom
  dicom:
    port: 4104
    ae_title: INTU_SCP
    calling_ae_titles:
      - CT_SCANNER_1
      - MR_SCANNER_2
      - PACS_PRIMARY
    tls:
      cert_file: /etc/intu/certs/dicom.crt
      key_file: /etc/intu/certs/dicom.key

Properties

port int required
The TCP port the DICOM SCP listens on. Common ports include 104 (requires root) and 4104.
ae_title string optional
The Application Entity (AE) title for this SCP. Used by remote systems to identify and verify the destination. Defaults to INTU_SCP.
calling_ae_titles string[] optional
Whitelist of allowed calling AE titles. Only connections from listed AE titles are accepted. When omitted, all AE titles are allowed.
tls object optional
TLS configuration for DICOM TLS (formerly known as DICOM Secure Transport). Contains cert_file and key_file.

DICOM Concepts

Term Description
AE Title Application Entity Title, a unique identifier for a DICOM node on the network.
C-STORE DICOM service for storing (sending) composite objects such as images to an SCP.
SCP Service Class Provider: the receiver/server side of a DICOM operation.
SCU Service Class User: the sender/client side of a DICOM operation.
SOP Class Specifies the type of DICOM object (e.g. CT Image, MR Image, Structured Report).

Complete Example

A DICOM listener that receives radiology images from approved scanners and routes the study metadata to a FHIR ImagingStudy destination.

yaml
id: dicom-radiology-ingest
enabled: true

listener:
  type: dicom
  dicom:
    port: 4104
    ae_title: INTU_SCP
    calling_ae_titles:
      - CT_SCANNER_1
      - MR_SCANNER_2
      - PACS_PRIMARY

validator:
  runtime: node
  entrypoint: validator.ts

transformer:
  runtime: node
  entrypoint: transformer.ts

destinations:
  - fhir-imaging-api
  - pacs-archive

TypeScript Transformer Example

This transformer extracts DICOM metadata from the received object and maps it to a FHIR ImagingStudy resource.

typescript
import { Message, TransformResult } from "@intu/sdk";

interface DicomMetadata {
  studyInstanceUid: string;
  seriesInstanceUid: string;
  sopInstanceUid: string;
  sopClassUid: string;
  modality: string;
  studyDate: string;
  studyTime: string;
  studyDescription: string;
  patientId: string;
  patientName: string;
  accessionNumber: string;
  referringPhysician: string;
  bodyPartExamined: string;
  numberOfFrames: number;
  storagePath: string;
}

export default function transform(msg: Message): TransformResult {
  let dicom: DicomMetadata;
  try {
    dicom = JSON.parse(msg.body as string);
  } catch {
    return { success: false, error: "Failed to parse DICOM metadata" };
  }

  const imagingStudy = {
    resourceType: "ImagingStudy",
    identifier: [
      {
        system: "urn:dicom:uid",
        value: `urn:oid:${dicom.studyInstanceUid}`,
      },
    ],
    status: "available",
    subject: {
      identifier: {
        system: "urn:oid:2.16.840.1.113883.19.5",
        value: dicom.patientId,
      },
      display: dicom.patientName,
    },
    started: formatDicomDateTime(dicom.studyDate, dicom.studyTime),
    description: dicom.studyDescription,
    referrer: dicom.referringPhysician
      ? { display: dicom.referringPhysician }
      : undefined,
    series: [
      {
        uid: dicom.seriesInstanceUid,
        modality: {
          system: "http://dicom.nema.org/resources/ontology/DCM",
          code: dicom.modality,
        },
        bodySite: dicom.bodyPartExamined
          ? {
              system: "http://snomed.info/sct",
              display: dicom.bodyPartExamined,
            }
          : undefined,
        instance: [
          {
            uid: dicom.sopInstanceUid,
            sopClass: {
              system: "urn:ietf:rfc:3986",
              code: `urn:oid:${dicom.sopClassUid}`,
            },
            numberOfFrames: dicom.numberOfFrames || 1,
          },
        ],
      },
    ],
    endpoint: [
      {
        reference: dicom.storagePath,
      },
    ],
  };

  return {
    success: true,
    body: JSON.stringify(imagingStudy),
    contentType: "application/fhir+json",
  };
}

function formatDicomDateTime(date: string, time: string): string {
  if (!date || date.length < 8) return new Date().toISOString();
  const d = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
  if (time && time.length >= 4) {
    const h = time.slice(0, 2);
    const m = time.slice(2, 4);
    const s = time.length >= 6 ? time.slice(4, 6) : "00";
    return `${d}T${h}:${m}:${s}Z`;
  }
  return `${d}T00:00:00Z`;
}