Thanks to visit codestin.com
Credit goes to github.com

Skip to content

enricogasparini/dicom-qr-gateway

Repository files navigation

DICOM QR Gateway

CI License: MIT Python

A small, opinionated REST gateway for DICOM Query/Retrieve (C-FIND, C-MOVE) operations against PACS systems. Built with FastAPI and pynetdicom.

The gateway translates HTTP+JSON requests into DICOM network operations, so applications can integrate with PACS systems without needing to embed a DICOM toolkit themselves.

Features

  • POST /q — Query (C-FIND) studies for a patient, with optional modality / age / result-count filtering.
  • POST /qr — Query then retrieve matching studies via C-MOVE to a destination AE.
  • GET /health, GET /version — Liveness + version endpoints.
  • Auto-generated OpenAPI docs at /docs (Swagger UI) and /redoc.
  • Structured JSON errors with correlation IDs; exception details are never leaked to clients.
  • Pydantic request validation; sensible DICOM-aware rules (AE title charset, IP, port range, modality codes).
  • Single-association, multi-modality C-FIND (one round-trip per query).
  • Native pynetdicom timeouts (ACSE / DIMSE / network) — no thread- based cancellation hacks.
  • Runs as a non-root user inside a slim Docker image, with a built-in healthcheck.

Quick start

Run locally

pip install -e ".[dev]"
make run
# → http://localhost:6000/docs

Run with Docker

docker build -t dicom-qr-gateway:local .
docker run --rm -p 8888:6000 dicom-qr-gateway:local
# → http://localhost:8888/docs

See SECURITY.md for a reverse-proxy template (nginx, traefik, …) to place in front of the gateway in production.

API

POST /q — query only

Request body (JSON):

Field Type Required Description
pacs_ip string yes IPv4/IPv6 address or hostname of the PACS.
pacs_port int (1-65535) yes TCP port.
pacs_ae_title string yes AE title of the PACS (1-16 chars).
c_find_ae_title string yes AE title used as the C-FIND SCU.
patient_id string (1-64) yes Patient ID (no *, ?, \).
modalities string[] no Restrict by modality (e.g. ["CT","MR"]).
max_years_old int (1-200) no Drop studies older than N years.
max_results int (1-1000) no Return at most N most-recent studies.

Example:

curl -sS http://localhost:6000/q \
  -H 'content-type: application/json' \
  -d '{
    "pacs_ip": "127.0.0.1",
    "pacs_port": 4242,
    "pacs_ae_title": "ORTHANCA",
    "c_find_ae_title": "SCU",
    "patient_id": "1112223333",
    "modalities": ["CT","MR"],
    "max_years_old": 5,
    "max_results": 10
  }'

Response (200):

{
  "operation": "C-FIND",
  "status": "ok",
  "patient_id": "1112223333",
  "study_details": [
    {
      "PatientName": "DOE^JOHN",
      "PatientID": "1112223333",
      "DateOfBirth": "19700101",
      "StudyInstanceUID": "1.2.3.4",
      "StudyDescription": "CT ABDOMEN",
      "ModalitiesInStudy": ["CT"],
      "StudyDate": "20240101",
      "StudyTime": "120000",
      "AccessionNumber": "ACC-1",
      "ReferringPhysicianName": "REF^MD",
      "PerformingPhysicianName": "PERF^MD",
      "InstitutionName": "Hospital"
    }
  ]
}

POST /qr — query and retrieve

Same fields as /q, plus one additional required field:

Field Type Required Description
c_move_ae_title string yes AE title of the C-MOVE destination (1-16 chars).

Response on full success (200) or partial success (207):

{
  "operation": "C-MOVE",
  "status": "ok",
  "message": "Operation completed successfully",
  "success_count": 2,
  "total_count": 2,
  "patient_id": "1112223333",
  "study_details": [ /* ... */ ]
}

Status codes

Code Meaning
200 Success.
207 C-MOVE partially successful (status: "partial").
400 Invalid request payload.
404 No studies matched the query.
500 Unexpected server error.
502 DICOM operation failed (e.g. association rejected, network error).

Error responses (400/500/502) include a correlation_id in the body and an X-Correlation-ID response header so clients can cross-reference issues with server-side logs. The 404 response does not include correlation_id but the header is still present.

Configuration

All settings are environment variables:

Variable Default Description
DICOM_DEBUG false Verbose logging (sets app + pynetdicom to DEBUG).
LOG_TO_CONSOLE true Attach a stdout log handler.
ACSE_TIMEOUT 10 Association negotiation timeout (s).
DIMSE_TIMEOUT 10 DIMSE message timeout (s).
NETWORK_TIMEOUT 10 Low-level network timeout (s).
CONNECTION_TIMEOUT 10 TCP connection timeout (s).

Development

make dev      # install with dev extras
make lint     # ruff check + ruff format --check + mypy
make format   # ruff format + ruff --fix
make test     # pytest
make cov      # pytest with HTML coverage report

Coverage target: ≥ 90 % (enforced by CI).

Project layout

.
├── app.py                      # FastAPI app factory
├── config.py                   # Env-driven config
├── __version__.py
├── api/
│   ├── schemas.py              # Pydantic request/response models
│   └── endpoints.py            # FastAPI routers
├── services/
│   ├── dicom_client.py         # pynetdicom wrapper (C-FIND, C-MOVE)
│   ├── qr_service.py           # Orchestration + filtering
│   └── study_mapper.py         # Dataset → DTO mapping
├── tests/                      # pytest suite
├── samples/pacs/               # Two-Orthanc test harness (see its README)
└── Dockerfile

Security notes

  • The gateway does not authenticate HTTP clients by default. Deploy behind a reverse proxy (nginx, traefik) or API gateway that enforces authN/authZ appropriate to your environment.
  • PACS credentials (AE titles, IPs, ports) are supplied per request; the gateway stores no state.
  • Exception text is never returned to clients; use the correlation_id to look up server-side logs.
  • Run the container as the provided app user (default). See SECURITY.md.

Releasing

# bump version + tag + push
echo '__version__ = "X.Y.Z"' > __version__.py
git commit -am "chore: release vX.Y.Z"
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push && git push --tags

CI runs on every tag push.

License

MIT

About

FastAPI gateway that exposes DICOM C-FIND (query) and C-MOVE (retrieve) operations as JSON/HTTP endpoints, built with pynetdicom.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors