From c536be0b883bb9bf1d984c216270a8057ee41e57 Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Sat, 24 Jun 2023 15:22:17 -0400 Subject: [PATCH] BUG: Fix color synchronization inaccuarcy in volume rendering module When a color node with few color table entries (such as fMRI) was used for showing a volume, the "Synchronize with Volumes module" button in volume rendering module very inaccurately copied the color entries to the color transfer function. The problem was that only every 64th color table entry was copied. Fixed the issue by copying up to 24 color entries (evenly sampled in the lookup table) of the table into the transfer function. This number is large enough to accurately represent a color table, but not too high so that users can still edit the color transfer manually. Also fixed synchronization of continuous color mapping. All control points of continuous color maps are copied, so these are copied in full fidelity. Fixes the problem reported at https://discourse.slicer.org/t/the-colormap-in-volume-rendering-is-wrong-when-using-fmri-lookup-table-in-volume/30186 --- .../Logic/vtkSlicerVolumeRenderingLogic.cxx | 144 +++++++++++------- .../Logic/vtkSlicerVolumeRenderingLogic.h | 5 +- 2 files changed, 88 insertions(+), 61 deletions(-) diff --git a/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.cxx b/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.cxx index fee181b602e..862c7bdddf6 100644 --- a/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.cxx +++ b/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.cxx @@ -488,80 +488,108 @@ void vtkSlicerVolumeRenderingLogic::SetThresholdToVolumeProp( //---------------------------------------------------------------------------- void vtkSlicerVolumeRenderingLogic::SetWindowLevelToVolumeProp( - double scalarRange[2], double windowLevel[2], vtkLookupTable* lut, vtkVolumeProperty* volumeProp) + double scalarRange[2], double windowLevel[2], vtkScalarsToColors* colors, vtkVolumeProperty* volumeProp) { if (!volumeProp || !scalarRange || !windowLevel) - { + { vtkWarningMacro("SetWindowLevelToVolumeProp: Inputs do not exist."); return; - } - - double windowLevelMinMax[2]; - windowLevelMinMax[0] = windowLevel[1] - 0.5 * windowLevel[0]; - windowLevelMinMax[1] = windowLevel[1] + 0.5 * windowLevel[0]; + } - double previous = VTK_DOUBLE_MIN; + double windowLevelMinMax[2] = + { + windowLevel[1] - 0.5 * windowLevel[0], + windowLevel[1] + 0.5 * windowLevel[0], + }; vtkNew colorTransfer; - const int size = lut ? lut->GetNumberOfTableValues() : 0; - if (size == 0) - { - const double black[3] = {0., 0., 0.}; - const double white[3] = {1., 1., 1.}; - colorTransfer->AddRGBPoint(scalarRange[0], black[0], black[1], black[2]); - colorTransfer->AddRGBPoint(windowLevelMinMax[0], black[0], black[1], black[2]); - colorTransfer->AddRGBPoint(windowLevelMinMax[1], white[0], white[1], white[2]); - colorTransfer->AddRGBPoint(scalarRange[1], white[0], white[1], white[2]); - } - else if (size == 1) - { - double color[4]; - lut->GetTableValue(0, color); - - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous), - color[0], color[1], color[2]); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[0], previous), - color[0], color[1], color[2]); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous), - color[0], color[1], color[2]); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous), - color[0], color[1], color[2]); - } - else // if (size > 1) - { - previous = VTK_DOUBLE_MIN; - - double color[4]; - lut->GetTableValue(0, color); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous), - color[0], color[1], color[2]); + vtkColorTransferFunction* inputColorTransfer = vtkColorTransferFunction::SafeDownCast(colors); + if (inputColorTransfer) + { + // Colors are defined by transfer function + // We cannot simply copy but we need to scale and offset as specified by window/level + double inputColorTransferRange[2] = { 0.0, 1.0 }; + inputColorTransfer->GetRange(inputColorTransferRange); + const double scale = (windowLevelMinMax[1] - windowLevelMinMax[0]) / (inputColorTransferRange[1] - inputColorTransferRange[0]); + const double offset = windowLevelMinMax[0] - scale * inputColorTransferRange[0]; + const vtkIdType colorCount = inputColorTransfer->GetSize(); + double color_X_RGB_MS[6] = { 0., 0., 0., 0., 0.5, 1.0 }; // x, RGB, midpoint, sharpness + for (vtkIdType i = 0; i < colorCount; ++i) + { + inputColorTransfer->GetNodeValue(i, color_X_RGB_MS); + colorTransfer->AddRGBPoint(offset + color_X_RGB_MS[0] * scale, + color_X_RGB_MS[1], color_X_RGB_MS[2], color_X_RGB_MS[3], color_X_RGB_MS[4], color_X_RGB_MS[5]); + } + } + else + { + // Colors are defined by lookup table + vtkLookupTable* lut = vtkLookupTable::SafeDownCast(colors); + double previous = VTK_DOUBLE_MIN; + const vtkIdType numberOfColors = lut ? lut->GetNumberOfTableValues() : 0; + if (numberOfColors == 0) + { + const double black[3] = { 0., 0., 0. }; + const double white[3] = { 1., 1., 1. }; + colorTransfer->AddRGBPoint(scalarRange[0], black[0], black[1], black[2]); + colorTransfer->AddRGBPoint(windowLevelMinMax[0], black[0], black[1], black[2]); + colorTransfer->AddRGBPoint(windowLevelMinMax[1], white[0], white[1], white[2]); + colorTransfer->AddRGBPoint(scalarRange[1], white[0], white[1], white[2]); + } + else if (numberOfColors == 1) + { + double color[4]; + lut->GetTableValue(0, color); - double value = windowLevelMinMax[0]; + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous), + color[0], color[1], color[2]); + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[0], previous), + color[0], color[1], color[2]); + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous), + color[0], color[1], color[2]); + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous), + color[0], color[1], color[2]); + } + else // if (numberOfColors > 1) + { + double color[4] = { 0.0, 0.0, 0.0, 1.0 }; + lut->GetTableValue(0, color); + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous), + color[0], color[1], color[2]); - double step = windowLevel[0] / (size - 1); + // We place up to maxNumberOfPoints points in the color transfer function. + // The number is high enough to accurately describe most color tables, + // but not too high so that the user can still edit the function manually. + const vtkIdType maxNumberOfPoints = 24; + + const vtkIdType numberOfPoints = std::min(numberOfColors, maxNumberOfPoints); + // convert from point index to color index + double pointIndexScale = static_cast(numberOfColors - 1) / (numberOfPoints - 1); + double offset = windowLevelMinMax[0]; + double scale = windowLevel[0] / (numberOfColors - 1); + for (vtkIdType pointIndex = 0; pointIndex < numberOfPoints; ++pointIndex) + { + vtkIdType colorIndex = pointIndex * pointIndexScale; + lut->GetTableValue(colorIndex, color); + const double value = offset + colorIndex * scale; + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(value, previous), + color[0], color[1], color[2]); + } - int downSamplingFactor = 64; - for (int i = 0; i < size; i += downSamplingFactor, - value += downSamplingFactor*step) - { - lut->GetTableValue(i, color); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(value, previous), + lut->GetTableValue(numberOfColors - 1, color); + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous), + color[0], color[1], color[2]); + colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous), color[0], color[1], color[2]); + } } - lut->GetTableValue(size - 1, color); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous), - color[0], color[1], color[2]); - colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous), - color[0], color[1], color[2]); - } - vtkColorTransferFunction *volumePropColorTransfer = volumeProp->GetRGBTransferFunction(); if (this->IsDifferentFunction(colorTransfer.GetPointer(), volumePropColorTransfer)) - { + { volumePropColorTransfer->DeepCopy(colorTransfer.GetPointer()); - } + } volumeProp->SetInterpolationTypeToLinear(); volumeProp->ShadeOn(); @@ -727,7 +755,7 @@ void vtkSlicerVolumeRenderingLogic::CopyScalarDisplayToVolumeRenderingDisplayNod threshold[1] = vpNode->GetWindowLevelMax(); } - vtkLookupTable* lut = vpNode->GetColorNode() ? vpNode->GetColorNode()->GetLookupTable() : nullptr; + vtkScalarsToColors* lut = vpNode->GetColorNode() ? vpNode->GetColorNode()->GetScalarsToColors() : nullptr; vtkVolumeProperty *prop = vspNode->GetVolumePropertyNode()->GetVolumeProperty(); int disabledModify = vspNode->StartModify(); diff --git a/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.h b/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.h index 2a397bd44b8..1a7e4ab15e9 100644 --- a/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.h +++ b/Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.h @@ -36,7 +36,6 @@ class vtkMRMLVolumePropertyNode; // VTK includes class vtkColorTransferFunction; -class vtkLookupTable; class vtkPiecewiseFunction; class vtkScalarsToColors; class vtkVolumeProperty; @@ -217,7 +216,7 @@ class VTK_SLICER_VOLUMERENDERING_MODULE_LOGIC_EXPORT vtkSlicerVolumeRenderingLog /// \sa SetThresholdToVolumeProp void SetWindowLevelToVolumeProp( double scalarRange[2], double windowLevel[2], - vtkLookupTable* lut, vtkVolumeProperty* node); + vtkScalarsToColors* lut, vtkVolumeProperty* node); /// Create an opacity transfer function for gradient opacity. /// It ranges from 0 to scalarRange[1] - scalarRange[0]. @@ -229,7 +228,7 @@ class VTK_SLICER_VOLUMERENDERING_MODULE_LOGIC_EXPORT vtkSlicerVolumeRenderingLog /// transfer function from the labelmap LUT \a colors. /// \sa SetWindowLevelToVolumeProp, SetThresholdToVolumeProp void SetLabelMapToVolumeProp( - vtkScalarsToColors* lut, vtkVolumeProperty* node); + vtkScalarsToColors* colors, vtkVolumeProperty* node); /// Update DisplayNode from VolumeNode, /// Can pass a VolumePropertyNode and an ROI node to be the display node.