Skip to content

Integration in Java

Loic Royer edited this page Mar 1, 2016 · 5 revisions

Java integration

This page is meant for people interested in a low-level Java integration of ClearVolume. If you are looking for Fiji/ImageJ2 or KNIME integration plugins, please click on the corresponding link.

ClearVolume is primarily a Java project. Therefore, integration of ClearVolume into Java based microscope control software is the most straightforward and flexible. There two main API levels: the low-level interface provides just the core volume rendering capabilities, whereas the high-level API offers a more powerfull framework that supports filtering, time-shifting, asynchronous passing, remote viewing and more.

Please note that the ClearVolume Java source code has tests and demos that are the best and most up-to-date information on how to use the library. There is a good chance that the code below does not work anymore with the latest version. Please consider the information below as illustrative and rely on the examples and tests provided in the repository as the authoritative reference.

Gradle/Maven Artifacts:

You can use your favourite dependency/build tool to get ClearVolume:

compile group: "net.clearvolume", name: "clearvolume", version: "(,1.1]"

repositories {
    maven { url  "http://dl.bintray.com/clearvolume/ClearVolume" }
    maven { url  "http://dl.bintray.com/rtlib/CoreMem" }
}

Low-level interface

The low-level API has the simplicity of its elegance. The following code is sufficient to create a renderer and display some synthetic volumetric data:

#!java

// obtain the best renderer, this usually means picking the best supported GPU programming framwork such as CUDA or OpenCL or GLSL on the most performant GPU installed.
final ClearVolumeRendererInterface lClearVolumeRenderer = ClearVolumeRendererFactory.newBestRenderer("ClearVolumeTest",768,768,NativeTypeEnum.UnsignedShort,768,768,2);

// Different transfer functions can be used:
lClearVolumeRenderer.setTransferFunction(TransferFunctions.getGrayLevel());
lClearVolumeRenderer.setVisible(true);

// volume dimensions:
final int lResolutionX = 256;
final int lResolutionY = 256;
final int lResolutionZ = 256;

// size of the buffer in bytes (16bit data)
final int lLengthInBytes = lResolutionX * lResolutionY* lResolutionZ* 2;

// standard java byte array for holding the data:
final byte[] lVolumeDataArray = new byte[lLengthInBytes];

// Filling the buffer with some dummy yet interesting looking data:
for (int z = 0; z < lResolutionZ; z++)
	for (int y = 0; y < lResolutionY; y++)
		for (int x = 0; x < lResolutionX; x++)
		{
			final int lIndex = 2 * (x + lResolutionX * y + lResolutionX * lResolutionY * z);
			lVolumeDataArray[lIndex + 1] = (byte) (((byte) x ^ (byte) y ^ (byte) z));
		}

		
// sets the current buffer to be displayed:
lClearVolumeRenderer.setVolumeDataBuffer(ByteBuffer.wrap(lVolumeDataArray), lResolutionX, lResolutionY, lResolutionZ);

// Waits for the renderer's window to be closed:
while (lClearVolumeRenderer.isShowing())
{
	Thread.sleep(100);
}

// closes the renderer and release all ressources:
lClearVolumeRenderer.close();

}

High-level interface

The high-level API has much more functionality. It relies on the notion of volume sinks and sources that allow to compose volume processing, distribution and visualisation pipelines. The following code illustrates how to create a renderer that supports asynchronous passing, channel filtering, and time-shifting.

#!java

// Just as for the low-level API we create the best possible renderer:
ClearVolumeRendererInterface lClearVolumeRenderer = ClearVolumeRendererFactory.newBestRenderer("ClearVolume", pWindowWidth, pWindowHeight, pBytesPerVoxel,	pMaxTextureWidth, pMaxTextureHeight);

// We make the renderer visible:
lClearVolumeRenderer.setVisible(true);

// To avoid memory-trashing we use a VolumeManager to handle the lifcycle of data buffers: 
VolumeManager lVolumeManager = lClearVolumeRenderer.createCompatibleVolumeManager(sMaxAvailableVolumes);

// We create a renderer sink that wraps the renderer:
ClearVolumeRendererSink lClearVolumeRendererSink 
	= new ClearVolumeRendererSink(lClearVolumeRenderer, lVolumeManager, sMaxMillisecondsToWaitForCopy, TimeUnit.MILLISECONDS);

// We create a time shifting sink that will first receive volumes:
TimeShiftingSink lTimeShiftingSink = new TimeShiftingSink(sTimeShiftSoftHoryzon, sTimeShiftHardHoryzon);

// We create a GUI element for the TimeShift functionality:
TimeShiftingSinkJFrame lTimeShiftingSinkJFrame = new TimeShiftingSinkJFrame(lTimeShiftingSink);
lTimeShiftingSinkJFrame.setVisible(true);
	
// In addtion to the time-shiting feature, we also want to select which channels (views or/and colors) we want to display. For this we need a ChannelFilterSink:
ChannelFilterSink lChannelFilterSink 
	= new ChannelFilterSink(lTimeShiftingSink.setRelaySink);

// And a corresponding GUI element:
ChannelFilterSinkJFrame lChannelFilterSinkJFrame 
	= new ChannelFilterSinkJFrame(lChannelFilterSink);
lChannelFilterSinkJFrame.setVisible(true);

// Finally we need an intermediate sink that enables asynchronous decoupling:
AsynchronousVolumeSinkAdapter lAsynchronousVolumeSinkAdapter 
	= new AsynchronousVolumeSinkAdapter(lSinkAfterAsynchronousVolumeSinkAdapter, sMaxQueueLength, sMaxMillisecondsToWait, TimeUnit.MILLISECONDS);

// Now we assemble the chain:

// First element in the chain is the asynchronous sink:
lAsynchronousVolumeSinkAdapter.setRelaySink(lTimeShiftingSink);

// Then the  time-shift sink forwards volumes to the channel filter:
lTimeShiftingSink.setRelaySink(lChannelFilterSink);

// Which filters and sends the appropriate volume to the renderer:
lChannelFilterSink.setRelaySink(lClearVolumeRendererSink);

// When using a time-shifting sink the renderer sink is not responsible for managing the life-cycle of the volumes, therefore we ask the renderer to forward volumes to a null sink that does nothing: 
lClearVolumeRendererSink.setRelaySink(new NullVolumeSink());




// this starts a thread that will process incoming volumes asynchronously.
lAsynchronousVolumeSinkAdapter.start();

//  send volumes:

// The Volume class encapsulates data aand metadata, here (java char) 16 bit unsigned data:
Volume<Character> lRequestedVolume =
	lVolumeManager.requestAndWaitForVolume(sMaxMillisecondsToWait, TimeUnit.MILLISECONDS, Character.class, 1, pWidthInVoxels, pHeightInVoxels, pDepthInVoxels);
// Note the dimensionns are (1, pWidthInVoxels, pHeightInVoxels, pDepthInVoxels) the first 1 means that the 'vector' length is 1, this is to provide futur support for volumes that have vector data for each voxel.

// Now you can set the data and metadata of the volume by calling methods of the Volume class...

// Now you pass teh volume:
lVolumeSinkInterface.sendVolume(lRequestedVolume);

// here you can loop and/or request and send more volumes...

// Once we are done, we can stip the asynchronous processing:
lAsynchronousVolumeSinkAdapter.stop();

// And close everything...
lChannelFilterSinkJFrame.close();
lChannelFilterSinkJFrame.close();
lClearVolumeRenderer.close();