Inquiry about the 'Auto' Control Mode in qMRMLVolumeThresholdWidget

Dear 3D Slicer Community,

I am a learner of Slicer module development. I am writing to inquire about the underlying principle/algorithm behind the “Auto” option in the Control Mode of the qMRMLVolumeThresholdWidget.

Through tracing the source code in qMRMLVolumeThresholdWidget.cxx and vtkMRMLScalarVolumeDisplayNode.cxx on GitHub, it appears that the following code snippet in the vtkMRMLScalarVolumeDisplayNode.cxx explains the basis of this “Auto” option:

void vtkMRMLScalarVolumeDisplayNode::CalculateAutoLevels()
{
  if (!this->GetAutoWindowLevel() && !this->GetAutoThreshold())
  {
    vtkDebugMacro("CalculateScalarAutoLevels: " << (this->GetID() == nullptr ? "nullid" : this->GetID())
                  << ": Auto window level not turned on, returning.");
    return;
  }

  vtkImageData *imageDataScalar = this->GetScalarImageData();
  if (!imageDataScalar)
  {
    vtkDebugMacro("CalculateScalarAutoLevels: input image data is null");
    return;
  }
  // Make sure the point data is up to date.
  // Remember, the display node pipeline is not connected to a consumer (volume
  // display nodes are cloned by the slice logic) therefore no-one has run the
  // filters.
  if (this->GetInputImageData())
  {
    this->GetScalarImageDataConnection()->GetProducer()->Update();
  }

  if (!(imageDataScalar->GetPointData()) ||
      !(imageDataScalar->GetPointData()->GetScalars()))
  {
    vtkDebugMacro("CalculateScalarAutoLevels: input image data is null");
    return;
  }

  if (this->HistogramStatistics == nullptr)
  {
    this->HistogramStatistics = vtkImageHistogramStatistics::New();

    // Set automatic window/level to include the entire intensity range
    // (except top/bottom 0.1%, to not let a very thin tail of the intensity
    // distribution to decrease the image contrast too much).
    // While in CT and sometimes in MRI, there may be a large empty area
    // outside the reconstructed image, which could be suppressed
    // by a larger lower percentile value, it would make the method
    // too specific to particular imaging modalities and could lead to
    // suboptimal results for other types of images.
    // Therefore, we choose small, symmetric percentile values here
    // and maybe add modality-specific methods later (e.g., for CT
    // images we could set lower value to -1000HU).
    this->HistogramStatistics->SetAutoRangePercentiles(0.1, 99.9);

    // Percentiles are very low (0.1%), so there is no need for
    // range expansion.
    this->HistogramStatistics->SetAutoRangeExpansionFactors(0.0, 0.0);
  }

  this->IsInCalculateAutoLevels = true;
  this->HistogramStatistics->SetInputData(imageDataScalar);
  this->HistogramStatistics->Update();
  double* intensityRange = this->HistogramStatistics->GetAutoRange();
  vtkDebugMacro("CalculateScalarAutoLevels:"
                << " lower: " << intensityRange[0] << " upper: " << intensityRange[1]);

  int disabledModify = this->StartModify();
  if (this->GetAutoWindowLevel())
  {
    this->SetWindowLevelMinMax(intensityRange[0], intensityRange[1]);
  }
  if (this->GetAutoThreshold())
  {
    this->SetThreshold(intensityRange[0], intensityRange[1]);
  }
  this->EndModify(disabledModify);
  this->IsInCalculateAutoLevels = false;
}

If my tracking and understanding are correct, the above code snippet appears to primarily perform the following tasks::

  1. After loading the image data, ensure that the point data (scalar values) of the image are up to date.
  2. Utilize VTK’s vtkImageHistogramStatistics class to compute the grayscale histogram statistics of the image data, setting the percentiles for the automatic range computation to (0.1, 99.9). This implies that the histogram statistics will includes all values of image intensity except the top/bottom 0.1%.
  3. Update the histogram statistics and invoke the ‘GetAutoRange’ method of the vtkImageHistogramStatistics class to obtain the resulting threshold range.

So, it seems that the algorithm behind this ‘Auto’ option is based on custom percentile ranges for the image’s histogram statistics rather than methods like Otsu’s method?

Could you please confirm if my understanding above is correct? Additionally, I would greatly appreciate any further explanation or supplementary details regarding the underlying principle/algorithm of this “Auto” option.

Thank you very much!

Both window/level and threshold is set by default so that (almost) the entire data range is included in the displayed range.

“Almost” the entire data range, because a little bit above 0th percentile value is used instead of actual minimum voxel value; and instead of maximum scalar value, a little bit below 100th percentile value is used. These percentiles are a bit more robust: they are less sensitive to presence of a few outlier voxels.

I have not found classic binary thresholding methods (such as Otsu) practically useful. The problem is that the methods solve a quite simple problem in a quite unpredictable way. I’ve made them available in Segment Editor’s Threshold effect (in the Automatic threshold section), but you’ll see that it is hard to find any good use case for them.

1 Like