Understanding DICOM, OHIF Viewer, and PACS

Muhammad HassanMuhammad Hassan
11 min read

1. Introduction

In medical imaging, the seamless storage, management, and viewing of images are essential for accurate diagnosis and effective patient care. This document explains the roles of DICOM, OHIF Viewer, and PACS in this process and details how these components work together.

2. What is DICOM?

Definition

  • DICOM (Digital Imaging and Communications in Medicine) is the global standard for managing medical imaging information. It ensures that medical images and associated data, such as patient information, are stored, transmitted, and accessed in a consistent manner across different systems and devices.

Key Features

  • File Format: DICOM files encapsulate both the image data (e.g., CT scans, MRIs) and metadata (e.g., patient details, imaging parameters).

  • Communication Protocol: DICOM also defines the protocols for transferring images across networks, allowing various medical systems to exchange and interpret the data correctly.

3. What is OHIF Viewer?

Definition

  • OHIF (Open Health Imaging Foundation) Viewer is an open-source, web-based application designed to view and interact with DICOM images. It provides a user-friendly interface for healthcare professionals to analyze medical images directly from their web browsers.

Key Features

  • Web-Based Accessibility: OHIF Viewer can be accessed via any modern web browser, making it a flexible solution for both on-site and remote image viewing.

  • DICOM Compatibility: The viewer is tailored to work with DICOM files, enabling users to view, annotate, and manipulate medical images in a standard format.

  • Integration with PACS: OHIF Viewer can be connected to a PACS server, allowing it to fetch, display, and interact with images stored on the server.

4. What is PACS?

Definition

  • PACS (Picture Archiving and Communication System) is a medical imaging technology used for the storage, retrieval, management, and distribution of DICOM images. PACS serves as the central repository in a healthcare environment, where all medical images are archived.

Key Components

  • Image Storage: PACS stores large volumes of DICOM images and associated metadata.

  • Image Retrieval: Users can search, retrieve, and view images stored within PACS using various interfaces.

  • Communication with OHIF: PACS communicates with viewers like OHIF through DICOMweb protocols, allowing seamless access to stored images.

Common PACS Servers

  • Orthanc: A lightweight, open-source PACS server that is easy to set up and integrates well with OHIF Viewer.

  • DCM4CHEE: An open-source PACS server widely used in large-scale environments, known for its robust features and scalability.

  • Conquest: A free, open-source DICOM server that is simpler but effective for smaller installations.

5. Connecting OHIF Viewer to a PACS Server

Implementation 1: Using OHIF and Orthanc by MAPDR (work fine but this is a modified image by someone but works fine when you upload data tin orthanc it shows in ohif )

Overview: This implementation demonstrates how to set up an OHIF Viewer connected to an Orthanc PACS server using Docker and Docker Compose. The steps are based on a tutorial by MAPDR, which can be found on YouTube and GitHub.

Prerequisites:

  • Ubuntu 20.04 Server (or similar Linux distribution)

  • Docker and Docker Compose installed

