In this assignment, you will write code to visualize three-dimensional scalar fields through the use of volume rendering. We will rely on a volume renderer from the VTK library.

Please click here to create your repository

Objectives

This assignment is designed to teach more advanced scalar field visualization through understanding how transfer functions work, utilizing a mapping from data values to optical properties. Specific objectives include:

  • Utilizing transfer functions to specify the input to a volume renderer.
  • Implementing an interface to specify transfer functions using d3.js.
  • Understanding how interacting with a transfer function can be used to investigate volumetric data.
  • Experimenting with your designed transfer functions to investigate three-dimensional scalar fields.

Description

In this assignment, we will be utilizing volumetric scalar fields. Your template repo includes four example scalar fields in the datasets directory. These are encoded as a .vti (VTK ImageData) format, which will be parsed for you using the vtk.js library. This library will also do the heavy lifting of computing the volume rendered image. Your main task is to build an interface in d3.js to design the transfer functions that vtk.js will adjust how the final image looks.

We have provided a significant amount of skeleton code for you in volren.js as well as a11.js. volren.js should not need to be edited. It includes two functions initializeVR() and updateVR() that will create a volume rendering canvas as well as update it with modified transfer functions.

The template html file, index.html, is set up so that it has two different div’s, one for the volume renderer, with id volren, and one for the transfer function editor, with id tfunc. tfunc will ultimately contain an SVG that you specify using d3.js.

Your transfer function editor should be designed to modify and visualize the contents of two variables: colorTF and opacityTF, both declared in a11.js. To accomplish this, you will use d3.js to populate the contents of an SVG objects stored in the div tfunc.

Both transfer functions will be encoded as a simple list of pairs of the format [t, val] where t is a particular scalar value and val is either an opacity (between \([0,1]\)) or a color (specified as a d3.rgb() object). These pairs are control points that VTK will linearly interpolate for you to define the appropriate color (emission) and opacity (absorption) values for any scalar value.

Part 1: Implementing Color Transfer Functions

First, you will implement three different color transfer functions. These can be selected by the user by pressing a button in index.html (we’ve provided an example of one such button for you, you should add two more).

For each button press, you should update the variable colorTF and populate it with a color transfer function that you specify in a11.js. There should be three types, based on a sequential color map of your choice, a diverging color map of your choice, and a categorical color map of your choice. You are welcome to specify these as a manual list, but you may also want to use many of the predefined color maps that d3 constructors for you. The example code and optional reading discussed in L12 and its optional reading show some examples here (see d3-scale-chromatic as well).

Specifying a color transfer function requires two pieces. You must: (1) specify the variable colorScale that will be used to interpolate color for your interface and (2) specify the variable colorTF that will be a list of colors you pass to VTK.

Your specification should cover the full range of data values. When the input dataset is loaded by the function initializeVR, it will populate a global variable dataRange with the minimum (dataRange[0]) and maximum (dataRange[1]) of the dataset. You should use this to specify a list of color values in colorTF.

For the sequential scale, the colorTF you specify must correspond to a sequential color map. I recommend using at least a few control points for this (d3.schemeXXX allows you to design a variety of scales this way, typically using 3 to 9 control points). One way to do this is use the function makeSequential() to both update colorScale and then use the updated colorScale to specify a list of points for colorTF.

The diverging scale should be constructed similarly, except using a diverging color map.

The categorical scale can be used to provide a coarse set of labels to different ranges of scalar values. To do this, instead of using a d3.scaleLinear, I used a d3.scaleQuantize with an appropriate categorical color scheme.

Once a new color transfer function is specified, you should update the visualization of the interface by calling updateTFunc() and update the transfer function with the volume renderer by calling updateTF. Note that, when using a categorical color map, you should set the optional third parameter of updateTF() to true. When set to false, it forces VTK to linearly interpolate the color map, when set to true, it will use nearest neighbor interpolation, and when undefined, it will keep the current state.

Finally, after implementing these three variants for color maps, you should generate a visualization that shows them in the tfunc view. I did this using a simple color bar (a list of rectangles) which was sampled across the dataRange and populating their fill color with colorScale. I also provided a horizontal axis that indicated the domain of the transfer function and aligned with the color bar (this made use of the xScale variable described below).

