spacetelescope / jdaviz

JWST astronomical data analysis tools in the Jupyter platform
https://jdaviz.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
142 stars 74 forks source link

API Proposal: Importing/loading spatial and spectral regions #2871

Closed pllim closed 5 months ago

pllim commented 6 months ago

Scope: Propose API to import/load spatial and spectral regions.

Out of scope:

🐱

Accepted plan

See https://github.com/spacetelescope/jdaviz/issues/2871#issuecomment-2128066533

Spatial Regions

Existing API

These work in Cubeviz and Imviz.

viz_helper.load_regions_from_file()

https://github.com/spacetelescope/jdaviz/blob/069834a4ac8e47565bf359011178af812e72a03e/jdaviz/core/helpers.py#L643-L644

viz_helper.load_regions()

https://github.com/spacetelescope/jdaviz/blob/069834a4ac8e47565bf359011178af812e72a03e/jdaviz/core/helpers.py#L675-L676

To export

viz.app.get_subsets()

https://github.com/spacetelescope/jdaviz/blob/069834a4ac8e47565bf359011178af812e72a03e/jdaviz/app.py#L949-L952

viz.get_interactive_regions()

https://github.com/spacetelescope/jdaviz/blob/f985a7fe0bd3119db88013ce7f4b4aa7a4283fff/jdaviz/core/helpers.py#L861

Proposed API

No change; same as Existing API. One could argue to absorb load_regions_from_file into load_regions but both have existed for a while now and no one complained about it, so no motivation for extra churn.

Spectral Regions

Existing API

This theoretically works on any viz with a spectrum viewer.

from glue.core.roi import XRangeROI
sv = specviz_helper.app.get_viewer('spectrum-viewer')
sv.apply_roi(XRangeROI(6500, 7400))
viz_helper.app.state.drawer = True

To export

viz.app.get_subsets()

https://github.com/spacetelescope/jdaviz/blob/069834a4ac8e47565bf359011178af812e72a03e/jdaviz/app.py#L949-L952

Proposed API 1

Same as spatial regions:

Pros: Less API for user to remember.

Cons: Possibility of over-complicating the logic in those methods due to different usages and file formats between spatial and spectral regions. Might also have to rename get_* methods.

Proposed API 2

Similar to spatial regions but not identical:

Pros: Cleanly separating out logic for spatial and spectral. User's intention is clear.

Cons: Possibility of code duplication where logic is shared between spatial and spectral regions. User has to remember to use different APIs for spatial vs spectral. Might have to rename spatial region methods by adding spatial to them.

Proposed API 3

The concept was originally suggested by @kecnry: The idea is we use plugin API directly. Example usage would be something like this (such functionality currently does not exist):

plg = viz.plugins["Subset Tools"]

plg.dataset = "some_image[SCI,1]"  # Might need this for Imviz due to linking galore

plg.import_region("subset1a.reg")
plg.combine_mode = "or"
plg.import_region(subset_1b_region_object)

plg.subset = "Create New"
plg.import_region("subset2.reg")

# Presumably combine_mode would be ignored
# and each region becomes a new Subset?
plg.import_region("lots_of_regions.reg")

Pros: "import" might be okay instead of "load" for plugin method since we also have "export" plugin. The API is controlled by plugin, so no need to choose between attaching it to helper or viewer.

Not-a-con-but-also-not-a-pro: We have to implement the functionality in the plugin first to support the API and possible new GUI elements to go with it.

Cons: Unclear what the behavior should be when a region file has multiple regions to be loaded via this plugin API, since the regions package does not understand glue Subset operations. For Imviz, behavior can be confusing when user changes Orientation between importing regions via this API. We have to deprecate load_* and get_* methods in helpers.

javerbukh commented 6 months ago

Of all of these options, I prefer API 1. Although, I think with version 4.0 we have an opportunity to make setting and retrieving subsets more straightforward. As a fresh user, I would assume the API for creating and retrieving subsets would look like create_subset and get_subset. Instead, we have load_regions, load_regions_from_files, get_subsets, and get_interactive_regions.

I would propose moving that functionality into one method for creating subsets, one for creating them from a file, and one for retrieving them for version 4.0. It looks like get_subsets already has a large amount of the functionality for retrieving subset data covered, and we can use the return values from that method as a test case for loading subsets in a create_subsets method.

So the two different methods for creating subsets would look like:

def create_subsets(self, subset_type='Spectral', subset_name=None, bounding_box)

and

def create_subsets_from_file(region_file, region_format='ds9', max_num_regions=20, **kwargs)

I know I took Proposal 1 and turned it into 10 sentences instead of 1 but I think renaming the method is an important part of clarifying the API for a user. Happy to hear your thoughts on this!

pllim commented 6 months ago

@javerbukh , would an API called create_subsets being unable to generate complicated composite subsets programmatically be confusing?

javerbukh commented 6 months ago

I know we do not support that currently but I do think it is possible to do programmatically. Should that be a blocker for this effort?

pllim commented 6 months ago

Should that be a blocker for this effort?

πŸ€·β€β™€οΈ

I am not sure if it is possible until we have a way to serialize the operators into something regions can understand? That part is currently not existing.

Maybe @camipacifici can chime in here.

Also, regular users not familiar with glue internals might find "regions" more intuitive than "subsets".

camipacifici commented 6 months ago

Thoughts on proposed API:

I like the idea of a method called create_subsets (or create_regions) that is more similar to what the user sees in the UI, but then the spatial case would need to change as well. It is important to keep them consistent. Either load or create. I agree with Pey Lian that regions might be more intuitive than subset, but please check with Jenn. She might have some insights after the usability testing.

It would be ideal to have the possibility of combining regions to create composite regions. It doesn't have to be MVP, but definitely something to plan. Maybe it could be a separate method. The user could load individual regions and then combine them according to the usual arithmetic we have in the UI. Just an idea.

pllim commented 6 months ago

Just to make sure I understand, is this what you all are counter-proposing?

pllim commented 6 months ago

And just to be clear, I am not touching any export functions/methods. So design of viz.get_* and viz.app.get_* is irrelevant here. (I just added the "export" stuff in original post for completeness.)

javerbukh commented 6 months ago

It makes sense to have a separate method for loading with a file since the code will pretty different. My main point is that the naming convention should be consistent for all things that touch subsets/regions. I think using regions instead of subsets in the method names is fine, as long as it is consistent.

pllim commented 6 months ago

In that case, are we converging on Proposed API 1 as-is, @javerbukh and @camipacifici ?

camipacifici commented 6 months ago

If the get_ remain untouched (i.e., two different calls for spatial and spectral), then probably we should have different methods for loading spatial and spectral region as well. Unless, the get_ calls will also change to a global get_regions in the future. This is a dev decision though. Maybe poll the others and let me know what you all think is best here.

In any case, I think we are settling on the load approach with the possibility of combining loaded regions as a different method. It doesn't need to be implemented right away, but it would be useful to start thinking about the API for this too.

pllim commented 6 months ago

The ticket only asked to ponder about loading, not getting. If you want to refactor get_* too then we have to expand the scope of the ticket or create a follow-up ticket. πŸ€·β€β™€οΈ

camipacifici commented 6 months ago

I didn't ask to ponder about how to refactor get_. I asked to poll the other devs to see if they are in favor or against refactoring get_ in the future. If they are in favor, load_ can be a single method. If they are against, load_ should follow get_. Or whatever else you suggest we should do as long as they are (or will be) consistent.

pllim commented 5 months ago

OK thanks for the clarification. Two πŸ‘ so far from devs to refactor get_ in the future. But I will wait a bit more.

pllim commented 5 months ago

Re: https://github.com/spacetelescope/jdaviz/issues/2871#issuecomment-2112938916

@camipacifici , still just the 2 πŸ‘ and no πŸ‘Ž . So does this mean we go with Proposed API 1?

pllim commented 5 months ago

Update: I added Proposed API 3 based on discussions this morning.

camipacifici commented 5 months ago

Thank you for this other proposal. Will there be similar calls for export_region when the user creates them in the UI? Will those replace get_*?

The UI functionality of the subset plugin should be improved regardless, so I do not see this as a problem among your cons. For the other cons, I rely on you all.

Both API1 and API3 look ok to me from a user stand point. Please advise on what would be best technically.

pllim commented 5 months ago

Thank you for this other proposal. Will there be similar calls for export_region when the user creates them in the UI? Will those replace get_*?

Good question and probably @kecnry can chime in here. A little weird for plugin API to export to object too when it can already export to files (standalone app users get no benefit from this) but it does put all the related functionality in one place.

