3D Example app

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.

HelloFoviaDetail

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
View source /foviaserver/public/apps/hellofovia/hellofovia.js

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);
    }