Steps:

  1. Clone the GitHub Repository:

    • Clone the repository that contains the necessary configurations and files for OHIF and Orthanc.
    git clone https://github.com/hyper4saken/ohif-orthanc.git
  • Change into the directory containing the project files.
    cd ohif-orthanc
  1. Review the Folder Structure:

    • The cloned folder contains:

      • A Python DICOM importer script.

      • A docker-compose.yml file to deploy OHIF and Orthanc.

      • Configuration files for Nginx, OHIF, and Orthanc.

  2. Docker Compose Setup:

    • The docker-compose.yml file defines two services:

      • Orthanc (PACS Server): Uses the jodogne/orthanc-plugins Docker image. It exposes ports 8042 for web access and 4242 for DICOM communication.

      • OHIF Viewer: Uses the ohif/viewer Docker image. It exposes port 3000 for web access.

    version: "3"
    services:
      pacs:
        container_name: orthanc
        image: jodogne/orthanc-plugins
        ports:
          - 8042:8042
          - 4242:4242
        volumes:
          - ./orthanc.json:/etc/orthanc/orthanc.json:ro
          - ./orthanc_db:/var/lib/orthanc/db/
        restart: always
        networks:
          - pacs

      ohif_viewer:
        container_name: ohif
        image: ohif/viewer
        ports:
          - 3000:80
        environment:
          - APP_CONFIG:/usr/share/nginx/html/app-config.js
        volumes:
          - ./nginx_ohif.conf:/etc/nginx/conf.d/default.conf:ro
          - ./ohif.js:/usr/share/nginx/html/app-config.js:ro
          - ./logo.png:/usr/share/nginx/html/logo.png:ro
        restart: always
        networks:
          - pacs

    networks:
      pacs:
  1. Orthanc Configuration:

    • The Orthanc configuration file orthanc.json includes settings for DICOM AE titles, user credentials, and other parameters. Adjust these settings to your environment and ensure strong password practices.
  2. Deploying the Containers:

    • Pull and run the Docker containers using Docker Compose:
    docker-compose up -d
  • This command pulls the necessary images and starts the OHIF and Orthanc containers in detached mode.
  1. Access OHIF Viewer:

    • Open a web browser and navigate to http://server-ip:3000 (replace server-ip with your server's IP address) to access the OHIF Viewer.
  2. Importing DICOM Files:

    • You can import DICOM files directly into OHIF or send images to the Orthanc server using any DICOM-compatible software by specifying the AE Title and Port number of your Orthanc server.

Outcome: By following these steps, you’ll have an OHIF Viewer connected to an Orthanc PACS server, allowing you to view and manage medical images through a web interface.

Implementation 2: Using Orthanc with PostgreSQL via Docker (work fine but it has buit in viewer but didnt i connect to ohif )

Overview: This implementation focuses on setting up Orthanc with a PostgreSQL database using Docker, as per the instructions provided on the Orthanc official documentation. This setup persists the database, ensuring that the medical imaging data is retained across container restarts.

Prerequisites:

  • Docker installed on your system

Steps:

  1. Start the PostgreSQL Container:

    • Run the official PostgreSQL Docker container with environment variables for the PostgreSQL username and password.
    docker run --name some-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=pgpassword --rm postgres
  1. Create the Orthanc Database:

    • In a separate terminal shell, create a database within the PostgreSQL container to host the Orthanc data.
    docker run -it --link some-postgres:postgres --rm postgres sh -c 'echo "CREATE DATABASE orthanc;" | exec psql -h "$POSTGRES_PORT_5432_TCP_ADDR" -p "$POSTGRES_PORT_5432_TCP_PORT" -U postgres'
  • Enter the password (pgpassword) when prompted.
  1. Retrieve PostgreSQL Container IP and Port:

    • Fetch the IP address and port of the running PostgreSQL container to configure Orthanc.
    docker inspect --format '{{ .NetworkSettings.IPAddress }}' some-postgres
    docker inspect --format '{{ .NetworkSettings.Ports }}' some-postgres
  1. Get the Default Orthanc Configuration File:

    • Download the default Orthanc configuration file from the Orthanc Docker image.
    docker run --rm --entrypoint=cat jodogne/orthanc-plugins:1.12.4 /etc/orthanc/orthanc.json > /tmp/orthanc.json
  1. Modify the Orthanc Configuration:

    • Add a PostgreSQL section to the Orthanc configuration file (/tmp/orthanc.json). Adjust the Host and Port values based on the output from the previous step.
    "PostgreSQL" : {
      "EnableIndex" : true,
      "EnableStorage" : true,
      "Host" : "172.17.0.38",
      "Port" : 5432,
      "Database" : "orthanc",
      "Username" : "postgres",
      "Password" : "pgpassword"
    }

my config:

  1. Start the Orthanc Container:

    • Run the Orthanc container with the modified configuration file. This container will be linked to the PostgreSQL database.
    docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/orthanc.json:/etc/orthanc/orthanc.json:ro j

Implementation 3 :Connecting OHIF Viewer to a PACS Server Using DCM4CHEE ( Didnt work try to upload studies but didnt show in OHIF )

