User Tools

Site Tools


dosimetry:userguide:thinknode

This is an old revision of the document!


thinknode™ Examples

These examples provide a starting point for issuing http connections and requests to the dosimetry app on the thinknode™ framework. They are provided as is, and are written in python and c++. Any further dependencies are listed along with the provided scripts.

C++

A simple C++ project that handles posting immutable objects and calculation requests to thinknode™ framework. The main() function toggles on which task to perform. Below are the defined functions of the project as well as a link to download the file in its entirety.

Dependencies:

thinknode.cpp

// Copyright (c) 2015 .decimal, Inc. All rights reserved.
// Desc:     Worker to perform tasks on thinknode framework
 
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <json/json.h>
 
#define CURL_STATICLIB
#include <curl/curl.h>
 
using namespace std;
 
// API configuration
string basic_user = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456";    // Base64 encoded username:password
string api_url = "https://api.thinknode.com/v1.0";         // thinknode url
string app_name = "Dosimetry";                             // app name
string app_version = "1.0.0";                              // app version
 
// Curl get request call back
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
...
 
// Select a specific json tag into string
string get_json_value(string json, string id, int num = 0)
...
 
// Curllib get http request
string do_curl_get(string auth, string url)
...
 
// Curllib post http request
string do_curl_post(string auth, string json, string url)
...
 
// Handles http request to get the user ID from the basic_user
string get_user_token()
...
 
// Handles http request for realm id
string get_realm_id(string token)
...
 
// Handles http request for context id
string get_context_id(std::map<string, string> config)
...
 
// API Authentication
std::map<string, string> authenticate()
...
 
// Grab and post the specified calc request
void post_calc_request()
...
 
// Grab and post sepcified object to the ISS
void post_immutable_object()
...
 
int main(void)
...

C++: Immutable Storage

The below example is a function in the thinknode.cpp class to post an immutable object to the dosimetry app on the thinknode™ framework. This example can be used for any immutable storage post using any datatype by replacing the json iss file.

Dependencies:

void post_immutable_object()
{
    // Immutable info
    string path = "C:\\";                                    // Path of folder json file is located in
    string sjon_iss_file = "aperture_creation_params.json";  // local json object file
    string obj_name = "aperture_creation_params";            // app named_type
 
    std::map<string, string> iam = authenticate();
 
    // Read local immutable json file
    std::ifstream json_file((path + sjon_iss_file).c_str());
    string str((std::istreambuf_iterator<char>(json_file)), std::istreambuf_iterator<char>());    
 
    // Post object
    std::cout << "Posting Object to ISS..." << std::endl;
    string authentication_string = "Authorization : Bearer " + iam["user_token"];
    string res = do_curl_post(
        authentication_string, 
        str, 
        api_url + "/iss/named/" + app_name + "/" + obj_name + "/?context=" + iam["context_id"]);
    std::cout << "Immuntable ID: " << res << std::endl;   
}

Returns:

  1. The ID (in json) of the object stored in Immutable Storage.

C++: Calculation Request

The below example is a function in the thinknode.cpp class to post a calculation request to dosimetry. This example can be used for any calculation request using any datatype by replacing the calculation request json file. This request will post a calculation request, check the status using long polling with a specified timeout, and return the calculation result.

Dependencies:

