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

feat: RGB support #41

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

feat: RGB support #41

wants to merge 2 commits into from

Conversation

gselzer
Copy link
Collaborator

@gselzer gselzer commented Aug 29, 2024

This PR aims to enable the viewing of RGB datasets. Users specify that their provided data is RGB using the new ChannelMode value. The current mechanism enables this mode when the channel axis (specified through existing means) has exactly three indices.

To enable the selection of different modes, the ChannelModeButton is refactored into a ChannelModeComboBox, which can be altered based on the data to contain the rgb mode.

image

This PR requires review, and likely additional minor cleanup.

Closes #20

Copy link

codecov bot commented Aug 29, 2024

Codecov Report

Attention: Patch coverage is 88.23529% with 12 lines in your changes missing coverage. Please review.

Project coverage is 77.87%. Comparing base (10abe13) to head (783dd39).

Files with missing lines Patch % Lines
src/ndv/viewer/_backends/_pygfx.py 61.53% 5 Missing ⚠️
src/ndv/viewer/_components.py 80.95% 4 Missing ⚠️
src/ndv/viewer/_viewer.py 93.61% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #41      +/-   ##
==========================================
- Coverage   77.90%   77.87%   -0.04%     
==========================================
  Files          13       13              
  Lines        1856     1885      +29     
==========================================
+ Hits         1446     1468      +22     
- Misses        410      417       +7     

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

Copy link
Collaborator Author

@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.

Just wrote down some remaining concerns I had

self.setChecked(mode == ChannelMode.MONO)
super().__init__(parent, enum_class=ChannelMode)

def enable_rgb(self, enable: bool) -> None:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would be happy to hear if the reviewers have any better ideas here - this seemed like solution that added the fewest lines of code.

src/ndv/viewer/_viewer.py Outdated Show resolved Hide resolved
src/ndv/viewer/_viewer.py Outdated Show resolved Hide resolved
@gselzer
Copy link
Collaborator Author

gselzer commented Sep 4, 2024

Converting this back into a draft because additional changes are needed to support smooth transitions between modes

@gselzer gselzer marked this pull request as ready for review September 5, 2024 22:38
@gselzer
Copy link
Collaborator Author

gselzer commented Sep 5, 2024

I'm much happier with this after a couple of changes, however I've built it on top of #40, so that PR must be merged first

@tlambert03
Copy link
Member

merged #40, can you resolve conflicts?

@gselzer
Copy link
Collaborator Author

gselzer commented Sep 16, 2024

merged #40, can you resolve conflicts?

Sure!

@gselzer gselzer force-pushed the rgb-support branch 2 times, most recently from 7e6e69f to ba216c6 Compare September 16, 2024 20:56
@gselzer
Copy link
Collaborator Author

gselzer commented Sep 16, 2024

@tlambert03 conflicts resolved!

@tlambert03
Copy link
Member

tlambert03 commented Sep 17, 2024

could you add that nifty rgb demo to the examples folder so we have a quick way to test rgb?

how did i miss it 🤦‍♂️ ... sorry :)

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.

I'm happy about the direction here! couple small discussion points. Mostly, we need to do a little magic/heuristics here and i think it's worth laying out exactly what assumptions we are going to make about data formats coming in

src/ndv/util.py Outdated Show resolved Hide resolved
self.set_visualized_dims(visualized_dims)

is_rgb = (self._channel_axis is not None) and (sizes[self._channel_axis] == 3)
Copy link
Member

Choose a reason for hiding this comment

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

we also need to allow a size of 4 for rgba

Copy link
Member

Choose a reason for hiding this comment

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

also, just curious to discuss what we intend to imply by having "rgb" available here in the combo box. I guess it means "we could show this to you as rgb if you want"... and perhaps that's ok. But let's briefly discuss the case of something like a three-channel fluorescence image (3, Y, X). It's true that we could show you that as RGB, but chances are it won't look reasonable if it's not an 8bit file (since channel scaling would be way off), whereas something that comes in as (Y, X, 3) does imply something different about the input. In either case, it's harmless to show the rgb option in the dropdown, but it may not be useful. worth discussing

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

also, just curious to discuss what we intend to imply by having "rgb" available here in the combo box.

Yeah, the thought was that a 3-channel image may or may not be RGB, and it would be wrong to assume such. If the user does not specify the mode, then 3-channel images still appear with ChannelMode.MONO. But then if you want to view in RGB, you can do so, as the option is now available in the combo box.

But let's briefly discuss the case of something like a three-channel fluorescence image (3, Y, X). It's true that we could show you that as RGB, but chances are it won't look reasonable if it's not an 8bit file (since channel scaling would be way off),

Yup, this was the reasoning behind ChannelMode.MONO remaining the default.

whereas something that comes in as (Y, X, 3) does imply something different about the input.

