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

Foundational SPICE tools #279

Merged
merged 20 commits into from
Dec 13, 2023

Conversation

laspsandoval
Copy link
Contributor

@laspsandoval laspsandoval commented Nov 15, 2023

Change Summary

Overview

-Added some basic tools.

New Dependencies

None

New Files

  • spice_utils.py
    • loads and clears kernels
    • lists loaded kernel filenames
    • lists all constants in the Spice constant pool
    • using historical attitude kernels, figures out processing dates

Testing

  • test_spice_utils.py
    • tests spice_utils.py
  • I put almost all of the ultra spice test data in the /tools/tests/test_data/spice directory. Github would not let me put de440.bsp in because the file was too large. We should discuss how we want to handle that.

Note: there are other test files in: smb://lasp-store.lasp.colorado.edu/projects/Phase_Development/IMAP-SOC/Technical/POC/MOC Interface/PRELIM_SPICE_TEST1.zip that I have not yet added to the repo.

closes #66
closes IMAP-Science-Operations-Center/sds-data-manager#82

@laspsandoval laspsandoval added this to the SPICE Tools Complete milestone Nov 15, 2023
@laspsandoval laspsandoval self-assigned this Nov 15, 2023
@laspsandoval
Copy link
Contributor Author

pre-commit.ci autofix


def furnsh(self):
"""Load a kernel file into the SPICE system."""
self.clear()
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this do? does it clear spice kernel pool?

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

Copy link
Contributor

Choose a reason for hiding this comment

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

If you could add a note about why it's important to clear spice kernel pool, it will help when someone new reads it.

# 0 : starting from first match
# 1000 : num variable names retrieved in call
# 81 : max len of variable name
kervars = spice.gnpool("*", 0, 1000, 81)
Copy link
Contributor

Choose a reason for hiding this comment

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

can you change kervars to kernel_vars?

Comment on lines 91 to 96
# retrieve names of kernel variables
# * means matches all variables
# 0 : starting from first match
# 1000 : num variable names retrieved in call
# 81 : max len of variable name
kervars = spice.gnpool("*", 0, 1000, 81)
Copy link
Contributor

Choose a reason for hiding this comment

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

We could include the link in the comment too if someone like to dive deeper.

Suggested change
# retrieve names of kernel variables
# * means matches all variables
# 0 : starting from first match
# 1000 : num variable names retrieved in call
# 81 : max len of variable name
kervars = spice.gnpool("*", 0, 1000, 81)
# retrieve names of kernel variables using below inputs per https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/gdpool_c.html
# name = '*', means name of the variable whose value is to be returned.
# start = 0, Which component to start retrieving for `name'.
# room = 1000, The largest number of values to return.
# n = 81, Number of values returned for `name'.

return result


def ls_attitude_coverage(custom_pattern=None) -> tuple:
Copy link
Contributor

Choose a reason for hiding this comment

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

just to be sure ls here means list, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right

Copy link
Contributor

Choose a reason for hiding this comment

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

looks like Greg made suggestion above to spell it out. can we do same here? ty!


matching_pattern = re.match(pattern, basename)
if matching_pattern:
parts = matching_pattern.groups()
Copy link
Contributor

Choose a reason for hiding this comment

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

a comment here would be helpful.


