Basic Functionality

<< Click to Display Table of Contents >>

Navigation:  XStream® HDVR® SDK >

Basic Functionality

Previous pageReturn to chapter overviewNext page

C++ C++ Java Java .NET .NET

Summary

 

This unit describes the steps required to enable basic XStream® HDVR® functionality in your application—how to connect to the server, load a DICOM dataset, specify a default preset, render an image and shut down the application. These topics are expanded upon in the Implementation Concepts and Advanced Functionality units. Several example applications that implement this basic functionality are provided with the XStream HDVR distribution, and are discussed in the HDVR® Basic Application page.

 

oInitialization
oLoading a Dataset
oCreating the Octree
oCreating a RenderEngineContext
oSetting Render Parameters
oRendering an Image
oReleasing of Systems Resource

 

Initialization

 

XStream HDVR is typically deployed using a client-server model, with the clients and rendering server running on separate systems. The API enables the client to dynamically change between different rendering servers to implement High Availability (HA) and for load balancing. The system may also be configured to use a thick client, where a client and a server are on the same system.

 

Once the XStream HDVR server is running, the client application establishes a connection using the rendering server's machine address and designated port ID. The machine address is given as a character string containing either a numerical IP address or a machine name; the port number is an integer that represents a unique physical port. During early stages of development, it may be desirable to run both the server and the test client application on the same system by specifying localhost as the IP address of the server. In some deployment situations, it may be desirable to distribute your final product using a thick client.

 

C++ based applications must first create a single instance of the ILibrary class as the starting point for any XStream HDVR enabled application. Once the ILibrary object has been created, other XStream HDVR object types can be instantiated. It is not necessary to create an ILibrary object in Java/.NET based applications to get started.

 

The hdrcclientOpenLibrary() method creates an instance of an ILibrary object. It takes an input parameter that specifies the current version of the XStream HDVR SDK to ensure server compatibility with the DLLs. It takes as an output parameter the address of a pointer to the ILibrary object created by this method.

 

// Create an ILibrary instance

ILibrary *pLibrary;

hdrcclientOpenLibrary(HDRCCLIENT_SDK_VERSION, &pLibrary);

 

Through the ILibrary::CreateObject() method, an IServerContext object is created that facilitates all communication to the server.

 

// Create an IServerContext instance

IServerContext *pServerContext;

pLibrary->CreateObject(&CLSID_ServerContext, &pServerContext);

 

TheIServerContext::Connect() method establishes a connection to a server application using the machine address and port number. The machine address is given as a character string containing either a numerical IP address or a machine name; the port number is an integer. It also takes an ILogger object to implement a custom logging function used by the SDK.

 

The IServerContext::Connect() method uses the serverResponse output parameter to report the connection status. The server response codes are shown in the table below.

 

Server Response Codes

0

Server has too many connections.

-1

Connection successful.

-2

Could not find host (invalid address, etc.).

-3

Invalid license file.

 

// Connect to the server

CLogError errorLog;

h_int32 serverResponseCode;

pServer->Connect("localhost", 6778, &errorLog, &serverResponseCode);

 

if (serverResponseCode == 0) {

   cout << "server has too many connections";

} else if (serverResponse == -2) {

   cout << "server cannot be found, invalid address";

} else if (serverResponse == -3) {

   cout << "server has an invalid license";

} else {

   cout << "server connection established";

}

 

Java/.NET applications instantiate a hdrcServerContext to manage the server communication. The constructor establishes a connection to a server application using the machine address and port number. The machine address is given as a character string containing either a numerical IP address or a machine name, the port number is an integer.

 

If the hdrcServerContext constructor fails to connect to the server it will throw one of the following three exceptions: UnknownHostException, IOException, ServerBusyIOException. The exception type and message will contain information on the reason for the connection failure.

 

