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

Faster shortcut for working out coordinates values for non-correlated WCS #780

Merged
merged 12 commits into from
Dec 13, 2024

Conversation

nabobalis
Copy link
Contributor

@nabobalis nabobalis commented Nov 8, 2024

Fixes #585

So in my case, I use a DKIST compound model which has two coupled pixel axes and a time axes.
If I want just the time axis, the current code will work out the entire grid leading to a very dense and memory intense results.

So I added a really specific hack which works for my code and seems to not break the rest of the test suite.

TODO:

Unit tests:

  • EDGE CASES - THERE SHOULD BE SOME?

@nabobalis nabobalis added this to the 2.2.4 milestone Nov 8, 2024
@nabobalis nabobalis added the backport 2.2 on-merge: backport to 2.2 label Nov 8, 2024
@nabobalis nabobalis modified the milestones: 2.2.4, 2.2.5 Nov 8, 2024
@nabobalis nabobalis force-pushed the YOLO branch 6 times, most recently from b34f6c5 to 4e5af4b Compare November 11, 2024 18:59
ndcube/ndcube.py Outdated Show resolved Hide resolved
@nabobalis nabobalis changed the title My finest work Faster Shortcut for working out coordinates values for non-correlated WCS Nov 11, 2024
@nabobalis nabobalis changed the title Faster Shortcut for working out coordinates values for non-correlated WCS Faster shortcut for working out coordinates values for non-correlated WCS Nov 11, 2024
ndcube/ndcube.py Outdated Show resolved Hide resolved
ndcube/ndcube.py Outdated Show resolved Hide resolved
ndcube/ndcube.py Outdated Show resolved Hide resolved
ndcube/ndcube.py Outdated Show resolved Hide resolved
@nabobalis nabobalis force-pushed the YOLO branch 2 times, most recently from e1cb770 to 0ee79ff Compare November 22, 2024 20:01
@nabobalis nabobalis force-pushed the YOLO branch 3 times, most recently from 5ceb5ed to 1ddcc3f Compare November 26, 2024 20:02
Comment on lines +564 to +662
if not wcs:
return ()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was missing and is included in the other version of this method, so I added it.

Copy link
Member

@DanRyanIrish DanRyanIrish left a comment

Choose a reason for hiding this comment

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

More review to follow.

ndcube/ndcube.py Outdated Show resolved Hide resolved
ndcube/ndcube.py Outdated Show resolved Hide resolved
@DanRyanIrish
Copy link
Member

DanRyanIrish commented Nov 28, 2024

Hi @nabobalis. My reading of this PR is that it should be generalised to make it a bit simpler to implement and follow. If I understand correctly, you want to special-case the scenario where all wanted world axes (whether 1 or more) correspond to their own unique pixel axis. In this scenario, you don't have to work out all the coordinates for the entire (m, n) pixel grid, but instead just then 1-D dimensions corresponding to the desired world axes. This saves RAM and time. Am I right so far?

If this is the case, then I think it would help readability, to move your code to a new private method, e.g. _generate_independent_world_coords(). The pre-existing code could be moved to another private method, e.g. _generate_dependent_world_coords(), and then _generate_world_coords() could simply check whether the needed axes are independent, and then call the appropriate method above. Something like this:

def _generate_world_coords(self, pixel_corners, wcs, needed_axes=None, *, units):
    if needed_axes is None:
        needed_axes = list(range(wcs.n_world_dims))
    axes_are_independent = []
    pixel_axes = {}
    for world_axis in needed_axes:
        pix_ax = ndcube.utils.wcs.world_axis_to_pixel_axes(world_axis, wcs.axis_correlation_matrix)
        axes_are_independent.append(len(pix_ax) == 1)
        pixel_axes = pixel_axes.union(set(pix_ax))
    if all(axes_are_independent) and len(pixel_axes) == len(needed_axes):
        return self._generate_independent_world_coords(pixel_corners, wcs, pixel_axes, units)
    else:
        return self._generate_dependent_world_coords(pixel_corners, wcs, pixel_axes, units)

This brings us to your new code, i.e. _generate_independent_world_coords(). Since the check whether the needed axes are all independent will be done in the new _generate_world_coords(), we don't have to repeat that. We need to check whether we want pixel centers or corners, then define the pixel grids, where unwanted axes are set to np.array([0], dtype=int).reshape([1] * ndim) where ndim is the number of dimensions in the data array. Wanted dimensions are then set to np.arange(n).astype(int).reshape((1,...,n,...1) where n is the length of the relevant axis. So something like this:

def _generate_independent_world_coords(pixel_corners, wcs, pixel_axes, units):
    naxes = len(self.shape)
    pixel_indices = [np.array([0], dtype=int).reshape([1] * naxes)] * naxes
    for pixel_axis in pixel_axes:
        # Define limits of desired pixel range based on whether corners or centers are desired 
        len_axis = self.data.shape[::-1][pixel_axis]
        lims = (-0.5, len_axis + 1) if pixel_corners else (0, len_axis)
        pix_ind = np.arange(lims[0], lims[1])
        shape = [1] * naxes
        shape[pixel_axis] = len(pix_ind)
        pix_ind = pix_ind.reshape(shape)
        pixel_indices[pixel_axis] = pix_ind
    world_coords = wcs.pixel_to_world_values(*pixel_indices)
    if units:
        # Attach units to each coord here.
    return world_coords

I presume there's still some work to actually make this code work, but hopefully it's a good start. Let me know if you have questions.

(And these private methods should probably have their own docstrings so it's easier for developers to understand them in the future.)

@nabobalis
Copy link
Contributor Author

Hi @nabobalis. My reading of this PR is that it should be generalised to make it a bit simpler to implement and follow. If I understand correctly, you want to special-case the scenario where all wanted world axes (whether 1 or more) correspond to their own unique pixel axis. In this scenario, you don't have to work out all the coordinates for the entire (m, n) pixel grid, but instead just then 1-D dimensions corresponding to the desired world axes. This saves RAM and time. Am I right so far?

Yes.

I will try to attempt your suggestions.


coords = ndc[item].axis_world_coords()
assert u.allclose(coords, [1.02e-09, 1.04e-09, 1.06e-09, 1.08e-09] * u.m)
assert u.allclose(coords[0], [1.02e-09, 1.04e-09, 1.06e-09, 1.08e-09] * u.m)
Copy link
Member

Choose a reason for hiding this comment

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

Why have these asserts had to change?

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 wish I knew, I assume since I broke the code.

Copy link
Contributor Author

@nabobalis nabobalis Dec 11, 2024

Choose a reason for hiding this comment

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

But this is common in most of the other tests, you return a tuple of length N and you have to escape it always to get the coord. There are lots of tests where you do len(coords) and it is 1 but then you need to index the return to get the coord info.

@DanRyanIrish
Copy link
Member

@nabobalis The issue might be caused by an incorrect handling ExtraCoords. Can you confirm by restricting the condition to using your new implementation to when wcs is not an ExtraCoords object? If that fixes it, then we will have a strong indication of where the problem is.

@nabobalis
Copy link
Contributor Author

@nabobalis The issue might be caused by an incorrect handling ExtraCoords. Can you confirm by restricting the condition to using your new implementation to when wcs is not an ExtraCoords object? If that fixes it, then we will have a strong indication of where the problem is.

Currently the "test_axis_world_coords_wave_ec" test passes but fails on the Extracoords calls when we pass that as the WCS, so that implies to me that you are correct. What I wrote does not handle EC correctly.

@nabobalis
Copy link
Contributor Author

And based on that, I think I worked out what I fucked up. The tests are passing now for me locally.

@nabobalis nabobalis requested a review from Cadair December 13, 2024 21:26
@DanRyanIrish DanRyanIrish merged commit cd93cf8 into main Dec 13, 2024
19 checks passed
@DanRyanIrish DanRyanIrish deleted the YOLO branch December 13, 2024 22:52
Copy link

lumberbot-app bot commented Dec 13, 2024

Owee, I'm MrMeeseeks, Look at me.

There seem to be a conflict, please backport manually. Here are approximate instructions:

  1. Checkout backport branch and update it.
git checkout 2.2
git pull
  1. Cherry pick the first parent branch of the this PR on top of the older branch:
git cherry-pick -x -m1 cd93cf83bcde0426be777a23a7734877975a10d8
  1. You will likely have some merge/cherry-pick conflict here, fix them and commit:
git commit -am 'Backport PR #780: Faster shortcut for working out coordinates values for non-correlated WCS '
  1. Push to a named branch:
git push YOURFORK 2.2:auto-backport-of-pr-780-on-2.2
  1. Create a PR against branch 2.2, I would have named this PR:

"Backport PR #780 on branch 2.2 (Faster shortcut for working out coordinates values for non-correlated WCS )"

And apply the correct labels and milestones.

Congratulations — you did some good work! Hopefully your backport PR will be tested by the continuous integration and merged soon!

Remember to remove the Still Needs Manual Backport label once the PR gets merged.

If these instructions are inaccurate, feel free to suggest an improvement.

@lumberbot-app lumberbot-app bot added the Still Needs Manual Backport This PR needs manually backporting. label Dec 13, 2024
@nabobalis nabobalis removed the Still Needs Manual Backport This PR needs manually backporting. label Dec 14, 2024
@Cadair Cadair modified the milestones: 2.2.5, 2.3.0 Dec 17, 2024
@nabobalis nabobalis removed the backport 2.2 on-merge: backport to 2.2 label Dec 17, 2024
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.

_generate_world_coords is slow and uses a lot of memory
3 participants