# Historical attitude kernels only
if custom_pattern is None:
pattern = (
Copy link
Contributor

Choose a reason for hiding this comment

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

a comment would be helpful

n_files_before_clear = spice.ktotal("ALL")
assert n_files_before_clear > 0

# Clear loaded kernels and verify
Copy link
Contributor

Choose a reason for hiding this comment

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

These comments are helpful. ty!

assert n_files_loaded_after_clear == 0


def test_furnsh_type(caplog):
Copy link
Contributor

Choose a reason for hiding this comment

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

what's caplog? just curious

Copy link
Collaborator

@greglucas greglucas left a comment

Choose a reason for hiding this comment

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

My main comment would be that this seems like it'd be nice to have your kernel manager be a contextmanager (you already have manager in the name even ;) ). Let me know if you have thoughts for/against that or any questions and I'd be happy to chat.

The other question I have since I haven't tried this PR out locally is with the spice files you uploaded and whether they would even work for me because they have some hard-coded paths associated with them. Will this cause issues for people in the future?

Comment on lines 7 to 11
PATH_SYMBOLS += ( 'GENERIC' )
PATH_VALUES += ( '/project/mini-rf/poc/LRO/data/storage/ancillary/spice/frames' )

PATH_SYMBOLS += ( 'IMAP' )
PATH_VALUES += ( '/project/sis/users/duttont1/software/imap/lib' )
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't seem like they'd work? Should we change them to be relative to the project somehow, like: /mnt/spice/... Or can they be removed altogether?

@@ -0,0 +1,17 @@
KPL/MK

Meta kernel that loads the arecibo configuration kernels into the sarbeta program.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Meta kernel that loads the arecibo configuration kernels into the sarbeta program.
Meta kernel that loads the IMAP configuration kernels.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was wondering if arecibo was some sort of spice-specific buzz word or the actual arecibo telescope 😅

\begindata

PATH_SYMBOLS += ( 'IMAP' )
PATH_VALUES += ( '/Users/lasa6858/Desktop/ultra/ultra_prototype_v1/kernels' )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Won't work for other users, make more general?

logger = logging.getLogger(__name__)


class SpiceKernelManager:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems like it'd be nice to be a context manager, so the loading/closing of the kernels was all taken care of within the scope of when you're using it. Then a user wouldn't need to call furnsh() or clear() directly. Thoughts on that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like this might even already be implemented in spiceypy? So, do we need our own version here or can you expose that somehow instead.
https://github.com/AndrewAnnex/SpiceyPy/blob/22f7aaa0944d01994540f0acd5136a54ed239efe/src/spiceypy/spiceypy.py#L289
https://spiceypy.readthedocs.io/en/main/contextmanager.html

Comment on lines 22 to 23
extensions : list of str, optional
A list of file extensions to be considered as valid SPICE kernel files.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Additional extensions? If I add one entry .greg to the extension list, I wouldn't necessarily want to have to add all the other default extensions too.

Are there other extensions we need to consider even, or can we just only allow default ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could only allow the default ones. I think it's useful during development to be able to select a single extension that I want to furnish and then view the constants for only that kernel type. But this could totally be used without passing the extensions and just relying on the default ones.

result = {}
for kervar in sorted(kervars):
# retrieve data about a kernel variable
n, kernel_type = spice.dtpool(kervar) # pylint: disable=W0632
Copy link
Collaborator

Choose a reason for hiding this comment

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

I had to look up W0632, can you balance the tuple instead of ignoring the linter?



def ls_attitude_coverage(custom_pattern=None) -> tuple:
"""Process attitude kernels to extract and convert dates to UTC format.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not following what this function is being used for. Do we need a custom pattern, or can we prescribe the pattern directly? I think you might be trying to be too nice and flexible, but that can come later if we need to add it.

Maybe more basic is why we are getting this data out of the filename instead of the kernel data itself? i.e. can we query the object within spicepy somehow to get this information instead of parsing filenames.

Copy link
Contributor Author

@laspsandoval laspsandoval Dec 12, 2023

Choose a reason for hiding this comment

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

This might change. It was my first attempt at figuring out how we would select the most recent historical attitude kernel. I will put a TODO here in case we find that symlink can do that. I don't want to delete it quite yet though.


expected = [str(directory / "imap_spk_demo.bsp")]

assert sorted(result) == sorted(expected)
Copy link
Collaborator

Choose a reason for hiding this comment

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

They are length-1 lists, so don't need to sort.


# Test with valid extensions
result = ls_attitude_coverage()
assert result is not None
Copy link
Collaborator

Choose a reason for hiding this comment

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

You are doing an isinstance right below this, so that will also ensure result is not None.

Suggested change
assert result is not None


# Test with an empty directory
kernel_object.clear()
empty_result = ls_attitude_coverage()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this raise or be an empty tuple instead?

if extensions is None:
result.append(file)
# Check if the file ends with any of the specified extensions
elif any(file.endswith(ext) for ext in extensions):
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this filtering be done using a file type input for ktotal() and kdata() instead of passing in "ALL"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is definitely more than one way to do this. kdata gives information about the ith kernels loaded of a certain type. Instead of using extensions I could use kernel type (SPK instead of .bsp for example). I started using extensions in SpiceKernelManager and just continued using it in list_loaded_kernels. Would it be more helpful for you if I added a way to use kernel type instead?

Copy link
Collaborator

@bourque bourque left a comment

Choose a reason for hiding this comment

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

Awesome work here, this is super useful! I just had mostly nit-picky naming and formatting suggestions.


Parameters
----------
extensions: list of str, optional
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
extensions: list of str, optional
extensions: list of str or None, optional

return result


def ls_spice_constants() -> dict:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def ls_spice_constants() -> dict:
def list_all_constants() -> dict:

: tuple
Tuple giving the most recent start and end time in ET.
"""
att_kernels = ls_kernels([".ah.bc", ".ah.a"])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
att_kernels = ls_kernels([".ah.bc", ".ah.a"])
attitude_kernels = ls_kernels([".ah.bc", ".ah.a"])

@@ -0,0 +1,17 @@
KPL/MK

Meta kernel that loads the arecibo configuration kernels into the sarbeta program.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was wondering if arecibo was some sort of spice-specific buzz word or the actual arecibo telescope 😅

@@ -0,0 +1,30 @@
KPL/MK

Meta kernel that loads the arecibo configuration kernels into the sarbeta program.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Meta kernel that loads the arecibo configuration kernels into the sarbeta program.
Meta kernel that loads the IMAP configuration kernels.

assert n_files_loaded == n_test_files


def test_clear(kernel_object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def test_clear(kernel_object):
def test_clear(kernel_object):
"""Tests the clearing of kernels via the ``clear`` method"""

assert n_files_loaded_after_clear == 0


def test_furnsh_type(caplog):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm a bit confused as to what this test is checking for. It looks more like it is making sure that an invalid kernel type is resulting in an expected error message? If so, might want to rename the test to something like test_invalid_kernel. It would also be helpful to include a docstring.

kernel_object.clear()


def test_ls_kernels(kernel_object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def test_ls_kernels(kernel_object):
def test_ls_kernels(kernel_object):
"""Tests the ``ls_kernels`` function"""

assert sorted(result) == sorted(expected)


def test_ls_spice_constants(kernel_object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def test_ls_spice_constants(kernel_object):
def test_ls_spice_constants(kernel_object):
"""Tests the ``ls_spice_constants`` function"""

assert list(result.keys()) == expected_keys


def test_ls_attitude_coverage(kernel_object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
def test_ls_attitude_coverage(kernel_object):
def test_ls_attitude_coverage(kernel_object):
"""Tests the ``ls_attitude_coverage`` function"""

Copy link
Contributor

@tech3371 tech3371 left a comment

Choose a reason for hiding this comment

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

It looks good to me. I had one comment.

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 brought in by mistake?

@laspsandoval laspsandoval merged commit be2f45e into IMAP-Science-Operations-Center:dev Dec 13, 2023
@sdhoyt sdhoyt linked an issue Dec 14, 2023 that may be closed by this pull request
laspsandoval added a commit to laspsandoval/imap_processing that referenced this pull request Apr 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Create SPICE libraries for processing SPICE draft code
5 participants