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

toggle flux <-> surface brightness units #2781

Merged
merged 13 commits into from
May 7, 2024

Conversation

gibsongreen
Copy link
Contributor

@gibsongreen gibsongreen commented Apr 2, 2024

Description

This pull request is to address adding a UI toggle for translating between surface brightness and flux within the unexposed Unit Conversion Plugin.

Main Notes:

  • Cubeviz example notebook data is in MJy / sr. When translating from MJy -> MJy / sr (second trip), data_item in data_collection is still used and has values in MJy/sr (data_collection doesn't update).
  • Address Tracebacks for UnitConversionError: 'MJy' (spectral flux density) and 'MJy / sr' are not convertible and IncompatibleAttribute: flux .
  • Upstream change needed to reset y-axis limits.
  • pixel scale factor moved to app.py (from cubeviz/spec_extract), test needs to move and be rewritten.

PR (buggy) behavior:

Screen.Recording.2024-04-25.at.4.48.56.PM.mov

Change log entry

  • Is a change log needed? If yes, is it added to CHANGES.rst? If you want to avoid merge conflicts,
    list the proposed change log here for review and add to CHANGES.rst before merge. If no, maintainer
    should add a no-changelog-entry-needed label.

Checklist for package maintainer(s)

This checklist is meant to remind the package maintainer(s) who will review this pull request of some common things to look for. This list is not exhaustive.

  • Are two approvals required? Branch protection rule does not check for the second approval. If a second approval is not necessary, please apply the trivial label.
  • Do the proposed changes actually accomplish desired goals? Also manually run the affected example notebooks, if necessary.
  • Do the proposed changes follow the STScI Style Guides?
  • Are tests added/updated as required? If so, do they follow the STScI Style Guides?
  • Are docs added/updated as required? If so, do they follow the STScI Style Guides?
  • Did the CI pass? If not, are the failures related?
  • Is a milestone set? Set this to bugfix milestone if this is a bug fix and needs to be released ASAP; otherwise, set this to the next major release milestone. Bugfix milestone also needs an accompanying backport label.
  • After merge, any internal documentations need updating (e.g., JIRA, Innerspace)?

@gibsongreen gibsongreen added this to the 3.9 milestone Apr 2, 2024
@github-actions github-actions bot added specviz plugin Label for plugins common to multiple configurations labels Apr 2, 2024
@gibsongreen gibsongreen modified the milestones: 3.9, 3.10 Apr 2, 2024
@astrofrog
Copy link
Collaborator

@gibsongreen - at first glance, I think def equivalent_units(self, data, cid, units) might need to be updated so as to return the surface brightness units too. That method is used by glue to determine the possible units that the y_display_unit callback property can take.

@gibsongreen
Copy link
Contributor Author

@gibsongreen - at first glance, I think def equivalent_units(self, data, cid, units) might need to be updated so as to return the surface brightness units too. That method is used by glue to determine the possible units that the y_display_unit callback property can take.

lambda x: x)]

Hey there, I believe that this is relating to the custom equivalency. I use the data item for any spectra that goes through spectral extraction. This remains as just x in the equivalency because the data collection item is not getting updated by the toggle. So by using x in this case the data item's flux array values don't need to be divided by the scale factor.

