Only administrators can move posts between topics. If you give a list of posts that you want to move out to a separate topic then I can move them for you, but it is probably easier to just create a new topic and include links to relevant posts.
Sorry, Iām referring to the SlicerQREADS Development topic. I will post about the extraction to a new topic thereā¦
Hello @lassoan, what do you mean by āenter interactive rendering mode?ā
Thanks
The renderer (or the interactor) has a flag that temporarily changes the desired update rate while interacting with the view (typically zooming/rotating the view). This results in faster, lower-quality rendering while the view is being rotated and when you release the mouse button then a normal-quality rendering is done. This interactive mode is enabled by the volume rendering offset slider, too. Note that this makes the most difference for CPU volume rendering, which should not be used on modern hardware.
OK, new terms; Volume rendering offset slider. Is this a type of widget? Also, shouldnāt the lower-quality rendering mode be enabled automatically for CPU volume rendering? How do you set this flag? Whatās the name of it.
Thanks
I meant the slider labeled as āShiftā (sorry for the confusion with mentioning āoffsetā):
You want to have high-quality volume rendering. You just want to temporarily lower the resolution while you are interactively adjusting parameters. Nowadays average laptops have enough graphics capability that GPU volume rendering is a few times faster than CPU volume rendering (and discrete GPUs are 10-100x faster), so I would recommend using GPU volume rendering by default. Most likely you would run into issues with GPU rendering only if you are trying to render a large, high-resolution CT on a low-performance computer, but there I would recommend to downsample the volume (lower the resolution by a factor of 2x along each axis would results in 8x smaller memory requirement and barely visible image quality loss for volume rendering of a high-resolution volume).
If there are many users with old computers in your organization that would like to volume-render high-resolution volumes then it could worth adding an option to automatically downsample large volumes after loading.
You can find source code for a function on the GUI by searching for text that you see in the GUI and then following the references, as shown in this example. I give an example for this āShiftā slider, too:
- search for
shift ui
in the Slicer github repository (we addui
to the search terms because most user interface elements are specified in .ui files) - the first search hit has volume rendering in its name and it is a .ui file (
qSlicerVolumeRenderingPresetComboBox.ui
), so it looks like this is the file we need, click on that link - Search for
offset
in the file and you can find a slider with the namePresetOffsetSlider
. This is the name how the slider is referred to in source code. Now you just need to search forPresetOffsetSlider
in the source code. - Again, the first hit is the correct one. Click on the link to see the file.
- Search for
PresetOffsetSlider
in the file and you can see how interaction events (SliderPressed
,SliderReleased
, etc. map to methods). You can search for the associated method namesstartInteraction
,endInteraction
to see how interactive rendering mode is activated/deactivated:
Thank you and yes @lassoan. I tried this search method after @jamesobutler instructed me on how to use the Slicer Code Repo to search for the code. I totally did not know how to do the final translation to Python code. Do I need to create new code on the C++ level, or is there a way to reuse this code? Thatās where I got stuck.
Thanks
You can use the same classes from C++ and Python. There are just a number of trivial syntax differences between Python and C++ that we listed here.
OK, just to complete my understanding of how to translate to Python from the C++ I find. I appreciate your patience here and please forgive me for my gap in understandingā¦
In qSlicerVolumeRenderingPresetComboBox.cxx I found the followingā¦
QObject::connect(this->PresetOffsetSlider, SIGNAL(valueChanged(double)),
q, SLOT(offsetPreset(double)));
QObject::connect(this->PresetOffsetSlider, SIGNAL(sliderPressed()),
q, SLOT(startInteraction()));
QObject::connect(this->PresetOffsetSlider, SIGNAL(valueChanged(double)),
q, SLOT(interaction()));
QObject::connect(this->PresetOffsetSlider, SIGNAL(sliderReleased()),
q, SLOT(endInteraction()));
From here, do I set up 4 corresponding signal/slot connections in Python? I.e,ā¦
self.ui.RenderShiftSlider.connect("valueChanged(double)", self.offsetPreset()
self.ui.RenderShiftSlider.connect("sliderPressed", self.startInteraction()
self.ui.RenderShiftSlider.connect("valueChanged(double)", self.interaction()
self.ui.RenderShiftSlider.connect("sliderReleased", self.endInteraction()
Thanks
ā¦ and I imagine for example, in this function
void qSlicerVolumeRenderingPresetComboBox::startInteraction()
{
Q_D(qSlicerVolumeRenderingPresetComboBox);
if (!d->VolumePropertyNode)
{
return;
}
vtkVolumeProperty* volumeProperty = d->VolumePropertyNode->GetVolumeProperty();
if (volumeProperty)
{
volumeProperty->InvokeEvent(vtkCommand::StartInteractionEvent);
}
}
I donāt know how I would somehow invoke the StartInteractionEvent from Python.
@jcfr is using a similar Slider control to change the thickness. But, the only Slot (or event handler) is updateParameterNodeFromGUI
self.ui.SlabThicknessSliderWidget.connect("valueChanged(double)", self.updateParameterNodeFromGUI)
In updateParameterNodeFromGUI is this staementā¦
self._parameterNode.SetParameter("SlabThicknessInMm", str(self.ui.SlabThicknessSliderWidget.value))
ā¦ and things magically happen from there.
I guess āSlabThicknessInMmā is a slicer parameter?
The correct syntax is:
self.ui.RenderShiftSlider.connect("valueChanged(double)", self.offsetPreset
It will look something like this in Python:
def startInteraction(self):
if not self.VolumePropertyNode:
return
volumeProperty = self.VolumePropertyNode.GetVolumeProperty()
if volumeProperty:
volumeProperty.InvokeEvent(vtk.vtkCommand.StartInteractionEvent)
There is nothing magical, itās just the GUI and logic are nicely separated. Most likely you have added an observer to the parameter node in your logic class, so if the parameter node changes then a callback function is executed, which updates your view.
here is all of my codeā¦
self.OldPresetPosition = 0
self.ui.RenderShiftSlider.connect("sliderPressed", self.startInteraction)
self.ui.RenderShiftSlider.connect("valueChanged(double)", self.offsetPreset)
self.ui.RenderShiftSlider.connect("valueChanged(double)", self.interaction)
self.ui.RenderShiftSlider.connect("sliderReleased", self.endInteraction)
def startInteraction(self):
if not self.displayNode:
return
volumeProperty = self.displayNode.GetVolumeProperty()
if volumeProperty:
volumeProperty.InvokeEvent(vtk.vtkCommand.StartInteractionEvent)
def endInteraction(self):
if not self.displayNode:
return
volumeProperty = self.displayNode.GetVolumeProperty()
if volumeProperty:
volumeProperty.InvokeEvent(vtk.vtkCommand.EndInteractionEvent)
def offsetPreset(self,newPosition):
if not self.displayNode:
return
# WRONG SYTAX emit presetOffsetChanged(newPosition - self.OldPresetPosition, 0., False)
self.OldPresetPosition = newPosition
def interaction(self):
if not self.displayNode:
return
volumeProperty = self.displayNode.GetVolumeProperty()
if volumeProperty:
volumeProperty.InvokeEvent(vtk.vtkCommand.InteractionEvent)
In the above code, Iām using the node referencing my volume rendering named self.displayNode, instead of self.VolumePropertyNode. self.OldPresetPosition is a local variable that keeps track of the old value.
I hope thatās mostly correct, how do I translate ā¦
emit presetOffsetChanged(newPosition - self.OldPresetPosition, 0., False)
ā¦ to Python. What is āemitā?
āemitā is a Qt keyword, it makes a Qt class emit a signal that can be observed or connected to Qt slots. Since you donāt need to notify anyone about the offset change, you can simply skip this line.
Do I replace the it with code that change the render opacity? Iām guessing the presetOffsetChanged or
moveAllPoints code does exactly thatā¦
I found this in the codeā¦
QObject::connect(this->PresetComboBox, SIGNAL(presetOffsetChanged(double, double, bool)),
this->VolumePropertyNodeWidget, SLOT(moveAllPoints(double, double, bool)));
Shall I implement the moveAllPoints method?
Yes, unfortunately this method is implemented at the wrong level (in GUI instead of widget) therefore it cannot be reused but you need to implement it in your own code.
ā¦ or shall I use this oneā¦
def updateRenderShift(self):
gradientThreshold = self.ui.RenderShiftSlider.value
maxOpacity = 1.0
gradientOpacityTransferFunction = self.displayNode.GetVolumePropertyNode().GetVolumeProperty().GetGradientOpacity()
gradientOpacityTransferFunction.RemoveAllPoints()
gradientOpacityTransferFunction.AddPoint(0, 0.0)
gradientOpacityTransferFunction.AddPoint(gradientThreshold-1, 0.0)
gradientOpacityTransferFunction.AddPoint(gradientThreshold+1, maxOpacity)
self.displayNode.SetVisibility(True)
Thanks
Something similar. The code snippet above removes all the points, then creates a new gradient opacity transfer function. If you want to shift an existing preset then you need to iterate through its points of the opacity and color transfer functions and change the x coordinate values of the points.
Hello @lassoan @jamesobutler .
For adjusting the x coordinate (gradient threshold), the documentation statesā¦
:param gradientThreshold: regions that has gradient value below this threshold will be made transparent
(minimum value is 0.0, higher values make more tissues transparent, starting with soft tissues)
Question: What do I use for the max gradientThreshold value? The series max pixel value? If not, then what?
Thanks
Note that the code snippet above adjusts the gradient opacity transfer function. The offset slider in volume rendering module does not touch the gradient opacity transfer function, just the scalar opacity and color transfer (because the gradient is not sensitive to absolute image intensity changes, so it should not be adjusted along with the two other transfer functions).