void post_calc_request()
{
    // Request info
    string path = "C:\\";                            // Path of folder json file is located in
    string sjon_iss_file = "compute_aperture.json";  // local json calc request
 
    std::map<string, string> iam = authenticate();
 
    std::ifstream json_file((path + sjon_iss_file).c_str());
    string str((std::istreambuf_iterator<char>(json_file)), std::istreambuf_iterator<char>()); 
 
    string authentication_string = "Authorization : Bearer " + iam["user_token"];
    // Get calculation id
    std::cout << "Sending Calculation..." << std::endl;
    string calculation_id = get_json_value(
        do_curl_post(authentication_string, 
        str, api_url + "/calc/?context=" + iam["context_id"]), 
        "id");
 
    // Get calculation Status - using long polling
    std::cout << "Checking Calculation Status..." << std::endl;
    string calculation_status = get_json_value(
        do_curl_get(authentication_string, 
        api_url + "/calc/" + calculation_id + "/status/?status=completed&progress=1&timeout=5"), 
        "type");
    if (calculation_status.find("failed") != string::npos) 
    { 
        std::cout << "Server Responded: " << calculation_status << std::endl; 
        return; 
    }
 
    // Get calculation Result
    std::cout << "Fetching Calculation Result..." << std::endl;
    string calculation_result = do_curl_get(
        authentication_string, 
        api_url + "/calc/" + calculation_id + "/result/?context=" + iam["context_id"]);
 
    std::cout << "Calculation Result: " << calculation_result << std::endl;
}

Returns:

  1. The calculation result (in json) of the API function called.

Python

The provided python scripts and libraries are meant to be a foundation and starting point for using the astroid Dosimetry app on the thinknode™ framework. The provided scripts outline the basics of using ISS to store objects, as well as constructing and making calculation requests to the calculation provider. The below sections detail the basic usage for each script.

Download: The python astroid_script_library can be downloaded from the .decimal GitHub repository.

thinknode.cfg

There is a simple configuration file (thinknode.cfg) that is used to store user data for connecting to the Dosimetry app on the thinknode™ framework. This file is required by all scripts in the python astroid_script_library to authenticate and use the Dosimetry app. A sample file with no user data is available in the repository and the details of the information to include in the file are provided below.

  • basic_user being a base64 encoded username and password. Refer to the thinknode documentation for more information.
  • api_url being the connection string to the thinknode™ framework.
  • app_name being the current app name (e.g. dosimetry).
  • app_version being the current version of dosimetry existing on the thinknode™ framework being used.
thinknode.cfg
{
    "basic_user": "<Base64 encoded username:password>",
    "api_url": "https://api.thinknode.com/v1.0",
    "app_name": "dosimetry",
    "app_version": "1.0.0.0",
    "realm_name": "Realm Name"
}

Python: Immutable Storage

Post Generic ISS Object

The post_iss_object_generic.py is a basic python script that provides an example to post any dosimetry type as an immutable object to the dosimetry app on the thinknode™ framework. This example can be used for any immutable storage post using any datatype by replacing the json iss file. The current example posts an aperture_creation_params datatype object that is read in from the aperture_creation_params.json data file.

Dependencies:

post_iss_object_generic.py
# Copyright (c) 2015 .decimal, Inc. All rights reserved.
# Desc:     Post an immutable json object to the thinknode framework
 
from lib import thinknode_worker as thinknode
from lib import decimal_logging as dl
import requests
import json
 
iss_dir = "iss_files"
json_iss_file = "aperture_creation_params.json"
obj_name = "aperture_creation_params"
 
# Get IAM ids
iam = thinknode.authenticate(thinknode.read_config('thinknode.cfg'))
 
# App object to post to iss
with open(iss_dir + '/' + json_iss_file) as data_file:
    json_data = json.load(data_file)
 
# Post immutable object to ISS
res = thinknode.post_immutable(iam, json_data, obj_name)    
dl.data("Immutable id: ", res.text)

Returns:

  1. The ID (in json) of the object stored in Immutable Storage.

Python: Calculation Request

Generic Calc Request

The post_calc_request_generic.py is a basic example to post a calculation request to dosimetry. This example can be used for any calculation request using any datatype by replacing the calculation request json file. This request will post a calculation request, check the status using long polling with a specified timeout, and return the calculation result.

Dependencies:

post_calc_request_generic.py
# Copyright (c) 2015 .decimal, Inc. All rights reserved.
# Desc:     Post a json calculation request to the thinknode framework
 
from lib import thinknode_worker as thinknode
from lib import decimal_logging as dl
import requests
import json
 
request_dir = "request_files"
json_calc_file = "compute_aperture.json"
 
