# Introduction

**Bunkerhill Consortium** is a collection of research institutions whose researchers collaborate to develop, validate, and use each other's AI algorithms.

Due to the legal framework and software platform that connects research institutions in the Consortium, researchers at these institutions can share data and algorithms with each other without hurdles (ex: IRB, technical review, data use agreements, licensing agreements).


# APIs


# Inference API

Enables a developer to access information about model inferences on studies from an institution

{% hint style="info" %}
**Tip:** [Client SDK](/software/sdks/client-sdk) implements clients that make it easier to interact with our Inferences API in various languages.
{% endhint %}

## The `Inference` object

This is an object representing a model inference on a study. It contains information about the model, the study, inputs derived from the study, and outputs estimated by running the model on the inputs derived from the study.

Inputs derived from the study correspond to DICOM data elements, such as the patient's sex, the pixel spacing of the series, or the X/Y/Z position of the image. A data element's value may apply to an entire study or only specific series or instances. For example, the patient's sex applies to the entire study, whereas the pixel spacing only applies to specific series and the X/Y/Z position only applies to specific instances.

Outputs estimated by running the model on the inputs may also apply to the entire study or only specific series or instances.

### Attributes

***

**`model`** (dictionary)

Model information associated with the inference, including its name and version.

<details>

<summary>Show child attributes</summary>

***

**`model.name`** (string)

Name of the model.

***

**`model.version`** (string)

Version of the model, using semantic versioning.

</details>

***

**`study`** (dictionary)

Study information associated with the inference, including the date and references to DICOM files.

<details>

<summary>Show child attributes</summary>

***

**`study.date`** (string)

Date of the study, in `YYYYMMDD` format.

***

**`study.files`** (array containing dictionaries)

Files belonging to the study, grouped by series in the study.

Each dictionary has 3 attributes:

* `study_instance_uid` (string): The study instance UID of the study.
* `series_instance_uid` (string): The series instance UID of the series.
* `url` (string): The pre-signed URL of the compressed file containing files corresponding to the study and series.

</details>

***

**`inputs`** (dictionary)

Inputs passed to the model for inference that are derived from the study. Inputs that are too large to be easily serializable (e.g. pixel array) are excluded.

<details>

<summary>Show child attributes</summary>

***

The keys of the dictionary are the names of the inputs.

Since inputs derived from the study correspond to DICOM data elements and a data element's value may apply to the entire study or only specific series or instances, the format of the values of the dictionary varies based on the input:

* The value of an input that applies to an entire study is a dictionary, where the key is the study instance UID of the study and the value is the value of the input.
* The value of an input that applies to specific series is a nested dictionary, where the key is the study instance UID of the study and the value is an inner dictionary, where the keys are the series instance UIDs of the series and the values are the series-specific values of the respective inputs.
* The value of an input that applies to specific instances is a doubly-nested dictionary, where the keys of the inner-most dict are the instance numbers of the instances and the values are the instance-specific values of the respective inputs.

**Examples of `inputs` objects**

```json
// Entire study
{
  "$INPUT_NAME": {
    "$STUDY_INSTANCE_UID": $VALUE
  }
}

// Series-specific
{
  "$INPUT_NAME": {
    "$STUDY_INSTANCE_UID": {
      "$SERIES_INSTANCE_UID": $VALUE
    }
  }
}

// Instance-specific
{
  "$INPUT_NAME": {
    "$STUDY_INSTANCE_UID": {
      "$SERIES_INSTANCE_UID": {
        1: $VALUE,
        2: $VALUE,
        ...
      }
    }
  }
}
```

</details>

***

**`outputs`** (dictionary)

Outputs estimated by running the model on the inputs for inference.

<details>

<summary>Show child attributes</summary>

***

The keys of the dictionary are the names of the outputs.

Since outputs estimated by a model may apply to the entire study or only specific series or instances, the format of the values of the dictionary varies based on the model and output:

* The value of an output that applies to an entire study is a dictionary, where the key is the study instance UID of the study and the value is the value of the output.
* The value of an output that applies to specific series is a nested dictionary, where the key is the study instance UID of the study and the value is an inner dictionary, where the keys are the series instance UIDs of the series and the values are the series-specific values of the respective outputs.
* The value of an output that applies to specific instances is a doubly-nested dictionary, where the keys of the inner-most dict are the instance numbers of the instances and the values are the instance-specific values of the respective outputs.

**Example of `outputs` objects**