I don't see the get_* stuff going anywhere. They are not public anyway. I think we just won't rename them nor make them public.

kecnry commented 5 months ago

If we go with API3, yes, I think it would make sense to have an export_region method in the subset plugin API.

We could also consider accessing these through the export plugin so that you can access any object directly from the export API instead of or in addition to writing to the file... we already have the logic there to determine the format).

pllim commented 5 months ago

I was thinking export_region to be in Export plugin but now I am not sure. Should we group all the region stuff into Subset Tools plugin, or group them by import/export? πŸ€”

pllim commented 5 months ago

When it comes the time people can add/remove plugins at will, does that mean they can no longer do these things unless those plugins are enabled (as opposed to attaching the API to helper/viewer)?

kecnry commented 5 months ago

I was thinking export_region to be in Export plugin but now I am not sure. Should we group all the region stuff into Subset Tools plugin, or group them by import/export?

I would not put any regions/subset-specific API into the export plugin, but we can use the general export method to also expose the underlying objects instead of only supporting writing to files.

When it comes the time people can add/remove plugins at will, does that mean they can no longer do these things unless those plugins are enabled (as opposed to attaching the API to helper/viewer)?

Yes, but I don't see any issue with that myself πŸ€·β€β™‚οΈ

pllim commented 5 months ago

expose the underlying objects instead of only supporting writing to files.

Sure, but as far as the user is concerned, then exporting region API would be in the Export plugin. Unless you are saying we do that in addition to also putting export regions API in Subset Tools plugin?

kecnry commented 5 months ago

I think both is probably fine (subset plugin for now for the basic functionality, and export plugin down the road for translating to different formats, bulk retrieval, etc).

camipacifici commented 5 months ago

Looks like API3 is good. I re-send my question about the get_* methods because they are public. I am thinking get_interactive_regions and get_spectral_regions. Will they stay or will they go?

pllim commented 5 months ago

get_interactive_regions

Since I was the one that wrote this one, I am okay to deprecate it in favor of the new API. "interactive" is kinda meaningless now that we don't advertise loading static masks.

get_spectral_regions

I cannot speak for this one. @kecnry last touched it in https://github.com/spacetelescope/jdaviz/pull/2127 . I am thinking we can also deprecate it unless other people object.

pllim commented 5 months ago

But this also means with Option 3, we also have to deprecate viz.load_regions and viz.load_regions_from_file, is that correct?

camipacifici commented 5 months ago

Right, those too.

pllim commented 5 months ago

OK I updated pros and cons of Option 3 to include deprecation of all those APIs. Seems pretty disruptive but if people really like the plugin API idea, we have little choice.

rosteen commented 5 months ago

Seems pretty disruptive

This does worry me a bit. I still like option 1, but can see the arguments for 3 as well. So I guess don't count me as a strong vote either way 😬. (so helpful)

pllim commented 5 months ago

I think at tag-up today, we chose Proposed API 3, so here is the plan. We can choose to implement new things first and deprecated later for smaller pointed tickets. We can also choose to split import and export work into different tickets. Though doing them all at once might be cleaner. Proposed new API signatures are negotiable.

New functionality Deprecation No change
Import
  • subset_plugin.import_region(region_file_or_regions)
  • subset_plugin.combine_mode = "operation_name"
  • subset_plugin.dataset = "data_label" # Imviz only
  • viz.load_regions(regions)
  • viz.load_regions_from_file(region_file)
  • imviz.load_data(region_file)
  • viewer.apply_roi()
Export
  • subset_plugin.export_region(region_type, list_of_subset_labels, region_file)
  • viz.get_interactive_regions()
  • viz.get_spectral_region()
  • app.get_subsets()
  • export_plugin.export()
  • export_plugin._obj.save_subset_as_region()
  • export_plugin._obj.save_subset_as_table()

The plan:

  1. User API and possible refactor of subset plugin 🐱
  2. Subset Plugin API: Import single region from file, combination mode 🐱
  3. Subset Plugin API: Import multiple regions from a single file, deprecate others 🐱
  4. Subset Plugin API: Retrieve Subsets as regions, deprecate others 🐱
  5. Subset Plugin UI: Import region(s) from file 🐱
  6. ???
  7. Profit!!!!