|
Documentation: Architecture

Get started

Architecture

Five logical planes on one Docker network, with a 24-hour DICOM cache and no long-term imaging storage.

PACS-AI is a containerized DICOM ingestion and inference platform deployed on-premises at the customer site. It connects to the hospital PACS, automatically retrieves studies of configured modalities (echo, XA, CT), runs them through registered AI inference models, and exposes structured results, together with the original imaging, through a multi-tenant REST API and the embedded viewer. Authentication is delegated to a customer-owned Firebase project. All DICOM data lives in a 24-hour rolling cache; PACS-AI itself keeps no long-term imaging storage.

The five planes

  1. Edge: nginx, TLS termination and reverse proxy (ports 80 and 443). Port 80 always redirects to 443. In production nginx serves the viewer at /, proxies /api/* to api-pacs, and exposes Orthanc only through the authenticated DICOMweb proxy at /api/proxy/orthanc/dicom-web/ (via api-pacs); a direct /orthanc/ route exists only in the development config.
  2. Control plane: api-pacs, a Go REST API (Chi, domain-driven layout) on port 8000 plus three background workers.
  3. DICOM plane: Orthanc DICOM store (REST on 8042, DIMSE on the configured port) with its own Postgres index.
  4. Execution plane: cardio-agent/study-service, FastAPI on port 8600, a Celery worker and the per-model inference containers.
  5. Data stores: PostgreSQL 5433 (ingestion control plane), PostgreSQL 5434 (cardio-agent jobs and results), Redis 6379 (Celery broker, IAM cache, rate-limit counters), Elasticsearch 9200 with Kibana 5601 (study index and audit events), and Firebase Auth + Firestore (tenants, users, invites, metadata).
Service Folder Role Port
api-pacs api-pacs/ Go REST API and workers (entry point cmd/main.go, workers in interfaces/cron.go) 8000
pacs-ai (viewer) ../PACS-AI/ (docker-compose.yml) The OHIF-based web viewer, built from the PACS-AI Dockerfile and served behind nginx 80 (exposed on pacs-net only)
orthanc orthanc/ DICOM server: C-STORE, C-MOVE, REST 8042 REST, ORTHANC_DICOM_PORT DIMSE
orthanc-pacs orthanc-pacs/ Simulated remote PACS, development only, opt-in (commented out in the root compose) compose only
study-service cardio-agent/study-service/ FastAPI + Celery worker, model execution 8600
cardio-agent backend cardio-agent/backend/ LangGraph agent framework (interactive) internal
nginx nginx/ Reverse proxy and TLS termination 80, 443
postgresql postgresql/ Control-plane database 5433 on host
cardio postgres root docker-compose.yml cardio-agent database 5434 on host
redis redis/ Celery broker, cache, counters 6379
elasticsearch, kibana elasticsearch/ Study and audit-log indexing; Kibana UI 9200; 5601 on host
orchestrator orchestrator/ FastAPI + LangGraph LLM orchestrator (optional, standby; not in the compose include: list) 8585
ollama ollama/ Local LLM runtime (optional, GPU; not in the compose include: list) 11434
model containers model-template/, model-examples/ One FastAPI inference container per model 80 per container

The root docker-compose.yml wires the services together with include: (orthanc, redis, elasticsearch, postgresql, api-pacs, ../PACS-AI, nginx, cardio-agent/backend and cardio-agent/study-service) and defines the cardio-agent Postgres inline. Every container joins the external Docker network pacs-net, which you create once before the first start. When services are split across hosts, add internal DNS or /etc/hosts entries for service-to-service names.

api-pacs internal layout

Each module under api-pacs/module/ follows domain / application / infrastructure / interface layers: inference (jobs, candidate discovery, retrieval, dispatch), orthanc (HTTP and DIMSE client for C-FIND and C-MOVE), elasticsearch (indexing and search), iam (Firebase auth), tenant and user (multi-tenancy), lead (the contact-form and newsletter endpoints used by this website) and orchestrator (thin shim to the optional orchestrator).

  • Ingestion runner: C-FINDs the remote PACS, records candidates and computes stability.
  • Retrieval worker: C-MOVEs stable candidates into local Orthanc, then POST /ingest/study to study-service.
  • Reconciliation worker: runs once at startup and then on its interval, polls a bounded batch of active runs (never terminal history) and repairs stale or inconsistent state.

Data flow

  1. Discovery: the ingestion runner queries the remote PACS, records candidates as DISCOVERED, and marks them STABLE once series and instance counts settle.
  2. Retrieval: the retrieval worker C-MOVEs STABLE candidates into local Orthanc and hands the study to study-service.
  3. Processing: study-service creates a pipeline_jobs row and enqueues a Celery task; the worker fetches frames from Orthanc, calls the model container, and stores results in pipeline_results.
  4. Reconciliation: the reconciliation worker syncs status against study-service for stale jobs.
  5. Cleanup: the retention cleanup worker (phase 4 of the ingestion plan) is not yet shipped; Orthanc cache expiry handles DICOM deletion today.

Where state lives

Store What it holds
PostgreSQL 5433 Ingestion control plane only: ingestion_jobs, ingestion_candidates, ingestion_processing_jobs and ingestion_processing_runs (migrations 000001 to 000007). No tenant or user data.
PostgreSQL 5434 cardio-agent execution state: pipeline_jobs, pipeline_results.
Firebase Auth + Firestore All IAM data: tenants, users, user_metadata, email invites, onboarding-questionnaire answers, versioned policy acceptance. Roles are a value on each user document, not a separate collection.
Orthanc volume DICOM instances, 24-hour rolling cache (ORTHANC_LOCAL_CACHE_EXPIRATION_IN_HOURS).
Elasticsearch Study index and audit events; no default index-lifecycle policy ships, the customer defines retention.
PACS AI Logo status Status Terms and Conditions Privacy Policy Privacy Impact Assessment Document (EFVP)

© 2026 HeartWise AI Lab, Montreal Heart Institute. All rights reserved.