

# Using DICOMweb with AWS HealthImaging
<a name="using-dicomweb"></a>

You can retrieve DICOM objects from AWS HealthImaging using a representation of [DICOMweb](https://www.dicomstandard.org/using/dicomweb) APIs, which are web-based APIs that follow the DICOM standard for medical imaging. This functionality enables you to interoperate with systems that utilize DICOM Part 10 binaries while simultaneously taking advantage of HealthImaging’s [cloud native actions](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_Operations.html). The focus of this chapter is how to use HealthImaging's implementation of DICOMweb APIs to return DICOMweb responses.

**Important**  
HealthImaging stores DICOM data as [image sets](getting-started-concepts.md#concept-image-set). Use HealthImaging cloud-native actions to manage and retrieve image sets. HealthImaging's DICOMweb APIs can be used to return image set information with DICOMweb-conformant responses.  
The APIs listed in this chapter are built in conformance to the [DICOMweb](https://www.dicomstandard.org/using/dicomweb) standard for web-based medical imaging. Because they are representations of DICOMweb APIs, they are not offered through AWS CLI and AWS SDKs.

**Topics**
+ [Storing instances with STOW-RS](dicomweb-storing.md)
+ [Retrieving DICOM data from HealthImaging](dicomweb-retrieve.md)
+ [Searching DICOM data in HealthImaging](dicomweb-search.md)
+ [OIDC authentication for DICOMweb APIs](dicomweb-oidc.md)

# Storing instances with STOW-RS
<a name="dicomweb-storing"></a>

AWS HealthImaging offers a representation of the [https://www.dicomstandard.org/using/dicomweb/store-stow-rs](https://www.dicomstandard.org/using/dicomweb/store-stow-rs) APIs for importing data. Use these APIs to synchronously store DICOM data to your HealthImaging data store.

The following table describes the HealthImaging representations of DICOMweb STOW-RS APIs available for importing data.


**HealthImaging representations of DICOMweb STOW-RS APIs**  

| Name | Description | 
| --- | --- | 
| StoreDICOM | Store one or more instances to a HealthImaging data store. | 
| StoreDICOMStudy | Store one or more instances corresponding to a specified Study Instance UID to a HealthImaging data store. | 

Data imported with the `StoreDICOM` and `StoreDICOMStudy` actions will be organized as new primary image sets, or added to existing primary image sets, using the same logic as asynchronous [import jobs](https://docs.aws.amazon.com/healthimaging/latest/devguide/understanding-import-jobs.html). If the metadata elements of newly imported DICOM P10 data conflict with existing primary [image sets](https://docs.aws.amazon.com/healthimaging/latest/devguide/getting-started-concepts.html#concept-image-set), the new data will be added to non-primary [image sets](https://docs.aws.amazon.com/healthimaging/latest/devguide/getting-started-concepts.html#concept-image-set).

**Note**  
These actions support upload of up to 1GB of DICOM data per request.
The API response will be in the JSON format, conformant to the DICOMweb STOW-RS standard.

**To initiate a StoreDICOM request**  


1. Collect your AWS region, HealthImaging `datastoreId`, and DICOM P10 file name.

1. Construct a URL for the request of the form: `https://dicom-medical-imaging.region.amazonaws.com/datastore/datastore-id/studies`

1. Determine the content length of the DICOM P10 file using your preferred command, for example `$(stat -f %z $FILENAME)`.

1. Prepare and send your request. `StoreDICOM` uses a HTTP POST request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol.

 

**Example 1: To store a DICOM P10 file using the `StoreDICOM` action**  

```
curl -X POST -v \
  'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies' \
  --aws-sigv4 "aws:amz:$AWS_REGION:medical-imaging" \
  --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
  --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
  --header "x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD" \
  --header "x-amz-decoded-content-length: $CONTENT_LENGTH" \
  --header 'Accept: application/dicom+json' \
  --header "Content-Type: application/dicom" \
  --upload-file $FILENAME
```

**Example 2: To store a DICOM P10 file using the `StoreDICOMStudy` action**  
The only difference between StoreDICOM and StoreDICOMStudy is that a Study Instance UID is passed as a parameter to StoreDICOMStudy, and the uploaded instances must be members of the specified study.  

```
curl -X POST -v \
  'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457' \
  --aws-sigv4 "aws:amz:$AWS_REGION:medical-imaging" \
  --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
  --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
  --header "x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD" \
  --header "x-amz-decoded-content-length: $CONTENT_LENGTH" \
  --header 'Accept: application/dicom+json' \
  --header "Content-Type: application/dicom" \
  --upload-file $FILENAME
```

**Example 3: To store DICOM P10 files with a multi-part HTTP payload**  
Multiple P10 files can be uploaded with a single multi-part upload action. The following shell commands demonstrate how to assemble a multi-part payload containing two P10 files, and upload it with the `StoreDICOM` action.  

```
#!/bin/sh
FILENAME=multipart.payload
BOUNDARY=2a8a02b9-0ed3-c8a7-7ebd-232427531940
boundary_str="--$BOUNDARY\r\n"
mp_header="${boundary_str}Content-Type: application/dicom\r\n\r\n"

##Encapsulate the binary DICOM file 1.
printf '%b' "$mp_header" > $FILENAME
cat file1.dcm >> $FILENAME

##Encapsulate the binary DICOM file 2 (note the additional CRLF before the part header).
printf '%b' "\r\n$mp_header" >> $FILENAME
cat file2.dcm >> $FILENAME

## Add the closing boundary.
printf '%b' "\r\n--$BOUNDARY--" >> $FILENAME

## Obain the payload size in bytes.
multipart_payload_size=$(stat -f%z "$FILENAME")

# Execute CURL POST request with AWS SIGv4
curl -X POST -v \
  'https://iad-dicom.external-healthlake-imaging.ai.aws.dev/datastore/b5f34e91ca734b39a54ac11ea42416cf/studies' \
  --aws-sigv4 "aws:amz:us-east-1:medical-imaging" \
  --user "AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
  --header "x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD" \
  --header "x-amz-decoded-content-length: ${multipart_payload_size}" \
  --header 'Accept: application/dicom+json' \
  --header "Content-Type: multipart/related; type=\"application/dicom\"; boundary=\"${BOUNDARY}\"" \
  --data-binary "@$FILENAME"

# Delete the payload file
rm $FILENAME
```

# Retrieving DICOM data from HealthImaging
<a name="dicomweb-retrieve"></a>

AWS HealthImaging offers representations of [https://www.dicomstandard.org/using/dicomweb/retrieve-wado-rs-and-wado-uri](https://www.dicomstandard.org/using/dicomweb/retrieve-wado-rs-and-wado-uri) APIs to retrieve data at the series and instance levels. With these APIs, it is possible to retrieve all metadata for a DICOM series from a HealthImaging [data store](getting-started-concepts.md#concept-data-store). It is also possible to retrieve a DICOM instance, DICOM instance metadata, and DICOM instance frames (pixel data). HealthImaging's `DICOMweb WADO-RS` APIs offer flexibility in how you retrieve data stored in HealthImaging and provide interoperability with legacy applications.

**Important**  
HealthImaging stores DICOM data as [image sets](getting-started-concepts.md#concept-image-set). Use HealthImaging [cloud native actions](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_Operations.html) to manage and retrieve image sets. HealthImaging's DICOMweb APIs can be used to return image set information with DICOMweb-conformant responses.  
The APIs listed in this section are built in conformance to the DICOMweb (WADO-RS) standard for web-based medical imaging. Because they are representations of DICOMweb APIs, they are not offered through AWS CLI and AWS SDKs.

The following table describes all HealthImaging representations of DICOMweb WADO-RS APIs available for retrieving data from HealthImaging.


**HealthImaging representations of DICOMweb WADO-RS APIs**  

| Name | Description | 
| --- | --- | 
| GetDICOMSeriesMetadata | Retrieve DICOM instance metadata (.json file) for a DICOM series in a HealthImaging data store by specifying the Study and Series UIDs associated with a resource. See [Retrieve series metadata](dicomweb-retrieve-series-metadata.md). | 
| GetDICOMInstance | Retrieve a DICOM instance (.dcm file) from a HealthImaging data store by specifying the Series, Study, and Instance UIDs associated with a resource. See [Retrieve an instance](dicomweb-retrieve-instance.md). | 
| GetDICOMInstanceMetadata | Retrieve DICOM instance metadata (.json file) from a DICOM instance in a HealthImaging data store by specifying the Series, Study, and Instance UIDs associated with a resource. See [Retrieve instance metadata](dicomweb-retrieve-instance-metadata.md). | 
| GetDICOMInstanceFrames | Retrieve single or batch image frames (multipart request) from a DICOM instance in a HealthImaging data store by specifying the Series UID, Study UID, Instance UIDs, and frame numbers associated with a resource. See [Retrieve frames](dicomweb-retrieve-instance-frames.md). | 

**Topics**
+ [Getting a DICOM instance from HealthImaging](dicomweb-retrieve-instance.md)
+ [Getting DICOM instance metadata from HealthImaging](dicomweb-retrieve-instance-metadata.md)
+ [Getting DICOM series metadata from HealthImaging](dicomweb-retrieve-series-metadata.md)
+ [Getting DICOM instance frames from HealthImaging](dicomweb-retrieve-instance-frames.md)
+ [Getting DICOM bulkdata from HealthImaging](dicom-retrieve-bulkdata.md)

# Getting a DICOM instance from HealthImaging
<a name="dicomweb-retrieve-instance"></a>

Use the `GetDICOMInstance` action to retrieve a DICOM instance (`.dcm` file) from a HealthImaging [data store](getting-started-concepts.md#concept-data-store) by specifying the Series, Study, and Instance UIDs associated with the resource. The API will only return instances from primary image sets unless the optional [image set parameter](getting-started-concepts.md#concept-image-set) is provided. You can retrieve any instance (from primary or non-primary image sets) in the data store by specifying the `imageSetId` as a query parameter. DICOM data can be retrieved in either its stored transfer syntax or as uncompressed (ELE) format.

**To get a DICOM instance (`.dcm`)**  


1. Collect HealthImaging `datastoreId` and `imageSetId` parameter values.

1. Use the [https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_GetImageSetMetadata.html](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_GetImageSetMetadata.html) action with the `datastoreId` and `imageSetId` parameter values to retrieve associated metadata values for `studyInstanceUID`, `seriesInstanceUID`, and `sopInstanceUID`. For more information, see [Getting image set metadata](get-image-set-metadata.md).

1. Construct a URL for the request using the values for `datastoreId`, `studyInstanceUID`, `seriesInstanceUID`, `sopInstanceUID`, and `imageSetId`. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastore-id/studies/study-instance-uid/series/series-instance-uid/instances/sop-instance-uid?imageSetId=image-set-id
   ```

1. Prepare and send your request. `GetDICOMInstance` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following code example uses the `curl` command line tool to get a DICOM instance (`.dcm` file) from HealthImaging.

------
#### [ Shell ]

   ```
   curl --request GET \
     'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/instances/1.3.6.1.4.1.5962.1.1.4.1.1.20040826186059.5457?imageSetId=459e50687f121185f747b67bb60d1bc8' \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom; transfer-syntax=1.2.840.10008.1.2.1' \
     --output 'dicom-instance.dcm'
   ```

------
**Note**  
The `transfer-syntax` UID is optional and defaults to Explicit VR Little Endian if not included. Supported transfer syntaxes include:  
Explicit VR Little Endian (ELE) - `1.2.840.10008.1.2.1` (default for lossless image frames)
If `transfer-syntax=*` then the image frame(s) will be returned in the stored transfer syntax.
High-Throughput JPEG 2000 with RPCL Options Image Compression (Lossless Only) - `1.2.840.10008.1.2.4.202` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.202`
JPEG 2000 Lossless - `1.2.840.10008.1.2.4.90` - if the instance is stored in HealthImaging as lossless.
JPEG Baseline (Process 1): Default Transfer Syntax for Lossy JPEG 8-bit Image Compression - `1.2.840.10008.1.2.4.50` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.50`
JPEG 2000 Image Compression - `1.2.840.10008.1.2.4.91` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.91`
High-Throughput JPEG 2000 Image Compression - `1.2.840.10008.1.2.4.203` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.203`
JPEG XL Image Compression - `1.2.840.10008.1.2.4.112` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.112`
Instances stored in HealthImaging with one or more image frames encoded in the MPEG family of [Transfer Syntaxes](supported-transfer-syntaxes.md) (which includes MPEG2, MPEG-4 AVC/H.264 and HEVC/H.265) may be retrieved with the corresponding transfer-syntax UID. For example, `1.2.840.10008.1.2.4.100` if the instance is stored as MPEG2 Main Profile Main Level.

   For more information, see [Supported transfer syntaxes](supported-transfer-syntaxes.md) and [Image frame decoding libraries for AWS HealthImaging](reference-libraries.md).

# Getting DICOM instance metadata from HealthImaging
<a name="dicomweb-retrieve-instance-metadata"></a>

Use the `GetDICOMInstanceMetadata` action to retrieve the metadata from a DICOM instance in a HealthImaging [ data store](getting-started-concepts.md#concept-data-store) by specifying the Series, Study, and Instance UIDs associated with the resource. The API will only return instance metadata from primary image sets unless the optional [ image set](getting-started-concepts.md#concept-image-set) parameter is provided. You can retrieve any instance metadata (from primary or non-primary image sets) in the data store by specifying the `imageSetId` as a query parameter.

**To get DICOM instance metadata (`.json`)**  


1. Collect HealthImaging `datastoreId` and `imageSetId` parameter values.

1. Use the [https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_GetImageSetMetadata.html](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_GetImageSetMetadata.html) action with the `datastoreId` and `imageSetId` parameter values to retrieve associated metadata values for `studyInstanceUID`, `seriesInstanceUID`, and `sopInstanceUID`. For more information, see [Getting image set metadata](get-image-set-metadata.md).

1. Construct a URL for the request using the values for `datastoreId`, `studyInstanceUID`, `seriesInstanceUID`, `sopInstanceUID`, and `imageSetId`. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastore-id/studies/study-instance-uid/series/series-instance-uid/instances/sop-instance-uid/metadata?imageSetId=image-set-id
   ```

1. Prepare and send your request. `GetDICOMInstanceMetadata` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following code example uses the `curl` command line tool to get DICOM instance metadata (`.json` file) from HealthImaging.

------
#### [ Shell ]

   ```
   curl --request GET \
     'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/instances/1.3.6.1.4.1.5962.1.1.4.1.1.20040826186059.5457/metadata?imageSetId=459e50687f121185f747b67bb60d1bc8' \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom+json'
   ```

------
**Note**  
The Transfer Syntax UID indicated in the metadata matches the Stored Transfer Syntax UID (`StoredTransferSyntaxUID`) in HealthImaging.

# Getting DICOM series metadata from HealthImaging
<a name="dicomweb-retrieve-series-metadata"></a>

Use the `GetDICOMSeriesMetadata` action to retrieve the metadata for a DICOM series (`.json` file) from a HealthImaging [data store](getting-started-concepts.md#concept-data-store). You can retrieve series metadata for any primary [ image set](getting-started-concepts.md#concept-image-set) in the HealthImaging data store by specifying the Study and Series UIDs associated with the resource. You can retrieve series metadata for non-Primary image sets by providing the image set ID as a query parameter. The series metadata is returned in `DICOM JSON` format.

**To get DICOM series metadata (`.json`)**  


1. Collect HealthImaging `datastoreId` and `imageSetId` parameter values.

1. Construct a URL for the request using the values for `datastoreId`, `studyInstanceUID`, `seriesInstanceUID`, and optionally `imageSetId`. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastore-id/studies/study-instance-uid/series/series-instance-uid/metadata
   ```

1. Prepare and send your request. `GetDICOMSeriesMetadata` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following code example uses the `curl` command line tool to get metadata (`.json` file) from HealthImaging.

------
#### [ Shell ]

   ```
   curl --request GET \
    'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/metadata \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom+json' \
     --output 'series-metadata.json'
   ```

------

   With the optional `imageSetId` parameter. 

------
#### [ Shell ]

   ```
   curl --request GET \
     'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/metadata?imageSetId=459e50687f121185f747b67bb60d1bc8' \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom+json' \
     --output 'series-metadata.json'
   ```

------
**Note**  
The `imageSetId` parameter is required to retrieve series metadata for non-primary image sets. The `GetDICOMInstanceMetadata` action will only return series metadata for primary image sets if the `datastoreId`, `studyInstanceUID`, `seriesInstanceUID` are specified (without an `imagesetID`).

# Getting DICOM instance frames from HealthImaging
<a name="dicomweb-retrieve-instance-frames"></a>

Use the `GetDICOMInstanceFrames` action to retrieve single or batch image frames (`multipart` request) from a DICOM instance in a HealthImaging [data store](getting-started-concepts.md#concept-data-store) by specifying the Series UID, Study UID, Instance UIDs, and frame numbers associated with a resource. You can specify the [image set](getting-started-concepts.md#concept-image-set) from which instance frames should be retrieved by providing the image set ID as a query parameter. The API will only return instance frames from primary image sets unless the optional [image set](getting-started-concepts.md#concept-image-set) parameter is provided. You can retrieve any instance frame (from primary or non-primary image sets) in the data store by specifying the `imageSetId` as a query parameter. 

DICOM data can be retrieved in either its stored transfer syntax or as uncompressed (ELE) format.

**To get DICOM instance frames (`multipart`)**  


1. Collect HealthImaging `datastoreId` and `imageSetId` parameter values.

1. Use the [https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_GetImageSetMetadata.html](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_GetImageSetMetadata.html) action with the `datastoreId` and `imageSetId` parameter values to retrieve associated metadata values for `studyInstanceUID`, `seriesInstanceUID`, and `sopInstanceUID`. For more information, see [Getting image set metadata](get-image-set-metadata.md).

1. Determine the image frames to retrieve from the associated metadata to form the `frameList` parameter. The `frameList` parameter is a comma-separated list of one or more non-duplicate frame numbers, in any order. For example, the first image frame in the metadata will be frame 1.
   + Single-frame request: `/frames/1`
   + Multi-frame request: `/frames/1,2,3,4`

1. Construct a URL for the request using the values for `datastoreId`, `studyInstanceUID`, `seriesInstanceUID`, `sopInstanceUID`, `imageSetId`, and `frameList`. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastore-id/studies/study-instance-uid/series/series-instance-uid/instances/sop-instance-uid/frames/1?imageSetId=image-set-id
   ```

1. Prepare and send your request. `GetDICOMInstanceFrames` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following code example uses the `curl` command line tool to get image frames in a `multipart` response from HealthImaging.

------
#### [ Shell ]

   ```
   curl --request GET \
     'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/instances/1.3.6.1.4.1.5962.1.1.4.1.1.20040826186059.5457/frames/1?imageSetId=459e50687f121185f747b67bb60d1bc8' \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: multipart/related; type=application/octet-stream; transfer-syntax=1.2.840.10008.1.2.1'
   ```

------
**Note**  
The `transfer-syntax` UID is optional and defaults to Explicit VR Little Endian if not included. If transcoding to ELE is not feasible (due to import with warning) then pixels will be returned without transcoding. Supported transfer syntaxes include:  
Explicit VR Little Endian (ELE) - `1.2.840.10008.1.2.1` (default for lossless image frames)
If `transfer-syntax=*` then the image frame(s) will be returned in the stored transfer syntax.
High-Throughput JPEG 2000 with RPCL Options Image Compression (Lossless Only) - `1.2.840.10008.1.2.4.202` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.202`
JPEG 2000 Lossless - `1.2.840.10008.1.2.4.90` - if the instance is stored in HealthImaging as lossless.
JPEG Baseline (Process 1): Default Transfer Syntax for Lossy JPEG 8-bit Image Compression - `1.2.840.10008.1.2.4.50` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.50`
JPEG 2000 Image Compression - `1.2.840.10008.1.2.4.91` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.91`
High-Throughput JPEG 2000 Image Compression - `1.2.840.10008.1.2.4.203` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.203`
JPEG XL Image Compression - `1.2.840.10008.1.2.4.112` - if the instance is stored in HealthImaging as `1.2.840.10008.1.2.4.112`
Instances stored in HealthImaging with one or more image frames encoded in the MPEG family of [Transfer Syntaxes](supported-transfer-syntaxes.md) (which includes MPEG2, MPEG-4 AVC/H.264 and HEVC/H.265) may be retrieved with the corresponding transfer-syntax UID. For example, `1.2.840.10008.1.2.4.100` if the instance is stored as MPEG2 Main Profile Main Level.
You may receive a 406 `NotAcceptableException` if the requested transfer syntax cannot be returned based on the stored transfer syntax, or if there are specific processing warnings for the instance. If this occurs, retry the call with `transfer-syntax=*`.

   For more information, see [Supported transfer syntaxes](supported-transfer-syntaxes.md) and [Image frame decoding libraries for AWS HealthImaging](reference-libraries.md).

# Getting DICOM bulkdata from HealthImaging
<a name="dicom-retrieve-bulkdata"></a>

Use the `GetDICOMBulkdata` action to retrieve binary data that has been separated from DICOM metadata in a HealthImaging data store. When retrieving instance or series metadata, binary attributes larger than 1MB will be represented by a `BulkDataURI` instead of inline values. You can retrieve the binary data for any primary image set in the HealthImaging data store by using the `BulkDataURI` provided in the metadata response. You can retrieve bulkdata for non-Primary image sets by providing the image set ID as a query parameter.

**To get DICOM bulkdata**  


When you retrieve DICOM metadata from a HealthImaging DICOMweb WADO-RS action, such as `GetDICOMInstanceMetadata` or `GetDICOMSeriesMetadata`, large binary attributes will be replaced in-line with BulkDataURIs, as shown below:

```
"00451026": {
    "vr": "UN",
    "BulkDataURI": "https://dicom-medical-imaging.us-west-2.amazonaws.com/datastore/<datastoreId>/studies/<StudyInstanceUID>/series/<SeriesInstanceUID>/instances/<SOPInstanceUID>/bulkdata/<bulkdataUriHash>"
}
```

To retrieve a DICOM element with the `GetDICOMBulkdata` action, use the following steps.

1. Construct a URL for the request using the values from the `BulkDataURI`, of the form:

   ```
   https://dicom-medical-imaging.region.amazonaws.com/datastore/datastore-id/studies/study-instance-uid/series/series-instance-uid/instances/sop-instance-uid/bulkdata/bulkdata-uri-hash
   ```

1. Issue your `GetDICOMBulkdata` command as an HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following code example uses the `curl` command line tool to retrieve a DICOM element from a primary image set:

   ```
   curl --request GET \
     'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/instances/1.2.840.10008.5.1.4.1.1.7/bulkdata/b026324c6904b2a9cb4b88d6d61c81d1' \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/octet-stream' \
     --output 'bulkdata.bin'
   ```

   To retrieve a DICOM data element from a non-primary image set, supply an `ImageSetId` parameter:

   ```
   curl --request GET \
     'https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/d9a2a515ab294163a2d2f4069eed584c/studies/1.3.6.1.4.1.5962.1.2.4.20040826285059.5457/series/1.3.6.1.4.1.5962.1.3.4.1.20040825185059.5457/instances/1.2.840.10008.5.1.4.1.1.7/bulkdata/b026324c6904b2a9cb4b88d6d61c81d1?imageSetId=459e50687f121185f747b67bb60d1bc8' \
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/octet-stream' \
     --output 'bulkdata.bin'
   ```

**Note**  
The `imageSetId` parameter is required to retrieve bulkdata for non-primary image sets. The GetDICOMBulkdata action will only return bulkdata for primary image sets if the `datastoreId`, `studyInstanceUID`, `seriesInstanceUID`, and `SOPInstanceUID` are specified (without an `imagesetID`).

# Searching DICOM data in HealthImaging
<a name="dicomweb-search"></a>

AWS HealthImaging offers representations of [ DICOMweb QIDO-RS](https://www.dicomstandard.org/using/dicomweb/query-qido-rs) APIs to search for studies, series, and instances by Patient ID, and receive their unique identifiers for further usage. HealthImaging's DICOMweb QIDO-RS APIs offer flexibility in how you search for data stored in HealthImaging and provide interoperability with legacy applications.

**Important**  
HealthImaging's DICOMweb APIs can be used to return image set information with QIDO-RS. HealthImaging DICOMweb APIs reference only [image sets](getting-started-concepts.md#concept-image-set) unless otherwise noted. Use HealthImaging [cloud native actions,](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_Operations.html) or the optional image set parameter of DICOMweb actions to retrieve non-primary image sets. HealthImaging's DICOMweb APIs can be used to return image set information with DICOMweb-conformant responses.  
 HealthImaging DICOMweb QIDO-RS actions can return a maximum of 10,000 records. In the case that more than 10,000 resources exist, they will not be retrievable via the QIDO-RS actions, but may be retrieved via DICOMweb WADO-RS actions or [cloud native actions](https://docs.aws.amazon.com/healthimaging/latest/APIReference/API_Operations.html).   
The APIs listed in this section are built in conformance to the DICOMweb (QIDO-RS) standard for web-based medical imaging. They are not offered through AWS CLI and AWS SDKs.

## DICOMweb search APIs for HealthImaging
<a name="dicomweb-search-services"></a>

The following table describes all HealthImaging representations of DICOMweb QIDO-RS APIs available for searching data in HealthImaging.


**HealthImaging representations of DICOMweb QIDO-RS APIs**  

| Name | Description | 
| --- | --- | 
| SearchDICOMStudies | Search for DICOM studies in HealthImaging by specifying search query elements using a GET request. Study search results are returned in JSON format, ordered by last update, date descending (latest to oldest). See [Search for studies](dicomweb-search-studies.md). | 
| SearchDICOMSeries | Search for DICOM series in HealthImaging by specifying search query elements using a GET request. Series search results are returned in JSON format, ordered by Series Number (0020, 0011) in ascending order (oldest to latest). See [Search for series](dicomweb-search-series.md). | 
| SearchDICOMInstances | Search for DICOM instances in HealthImaging by specifying search query elements using a GET request. Instance search results are returned in JSON format, ordered by Instance Number (0020, 0013) in ascending order (oldest to latest). See [Search for instances](dicomweb-search-instances.md). | 

## Supported DICOMweb query types for HealthImaging
<a name="dicomweb-query-types"></a>

HealthImaging supports QIDO-RS hierarchical resource queries at the Study, Series, and SOP Instance levels. When using QIDO-RS hierarchical search for HealthImaging:
+ Searching for studies returns a list of Studies
+ Searching for a Study’s Series requires a known `StudyInstanceUID` and returns a list of Series
+ Searching a list of Instances requires a known `StudyInstanceUID` and `SeriesInstanceUID`

The following table describes supported QIDO-RS hierarchical query types for searching data in HealthImaging.


**HealthImaging supported QIDO-RS query types**  

| Query type | Example | 
| --- | --- | 
| Attribute value queries | Search for all series in a Study where `modality=CT`. `.../studies/1.3.6.1.4.1.14519.5.2.1.6279.6001.101370605276577556143013894866/series?00080060=CT` Search all studies where patient ID and study date are these values, respectively. `…/studies?PatientID=11235813&StudyDate=20130509` | 
| Keyword queries | Search all series using the `SeriesInstanceUID` keyword. `.../studies/1.3.6.1.4.1.14519.5.2.1.6279.6001.101370605276577556143013894866/series?SeriesInstanceUID=1.3.6.1.4.1.14519.5.2.1.6279.6001.101370605276577556143013894868` | 
| Tag queries | Search for tags using query parameters passed in group/element form. \$1group\$1\$1element\$1 like 0020000D | 
| Range queries |  `...?Modality=CT&StudyDate=AABBYYYY-BBCCYYYY`  | 
| Result paging with limit and offset | `.../studies?limit=1&offset=0&00080020=20000101` You can use the limit and offset parameters to paginate search responses. The default value of limit is 1000, and see [AWS HealthImaging endpoints and quotas](endpoints-quotas.md) for the maximum value. Max limit = 1000, Max offset = 9000  | 
| Wildcard queries |  Wildcard queries provide more flexibility on search using "\$1" and "?". "\$1" matches any sequence of characters (including a zero length value) and "?" matches any single character. Search for all studies in a datastore where StudyDescription contains "Nuclear": `.../studies?StudyDescription=*Nuclear*` Search for all studies where StudyDescription ends with "Nuclear": `.../studies?StudyDescription=*Nuclear` Search for all studies where StudyDescription starts with "Nuclear": `.../studies?StudyDescription=Nuclear*` Search for all studies where PatientID has exactly any 3 characters after 200965981: `.../studies?PatientID=200965981???`  | 
| FuzzyMatching queries |  Enable fuzzy matching on name DICOM attributes (PatientName (0010,0010), ReferringPhysicianName(0008,0090)) by adding the fuzzymatching optional query parameter: `.../studies?fuzzymatching=true&PatientName="Thomas^Albert"` This query performs case-insensitive prefix word matching on any part of the PatientName value. It returns results with PatientName values like "thomas", "Albert", "Thomas Albert", "Thomas^Albert", but not "hom" or "ber".  | 

**Topics**
+ [DICOMweb search APIs for HealthImaging](#dicomweb-search-services)
+ [Supported DICOMweb query types for HealthImaging](#dicomweb-query-types)
+ [Searching for DICOM studies in HealthImaging](dicomweb-search-studies.md)
+ [Searching for DICOM series in HealthImaging](dicomweb-search-series.md)
+ [Searching for DICOM instances in HealthImaging](dicomweb-search-instances.md)

# Searching for DICOM studies in HealthImaging
<a name="dicomweb-search-studies"></a>

Use the `SearchDICOMStudies` API to search for DICOM studies in a HealthImaging [data store](getting-started-concepts.md#concept-data-store). You can search for DICOM studies in HealthImaging by constructing a URL that includes supported DICOM data elements (attributes). Study search results are returned in JSON format, ordered by last update, date descending (latest to oldest).

**To search for DICOM studies**  


1. Collect HealthImaging `region` and `datastoreId` values. For more information, see [Getting data store properties](get-data-store.md).

1. Construct a URL for the request, including all applicable Study elements. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastoreId/studies[?query]
   ```  
**Study elements for `SearchDICOMStudies`**    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/healthimaging/latest/devguide/dicomweb-search-studies.html)

1. Prepare and send your request. `SearchDICOMStudies` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following example uses the `curl` command line tool to search for information about DICOM studies.

------
#### [ curl ]

   ```
   curl --request GET \
     "https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/datastoreId/studies[?query]"
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom+json' \
     --output results.json
   ```

   Study search results are returned in JSON format, ordered by last update, date descending (latest to oldest).

------

# Searching for DICOM series in HealthImaging
<a name="dicomweb-search-series"></a>

Use the `SearchDICOMSeries` API to search for DICOM series in a HealthImaging [data store](getting-started-concepts.md#concept-data-store). You can search for DICOM series in HealthImaging by constructing a URL that includes supported DICOM data elements (attributes). Series search results are returned in JSON format, ordered by ascending (oldest to latest).

**To search for DICOM series**  


1. Collect HealthImaging `region` and `datastoreId` values. For more information, see [Getting data store properties](get-data-store.md).

1. Collect the `StudyInstanceUID` value. For more information, see [Getting image set metadata](get-image-set-metadata.md).

1. Construct a URL for the request, including all applicable Series elements. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastoreId/studies/StudyInstanceUID/series[?query]
   ```  
**Series elements for `SearchDICOMSeries`**    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/healthimaging/latest/devguide/dicomweb-search-series.html)

1. Prepare and send your request. `SearchDICOMSeries` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following example uses the `curl` command line tool to search for DICOM series information.

------
#### [ curl ]

   ```
   curl --request GET \
     "https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/datastoreId/studies/StudyInstanceUID/series[?query]"
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom+json' \
     --output results.json
   ```

   Series search results are returned in JSON format, ordered by `Series Number (0020,0011)` in ascending order (oldest to latest).

------

# Searching for DICOM instances in HealthImaging
<a name="dicomweb-search-instances"></a>

Use the `SearchDICOMInstances` API to search for DICOM instances in a HealthImaging [data store](getting-started-concepts.md#concept-data-store). You can search for DICOM instances in HealthImaging by constructing a URL that includes supported DICOM data elements (attributes). The Instance results are returned in JSON format, ordered by ascending (oldest to latest).

**To search for DICOM instances**  


1. Collect HealthImaging `region` and `datastoreId` values. For more information, see [Getting data store properties](get-data-store.md).

1. Collect values for `StudyInstanceUID` and `SeriesInstanceUID`. For more information, see [Getting image set metadata](get-image-set-metadata.md).

1. Construct a URL for the request, including all applicable search elements. To view the entire URL path in the following example, scroll over the **Copy** button. The URL is of the form:

   ```
   GET https://dicom-medical-imaging.region.amazonaws.com/datastore/datastoreId/studies/StudyInstanceUID/series/SeriesInstanceUID/instances[?query]
   ```  
**Instance elements for `SearchDICOMInstances`**    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/healthimaging/latest/devguide/dicomweb-search-instances.html)

   HealthImaging uses the DICOM element [(0008,1196)](https://dicom.nema.org/dicom/2013/output/chtml/part18/sect_6.6.html#sect_6.6.1.3.2.1.1) to persist import warning codes. The import warning codes are searchable at the instance level. Import warning codes may be searched with wildcard or specific warning codes. See [HealthImaging Warning Codes](reference-warning-codes.md).

1. Prepare and send your request. `SearchDICOMInstances` uses a HTTP GET request with [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html) signing protocol. The following example uses the `curl` command line tool to search for information about DICOM instances.

------
#### [ curl ]

   ```
   curl --request GET \
     "https://dicom-medical-imaging.us-east-1.amazonaws.com/datastore/datastoreId/studies/StudyInstanceUID/series/SeriesInstanceUID/instances[?query]"
     --aws-sigv4 'aws:amz:us-east-1:medical-imaging' \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --header "x-amz-security-token:$AWS_SESSION_TOKEN" \
     --header 'Accept: application/dicom+json' \
     --output results.json
   ```

   Instance search results are returned in JSON format, ordered by `Instance Number (0020,0013)` in ascending order (oldest to latest)

------

# OIDC authentication for DICOMweb APIs
<a name="dicomweb-oidc"></a>

AWS HealthImaging supports [OAuth 2.0](https://oauth.net/2/)-based authentication for DICOMweb API requests using [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html), in addition to the existing [AWS Signature Version 4 (SigV4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) authentication. OIDC enables you to integrate HealthImaging directly with external identity providers (IdPs) and enables you to provide standards-based applications access to your medical imaging data through HealthImaging DICOMweb endpoints without requiring each application to have AWS credentials.

**Topics**
+ [Custom Token Verification with Lambda Authorizers](dicomweb-oidc-how.md)
+ [Set up an AWS Lambda authorizer for OIDC authentication](dicomweb-oidc-requirements.md)

# Custom Token Verification with Lambda Authorizers
<a name="dicomweb-oidc-how"></a>

HealthImaging implements OIDC support through an architecture that uses Lambda authorizers, allowing customers to implement their own token verification logic. This design gives you flexible control over how tokens are validated and how access decisions are enforced, accommodating for a diverse landscape of OIDC-compatible Identity Providers (IdPs) and varying token verification methods.

## Authentication Flow
<a name="dicomweb-oidc-authentication-flow"></a>

Here's how the authentication works at a high level:

1. **Client calls the DICOMweb API:** Your application authenticates with your chosen OIDC identity provider and receives a signed ID token (JWT). For each DICOMweb HTTP request, the client must include the OIDC access token in the Authorization header (typically a Bearer token). Before the request reaches your data, HealthImaging extracts this token from the incoming request and calls a Lambda authorizer that you configure.

   1. The header typically follows the format: `Authorization: Bearer <token>`.

1. **Initial verification:** HealthImaging verifies access token claims in order to quickly reject any obviously invalid or expired tokens without invoking the Lambda function unnecessarily. HealthImaging performs an initial verification of certain standard claims in the access token before invoking the Lambda authorizer:

   1. `iat` (Issued At): HealthImaging checks if the token's issue time is within acceptable limits.

   1. `exp` (Expiration Time): HealthImaging verifies that the token has not expired.

   1. `nbf` (Not Before Time): If present, HealthImaging ensures the token is not being used before its valid start time.

1. **HealthImaging invokes a Lambda authorizer:** If the initial claim verification passes, HealthImaging then delegates further token verification to the customer-configured Lambda authorizer function. HealthImaging passes the extracted token and other relevant request information to the Lambda function. The Lambda function verifies the token's signature and claims.

1. **Verify with an identity provider:** The Lambda contains custom code that checks the ID token signature, performs more extensive token verification (e.g., issuer, audience, custom claims), and validates those claims against the IdP when necessary.

1. **Authorizer returns an access policy:** After successful verification, the Lambda function determines the appropriate permissions for the authenticated use. The Lambda authorizer then returns the amazon resource name (ARN) of an IAM role that represents the set of permissions to be granted.

1. **Request execution:** If the assumed IAM role has the necessary permissions, HealthImaging proceeds with returning the requested DICOMWeb resource. If the permissions are insufficient, HealthImaging denies the request and returns an appropriate error response error (i.e., 403 Forbidden).

**Note**  
The authorizer lambda function it not managed by AWS HealthImaging service. It executes in your AWS account. Customers are charged for the function invocation and execution time separately then their HealthImaging charges.

## Architecture Overview
<a name="dicomweb-oidc-architecture-overview"></a>

![\[Diagram showing workflow: Client sends token, Lambda authorizer validates, HealthImaging processes request\]](http://docs.aws.amazon.com/healthimaging/latest/devguide/images/security-oidc-workflow-lambda.png)


## Prerequisites
<a name="dicomweb-oidc-prerequisites"></a>

### Access Token Requirements
<a name="dicomweb-oidc-token-requirements"></a>

HealthImaging requires the access token to be in JSON Web Token (JWT) format. Many Identity Providers (IDPs) offer this token format natively, while others allow you to select or configure the access token form. Ensure your chosen IDP can issue JWT tokens before proceeding with the integration.

Token Format  
The access token must be in JWT (JSON Web Token) format

Required Claims    
`exp` (Expiration Time)  
Required claim that specifies when the token becomes invalid.  
+ Must be after the current time in UTC
+ Represents when the token becomes invalid  
`iat` (Issued At)  
Required claim that specifies when the token was issued.  
+ Must be before the current time in UTC
+ Must NOT be earlier than 12 hours before the current time in UTC
+ This effectively enforces a maximum token lifetime of 12 hours  
`nbf` (Not Before Time)  
Optional claim that specifies the earliest time the token can be used.  
+ If present, will be evaluated by HealthImaging
+ Specifies the time before which the token must not be accepted

### Lambda authorizer response time requirements
<a name="dicomweb-oidc-lambda"></a>

HealthImaging enforces strict timing requirements for Lambda authorizer responses to ensure optimal API performance. Your Lambda function **must** return within 1 second.

## Best practices
<a name="dicomweb-oidc-best-practices"></a>

### Optimize Token Verification
<a name="dicomweb-oidc-optimization"></a>
+ Cache JWKS (JSON Web Key Sets) when possible
+ Cache valid access tokens when possible
+ Minimize network calls to your Identity Provider
+ Implement efficient token validation logic

### Lambda Configuration
<a name="dicomweb-oidc-lambda-configuration"></a>
+ Python and Node.js based functions typically initialize faster
+ Reduce the amount of external libraries to load
+ Configure appropriate memory allocation to ensure consistent performance
+ Monitor execution times using CloudWatch metrics

## OIDC Authentication Enablement
<a name="dicomweb-oidc-enablement"></a>
+ OIDC authentication can **only** be enabled when creating a **new** datastore
+ Enabling OIDC for existing datastores is not supported through the API
+ To enable OIDC on an existing datastore, customers must contact AWS Support

# Set up an AWS Lambda authorizer for OIDC authentication
<a name="dicomweb-oidc-requirements"></a>

This guide assumes you have already configured your Identity Provider (IdP) of choice to provide access tokens compatible with the requirements of the HealthImaging OIDC authentication feature.

## 1. Configure IAM Roles for DICOMWeb API Access
<a name="dicomweb-oidc-iam-roles"></a>

Before configuring the Lambda authorizer, create IAM roles for HealthImaging to assume when processing DICOMWeb API requests. The authorizer Lambda function returns one of these roles ARN after successful token verification, allowing HealthImaging to execute the requests with appropriate permissions.

1. Create IAM policies defining the desired DICOMWeb API privileges. Refer to the "[Using DICOMweb](https://docs.aws.amazon.com/healthimaging/latest/devguide/using-dicomweb.html)" section of the HealthImaging documentation for available permissions.

1. Create IAM roles that:
   + Attach these policies
   + Include a trust relationship allowing the AWS HealthImaging service principal (`medical-imaging.amazonaws.com`) to assume these roles.

Here is an example of a policy allowing associated roles to access to HealthImaging DICOMWeb read-only API:

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "MedicalImagingDicomWebOperations",
            "Effect": "Allow",
            "Action": [
                "medical-imaging:SearchDICOMInstances",
                "medical-imaging:GetImageSetMetadata",
                "medical-imaging:GetDICOMSeriesMetadata",
                "medical-imaging:SearchDICOMStudies",
                "medical-imaging:GetDICOMBulkdata",
                "medical-imaging:SearchDICOMSeries",
                "medical-imaging:GetDICOMInstanceMetadata",
                "medical-imaging:GetDICOMInstance",
                "medical-imaging:GetDICOMInstanceFrames"
            ],
            "Resource": "arn:aws:medical-imaging:us-east-1:123456789012:datastore/datastore-123"
        }
    ]
}
```

------

Here is an example of the trust relationship policy that should be associated to the role(s):

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "OIDCRoleFederation",
            "Effect": "Allow",
            "Principal": {
                "Service": "medical-imaging.amazonaws.com"
        },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

------

The Lambda authorizer you'll create in the next step can evaluate the token claims and return the ARN of the appropriate role. AWS HealthImaging will then impersonate this role to execute the DICOMWeb API request with the corresponding permissions.

For example:
+ A token with "admin" claims might return an ARN for a role with full access
+ A token with "reader" claims might return an ARN for a role with read-only access
+ A token with "department\$1A" claims might return an ARN for a role specific to that department's access level

This mechanism allows you to map your IdP's authorization model to specific AWS HealthImaging permissions through IAM roles.

## 2. Create and Configure Lambda Authorizer Function
<a name="dicomweb-oidc-configure-lambda"></a>

Create a Lambda function that will verify the JWT token and return the appropriate IAM role ARN based on the token claims evaluation. This function is invoked by the health imaging service and passed an event that contains the HealthImaging datastore Id, the DICOMWeb operation, and the access token found in the HTTP request:

```
{
  "datastoreId": "{datastore id}",
  "operation": "{Healthimaging API name e.g. GetDICOMInstance}",
  "bearerToken": "{access token}"
}
```

The Lambda authorizer function must return a JSON response with the following structure:

```
{
  "isTokenValid": {true or false},
  "roleArn": "{role arn or empty string meaning to deny the request explicitly}"
}
```

You can refer to the implementation example for more information.

**Note**  
Because the DICOMWeb request is only answered after the access token is verified by the lambda authorizer, it is important that the execution of this function be as fast as possible to provide with the best DICOMWeb API response time.

For the HealthImaging service to be authorized to invoke the lambda authorizer function, it must have a resource policy that allows HealthImaging service to invoke it. This resource policy can be created in the permission menu of the lambda configuration tab or Using AWS CLI:

```
aws lambda add-permission \
    --function-name YourAuthorizerFunctionName \
    --statement-id HealthImagingInvoke \
    --action lambda:InvokeFunction \
    --principal medical-imaging.amazonaws.com
```

This resource policy allows the HealthImaging service to invoke your Lambda authorizer when authenticating DICOMWeb API requests.

**Note**  
The lambda resource policy can be updated later on with an "ArnLike" condition matching the ARN of a specific HealthImaging datastore.

Here is an example of lambda resource policy:

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Id": "default",
  "Statement": [
    {
      "Sid": "LambaAuthorizer-HealthImagingInvokePermission",
      "Effect": "Allow",
      "Principal": {
        "Service": "medical-imaging.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:us-east-1:123456789012::function:{LambdaAuthorizerFunctionName}",
      "Condition": {
        "ArnLike": {
          "AWS:SourceArn": "arn:aws:medical-imaging:us-east-1:123456789012:datastore/datastore-123"
        }
      }
    }
  ]
}
```

------

## 3. Create a New Datastore with OIDC Authentication
<a name="dicomweb-oidc-datastore"></a>

To enable OIDC authentication, you must create a new datastore using the AWS CLI with the parameter "lambda-authorizer-arn". OIDC Authentication cannot be enabled on existing datastores without contacting AWS Support.

Here's an example of how to create a new datastore with OIDC authentication enabled:

```
aws medical-imaging create-datastore \
    --datastore-name YourDatastoreName \
    --lambda-authorizer-arn YourAuthorizerFunctionArn
```

You can check if a specific datastore has OIDC authentication feature enabled by using the AWS CLI get-datastore command, and verifying if the attribute "lambdaAuthorizerArn" is present:

```
aws medical-imaging get-datastore --datastore-id YourDatastoreId
```

```
{
    "datastoreProperties": {
        "datastoreId": YourdatastoreId,
        "datastoreName": YourDatastoreName,
        "datastoreStatus": "ACTIVE",
        "lambdaAuthorizerArn": YourAuthorizerFunctionArn,
        "datastoreArn": YourDatastoreArn,
        "createdAt": "2025-09-30T14:16:04.015000-05:00",
        "updatedAt": "2025-09-30T14:16:04.015000-05:00"
    }
}
```

**Note**  
The execution role for the AWS CLI datastore creation command must have appropriate permissions to invoke the Lambda authorizer function. This mitigates privilege escalation attacks where malicious users could execute unauthorized Lambda functions through the datastore authorizer configuration.

## Exception Codes
<a name="dicomweb-oidc-exceptions"></a>

In case of authentication failure HealthImaging returns the following HTTP error response codes and body messages:


| Condition | AHI response | 
| --- | --- | 
| Lambda Authorizer does not exist or is invalid | 424 Authorizer Misconfiguration | 
| Authorizer terminated due to execution failure | 424 Authorizer Failed | 
| Any other unmapped authorizer error | 424 Authorizer Failed | 
| Authorizer returned invalid/ill-formed response | 424 Authorizer Misconfiguration | 
| Authorizer ran more than 1s | 408 Authorizer Timeout | 
| Token is expired or otherwise invalid | 403 Invalid or Expired Token | 
| AHI can't federate the returned IAM Role due to authorizer misconfiguration | 424 Authorizer Misconfiguration | 
| Authorizer returned an empty Role | 403 Access Denied | 
| Returned Role is not callable (assume-role/trust misconfig) | 424 Authorizer Misconfiguration | 
| Request rate exceeds DICOMweb Gateway limits | 429 Too many requests | 
| Datastore, Return Role, or Authorizer Cross Account/Cross Region | 424 Authorizer Cross Account/Cross Region Access | 

## Implementation Example
<a name="dicomweb-oidc-implementation"></a>

This Python example demonstrates a lambda authorizer function that verifies AWS Cognito access tokens from HealthImaging events and returns an IAM role ARN with appropriate DICOMWeb privileges.

The Lambda authorizer implements two caching mechanisms to reduce external calls and response latency. The JWKS (JSON Web Key Set) is fetched once every hour and stored in the function's temporary folder, allowing subsequent function invocations to read it locally instead of fetching from the public network. You will also notice that a token\$1cache dictionary object is instantiated in the global context of this Lambda function. Global variables are shared by all invocations that reuse the same warmed Lambda context. Thanks to this, successfully verified tokens can be stored in this dictionary and looked up quickly during the next execution of this same Lambda function. The caching method represents a generalist approach that could fit access tokens issued from most identity providers. For an AWS Cognito specific caching option, refer to [Managing User pool](https://docs.aws.amazon.com/cognito/latest/developerguide/managing-users.html) section and [caching section](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-caching-tokens.html) of [AWS Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html).

```
import json
import os
import time
import logging
from jose import jwk, jwt
from jose.exceptions import ExpiredSignatureError, JWTClaimsError, JWTError
import requests
import tempfile

# Configure logging
logger = logging.getLogger()
log_level = os.environ.get('LOG_LEVEL', 'WARNING').upper()
logger.setLevel(getattr(logging, log_level, logging.WARNING))

# Global token cache with TTL
token_cache = {}

# JWKS cache file path
JWKS_CACHE_FILE = os.path.join(tempfile.gettempdir(), 'jwks.json')
JWKS_CACHE_TTL = 3600  # 1 hour

# Load environment variables once
USER_POOL_ID = os.environ['USER_POOL_ID']
CLIENT_ID = os.environ['CLIENT_ID']
ROLE_ARN = os.environ.get('AHIDICOMWEB_READONLY_ROLE_ARN', '')

def cleanup_expired_tokens():
    """Remove expired tokens from cache"""
    now = int(time.time())
    expired_keys = [token for token, data in token_cache.items() if now > data['cache_expiry']]
    for token in expired_keys:
        del token_cache[token]

def get_cached_jwks():
    """Get JWKS from cache file if valid, otherwise return None """
    try:
        if os.path.exists(JWKS_CACHE_FILE):
            # Check if cache file is still valid
            cache_age = time.time() - os.path.getmtime(JWKS_CACHE_FILE)
            if cache_age < JWKS_CACHE_TTL:
                with open(JWKS_CACHE_FILE, 'r') as f:
                    jwks = json.load(f)
                    logger.debug(f'Using cached JWKS (age: {int(cache_age)}s)')
                    return jwks
            else:
                logger.debug(f'JWKS cache expired (age: {int(cache_age)}s)')
    except Exception as e:
        logger.debug(f'Error reading JWKS cache: {e}')
    
    return None

def cache_jwks(jwks):
    """Cache JWKS to file"""
    try:
        with open(JWKS_CACHE_FILE, 'w') as f:
            json.dump(jwks, f)
        logger.debug('JWKS cached successfully')
    except Exception as e:
        logger.debug(f'Error caching JWKS: {e}')

def fetch_jwks(jwks_url):
    """Fetch JWKS from URL and cache it"""
    logger.debug('Fetching JWKS from URL')
    jwks = requests.get(jwks_url, timeout=10).json()
    # Convert to dict for faster lookups
    jwks['keys_by_kid'] = {key['kid']: key for key in jwks['keys']}
    cache_jwks(jwks)
    return jwks

def is_token_cached(token):
    if token not in token_cache:
        return None
    
    cached = token_cache[token]
    now = int(time.time())
    
    if now > cached['cache_expiry']:
        del token_cache[token]
        return None
    
    return cached

def cache_token(token, payload):
    now = int(time.time())
    token_exp = payload.get('exp')
    cache_expiry = min(now + 60, token_exp)  # 1 minute or token expiry, whichever is sooner
    
    token_cache[token] = {
        'payload': payload,
        'cache_expiry': cache_expiry,
        'role_arn': ROLE_ARN
    }

def handler(event, context):
    cleanup_expired_tokens() # start be removing expired tokens from the cache
    try:
        # Extract token from bearerToken or authorizationToken field
        token = event.get('bearerToken')
        if not token:
            raise Exception('No token provided')
        
        # Check cache first
        cached = is_token_cached(token)
        if cached:
            logger.debug('Token found in cache, skipping verification')
            return {
                'isTokenValid': True,
                'roleArn': cached['role_arn']
            }
        
        # Get Cognito configuration
        region = context.invoked_function_arn.split(':')[3]
        
        # Get JWKS (cached or fresh)
        jwks_url = f'https://cognito-idp.{region}.amazonaws.com/{USER_POOL_ID}/.well-known/jwks.json'
        jwks = get_cached_jwks()
        if not jwks:
            jwks = fetch_jwks(jwks_url)
        
        # Decode token header to get kid
        headers = jwt.get_unverified_headers(token)
        kid = headers['kid']
        
        # Find the correct key
        key = None
        for jwk_key in jwks['keys']:
            if jwk_key['kid'] == kid:
                key = jwk_key
                break
        
        if not key:
            # Key not found - try refreshing JWKS in case of key rotation
            logger.debug('Key not found in cached JWKS, fetching fresh JWKS')
            jwks = fetch_jwks(jwks_url)
            for jwk_key in jwks['keys']:
                if jwk_key['kid'] == kid:
                    key = jwk_key
                    break
        
        if not key:
            raise Exception('Public key not found')
        
        # Construct the public key
        public_key = jwk.construct(key)
        
        # Verify and decode the token (includes expiry validation)
        payload = jwt.decode(
            token,
            public_key,
            algorithms=['RS256'],
            audience=CLIENT_ID,
            issuer=f'https://cognito-idp.{region}.amazonaws.com/{USER_POOL_ID}'
        )
        
        logger.debug('Token validated successfully')
        logger.debug('User: %s', payload.get('username', 'unknown'))
        
        # Cache the validated token
        cache_token(token, payload)
        
        # Return authorization response
        return {
            'isTokenValid': True,
            'roleArn': ROLE_ARN
        }
        
    except ExpiredSignatureError:
        logger.debug('Token expired')
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
    except JWTClaimsError:
        logger.debug('Invalid token claims')
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
    except JWTError as e:
        logger.debug('JWT validation error: %s', e)
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
    except Exception as e:
        logger.debug('Authorization failed: %s', e)
        return {
            'isTokenValid': False,
            'roleArn': ''
        }
```