Skip to content

Commit

Permalink
ENH: Improve Endoscopy flythrough
Browse files Browse the repository at this point in the history
  • Loading branch information
Leengit committed Nov 1, 2023
1 parent 033bc60 commit 59a5224
Showing 1 changed file with 23 additions and 11 deletions.
34 changes: 23 additions & 11 deletions Modules/Scripted/Endoscopy/Endoscopy.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def __init__(self, parent):
"""
Create or import a markups curve.
Clicking "Create flythrough" will make a flythrough curve and enable the flythrough panel.
You can manually scroll through the path with the Frame slider. The Play/Pause button toggles animated flythrough.
You can manually scroll through the path with the Frame slider. The Play/Pause button toggles animated flythrough.
The Frame Skip slider speeds up the animation by skipping points on the path.
The Frame Delay slider slows down the animation by adding more time between frames.
The View Angle provides is used to approximate the optics of an endoscopy system.
Expand Down Expand Up @@ -123,8 +123,8 @@ def setup(self):
self.outputPathNodeSelector.setMRMLScene(slicer.mrmlScene)

# In the Endoscopy module we want mouse involvement in the 3-dimensional view to rotate the camera orientation.
# We want to disable the use of the mouse to do all other things in that pane.
# TODO: Is that what we really want? Is this the way to do that or need we do more or different?
# How do we ensure that is enabled? How do we disable all other uses of the mouse in that pane.
# TODO: Is that what we really want? Is this the way to do that or need we do more, less, different?
interactionNode = slicer.mrmlScene.GetNodeByID("vtkMRMLInteractionNodeSingleton")
interactionNode.SwitchToViewTransformMode()
interactionNode.SetPlaceModePersistence(0)
Expand Down Expand Up @@ -269,6 +269,7 @@ def setupFlythroughUI(self):
# View angle slider
flythroughViewAngleSlider = ctk.ctkSliderWidget()
logging.debug(" flythroughViewAngleSlider = ctk.ctkSliderWidget()")
# View angles are in degrees
flythroughViewAngleSlider.decimals = 0
flythroughViewAngleSlider.minimum = 30
flythroughViewAngleSlider.maximum = 180
Expand All @@ -277,8 +278,8 @@ def setupFlythroughUI(self):
' flythroughViewAngleSlider.connect("valueChanged(double)", '
+ "self.flythroughViewAngleSliderValueChanged)"
)
flythroughFormLayout.addRow(_("View Angle:"), flythroughViewAngleSlider)
logging.debug(' flythroughFormLayout.addRow("View Angle:", flythroughViewAngleSlider)')
flythroughFormLayout.addRow(_("View Angle°:"), flythroughViewAngleSlider)
logging.debug(' flythroughFormLayout.addRow("View Angle°:", flythroughViewAngleSlider)')
self.flythroughViewAngleSlider = flythroughViewAngleSlider

# Play button
Expand Down Expand Up @@ -396,13 +397,16 @@ def setFiducialNode(self, newFiducialNode):
self.updateWidgetFromMRML()

def updateWidgetFromMRML(self):
# TODO: There are many other widgets and many other form fields. Which need to be included here?
# TODO: There are other widgets and other form fields. What rule determines which need to be included here?
if self.camera:
# View angles are in degrees
self.flythroughViewAngleSlider.value = self.camera.GetViewAngle()
if self.cameraNode:
pass
if self.fiducialNode:
pass
if self.transform:
pass

def onCameraModified(self, observer, eventid):
self.updateWidgetFromMRML()
Expand All @@ -411,7 +415,7 @@ def onCameraNodeModified(self, observer, eventid):
self.updateWidgetFromMRML()

def enableOrDisableCreateButton(self):
"""Connected to both the fiducial and camera node selector. It allows to
"""Connected to both the fiducial and camera node selector. It allows to
enable or disable the 'Create model' button."""
self.createPathButton.enabled = (
self.cameraNodeSelector.currentNode()
Expand All @@ -430,7 +434,13 @@ def onFiducialNodeModified(self, observer, eventid):
self.logic = EndoscopyLogic(self.fiducialNode)

def onCreatePathButtonClicked(self):
"""Connected to 'Create model' button. It allows to:
# TODO: Don't create a model, though do create the cursor (so the user can see where in the flythrough we are)
# and probably its transform (though why?).
# TODO: Since we are not creating a model, change the UI so that a name for the model cannot be specified, etc.
# TODO: Perhaps remove this button and instead invoke this functionality whenever the user selects an input
# curve within the Endoscopy module.

"""Connected to 'Create model' button. It allows to:
- compute the path
- create the associated model"""

Expand All @@ -443,6 +453,7 @@ def onCreatePathButtonClicked(self):
numberOfControlPoints = self.logic.resampledCurve.GetNumberOfControlPoints()
logging.debug(f"-> Computed path contains {numberOfControlPoints} elements")

# TODO: Build cursor and its transform, but not this model
logging.debug("Create model...")
model = EndoscopyPathModel(self.logic.resampledCurve, fiducialNode, outputPathNode)
logging.debug("-> Model created")
Expand Down Expand Up @@ -473,6 +484,7 @@ def flythroughFrameDelaySliderValueChanged(self, newValue):

def flythroughViewAngleSliderValueChanged(self, newValue):
if self.cameraNode:
# View angles are in degrees
self.cameraNode.GetCamera().SetViewAngle(newValue)

def onPlayButtonToggled(self, checked):
Expand Down Expand Up @@ -915,7 +927,7 @@ def __init__(self, resampledCurve, inputCurve, outputPathNode=None, cursorType=N
:param resampledCurve: resampledCurve generated by EndoscopyLogic
:param inputCurve: input node, just used for naming the output node.
:param outputPathNode: output model node that stores the path points.
:param cursorType: can be 'markups' or 'model'. Markups has a number of advantages (radius it is easier to
:param cursorType: can be 'markups' or 'model'. Markups has a number of advantages (radius it is easier to
change the size, can jump to views by clicking on it, has more visualization options, can be scaled to fixed
display size), but if some applications relied on having a model node as cursor then this argument can be used
to achieve that.
Expand All @@ -940,7 +952,6 @@ def __init__(self, resampledCurve, inputCurve, outputPathNode=None, cursorType=N
idArray.InsertNextTuple1(0)

for resampledCurveControlPointIndex in range(resampledCurve.GetNumberOfControlPoints()):
# TODO: Rename `point` to something more informative
point = np.zeros((3,))
resampledCurve.GetNthControlPointPositionWorld(resampledCurveControlPointIndex, point)
pointIndex = points.InsertNextPoint(*point)
Expand All @@ -959,7 +970,7 @@ def __init__(self, resampledCurve, inputCurve, outputPathNode=None, cursorType=N
model.CreateDefaultDisplayNodes()
model.GetDisplayNode().SetColor(1, 1, 0) # yellow

model.SetAndObservePolyData(polyData)
model.SetAndObservePolyData(polyData) # TODO: Restore me

# Camera cursor
cursor = model.GetNodeReference("CameraCursor")
Expand All @@ -972,6 +983,7 @@ def __init__(self, resampledCurve, inputCurve, outputPathNode=None, cursorType=N
cursor.CreateDefaultDisplayNodes()
cursor.GetDisplayNode().SetSelectedColor(1, 0, 0) # red
cursor.GetDisplayNode().SetSliceProjection(True)
cursor.GetDisplayNode().BackfaceCullingOn() # so that the camera can see through the cursor from inside
cursor.AddControlPoint(vtk.vtkVector3d(0, 0, 0), " ") # do not show any visible label
cursor.SetNthControlPointLocked(0, True)
else:
Expand Down

0 comments on commit 59a5224

Please sign in to comment.