Skip to content

Better gui for many channels (wx-specific) #195

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

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

gnodar01
Copy link

  • Creates a scrollable area for LUTs
  • Creates a multi-select to choose which LUTs go in the scrollable area

Resolves #193

gnodar01 and others added 5 commits May 12, 2025 10:30
- only numbers in display options
- only show display options if composite
- TODO: only display selection options on mode change
Copy link

codecov bot commented May 13, 2025

Codecov Report

Attention: Patch coverage is 96.85864% with 6 lines in your changes missing coverage. Please review.

Project coverage is 85.73%. Comparing base (758d519) to head (774c7c7).

Files with missing lines Patch % Lines
src/ndv/views/_wx/_array_view.py 96.85% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #195      +/-   ##
==========================================
+ Coverage   85.23%   85.73%   +0.50%     
==========================================
  Files          46       46              
  Lines        4963     5146     +183     
==========================================
+ Hits         4230     4412     +182     
- Misses        733      734       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gnodar01
Copy link
Author

gnodar01 commented May 13, 2025

@tlambert03 @gselzer Implemented what was requested in #193.

Screen.Recording.2025-05-13.at.5.57.49.PM.mov

Draft because it could use a bit of cleanup, but also there's a bit of a problem around lifecycle control. In the video you can see when I switch back to composite mode, all of the channels are visible instead of only the ones selected in the options.

First channel mode is WxArrayView.set_channel_mode is called and then after each WxLutView.set_visible is called. If this happened in the opposite order I could emit an event after set_channel_mode to call set_visible on only the luts that are selected in the multiselect options (at the cost of having two calls of set_visible per LUT view per mode change).

There's other approaches, but I wanted to avoid a state injection on WxLutView that conditions set_visible on whether its enabled in the multiselect options.

Plenty of other approaches, but wanted to get your thoughts first. TBH the scroll area gets me most of what I want anyway, so I wouldn't bemoan getting rid of the multiselect in general. But if you like it, any advice on how we could change up the lifecycle, or whether that's the wrong way to think about it?

Also haven't looked into putting this into the tests yet, but any pointers would be appreciated.

@gselzer
Copy link
Collaborator

gselzer commented May 14, 2025

@gnodar01 this is great! Thanks so much for jumping in!

TBH the scroll area gets me most of what I want anyway, so I wouldn't bemoan getting rid of the multiselect in general.

Could always split this into multiple PRs!

There's other approaches, but I wanted to avoid a state injection on WxLutView that conditions set_visible on whether its enabled in the multiselect options.

Yeah, that's tricky...without knowing too much about wx, do you think the lut selector could tap into the wx.ShowEvent emitted by the LutView's set_visible? It seems like Event filters in wx are pretty coarse-grained, which is unfortunate...

@gselzer
Copy link
Collaborator

gselzer commented May 14, 2025

Also haven't looked into putting this into the tests yet, but any pointers would be appreciated.

Oh, and by the way, I think the place to test this behavior would be here

@tlambert03
Copy link
Member

just wanted to also express my enthusiasm and gratitude :) will have a look soon if @gselzer hasn't already given it a full review by then

@gnodar01
Copy link
Author

gnodar01 commented May 14, 2025

Of course! Thanks for saving me a ton of time by creating ndv.

Okay, so I ended up fixing by taking a different approach such that WxLutView.set_visible now emits rather than calling Show/Hide on the widget directly. Then we validate later in WxArrayView and call Show/Hide only if necessary.

When reviewing, please do pay attention to my use of Signal. It's a really great tool (love the observer pattern), but I fear I may have abused it.

Will add tests in the meantime.

Copy link
Member

@tlambert03 tlambert03 left a comment

Choose a reason for hiding this comment

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

just a few high level observations and thoughts, and a couple inline nits

  • if there are only a few channels, that "channel display options" row takes up some unfortunate space. So I wonder whether the whole behavior (or at least the visibility of that option) should be gated behind some minimum number of channels?
  • i wonder whether toggling the inclusion of a channel in the channel display options should also toggle the visibility of the channel itself? (that is, do we ever want a channel to be visible on the canvas that doesn't show up in the list of channels)? I noticed that by trying it with a 16 channel image, choosing "select none", and being a little confused at first at what I was looking at, and why it had so many channels
  • not for this PR, but a note for my future self. the relatively slow performance with 16 channels, even when only one of them is showing at all, makes me wonder whether we're making unnecessary slicing and/or rendering calls for non-visible textures

@gnodar01
Copy link
Author

gnodar01 commented May 15, 2025

if there are only a few channels, that "channel display options" row takes up some unfortunate space. So I wonder whether the whole behavior (or at least the visibility of that option) should be gated behind some minimum number of channels?

Good idea, will add that.

Update: Done ✅

i wonder whether toggling the inclusion of a channel in the channel display options should also toggle the visibility of the channel itself? (that is, do we ever want a channel to be visible on the canvas that doesn't show up in the list of channels)? I noticed that by trying it with a 16 channel image, choosing "select none", and being a little confused at first at what I was looking at, and why it had so many channels

Didn't even think about that. Will add that as well.

Update: Done ✅

I have it so it automatically sets visibility off when display is off. When display is set to on, you then have to set visibility on manually. That felt more natural to me, but can easily make it so display on triggers visibility on too.

@gnodar01 gnodar01 marked this pull request as ready for review May 19, 2025 20:33
@gnodar01
Copy link
Author

gnodar01 commented May 19, 2025

@tlambert03 @gselzer Tests done. Ready for review, and merge if no problems. Happy to address any additional changes as well.

Copy link
Collaborator

@gselzer gselzer left a comment

Choose a reason for hiding this comment

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

This has made some nice steps forward since I last looked! Thanks again @gnodar01!

I have it so it automatically sets visibility off when display is off. When display is set to on, you then have to set visibility on manually. That felt more natural to me, but can easily make it so display on triggers visibility on too.

I actually feel the opposite, although I wouldn't say I feel terribly strongly:

  • If you click "Select None" and then "Select All", it's unfortunate that to actually see the data you'd then have to go through and click all of the "Visible" checks for each channel.
  • If your goal is just to visualize Channel 5 (of some large number of channels), you might click "Select None", and then you click on Channel 5 in the "Channel Display Options". But then there's the additional inconvenience of checking the visible box.

(that is, do we ever want a channel to be visible on the canvas that doesn't show up in the list of channels)?

There's a lot of overlap, isn't there? 😅

In fact, I think another question has merit: Do we ever want a channel to show up in the list of channels if it is not visible on the canvas?

Certainly, if you're below the threshold required to show the LUT selector, then you need all of the channels in the list.

However, whenever the LUT selector is present, the two checkboxes provide a bit of redundancy, and a possible source of bugs, no?

I know it's late in the development process here (especially those tests, very nice 😍), so feel free to disregard, but I was inspired to draw a potential alternative design, which would only expose a single source of "LUT visibility" - thoughts @tlambert03, @gnodar01?
thumbnail_1000007190

# mostly copied from _qt.qt_view._QLUTWidget
class _LutChannelSelector(wx.Panel):
# actually set[ChannelKey] | set
selectionChanged = Signal(set)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this signal actually necessary in practice? I tried commenting out wdg.lut_selector.selectionChanged.connect(lambda: self._wxwidget.Layout()), and I couldn't notice the effect...

Comment on lines 509 to +525
def set_visible(self, visible: bool) -> None:
if visible:
if visible and self._display_status != _DisplayStatus.HIDDEN:
self._wxwidget.Show()
else:
self.lutUpdated.emit()
# elif visible do nothing
elif not visible:
self._wxwidget.Hide()
self.lutUpdated.emit()

def set_display(self, display: bool) -> None:
if display and self._display_status == _DisplayStatus.HIDDEN:
self._display_enable()
self.lutUpdated.emit()
# elif display do nothing
elif not display:
self._display_hidden()
self.lutUpdated.emit()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you provide some clarification and comments on what the difference between these functions is? Not sure I really understand...


self.lut_selector.Hide()
self._lut_toolbar_panel.Hide()
self._lut_toolbar_shown = False
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could just rely on the state of self._lut_toolbar_panel or self.lut_selector instead of using a separate variable, since their visibility always seems to be synced...

Comment on lines +856 to +857
if len(self._luts) < self._wxwidget._toolbar_display_thresh:
self._wxwidget.toggle_lut_toolbar(False)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm guessing there's a potential bug here where you have self._wxwidget._toolbar_display_thresh channels, some of which are not displayed. If you then remove a lut view to go below the threshold, making the toolbar vanish, you then have no UI controls to visualize the channel?

...I don't think this method is currently called by anything though, so I don't know, maybe just throw in a FIXME?

Comment on lines +134 to +141
# channel_mode = viewer._wxwidget.channel_mode_combo
# viewer.set_channel_mode(ChannelMode.GRAYSCALE)
# _processEvent(wxapp, wx.EVT_COMBOBOX, channel_mode)

## all channels should be hidden
# for ch, lut_view in viewer._luts.items():
# if type(ch) is int or (type(ch) is str and ch.isdigit()):
# assert not lut_view._wxwidget.IsShown()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just wondering about the status of this code. Is this something you wanted to keep or move somewhere? Or does it not pass (yet)?

@gselzer
Copy link
Collaborator

gselzer commented May 20, 2025

Aww, man, double review?! Sorry! 🫣

EDIT: Wow, my review appeared on my machine as two reviews, so I hid one and refreshed, and now only the hidden one remains! Sorry for the confusion!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[feat request] Implement a way to organize composite view on many-channel images
3 participants