Part 2: Implementing Opacity Transfer Functions

Your opacity transfer function should be specified by a list of control points in the variable opacityTF. Like color transfer functions, your opacity transfer function should be specified by a list of control points, this time stored in the variable opacityTF. This list will be passed to VTK to control the volume rendering process.

Specifically, you will implement an editable curve interface for this. You should initialize opacityTF with at least five control points. Each of these will be visualized by drawing a polyline where you can drag the corners of the polyline to update the control points of opacityTF. This will require two scales, xScale and yScale that will map from data attributes to screen space. Specifically, xScale should map from the range of data values and yScale should map from the space of possible opacities (\([0,1]\)).

To draw an editable curve, we will draw both a polyline as well as a set of discrete circles. The circles will be hooked to a d3 drag interface, specified by 3 functions for the start, movement, and end of a drag event. The start event will set a variable selected for which control point is selected, which we do using a variable index. The drag event should access the position of the mouse and update opacityTF as the mouse moves. When this happens, you should then update both the visualization of the polylne by calling updateTFunc() and update the volume renderer by calling updateVR. You might find this example of a line chart with draggable circles particularly helpful. (Note: older version of d3 had a slightly different signature for drag events, so when you’re looking for examples online keep this in mind. See https://observablehq.com/@d3/d3v6-migration-guide#events for more details.)

When editing the curve, you must handle two special cases. First, if the point being dragged is one of the endpoints of the curve, you should only allow it to move up or down. This will ensure that all possible scalar values have a defined opacity. Second, when moving an interior point to the polyline, you not allow it to move so far left or right so as to cross the point before or after it in the curve. Both of these constraints can be checked by looking at the value pos defined in the function dragged(). All points must only be allowed to be moved within the range of valid control points. All \(x\)-coordinates must lie within dataRange and all \(y\)-coordinates must lie within \([0,1]\).

When moving points, I also changed their fill color to represent the color specified by colorScale. This isn’t required, but I found it added some context to which opacity values were mapped to which colors.

Submission

You should use git to submit all source code files. The expectation is that your code will be graded by cloning your repo and then executing it within a modern browser (Chrome, Firefox, etc.)

Please provide a README.md file that provides a text description of how to run your program and any parameters that you used. Also document any idiosyncrasies, behaviors, or bugs of note that you want us to be aware of.

To summarize, my expectation is that your repo will contain:

  1. A README.md file
  2. A index.html file
  3. An a11.js file
  4. All other Javascript files necessary to run the code (including volren.js, vtk.js, and d3.js plus any others you require)
  5. Any .css files containing style information

Grading

Deductions

Reason Value
Bugs or syntax errors -10 each bug at grader's discretion to fix


Point Breakdown of Features

Requirement Value
Consistent modular coding style 5
External documentation (README.md) following the template provided in the base repository 5
Header documentation, Internal documentation (Block for functions and Inline descriptive comments). Wherever applicable / for all files 10
Expected output / behavior for your chart based on the assignment specification, including

Implementing a sequential color transfer function5
Implementing a diverging color transfer function5
Implementing a categorical color transfer function5
Allowing the user to switch between color transfer functions with buttons10
Visualizing the color transfer function with a color bar10
Using a curve to specify the opacity transfer function15
Implementing dragging to update the opacity transfer function15
Correctly visualizing all contextual elements (axes, scales, etc.)15

80
Total 100/100


Cumulative Relationship to Final Grade

Worth 5% of your final grade

Extra Credit

Implementing features above and beyond the specification may result in additional extra credit, please document these in your README.md. Particularly, you may want to consider additional user interface features that allow you to inspect and/or edit the transfer function.

Instead of using buttons to specify the color transfer function, you are welcome to consider a more sophisticated interface when the user can design a color transfer function. Depending on the sophistication level, this is worth as much as 50% extra credit. If you choose to implement such an interface, you need not support the button-based selection, provided they support designing colorTF’s which are equally or more sophisticated.