Skip to content

Commit

Permalink
BUG: Fix color synchronization inaccuarcy in volume rendering module
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lassoan committed Jun 27, 2023
1 parent 4a4d3b4 commit 35e5586
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 61 deletions.
Expand Up @@ -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<vtkColorTransferFunction> 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<double>(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();
Expand Down Expand Up @@ -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();
Expand Down
Expand Up @@ -36,7 +36,6 @@ class vtkMRMLVolumePropertyNode;

// VTK includes
class vtkColorTransferFunction;
class vtkLookupTable;
class vtkPiecewiseFunction;
class vtkScalarsToColors;
class vtkVolumeProperty;
Expand Down Expand Up @@ -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].
Expand All @@ -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.
Expand Down

0 comments on commit 35e5586

Please sign in to comment.