# Your first job

# 1. Submit new job

Submitting new jobs happens through the POST /job route. Note that you will need to pass the authentication header when making the call.

In the body of the HTTP POST request, you can specify how your job looks like. The most important parts are:

  • Input files: which scan modalities you are sending over and how you will transfer them to the Engine API.
  • Output files: which results you are expecting and how you will receive them from the Engine API.

In this quick start guide, we start off with a very simple job, providing a CBCT scan as input file and requesting a mesh file of the segmented mandible as output.

Our simple job body will look like this. The input file is a single-file CBCT scan in DICOM format (.dcm). The output file is a single mesh file in STL format (.stl). We specify that the mesh should be generated for the structure "mandible".

request_body.json
{
    "inputFiles": {
        "cbct": {
            "extension": ".dcm"
        }
    },
    "outputFiles": {
        "meshes": [
            {
                "extension": ".stl",
                "structures": ["mandible"]
            }
        ]
    }
}

Now that we constructed our job body, we can submit it to the Engine API through the POST /job route. In the example below, we are using the 24.06 version of the engine in the EU region: https://eu.virtualpatientengine.com/24.06/job. We will do a HTTP POST request, passing the job body along (as JSON). We will also need to set the authentication header, as explained in the authentication section.

curl --request POST \
--url https://eu.virtualpatientengine.com/24.06/job \
--header "Authorization: Bearer <YOUR_GENERATED_ACCESS_TOKEN>" \
--header "Content-Type: application/json" \
--data @./request_body.json
var axios = require('axios');
const body = require('./request_body.json');

axios
  .post('https://eu.virtualpatientengine.com/24.06/job', body, {
    headers: {
      Authorization: 'Bearer <YOUR_GENERATED_ACCESS_TOKEN>',
    },
  })
  .then((response) => {
    console.log(response.data);
  });
import http.client
import json

with open("./request_body.json", "r") as file:
    data = json.load(file)

conn = http.client.HTTPSConnection("eu.virtualpatientengine.com")

headers = {"Authorization": "Bearer <YOUR_GENERATED_ACCESS_TOKEN>"}

conn.request("POST", "/24.06/job", json.dumps(data), headers=headers)

response = conn.getresponse()
print(response.read().decode())

If everything is set up correctly, you will receive a 200 OK status response from the Engine API. The response body will contain useful information about the job in the form of a JSON object (needed for the next steps).

response_body.json
{
  "jobId": "<UNIQUE-UUID>",
  "presignedUploadUrl": {
    "cbct": {
      "presignedUploadUrl": "<UNIQUE-UPLOAD-URL>",
      "notifyFinishUploadUrl": "<UNIQUE-NOTIFICATION-URL>"
    },
  }
}

# 2. Upload your data

The job has been submitted successfully, but it will only start processing after all input data has been transferred.

  1. Upload the CBCT scan (DICOM file) to the provided pre-signed upload URL for the CBCT input file, using a HTTP PUT request.
  2. Notify the Engine API that the upload has finished, by sending a HTTP PUT request to the provided notification URL.
curl --upload-file "/dicom.dcm" "<UNIQUE-UPLOAD-URL>"
curl --request PUT \
  --url "<UNIQUE-NOTIFICATION-URL>" \
  --header "Authorization: Bearer <YOUR_GENERATED_ACCESS_TOKEN>"
var axios = require('axios');
var fs = require('fs');

// 1. Upload the CBCT scan
const filePath = './dicom.dcm';

axios
  .put('<UNIQUE-UPLOAD-URL>', fs.createReadStream(filePath), {
    headers: {
      'Content-length': fs.statSync(filePath).size,
    },
  })
  .then((response) => {
    console.log(response.status);
  });
// 2. Notify the Engine API
axios
  .put('<UNIQUE-NOTIFICATION-URL>', null, {
    headers: {
      Authorization: 'Bearer <YOUR_GENERATED_ACCESS_TOKEN>',
    },
  })
  .then((response) => {
    console.log(response.status);
  });
import requests  # We use requests since it's a bit easier to upload files

# 1. Upload the CBCT scan
with open("./dicom.dcm", "rb") as f:
    result = requests.put(
        "<UNIQUE-UPLOAD-URL>",
        data=f,
        headers={"Content-Type": "application/octet-stream"},
    )
    print(result.status_code)
# 2. Notify the Engine API
result = requests.put(
    "<UNIQUE-NOTIFICATION-URL>",
    headers={"Authorization": "Bearer <YOUR_GENERATED_ACCESS_TOKEN>"},
)
print(result.status_code)

# 3. Poll for the job status

Once all input data has been uploaded (and notified), the Engine API will start processing the job. You can periodically poll the job through the GET /pollJobResults route, passing the unique job ID (obtained from the response body when submitting the job) as a query parameter. For the Europe deployment, the full URL could look like this: https://eu.virtualpatientengine.com/24.06/pollJobResults?jobId=<UNIQUE-JOB-ID>.

curl --request GET \
  --url "https://eu.virtualpatientengine.com/24.06/pollJobResults?jobId=<UNIQUE-JOB-ID>" \
  --header "Authorization: Bearer <YOUR_GENERATED_ACCESS_TOKEN>"
var axios = require('axios');

axios
  .get('https://eu.virtualpatientengine.com/24.06/pollJobResults?jobId=<UNIQUE-JOB-ID>', {
    headers: {
      Authorization: 'Bearer <YOUR_GENERATED_ACCESS_TOKEN>',
    },
  })
  .then((response) => {
    console.log(response.status);
    console.log(response.data);
  });
import requests  # We use requests since it's a bit easier to upload files

result = requests.get(
    "https://eu.virtualpatientengine.com/24.06/pollJobResults?jobId=<UNIQUE-JOB-ID>",
    headers={"Authorization": "Bearer <YOUR_GENERATED_ACCESS_TOKEN>"},
)
print(result.status_code)
print(result.json())

As long as the job is processing, this route will return a 202 PROCESSING status code. This might take several minutes depending on the type of job and load on the servers.

As soon as the job processing has finished, the Engine API will return a 200 OK status code, together with a JSON response body. Although the body contains more information, we display a simplified version below. The most important part is the success flag, indicating that everything went well, and the outputFiles dictionary, containing the pre-signed download URLs for the output files (used in the next step).

polling_response_body_simplified.json
{
  "success": true,
  "outputFiles": {
    "meshes": [
        {
            "status": "Available",
            "size": 12345,
            "extension": ".stl",
            "structures": ["mandible"],
            "url": "<UNIQUE-DOWNLOAD-URL>",
        }
    ],
  },
}

# 4. Download the results

Now that the job has finished, you can download the results from the provided URL in the JSON response. Similar to the upload URLs, these are pre-signed, so you do not need to add any authentication headers to the request.

curl -o ./mandible.stl "<UNIQUE-DOWNLOAD-URL>"
var axios = require('axios');
var fs = require('fs');

axios
  .get('<UNIQUE-DOWNLOAD-URL>', {
    responseType: 'stream',
  })
  .then((response) => {
    response.data.pipe(fs.createWriteStream('./mandible.stl'));
  });
from urllib.request import urlretrieve

urlretrieve(
    "<UNIQUE-DOWNLOAD-URL>",
    "./mandible.stl",
)

Congratulations on getting your first mandibular bone segmented! 🎉

Segmented mandibular bone
Segmented mandibular bone