Changing Units for a vtkMRMLScalarVolume Node

I currently have a module where I load a volume with units of “Shear Wave Velocity”. I want users to be able to select different units for the volume such as “Young’s Modulus” or “Shear Modulus”. When different units are selected I need the threshold, window/level, and color bar to convert accordingly. The conversion equation for Shear Wave Velocity to Young’s Modulus is
youngs_modulus = (shear_wave_velocity ^ 2) * 3.0

Shear wave velocity:

Young’s Modulus:

But I also want to save the voxel data somewhere in its original Shear Wave Velocity units so I can calculate things based off the original data.

The way I currently do this is that I have the original volume (vtkMRMLScalarVolumeNode1) which the user never sees, and a display volume (vtkMRMLScalarVolumeNode2). When the user selects new units, I get the voxel value array of vtkMRMLScalarVolume1 with slicer.util.arrayFromVolume(vol_1).copy(), run my conversion equation on the array, and change the voxel values of my display volume with slicer.util.updateVolumeFromArray(new_arr, vol_2)

Unfortunately I then have to also convert the threshold min max and window/level of the display volume to the new units. Is there a better way to do this rather than have a second vtkMRMLScalarVolumeNode for display? Like could I have multiple vtkMRMLScalarVolumeDisplayNodes for vtkMRMLScalarVolumeNode1 which are in different units than vtkMRMLScalarVolumeNode1?

cc: @lassoan, @pieper, @jcfr

Are there examples of other image sets where Slicer already does some changing to a different unit representation?

To show a volume and do things like run stats on it, would you have to create a new volume node with the image data scaled to the new representation? Or is this something that should actually be handled by a display node? A new volume node would seemingly be an expensive operation if changing representation of a sequence volume where there is a lot more data.

The described situation of different unit representations and their relationship to MRML widgets such as for Window/Level and Threshold seem to be a bit of a bear if representations can’t be synced somehow. Such as if one uses a threshold min/max of 3/10 and then you select a representation which is the values squared, you would want the widget values to be 9/100. This would maintain the same pixels shown just in a different unit representation.

Thresholding and window/level is easy, you just set up the display node when you generate the volume.

Scalar bar is a bit more tricky, because you need to set the unit manually in the color bar (in DataProbe module). The proper solution would be to use VoxelValueQuantity and VoxelValueUnits properties of the volume node and generate the label based on that automatically. You can either update the DataProbe module to do this (it is just a Python scripted module that you can change easily; we can help you getting started) or you can implement this label update in your own module.

There is absolutely nothing wrong with this. Your module can very quickly and easily can generate all the quantities that you are interested in. Probably the simplest is to generate all the volumes the user could be interested in. Since ultrasound images are relatively small (usually medium-resolution 2D+t images; or low-resolution 3D+t images) computation time and memory needs should be negligible.

We currently do all real world value mapping (RWVM) at import/export. There are so many ways of converting stored values to real world values (transforming with linear or arbitrary non-linear function, piece-wise linear function, or lookup-tables) that it would not be feasible to build that into all visualization and processing functions. If we added RWVM infrastructure to MRML then we would need to store both the raw and converted volume, thereby increasing complexity, memory usage, potential inconsistencies when saving/loading the data (or slower loading due to conversion after loading).

MRML scalar volume can already store quantity and units, which allows modules to cleanly manage different raw and real world value volumes. What should be improved is how this information is utilized - for example in Data Probe, scalar bar, sliders/spinboxes that display voxel values.