Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streamline Endoscopy UX removing "Use this curve" button #7439

Merged
merged 22 commits into from
Dec 8, 2023

Conversation

jcfr
Copy link
Member

@jcfr jcfr commented Nov 30, 2023

Selecting the curve is now sufficient to work with the associated flythrough path. Also listen to the more specific PointEndInteractionEvent to only update the curve after user is done updating the curve.

Instantiate one EndoscopyLogic and simplify its API

... introducing:

  • updateCameraFromOrientationAtIndex()
  • getNumberOfControlPoints()
  • saveOrientationAtIndex()
  • removeOrientationAtIndex()
  • createTransformFromInputCurve() / getTransformFromInputCurve() / setInputCurveTransform()
  • createCursorFromInputCurve() / getCursorFromInputCurve() / setInputCurveCursor()

... renaming:

  • setControlPoints to setControlPointsByResamplingAndInterpolationFromInputCurve
  • interpolateOrientations to interpolateOrientationsForControlPoints
  • loadCameraOrientations() to getCameraOrientationsFromInputCurve()
  • saveCameraOrientations() to setInputCurveCameraOrientations()
  • onPlayButtonToggled() to setPlaybackEnabled() (to allow programmatic start/stop of playback)

... updating:

  • interpolateOrientationsForControlPoints() to require a cameraOrientations parameter. This allows to remove the need for maintaining a dedicated cameraOrientations instance variable

.. removing:

  • createCursor()

It also revisits how camera, camera transform ad camera cursor are referenced. These are now directly referenced from the input curve using Camera, CameraTransform and CameraCursor reference role.

Other changes:

  • convert relative orientation API to staticmethod
  • remove unneeded instance variable related to Endoscopy model export
  • consolidate all widget update into updateWidgetFromMRML()
  • remove ignoreInputCurveModified instance variable and instead introduce a updatingControlPoints variable to guard setControlPoints()
  • allow to set and get the input curve CameraOrientations independently of the EndoscopyWidget. This is done through getCameraOrientationsFromInputCurve() and setInputCurveCameraOrientations()

@jcfr
Copy link
Member Author

jcfr commented Nov 30, 2023

To ensure all parameters can be updated programmatically and independently of the UI, it would also be nice to either introduce parameter node or set dedicated attributes directly on the curve node for the following:

  • frame
  • frame skip
  • frame delay
  • view angle
  • plane normal

Association of the following object also needs to be captured at the MRML level (instead of a logic variable):

  • camera
  • cursor transform
  • cursor fiducial

@Leengit Is it something you could help with ?

@jcfr jcfr requested a review from Leengit November 30, 2023 14:38
@jcfr
Copy link
Member Author

jcfr commented Nov 30, 2023

Association of the following object also needs to be captured at the MRML level (instead of a logic variable)

@lassoan Do we already have an established pattern to "define" the equivalent of reference role from python ?

@lassoan
Copy link
Contributor

lassoan commented Nov 30, 2023

Do we already have an established pattern to "define" the equivalent of reference role from python ?

I'm not exactly sure what you mean but we store the user's node selections on the GUI as node references in the parameter node.

Copy link
Contributor

@Leengit Leengit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great -- thank you for the effort! I've made a bunch of suggestions.

Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
@jcfr jcfr force-pushed the Endoscopy-clean-api branch 14 times, most recently from 0ef0f47 to dba2b4d Compare December 6, 2023 21:23
@jcfr jcfr requested a review from Leengit December 6, 2023 21:24
@jcfr
Copy link
Member Author

jcfr commented Dec 6, 2023

you mean but we store the user's node selections on the GUI as node references in the parameter node.

@lassoan Flythrough parameters (e.g view angle) are saved as regular node attributes, relationship to nodes are set as node references ("Camera", "CameraTransform" and "CameraCursor")

Since the "inputCurve" is what is driving the workflow, I didn't introduce a parameter node.

By convention, should we start these with a lower-case letter ? I kept the upper-case letter for historical reason but this can be changed. I am asking before reference role are currently lower-cased (e.g display, storage, ...)

Copy link
Contributor

@Leengit Leengit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get you a quick review, I have not yet looked at your most recent changes to the code. However, my testing by running 3D Slicer shows that almost everything is beautiful. The one problem I am still seeing is that when I Close Scene, my console prints out

Close main MRML scene
~/git/Slicer/Slicer-SuperBuild-Release-Qt5.15.2/Slicer-build/bin/Python/slicer/util.py:2786: UserWarning: does not have observer
  warn("does not have observer")
~/git/Slicer/Slicer-SuperBuild-Release-Qt5.15.2/Slicer-build/bin/Python/slicer/util.py:2771: UserWarning: already has observer
  warn("already has observer")
