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

Index Mapping Util for Rectangular grids #1258

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from

Conversation

nkerman
Copy link
Contributor

@nkerman nkerman commented Jan 10, 2025

Change Summary

Overview

Create ena_maps directory, and build function to match indices from a rectangular gridding of the sky in SpiceFrame 1 to a separate rectangular gridding of the sky in SpiceFrame 2.

Closes #1257

New Files

  • imap_processing/ena_maps/map_utils.py
    • Utility functions for making (L2) ENA maps
    • Currently only contains rectangular index matching function.

Updated Files

  • updated imap_processing/ultra/utils/spatial_utils.py
    • Modified build_az_el_grid function to also return bin edges as requested here.

Testing

  • Updated tests for build_az_el_grid to test bin edges
  • Tests for new index matching function, no longer dependent on Spice
    • If anyone has a good idea for how to further test this functionality is correctly matches indices across different frames, I'd be interested! @subagonsouth
    • E.g., if there's a simple-ish way to define two SpiceFrames which are at some angle to one another, I expect we could conceptually figure out which bins each point should land in, and then the index in the projected rectangular grid. At present, I can't quite work out how to do this Spice-wise.

@nkerman nkerman self-assigned this Jan 10, 2025
@nkerman nkerman added Ins: Ultra Related to the IMAP-Ultra instrument Level: L2 Level 2 processing Mapper Tools Work related to common mapper tools labels Jan 10, 2025
@nkerman nkerman changed the title Ena maps utils Index Mapping Util for Rectangular grids Jan 10, 2025
Copy link
Contributor

@subagonsouth subagonsouth left a comment

Choose a reason for hiding this comment

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

It is probably my fault that this is not quite what I had in mind for this ticket. I should probably shift gears for a bit an write up several well documented tickets that define some atomic functions that I think will be useful in building a foundation for all of the shared mapping/L2 work.


