DIPlib / diplib

Quantitative Image Analysis in C++, MATLAB and Python
https://diplib.org
Apache License 2.0
228 stars 50 forks source link

dip.Image.ShowSlice #103

Closed wcaarls closed 2 years ago

wcaarls commented 2 years ago

I have added dip.Image.ShowSlice (4afd092d), and would like to make that the recommended way of creating PyDIPviewer windows, instead of dip.viewer.Show. Given that the viewer is shipped in all binary wheels, a tighter integration seems to make sense. What do you think?

crisluengo commented 2 years ago

I like this!

Could we make it dip.Image.Show, and rename the other one? Or would that be confusing?

I can even imagine removing the pyplot function altogether. I bet that not importing pyplot would make the startup shorter…

crisluengo commented 2 years ago

Would there be a way for the C++ code to spawn a thread that does the event loop? It’d be cool if there is no explicit running of Spin. Under Linux with GLUT it’s a lot easier to use this viewer than on macOS with GLFW. I have no idea if there would be any issues with this running in parallel to the Python interpreter?

wcaarls commented 2 years ago

The reason to keep the pyplot functionality is that it works nicely in Jupyter notebooks, especially ones that run remotely. I could imagine renaming dip.Image.Show to Plot, but what about legacy code? In any case, I would be more comfortable with that if dip.viewer.Spin was unnecessary.

It is possible to spawn a thread for GLFW in the same way as is done for GLUT, but it would only work for Linux and Windows. Under MacOS, the event loop must be run in the main thread. Since Linux and Windows already use GLUT, not much is gained. Matplotlib uses some magic to hook into the repl that I haven't been able to figure out (this is why I created examples/python/pydip.py). I will try to look again.

wcaarls commented 2 years ago

I committed a change that hooks dip.viewer.Draw into the REPL event hook (ae1e739). However, this is not without caveats, because only one hook can be installed. As such, I only install it when Show is called, do not overwrite an existing hook, and remove it after a call to Spin or CloseAll. Even so, please test this in as many situations as possible!

Note: the hook is only called at 10Hz, so interaction is not as smooth as GLUT, or calling Spin.

crisluengo commented 2 years ago

Nice!

This works great on both my Intel macOS 11 desktop and my M1 macOS 12 laptop. Except once you've used pyplot, the event hook is taken, and doesn't seem to be released ever. Also, pyplot seems to just steal the event hook away from DIPviewer:

import diplib as dip
a = dip.ImageRead('cermet')
a.ShowSlice()
# Interaction is immediately possible (though slow)
a.Show()
# Interaction is now possible with the pyplot window, no longer with the DIPviewer window
a.Spin()
# Interaction is now possible in both

We should document this somewhere, but I don't really know where or how... it's weird behavior! Still, I like it better than before.

One advantage of calling dip.viewer.Draw at least once is that you can now exit Python without all the error messages if you happen to leave a window open.

crisluengo commented 2 years ago

Since dip.ShowSlice is an alias for dip.ShowViewer, why don't we just use the ShowViewer name directly?

Image.ShowViewer = ShowViewer

It avoids one extra name in the module:

>>> dip.ShowTAB
dip.Show(        dip.ShowModal(   dip.ShowSlice(   dip.ShowViewer(  

Not because there are too many names, but just to avoid confusion: which one do I use?

wcaarls commented 2 years ago

Hmm, I did not intend to expose any of those functions in the dip namespace. I suggest just having

Unless you like ShowViewer better.

crisluengo commented 2 years ago

No, I like the ShowSlice name, it rolls well. :)

Can we put an underscore in front of the functions in the dip namespace? Or should we declare these functions directly in the dip.viewer namespace? (how?) Or maybe it is possible to del these functions after they have been assigned to the proper names?

wcaarls commented 2 years ago

Done (fb550d2). I also delayed loading matplotlib until Show is actually called. The behavior is still weird from a user standpoint though.

I installed IPython just because tab completion doesn't work in Windows. But that REPL works differently, so I also wrote some code to integrate with that. As a bonus, at least here it allows smooth interaction in both dipviewer and matplotlib windows!

crisluengo commented 2 years ago

Nice! Thanks!

Any reason you don't want to do Image.ShowSlice = _ShowSlice?

wcaarls commented 2 years ago

Blindness? :-) .