Implementation Steps

  1. Clone the DCM4CHEE Repository:

     git clone https://github.com/dcm4che/dcm4chee-arc-light.git
     cd dcm4chee-arc-light
    
  2. Modify the Docker Compose Configuration: In the docker directory, update the docker-compose.yml file as needed:

     version: "3"
     services:
       ldap:
         image: dcm4che/slapd-dcm4chee:2.6.6-32.0
         logging:
           driver: json-file
           options:
             max-size: "10m"
         ports:
           - "389:389"
         environment:
           STORAGE_DIR: /storage/fs1
         volumes:
           - /var/local/dcm4chee-arc/ldap:/var/lib/openldap/openldap-data
           - /var/local/dcm4chee-arc/slapd.d:/etc/openldap/slapd.d
       db:
         image: dcm4che/postgres-dcm4chee:16.2-32
         logging:
           driver: json-file
           options:
             max-size: "10m"
         ports:
          - "5432:5432"
         environment:
           POSTGRES_DB: pacsdb
           POSTGRES_USER: pacs
           POSTGRES_PASSWORD: pacs
         volumes:
           - /etc/localtime:/etc/localtime:ro
           - /etc/timezone:/etc/timezone:ro
           - /var/local/dcm4chee-arc/db:/var/lib/postgresql/data
       arc:
         image: dcm4che/dcm4chee-arc-psql:5.32.0
         logging:
           driver: json-file
           options:
             max-size: "10m"
         ports:
           - "8080:8080"
           - "8443:8443"
           - "9990:9990"
           - "9993:9993"
           - "11112:11112"
           - "2762:2762"
           - "2575:2575"
           - "12575:12575"
         environment:
           POSTGRES_DB: pacsdb
           POSTGRES_USER: pacs
           POSTGRES_PASSWORD: pacs
           WILDFLY_CHOWN: /storage
           WILDFLY_WAIT_FOR: ldap:389 db:5432
         depends_on:
           - ldap
           - db
         volumes:
           - /etc/localtime:/etc/localtime:ro
           - /etc/timezone:/etc/timezone:ro
           - /var/local/dcm4chee-arc/wildfly:/opt/wildfly/standalone
           - /var/local/dcm4chee-arc/storage:/storage
    
  3. Start the DCM4CHEE Services:

     docker-compose -f docker-compose.yml up -d
    
  4. Clone and Run OHIF Viewer:

     git clone https://github.com/OHIF/Viewers.git
     cd Viewers
     docker build -t ohif/viewer .
     docker run -p 3000:80 ohif/viewer
    
  5. Modify OHIF Viewer Configuration: Edit platform/viewer/public/config/servers.json to connect to DCM4CHEE PACS:

     /** @type {AppTypes.Config} */
    
     window.config = {
       routerBasename: '/',
       // whiteLabeling: {},
       extensions: [],
       modes: [],
       customizationService: {},
       showStudyList: true,
       // some windows systems have issues with more than 3 web workers
       maxNumberOfWebWorkers: 3,
       // below flag is for performance reasons, but it might not work for all servers
       showWarningMessageForCrossOrigin: true,
       showCPUFallbackMessage: true,
       showLoadingIndicator: true,
       experimentalStudyBrowserSort: false,
       strictZSpacingForVolumeViewport: true,
       groupEnabledModesFirst: true,
       maxNumRequests: {
         interaction: 100,
         thumbnail: 75,
         // Prefetch number is dependent on the http protocol. For http 2 or
         // above, the number of requests can be go a lot higher.
         prefetch: 25,
       },
       // filterQueryParam: false,
       defaultDataSourceName: 'dicomweb',
       /* Dynamic config allows user to pass "configUrl" query string this allows to load config without recompiling application. The regex will ensure valid configuration source */
       // dangerouslyUseDynamicConfig: {
       //   enabled: true,
       //   // regex will ensure valid configuration source and default is /.*/ which matches any character. To use this, setup your own regex to choose a specific source of configuration only.
       //   // Example 1, to allow numbers and letters in an absolute or sub-path only.
       //   // regex: /(0-9A-Za-z.]+)(\/[0-9A-Za-z.]+)*/
       //   // Example 2, to restricts to either hosptial.com or othersite.com.
       //   // regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/
       //   regex: /.*/,
       // },
       dataSources: [
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
           sourceName: 'dicomweb',
           configuration: {
             friendlyName: 'AWS S3 Static wado server',
             name: 'aws',
             wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
             qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
             wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
             qidoSupportsIncludeField: false,
             imageRendering: 'wadors',
             thumbnailRendering: 'wadors',
             enableStudyLazyLoad: true,
             supportsFuzzyMatching: false,
             supportsWildcard: true,
             staticWado: true,
             singlepart: 'bulkdata,video',
             // whether the data source should use retrieveBulkData to grab metadata,
             // and in case of relative path, what would it be relative to, options
             // are in the series level or study level (some servers like series some study)
             bulkDataURI: {
               enabled: true,
               relativeResolution: 'studies',
             },
             omitQuotationForMultipartRequest: true,
           },
         },
    
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
           sourceName: 'ohif2',
           configuration: {
             friendlyName: 'AWS S3 Static wado secondary server',
             name: 'aws',
             wadoUriRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb',
             qidoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb',
             wadoRoot: 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb',
             qidoSupportsIncludeField: false,
             supportsReject: false,
             imageRendering: 'wadors',
             thumbnailRendering: 'wadors',
             enableStudyLazyLoad: true,
             supportsFuzzyMatching: false,
             supportsWildcard: true,
             staticWado: true,
             singlepart: 'bulkdata,video',
             // whether the data source should use retrieveBulkData to grab metadata,
             // and in case of relative path, what would it be relative to, options
             // are in the series level or study level (some servers like series some study)
             bulkDataURI: {
               enabled: true,
               relativeResolution: 'studies',
             },
             omitQuotationForMultipartRequest: true,
           },
         },
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
           sourceName: 'ohif3',
           configuration: {
             friendlyName: 'AWS S3 Static wado secondary server',
             name: 'aws',
             wadoUriRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',
             qidoRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',
             wadoRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',
             qidoSupportsIncludeField: false,
             supportsReject: false,
             imageRendering: 'wadors',
             thumbnailRendering: 'wadors',
             enableStudyLazyLoad: true,
             supportsFuzzyMatching: false,
             supportsWildcard: true,
             staticWado: true,
             singlepart: 'bulkdata,video',
             // whether the data source should use retrieveBulkData to grab metadata,
             // and in case of relative path, what would it be relative to, options
             // are in the series level or study level (some servers like series some study)
             bulkDataURI: {
               enabled: true,
               relativeResolution: 'studies',
             },
             omitQuotationForMultipartRequest: true,
           },
         },
    
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
           sourceName: 'local5000',
           configuration: {
             friendlyName: 'Static WADO Local Data',
             name: 'DCM4CHEE',
             qidoRoot: 'http://localhost:5000/dicomweb',
             wadoRoot: 'http://localhost:5000/dicomweb',
             qidoSupportsIncludeField: false,
             supportsReject: true,
             supportsStow: true,
             imageRendering: 'wadors',
             thumbnailRendering: 'wadors',
             enableStudyLazyLoad: true,
             supportsFuzzyMatching: false,
             supportsWildcard: true,
             staticWado: true,
             singlepart: 'video',
             bulkDataURI: {
               enabled: true,
               relativeResolution: 'studies',
             },
           },
         },
    
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomwebproxy',
           sourceName: 'dicomwebproxy',
           configuration: {
             friendlyName: 'dicomweb delegating proxy',
             name: 'dicomwebproxy',
           },
         },
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
           sourceName: 'dicomjson',
           configuration: {
             friendlyName: 'dicom json',
             name: 'json',
           },
         },
         {
           namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal',
           sourceName: 'dicomlocal',
           configuration: {
             friendlyName: 'dicom local',
           },
         },
       ],
       httpErrorHandler: error => {
         // This is 429 when rejected from the public idc sandbox too often.
         console.warn(error.status);
    
         // Could use services manager here to bring up a dialog/modal if needed.
         console.warn('test, navigate to https://ohif.org/');
       },
       // whiteLabeling: {
       //   /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
       //   createLogoComponentFn: function (React) {
       //     return React.createElement(
       //       'a',
       //       {
       //         target: '_self',
       //         rel: 'noopener noreferrer',
       //         className: 'text-purple-600 line-through',
       //         href: '/',
       //       },
       //       React.createElement('img',
       //         {
       //           src: './assets/customLogo.svg',
       //           className: 'w-8 h-8',
       //         }
       //       ))
       //   },
       // },
       hotkeys: [
         {
           commandName: 'incrementActiveViewport',
           label: 'Next Viewport',
           keys: ['right'],
         },
         {
           commandName: 'decrementActiveViewport',
           label: 'Previous Viewport',
           keys: ['left'],
         },
         { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
         { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
         { commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
         {
           commandName: 'flipViewportHorizontal',
           label: 'Flip Horizontally',
           keys: ['h'],
         },
         {
           commandName: 'flipViewportVertical',
           label: 'Flip Vertically',
           keys: ['v'],
         },
         { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
         { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
         { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
         { commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
         { commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
         { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
         // {
         //   commandName: 'previousViewportDisplaySet',
         //   label: 'Previous Series',
         //   keys: ['pagedown'],
         // },
         // {
         //   commandName: 'nextViewportDisplaySet',
         //   label: 'Next Series',
         //   keys: ['pageup'],
         // },
         {
           commandName: 'setToolActive',
           commandOptions: { toolName: 'Zoom' },
           label: 'Zoom',
           keys: ['z'],
         },
         // ~ Window level presets
         {
           commandName: 'windowLevelPreset1',
           label: 'W/L Preset 1',
           keys: ['1'],
         },
         {
           commandName: 'windowLevelPreset2',
           label: 'W/L Preset 2',
           keys: ['2'],
         },
         {
           commandName: 'windowLevelPreset3',
           label: 'W/L Preset 3',
           keys: ['3'],
         },
         {
           commandName: 'windowLevelPreset4',
           label: 'W/L Preset 4',
           keys: ['4'],
         },
         {
           commandName: 'windowLevelPreset5',
           label: 'W/L Preset 5',
           keys: ['5'],
         },
         {
           commandName: 'windowLevelPreset6',
           label: 'W/L Preset 6',
           keys: ['6'],
         },
         {
           commandName: 'windowLevelPreset7',
           label: 'W/L Preset 7',
           keys: ['7'],
         },
         {
           commandName: 'windowLevelPreset8',
           label: 'W/L Preset 8',
           keys: ['8'],
         },
         {
           commandName: 'windowLevelPreset9',
           label: 'W/L Preset 9',
           keys: ['9'],
         },
       ],
       servers: {
         dicomWeb: [
           {
             name: 'DCM4CHEE',
             wadoUriRoot: 'http://192.168.131.131:8080/dcm4chee-arc/aets/DCM4CHEE/wado',
             qidoRoot: 'http://192.168.131.131:8080/dcm4chee-arc/aets/DCM4CHEE/rs',
             wadoRoot: 'http://192.168.131.131:8080/dcm4chee-arc/aets/DCM4CHEE/rs',
             qidoSupportsIncludeField: true,
             imageRendering: 'wadors',
             thumbnailRendering: 'wadors',
             enableStudyLazyLoad: true,
             supportsFuzzyMatching: true,
           },
         ],
       },
      servers: {
         dicomWeb: [
           {
             name: 'DCM4CHEE',
             wadoUriRoot: 'http://192.168.131.131:8080/dcm4chee-arc/aets/DCM4CHEE/wado',
             qidoRoot: 'http://192.168.131.131:8080/dcm4chee-arc/aets/DCM4CHEE/rs',
             wadoRoot: 'http://192.168.131.131:8080/dcm4chee-arc/aets/DCM4CHEE/rs',
             qidoSupportsIncludeField: true,
             imageRendering: 'wadors',
             thumbnailRendering: 'wadors',
             enableStudyLazyLoad: true,
             supportsFuzzyMatching: true,
           },
         ],
       },
     };
    
  6. Test the Connection: Open the OHIF Viewer in your browser at http://localhost:3000 and ensure it connects to the DCM4CHEE PACS.

0
Subscribe to my newsletter

Read articles from Muhammad Hassan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Muhammad Hassan
Muhammad Hassan

Hey there! I'm currently working as an Associate DevOps Engineer, and I'm diving into popular DevOps tools like Azure Devops,Linux, Docker, Kubernetes,Terraform and Ansible. I'm also on the learning track with AWS certifications to amp up my cloud game. If you're into tech collaborations and exploring new horizons, let's connect!