Skip to content

Comments

Populate map principal data CATDESC from descriptor#2712

Open
jtniehof wants to merge 6 commits intoIMAP-Science-Operations-Center:devfrom
jtniehof:catdesc
Open

Populate map principal data CATDESC from descriptor#2712
jtniehof wants to merge 6 commits intoIMAP-Science-Operations-Center:devfrom
jtniehof:catdesc

Conversation

@jtniehof
Copy link
Collaborator

Change Summary

Overview

The CATDESC (and thus plot title in CAVA) for all ENA maps is "Mono-energetic ENA intensity". This doesn't provide much detail on what intensity is being shown, and the IMWG has requested that this be updated with a human-readable description of the map descriptor.

File changes

  • ena_maps/utils/naming.py: add function to parse a CATDESC from a MapDescriptor
  • tests/ena_maps/test_naming.py: tests for above
  • ena_maps/ena_maps.py: patch the CATDESC in build_cdf_dataset
  • tests/ena_maps/test_ena_maps.py: update tests for above

Testing

New test was added for MapDescriptor.to_catdesc; the test for build_cdf_dataset was updated to check the CATDESC was properly populated. build_cdf_dataset now requires a fairly well-constructed descriptor, so some tests were updated to provide that.

Other notes

This PR is in draft while the IMWG reviews the actual contents of the CATDESC; there shouldn't be structural changes after that.

This does create a circular import between ena_maps and ena_maps.utils.naming. It doesn't seem to cause problems but I still don't like it.

There are several cases where a malformed map descriptor will cause a regex to fail and horrible exceptions to be thrown. I figure the implicit contract that deep in the code is that the descriptor is well-formed, and I usually avoid catching an exception just to throw another exception. Open to alternate approaches.

 * Put species before the quantity
 * Spell out Combined (and space out from instrument name)
 * Special-case ISN [species] Rate
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.

Saw this draft PR and thought I would give some feedback.

Comment on lines 186 to 194
instrument_names = {
"l": "Lo",
"t": "Lo",
"ilo": "Lo",
"h": "Hi",
"u": "Ultra",
"idx": "IDEX",
"glx": "GLOWS",
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There is some overlap here with the MappableInstrumentShortName enum class. I wonder of we could leverage that somehow. The two issues that would need to be sorted are:

  • trimming the full Lo names
  • convert Lo, Hi, Ultra to CamelCase but keep IDEX, GLOWS as is.

https://github.com/jtniehof/imap_processing/blob/0f6de5e32ad425b7c4fab46ccc86918bd578cc52/imap_processing/ena_maps/utils/naming.py#L18

One idea would be to modify the class such that the value attribute is a named tuple so that you could do something like:

instrument_long = self.instrument.long_name
instrument_short = self.instrument.short_name

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I looked a bit at that class and was hesistant to do much changing because the CATDESC names are fairly specific to this task, and aren't new generic names. Your comment here and below encouraged me to take another stab at it and I think the results, while still hacky, are at least shorter and easier to understand.

Comment on lines 195 to 201
instrument = next(
(
name
for desc, name in instrument_names.items()
if self.instrument_descriptor.startswith(desc)
)
)
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 this could be reversed to use the mapping and instrument attribute:

Suggested change
instrument = next(
(
name
for desc, name in instrument_names.items()
if self.instrument_descriptor.startswith(desc)
)
)
instrument = instrument_names[self.instrument.value]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wrapping this up in using self.instrument instead of the descriptor made this whole mess go away.

Comment on lines 203 to 205
species = self.species.title()
if species == "Uv":
species = "UV"
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nit... a ternary operator should be used here.

Suggested change
species = self.species.title()
if species == "Uv":
species = "UV"
species = "UV" if self.species == "uv" else self.species.title()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am deeply ashamed that I missed a chance to use my favorite operator.

Comment on lines 1424 to 1438
# And CATDESC for principal data
possible_principal_data = (
"dust_rate",
"ena_intensity",
"ena_spectral_index",
"glows_rate",
"isn_rate",
"isn_rate_bg_subtracted",
)
for principal_data in possible_principal_data:
if principal_data in cdf_ds:
catdesc = naming.MapDescriptor.from_string(descriptor).to_catdesc()
cdf_ds[principal_data].attrs["CATDESC"] = catdesc
# Only one principal variable
break
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like it would be cleaner to return a tuple form to_catdesc() consisting of the variable name and the catdesc or add a principal_data() property method so that you could just do:

principal_var, catdesc = naming.MapDescriptor.from_string(descriptor).to_catdesc()
cdf_ds[principal_var].attrs["CATDESC"] = catdesc

or

map_desc = naming.MapDescriptor.from_string(descriptor)
cdf_ds[map_desc.principal_data].attrs["CATDESC"] = map_desc.to_catdesc()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, a property on MapDescriptor is a lot better...I was already nervous about the fact I was copy-pasting this into the L3 code, and the Ultra situation made it even worse. See if the new approach makes sense.

{"DELTA_PLUS_VAR": "epoch_delta", "BIN_LOCATION": 0}
)

# And CATDESC for principal data
Copy link
Contributor

Choose a reason for hiding this comment

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

Please note that Ultra does not use this build_cdf_dataset() method currently. I think there is an intent to move this method to the generic SkyMap class and transition Ultra to using that, but you will need to dig through the Ultra code to see where you can fit this in for updating the Ultra CATDESC.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for that catch! I was feeling a little nervous since it wasn't dealing with HEALPix.

I wound up making a complete copy of the main ultra_l2 test because it works quite differently between using the output_map_structure vs. the descriptor--the descriptor can't specify ECLIPJ2000 and it also casts the pixel size to a float somewhere along the way. That said, now I see there was another test using the descriptor so going to go move into that test now....done. I can rebase out that misstep if preferred.

Comment on lines 307 to 322

def test_to_catdesc(self):
cases = [
(
"h45-spx-h-hf-sp-ram-hae-4deg-3mo",
"IMAP Hi45 H Spectral, HAE Helio Frame, Surv Corr, Ram, 4 deg, 3 Mon",
),
(
"h45-spx0305-h-hf-sp-ram-hae-4deg-3mo",
"IMAP Hi45 H Spectral, HAE Helio Frame, Surv Corr, Ram, 4 deg, 3 Mon",
),
(
"hic-ena-h-hf-sp-ram-hae-4deg-3mo",
"IMAP Hi Combined H Inten, HAE Helio Frame, Surv Corr, Ram,"
" 4 deg, 3 Mon",
),
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be better suited as a parameterized test. For example:

@pytest.mark.parametrize(
    "map_desc, expected_catdesc",
    [
        (
            "h45-spx-h-hf-sp-ram-hae-4deg-3mo",
            "IMAP Hi45 H Spectral, HAE Helio Frame, Surv Corr, Ram, 4 deg, 3 Mon",
        ),
        (
            "h45-spx0305-h-hf-sp-ram-hae-4deg-3mo",
            "IMAP Hi45 H Spectral, HAE Helio Frame, Surv Corr, Ram, 4 deg, 3 Mon",
        ),
        (
            "hic-ena-h-hf-sp-ram-hae-4deg-3mo",
            "IMAP Hi Combined H Inten, HAE Helio Frame, Surv Corr, Ram,"
            " 4 deg, 3 Mon",
        ),
    ],
)
def test_to_catdesc(self, map_desc, expected_catdesc):
    md = MapDescriptor.from_string(descriptor_str)
    actual_catdesc = md.to_catdesc()
    assert actual_catdesc == expected_catdesc

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Spot the guy who uses base unittest :) Thanks.

@jtniehof
Copy link
Collaborator Author

Saw this draft PR and thought I would give some feedback.

Thanks for many good catches, @subagonsouth. I think I've hit them all, but I left the conversations unresolved so you can check.

I have to update the L3 code and then I'm going to make one last check of how these display in CAVA before marking ready.

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.

Great! Your changes cleaned this up nicely.

@jtniehof
Copy link
Collaborator Author

Thanks! Jamey agrees that any concerns with display in CAVA are issues for CAVA, and we should proceed with this. So marking ready.

@jtniehof jtniehof marked this pull request as ready for review February 20, 2026 14:43
@laspsandoval laspsandoval added the CDF Related to CDF files label Feb 20, 2026
@jtniehof jtniehof moved this to PR Open in IMAP Feb 20, 2026
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.

Thanks for implementing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CDF Related to CDF files

Projects

Status: PR Open

Development

Successfully merging this pull request may close these issues.

3 participants