Issues Modifying the Transform Tree

Hi all,

my team is currently developing a Slicer/Python module for a internal research project.
At the moment I am still waiting for the permission to publish parts of the source code here, so I cannot provide you with the entire plugin code so far (which hopefully will change soon :slight_smile:).

What the plugin is essentially loading multiple volumns and performs some registrations (via BRAINS).
This finally gives me the following (linear) transform tree:
volumeT2h → volumeT2 → volumeT1 → volumeCiss
where “->” is the registration transform.

So I want to put volumeT1 into the “global” world coordinate system. To do so, I copy the transform from volumeT1 to volumeCISS, invert the linear matrix and perform the following commands:

volCiss.SetAndObserveTransformNodeID(tfCissToT1.GetID()) <— tfCissToT1 is the inverted transform
volT1.SetAndObserveTransformNodeID(None)
volT1.Modified() <— may not required

The registration steps as well as the transform handling and the three lines above are executed by a procedure inside my module.

And here is the weird behavior: After the procedure has finished, the transform tree remains unchanged (only) for volT1, which still “observes” the tfT1toCiss, whereas volT1 should actually be in world space.

So I tried to debug this issue with the debugger (PyCharm) and right after these three lines the evaluation of the volT1 node tells me that there is no transform node assigned to volT1. But again, after the procedure has finished, the SAME evaluation (done on the internal Slicer/Python console) tells me, that volT1 is assigned to tfT1toCiss.

In addition to that, if I then execute the three lines (above) manually inside the Slicer/Python console, the commands work “correctly” and I finally get the expected result.

I finally modified the procedure to the following scheme:

def my_procedure():
…do_some_preprocessing_and_registrations()
…my_three_lines_from_above()
…[At this point, my debugger tells me that volT1 has no transform assigned anymore]
…try:
…import code
…code.interact(local=locals())
…except SystemExit:
…pass
…[At this point, volT1 has the unwanted transform assigned, again]
…my_three_lines_from_above() [again :slight_smile: ]
…[At this point, volT1 has the no transform assigned. Yeah!]

(Dots indicate whitespaces.)

At the call code.interact(), an interactive interpreter is accessable inside the Slicer/Python console, in which I always type in “exit()”. Now, aside that console-exit()-workaround-thing, the procedure works as desired.

This behavior is waaaaay unexpected to me and, unfortunately, out of my knowledge scope :frowning:

May some of you folks have an idea, why Slicer currently behaves like this.

Again, I’m going to talk to my team in order to get the permission to publish the entire source ASAP.

Cheers,
Johann

We cannot reproduce the problem based on the information you provided and without that we cannot investigate or fix anything. Please post a complete python snippet that reproduces the problem. You can start by loading some images using SampleData volume, assigning parent transforms, setting transformation matrices in them, etc.

1 Like

Do you actually have this in your code? There is no such method in Slicer.

One more thing: when a registration CLI module completes the registration successfully, it sets the computed transform as a parent of the moving volume (so that you can actually see the registration result). You may swap fixed/moving volumes you don’t like this or just set the desired parent transforms after the registration is completed. Note that CLI modules run in the background by default and they set the parent transform when the registration is completed (it can happen anytime). If it confuses your module then either wait for completion of the registration or add an observer to the CLI module node and reorganize the tree when the registration is finished.

Wow! Thank you for your fast response!

Yes indeed, the method I am referring to is SetAndObserveTransformNodeID.

Sorry, I forgot to mention that I am using a recent nightly build of Slicer. But the described behavior is equal to Slicer 4.5.

I’m going to try to get the permission next week. Then I will report back to you.

Cheers,
Johann

I have uploaded a slightly debloated version of the widget with an example dataset integrated.
You can find it here:

Short instruction to trigger the strange behavior:
-Right after you start the widget, click at the “Load data and init” button.
-Set RotationIS to -180.0
-Set LR to 180.0, PA and IS to 0.0!
-Switch back to the test widget.
-Click at “Run global registrations”.

After a few seconds the Python prompt will show up in interactive mode. At line 359 in the widget source ([1]), I applied the transform modification, which seems to be without effect. (volT1 is not in global world space…)
To continue the execution, type “exit()” in the prompt.
The code after line 377 ([3]) applies again the transform modification. But this time it is successful. (volT1 is now in global world space!)

Without the try/catch code ([2]) in between, both [1] and [3] fail to apply the transform modification. And I cannot figure out, why… :frowning:

Cheers,
Johann

Calling slicer.app.processEvents() at the end of runBRAINSRegistration method will solve your problem.

Explanation: When a registration CLI module completes the registration successfully, it sets the computed transform as a parent of the moving volume (so that you can actually see the registration result). This parent transform setting does not happen immediately but when the the application becomes idle or slicer.app.processEvents() is called. So, you have to make sure you set the parent transform after the CLI module’s parent transform setting is completed.

1 Like

Sir, you hit the nail right on its head!
That function call finally fixes my issue.

Thank you for your support and for the explanation.

Sorry to ask you again.

We tried to call this slicer.app.processEvents() method and it worked fine on Windows and Linux. Unfortunately, it did not work on MacOS.
Any ideas?

Cheers,
Johann

This should work the same way on all OS. Are you sure you use exactly the same Slicer and module versions?

I have prepared a slightly modified version of the first TestWidget example script (but needs volumes from TestWidget.zip):

I was able to verify the issue on my own Hackintosh setup on Slicer 4.6.

I just added the processEvents() to the BRAINS registration method.
-Linux, Slicer 4.6+4.7(nightly) -> works!
-Windows Slicer 4.6 -> works!
-MacOS Slicer 4.6+4.7(nightly) -> does not work!

The tested Slicer versions slightly differ. However, I guess this issue is not sensitive to the specific Slicer versions but to the used OS.
Maybe it is a Qt/MacOS bug and Slicer-independent.

Is the problem the same as before (moving volume’s parent transform is overwritten after the CLI execution is completed)?

Just for testing, you may add some delay and see if it changes things:

slicer.app.processEvents()
time.delay(5)
slicer.app.processEvents()

Yes, the problem is the same as before on MacOS.

I guess you mean time.sleep(5) as time.delay does not exist.
Unfortunately, no changes using these three lines. (using time.sleep)

I’m working on eliminating the need for processEvents() to make the results more deterministic. It may fix your problem on Mac. It’ll be available in a couple of days.

1 Like

Thank you, sir!
I’ll try it out as soon as the new version is available!

I’ve made the mechanism more robust for setting parent transforms at the end of CLI module execution. Please test the nightly build that you can download tomorrow or later.

2 Likes

I finally managed to test the latest nightly build at all our MacOS setups and it seems to be fixed! :slight_smile:

Thank you for all your support!

1 Like