jdaviz/app.py Outdated
Comment on lines 87 to 94
]
+ [
'Jy / sr', 'mJy / sr', 'uJy / sr', 'MJy / sr',
'W / (m2 Hz sr)',
'eV / (s m2 Hz sr)',
'erg / (s cm2 sr)',
'erg / (s cm2 Angstrom sr)', 'erg / (s cm2 Hz sr)',
'ph / (s cm2 Angstrom sr)', 'ph / (s cm2 Hz sr)'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@astrofrog I do think this update to the equivalent units set needed to be addressed, however the tracebacks regarding IncompatibleAttribute and UnitConversionError aren't resolved from this addition.

Both tracebacks last jdaviz trigger for this diff is in unit_conversions.py at the two statements: self.spectrum_viewer.state.y_display_unit = str(spec_units). Both occur whether or not I use astropy's custom equivalencies. Everything updates in the viewer/display_units, but the tracebacks proceed.

Also, it still may be needed to determine, but with this change, y-limits on the profile viewer are not updating with this change (but this may change if the tracebacks are resolved).

@astrofrog
Copy link
Collaborator

astrofrog commented Apr 25, 2024

I had a chance to investigate this and am a little confused by the current implementation of some of the unit-related functionality which might explain the issue here. I tried out the cubeviz notebook with this branch, and currently the only available unit under surface brightness is MJy/sr, so it makes sense it will error if you try and change it to anything else currently. This appears to be due to the fact that the choices for the units are set in _on_glue_y_display_unit_changed. The choices end up being set to:

            choices = create_flux_equivalencies_list(y_u, x_u)
            # ensure that original entry is in the list of choices
            if not np.any([y_u == u.Unit(choice) for choice in choices]):
                choices = [y_unit] + choices

However create_flux_equivalencies_list currently will return an empty list for MJy/sr:

def create_flux_equivalencies_list(flux_unit, spectral_axis_unit):
    """Get all possible conversions for flux from current flux units."""
    if ((flux_unit in (u.count, (u.MJy / u.sr), u.dimensionless_unscaled))
            or (spectral_axis_unit in (u.pix, u.dimensionless_unscaled))):
        return []

so then only MJy/sr is in the list of choices. That seems like the cause of the issue here?

What I'm also confused about is why the units in the list are updated when y_display_unit is changed - that seems circular, as y_display_unit should be one of the available choices of units. The choices should be updated not when y_display_unit changes but when there is a change in the attributes on the x or y axis?

@astrofrog
Copy link
Collaborator

Ok I think I see why you are listening to changes in y_display_unit - it's because you are providing another layer of unit choices on top of the one actually available under the scenes in glue. It looks like if I access the glue viewer behind the scenes I can change the units to MJy instead of MJy/sr and the plot updates without any issues:

Screenshot from 2024-04-25 10-24-33

although this doesn't actually seem to change anything in the plot, which doesn't seem right.

Furthermore, if I convert it back to MJy / sr it raises an error, I think because the following does not get executed:

           elif '_pixel_scale_factor' in spec.meta:
                # custom equivalency
                eqv = [(u.MJy / u.sr,
                        u.MJy,
                        lambda x: (x * spec.meta['_pixel_scale_factor']),
                        lambda x: x)]

To be clear, at this point I have not even created a subset, but this should in principle work. So I guess the question is, what exactly is _pixel_scale_factor and why is the presence of that in spec.meta needed?

@astrofrog
Copy link
Collaborator

Ok so it appears that _pixel_scale_factor only gets set when using the spectrum extraction plugin, but at this point I haven't even created any subset so have not used the spectrum extraction plugin, and therefore the main dataset does not have this property. This might be the cause of the issue here? Furthermore if I do create a subset, and remove the main dataset I still run into issues - I think because when you do get_object(cls=Spectrum1D) it will give you a new spectrum object, not necessarily the collapsed spectrum you made before which had _pixel_scale_factor set?

jdaviz/app.py Outdated
spec = data.get_object(cls=Spectrum1D)
if len(values) == 2:
# Need this for setting the y-limits
spec_limits = [spec.spectral_axis[0].value, spec.spectral_axis[-1].value]
eqv = u.spectral_density(spec_limits*spec.spectral_axis.unit)

elif '_pixel_scale_factor' in spec.meta:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Check if this condition ever gets triggered for you - it does not appear to be to me, which I think is the root cause of the issues you are seeing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The pixel_scale_factor is set to spectrum that pass through Cubeviz's collapse function. Another effort that is being worked on is to have all spectra go through the collapse function instead of glues auto-collapse. When this is implemented all spectra that appear in the spectrum-viewer will have this pixel scale factor.

For the purpose of this PR, we do need to create a spatial subset, and pass it through spectral extraction in order to have a pixel scale factor stored. Once this is completed, and the translation is attempted, it will enter this elif statement. It does also mean that for now, only this one spectrum will have translate in the viewer.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the issue with this PR is that even if one layer does have this property defined, you will still see an error if any layers are not eg a subset.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In tagup today, Kyle suggested something similar. I plan on testing this PR where I only use the spectra that have passed through the collapse function (either removing the others or rebasing on Kyle's PR). I will get back to you soon on the results!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update: I removed all data items from the data collection excluding the spectrum that went through the collapse function. I still am encountering the same errors.

I also rebased onto this branch: #2827 which passes all spectra through the collapse function and I also still encounter the tracebacks

@astrofrog
Copy link
Collaborator

astrofrog commented Apr 25, 2024

A related issue is that the following also fails for me:

Screenshot from 2024-04-25 10-59-58

even though there clearly is a spectrum shown in the viewer, just not one made from a subset.

Comment on lines 55 to -57
def create_flux_equivalencies_list(flux_unit, spectral_axis_unit):
"""Get all possible conversions for flux from current flux units."""
if ((flux_unit in (u.count, (u.MJy / u.sr), u.dimensionless_unscaled))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@astrofrog I had made this update to remove u.MJ / u.sr, so the list of surface brightness units should populate with all available options.

Copy link
Member

@kecnry kecnry 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 coming along well! Just a few comments/questions from looking through the diff

'Jy', 'mJy', 'uJy',
include_prefix_units=True, equivalencies=eqv)))
+ [
'Jy', 'mJy', 'uJy', 'MJy',
'W / (m2 Hz)', 'W / (Hz m2)', # Order is different in astropy v5.3
Copy link
Member

Choose a reason for hiding this comment

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

do we know which version is for 5.3+? #2827 will bump astropy requirement beyond 5.3 so maybe at that point we can clean this up?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've found the order with which units are displayed/stored has significancy in the conversion and in causing tracebacks, I would like this to also to be cleaner. The "W / (Hz m^2)" is the unit 5.3+.

Copy link
Member

Choose a reason for hiding this comment

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

nothing to do here for now. Once rebased on top of this, #2827 can always remove the unneeded line.

jdaviz/app.py Outdated
lambda x: (x * spec.meta['_pixel_scale_factor']),
lambda x: x)]