try {

   // connect to the server. this will throw if the connection fails.

   server = new hdrcServerContext("localhost", 6778);

} catch(hdrcServerContext.ServerBusyIOException ex ) {

   System.out.println("Server busy."); 

   return;                

} catch(IOException ex) {

   // License file or SDK version is invalid.

   System.out.println(ex.getMessage()); 

   return;

} catch(UnknownHostException ex) {

   // Could not find server.

   System.out.println(ex.getMessage()); 

   return;

}

 

Loading a Dataset

 

The server creates a volume based on a variety of different synchronous and asynchronous loading mechanisms that use either the DICOM loader or RAW data loader, or that are augmented through the Custom Loading Function. The below example describes how to load a directory of DICOM data synchronously. Details about the various loading methods are described in the Data Loading Fundamentals chapter.

 

In C++, the simplest way to load data into the XStream HDVR SDK is through the IServerContext::LoadDicomDirectory() method. It takes as input parameters the directory containing the DICOM series to be loaded, and a pointer to an IVolumeDataContext object that holds the newly created volume. This method parses the DICOM headers, arranges the data into a volume, and fills in the gaps when the Z direction is not uniform. The target directory should contain only one DICOM series that contains a contiguous set of image data. A developer may also choose from one of Fovia's Sample Datasets for initial testing purposes.

 

IVolumeDataContext *pVolumeDataContext = NULL;

int errorCode = pServerContext->LoadDicomDirectory(&pVolumeDataContext, "C:/Datasets/DICOM");

 

In Java/.NET, the simplest way to load data into the XStream HDVR SDK is through the hdrcServerContext.loadDicomDirectory() method. It takes as an input parameter the location of the dataset to be loaded, and returns a reference to an hdrcVolumeDataContext object that holds the newly created volume. This method parses the DICOM headers, arranges the data into a volume, and fills in the gaps when the Z direction is not uniform. The target directory should contain only one DICOM series that contains a contiguous set of image data. A developer may also choose from one of Fovia's Sample Datasets for initial testing purposes.

 

hdrcVolumeDataContext volumeDataContext = serverContext.loadDicomDirectory("C:/Datasets/DICOM");

 

Creating the Octree

 

The octree is an optimization data structure used by the rendering engine that improves performance during analysis and rendering of volumetric data, and is required by the Adaptive Rendering engines for rendering and segmentation. An octree is NOT required for any of the Brute Force rendering engines. Refer to the Render Engine chapter for additional details.

 

In C++, the IOctreeContext object is created through the IServerContext class. The octree object is typically included as an input parameter, along with the IVolumeDataContext object, to methods that interact with or manipulate the volume data. The IOctreeContext object can also be used directly to control visualization of different transfer functions and segmentation regions.

 

The IServerContext::CreateOctreeEx() method optionally allows trilinear or tricubic smoothing to be applied to the dataset when the octree is created. This operation will directly modify the original dataset voxel values to apply the smoothing. Subsequent calls to IServerContext::CreateOctree() with no smoothing will still benefit from the smoothing operation that was originally applied to the dataset. Therefore, smoothing should be applied only once per dataset. Subsequent calls to IServerContext::CreateOctreeEx() with a smoothing option will re-apply smoothing to the already smoothed dataset. The results of this operation could be undesirable.

 

// Create the octree

IOctreeContext *pOctreeContext;

pServerContext->CreateOctree(&pOctreeContext, pVolumeDataContext);

 

In Java/.NET, the hdrcOctreeContect object is created through the hdrcServerContext class. The octree object is typically included as an input parameter, along with the hdrcVolumeDataContext object, to methods that interact with or manipulate the volume data. The hdrcOctreeContext object can also used directly to control visualization of different transfer functions and segmentation regions.

 

The hdrcServerContext::createOctree() method optionally allows trilinear or tricubic smoothing to be applied to the dataset when the octree is created. This operation will directly modify the original dataset voxel values to apply the smoothing. Subsequent calls to hdrcServerContext::createOctree() with no smoothing will still benefit from the smoothing operation that was originally applied to the dataset. Therefore, smoothing should be applied only once per dataset. Subsequent calls to hdrcServerContext::createOctree() with a smoothing option will re-apply smoothing to the already smoothed dataset. The results of this operation could be undesirable.

 

