Skip to content
This repository was archived by the owner on May 6, 2025. It is now read-only.

Commit 35e5586

Browse files
committed
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
1 parent 4a4d3b4 commit 35e5586

File tree

2 files changed

+88
-61
lines changed

2 files changed

+88
-61
lines changed

Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.cxx

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -488,80 +488,108 @@ void vtkSlicerVolumeRenderingLogic::SetThresholdToVolumeProp(
488488

489489
//----------------------------------------------------------------------------
490490
void vtkSlicerVolumeRenderingLogic::SetWindowLevelToVolumeProp(
491-
double scalarRange[2], double windowLevel[2], vtkLookupTable* lut, vtkVolumeProperty* volumeProp)
491+
double scalarRange[2], double windowLevel[2], vtkScalarsToColors* colors, vtkVolumeProperty* volumeProp)
492492
{
493493
if (!volumeProp || !scalarRange || !windowLevel)
494-
{
494+
{
495495
vtkWarningMacro("SetWindowLevelToVolumeProp: Inputs do not exist.");
496496
return;
497-
}
498-
499-
double windowLevelMinMax[2];
500-
windowLevelMinMax[0] = windowLevel[1] - 0.5 * windowLevel[0];
501-
windowLevelMinMax[1] = windowLevel[1] + 0.5 * windowLevel[0];
497+
}
502498

503-
double previous = VTK_DOUBLE_MIN;
499+
double windowLevelMinMax[2] =
500+
{
501+
windowLevel[1] - 0.5 * windowLevel[0],
502+
windowLevel[1] + 0.5 * windowLevel[0],
503+
};
504504

505505
vtkNew<vtkColorTransferFunction> colorTransfer;
506506

507-
const int size = lut ? lut->GetNumberOfTableValues() : 0;
508-
if (size == 0)
509-
{
510-
const double black[3] = {0., 0., 0.};
511-
const double white[3] = {1., 1., 1.};
512-
colorTransfer->AddRGBPoint(scalarRange[0], black[0], black[1], black[2]);
513-
colorTransfer->AddRGBPoint(windowLevelMinMax[0], black[0], black[1], black[2]);
514-
colorTransfer->AddRGBPoint(windowLevelMinMax[1], white[0], white[1], white[2]);
515-
colorTransfer->AddRGBPoint(scalarRange[1], white[0], white[1], white[2]);
516-
}
517-
else if (size == 1)
518-
{
519-
double color[4];
520-
lut->GetTableValue(0, color);
521-
522-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous),
523-
color[0], color[1], color[2]);
524-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[0], previous),
525-
color[0], color[1], color[2]);
526-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous),
527-
color[0], color[1], color[2]);
528-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous),
529-
color[0], color[1], color[2]);
530-
}
531-
else // if (size > 1)
532-
{
533-
previous = VTK_DOUBLE_MIN;
534-
535-
double color[4];
536-
lut->GetTableValue(0, color);
537-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous),
538-
color[0], color[1], color[2]);
507+
vtkColorTransferFunction* inputColorTransfer = vtkColorTransferFunction::SafeDownCast(colors);
508+
if (inputColorTransfer)
509+
{
510+
// Colors are defined by transfer function
511+
// We cannot simply copy but we need to scale and offset as specified by window/level
512+
double inputColorTransferRange[2] = { 0.0, 1.0 };
513+
inputColorTransfer->GetRange(inputColorTransferRange);
514+
const double scale = (windowLevelMinMax[1] - windowLevelMinMax[0]) / (inputColorTransferRange[1] - inputColorTransferRange[0]);
515+
const double offset = windowLevelMinMax[0] - scale * inputColorTransferRange[0];
516+
const vtkIdType colorCount = inputColorTransfer->GetSize();
517+
double color_X_RGB_MS[6] = { 0., 0., 0., 0., 0.5, 1.0 }; // x, RGB, midpoint, sharpness
518+
for (vtkIdType i = 0; i < colorCount; ++i)
519+
{
520+
inputColorTransfer->GetNodeValue(i, color_X_RGB_MS);
521+
colorTransfer->AddRGBPoint(offset + color_X_RGB_MS[0] * scale,
522+
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]);
523+
}
524+
}
525+
else
526+
{
527+
// Colors are defined by lookup table
528+
vtkLookupTable* lut = vtkLookupTable::SafeDownCast(colors);
529+
double previous = VTK_DOUBLE_MIN;
530+
const vtkIdType numberOfColors = lut ? lut->GetNumberOfTableValues() : 0;
531+
if (numberOfColors == 0)
532+
{
533+
const double black[3] = { 0., 0., 0. };
534+
const double white[3] = { 1., 1., 1. };
535+
colorTransfer->AddRGBPoint(scalarRange[0], black[0], black[1], black[2]);
536+
colorTransfer->AddRGBPoint(windowLevelMinMax[0], black[0], black[1], black[2]);
537+
colorTransfer->AddRGBPoint(windowLevelMinMax[1], white[0], white[1], white[2]);
538+
colorTransfer->AddRGBPoint(scalarRange[1], white[0], white[1], white[2]);
539+
}
540+
else if (numberOfColors == 1)
541+
{
542+
double color[4];
543+
lut->GetTableValue(0, color);
539544

540-
double value = windowLevelMinMax[0];
545+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous),
546+
color[0], color[1], color[2]);
547+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[0], previous),
548+
color[0], color[1], color[2]);
549+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous),
550+
color[0], color[1], color[2]);
551+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous),
552+
color[0], color[1], color[2]);
553+
}
554+
else // if (numberOfColors > 1)
555+
{
556+
double color[4] = { 0.0, 0.0, 0.0, 1.0 };
557+
lut->GetTableValue(0, color);
558+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[0], previous),
559+
color[0], color[1], color[2]);
541560