# Get IAM ids
iam = thinknode.authenticate(thinknode.read_config('thinknode.cfg'))
 
# App calculation request
with open(request_dir + '/' + json_calc_file) as data_file:
    json_data = json.load(data_file)
 
# Send calc request and wait for answer
res = thinknode.do_calculation(iam, json_data, True)
dl.data("Calculation Result: ", res.text)

Returns:

  1. The calculation result (in json) of the API function called.

SOBP Dose Calculation

The post_calc_request_sobp_dose.py and post_calc_request_sobp_dose_with_shifter.py are more complete examples that create input data and perform an sobp dose calculation function request to the dosimetry app on the thinknode™ framework.

The post_calc_request_sobp_dose.py example creates the entire calculation request inline using thinknode structure, array, and function requests. The entire dose calculation request is performed using one thinknode calculation provider call. While this structure of a request is a little more complicated to setup and perform, it removes the need to post to ISS or perform ancillary calculations separately.

The post_calc_request_sobp_dose_with_shifter.py adds in the complication of adding a degrader to the sobp calculation. This example performs three separate calculation requests. The first two requests are used to construct the proton degrader_geometry and the third performs the actual dose calculation request using the previously constructed degrader. The entire example could be condensed into a single more complicated thinknode calculation structure, eliminating the need to perform the separate requests, but in some instances it can be more straight-forward to perform some of the calculations separately as shown. As seen in the example, the first two calculation results for the proton degrader are what is placed into the sobp calculation request, instead of the actual function calls as was done in the case of the aperture in the previous example.

Dependencies:

Example

Below is an abbreviated version of the post_calc_request_sobp_dose.py file. The abbreviated sections are denoted as “…”. In the below sample, the dose_calc variable is a thinknode function request that is made of individually constructed arguments. Notice how the compute_aperture() thinknode request function is created using the aperture_creation_params class defined in the dosimetry_types module allowing for easier dosimetry type creation and code readability. Also note that aparams.view is comprised of another class, multiple_source_view, defined from the dosimetry_types module.

  • Modules used and explanation:
    • The dosimetry_types (dt) module is a class library of all the dosimetry data types as described in the Dosimetry Manifest Guide. This library provides easier manual construction of the dosimetry data types.
    • The thinknode_worker (thinknode) module is a library that provides worker functions for performing and building the authentication, iss, and calculation requests to the thinknode framework.
    • The decimal_logger (dl) module is a library that provides nicely formatted log output. This includes optional file logging, timestamps, and message coloring (when run through command windows).

Refer to the .decimal Libraries section for more information on the provided decimal libraries.

import json
from lib import thinknode_worker as thinknode
from lib import decimal_logging as dl
from lib import dosimetry_types as dt
 
# Get IAM ids
iam = thinknode.authenticate(thinknode.read_config('thinknode.cfg'))
 
def make_grid(corner, size, spacing):
...
 
def make_water_phantom(corner, size, spacing):
    return \
        thinknode.function("dosimetry", "create_uniform_image_on_grid_3d",
            [
                make_grid(corner, size, spacing),
                thinknode.value(1),
                thinknode.value("relative_stopping_power")
            ])
 
def make_dose_points(pointCount):
...
 
def get_example_sobp_machine(id):
...
 
def make_layers(sad, range, mod):
...
 
def make_target():
    return \
        thinknode.function("dosimetry", "make_cube",
            [      
                thinknode.value([-32, -20, -30]),
                thinknode.value([16, -10, 30])
            ])
 
def make_view():
    mv = dt.multiple_source_view()
    ds = {}
    ds['corner'] = [-100, -100]
    ds['size'] = [200, 200]
    mv.display_surface = ds
    mv.center = [0, 0, 0]
    mv.direction = [0, 1, 0]
    mv.distance = [2270, 2270]
    mv.up = [0, 0, 1]
 
    return mv.out()
 