# below is the generalized version
Copy link
Member

Choose a reason for hiding this comment

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

do we not need the generalized version already?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe that the equivalency that gets passed to:

return (values * u.Unit(original_units)).to_value(u.Unit(target_units), equivalencies=eqv)

Will be used irregardless of what the actual units for original_units and target_units. The generalized version should be used, but it results in more issues. Also, for this commit I was using the specific equivalency for the sake of simplicity in diagnosing bugs with Tom.

Copy link
Member

Choose a reason for hiding this comment

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

ok, if the other one handles the prefixes, etc, then can we just remove the commented code?

Comment on lines 81 to 83
self.flux_unit = UnitSelectPluginComponent(self,
items='flux_unit_items',
selected='flux_unit_selected')
Copy link
Member

Choose a reason for hiding this comment

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

Should we have separate flux_unit and sb_unit and the flux_or_sb switch just changes which is visible (instead of swapping out all the choices). Or, alternatively, should this be renamed to flux_or_sb_unit?

Either way, we should also think about how this affects specviz where we probably will not allow translating since we won't know the scale factor, but the input spectra could either be in flux or surface brightness 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added a return statement at the beginning of:

It is also disabled through the vue file. You can still 'see' the bool but it doesn't do anything (before if you tried you would get the ValueError thrown that there isn't a scale factor). If there are additional ways to disable between configs I will make sure to include them in this PR. A following comment addresses flux/sb_unit

self.flux_or_sb = SelectPluginComponent(self,
items='flux_or_sb_items',
selected='flux_or_sb_selected',
manual_options=['Surface Brightness', 'Flux'])

@property
def user_api(self):
Copy link
Member

Choose a reason for hiding this comment

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