542-
double step = windowLevel[0] / (size - 1);
561+
// We place up to maxNumberOfPoints points in the color transfer function.
562+
// The number is high enough to accurately describe most color tables,
563+
// but not too high so that the user can still edit the function manually.
564+
const vtkIdType maxNumberOfPoints = 24;
565+
566+
const vtkIdType numberOfPoints = std::min(numberOfColors, maxNumberOfPoints);
567+
// convert from point index to color index
568+
double pointIndexScale = static_cast<double>(numberOfColors - 1) / (numberOfPoints - 1);
569+
double offset = windowLevelMinMax[0];
570+
double scale = windowLevel[0] / (numberOfColors - 1);
571+
for (vtkIdType pointIndex = 0; pointIndex < numberOfPoints; ++pointIndex)
572+
{
573+
vtkIdType colorIndex = pointIndex * pointIndexScale;
574+
lut->GetTableValue(colorIndex, color);
575+
const double value = offset + colorIndex * scale;
576+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(value, previous),
577+
color[0], color[1], color[2]);
578+
}
543579

544-
int downSamplingFactor = 64;
545-
for (int i = 0; i < size; i += downSamplingFactor,
546-
value += downSamplingFactor*step)
547-
{
548-
lut->GetTableValue(i, color);
549-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(value, previous),
580+
lut->GetTableValue(numberOfColors - 1, color);
581+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous),
582+
color[0], color[1], color[2]);
583+
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous),
550584
color[0], color[1], color[2]);
585+
}
551586
}
552587

553-
lut->GetTableValue(size - 1, color);
554-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(windowLevelMinMax[1], previous),
555-
color[0], color[1], color[2]);
556-
colorTransfer->AddRGBPoint(vtkMRMLVolumePropertyNode::HigherAndUnique(scalarRange[1], previous),
557-
color[0], color[1], color[2]);
558-
}
559-
560588
vtkColorTransferFunction *volumePropColorTransfer = volumeProp->GetRGBTransferFunction();
561589
if (this->IsDifferentFunction(colorTransfer.GetPointer(), volumePropColorTransfer))
562-
{
590+
{
563591
volumePropColorTransfer->DeepCopy(colorTransfer.GetPointer());
564-
}
592+
}
565593

566594
volumeProp->SetInterpolationTypeToLinear();
567595
volumeProp->ShadeOn();
@@ -727,7 +755,7 @@ void vtkSlicerVolumeRenderingLogic::CopyScalarDisplayToVolumeRenderingDisplayNod
727755
threshold[1] = vpNode->GetWindowLevelMax();
728756
}
729757

730-
vtkLookupTable* lut = vpNode->GetColorNode() ? vpNode->GetColorNode()->GetLookupTable() : nullptr;
758+
vtkScalarsToColors* lut = vpNode->GetColorNode() ? vpNode->GetColorNode()->GetScalarsToColors() : nullptr;
731759
vtkVolumeProperty *prop = vspNode->GetVolumePropertyNode()->GetVolumeProperty();
732760

733761
int disabledModify = vspNode->StartModify();

Modules/Loadable/VolumeRendering/Logic/vtkSlicerVolumeRenderingLogic.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class vtkMRMLVolumePropertyNode;
3636

3737
// VTK includes
3838
class vtkColorTransferFunction;
39-
class vtkLookupTable;
4039
class vtkPiecewiseFunction;
4140
class vtkScalarsToColors;
4241
class vtkVolumeProperty;
@@ -217,7 +216,7 @@ class VTK_SLICER_VOLUMERENDERING_MODULE_LOGIC_EXPORT vtkSlicerVolumeRenderingLog
217216
/// \sa SetThresholdToVolumeProp
218217
void SetWindowLevelToVolumeProp(
219218
double scalarRange[2], double windowLevel[2],
220-
vtkLookupTable* lut, vtkVolumeProperty* node);
219+
vtkScalarsToColors* lut, vtkVolumeProperty* node);
221220

222221
/// Create an opacity transfer function for gradient opacity.
223222
/// It ranges from 0 to scalarRange[1] - scalarRange[0].
@@ -229,7 +228,7 @@ class VTK_SLICER_VOLUMERENDERING_MODULE_LOGIC_EXPORT vtkSlicerVolumeRenderingLog
229228
/// transfer function from the labelmap LUT \a colors.
230229
/// \sa SetWindowLevelToVolumeProp, SetThresholdToVolumeProp
231230
void SetLabelMapToVolumeProp(
232-
vtkScalarsToColors* lut, vtkVolumeProperty* node);
231+
vtkScalarsToColors* colors, vtkVolumeProperty* node);
233232

234233
/// Update DisplayNode from VolumeNode,
235234
/// Can pass a VolumePropertyNode and an ROI node to be the display node.

0 commit comments

Comments
 (0)