---
sidebar_position: 8
sidebar_label: Hanging Protocol
---
# Module: Hanging Protocol

## Overview

[Hanging protocols](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf) are an essential part of any radiology viewer.
OHIF uses Hanging Protocols to handle the arrangement of the images in the viewport. In
short, the registered protocols will get matched with the DisplaySets that are
available. Each protocol gets a score, and they are ranked. The
winning protocol (highest score) gets applied and its settings run for the viewports
to be arranged.


In `OHIF-v3` hanging protocols you can:

- Define what layout the viewport should starts with (e.g., 2x2 layout)
- Specify the type of the viewport and its orientation (e.g., stack, volume with Sagittal view)
- Define which displaySets gets displayed in which viewport of the layout (e.g,. displaySet that has modality of 'CT' and 'SeriesDescription' of 'Coronary Arteries' gets displayed in the first viewport of the layout)
- Apply certain initial viewport settings (e.g., inverting the contrast, jumping to a specific slice, etc.)
- Add specific synchronization rules for the viewports (e.g., synchronize the zoom of the viewports of the index 1, 2 OR synchronize the VOI of the viewports of the index 2, 3)


Using `hangingProtocolModule` you can provide/register the protocols for OHIF to
utilize.

Here is an example protocol which if used will hang a 1x3 layout with the first viewport showing a CT image, the second viewport showing a PT image and the third viewport showing their fusion, all in Sagittal orientations to achieve a view of


![](../../../assets/img/hangingProtocolExample.png)


```js
const oneByThreeProtocol = {
  id: 'oneByThreeProtocol',
  locked: true,
  hasUpdatedPriorsInformation: false,
  name: 'Default',
  createdDate: '2021-02-23T19:22:08.894Z',
  modifiedDate: '2022-10-04T19:22:08.894Z',
  availableTo: {},
  editableBy: {},
  imageLoadStrategy: 'interleaveTopToBottom',
  protocolMatchingRules: [
    {
      attribute: 'ModalitiesInStudy',
      constraint: {
        contains: ['CT', 'PT'],
      },
    },
  ],
  displaySetSelectors: {
    ctDisplaySet: {
      seriesMatchingRules: [
        {
          weight: 1,
          attribute: 'Modality',
          constraint: {
            equals: {
              value: 'CT',
            },
          },
          required: true,
        },
        {
          weight: 1,
          attribute: 'isReconstructable',
          constraint: {
            equals: {
              value: true,
            },
          },
          required: true,
        },
      ],
    },
    ptDisplaySet: {
      seriesMatchingRules: [
        {
          attribute: 'Modality',
          constraint: {
            equals: 'PT',
          },
          required: true,
        },
        {
          weight: 1,
          attribute: 'isReconstructable',
          constraint: {
            equals: {
              value: true,
            },
          },
          required: true,
        },
        {
          attribute: 'SeriesDescription',
          constraint: {
            contains: 'Corrected',
          },
        },
      ],
    },
  },
  stages: [
    {
      id: 'hYbmMy3b7pz7GLiaT',
      name: 'default',
      viewportStructure: {
        layoutType: 'grid',
        properties: {
          rows: 1,
          columns: 3,
        },
      },
      viewports: [
        {
          viewportOptions: {
            viewportId: 'ctAXIAL',
            viewportType: 'volume',
            orientation: 'sagittal',
            initialImageOptions: {
              preset: 'middle',
            },
            syncGroups: [
              {
                type: 'voi',
                id: 'ctWLSync',
                source: true,
                target: true,
              },
            ],
          },
          displaySets: [
            {
              id: 'ctDisplaySet',
            },
          ],
        },
        {
          viewportOptions: {
            viewportId: 'ptAXIAL',
            viewportType: 'volume',
            orientation: 'sagittal',
            initialImageOptions: {
              preset: 'middle',
            },
          },
          displaySets: [
            {
              id: 'ptDisplaySet',
            },
          ],
        },
        {
          viewportOptions: {
            viewportId: 'fusionSAGITTAL',
            viewportType: 'volume',
            orientation: 'sagittal',
            initialImageOptions: {
              preset: 'middle',
            },
            syncGroups: [
              {
                type: 'voi',
                id: 'ctWLSync',
                source: false,
                target: true,
              },
            ],
          },
          displaySets: [
            {
              id: 'ctDisplaySet',
            },
            {
              options: {
                colormap: 'hsv',
                voi: {
                  windowWidth: 5,
                  windowCenter: 2.5,
                },
              },
              id: 'ptDisplaySet',
            },
          ],
        },
      ],
      createdDate: '2021-02-23T18:32:42.850Z',
    },
  ],
  numberOfPriorsReferenced: -1,
};


function getHangingProtocolModule() {
  return [
    {
      id: 'oneByThreeProtocol',
      protocol: oneByThreeProtocol,
    },
  ];
}
```