```json
// Entire study
{
  "$OUTPUT_NAME": {
    "$STUDY_INSTANCE_UID": $VALUE
  }
}

// Series-specific
{
  "$OUTPUT_NAME": {
    "$STUDY_INSTANCE_UID": {
      "$SERIES_INSTANCE_UID": $VALUE
    }
  }
}

// Instance-specific
{
  "$OUTPUT_NAME": {
    "$STUDY_INSTANCE_UID": {
      "$SERIES_INSTANCE_UID": {
        1: $VALUE,
        2: $VALUE,
        ...
      }
    }
  }
}
```

</details>

***

### Example of an `Inference` object

```json
{
  "model": {
    "name": "predicts_zeros",
    "version": "1.0.0"
  },
  "study": {
    "date": "20230101",
    "files": [
      {
        "study_instance_uid": "$STUDY_INSTANCE_UID",
        "series_instance_uid": "$SERIES_INSTANCE_UID",
        "url": "$PRESIGNED_URL"
      },
      {
        "study_instance_uid": "$STUDY_INSTANCE_UID",
        "series_instance_uid": "$SERIES_INSTANCE_UID",
        "url": "$PRESIGNED_URL"
      }
    ]
  }
  "inputs": {
    "patient_sex": {
      "$STUDY_INSTANCE_UID": "F"
    },
    "pixel_spacing": {
      "$STUDY_INSTANCE_UID": {
        "$SERIES_INSTANCE_UID": [0.5, 0.5]
      }
    },
    "image_position_patient": {
      "$STUDY_INSTANCE_UID": {
        "$SERIES_INSTANCE_UID": {
          1: [0, 0, 0.1],
          2: [0, 0, 0.2],
          3: [0, 0, 0.3],
          ...
        }
      }
    }
  },
  "outputs": {
    "estimated_number": {
      "$STUDY_INSTANCE_UID": 0
    },
    "estimated_string": {
      "$STUDY_INSTANCE_UID": "zero"
    },
    "estimated_segmentation_array": {
      "$STUDY_INSTANCE_UID": {
        "$SERIES_INSTANCE_UID": "$PRESIGNED_URL"
      }
    }
  }
]
```


# DICOM C-STORE API

As members of the Bunkerhill Health Consortium, medical institutions can send data to Bunkerhill, where that data is stored and used to research, train, and validate AI models. Bunkerhill provides multiple ways to send this medical data from an institution:

* **Upload to Box:** data can be compressed to ZIP files and uploaded to a Box folder to which Bunkerhill has access, from which Bunkerhill will process the data.
* **C-STORE:** data can be sent via an institution’s PACS to Bunkerhill using C-STORE, where it will pass through an on-premises Edge service, from which Bunkerhill will process the data.

This document provides information on how to send data using the second method.

### Prerequisites

Before attempting to send data via C-STORE, do the following:

* Ensure that Bunkerhill’s Edge service is up and running at your institution.
* Contact a member of Bunkerhill’s support staff to communicate what data you wish to transfer. Bunkerhill staff will provide you with the following information:
  * The `Hostname` and `Port` of the Edge service to send the data to (this will be the same for all datashares within an institution).
  * A `Datashare ID`, which will be used to configure the C-STORE connection.

### Sending Data

The data should be in the form of one or more DICOM files which contain all information about your studies that you wish to share.

Establish a C-STORE association with the following parameters:

* `Hostname` and `Port` as described above (see Prerequisites).
* CALLED AE-TITLE equal to the `Datashare ID` described above.

After establishing the association, begin sending the data. Monitor for a few seconds after beginning the transfer to make sure that the files are sending properly.

Bunkerhill staff will communicate with you if any issues arise during the transfer.

### Troubleshooting

There are a few potential pitfalls to be aware of:

* Make sure that the CALLED AE-TITLE matches the `Datashare ID`. If they do not match, the association will be aborted shortly after being established.
* Some PACS systems place additional restrictions on CALLED AE-TITLE. If your PACS system does not allow for CALLED AE-TITLE to be equal to the `Datashare ID` Bunkerhill  provides, then please contact Bunkerhill staff. Bunkerhill will arrange a solution—typically either a datashare with a substitute CALLED AE-TITLE or a datashare where you will instead set CALLING AE-TITLE to be equal to `Datashare ID`.
* Bunkerhill buffers some files on disk during a datashare. If your datashare is very large, more disk may need to be allocated to the Bunkerhill Edge service in order to process it successfully.


# SDKs


# Client SDK

Clients that make it easier to interact with our APIs in various languages

{% content-ref url="/pages/YGbsb2eIcoOU470nUejh" %}
[Client SDK: Installation](/software/sdks/client-sdk/client-sdk-installation)
{% endcontent-ref %}

{% content-ref url="/pages/6H0npoVWdO70InzYnxxs" %}
[Client SDK: Usage](/software/sdks/client-sdk/client-sdk-usage)
{% endcontent-ref %}


# Client SDK: Installation

Installation instructions for Client SDK

## From source

To install from source, clone [bunkerhillhealth/bunkerhill-sdk](https://github.com/bunkerhillhealth/bunkerhill-sdk/) locally. Then follow the instructions for the desired client.

{% tabs %}
{% tab title="🐍 Python" %}

```bash
python -m pip install client_sdk/python
```

{% endtab %}

{% tab title="🟦 TS/JS" %}

```bash
cd client_sdk/javascript
npm install
npm run build
```

{% endtab %}
{% endtabs %}


# Client SDK: Usage

Usage instructions for the Client SDK

## Credentials

Authentication to the server requires a username and private key pair. Please obtain these credentials by reaching out to `engineering@bunkerhillhealth.com`. The private key should be stored securely, for example in a file named `private_key.pem`.

The credentials are exchanged with the server for a JWT access token, which is managed by the Client SDK and automatically passed with all API requests.


# Inference

Usage instructions for the Client SDK for Inference

## Quickstart

To read all inferences for a patient with MRN `$PATIENT_MRN` and model ID `$MODEL_ID` with username `$USERNAME` and private key filename `$PRIVATE_KEY_FILENAME`, run:

{% tabs %}
{% tab title="🐍 Python" %}

```python
from client_sdk import InferenceAPIClient

async with InferenceAPIClient(
    username="$USERNAME",
    private_key_filename="$PRIVATE_KEY_FILENAME",
) as client:
    inferences = await client.get_inferences(
        model_id="$MODEL_ID",
        patient_mrn="$PATIENT_MRN",
    )
```

{% endtab %}

{% tab title="🟦 TS/JS" %}

```javascript
import { InferenceAPIClient } from 'bunkerhill-inference-api/client';

const client = new InferenceAPIClient('$USERNAME', '$PRIVATE_KEY_FILENAME');

const inferences = await client.getInferences(
  '$MODEL_ID',
  '$PATIENT_MRN',
);
```

{% endtab %}
{% endtabs %}

## `InferenceAPIClient` Reference

{% tabs %}
{% tab title="🐍 Python" %}

### Constructor

#### Method signature

```python
def __init__(
    self,
    username: str,
    private_key_filename: Optional[str] = None,
    private_key_string: Optional[str] = None,
    base_url: str = 'https://api.bunkerhillhealth.com/',
) -> None:
    ...
```

#### Parameters

* `username` (`str`): The username to authenticate the client.
* `private_key_filename` (`Optional[str]`): Filename of the RSA private key.
* `private_key_string` (`Optional[str]`): The RSA private key as a string.
* `base_url` (`str`, has a default): The base URL of the Inference API. Defaults to '<https://api.bunkerhillhealth.com/>'.

**Notes**

* At least one of `private_key_filename` or `private_key_string` must be provided.

***

### `get_inferences`

Gets a list of `Inference` objects for a given patient and a given model from the Inference API. Must be called from an async context.

{% hint style="info" %}
Hint: The `get_inferences` method is asynchronous. To make use of this method from a synchronous application, calls to the `InferenceAPIClient` can be wrapped in `asyncio.run()`.
{% endhint %}

#### Method signature

<pre class="language-python"><code class="lang-python">def get_inferences(
<strong>    self,
</strong>    model_id: str,
    patient_mrn: str,
) -> List[Inference]:
    ...
</code></pre>

#### Parameters

* `model_id` (`str`): The model ID of the model.
* `patient_mrn` (`str`): The medical record number (MRN) of the patient.

#### Returns

`List[Inference]`: A list of JSON Dicts, one for each inference. See the [Inference API documentation](/software/apis/inference-api#inference-json-format) for more details on the format of these Dicts.

**Notes**

* You must have authorization to the specified `model_id`. If not, a `403` error will be raised.
* Only inferences corresponding to institutions that you are authorized to will be returned.
  {% endtab %}

{% tab title="🟦 TS/JS" %}

### Constructor

#### Method signature

```typescript
constructor(
  username: string,
  privateKeyFilename?: string,
  privateKeyString?: string,
  baseUrl: string = 'https://api.bunkerhillhealth.com/',
) {}
```

#### Parameters

* `username` (`string`): The username to authenticate the client.
* `privateKeyFilename` (`string`, optional): Filename of the RSA private key.
* `privateKeyString` (`string`, optional): The RSA private key as a string.
* `baseUrl` (`string`, has a default): The base URL of the Inference API. Defaults to '<https://api.bunkerhillhealth.com/>'.

**Notes**

* At least one of `privateKeyFilename` or `privateKeyString` must be provided.

***

### `getInferences`

Fetches a list of `Inference` objects for a given patient and a given model from the Inference API. Must be called from an async context.

#### Method signature

```typescript
async getInferences(
  modelId: string,
  patientMrn: string,
): Promise<Inference[]> {}
```

#### Parameters

* `modelId` (`str`): The model ID of the model.
* `patientMrn` (`str`): The medical record number (MRN) of the patient.

#### Returns

`Promise<Inference[]>`: A list of JSON Dicts, one for each inference. See [Inference JSON Format](/software/apis/inference-api#inference-json-format) for more details on the format of these Dicts.

**Notes**

* You must have authorization to the specified `modelId`. If not, a `403` error will be raised.
* Only inferences corresponding to institutions that you are authorized to will be returned.
  {% endtab %}
  {% endtabs %}


# Study

Usage instructions for the Client SDK for Study

## Quickstart

To write a report for a study with accession number `$ACCESSION_NUMBER` with username `$USERNAME` and private key filename `$PRIVATE_KEY_FILENAME`, run:

{% tabs %}
{% tab title="🐍 Python" %}

```python
from client_sdk import StudyAPIClient

async with StudyAPIClient(
    username="$USERNAME",
    private_key_filename="$PRIVATE_KEY_FILENAME",
) as client:
    await client.set_report(
        accession_number="$ACCESSION_NUMBER",
        report="$REPORT",
    )
```

{% endtab %}

{% tab title="🟦 TS/JS" %}

```javascript
import { StudyAPIClient } from 'bunkerhill-inference-api/client';

const client = new StudyAPIClient('$USERNAME', '$PRIVATE_KEY_FILENAME');

await client.setReport(
  '$ACCESSION_NUMBER',
  '$REPORT',
);
```

{% endtab %}
{% endtabs %}

## `StudyAPIClient` Reference

{% tabs %}
{% tab title="🐍 Python" %}

### Constructor

#### Method signature

<pre class="language-python"><code class="lang-python">def __init__(
<strong>    self,
</strong>    username: str,
    private_key_filename: Optional[str] = None,
    private_key_string: Optional[str] = None,
    base_url: str = 'https://api.bunkerhillhealth.com/',
) -> None:
    ...
</code></pre>

#### Parameters

* `username` (`str`): The username to authenticate the client.
* `private_key_filename` (`Optional[str]`): Filename of the RSA private key.
* `private_key_string` (`Optional[str]`): The RSA private key as a string.
* `base_url` (`str`, has a default): The base URL of the Inference API. Defaults to '<https://api.bunkerhillhealth.com/>'.

**Notes**

* At least one of `private_key_filename` or `private_key_string` must be provided.

***

### `set_report`

Sets a report for a `Study` object with a given accession number from the Study API. Must be called from an async context.

{% hint style="info" %}
Hint: The `set_report` method is asynchronous. To make use of this method from a synchronous application, calls to the `StudyAPIClient` can be wrapped in `asyncio.run()`.
{% endhint %}

#### Method signature

```python
def set_report(
  self,
  accession_number: str,
  report: str,
) -> None:
  ...
```

#### Parameters

* `accession_number` (`str`): The accession number of the study.
* `report` (`str`): The text of the report.

#### Returns

`None`

**Notes**

* You must have authorization to the specified `accession_number`. If not, a `403` error will be raised.
* Only studies corresponding to institutions that you are authorized to will be writable.
  {% endtab %}

{% tab title="🟦 TS/JS" %}

### Constructor

#### Method signature

```typescript
constructor(
  username: string,
  privateKeyFilename?: string,
  privateKeyString?: string,
  baseUrl: string = 'https://api.bunkerhillhealth.com/',
) {}
```

#### Parameters

* `username` (`string`): The username to authenticate the client.
* `privateKeyFilename` (`string`, optional): Filename of the RSA private key.
* `privateKeyString` (`string`, optional): The RSA private key as a string.
* `baseUrl` (`string`, has a default): The base URL of the Inference API. Defaults to '<https://api.bunkerhillhealth.com/>'.

**Notes**

* At least one of `privateKeyFilename` or `privateKeyString` must be provided.

***

### `setReport`

Sets a report for a `Study` object with a given accession number from the Study API. Must be called from an async context.

#### Method signature

```typescript
async getInferences(
  accessionNumber: string,
  report: string,
): Promise<void> {}
```

#### Parameters

* `accessionNumber` (`str`): The accession number of the study.
* `report` (`str`): The text of the report.

#### Returns

`Promise<void>`

**Notes**

* You must have authorization to the specified `accessionNumber`. If not, a `403` error will be raised.
* Only studies corresponding to institutions that you are authorized to will be accessible.
  {% endtab %}
  {% endtabs %}


