-
Notifications
You must be signed in to change notification settings - Fork 7
OneStepJobPlugin
Lesson 13 deals with the job engine, implemented in the Orthanc server in July 2018. We start with a simple plugin doing a very basic job. The job consist in writing a text, and some data, to the Orthanc log file. You think that is stupid? You are right! But the inherent complexity of the job engine requires a soft introduction into this topic.
The heart of the Orthanc job engine is the Step()
function. This function is called periodically by the Orthanc core system when a job has been created and submitted. The Step()
function executes a specific task of the job, typically for one DICOM file. The task is started with the first DICOM file, repeated in the next step for the second DICOM file, and so on, until the task for the last DICOM file has been executed. The Step() function returns different values when a task terminates:
-
succes
if the job is finished -
continue
if further tasks are required to finish the job -
failure
if an error appeared
Processing DICOM files is the most common mission of a task, but this is not a requirement. If a job is splitted in a sequence of tasks, a task can do anything.
In the last lesson we have seen how to segment the C++ code in different files. In this lesson we will learn a new concept which is the foundation of object-oriented programming (OOP). Objects usually correspond to things found in the real world, for example a circle in a graphics program, a patient in medical software, or a job in the Orthanc core system. The recipe to create an object is called class, an object is called an instance of the class.
In the present lesson we will write the recipe (class) to create (construct) a job, based on definitions included in the OrthancPluginCppWrapper (line 1857).
Let's first write the code of the MyJob.h header file:
#pragma once
#include <string>
#include <stdlib.h>
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
class MyJob : public OrthancPlugins::OrthancJob {
std::string jobName_;
public:
explicit MyJob(std::string jobType);
~MyJob();
OrthancPluginJobStepStatus Step();
void Stop(OrthancPluginJobStopReason reason);
void Reset();
};
We derive a class with the name MyJob
from the public OrthancPlugins::OrthancJob
class with a private member variable jobName_
. Then we define the public constructor
explicit MyJob(std::string jobType);
and the public destructor ~MyJob();
.
We declare three public member methods (functions) Step()
, Stop()
and Reset()
.
The code written in the MyJob.cpp file tells the program how to construct and destruct the MyJob object and what the three declared methods should do.
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
#include <Core/Toolbox.h>
#include <Core/Logging.h>
#include <string>
#include "MyJob.h"
// constructor
MyJob::MyJob(std::string jobType)
:
OrthancPlugins::OrthancJob(jobType),
jobName_(jobType) {
LOG(INFO) << "*** MyJob " << jobName_ << " constructor";
}
// destructor
MyJob::~MyJob() {
LOG(INFO) << "*** MyJob destructor";
}
OrthancPluginJobStepStatus MyJob::Step() {
LOG(INFO) << "*** MyJob " << jobName_ << "Step";
Json::Value detailInfo;
detailInfo = Json::objectValue;
detailInfo["Project"] = "RadioLogic Tutorial";
detailInfo["Author"] = "Marco Barnig";
MyJob::UpdateContent(detailInfo);
return OrthancPluginJobStepStatus_Success;
}
void MyJob::Stop(OrthancPluginJobStopReason reason) {
LOG(INFO) << "*** MyJob " << jobName_ << " Stop()";
}
void MyJob::Reset() {
LOG(INFO) << "*** MyJob " << jobName_ << " Reset()";
}
Only the MyJob::Step()
function does more than simply print a log message. It creates a JSON object detailInfo
with two key pairs: "Project":"RadioLogic Tutorial"
and "Author":"Marco Barnig"
. The job engine is updated with the command MyJob::UpdateContent(detailInfo);
and the value OrthancPluginJobStepStatus_Success;
is returned at the end of the first and unique step.
The code in the file OneStepJobPlugin.cpp contains the CallbackOneStepJob
function which is called by the REST API /start-one-step-job
.
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
#include <Core/SystemToolbox.h>
#include <Core/Toolbox.h>
#include <Core/Logging.h>
#include <string>
#include "MyJob.h"
void CallbackOneStepJob(OrthancPluginRestOutput* output, const char* url,
const OrthancPluginHttpRequest* request) {
LOG(INFO) << "*** CallbackOneStepJob Function called";
OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
// create a new job
MyJob* OneStepJob = new MyJob("OneStepJob");
LOG(INFO) << "*** New OneStepJob created";
// submit the job
int priority = 3;
std::string jobId = OrthancPlugins::OrthancJob::Submit(OneStepJob, priority);
// no need to free the jobId string, ownership is taken by OrthancJob
LOG(INFO) << "*** New OneStepJob submitted";
std::string HtmlCode = "<html>\n<head>\n<title>Test</title>\n</head>\n<body>\n<h2>OneStepJob with jobId " + jobId + " running in Orthanc Server</h2>\n</body>\n</html>\n";
OrthancPluginAnswerBuffer(context, output, HtmlCode.c_str(), HtmlCode.length(), "text/html");
}
extern "C" {
ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) {
Orthanc::Logging::Initialize(context);
OrthancPlugins::SetGlobalContext(context);
OrthancPluginSetDescription(context, "Orthanc Plugin Test with Job Engine; do HTTP GET call to <RadioLogicArchive IP>/start-one-step-job");
OrthancPlugins::RegisterRestCallback<CallbackOneStepJob>("/start-one-step-job", true);
return 0;
}
ORTHANC_PLUGINS_API void OrthancPluginFinalize() {
LOG(INFO) << "*** OneStepJobPlugin is finalizing";
}
ORTHANC_PLUGINS_API const char* OrthancPluginGetName() {
return "OneStepJobPlugin";
}
ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion() {
return "1.0.0";
}
}
Inside the CallbackOneStepJob
function a new instance, called OneStepJob
, is created with the MyJob
class (recipe), with the jobName_ OneStepJob
as parameter. The job is submitted to the job engine with the code
std::string jobId = OrthancPlugins::OrthancJob::Submit(OneStepJob, priority);
A unique jobId is provided by the job engine which is included in the HTML webpage, created from scratch, and returned as answer to the REST API HTTP request.
There are no new elements in the CMakeLists.txt, compared to the building file in lesson 12. Don't forget to put the complete and right files in the add_library
script:
add_library(OneStepJobPlugin SHARED
${CORE_SOURCES}
${CMAKE_SOURCE_DIR}/OneStepJobPlugin.cpp
${CMAKE_SOURCE_DIR}/MyJob.cpp
${AUTOGENERATED_SOURCES}
)
We have seen several times how to build and test a plugin and there is no need to repeat it here. If we start the RadioLogicArchive with the OneStepJobPlugin
and enter the URL /start-one-step-job
, we should see the following content:
A visit to the Orthanc Plugins webpage confirms that the OneStepJobPlugin
is up and running.
Going back to the Lookup
webpage and clicking the Jobs
button, the following webpage is displayed.
Our OneStepJob
is listed in the panel of inactive jobs with the tag Success
. The job was created on January 12, 2020 at 14:04:56 and completed at the same time. It was only one step! If we click on the job, we get the following general and detailed additional information about the job:
We are informed that the job was executed with priority 3, without errors, with an effective runtime of zero. I said it: It was only one step! The infos about the author and the project name are displayed in the details panel.
The progress is shown as 0, instead of 100. We missed to update the progress bar in the Step()
function. This is a small issue which we should include in the list of problems to fix in the next version of our OneStepJobPlugin
.
The next figure shows an abstract from the logs related to our current project.
Again, we can follow the sequence of the processes executed in the Orthanc system. Nothing special to mention.
Time to switch to lesson 14. A MultiStepsJobPlugin
.