//Create the octree

hdrcOctreeContext octreeContext = serverContext.createOctree(volumeDataContext.getId());

 

Creating the RenderEngineContext

 

The render engine is the class object that implements Fovia's XStream HDVR rendering technology. The render engine is the mechanism through which a developer sets various render parameters and generates the images displayed on the screen. The Client APIs provide two types of render engine classes: RenderEngineContext and RenderQueue. The RenderEngineContext class supports synchronous rendering operations that return a rendered image directly through its render() method. The RenderQueue class supports asynchronous render operations that return a rendered image to a user-defined callback function. The differences between these two and their relative advantages are discussed in the Render Engine Context vs Render Queue chapter. That chapter also provides an example using RenderEngineContext.

 

In C++, an IRenderEngineContext object is created through the IServerContext::CreateRenderEngine() method. This method takes as input parameters an IRenderEngineContext pointer, and the ENUM_RENDER_ENGINE_CLASSID enumeration that specifies the type of render engine to be created. The available types include parallel, perspective, MIP, and MPR engines. The render type for an engine can be changed at run-time using the RENDER_PARAMS::RenderType parameter.

 

// Create an IRenderEngineContext object for parallel rendering

IRenderEngineContext *pRenderEngine;

pServerContext->CreateRenderEngine(&pRenderEngine, RECID_PAR);

 

// Initialize the render engine with the volume and octree data

pRenderEngine->SetVolumeData(pVolumeData, pOctreeData);

 

In Java/.NET, an hdrcRenderEngineContext object is created through the hdrcServerContext.createRenderEngine() method. This method takes as an input parameters the RENDER_ENGINE_ID listed in hdrcDefines. This ID specifies the type of render engine to be created. The available types include parallel, perspective, MIP, and MPR engines. The render type for an engine can be changed at runtime using the RENDER_PARAMS::RenderType parameter.

 

// Create an hdrcRenderEngineContext object for parallel rendering

hdrcRenderEngineContext renderEngine;

renderEngine = serverContext.createRenderEngine(hdrcDefines.RENDER_ENGINE_ID_PAR);

 

// Initialize the render engine with the volume and octree data

renderEngine.setVolumeData(volumeData, octreeData);

 

Setting Render Parameters

 

Before an image can be rendered, a RENDER_PARAMS structure must be applied to the render engine to set the render parameters. The RENDER_PARAMS structure is specified when the volume is initially loaded, and then dynamically updated through the user interface control (zoom, pan, rotate, window/level, etc.). The render parameters can be specified programmatically or loaded from an XML parameter file. The XML preset format provides a mechanism to serialize the entire rendering state, and specifies the RENDER_PARAMS structure into a compact, human-readable format. This provides a useful set of default views, stored as part of a Key Image that enables the restoration of the 3D state at a later time, and provides a mechanism to synchronize the rendering state between a cluster of rendering servers. The XStream HDVR distribution includes a set of default presets that can serve as a base for your application preset library. Screen shots and XML files are described in the Default XML Presets chapter. Details on creating, saving and loading XML presets, as well as methods to allow presets to be managed at the client-side are described in Managing Presets. The below example demonstrates how to load an XML preset.

 

In C++, XML presets are loaded using IServerContext::LoadPreset() as shown below:

 

// Load a new preset into a RENDER_PARAMS structure.

pServerContext->LoadPreset("/data/presets/saved_preset.xml", &rp);

 

// Apply the RENDER_PARAMS structure.

pRenderEngine->SetRenderParams(&rp);

 

In Java/.NET, XML presets are loaded using hdrcServerContext.loadPreset() as shown below:

 

// Load a new preset into a RENDER_PARAMS structure.

serverContext.loadPreset("/data/presets/saved_preset.xml", rp);

 

// Apply the RENDER_PARAMS structure.

renderEngine.setRenderParams(rp);

 

Rendering an Image

 

Image rendering is accomplished through either the RenderEngineContext or the RenderQueue class. As described above, the RenderEngineContext class provides a synchronous rendering class and a deterministic mechanism for rendering frames with a given set of rendering parameters. The RenderQueue is an asynchronous rendering class that returns rendered frames through a callback mechanism. Each approach is described in more detail in the Render Engine Context vs. Render Queue section. This section provides an example that uses a RenderEngineContext to generate an output image, and assumes that the RENDER_PARAMS structure has already been updated as described above.

 

The RenderEngineContext class supports a variety of Render() methods that render both synchronously and asynchronously. These are described in the RenderEngineContext section of the Render Engine Context vs. Render Queue page. The Render() method takes two VOLVISIMAGE objects. The first specifies an input parameter that designates the quality of the image to be rendered, and the second is an output parameter with a reference to the rendered data. The input VOLVISIMAGE::Stage parameter must be set to the appropriate ENUM_RENDER_STAGE enumeration value. This will be either RENDER_STAGE_PROG0 for an interactive quality image, or RENDER_STAGE_FINAL for a final high quality image.

 

Optionally, the VOLVISIMAGE::CompressionType parameter may be set to control whether or not the server  transmits compressed image data to the client. The available compression types are specified in the ENUM_COMPRESSION_TYPE enumeration. Typically, this will be COMPRESSION_TYPE_JPEG for interactive and COMPRESSION_TYPE_NONE for the final rendering. When using JPEG compression, the VOLVISIMAGE::CompressionParam member specifies the compression quality: 0 (lowest) to 100 (highest).

 

It is the responsibility of the application to call the Render() method for at least one or more interactive renderings followed by a final rendering, since the final rendering builds upon the data computed during the intermediate phase.

 

In C++, an example of IRenderEngineContext::Render() is shown below:

 

// Declare the input and output VOLVISIMAGE structures

VOLVISIMAGE imgRequest, imgResponse;

 

// Zero out the VOLVISIMAGE structures so we don't pass bad input paramaters

BCOM_ZEROMEMORY( imgRequest );

BCOM_ZEROMEMORY( imgResponse );

 

// Render an interactive quality image

imgRequest.Stage = RENDER_STAGE_PROGR0;

imgRequest.CompressionType = COMPRESSION_TYPE_JPEG;

pRenderEngine->Render( &imgRequest, &imgResponse );

 

// Render a final quality image

imgRequest.Stage = RENDER_STAGE_FINAL;

imgRequest.CompressionType = COMPRESSION_TYPE_NONE;

pRenderEngine->Render( &imgRequest, &imgResponse );

 

In Java/.NET, an example of hdrcRenderEngine.render() is shown below:

 

// Declare the input and output VOLVISIMAGE structures

VOLVISIMAGE imgRequest = new VOLVISIMAGE();

VOLVISIMAGE imgResponse = new VOLVISIMAGE();

 

// Render an interactive quality image

imgRequest.Stage = RENDER_STAGE_PROGR0;

imgRequest.CompressionType = COMPRESSION_TYPE_JPEG;

renderEngine.render( imgRequest, imgResponse );

 

// Render a final quality image

imgRequest.Stage = RENDER_STAGE_FINAL;

imgRequest.CompressionType = COMPRESSION_TYPE_NONE;

renderEngine.render( imgRequest, imgResponse );

 

Releasing of System Resources / Memory Management

 

In the above sample code, various system resources were allocated, including:

 

oRenderEngineContext
oOctreeContext
oVolumeDataContext
oServerContext

 

When these resources are no longer needed, the resources must be released and deallocated in the proper fashion. Typically, the ServerContext is allocated when the application is started and released upon termination, but when data is no longer visualized within the application it should be released. While the client-side may consume a limited amount of resources, a large volume may exist on the server. It is the responsibility of the client to ensure that all of its server-side resources are released when they are no longer needed.

 

XStream HDVR C++ based client-side context class objects are allocated with XStream HDVR SDK utility methods and are controlled with a reference counting mechanism. Client-side methods exist that tell the server to deallocate unneeded resources. The developer should never attempt to manually delete such objects. Reference counting methods are only available in the C++ Client API. The Java/.NET languages implement reference counting mechanisms specific to Java/.NET, therefore the XStream HDVR SDK reference counting mechanism is not implemented in the Java/.NET API. For more information on XStream HDVR memory management see Memory Management.

 

The developer should never attempt to manually delete objects allocated with ServerContext class allocation utility methods.

 

Most XStream HDVR classes in the C++ API use reference counting to control object deallocation. When an object is created, its reference count is set to 1. Whenever a reference to that object is made by assigning a pointer to it, its reference count should be incremented with the object's IncRef() method. When a reference to an object is removed, its reference count should be decremented with the object's DecRef() method. When an object's reference count reaches 0 the object is automatically deallocated.

 

The Java and C# languages automatically implement their own reference counting and memory management mechanisms, so the IncRef() and DecRef() methods do not exist in these APIs.

 

Each server-side resource has a corresponding client-side resource that must manage and release its reference to the server-side resource.

For example, when an application loads a dataset from the client application, a client-side VolumeDataContext object is created, and a server-side IVolumeData object is created.

 

In C++, when the application no longer needs this dataset, the application should first call IVolumeDataContext::ReleaseSessionResources() to signal the server that it can delete its IVolumeData object. The application should then call the IVolumeDataContext::DecRef() method once for each terminated reference to the object so that its reference count goes down to 0. At this point, the IVolumeDataContext object will be deleted.

 

Deallocating a client-side object by calling its DecRef() method will NOT automatically cause the server-side representation of this object to be deallocated. The appropriate ReleaseSessionResources() method must be called.

 

In Java/.NET, when the application no longer needs this dataset the application should first call hdrcVolumeDataContext.releaseSessionResources() to signal the server that it can delete its IVolumeData object. Since hdrcVolumeDataContext is no longer in use, the Java garbage collector will release the client side resources.

 

The same pattern should be followed for the RenderEngineContext, RenderQueue, and OctreeContext classes, as well as any other class that implements the ReleaseSessionResources() method. When a client application terminates a session by disconnecting from the server, the server will automatically delete all resources associated with that session. However, to prevent excessive memory use it is recommended that the client application actively release both client and server side objects when they are no longer needed.

 

In C++, release of resources and references is as follows:

 

/ Free up server resources for the engine if the reference count becomes 0.

pRenderEngine->ReleaseSessionResources();

// Free up client resources for the engine if the reference count becomes 0.

pRenderEngine->DecRef();

 

// Free up server resources for the octree if the reference count becomes 0.

pOctree->ReleaseSessionResources();

// Free up client resources for the octree if the reference count becomes 0.

pOctree->DecRef();

 

// Free up server resources for the data if the reference count becomes 0.

pVolumeData->ReleaseSessionResources();

// Free up client resources for the data if the reference count becomes 0.

pVolumeData->DecRef();

 

In Java/.NET, the resources of resources is as follows:

 

// Free up server resources for the engine if the reference count becomes 0.

renderEngine.releaseSessionResources();

 

// Free up server resources for the octree if the reference count becomes 0.

octree.releaseSessionResources();

 

// Free up server resources for the data if the reference count becomes 0.

volumeData.releaseSessionResources();

 

In C++, when a client application has terminated all rendering sessions and does not need to maintain a connection with the XStream HDVR server it should disconnect from the server. This is done with the IServerContext::Disconnect() method. When the client disconnects from the server, all server side resources associated with that client session are deleted. At that point, the IServerContext and ILibrary reference count can be removed.

 

// Disconnect from the server

pServerContext->Disconnect();

 

// Release our server and library objects as well

pServerContext->DecRef();

pLibrary->DecRef();

 

In Java/.NET, when a client application has terminated all rendering sessions and does not need to maintain a connection with the XStream HDVR server it should disconnect from the server. This is done with the hdrcServerContext::disconnect() method. When the client disconnects from the server, all server side resources associated with that client session are deleted.

 

// Disconnect from the server

serverContext.disconnect();