Skip to content

Commit

Permalink
major refactor after feedback and discussion offline
Browse files Browse the repository at this point in the history
  • Loading branch information
tech3371 committed Sep 12, 2024
1 parent 3435253 commit bf9bf90
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 136 deletions.
113 changes: 13 additions & 100 deletions imap_processing/spice/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@

import typing
from enum import IntEnum
from typing import Optional, Union
from pathlib import Path
from typing import Union

import cdflib
import numpy as np
import pandas as pd
import spiceypy as spice

from imap_processing.spice.kernels import ensure_spice
from imap_processing.spice.time import met_to_j2000ns


class SpiceBody(IntEnum):
Expand Down Expand Up @@ -92,89 +91,11 @@ def imap_state(
return np.asarray(state)


def generate_spin_data(
start_met: float, end_met: Optional[float] = None
) -> pd.DataFrame:
def get_spin_data(path_to_spin_file: Path) -> pd.DataFrame:
"""
Generate a spin table CSV covering one or more days.
Read spin file and return spin data.
Spin table contains the following fields:
(
spin_number,
spin_start_sec,
spin_start_subsec,
spin_period_sec,
spin_period_valid,
spin_phas_valid,
spin_period_source,
thruster_firing
)
This function creates spin data using start MET and end MET time.
Each spin start data uses the nominal 15 second spin period. The spins that
occur from 00:00(Mid-night) to 00:10 UTC are marked with flags for
thruster firing, invalid spin period, and invalid spin phase.
Parameters
----------
start_met : float
Provides the start time in Mission Elapsed Time (MET).
end_met : float
Provides the end time in MET. If not provided, default to one day
from start time.
Returns
-------
spin_df : pandas.DataFrame
Spin data. May need to save data to CSV file.
"""
if end_met is None:
# end_time is one day after start_time
end_met = start_met + 86400

# Create spin start second data of 15 seconds increment
spin_start_sec = np.arange(start_met, end_met, 15)

spin_dict = {
"spin_number": np.arange(spin_start_sec.size, dtype=np.uint32),
"spin_start_sec": spin_start_sec,
"spin_start_subsec": np.full(spin_start_sec.size, 0, dtype=np.uint32),
"spin_period_sec": np.full(spin_start_sec.size, 15.0, dtype=np.float32),
"spin_period_valid": np.ones(spin_start_sec.size, dtype=np.uint8),
"spin_phas_valid": np.ones(spin_start_sec.size, dtype=np.uint8),
"spin_period_source": np.zeros(spin_start_sec.size, dtype=np.uint8),
"thruster_firing": np.zeros(spin_start_sec.size, dtype=np.uint8),
}

# Convert spin_start_sec to datetime to set repointing times flags
spin_start_dates = met_to_j2000ns(spin_start_sec)
spin_start_dates = cdflib.cdfepoch.to_datetime(spin_start_dates)

# Convert DatetimeIndex to Series for using .dt accessor
spin_start_dates_series = pd.Series(spin_start_dates)

# Find index of all timestamps that fall within 10 minutes after midnight
repointing_times = spin_start_dates_series[
(spin_start_dates_series.dt.time >= pd.Timestamp("00:00:00").time())
& (spin_start_dates_series.dt.time < pd.Timestamp("00:10:00").time())
]

repointing_times_index = repointing_times.index

# Use the repointing times to set thruster firing flag and spin period valid
spin_dict["thruster_firing"][repointing_times_index] = 1
spin_dict["spin_period_valid"][repointing_times_index] = 0
spin_dict["spin_phas_valid"][repointing_times_index] = 0

spin_df = pd.DataFrame.from_dict(spin_dict)
return spin_df


def get_spin_data(start_met: float, end_met: Optional[float] = None) -> pd.DataFrame:
"""
Get spin data for a given time range.
This function queries spin data for the input date range. Spin
table contains the following fields:
Spin data should contains the following fields:
(
spin_number,
spin_start_sec,
Expand All @@ -188,27 +109,19 @@ def get_spin_data(start_met: float, end_met: Optional[float] = None) -> pd.DataF
Parameters
----------
start_met : float
Provide the start time in Mission Elapsed Time (MET).
end_met : float
Provide the end time in MET. If not provided, default to one day
from start time.
path_to_spin_file : pathlib.Path
Input CSV file containing spin data.
Returns
-------
spin_data : pandas.DataFrame
Spin data. It's a dictionary with keys and values as numpy arrays.
Spin data.
"""
if end_met is None:
# end time is one day after start time
end_met = start_met + 86400

# TODO: write code to query spin database and return all spin data
# for the input date range once we have actual infrastructure in place.

# Call generate_spin_data function temporarily that generates sample spin data for
# the given date range.
return generate_spin_data(start_met, end_met)
# TODO: change this function if file type of spin data changes
# or if spin data is stored in a database
if path_to_spin_file.suffix != ".csv":
raise ValueError("Input file must be a CSV file.")
return pd.read_csv(path_to_spin_file)


def get_spacecraft_spin_phase(
Expand Down
80 changes: 80 additions & 0 deletions imap_processing/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import os
import re
from typing import Optional

import cdflib
import imap_data_access
import numpy as np
import pandas as pd
import pytest
import spiceypy as spice

from imap_processing import imap_module_directory
from imap_processing.spice.time import met_to_j2000ns


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -155,3 +159,79 @@ def _unset_metakernel_path(monkeypatch):
"""Temporarily unsets the SPICE_METAKERNEL environment variable"""
if os.getenv("SPICE_METAKERNEL", None) is not None:
monkeypatch.delenv("SPICE_METAKERNEL")


@pytest.fixture()
def generate_spin_data():
def make_data(start_met: int, end_met: Optional[int] = None) -> pd.DataFrame:
"""
Generate a spin table CSV covering one or more days.
Spin table contains the following fields:
(
spin_number,
spin_start_sec,
spin_start_subsec,
spin_period_sec,
spin_period_valid,
spin_phas_valid,
spin_period_source,
thruster_firing
)
This function creates spin data using start MET and end MET time.
Each spin start data uses the nominal 15 second spin period. The spins that
occur from 00:00(Mid-night) to 00:10 UTC are marked with flags for
thruster firing, invalid spin period, and invalid spin phase.
Parameters
----------
start_met : int
Provides the start time in Mission Elapsed Time (MET).
end_met : int
Provides the end time in MET. If not provided, default to one day
from start time.
Returns
-------
spin_df : pd.DataFrame
Spin data.
"""
if end_met is None:
# end_time is one day after start_time
end_met = start_met + 86400

# Create spin start second data of 15 seconds increment
spin_start_sec = np.arange(start_met, end_met, 15)

spin_dict = {
"spin_number": np.arange(spin_start_sec.size, dtype=np.uint32),
"spin_start_sec": spin_start_sec,
"spin_start_subsec": np.full(spin_start_sec.size, 0, dtype=np.uint32),
"spin_period_sec": np.full(spin_start_sec.size, 15.0, dtype=np.float32),
"spin_period_valid": np.ones(spin_start_sec.size, dtype=np.uint8),
"spin_phas_valid": np.ones(spin_start_sec.size, dtype=np.uint8),
"spin_period_source": np.zeros(spin_start_sec.size, dtype=np.uint8),
"thruster_firing": np.zeros(spin_start_sec.size, dtype=np.uint8),
}

# Convert spin_start_sec to datetime to set repointing times flags
spin_start_dates = met_to_j2000ns(spin_start_sec)
spin_start_dates = cdflib.cdfepoch.to_datetime(spin_start_dates)

# Convert DatetimeIndex to Series for using .dt accessor
spin_start_dates_series = pd.Series(spin_start_dates)

# Find index of all timestamps that fall within 10 minutes after midnight
repointing_times = spin_start_dates_series[
(spin_start_dates_series.dt.time >= pd.Timestamp("00:00:00").time())
& (spin_start_dates_series.dt.time < pd.Timestamp("00:10:00").time())
]

repointing_times_index = repointing_times.index

# Use the repointing times to set thruster firing flag and spin period valid
spin_dict["thruster_firing"][repointing_times_index] = 1
spin_dict["spin_period_valid"][repointing_times_index] = 0
spin_dict["spin_phas_valid"][repointing_times_index] = 0

spin_df = pd.DataFrame.from_dict(spin_dict)
return spin_df

return make_data
59 changes: 23 additions & 36 deletions imap_processing/tests/spice/test_geometry.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Tests coverage for imap_processing/spice/geometry.py"""

from pathlib import Path

import numpy as np
import pandas as pd
import pytest

from imap_processing.spice.geometry import (
SpiceBody,
generate_spin_data,
get_spacecraft_spin_phase,
get_spin_data,
imap_state,
Expand Down Expand Up @@ -44,42 +46,27 @@ def test_get_spacecraft_spin_phase():
assert len(spin_phases) == 1, "Spin phases length should match query times length."


def test_get_spin_data():
def test_get_spin_data(generate_spin_data, tmpdir):
"""Test get_spin_data() with generated spin data."""
# SWE test data time minus 56120 seconds to get mid-night time
start_time = 453051323.0 - 56120
spin_df = generate_spin_data(start_time)
spin_csv_file_path = Path(tmpdir) / "spin_data.spin.csv"
spin_df.to_csv(spin_csv_file_path, index=False)

start_time = 453051323.0

spin_data = get_spin_data(start_met=start_time)

assert len(spin_data.keys()) == 8, "Spin data must have 8 fields."


def test_generate_spin_data():
"""Test generate_spin_data() with generated spin data."""

start_time = 453051323.0
end_time = 453051323.0 + 86400

spin_data = generate_spin_data(start_time, end_time)
spin_data = get_spin_data(path_to_spin_file=spin_csv_file_path)

assert len(spin_data.keys()) == 8, "Spin data must have 8 fields."
assert len(spin_data["spin_number"]) > 0, "Spin number must have some values."
assert len(spin_data["spin_start_sec"]) > 0, "Spin start sec must have some values."
assert (
len(spin_data["spin_start_subsec"]) > 0
), "Spin start subsec must have some values."
assert (
len(spin_data["spin_period_sec"]) > 0
), "Spin period sec must have some values."
assert (
len(spin_data["spin_period_valid"]) > 0
), "Spin period valid must have some values."
assert (
len(spin_data["spin_phas_valid"]) > 0
), "Spin phas valid must have some values."
assert (
len(spin_data["spin_period_source"]) > 0
), "Spin period source must have some values."
assert (
len(spin_data["thruster_firing"]) > 0
), "Thruster firing must have some values."
assert len(spin_data) == 5760, "One day should have 5,760 records of 15 seconds."
assert isinstance(spin_data, pd.DataFrame), "Return type must be pandas.DataFrame."

assert set(spin_data.columns) == {
"spin_number",
"spin_start_sec",
"spin_start_subsec",
"spin_period_sec",
"spin_period_valid",
"spin_phas_valid",
"spin_period_source",
"thruster_firing",
}, "Spin data must have the specified fields."

0 comments on commit bf9bf90

Please sign in to comment.