I'm unsure if this was explicitly mentioned, but passing data in either (Y, X, 3) or (3, Y, X) should enable RGB viewing i.e. if you have a channel axis (discovered using existing logic) with 3 indices, RGB mode is enabled (and we can add 4 indices if desired).

By and large, we wanted to avoid making assumptions about the data, enabling an RGB mode wherever possible without assuming that the user wants it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

alpha support has been added with 339607c, and (Y, X, 3)/(Y, X, 4) images are now displayed as RGB(A) when the channel mode is auto. Notably, (3, Y, X) images are not.

src/ndv/viewer/_lut_control.py Outdated Show resolved Hide resolved
@gselzer
Copy link
Collaborator Author

gselzer commented Sep 17, 2024

@tlambert03 added a WIP commit fleshing out alpha support. Couple questions:

  • Shall we rename the channel mode (e.g. rgba?)
  • Should we have multiple examples? Do you like the alterations made to examples/rgb.py in that commit?

@gselzer
Copy link
Collaborator Author

gselzer commented Sep 17, 2024

Oh, and one more discussion point @tlambert03:

These SETUP lines have given me a bit of a headache. I've found that if you set the channel mode to RGB before setting the data, the channel axis is present as a slider, until you select a different mode (because NDViewer.set_channel_mode contains the logic for removing the channel slider if not mono mode). If you set the channel mode after, we end up in this strange intermediate state where RGB mode is "selected", but I can only see the final channel of the data. Thoughts?

image

@tlambert03 tlambert03 mentioned this pull request Sep 19, 2024
@tlambert03
Copy link
Member

Oh, and one more discussion point @tlambert03:

These SETUP lines have given me a bit of a headache.

played around with this a bit... i feel like setting channel mode second "feels" more correct (since it's more likely to be changed in the course of viewing). does this work for you? gselzer#3

@gselzer
Copy link
Collaborator Author

gselzer commented Oct 4, 2024

* Shall we rename the channel mode (e.g. `rgba`?)

I took the liberty of renaming it!

@tlambert03
Copy link
Member

tlambert03 commented Oct 6, 2024

This is all looking good and we can finish it up this week, I was thinking that the main thing to fix that race condition is probably just to remove the conditional check for existing image handles (there's little harm in calling refresh anyway, and it will cancel any waiting futures anyway). So, that, combined with putting the call to set channel mode after set data mode in the init should be good I hope

src/ndv/util.py Outdated Show resolved Hide resolved
src/ndv/viewer/_backends/_pygfx.py Outdated Show resolved Hide resolved
src/ndv/viewer/_backends/_pygfx.py Outdated Show resolved Hide resolved
@tlambert03
Copy link
Member

in e1ae37c I removed the conditional checks before calling self.refresh() ... i'm curious to know whether you can still hit the problem mentioned in #41 (comment)

@gselzer
Copy link
Collaborator Author

gselzer commented Oct 7, 2024

in e1ae37c I removed the conditional checks before calling self.refresh() ... i'm curious to know whether you can still hit the problem mentioned in #41 (comment)

I cannot see either issue now! No slider, and I'm seeing an rgba image! Great!

@tlambert03
Copy link
Member

seems that:

# run rgb example then...

In [15]: wdg.set_data(ndv.data.cells3d())

In [16]: wdg.set_data(img)

does show img as rgb, but composite is still selected in the combobox.

(sidenote: cells3d is shown as noise ... which is weird, but probably unrelated here)

@gselzer
Copy link
Collaborator Author

gselzer commented Oct 8, 2024

@tlambert03 I'd really like to squash all these commits together soon, as the history is rather messy, and do one final review.

This script is helpful for testing:

import math

import numpy

import ndv
from qtpy.QtWidgets import QApplication, QPushButton

# 2D RGB dataset
rgb = numpy.zeros((256, 256, 4), dtype=numpy.uint8)
for x in range(256):
    for y in range(256):
        rgb[x, y, 0] = x
        rgb[x, y, 1] = y
        rgb[x, y, 2] = 255 - x
        rgb[x, y, 3] = int(math.sqrt((x - 128) ** 2 + (y - 128) ** 2))

# 4D ZCYX dataset
cells = ndv.data.cells3d()

# Button to switch between them
def switch(checked: bool) -> None:
    n.set_data(rgb if checked else cells)
app = QApplication([])
btn = QPushButton("Switch image")
btn.setCheckable(True)
btn.toggled.connect(switch)

# Run it!
n = ndv.NDViewer()
n._btns.addWidget(btn)
n.show()
app.exec()

@gselzer gselzer force-pushed the rgb-support branch 9 times, most recently from a364d4f to 165978f Compare October 9, 2024 19:14
For provenance, VisPy allows RGB data to come from unsigned integers
and floating point data pre-nomalized within the range [0, 1].
It was only used once, and by inlining the functionality earlier on we
can save some lines of code later
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.

todo: support rgb
2 participants