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.
Configuration
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
104 (requires root) and 4104.INTU_SCP.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.
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.
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`;
}