def compute_aperture():
    ap_params = dt.aperture_creation_params()
 
    # ap_params.targets.append(thinknode.reference("55802bcf49400020000c")) # Use existing ISS target
    ap_params.targets.append(make_target())
    ap_params.target_margin = 20.0
    ap_params.view = make_view()
    ap_params.mill_radius = 0.0
    ap_params.downstream_edge = 250.5
 
    # Make aperture_creation_params
    args = {}
    args["targets"] = thinknode.array_named_type("dosimetry", "triangle_mesh", ap_params.targets)
    args["target_margin"] = thinknode.value(ap_params.target_margin)
    args["view"] = thinknode.value(ap_params.view)
    args["mill_radius"] = thinknode.value(ap_params.mill_radius)
    args["organs"] = thinknode.value(ap_params.organs)
    args["half_planes"] = thinknode.value(ap_params.half_planes)
    args["corner_planes"] = thinknode.value(ap_params.corner_planes)
    args["centerlines"] = thinknode.value(ap_params.centerlines)
    args["overrides"] = thinknode.value(ap_params.overrides)
    args["downstream_edge"] = thinknode.value(ap_params.downstream_edge)
 
    return \
        thinknode.function("dosimetry", "compute_aperture",
            [
                thinknode.structure_named_type("dosimetry", "aperture_creation_params", args)
            ])
 
beam_geometry = \
...
 
# Call compute_sobp_pb_dose2
dose_calc = \
    thinknode.function("dosimetry", "compute_sobp_pb_dose2",
        [
            make_water_phantom([-100, -100, -100], [200, 200, 200], [2, 2, 2]), #stopping_power_image
            thinknode.value(make_dose_points(181)), # dose_points
            beam_geometry, #beam_geometry
            make_grid([-75, -75], [150, 150], [2, 2]), # bixel_grid
            make_layers(2270.0, 152.0, 38.0),
            compute_aperture(), # aperture based on targets
            thinknode.value([proton_degr]) # degraders
        ])
 
# Perform calculation
res = thinknode.do_calculation(iam, dose_calc, id)
dl.data("Calculation Result: ", res.text)

Python: decimal Libraries

dosimetry_types

The dosimetry_types module is a reconstruction of dosimetry manifest types in python class format. This includes interdependencies between types (e.g. the class “aperture_creation_params.view” requires the class “multiple_source_view”).

Each data type detailed in the Dosimetry Manifest Guide has a corresponding class in this python module.

Below you will see as snippet from the dosimetiry_types module that shows the class for the aperture_creation_params dosimetry type along with its default initializations and .out function.

  • Interdependence: When dosimetry data types are constructed of other or multiple dosimetry types, they will be constructed as such in each class as displayed by the view parameter of the aperture_creation_params in this example. The sobp dose calculation sample python script provides an example of this usage in actual practice.
  • out function: Each class's .out function provides an ordered dictionary of each of the values in the class. This is explicitly an ordered dictionary since when calling a function in a calculation request, the order of the values provided matters if constructing the request by thinknode value type.
class aperture_creation_params:
 
  #Initialize
    def __init__(self):
	self.targets = [] 
	self.target_margin = 0.0 
	multiple_source_vie = multiple_source_view() 
	self.view = multiple_source_vie.out()
	self.mill_radius = 0.0 
	self.organs = [] 
	self.half_planes = [] 
	self.corner_planes = [] 
	self.centerlines = [] 
	self.overrides = [] 
	self.downstream_edge = 0.0 
 
    def out(self):
	values = OrderedDict([("targets", self.targets),
				("target_margin", self.target_margin),
				("view", self.view),
				("mill_radius", self.mill_radius),
				("organs", self.organs),
				("half_planes", self.half_planes),
				("corner_planes", self.corner_planes),
				("centerlines", self.centerlines),
				("overrides", self.overrides),
				("downstream_edge", self.downstream_edge)
			]) 
	return values 

thinknode_worker

The thinknode_worker module is the main work horse for communication with the dosimetry app and thinknode. The module will handle authentication, posting objects to ISS, creating most of the common calculation request structures, and posting the calculation request.