QXcbConnection: XCB error: 3 (BadWindow), sequence: 39328, resource id: 6308414, major code: 40 (TranslateCoords), minor code: 0

All told, this represents an improvement so I am marking it as approved.

Leengit
Leengit previously approved these changes Dec 7, 2023
Copy link
Contributor

@Leengit Leengit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good. I've made a few comments about the code; use them as you see fit.

Modules/Scripted/Endoscopy/Endoscopy.py Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Show resolved Hide resolved
Modules/Scripted/Endoscopy/Endoscopy.py Outdated Show resolved Hide resolved
jcfr and others added 4 commits December 7, 2023 17:39
Co-authored-by: Lee Newberg <lee.newberg@kitware.com>
…entations

This allows to set and get the input curve CameraOrientations independently
of the EndoscopyWidget.

To support this it also updates the `interpolateOrientations` to require
a cameraOrientations parameter. This allows to remove the need for maintaining
a dedicated "cameraOrientations" instance variable.

It also renames the functions:
* `loadCameraOrientations` to `getCameraOrientationsFromInputCurve`
* `saveCameraOrientations` to `setInputCurveCameraOrientations`
Following cc0ee37 (ENH: Update Endoscopy removing obsolete "model"
cursor type), there is no need to keep track of the model instance. User
may retrieve the model node by its name set as "Path-{curveName}".
Directly set references to the transform and cursor nodes on the
input curve node and introduce API methods for creating and retrieving
the associated nodes:
* createTransformFromInputCurve() / getTransformFromInputCurve() / setTransformToInputCurve()
* createCursorFromInputCurve() / getCursorFromInputCurve() / setCursorToInputCurve()
…points

Since the function ensures a smooth transition of orientations along
the control points, this name explicitly mentions the interpolation
of orientations and clarifies that it is associated with control points.

Suggested-by: Lee Newberg <lee.newberg@kitware.com>
…t curve

Follow-up on commit e3eceb6 and rename "setControlPoints" to
"setControlPointsByResamplingAndInterpolationFromInputCurve".

This name emphasizes a direct relationship with the input curve and
reflects the process of updating or creating control points through
resampling and interpolation.

Suggested-by: Lee Newberg <lee.newberg@kitware.com>
…ions

Remove the need to explicitly interpolate orientations by systematically
calling "interpolateOrientationsForControlPoints()" after saving or deleting
orientations.
…erence

Remove cameraNode instance variable and manage association of camera with
input curve by adding a "Camera" reference.
Selecting the curve is now sufficient to work with the associated flythrough
path.

Summary of changes:

* Remove "Use this curve" button and instead use the input curve upon
  selection.

* Simplify and instantiate one EndoscopyLogic

* Remove enableOrDisableCreateButton and consolidate all UI update in updateWidgetFromMRML

* Remove ignoreInputCurveModified instance variable and instead introduce a
  updatingControlPoints variable to guard setControlPoints

* Observe the more specific curve events "PointModifiedEvent" and "PointEndInteractionEvent"
  instead of "ModifiedEvent" and adds the corresponding callbacks "onInputCurveControlPointModified" and
  "onInputCurveControlPointEndInteractionEvent". This ensures to have the flythrough path
  updated only when user is done updating the curve or when the input curve is programmatically modified.

* Rename "onPlayButtonToggled" to "setPlaybackEnabled" to allow programmatic
  start/stop of playback.

* Reset input curve and stop playback on module cleanup or when
  "slicer.mrmlScene.StartCloseEvent" is invoked.
This commit addresses an issue in `EndoscopyLogic` related to obtaining
and setting camera properties. The problem arose from the camera being
set as a reference on the input curve, leading to inconsistencies in
managing camera observers.

The warnings "does not have observer" and "already has observer" were
reported upon closing the scene, indicating a desynchronization in camera
observer management. This commit resolves these warnings by simply removing
the observation of the camera.

Further investigation revealed that observing the camera is unnecessary
because:

1. Camera properties are retrieved during keyframe saving.
2. Camera properties are updated when adjusting the position along the flythrough path.
3. Camera view angle is updated during adjustments.
This commit ensures that clicking on the Back and Next buttons does not
progress beyond the behavior triggered by clicking on the First and Last
buttons.
@Leengit
Copy link
Contributor

Leengit commented Dec 8, 2023

Thank you for your continued efforts. In case it isn't on your radar, when I Close Scene I still get messages in the 3D Slicer Python Console

/home/local/KHQ/lee.newberg/git/Slicer/Slicer-SuperBuild-Release-Qt5.15.2/Slicer-build/bin/Python/slicer/util.py:2786: UserWarning: does not have observer
  warn("does not have observer")
