Cameleer

DICOMweb component

The dicomweb component speaks DICOMweb (PS3.18): as a producer it stores (STOW-RS), searches (QIDO-RS) and retrieves (WADO-RS) studies on a DICOMweb server such as a cloud DICOM store; as a consumer it serves those resources from routes on the shared HTTP server.

Scheme: dicomweb: · Crate: cameleer-health · Pack: health (feature health) · Cargo feature: health · Consumer: yes · Producer: yes · Apache Camel: no equivalent (Cameleer extension)

When to use it

  • Upload studies from a hospital gateway to Azure Health Data Services DICOM, the Google Cloud Healthcare API, AWS HealthImaging (STOW-RS import) or Orthanc.
  • Bridge DIMSE and DICOMweb: a dicom consumer receives from modalities, a dicomweb producer stores in the cloud; or a dicomweb consumer gives a DIMSE PACS a DICOMweb front end.
  • Search and pull studies for AI pipelines and research exports.

URI format

text
dicomweb:http(s)://host:port/base[?options]    (producer)
dicomweb:/base[?options]                       (consumer)

The producer's URL is the DICOMweb base (the part before /studies). The consumer serves /base/studies... on the HTTP server that platform-http uses (camel.server.port, default 8080, or port=).

Example

yaml example: dicomweb-stow
- route:
    id: dimse-to-dicomweb
    from:
      uri: "dicom:GATEWAY@0.0.0.0:{{env:DICOM_PORT:11112}}"
      steps:
        - to: "dicomweb:{{env:DICOMWEB_URL:http://localhost:8042/dicom-web}}?bearerToken={{env:DICOMWEB_TOKEN:}}"
        - log: "Stored ${header.CamelDicomCompleted} instance(s), HTTP ${header.CamelDicomHttpStatus}"

Search, then retrieve the metadata of each study found:

yaml example: dicomweb-qido
- route:
    id: find-studies
    from:
      uri: "timer:find?repeatCount=1"
      steps:
        - setBody:
            simple: '{"PatientID": "{{env:PATIENT:P1}}", "StudyDate": "20260101-", "StudyDescription": ""}'
        - unmarshal:
            json: {}
        - to: "dicomweb:{{env:DICOMWEB_URL:http://localhost:8042/dicom-web}}?operation=qido&limit=20"
        - split:
            simple: "${body}"
            steps:
              - setHeader:
                  name: CamelDicomStudyInstanceUid
                  simple: "${body[0020000D][Value][0]}"
              - to: "dicomweb:{{env:DICOMWEB_URL:http://localhost:8042/dicom-web}}?operation=metadata"
              - log: "Study ${header.CamelDicomStudyInstanceUid}: ${header.CamelDicomResultCount} instances"

A DICOMweb server backed by a route (here, forwarding to a DIMSE PACS):

yaml example: dicomweb-server
- route:
    id: dicomweb-front-end
    from:
      uri: "dicomweb:/dicom-web"
      steps:
        - choice:
            when:
              - simple: "${header.CamelDicomWebOperation} == 'stow'"
                steps:
                  - to: "dicom:PACS@{{env:PACS_HOST:pacs.local}}:104"
              - simple: "${header.CamelDicomWebOperation} == 'qido'"
                steps:
                  - to: "dicom:PACS@{{env:PACS_HOST:pacs.local}}:104?operation=find"
            otherwise:
              steps:
                - to: "dicom:PACS@{{env:PACS_HOST:pacs.local}}:104?operation=get"

Rust DSL

rust
use cameleer_core::prelude::*;

pub fn routes(ctx: &CamelContext) -> Result<()> {
    ctx.add_pack(&cameleer_health::HEALTH);
    ctx.add_routes(|b| {
        b.from("dicom:GATEWAY@0.0.0.0:11112")
            .to("dicomweb:https://dicom.example.org/v2?bearerToken={{dicomweb.token}}");
    })
}

Producer operations

operationrequestbody inbody out
stow (default)POST /studies (or /studies/{CamelDicomStudyInstanceUid}), multipart/related; type="application/dicom"Part 10 bytes, a Value tree, or an array of themthe response (a Value tree of the DICOM JSON answer)
qidoGET /studies, /series or /instances (queryLevel), within a study or series when the UID headers are setthe query keys: a map of keywords or tags, or a Value tree (empty values become includefield)an array of Value trees
wadoGET /studies/{s}[/series/{se}[/instances/{i}]]ignored (UIDs from headers or the body)an array of Part 10 bytes
metadatathe same path plus /metadataignoredan array of Value trees

HTTP errors raise DicomWebException with the status and the start of the response body. A STOW-RS answer that lists failed instances (HTTP 202) raises it too, naming each failed instance and its Failure Reason, unless failOnPartialSuccess=false.

Consumer resources

requestthe exchange (CamelDicomWebOperation)the route answers
POST /studies[/{s}]stow, once per instance, body: Part 10 bytesnothing; a failed exchange marks the instance failed (Failure Reason A700, or the route's CamelDicomStatus)
GET /studies, /series, /instances, /studies/{s}/series, /studies/{s}/instances, /studies/{s}/series/{se}/instancesqido, body: the identifier as a Value tree, CamelDicomQueryLevel, CamelDicomQuery (the raw parameters)an array of Value trees (limit and offset are applied to it)
GET /studies/{s}[/series/{se}[/instances/{i}]]wado, body: the identifier with the UIDsan array of Part 10 bytes, sent as multipart/related
.../metadatametadatathe same; answered as DICOM JSON with bulk data over bulkDataThreshold as BulkDataURI
.../instances/{i}/bulkdata/{tag}wadothe same; the attribute's bytes are answered

STOW-RS answers 200 when every instance was stored, 202 when some failed and 409 when all failed, with ReferencedSOPSequence (and a Retrieve URL per instance) and FailedSOPSequence. An instance posted to /studies/{s} that belongs to another study fails with reason A900. WADO-RS honours transfer-syntax= in the Accept header by converting between native syntaxes. frames, rendered and thumbnail answer 501: they need pixel codecs.

Options

optiondefaultdescription
operationstowProducer: stow, qido, wado, metadata. Header CamelDicomWebOperation overrides it.
queryLevelstudiesProducer, qido: studies, series or instances.
limit, offset, fuzzymatching, includefieldProducer, qido: QIDO-RS parameters (includefield takes a comma list).
transferSyntax*Producer, wado: the transfer-syntax asked for in the Accept header (* = as stored).
bearerTokenProducer: Authorization: Bearer ... (use a property placeholder or {{vault:...}}).
username, passwordProducer: Basic authentication.
failOnPartialSuccesstrueProducer, stow: fail when the server reports failed instances.
timeout60sProducer: request timeout.
sslContextParametersProducer: TLS for https (default: the system trust store). Consumer: serve HTTPS.
port, hostcamel.server.port / 8080, 0.0.0.0Consumer: the HTTP server.
sslfalseConsumer: serve HTTPS with the camel.server.ssl.* settings.
bulkDataThreshold1024Consumer: bulk values larger than this go out as BulkDataURI in metadata and search results.

Message headers

headerdirectiondescription
CamelDicomWebOperationout (consumer), in (producer)stow, qido, wado, metadata.
CamelDicomCommandout (consumer)STOW-RS, QIDO-RS, WADO-RS.
CamelDicomStudyInstanceUid, CamelDicomSeriesInstanceUid, CamelDicomSopInstanceUidin (producer), out (consumer)UIDs of the request.
CamelDicomSopClassUidout (consumer, stow)Of the stored instance.
CamelDicomQueryLevel, CamelDicomQueryout (consumer) / in (producer, CamelDicomQuery adds parameters)Search level and parameters.
CamelDicomWebAuthorizationin (producer)An Authorization value for this request.
CamelDicomHttpStatusout (producer)HTTP status of the response.
CamelDicomCompleted, CamelDicomFailedout (producer, stow)Stored and failed instances.
CamelDicomResultCountout (producer)Matches or retrieved instances.
CamelDicomStatusin (consumer, stow)A failure status to report for the instance.

Run it with Docker

sh
docker run --rm -p 11112:11112 -e DICOMWEB_URL -e DICOMWEB_TOKEN -v "$PWD:/routes:ro" pmsudhi/cameleer run /routes/gateway.camel.yaml

Common questions

Which servers has this been tested with?

Orthanc 1.12.2 with its DICOMweb plugin 1.16, in both directions: the dicomweb producer against Orthanc (STOW, QIDO, WADO, metadata), and Orthanc's DICOMweb client against a dicomweb consumer (STOW, QIDO, WADO). Cloud services have not been tested with real accounts yet.

How do I authenticate to Azure, Google Cloud or AWS?

Azure Health Data Services and the Google Cloud Healthcare API take OAuth 2.0 bearer tokens: get one with the cloud's CLI or identity service and pass it as bearerToken (or per message in CamelDicomWebAuthorization). AWS HealthImaging needs SigV4 request signing, which this component does not do yet.

Is JSON metadata with separate bulk data accepted by STOW-RS?

Not by the consumer: it takes application/dicom parts (415 otherwise). The producer always sends Part 10 instances.

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