# Ignore linting rule to allow for 6 unrelated args
# Also need to manually specify allowed str literals for order parameter
def match_rect_indices_frame_to_frame( # noqa: PLR0913
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that this function is doing too much...

  • building both az_el grids
  • flattening
  • doing transforms
  • generating indices for mapping both directions
  • reshaping back to 2D

To make it more general, flexible, and reusable, I would like to see the following function extracted.

def map_2d_coords_to_pixels(coords: NDArray, pixel_edges: Tuple(array_like), order: typing.Literal["C"] | typing.Literal["F"] = "F") -> NDArray:
    """
    Compute the flat indices that map the input coordinates to the pixels defined by pixel_edges.

    Parameters
    -----------
    coords : ndarray with shape (N, 2) where N is the number of input coordinate pairs.
    pixel_edges: Tuple[ndarray] where pixel_edges[0] defines the 0th axis pixel edges...
    order: order to use when converting from tuple of index arrays into an array of flat indices

    Returns
    --------
    indices : ndarray
        Flat array of indices with size = N that map the input coordinates into an array of pixels defined by the input pixel_edges that has been flattened with the same `order` as input.
    """

The whole push-pull idea is likely going to be specific to Ultra, so avoiding baking that in at this stage would be good. The function that builds the grids will likely want to be moved into this common map_utils module, though that can be in another PR.

Copy link
Contributor Author

@nkerman nkerman Jan 13, 2025

Choose a reason for hiding this comment

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

Okay, to confirm, you do see there being some overarching function that does basically this, with its components broken out a bit into sub-functions?

For Ultra, I think we want to be able to hand a single function input+output frames, and input+output spacings, and have it match up the indices, which does involve all these steps, with the exception of reshaping back to 2D, which doesn't currently happen here. I think you're referencing the ravel_multi_index, which is needed to convert from two small 1D indices (projected az bin, projected el bin) to a single 1D index of the 'unwrapped' sky.

The whole push-pull idea is likely going to be specific to Ultra, so avoiding baking that in at this stage would be good.

Agreed! Is there anywhere in this code currently that talks about push/pull, or did you mean this as more of a precaution?

The function that builds the grids will likely want to be moved into this common map_utils module, though that can be in another PR.

Got it - I made this ticket #1260 . Is it "cleaner" to do so in another PR? It seems messy to me to have all these PRs interacting with the same code, so I'd lean towards doing it here, but maybe that makes reviewing more difficult?

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, to confirm, you do see there being some overarching function that does basically this, with its components broken out a bit into sub-functions? For Ultra, I think we want to be able to hand a single function input+output frames, and input+output spacings, and have it match up the indices, which does involve all these steps (except reshaping back to 2D, which doesn't happen here. I think you're referencing the ravel_multi_index, which is needed to convert from two small 1D indices (projected az bin, projected el bin) to a single 1D index of the 'unwrapped' sky.

I'm not sure about an overarching function. For example, this function as written will regenerate the Map coordinates (azimuth, elevation, and pixel edges) for each PSET that you are binning. It would be good to avoid that by generating that once at the start of L2 processing and reuse throughout. Likewise, for the coordinate grid of each PSET, does Ultra store those in the PSET product? or do they really need to be regenerated in this function?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed! Is there anywhere in this code currently that talks about push/pull, or did you mean this as more of a precaution?
You're right. There is no push pull in this function. I wasn't careful enough reading the descriptions of the outputs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it! I think I agree in principle with the goal of not re-generating the map coordinates for each PSET that gets added, but that relies on there already being something like a map object set up, and I wonder if we're edging closer to just implementing that, when we had been talking about a more focused set of self-contained utils for starting Ultra L2.

Likewise, for the coordinate grid of each PSET, does Ultra store those in the PSET product? or do they really need to be regenerated in this function?

The L1c files will have the separate coordinates (i.e the 1D grids of az, el), but the np.meshgrid would need to be constructed and unwrapped. I'm not sure I see the harm in regenerating these relatively lightweight grids.

Copy link
Contributor

@subagonsouth subagonsouth Jan 14, 2025

Choose a reason for hiding this comment

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

but that relies on there already being something like a map object set up, and I wonder if we're edging closer to just implementing that, when we had been talking about a more focused set of self-contained utils for starting Ultra L2.

Perhaps this is true. I think that things are going in the right direction here and common, reusable code is being identified. The reason I identify this re-generation of coordinates as an issue is not due to inefficiencies/performance reasons, it is because it becomes more difficult to disentangle down the road and is inflexible. Specifically, I have two concerns:

  • What happens if Ultra decides they need to change the PSET grid spacing at some point in time or need different spacings for different energy bands. L2 needs to get the grid spacing from the PSET product so that different resolution products can be combined. Looking at the code again I realize this could be handled with the current implementation.
  • Similarly, Hi and Lo have a 1D spatial array in the PSET products rather than 2D and don't, AFAIK, have any need for pull binning. With that in mind, there is talk of needing to combine Hi and Ultra data, so assumptions about the input PSET dimensionality need to be removed as much as possible.

@nkerman
Copy link
Contributor Author

nkerman commented Jan 13, 2025

It is probably my fault that this is not quite what I had in mind for this ticket. I should probably shift gears for a bit an write up several well documented tickets that define some atomic functions that I think will be useful in building a foundation for all of the shared mapping/L2 work.

Thank you a ton for your continued quick reviews on these PRs, Tim - and I'm sorry if I'm not capturing what you're looking for here, and making you over-explain! I think I'm missing a lot of expertise in optimizing and writing re-usable code, and so far I've just been focused on building something that does what I know it needs to do.

If I were to extract map_2d_coords_to_pixels as you suggest, is this otherwise a PR that can move forward, or do we need to discuss more big picture before I should continue implementation?

Copy link
Contributor

@subagonsouth subagonsouth left a comment

Choose a reason for hiding this comment

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

After your questions, I have a couple of additional comments.

Comment on lines +166 to +167
input_az_in_proj_az_indices,
input_el_in_proj_el_indices,
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these indices needed? I'm trying to understand if there is a way to simplify this function signature. Complex function signatures are generally a sign that the function is overly complex, or doing too many things. Also, staying away from 2D indices will be helpful for if/when HEALPix is added.

- Ordered 1D array of indices in the projection frame
corresponding to each index in the input grid.
Not guaranteed to cover the entire projection grid.
Size is the same as the input indices
Copy link
Contributor

Choose a reason for hiding this comment

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

The use of "input indices" throughout this docstring is confusing. It had me re-reading the input parameter descriptions looking for indices that were input to the function.

@subagonsouth
Copy link
Contributor

If I were to extract map_2d_coords_to_pixels as you suggest, is this otherwise a PR that can move forward, or do we need to discuss more big picture before I should continue implementation?

Yes, I think this could be moved forward with that function extracted.

@greglucas
Copy link
Collaborator

Do we want to have "grid spacing" as the input parameter, or would it make more sense to pass in the edges themselves? i.e. if we are only dealing with one swath of the sky could we reduce this down to a smaller set of coordinates. This is similar to the "we have lots of zeros in our bins" discussion and whether we can avoid some of those lookups/transforms.

In Cartopy, we transform images between non-rectangular projections and use a kdtree to do the lookups which might help here as well. Here is some code that looks similar if you want any inspiration.
https://github.com/SciTools/cartopy/blob/main/lib/cartopy/img_transform.py

@nkerman nkerman marked this pull request as draft January 22, 2025 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ins: Ultra Related to the IMAP-Ultra instrument Level: L2 Level 2 processing Mapper Tools Work related to common mapper tools
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Index Mapping Util for Rectangular grids
3 participants