[Qt] void qSlicerSaveDataDialogPrivate::updateStatusIconFromMessageCollection(int, vtkMRMLMessageCollection*, bool) Data save information: ""
[Qt] void qSlicerSaveDataDialogPrivate::updateStatusIconFromMessageCollection(int, vtkMRMLMessageCollection*, bool) Data save information: ""
[Qt] void qSlicerSaveDataDialogPrivate::updateStatusIconFromMessageCollection(int, vtkMRMLMessageCollection*, bool) Data save information: ""
[Qt] void qSlicerSaveDataDialogPrivate::updateStatusIconFromMessageCollection(int, vtkMRMLMessageCollection*, bool) Data save information: ""
[Qt] void qSlicerSaveDataDialogPrivate::updateStatusIconFromMessageCollection(int, vtkMRMLMessageCollection*, bool) Data save information: ""
[Qt] void qSlicerSaveDataDialogPrivate::updateStatusIconFromMessageCollection(int, vtkMRMLMessageCollection*, bool) Data save information: ""
[Qt] Close main MRML scene

@jcfr
Copy link
Member Author

jcfr commented Dec 8, 2023

Can you locally modify util.py (e.g in the build tree) by adding an exception like this ?

diff --git a/Base/Python/slicer/util.py b/Base/Python/slicer/util.py
index 6e4c482edd..df3df4a75e 100644
--- a/Base/Python/slicer/util.py
+++ b/Base/Python/slicer/util.py
@@ -2784,6 +2784,7 @@ class VTKObservationMixin:
             obj.RemoveObserver(tag)
         except KeyError:
             warn("does not have observer")
+            raise Exception("does not have observer")
 
     def getObserver(self, obj, event, method, default=None):
         try:

Since I am unable to reproduce, this should allow you to identify the source of the message.

@Leengit
Copy link
Contributor

Leengit commented Dec 8, 2023

Can you locally modify util.py....

I get the below. (First, I download a CT chest, segment its airway, build a curve inside the airway, play the Endoscopy flythrough, save data to a new directory and then Close Scene.)

~/git/Slicer/Slicer-SuperBuild-Release-Qt5.15.2/Slicer-build/bin/Python/slicer/util.py:2786: UserWarning: does not have observer
  warn("does not have observer")
Traceback (most recent call last):
  File "~/git/Slicer/Slicer-SuperBuild-Release-Qt5.15.2/Slicer-build/bin/Python/slicer/util.py", line 2783, in removeObserver
    group, tag, priority = methods.pop(method)
KeyError: <bound method AirwaySegmentationWidget.updateGUIFromParameterNode of <AirwaySegmentation.AirwaySegmentationWidget object at 0x7f9f08336e50>>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "~/git/Slicer/ExtensionsIndex-SuperBuild-Release-Qt5.15.2/AirwaySegmentation-build/lib/Slicer-5.7/qt-scripted-modules/AirwaySegmentation.py", line 125, in onSceneStartClose
    self.setParameterNode(None)
  File "~/git/Slicer/ExtensionsIndex-SuperBuild-Release-Qt5.15.2/AirwaySegmentation-build/lib/Slicer-5.7/qt-scripted-modules/AirwaySegmentation.py", line 169, in setParameterNode
    self.removeObserver(self._parameterNode, vtk.vtkCommand.ModifiedEvent, self.updateGUIFromParameterNode)
  File "~/git/Slicer/Slicer-SuperBuild-Release-Qt5.15.2/Slicer-build/bin/Python/slicer/util.py", line 2787, in removeObserver
    raise Exception("does not have observer")
Exception: does not have observer
[Qt] Close main MRML scene

@jcfr
Copy link
Member Author

jcfr commented Dec 8, 2023

Thanks for the feedback, the stacktrace shows that the issue is unrelated to the Endoscopy module and originated from AirwaySegmentationWidget.

I will look into fixing AirwaySegmentationWidget, in the meantime, would appreciate if you could approve 🙏

@Leengit Leengit self-requested a review December 8, 2023 14:28
Copy link
Contributor

@Leengit Leengit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all your effort and good work. I approve.

@jcfr jcfr merged commit 51d73db into Slicer:main Dec 8, 2023
5 checks passed
@jcfr jcfr deleted the Endoscopy-clean-api branch December 8, 2023 18:27
@jcfr jcfr added the backport:5.x Identify pull request expected to be backported to the current 5.x release branch. label Dec 8, 2023
@jcfr jcfr removed the backport:5.x Identify pull request expected to be backported to the current 5.x release branch. label Dec 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants