Crash with ctkRangeWidget

Hi,

I have an issue with a wrong behavior of ctkRangeWidget that ends with the crash of Slicer 4.11.20200930 in Debug build (Ubuntu 20).

I think it is probably the same as this bug that has been closed without knowing how to reproduce:

In my case it occurs in qMRMLVolumeThresholdWidget when using the sequence player if the frames have data with different scalar range values. At the change of frame, the threshold widget updates and call setRange(double min, double max), ending up in ctkRangeWidget::setRange where the slider and spinbox values are compared in a Q_ASSERT (ctkRangeWidget.cpp line 380):

Q_ASSERT(d->equal(d->Slider->maximumValue(), d->MaximumSpinBox->value()));

What happens is that a few lines above the call to
d->Slider->setRange(d->MaximumSpinBox->minimum(), d->MinimumSpinBox->maximum());
doesn’t always update the slider’s values.

As a side question about this line, shoudn’t it rather be d->Slider->setRange(d->MinimumSpinBox->minimum(), d->MaximumSpinBox->maximum()); ?

The reason why the values are not updated is because there are some rounding calculations with conversions to int during the process. It would be long to describe all here, but in ctkDoubleRangeSlider::setRange, the call to d->Slider->setRange(d->toInt(newMin), d->toInt(newMax)); converts the ranges to number of steps.

In an example with previous max range being 1272 and new one 1267, with a step of 10, it results in the same value rounded_int(1272/10) == rounded_int(1267/10) == 127, and in the implementation of the previous call to setRange, the “emit rangeChanged” is not done:

void QAbstractSlider::setRange(int min, int max)
{
Q_D(QAbstractSlider);
int oldMin = d->minimum;
int oldMax = d->maximum;
d->minimum = min;
d->maximum = qMax(min, max);
if (oldMin != d->minimum || oldMax != d->maximum) {
sliderChange(SliderRangeChange);
emit rangeChanged(d->minimum, d->maximum);
setValue(d->value); // re-bound
}
}

At the end, the slider (d->Slider->maximumValue()) and the spin box (d->MinimumSpinBox->maximum()) values are not synchronized and we are in the assert situation where 1267 != 1272.

I don’t really know what would be the correct fix for that.

  • should the method QAbstractSlider::setRange be overridden in ctkRangeSlider and use more info for the condition check?
  • should ctkDoubleRangeSlider or ctkRangeWidget force call ctkRangeSlider::onRangeChanged or ctkRangeSlider::setValues in this situation?
  • something else ?

Thanks for the detailed analysis. Do you think the assert detects an actual error or it can normal that there are transient states during the check fails? We could simply remove this check or convert it to just logging a message if we find that it does not help but rather interferes with debugging.

At the end of the process of all events and updates, those values for the slider and spin box are out of synchronization, so there is a real error if they have to be. For my case the crash is more annoying than this error, considering that it occurs only for some specific cases where it’s not a problem to have slightly different values so it is a very minor bug, but maybe other people needing exact match will have bigger issues…

Replacing the assert by a log could be a first easy step, better having a “polluted” log than a crash.

I see in different places of these ctkRange classes that sometimes the values are forced set for specific cases, so it could be a possibility if we can detect this rounding side effect, but i’m new to ctk/Slicer so i don’t know the impact of it in other cases, these widgets are pretty complex.