-
Notifications
You must be signed in to change notification settings - Fork 7
MiniPlugin
Here comes lesson 11 with a recipe for the development of the third Orthanc plugin. Instead of down-scaling a lonely DICOM file, as we have done in lesson 10, we will scale a whole series.
To do so, we use a new function. In the Orthanc webpage showing the list of DICOM files included in a series, we will add a button to initiate the down-scaling process for these files.
Adding a button in the Orthanc Explorer code can be done with a few lines of Javacsript. This script is embedded into the plugin and the existing explorer.js code is extended with the Orthanc SDK function OrthancPluginExtendOrthancExplorer.
Here comes the script to add a button which initiates a REST call to the CallbackScaleSeries()
function via the URL /series/<seriesID>/scale
.
$('#series').live('pagebeforecreate', function() {
var b = $('<a>')
.attr('data-role', 'button')
.attr('href', '#')
.attr('data-icon', 'action')
.attr('data-theme', 'e')
.text('Scale images from this series to 50%');
b.insertBefore($('#series-delete').parent().parent());
b.click(function() {
if ($.mobile.pageData) {
var myURL = myFunction();
console.log("URL : " + myURL);
fetch(myURL).then(function(response) {
return response.text().then(function(text) {
console.log("Response: " + text);
if(text == "OK") {
// close popup-window wait with spinning wheel
$.mobile.sdCurrentDialog.close();
}
});
});
}
});
});
function myFunction() {
var myLocation = window.location;
console.log("Location : " + myLocation);
var myProtocol = myLocation.protocol;
var myHost = myLocation.host; // hostname + port number
console.log("Host : " + myHost);
var myHash = myLocation.hash;
console.log("Hash : " + myHash);
var myURL = myProtocol + "//" + myHost + "/series/" + myHash.split('=')[1] + "/scale";
// display popup window with waiting message and spinning wheel
$(document).simpledialog2({
mode: 'blank',
themeHeader: 'b',
themeDialog: 'a',
headerText: 'Scaling',
headerClose: false,
blankContent :
'<div style="text-align:center">' +
'<p>Please wait</p>' +
'<img src="' + myProtocol + '//' + myHost + '/radiologic/images/ajax-loader.gif" alt="loading" width="200" height="200" />' +
'</div>'
});
return myURL;
}
The first lines create the new button with jQuery Mobile. When the button is clicked, the function myFunction
, which defines the Backcall REST API URL based on window.location
, is called. The seriesId
is added to the URL and a pop-up window, showing a spinning wheel with the message "Please Wait", is displayed. The spinning-wheel-image is hosted in the serve-folder. The URL is returned by myFunction
and the HTTP REST request is launched with the native asynchronous Javascript command fetch
. When the OK
HTTP answer, send by the CallbackScaleSeries()
function, is received by the Javascript, the pop-up window is closed.
In this lesson, and in the following ones, we add plenty of comments and log messages, due to the increasing complexity of the code. This helps us to better understand and to debug the program.
To embed the Javascript inside the plugin we add the following code into the CMakeList.txt file.
EmbedResources(
ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/Resources/OrthancExplorer.js
)
To extract and apply the script we add the commands below in the MiniPlugin.cpp file.
#include <EmbeddedResources.h>
// inside the OrthancPluginInitialize function :
// Extend the default Orthanc Explorer with custom JavaScript
std::string explorer;
Orthanc::EmbeddedResources::GetFileResource(explorer,
Orthanc::EmbeddedResources::ORTHANC_EXPLORER);
OrthancPluginExtendOrthancExplorer(context, explorer.c_str());
In the past lessons we have learned how the callback function is coded. For the present plugin we apply some additional features in the CallbackScaleSeries()
function. First we extract the seriesID
from the incoming HTTP call.
LOG(INFO) << "CallbackScaleSeries request received";
// extract the GET arguments of the request
if (request->method == OrthancPluginHttpMethod_Get) {
const char * mySeriesId = request->groups[0];
LOG(INFO) << " *** mySeriesID " << mySeriesId;
}
Next we retrieve the list of DICOM files included in the series by calling the REST API /series/<seriesID>
in a browser. Let's first look how the data returned by this call is structured.
The list of the instanceId's is provided in an array with the JSON key Instances
. Here is the C++ code to extract this array:
// get list of instances related to series
OrthancPluginMemoryBuffer temp;
std::string seriesUri = "/series/" + std::string(mySeriesId);
OrthancPluginErrorCode seriesError = OrthancPluginRestApiGetAfterPlugins
(context, &temp, seriesUri.c_str());
LOG(INFO) << "*** seriesError: " << seriesError;
if (seriesError == 0) {
// show content of buffer
const char * seriesJson = static_cast< const char * >(temp.data);
LOG(INFO) << "JSON Data: " << seriesJson;
std::string jsonString = std::string(seriesJson);
Json::Reader reader;
Json::Value root;
bool parseStatus = reader.parse(jsonString, root);
if (parseStatus == true) {
const Json::Value& instances = root["Instances"];
LOG(INFO) << "*** InstanceId's: " << instances;
}
OrthancPluginFreeMemoryBuffer(context, &temp);
}
OrthancPluginAnswerBuffer
(context, output, "OK", 2, "text/plain");
}
The Orthanc SDK function OrthancPluginRestApiGetAfterPlugins() saves the HTTP body content into the buffer temp
. If the request is successful, the open source jsoncpp library is used to parse the content and to write it into the variable jsonString
. If there is no parsing error, the list of InstanceId's is stored into the variable instances
.
Finally the memory buffer is freed and a HTTP answer, with the plain text OK
, is returned as a confirmation and received by the Javascript.
The real work to scale all the DICOM images of the instances
list is done inside the function ScaleImage()
. This function is derived from the ScaleDicomImage()
function, included in the MicroPlugin code. We create a separate file for this function, named ScaleImage.cpp. To
include the file into the MiniPlugin project we must take the following actions:
The C++ header file has the extension .h
. Here is the related ScaleImage.h file which contains only the function interface.
#pragma once
#include <string>
void ScaleImage(std::string instanceId, std::string seriesUid);
The instruction #pragma
states to include the header file only once in the project. The function ScaleImage()
takes two parameters:
- instanceId
- seriesUid
Both are strings. Therefore we need to include the <string>
library into the header file.
In both the ScaleImage.cpp and the MiniPlugin.cpp we must include the ScaleImage header file:
#include "ScaleImage.h"
In the CMakeLists.txt file we must include the additional .cpp
file in the add_library
code sequence.
add_library(MiniPlugin SHARED
${CORE_SOURCES}
${CMAKE_SOURCE_DIR}/MiniPlugin.cpp
${CMAKE_SOURCE_DIR}/ScaleImage.cpp
${AUTOGENERATED_SOURCES}
)
The DICOM standard requires unique identifiers for all DICOM objects. A new instanceUID is generated automatically when a new DICOM instance is uploaded to the Orthanc server. To store the scaled instances in a new Series we must however modify the related SeriesUID manually. The Orthanc SDK function OrthancPluginGenerateUuid is the right tool to execute this task. Before calling the ScaleImage() function inside the
CallbackScaleSeries` function, we apply the following code:
// create new SeriesUid
char* newSeriesUid = OrthancPluginGenerateUuid(context);
std::string newSeriesUidString = std::string(newSeriesUid);
The generated newSeriesUidString
variable is passed as second parameter to the ScaleImage() function. The seriesUID (DICOM tag 0020,000e
) and the SeriesDescription (DICOM tag 0008,103e
) are modified inside the ScaleImage() function with a system call to the dcmodify command line utility which is part of the Offis DCM Toolkit.
// modify DICOM tags with new SeriesUID and SeriesDescription
std::string dcmodifyCommand
= "/dcmtk-3.6.4-linux-x86_64-static/bin/dcmodify -m \"(0020,000e)="
+ seriesUid + "\" -m \"(0008,103e)=Series with scaled images\" -gin -nb /tmp/scaled-"
+ instanceId + ".dcm";
int dcmodifyStatus = system(dcmodifyCommand.c_str());
LOG(INFO) << "*** System command dcmodify status: " << dcmodifyStatus;
The last code snippet to present concerns the loop handling the call to the ScaleImage()
function for each instanceId.
for (unsigned int i = 0; i < instances.size(); i++) {
std::string instanceId = instances[i].asString();
ScaleImage(instanceId, newSeriesUid);
}
Before building the plugin it's wise to check a last time all files and to validate the code with a C++ validator.
The next step is the same procedure as usual
to build the plugin.
wget https://www.web3.lu/downloads/MiniPlugin.zip
unzip MiniPlugin.zip
rm MinipluginPlugin.zip
cd Miniplugin
mkdir Build
cd Build
cmake -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE=Release ../
make
Here are the Build results:
Great! Let's add the compiled plugin to the RadioLogicArchive and test it.
The MiniPlugin is shown in the list of the installed plugins. I use a recent CT of my own head to check if the images are scaled down as expected.
Let's click the scale button to start the process.
If we go back to the studies page in the Orthanc Explorer we see that a second series with the scaled images has been added.
Awesome! 😃😀😄
Before leaving the present lesson, we should have a look at the log files. Printing messages to the log system or to the console doesn't make sense if we ignore them. Let's first view the output in the Javascript console opened for the DICOM Series webpage where we started the Scale process:
The messages comply to our expectation. Otherwise, we could easely inspect the Explorer.js
extension in a browser with the URL /plugin/explorer.js
.
To analyse the C++ messages when Orthanc is started in --verbose
mode, we can read the log files in the related Docker container in the Synology diskstation, or we can scroll through the output of the terminal, if the RadioLogicArchive was started in interactive mode. Below is an extract of the terminal window messages:
To make a distinction between the plugin log messages and the Orthanc core messages, the first ones are marked with three stars (***
) at the message beginning. It's easy to follow the process sequence:
- HTTP request for URI: /series/3c3d557b-fb8c27d1-39be765e-e8bdd59c-95a1ca82/scale
- CallbackSeriesScale request received
- SeriesId extracted
- REST GET call to URI /series/3c3d557b-fb8c27d1-39be765e-e8bdd59c-95a1ca82 without error
- JSON parsing in the request body
- Three system commands executed without error
- REST POST call to URI /instances
- Attachments created ann new instance stored
- Temporary files successfully removed
We are now fit to face new challenges in the next lesson. We will develop a simple plugin running a job. The job engine helps us to avoid the spinning wheel which blocks our system when doing tasks taking some time, for example scaling several hundred DICOM images in one batch.