Skip to content

Deployment Setup in a Hospital setting using Xnat and Monai label

AHarouni edited this page Feb 24, 2025 · 1 revision

1. Inference Workflow at Deployment

Please refer to xnat integration wiki for monai label set up with xnat to show how monai label can help in annotation, and do interactive infer. Here, we will leverage Xnat integration with monai label as a deployment solution to run AI on demand once a hospital generates an image. We can use something like orthanc to simulate your hospital

xnat_deploy_workflow

Deployment steps are shown below

Step Hospital Xnat Monai Label
1.2 Orthanc send dicom study to xnat . .
2.1 . Xnat receives dicoms .
2.2 . dicoms are routed to a specific project .
2.3 . automatically triggers a curl call to monai label to run inference .
3.1 . . Receives request, triggers inference
3.2 . . Monai label uses common file system to immediately run inference
3.3 . . Generates nifti segmenation mask
4.1 . . Converts inference nifti to dicom-seg
4.2 . . Creates Secondary capture of segmentation
4.3 . . Creates rectangles for center slices for xnat annotation
4.4 . . Creates pdf report with key images of tumors
4.5 . . Emails Pdf report to project PIs
4.6 . Monai label push volume calucation to xnat forms .
5.1 . . Send all formats to xnat and hospital
5.2 Receives dicom-seg Receives dicom-seg .

2. Setup

This workflow required multiple changes:

  1. Creating a command for xnat using the container service.
  2. Modifying the batch infer code on monai label
  3. Changing you app to use the new batch infer code
  4. Setting up Xnat to auto trigger the workflow.

2.1. Creating a command for xnat using the container service.

On the xnat side you need to have the container service working. You can then add a new command to use a basic curl command to monai label to run inference for the given image_id. This is the most tricky part. You can use the json file below as a starting point. You must change the the host ip in the file below.