we'll probably want to expose flux_or_sb through the user API here. And if we decide to rename flux_unit, then we'll need to account for backwards compatibility (there are other cases where we do that - see width/continuum_width in line_analysis, for example).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My following commit, the flux_unit and sb_unit will be included with this same methodology as line_analysis. Thank you for pointing me in the right direction! I am trying to finalize the case where the units have been translated, and after that, the units are converted (which differentiates from the data collection's data item).

@@ -52,6 +53,10 @@ class UnitConversion(PluginTemplateMixin):
flux_unit_items = List().tag(sync=True)
flux_unit_selected = Unicode().tag(sync=True)

show_translator = Bool(False).tag(sync=True)
Copy link
Member

Choose a reason for hiding this comment

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

would this ever change during an active session or is this just to disable this capability if this PR is merged before the 3.10 release?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am using show_translator for disabling right now. It does have a secondary purpose. When changed to true, an observe is triggered and the correct physical type is selected in the dropdown to start. It may not be best practices to set it this way, if this is done differently and correctly elsewhere I will make the change.

Copy link
Member

@kecnry kecnry May 6, 2024

Choose a reason for hiding this comment

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

ok, if its not easy to remove for now, then we can do this in the follow-up as well. I suspect we'll either want to listen to AddData events on the spectrum viewer or perhaps just make a call from the parser to set the initial value

EDIT: we'll be removing this in a follow-up PR.

@gibsongreen gibsongreen force-pushed the flux_sb_switch branch 2 times, most recently from 3c27481 to c158bcc Compare May 1, 2024 18:46
@gibsongreen gibsongreen modified the milestones: 3.10, 4.0 May 2, 2024
Comment on lines 45 to 46
* ``flux_unit`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
* ``flux_or_sb_unit`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
Copy link
Member

@kecnry kecnry May 3, 2024

Choose a reason for hiding this comment

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

I think this requires some thought/discussion. Do we want a single select/dropdown for flux_or_sb_unit that changes its choices based on flux_or_sb or separate flux_unit and sb_unit and just toggle which is shown - or maybe even show both (and then have changing one automatically update the other to match).

I'm leaning towards the second option since we'll eventually probably want to bring this to Imviz where there won't be a flux_or_sb selection, but will want to have a selection for surface brightness units which will affect the resulting flux units (from aperture photometry, for example).

EDIT: we'll be addressing this in a follow-up PR.

Comment on lines 82 to 92
return PluginUserApi(self, expose=('spectral_unit', 'flux_unit'))
return PluginUserApi(self, expose=('spectral_unit', 'flux_or_sb_unit', 'flux_or_sb'))
Copy link
Member

@kecnry kecnry May 3, 2024

Choose a reason for hiding this comment

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

let's only expose flux_or_sb for cubeviz. Depending on the decision on my point above regarding flux_or_sb_unit, we may or may not want to expose that for specviz/imviz as well, but if that would require extra work beside exposing here and an if-statement in the vue file, let's create a separate ticket for flux unit conversion in specviz and imviz.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On rebasing this PR: #2846

spectral_unit is now only exposed. Unexposing flux_or_sb_unit is why this test is failing, I know how to use _obj/unexposed functions, I am unsure how to use this class instance if it isn't exposed. I might expose it temporarily just so I can run the CI and know if all tests are passing:

def test_sb_unit_conversion(cubeviz_helper):

Copy link
Member

Choose a reason for hiding this comment

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

This PR can re-expose whatever is needed. We just wanted to hide them for the 3.10 release so we could have flexibility to decide on the API still.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bearing CI passing, I will move on to adding the flux_unit and sb_unit and toggling between them

Copy link

codecov bot commented May 3, 2024

Codecov Report

Attention: Patch coverage is 95.16129% with 3 lines in your changes are missing coverage. Please review.

Project coverage is 88.94%. Comparing base (71c8197) to head (8b1e752).
Report is 12 commits behind head on main.

Files Patch % Lines
...z/configs/imviz/plugins/coords_info/coords_info.py 50.00% 2 Missing ⚠️
...specviz/plugins/unit_conversion/unit_conversion.py 97.29% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2781      +/-   ##
==========================================
+ Coverage   88.90%   88.94%   +0.03%     
==========================================
  Files         111      111              
  Lines       17148    17201      +53     
==========================================
+ Hits        15246    15299      +53     
  Misses       1902     1902              

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

Copy link
Member

@kecnry kecnry 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 the hard work on this one! This works for me under the assumption that:

  • Cubeviz spectral extraction through plugin #2827 will be coming soon (this PR intentionally leaves things in an "awkward" state where only extracted spectra are handled... if for some reason Cubeviz spectral extraction through plugin #2827 is abandoned, we would need to either revert this or change the implementation)
  • test coverage is increased before merge to trigger this logic (although that could also be a follow-up if it'll be significantly easier after Cubeviz spectral extraction through plugin #2827 exists)
  • we have tickets for follow-up efforts to:
    • split the SB and flux dropdowns into two and enable for non-cubeviz configs 🐱
    • remove the show_translator traitlet entirely
  • changelog entry is added (as the PR stands now, the UI does not expose this, but the API does, so let's at least create an entry that links back to this PR and we'll likely append other PR numbers to it as the rest is exposed)

@kecnry kecnry removed the no-changelog-entry-needed changelog bot directive label May 6, 2024
@kecnry kecnry marked this pull request as ready for review May 6, 2024 14:24
Copy link
Contributor

@javerbukh javerbukh left a comment

Choose a reason for hiding this comment

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

There's more work to be done when enabling the translator but this is a good foundation for other PRs to build on. Thank you for your patience with this one!

@gibsongreen gibsongreen merged commit 5cbbdae into spacetelescope:main May 7, 2024
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cubeviz plugin Label for plugins common to multiple configurations specviz
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants