Cameleer

DICOM imaging with Cameleer

Cameleer routes medical images: it receives DICOM from modalities and PACS over DIMSE, filters and fixes tags, de-identifies with the PS3.15 profile, and forwards to PACS, cloud DICOMweb stores and AI models, in one small std-only binary.

Crate: cameleer-health · Pack: health (feature health, not in the default build) · Cargo feature: health

Apache Camel has no DICOM component, so the names here are Cameleer's own, chosen to read like Camel: the dicom component for DIMSE, the dicomweb component for DICOMweb, the dicom, dicomJson and dicomDeidentify data formats, and the dicom language with ${dicom.PatientID} in Simple. HL7 v2 (mllp) and FHIR (fhir) live in the same binary.

The building blocks

you want touse
receive images from a modality (be a Storage SCP)from: dicom:MYAE@0.0.0.0:11112
send images to a PACS (Storage SCU)to: dicom:PACS@host:104
store in Azure, Google Cloud, AWS or Orthanc over DICOMwebto: dicomweb:https://host/base
query and retrieve from a PACSdicom:...?operation=find, move, get
answer queries and retrieves from your own sourcea dicom: consumer route that returns matches or instances
serve DICOMwebfrom: dicomweb:/dicom-web
route on tags${dicom.Modality}, ${dicom.(0018,0015)}, ReferencedSeriesSequence[0].SeriesInstanceUID
change tagsunmarshal: dicom, edit the Value tree, marshal: dicom
de-identifymarshal: {dicomDeidentify: {uidKey: ..., options: ...}}
index metadata as JSONmarshal: dicomJson

Examples you can run

The files are in examples/medical/ and run end to end in the test suite (crates/cameleer-main/tests/dicom_examples.rs).

  • Modality to cloud (modality-to-cloud.camel.yaml): a Storage SCP for named modalities, keeps CT and MR, uploads with STOW-RS; when the cloud is down the instance is parked in a folder and a second route uploads it later, so the modality is never told "stored" for an image that was lost.
  • AI inference loop (ai-inference-loop.camel.yaml): keeps the series a model is for, de-identifies it, sends each instance to an inference service, and posts the finding as a FHIR DiagnosticReport that points at the original study. The model sees no identity; the original Study Instance UID never leaves the gateway except to the hospital's FHIR server.
  • De-identification pipeline (deid-pipeline.camel.yaml): a folder in, a folder out, with pseudonyms, shifted dates and an audit log.

De-identification you can verify

dicomDeidentify applies Table E.1-1 of PS3.15 (edition 2026d) with its options. Replacement UIDs, pseudonyms and date shifts are HMAC-SHA256 values under one secret key, so the output is deterministic: two gateways with the same key produce the same identifiers, and a study split across batches stays together. Each instance writes a record to the hash-chained audit log (digests of input and output, the profile and options, no identity).

Run the de-identifier inside a confidential VM (AMD SEV-SNP, Intel TDX) with Cameleer's attestation (cameleer-attest) to let a data owner check which binary and which policy saw the identifiable data before they send it.

Conformance summary

The full statement is the DICOM Conformance Statement (PS3.2 form); this is the short form.

areasupported
VerificationSCU and SCP
StorageSCU and SCP for every Storage SOP class of PS3.4 (the SCP accepts all by default; private SOP classes too)
Query/RetrievePatient Root and Study Root FIND, MOVE and GET as SCU and SCP; Modality Worklist FIND as SCU and SCP
Not supportedStorage Commitment, MPPS, Instance Availability, C-CANCEL while a request runs, asynchronous operations
Transfer syntaxesImplicit VR Little Endian, Explicit VR Little Endian, Deflated Explicit VR Little Endian, Explicit VR Big Endian (read, write, convert); every compressed syntax passed through unchanged (no transcoding to or from it)
Associationmaximum PDU length negotiation (default 16384), role selection, User Identity (username, username and passcode) as SCU and accepted as SCP, called and calling AE checks, extended negotiation items read and ignored
Character setsISO_IR 6, 100, 101, 109, 110, 126, 127, 138, 144, 148, 166, 13, 192, GB18030, GBK and their ISO 2022 forms with IR 87, 159, 149, 58
SecurityTLS 1.2 and 1.3 (BCP 195 profiles), mutual TLS; PS3.15 Annex E basic profile and options except the pixel options; hash-chained audit records (not yet the PS3.15 A.5 ATNA message format)
DICOMwebSTOW-RS (Part 10 parts), QIDO-RS (all levels, limit, offset, includefield), WADO-RS (instances, metadata, bulk data); not frames, rendered, thumbnail or UPS-RS

How it was verified

  • Hermetic tests (cargo test -p cameleer-health): codec round trips in every native transfer syntax, the Value tree, character sets against the byte sequences of PS3.5 Annexes H to K, DIMSE between Cameleer SCU and SCP (store, find, move, get, rejections, small PDUs, TLS), DICOMweb client against server, de-identification attribute by attribute against Table E.1-1.
  • Public test files: 33 pydicom test files (NEMA WG-04 images, character set and deflate samples, RT, SR, private sequences) read and write back byte for byte (scripts/fetch-dicom-testdata.sh).
  • Live interop (scripts/dicom-interop.sh, CAMEL_DICOM_INTEROP=1): DCMTK 3.6.7 (storescp, echoscu, storescu, findscu, getscu, movescu, with and without TLS) and Orthanc 1.12.2 (as a PACS, and its DICOMweb plugin 1.16 as both server and client).
  • Not yet: real cloud DICOM services (Azure, Google, AWS), dcm4che, real modalities, fuzzing of the Part 10 and PDU parsers, an independent security review, throughput benchmarks.

Found a problem?

If something on this page doesn't match what Cameleer does, or you hit a bug while building with it, please help fix it, whether you are a person or a coding agent: open an issue that describes what you saw, then fork the repository, fix it, add or update a test, run cargo test --workspace, and open a pull request. CONTRIBUTING.md explains the ground rules.

Edit this page on GitHub