## Skeleton of a Protocol

The skeleton of a hanging protocol is as follows:


### Id
unique identifier for the protocol, this id can be used inside mode configuration
to specify which protocol should be used for a specific mode. A mode can
request a protocol by its id (which makes OHIF to apply the protocol without
matching), or provides and array of ids which will
make the ProtocolEngine to choose the best matching protocol (based on
protocolMatching rules, which is next section).

### imageLoadStrategy
The image load strategy specifies a function (by name) containing logic to re-order
the image load requests.  This allows loading images viewed earlier to be done
sooner than those loaded later.  The available strategies are:

* interleaveTopToBottom to start at the top and work towards the bottom, for all series being loaded
* interleaveCenter is like top to bottom but starts at the center
* nth is a strategy that loads every nth instance, starting with the center
and end points, and then filling in progressively all along the image.  This results in partial
image view very quickly.

### protocolMatchingRules
A list of criteria for the protocol along with the provided points for ranking.

  - `weight`: weight for the matching rule. Eventually, all the registered
    protocols get sorted based on the weights, and the winning protocol gets
    applied to the viewer.
  - `attribute`: tag that needs to be matched against. This can be either
    Study-level metadata or a custom attribute such as "StudyInstanceUID",
    "StudyDescription", "ModalitiesInStudy", "NumberOfStudyRelatedSeries", "NumberOfSeriesRelatedInstances"
    In addition to these tags, you can also use a custom attribute that you have registered before.
    We will learn more about this later.



  - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be
    [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`]
   
  - | Rule 	| Single Value 	| Array Value 	| Example 	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    |---	|---	|---	| |
    | equals 	| === 	| All members are === in same order 	| value = ['abc', 'def', 'GHI']<br/>testValue = 'abc' (Fail)<br/><br/>          = ['abc'] (Fail)<br/><br/>          = ['abc', 'def', 'GHI'] (Valid)<br/><br/>          = ['abc', 'GHI', 'def'] (Fail)<br/><br/>          = ['abc', 'def'] (Fail)<br/><br/>value = 'Attenuation Corrected'<br/>testValue = 'Attenuation Corrected' (Valid)<br/>          = 'Attenuation' (Fail)<br/><br/> 	       value = ['Attenuation Corrected'] <br/>     testValue = ['Attenuation Corrected'] (Valid)<br/> = 'Attenuation Corrected' (Valid) <br/>  = 'Attenuation' (Fail) <br/>                                                            |
    | doesNotEqual 	| !== 	| Any member is !== for the array, either in value, order, or length 	| value = ['abc', 'def', 'GHI']<br/>testValue = 'abc' (Valid)<br/>          = ['abc'] (Valid)<br/>          = ['abc', 'def', 'GHI'] (Fail)<br/>          = ['abc', 'GHI', 'def'] (Valid)<br/>          = ['abc', 'def'] (Valid)<br/><br/>value = 'Attenuation Corrected'<br/>testValue = 'Attenuation Corrected' (Fail)Valid<br/><br/>          = 'Attenuation' (Valid) 	<br/><br/>      value = ['Attenuation Corrected']<br/> testValue = ['Attenuation Corrected'] (Fail) <br/> = 'Attenuation Corrected' (Fail)  <br/> = 'Attenuation' (Fail)                                                                                |
    | includes 	| Not allowed 	| Value is equal to one of the values of the array 	| value = ['abc', 'def', 'GHI']<br/>testValue = ['abc'] (Valid)<br/><br/>  = ‘abc’ (Fail)<br/><br/>= [‘abc’] (Fail)<br/><br/>        = ‘dog’ (Fail)<br/><br/>  =         = [‘att’, ‘abc’] (Valid)<br/><br/>          = ['abc', 'def', 'dog'] (Valid)<br/><br/>          = ['cat', 'dog'] (Fail)<br/><br/><br/> value = 'Attenuation Corrected' <br/> 	testValue = ['Attenuation Corrected', 'Corrected'] (Valid)<br/>= ['Attenuation', 'Corrected'] (Fail)<br/><br/><br/>value = ['Attenuation Corrected']<br/>testValue = 'Attenuation Corrected' (Fail)<br/> = ['Attenuation Corrected', 'Corrected'] (Valid)<br/> = ['Attenuation', 'Corrected'] (Fail) |
    | doesNotInclude 	| Not allowed 	| Value is not in one of the values of the array 	| value = ['abc', 'def', 'GHI']<br/>testValue = ‘Corr’ (Valid)<br/><br/>          = ‘abc’ (Fail)<br/><br/>          = [‘att’, ‘cor’] (Valid)<br/><br/>          = ['abc', 'def', 'dog'] (Fail)<br/><br/><br/>value = 'Attenuation Corrected' <br/> testValue = ['Attenuation Corrected', 'Corrected'] (Fail) <br/> = ['Attenuation', 'Corrected'] (Valid) <br/><br/><br/>value = ['Attenuation Corrected'] <br/> testValue = 'Attenuation' (Fail)<br/> = ['Attenuation Corrected', 'Corrected'] (Fail)	<br/>= ['Attenuation', 'Corrected'] (Valid)                                                                                                                     |
    | containsI 	| String containment (case insensitive) 	| String containment (case insensitive) is OK for one of the rule values 	| value = 'Attenuation Corrected'<br/>testValue = ‘Corr’ (Valid)<br/>          = ‘corr’ (Valid)<br/><br/>          = [‘att’, ‘cor’] (Valid)<br/><br/>          = [‘Att’, ‘Wall’] (Valid)<br/><br/>          = [‘cat’, ‘dog’] (Fail)<br/><br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'def' (Valid)<br/><br/>          = 'dog' (Fail)<br/><br/>          = ['gh', 'de'] (Valid)<br/><br/>          = ['cat', 'dog'] (Fail)<br/> 	                                                                                                                                                                                 |
    | contains 	| String containment (case sensitive) 	| String containment (case sensitive) is OK for one of the rule values 	| value = 'Attenuation Corrected'<br/>testValue = ‘Corr’ (Valid)<br/>          = ‘corr’ (Fail)<br/>          = [‘att’, ‘cor’] (Fail)<br/>          = [‘Att’, ‘Wall’] (Valid)<br/><br/>          = [‘cat’, ‘dog’] (Fail)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/><br/>testValue = 'def' (Valid)<br/>          = 'dog' (Fail)<br/><br/>          = ['cat', 'de'] (Valid)<br/><br/>          = ['cat', 'dog'] (Fail) 	                                                                                                                                                                                                      |
    | doesNotContain 	| String containment is false 	| String containment is false for all values of the array 	| value = 'Attenuation Corrected'<br/>testValue = ‘Corr’ (Fail)<br/><br/><br/>          = ‘corr’ (Valid)<br/><br/>          = [‘att’, ‘cor’] (Valid)<br/><br/>          = [‘Att’, ‘Wall’] (Fail)<br/><br/>          = [‘cat’, ‘dog’] (Valid)<br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'def' (Fail)<br/><br/><br/>          = 'dog' (Valid)<br/><br/>          = ['cat', 'de'] (Fail)<br/><br/>          = ['cat', 'dog'] (Valid) 	                                                                                                                                                                                 |
    | doesNotContainI 	| String containment is false (case insensitive) 	| String containment (case insensitive) is false for all values of the array 	| value = 'Attenuation Corrected'<br/>testValue = ‘Corr’ (Fail)<br/><br/>          = ‘corr’ (Fail)<br/><br/>          = [‘att’, ‘cor’] (Fail)<br/><br/>          = [‘Att’, ‘Wall’] (Fail)<br/><br/>          = [‘cat’, ‘dog’] (Valid)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'DEF' (Fail)<br/><br/>          = 'dog' (Valid)<br/><br/>          = ['cat', 'gh'] (Fail)<br/><br/>          = ['cat', 'dog'] (Valid) 	                                                                                                                                                                                        |
    | startsWith 	| Value begins with characters 	| Starts with one of the values of the array 	| value = 'Attenuation Corrected'<br/>testValue = ‘Corr’ (Fail)<br/><br/>          = ‘Att’ (Fail)<br/><br/>          = ['cat', 'dog', 'Att'] (Valid)<br/><br/>          = [‘cat’, ‘dog’] (Fail)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'deg' (Valid)<br/><br/>          = ['cat', 'GH']  (Valid)<br/><br/>          = ['cat', 'gh'] (Fail)<br/><br/>          = ['cat', 'dog'] (Fail) 	                                                                                                                                                                                                                     |
    | endsWith 	| Value ends with characters 	| ends with one of the value of the array 	| value = 'Attenuation Corrected'<br/>testValue = ‘TED’ (Fail)<br/><br/>          = ‘ted’ (Valid)<br/><br/>          = ['cat', 'dog', 'ted'] (Valid)<br/><br/>          = [‘cat’, ‘dog’] (Fail)<br/><br/><br/>value = ['abc', 'def', 'GHI']<br/>testValue = 'deg' (Valid)<br/><br/>          = ['cat', 'HI']  (Valid)<br/><br/>          = ['cat', 'hi'] (Fail)<br/><br/>          = ['cat', 'dog'] (Fail) 	                                                                                                                                                                                                                     |
    | greaterThan 	| value is => to rule   	| Not applicable 	| value = 30<br/><br/>testValue = 20 (Valid)<br/>          = 40 (Fail)<br/><br/> 	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    | lessThan 	| value is <= to rule 	| Not applicable 	| value = 30<br/><br/>testValue = 40 (Valid)<br/>          = 20 (Fail)<br/> 	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | range  	| Not applicable 	| 2 value requested (min and max) 	| value = 50<br/><br/>testValue = [10,60] (Valid)<br/>          = [60, 10] (Valid)<br/><br/>          = [0, 10] (Fail)<br/><br/>          = [70, 80] (Fail)<br/><br/>          = 45  (Fail)<br/><br/>          = [45] (Fail) 	                                                                                                                                                                                                                                                                                                                                                                                                   |
    | notNull 	| Not Applicable 	| Not Applicable 	| No value 	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    A sample of the matching rule is above which matches against the study description to be PETCT

    ```js
    {
      id: 'wauZK2QNEfDPwcAQo',
      weight: 1,
      attribute: 'StudyDescription',
      constraint: {
        contains: {
          value: 'PETCT',
        },
      },
      required: false,
    },
    ```

### displaySetSelectors
Defines the display sets that the protocol will use for arrangement.

```js
  displaySetSelectors: {
    ctDisplaySet: {
      seriesMatchingRules: [
        {
          weight: 1,
          attribute: 'Modality',
          constraint: {
            equals: {
              value: 'CT',
            },
          },
          required: true,
        },
        {
          weight: 1,
          attribute: 'isReconstructable',
          constraint: {
            equals: {
              value: true,
            },
          },
          required: true,
        },
      ],
    },
    ptDisplaySet: {
      seriesMatchingRules: [
        {
          attribute: 'Modality',
          constraint: {
            equals: 'PT',
          },
          required: true,
        },
        {
          weight: 1,
          attribute: 'isReconstructable',
          constraint: {
            equals: {
              value: true,
            },
          },
          required: true,
        },
        {
          attribute: 'SeriesDescription',
          constraint: {
            contains: 'Corrected',
          },
        },
      ],
    },
  }
```

As you see above we have specified two displaysets: 1) ctDisplaySet , 2) ptDisplaySet
The ctDisplaySet will match against all the series that are CT and reconstructable
The ptDisplaySet will match against all the series that are PT and reconstructable.

As you see each selector is composed of an `id` as the key and a set of `seriesMatchingRules` (displaySetMatchingRules) which gives score to the displaySet
based on the matching rules. The displaySet with the highest score will be used for the `id`.

### stages
Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules. Therefore, the `stages` property is array of objects, each object being one stage.

### viewportStructure
Defines the layout of the viewer. You can define the number of `rows` and `columns`. There should be `rows * columns` number of
viewport configuration in the `viewports` property. Note that order of viewports are rows first then columns.

```js
viewportStructure: {
    type: 'grid',
    properties: {
      rows: 1,
      columns: 2,
      viewportOptions: [],
    },
},
```

In addition to the equal viewport sizes, you can define viewports to span multiple rows or columns.

```js
viewportStructure: {
    type: 'grid',
    properties: {
      rows: 1,
      columns: 2,
      viewportOptions: [
        {
          x: 0,
          y: 0,
          width: 1 / 4,
          height: 1,
        },
        {
          x: 1 / 4,
          y: 0,
          width: 3 / 4,
          height: 1,
        },
      ],
    },
},
```


### viewports
This field includes the viewports that will get hung on the viewer.

```js
viewports: [
  {
    viewportOptions: {
      viewportId: 'ctAXIAL',
      viewportType: 'volume',
      orientation: 'sagittal',
      initialImageOptions: {
        preset: 'middle',
      },
      syncGroups: [
        {
          type: 'voi',
          id: 'ctWLSync',
          source: true,
          target: true,
        },
      ],
    },
    displaySets: [
      {
        id: 'ctDisplaySet',
      },
    ],
  },
  // the rest
],
```

As you can see in the hanging protocol we defined three viewports (but only showing one of them right above). Each viewport has two properties:

1. `viewportOptions`: defines the viewport properties such as
   - `viewportId`: unique identifier for the viewport (optional)
   - `viewportType`: type of the viewport (optional - options: stack, volume - default is stack
   - `background`: background color of the viewport (optional)
   - `orientation`: orientation of the viewport (optional - if not defined for volume -> acquisition axis)
   - `toolGroupId`: tool group that will be used for the viewport (optional)
   - `initialImageOptions`: initial image options (optional - can be specific imageIndex number or preset (first, middle, last))
   - `syncGroups`: sync groups for the viewport (optional)


2. `displaySets`: defines the display sets that are displayed on a viewport. It is an array of objects, each object being one display set.
   - `id`: id of the display set (required)
   - `options` (optional): options for the display set
        - voi: windowing options for the display set (optional: windowWidth, windowCenter)
        - voiInverted: whether the VOI is inverted or not (optional)
        - colormap: colormap for the display set (optional, it is an object with `{ name }` and optional extra `opacityMapping` property)
        - displayPreset: display preset for the display set (optional, used for 3D volume rendering. e.g., 'CT-Bone')


### Custom attribute
For any matching rules you can specify a custom attribute too. For instance,
if you have a timepoint attribute in for each of your studies, you can use that in the matching rules.

```js
{
  id: 'wauZK2QNEfDPwcAQo',
  weight: 1,
  attribute: 'timepoint',
  constraint: {
    equals: {
      value: 'baseline',
    },
  },
  required: false,
},
```

and then you need to register a callback in the HangingProtocolService to get the value for the attribute.

```js
HangingProtocolService.addCustomAttribute(
  'timepoint', // attributeId
  'addCustomAttribute', // attributeName
  study => { // callback that returns the value for the attribute
    const timePoint = fetchFromMyCustomBackend(study.studyInstanceUid);
    return timePoint;
  }
);
```



## Matching on Prior Study with UID

Often it is desired to match a new study to a prior study (e.g., follow up on
a surgery). Since the hanging protocols run on displaySets we need to have a
way to let OHIF knows that it needs to load the prior study as well. This can
be done by specifying both StudyInstanceUIDs in the URL. Below we are
running OHIF with two studies

```bash
http://localhost:3000/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095722.1
```

Now you have access to both studies and you can use matchingRules to match
displaySets.



Our roadmap includes enabling matching on prior studies without the UID (e.g., baseline, most recent and index).