Refer to the .decimal GitHub repository for the complete module. Below are a few of the more common thinknode http worker and their intended usages:

# Authenticate with thinknode and store necessary ids
# Gets the realm_id, bucket_id, and context_id for the current iam configuration
#   param config: connection settings (url and unique basic user authentication)
def authenticate(config):
 
# Send calculation request to thinknode api
#   param config: connection settings (url, user token, and ids for context and realm)
#   param json_data: calculation request in json format
#   param return_data: True = returns calculation result; False = returns calculation id
def do_calculation(config, json_data, return_data=True):
 
# Post immutable object to ISS
#   param config: connection settings (url, user token, and ids for context and realm)
#   param json_data: immutable object in json format
#   param obj_name: object name of app to post to
def post_immutable(config, json_data, obj_name):
 
# Post immutable object to ISS
#   param config: connection settings (url, user token, and ids for context and realm)
#   param obj_id: thinknode iss reference id for object to get
def get_immutable(config, obj_id):

decimal_logging

The decimal_logging module provides formatted and detailed output window and file logging.

The following settings are available in the decimal_logging.py file: display_timestamps: display timestamps in the output window/logfile display_types: display message types (e.g. debug, data, alert) in the output window/logfile log_file: sets the logfile name and location

The following image shows the logging settings for each message type as:

  1. Timestamps = True; Types = True
  2. Timestamps = False; Types = True
  3. Timestamps = False; Types = False

Node.js

The following section contains examples using node.js and (if applicable) the specified modules. These examples are for a high level approach to encoding and decoding the blob data that is part of the Dosimetry App calculation request.

Base64 Blob Format

The blob returned by a calculation request is formatted as such:

// value_type enum definitions
// Nil = 0;
// Boolean = 1;
// Number = 2;
// String = 3;
// Blob = 4;
// List = 5;
// Record = 6;
 
// For value_types nil, boolean, number, string
// <uint32 value_type enum (4 bytes)><data>
 
// For value_types blob, list, record
// <uint31 value_type enum (4 bytes)><size of each data (8 bytes)><data>

Node: Decrypt Base64 Blob Data

The following example shows , using node.js, how to decode the base64 encoded data returned by a calculation request.

// The below base64string is a blob array with the values [ -25, -25, 25, -25, 25, 25, -25, 25 ]
var b64string = "CF2Hl0z_eJxjYWBgcGBABpYH0GgHHHwMcQDM7AYN"; 
 
// The below base64string is a number set to the value 25.1
//var b64string = "NztmHgz_eJxjYmBgmDUTCCQtHQATFwOT";       
 
var buf = new Buffer(b64string, 'base64');
 
var zlib = require('zlib');
 
function read_base_255_number(buf, offset) {
    var n = 0;
    var s = 0;
    while (offset < buf.length) {
        var digit = buf[offset];
        var value = buf.readUInt8(offset);
        offset++;
        s++;
        if (digit.toString(16) === 'ff') {
            break;
        }
        n = n * 255;
        n += value;
    }
    return [s, n];
}
 
var size = read_base_255_number(buf, 4);
 
zlib.unzip(buf.slice(4 + size[0]), function (err, data) {
    if (err) {
        throw err
    }
    var value_type = data.readUInt32LE(0);
 
    // Number
    if (value_type === 2) {
        console.log("DOUBLE", data.readDoubleLE(4));
    } 
    // Blob
    else if (value_type === 4) {
        // Read size here
        var values = [];
        for (var i = 12; i < data.length; i+=8) {
            values.push(data.readDoubleLE(i));
        }
        console.log(values); // Outputs: [ -25, -25, 25, -25, 25, 25, -25, 25 ]
    }
});

USR-001

.decimal LLC, 121 Central Park Place, Sanford, FL. 32771

dosimetry/userguide/thinknode.1435852017.txt.gz · Last modified: 2021/07/29 18:21 (external edit)