This expands upon the /foviaserver/public/apps/hellofovia/hellofovia5.js (5
line) example with additional core concepts that may be useful for your first application. Be
sure to open the JavaScript console to look at various printouts to
better understand the object model.
This example includes
use of scanDICOMDir to return a list of available studies/series
use of Fovia.UI.HTMLViewport3D w/ repaintable viewports for rendering contents
use of onImageMetaDataReceived callback to access information on the rendered frame
use of onDrawCallback to draw a DICOM overlay on the rendered frame
use of a resize handler to respond to windows resize events
To run the example, click on the local link of http://localhost:8088/apps/hellofovia/hellofovia.html
Loading Data
Earlier examples used Fovia.ServerContext.loadDICOMDir(directoryPath)
for creating volumes. While this is a single command that loads
the data and builds the volume, it is limited to the case of a single 3D-able series in the directory. A more flexible model involves use of Fovia.ServerContext.scanDICOMDir(directoryPath)
to parse the directory and returns a JSON object that describes the
DICOM structure of all files in the directory. By reading the
JSON tree structure, a user interface can be built that allows the user
to select which study/series to load.
Review the ScanDirResults in the web console for these lines:
// print out the study details for each study found for (var i = 0; i < scanDirResults.getNumStudies(); i++) { console.log("ScanDirSeriesResults[" + i + "]"); console.log(scanDirResults.findStudy(i)); }
Each study contains one or more ScanDirSeriesResults for each DICOM series, which includes the seriesInstanceUID, the dicomSeries (SeriesDataContext), and a property containsSubSeries. If containsSubSeries is true, an array of subSeries (SeriesDataContext)
objects is available that organizes the DICOM series into chunks based
the internal splitting logic, review DICOM Splitting for more details. The dicomSeries and optional subSeries are a
SeriesDataContext, which include a set of series and image level DICOM tag values, along with a flag is3DableSeries. Any SeriesDataContext that has a is3DableSeries flag of true can be used to build a 3D volume Fovia.ServerContext.loadVolumeFromSeries(sdc).
The following code will create a volume for the first series of
the first study, your code should first verify that it is an is3DableSeries.
var seriesList = scanDirResults.findStudy(0); var sdc = seriesList[0].dicomSeries; loadVolumeFromSeries(sdc).then(function (volumeDataContext)
Any dicomSeries or subSeries can be used to build a volume, assuming series X and subSeries Y is is3DableSeries.
var sdc = seriesList[x].subSeries[y]; loadVolumeFromSeries(sdc).then(function (volumeDataContext)
HTMLViewport
Fovia.UI.HTMLViewport3D associated an HTML canvas object and a Fovia.renderEngine3D.
It is the responsibility of the HTMLViewport to paint the image
onto the HTML canvas, draw other elements (such as annotation, DICOM
overlay, cross-reference lines, etc.), and manage and respond to
mouse/touch event.
The constructor
is synchronous call thats takes the HTML canvas name, viewport
width/height, as well as a boolean to indicate if the viewport is repaintable. If it is repaintable, an internal copy of the last rendered image is preserved so the client can initiate a repaint(),
which causes the last rendered image to be painted on the canvas, and
allows the client application to trigger a redraw of the canvas without
incurring a round trip to the server.
After construction, an asynchronous call is made to init that associates the volumeDataContext with the viewport, and its internalrenderEngine3D.
There are two optional callbacks that can be associated with this object,
specifically the onDrawCallback and onImageMetaDataReceived. These are
called each time an image is returned from the server. The last line triggers a renderFinal
operation, which internally updates the canvas with the new rendered
image. It is important to note that during initialization, a call to rernderFinal() should always make to ensure the highest quality rendering, the rernder()
call should be made during mouseDrag or other interactive operations
since it is rougly 10X faster (but may be with very little
visible quality difference), followed by a call to rernderFinal() to generate the higest quality rendering. It should also be noted that the rernder() will always use the higher compression interactiveJPEG quality setting, while the rernderFinal() will use either the finalJPEG quality setting or PNG, based on the Server Configuration Settings.
foviaViewport = new Fovia.UI.HTMLViewport3D("fovia", window.innerWidth,
window.innerHeight, true); // initialize the viewport with the volume foviaViewport.init(volumeDataContext, Fovia.RenderType.parallel,
onDrawCallback, onImageMetaDataReceived).then(function () { // trigger a final render operation, now that
everything is initialized foviaViewport.renderFinal(); });
onImageMetaDataReceived and onDrawCallback
The Fovia.UI.HTMLViewport3D.init
call takes two callbacks. The onImageMetaDataReceived in called as
soon an an image is returned from the server, but before it is drawn on
the canvas. This call gives the application a chance to capture
date related to the frame that may be useful for on-screen drawing, or
trigger additional events to other viewports / rendering engines.
The onDrawCallback is called at the end of the rendering pipeline,
after the image is drawn onto the canvas. This allows the
application to draw onto the HTML canvas (and over top of the
image). var onImageMetaDataReceived = function (data) { // data.dicomTags -- dicom tags associated with this image. (Image-level DICOM Tags) // data.imageStats -- internal time to create intermediate/final image, and the PNG/JPG image
// data.renderParams -- Fovia.renderParams3D used to create the image // data.scrollObject -- spacing and currentSlice out of totalSlice when navigating in slab/MPR mode // data.viewport -- the Fovia.UI.HTMLViewport3D for which this image will be drawn into // data.volumeDataContext --volumeDataContext (reference to the SeriesDataContext) }
The onDrawCallback allows the application do do its own rendering on top of the canvas.
var onDrawCallback = function (htmlFoviaViewport, canvas, width, height, onDrawCallbackComplete) {
// htmlFoviaViewport -- the Fovia.UI.HTMLViewport3D for which the image will be drawn into. The
// client most likely has its own refernce to this object, but is was returned for completeness
// width / height -- size of the associated htmlViewport and canvas
// canvas -- HTML Canvas object for which this image will be drawn into, and how the appplicaiton will
// get the context for its drawing. Do not try to get this directly from the htmlViewport since in the
// cases of double buffer, the canvas may be a double buffer and not the actual screen context
var context = canvas.getContext("2d");
// this MUST be the last statement executed so the client API knows all rendering of this frame
// is complete. If the application implements the onDrawCallback asynchronously, it is most likely
// the case onDrawCallbackComplete should occur be invoked once all client drawing is complete. If
// this is a double buffer viewport, this is the only way the client API knows drawing is complete and
// that it is safe to copy the double buffer into the screen canvas. onDrawCallbackComplete(); }
Window Event Handling
The last concept shown in this example application is how to
respond to resize window events. A default resizeHanlder is
installed using the following
statement. The code utilizes a
JavaScript timer that triggers the actual update atfter 100ms. If
triggered more frequently, it could generate too many renderEvents,
resulting in sluggish behavior:
var resizeHandler = function (event) {
// Process the resize event in this code; since some browsers trigger hundreds
// of events per resize, wait for 100ms before requesting an update.
var resizeTimer = undefined;
resizeTimer = setTimeout(() => { // stop the timer; clearTimeout(resizeTimer); resizeTimer = undefined;
// application method to redraw with new width/height forceReLayout();
}, 100);
}
3D Example app
This expands upon the /foviaserver/public/apps/hellofovia/hellofovia5.js (5 line) example with additional core concepts that may be useful for your first application. Be sure to open the JavaScript console to look at various printouts to better understand the object model.This example includes
To run the example, click on the local link of http://localhost:8088/apps/hellofovia/hellofovia.html
Loading Data
Earlier examples used Fovia.ServerContext.loadDICOMDir(directoryPath) for creating volumes. While this is a single command that loads the data and builds the volume, it is limited to the case of a single 3D-able series in the directory. A more flexible model involves use of Fovia.ServerContext.scanDICOMDir(directoryPath) to parse the directory and returns a JSON object that describes the DICOM structure of all files in the directory. By reading the JSON tree structure, a user interface can be built that allows the user to select which study/series to load.Review the ScanDirResults in the web console for these lines:
// print out the study details for each study found
for (var i = 0; i < scanDirResults.getNumStudies(); i++) {
console.log("ScanDirSeriesResults[" + i + "]");
console.log(scanDirResults.findStudy(i));
}
Each study contains one or more ScanDirSeriesResults for each DICOM series, which includes the seriesInstanceUID, the dicomSeries (SeriesDataContext), and a property containsSubSeries. If containsSubSeries is true, an array of subSeries (SeriesDataContext) objects is available that organizes the DICOM series into chunks based the internal splitting logic, review DICOM Splitting for more details. The dicomSeries and optional subSeries are a SeriesDataContext, which include a set of series and image level DICOM tag values, along with a flag is3DableSeries. Any SeriesDataContext that has a is3DableSeries flag of true can be used to build a 3D volume Fovia.ServerContext.loadVolumeFromSeries(sdc). The following code will create a volume for the first series of the first study, your code should first verify that it is an is3DableSeries.
var seriesList = scanDirResults.findStudy(0);
var sdc = seriesList[0].dicomSeries;
loadVolumeFromSeries(sdc).then(function (volumeDataContext)
Any dicomSeries or subSeries can be used to build a volume, assuming series X and subSeries Y is is3DableSeries.
var sdc = seriesList[x].subSeries[y];
loadVolumeFromSeries(sdc).then(function (volumeDataContext)
HTMLViewport
Fovia.UI.HTMLViewport3D associated an HTML canvas object and a Fovia.renderEngine3D. It is the responsibility of the HTMLViewport to paint the image onto the HTML canvas, draw other elements (such as annotation, DICOM overlay, cross-reference lines, etc.), and manage and respond to mouse/touch event.
The constructor is synchronous call thats takes the HTML canvas name, viewport width/height, as well as a boolean to indicate if the viewport is repaintable. If it is repaintable, an internal copy of the last rendered image is preserved so the client can initiate a repaint(), which causes the last rendered image to be painted on the canvas, and allows the client application to trigger a redraw of the canvas without incurring a round trip to the server.
After construction, an asynchronous call is made to init that associates the volumeDataContext with the viewport, and its internal renderEngine3D. There are two optional callbacks that can be associated with this object, specifically the onDrawCallback and onImageMetaDataReceived. These are called each time an image is returned from the server. The last line triggers a renderFinal operation, which internally updates the canvas with the new rendered image. It is important to note that during initialization, a call to rernderFinal() should always make to ensure the highest quality rendering, the rernder() call should be made during mouseDrag or other interactive operations since it is rougly 10X faster (but may be with very little visible quality difference), followed by a call to rernderFinal() to generate the higest quality rendering. It should also be noted that the rernder() will always use the higher compression interactiveJPEG quality setting, while the rernderFinal() will use either the finalJPEG quality setting or PNG, based on the Server Configuration Settings.
foviaViewport = new Fovia.UI.HTMLViewport3D("fovia", window.innerWidth, window.innerHeight, true);
// initialize the viewport with the volume
foviaViewport.init(volumeDataContext, Fovia.RenderType.parallel, onDrawCallback,
onImageMetaDataReceived).then(function () {
// trigger a final render operation, now that everything is initialized
foviaViewport.renderFinal();
});
onImageMetaDataReceived and onDrawCallback
The Fovia.UI.HTMLViewport3D.init call takes two callbacks. The onImageMetaDataReceived in called as soon an an image is returned from the server, but before it is drawn on the canvas. This call gives the application a chance to capture date related to the frame that may be useful for on-screen drawing, or trigger additional events to other viewports / rendering engines. The onDrawCallback is called at the end of the rendering pipeline, after the image is drawn onto the canvas. This allows the application to draw onto the HTML canvas (and over top of the image).
var onImageMetaDataReceived = function (data) {
// data.dicomTags -- dicom tags associated with this image. (Image-level DICOM Tags)
// data.imageStats -- internal time to create intermediate/final image, and the PNG/JPG image
// data.renderParams -- Fovia.renderParams3D used to create the image
// data.scrollObject -- spacing and currentSlice out of totalSlice when navigating in slab/MPR mode
// data.viewport -- the Fovia.UI.HTMLViewport3D for which this image will be drawn into
// data.volumeDataContext -- volumeDataContext (reference to the SeriesDataContext)
}
The onDrawCallback allows the application do do its own rendering on top of the canvas.
var onDrawCallback = function (htmlFoviaViewport, canvas, width, height, onDrawCallbackComplete) {
// htmlFoviaViewport -- the Fovia.UI.HTMLViewport3D for which the image will be drawn into. The
// client most likely has its own refernce to this object, but is was returned for completeness
// width / height -- size of the associated htmlViewport and canvas
// canvas -- HTML Canvas object for which this image will be drawn into, and how the appplicaiton will
// get the context for its drawing. Do not try to get this directly from the htmlViewport since in the
// cases of double buffer, the canvas may be a double buffer and not the actual screen context
var context = canvas.getContext("2d");
// this MUST be the last statement executed so the client API knows all rendering of this frame
// is complete. If the application implements the onDrawCallback asynchronously, it is most likely
// the case onDrawCallbackComplete should occur be invoked once all client drawing is complete. If
// this is a double buffer viewport, this is the only way the client API knows drawing is complete and
// that it is safe to copy the double buffer into the screen canvas.
onDrawCallbackComplete();
}
Window Event Handling
The last concept shown in this example application is how to respond to resize window events. A default resizeHanlder is installed using the following statement. The code utilizes a JavaScript timer that triggers the actual update atfter 100ms. If triggered more frequently, it could generate too many renderEvents, resulting in sluggish behavior:window.addEventListener("resize", resizeHandler, false);
var resizeHandler = function (event) {
// Process the resize event in this code; since some browsers trigger hundreds
// of events per resize, wait for 100ms before requesting an update.
var resizeTimer = undefined;
resizeTimer = setTimeout(() => {
// stop the timer;
clearTimeout(resizeTimer);
resizeTimer = undefined;
// application method to redraw with new width/height
forceReLayout();
}, 100);
}