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

BUG: fix a bug where wcs_info_str's results would look different in numpy 2 VS numpy 1 #16586

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

Conversation

neutrinoceros
Copy link
Contributor

Description

Fixes #16585
I spent 20min trying to write a test for this but so far I couldn't find a simple example for setting up a non-trivial WCS object (e.g. WCS(), for which the bug isn't apparent).
In particular the example test in ndcube uses ndcube API so I can't take it straight from the report.
@Cadair, any pointers ?

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

Copy link

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

Copy link

👋 Thank you for your draft pull request! Do you know that you can use [ci skip] or [skip ci] in your commit messages to skip running continuous integration tests until you are ready?

@neutrinoceros neutrinoceros force-pushed the wcs/bug/numpy2_repr branch 5 times, most recently from aae680b to 032f52e Compare June 18, 2024 12:53
@neutrinoceros
Copy link
Contributor Author

Meanwhile, it took me a couple iterations to not break existing tests so maybe I can draw some inspiration from these.

@neutrinoceros
Copy link
Contributor Author

Alright, added a (partial) test where it seemed to make sense, let me know if you think it's worth building a more complete one.

@neutrinoceros neutrinoceros marked this pull request as ready for review June 18, 2024 13:07
Copy link
Contributor

@mhvk mhvk left a comment

Choose a reason for hiding this comment

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

This works, but it slightly feels like one is papering over shape being set to numpy integers. Though perhaps duck typing is really all OK.

An alternative to what you have might be to have a helper function that typesets the tuple with str instead of repr for the items, i.e.,

def format_tuple(t):
    contents =  ", ".join([str(_t) for _t in t])
    return f"({contents})" if len(t) != 1 else f"({contents},)"

EDIT: Could obviously adjust to take care of None too.

@@ -36,16 +36,21 @@ def deserialize_class(tpl, construct=True):
def wcs_info_str(wcs):
# Overall header

if wcs.array_shape is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

In principle, fine, though it is a bit odd that array_shape contains numpy scalars to start with - regular array.shape just contains int.

@@ -60,13 +65,24 @@ def wcs_info_str(wcs):
'Bounds\n')
# fmt: on

if wcs.pixel_bounds is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the same comment holds here.

@mhvk
Copy link
Contributor

mhvk commented Jun 19, 2024

Thinking a bit more, the current approach does seem best - we should not have to worry about the exact type used in the shape, etc. So, just the suggestion for a quick helper function remains.

@neutrinoceros
Copy link
Contributor Author

@mhvk I'm not sure that a helper function would be helpful here as we actually have 3 slightly different situations:

  • in one case, we want to use 'None' as a placeholder if wcs.array_shape is None
  • then, on the same condition and for the same tuple, we use '(0,)' as the placeholder
  • later, we use (None, ..., None) (with a varying number of Nones) as a placeholder for wcs.pixel_bounds

Trying to fit all this logic in one function wouldn't be much simpler than just implementing 3 in-place solutions, IMHO.
However, in the last case, I think the logic can be simplified, so let me do that now.

for ipix in range(wcs.pixel_n_dim):
# fmt: off
s += (('{0:' + str(pixel_dim_width) + 'g}').format(ipix) + ' ' +
('{0:' + str(pixel_nam_width) + 's}').format(wcs.pixel_axis_names[ipix] or 'None') + ' ' +
(" " * 5 + str(None) if pixel_shape[ipix] is None else
('{0:' + str(pixel_siz_width) + 'g}').format(pixel_shape[ipix])) + ' ' +
'{:s}'.format(str(None if wcs.pixel_bounds is None else wcs.pixel_bounds[ipix]) + '\n'))
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this one actually a problem? Since it was passed in to str, I would think the representation would not change. That would also remove the need for the less elegant of the overrides...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is a problem because in the application that @Cadair showcased, wcs.pixel_bounds[ipix] is a tuple (of numpy scalars). My test actually covers this, so you may convince yourself by checking out the test without the patch :)

Copy link
Contributor

Choose a reason for hiding this comment

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

It's weird because str(numpy_int) and str(int) should still be the same (sorry, have to run, can check later).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

they are, but str((np.int64(1),)) == '(np.int64(1),)' != str((int(1),)) == '(1,)'

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I didn't realize wcs.pixel_bounds[ipix] itself returned a tuple. Should have looked better...

@pllim pllim added the backport-v6.1.x on-merge: backport to v6.1.x label Jun 21, 2024
@pllim pllim requested review from mcara and Cadair June 21, 2024 20:21
@neutrinoceros
Copy link
Contributor Author

@Cadair what do you think of this patch ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-v6.1.x on-merge: backport to v6.1.x Bug wcs.wcsapi
Projects
None yet
Development

Successfully merging this pull request may close these issues.

wcs_info_str doesn't play nice with numpy 2.0 reprs
3 participants