{
  "name": "curl4monailabel_study",
  "label": "curl4monailabel_study",
  "description": "Curl to monai label to run infer on full study",
  "version": "1.0",
  "schema-version": "1.0",
  "image": "curlimages/curl:latest",
  "type": "docker",
  "command-line": "curl -X 'POST' $monailabel_host':'$monailabel_port'/batch/infer/'[monailabel_model]'?scope=study&run_sync=true' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{\"image_ids\":[\"[PRJ_ID]/[SUBJ_ID]/[ACCS_ID]/[SCAN_ID]/[SESS_ID]\"] }' ",
  "override-entrypoint": true,
  "mounts": [],
  "environment-variables": {
    "monailabel_port": "8000",
    "monailabel_host": "http://<<your host ip >>"
  },
  "ports": {},
  "inputs": [
    {
      "name": "subject_id",
      "description": "The DICOM series to be sent for inference ",
      "type": "string",
      "required": true,
      "replacement-key": "[SUBJ_ID]",
      "command-line-flag": "",
      "command-line-separator": "",
      "select-values": []
    },
    {
      "name": "project_id",
      "description": "The project id for the Dicom series.",
      "type": "string",
      "required": false,
      "replacement-key": "[PRJ_ID]",
      "command-line-flag": "",
      "command-line-separator": "",
      "select-values": []
    },
    {
      "name": "session_id",
      "description": "The session id for the DICOM.",
      "type": "string",
      "required": false,
      "replacement-key": "[SESS_ID]",
      "command-line-flag": "",
      "command-line-separator": "",
      "select-values": []
    },
    {
      "name": "accession_id",
      "description": "The accession id for the DICOM.",
      "type": "string",
      "required": false,
      "replacement-key": "[ACCS_ID]",
      "command-line-flag": "",
      "command-line-separator": "",
      "select-values": []
    },
    {
      "name": "scan_id",
      "description": "Scan number for the dicom",
      "type": "string",
      "required": true,
      "replacement-key": "[SCAN_ID]",
      "command-line-flag": "",
      "command-line-separator": "",
      "select-values": []
    },
    {
      "name": "monailabel_model",
      "description": "Model name for monai label to run",
      "type": "string",
      "required": true,
      "replacement-key": "[monailabel_model]",
      "command-line-flag": "",
      "command-line-separator": "",
      "select-values": []
    }
  ],
  "outputs": [],
  "xnat": [
    {
      "name": "curl4monailabel_study",
      "description": "Monai Label Study ",
      "contexts": [
        "xnat:imageScanData"
      ],
      "external-inputs": [
        {
          "name": "scan",
          "description": "Input scan",
          "type": "Scan",
          "required": true,
          "load-children": false
        }
      ],
      "derived-inputs": [
        {
          "name": "session",
          "type": "Session",
          "required": true,
          "load-children": true,
          "derived-from-wrapper-input": "scan",
          "multiple": false
        },
        {
          "name": "project-id",
          "type": "string",
          "required": true,
          "provides-value-for-command-input": "project_id",
          "user-settable": false,
          "load-children": true,
          "derived-from-wrapper-input": "session",
          "derived-from-xnat-object-property": "project-id",
          "multiple": false
        },
        {
          "name": "session-id",
          "type": "string",
          "required": true,
          "provides-value-for-command-input": "session_id",
          "user-settable": false,
          "load-children": true,
          "derived-from-wrapper-input": "session",
          "derived-from-xnat-object-property": "label",
          "multiple": false
        },
        {
          "name": "accession-id",
          "type": "string",
          "required": true,
          "provides-value-for-command-input": "accession_id",
          "user-settable": false,
          "load-children": true,
          "derived-from-wrapper-input": "session",
          "derived-from-xnat-object-property": "id",
          "multiple": false
        },
        {
          "name": "subject-id",
          "type": "string",
          "required": true,
          "provides-value-for-command-input": "subject_id",
          "user-settable": false,
          "load-children": true,
          "derived-from-wrapper-input": "session",
          "derived-from-xnat-object-property": "subject-id",
          "multiple": false
        },
        {
          "name": "scan-resource",
          "type": "Resource",
          "matcher": "@.label == 'DICOM'",
          "required": true,
          "provides-files-for-command-mount": "scan-in",
          "load-children": true,
          "derived-from-wrapper-input": "scan",
          "multiple": false
        },
        {
          "name": "scan-id",
          "type": "string",
          "required": true,
          "provides-value-for-command-input": "scan_id",
          "user-settable": false,
          "load-children": true,
          "derived-from-wrapper-input": "scan",
          "derived-from-xnat-object-property": "id",
          "multiple": false
        }
      ],
      "output-handlers": []
    }
  ],
  "reserve-memory": 1000,
  "container-labels": {},
  "generic-resources": {},
  "ulimits": {},
  "secrets": []
}

2.2. Modifying the batch infer code on monai label

I am using the batch infer end point so the dicom seg can be saved back to xnat. For this to work you need to modify the batch infer end point in your app or monai label itself as

### create new file in your app folder as lib/tasks/batch_infer.py
class MyBatchInferTask(BatchInferTask):
    def __call__(self, request, datastore: Datastore, infer: Callable):
        image_ids = request.get("image_ids", "")
        if not image_ids: # normal as before 
            image_ids = sorted(self.get_images(request, datastore))
        else: ## image_ids is already sent from xnat 
            request["result_extension"] = ".nii.gz"
            request["multi_gpu"] = False

2.3. Changing you app to use the new batch infer code

then use this new class as you batch infer, so you need to add these lines to your app main.py file

    def init_batch_infer(self) -> Callable:
        from lib.tasks.batch_infer import MyBatchInferTask
        return MyBatchInferTask()

2.4. Setting up Xnat to auto trigger the workflow.

After you see this working you need to go to even service on xnat to add an event. on "scan created" for your project or all projects then set action to the command you just created above "curl for monai label dli"

Image