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

Factor out get_ofp_states and get_telem_values for cheta #249 improvements #285

Merged
merged 2 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 1 addition & 77 deletions kadi/commands/utils.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst

import functools
import logging
from dataclasses import dataclass
from typing import List, Optional

import cheta.fetch_eng as fetch
import numpy as np
import plotly.graph_objects as pgo
from astropy.table import Table
from cxotime import CxoTime, CxoTimeLike, units as u
from cxotime import CxoTime, CxoTimeLike


__all__ = [
"add_figure_regions",
"compress_time_series",
"convert_state_code_to_raw_val",
"get_telem_values",
"fill_gaps_with_nan",
"NoTelemetryError",
"get_ofp_states",
"get_time_series_chunks",
"TimeSeriesChunk",
"TimeSeriesPoint",
Expand All @@ -28,10 +22,6 @@
logger = logging.getLogger(__name__)


class NoTelemetryError(Exception):
"""No telemetry available for the specified interval"""


@dataclass
class TimeSeriesPoint:
time: float
Expand Down Expand Up @@ -67,72 +57,6 @@ def __repr__(self):
return out


def get_telem_values(msids: list, stop, days: float = 14) -> Table:
"""
Fetch last ``days`` of available ``msids`` telemetry values before
time ``tstart``.

:param msids: fetch msids list
:param stop: stop time for telemetry (CxoTime-like)
:param days: length of telemetry request before ``tstart``
:returns: Table of requested telemetry values from fetch
"""
stop = CxoTime(stop)
start = stop - days * u.day
logger.info(f"Fetching telemetry for {msids} between {start.date} and {stop.date}")

with fetch.data_source("cxc", "maude allow_subset=False"):
msidset = fetch.MSIDset(msids, start.date, stop.date)

# Use the first MSID as the primary one to set the time base
msid0 = msidset[msids[0]]

if len(msids) == 1:
# Only one MSID so just filter any bad values
msid0.filter_bad()
times = msid0.times
else:
# Multiple MSIDs so interpolate all to the same time base The assumption
# here is that all MSIDs have the same basic time base, e.g. AOCMDQT1-3.
msidset.interpolate(times=msid0.times, bad_union=True)
times = msidset.times

# Finished when we found at least 10 good records (5 mins)
if len(times) < 10:
raise NoTelemetryError(
f"Found no telemetry for {msids!r} within {days} days of {stop}"
)

names = ["time"] + msids
out = Table([times] + [msidset[x].vals for x in msids], names=names)
return out


@functools.lru_cache(maxsize=1)
def get_ofp_states(stop, days):
"""Get the Onboard Flight Program states for ``stop`` and ``days`` lookback

This is normally "NRML" but in safe mode it is "SAFE" or other values. State codes:
['NNRM' 'STDB' 'STBS' 'NRML' 'NSTB' 'SUOF' 'SYON' 'DPLY' 'SYSF' 'STUP' 'SAFE']
"""
import astropy.table as tbl
from cheta.utils import logical_intervals

msid = "conlofp"
tlm = get_telem_values([msid], stop, days)
states_list = []
for state_code in np.unique(tlm[msid]):
states = logical_intervals(
tlm["time"], tlm[msid] == state_code, max_gap=2.1, complete_intervals=False
)
states["val"] = state_code
states_list.append(states)
states = tbl.vstack(states_list)
states.sort("datestart")

return states


def add_figure_regions(
fig: pgo.Figure,
figure_start: CxoTimeLike,
Expand Down
30 changes: 19 additions & 11 deletions kadi/commands/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,22 @@
import Ska.Shell
import Ska.tdb
from astropy.table import Table
from cheta.utils import logical_intervals
from cheta.utils import (
get_ofp_states,
get_telem_table,
logical_intervals,
NoTelemetryError,
)
from cxotime import CxoTime

import kadi
import kadi.commands
from kadi.commands.states import interpolate_states, reduce_states
from kadi.commands.utils import (
CxoTimeLike,
NoTelemetryError,
add_figure_regions,
compress_time_series,
convert_state_code_to_raw_val,
get_ofp_states,
get_telem_values,
)

__all__ = [
Expand All @@ -52,7 +54,6 @@
"ValidatePcadMode",
"ValidateLETG",
"ValidateHETG",
"NoTelemetryError",
"get_command_sheet_exclude_intervals",
]

Expand All @@ -75,9 +76,9 @@ class PlotAttrs:
:param title: (str): Plot title.
:param ylabel: (str): Y-axis label.
:param range: (list): Y-axis range (optional).
:param max_delta_time: (float): Maximum time delta before a new data point is plotted.
:param max_delta_val: (float): Maximum value delta before a new data point is plotted.
:param max_gap_time: (float): Maximum gap in time before a plot gap is inserted.
:param max_delta_time: (float): Maximum time delta before new data point is plotted.
:param max_delta_val: (float): Maximum value delta before new data point is plotted.
:param max_gap_time: (float): Maximum gap in time before plot gap is inserted.
"""

title: str
Expand Down Expand Up @@ -135,9 +136,16 @@ def __init_subclass__(cls, **kwargs):
@property
def tlm(self):
if not hasattr(self, "_tlm"):
self._tlm = get_telem_values(
msids=self.msids, stop=self.stop, days=self.days
logger.info(
Copy link
Member Author

Choose a reason for hiding this comment

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

The logging and exception handling was previously inside get_telem_values.

f"Fetching telemetry for {self.msids} between {self.start.date} and"
f" {self.stop.date}"
)
self._tlm = get_telem_table(self.msids, self.start, self.stop)
if len(self._tlm) == 0:
raise NoTelemetryError(
f"No telemetry for {self.msids} between {self.start.date} and"
f" {self.stop.date}"
)
self.update_tlm()
self.add_exclude_intervals()
return self._tlm
Expand Down Expand Up @@ -256,7 +264,7 @@ def exclude_ofp_intervals_except(self, states_expected: List[str]):
This includes a padding of 30 minutes after SAFE mode and 5 minutes for non-NRML
states other than SAFE like STUP, SYON, SYSF etc.
"""
ofp_states = get_ofp_states(self.stop.date, self.days)
ofp_states = get_ofp_states(self.start, self.stop)
for state in ofp_states:
if state["val"] not in states_expected:
pad_stop = 30 * u.min if state["val"] == "SAFE" else 5 * u.min
Expand Down
1 change: 1 addition & 0 deletions kadi/scripts/validate_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def main(args=None):
# Enable logging in relevant packages
logging.getLogger("kadi").setLevel(opt.log_level)
fetch.add_logging_handler(level=opt.log_level)
fetch.data_source.set("cxc", "maude allow_subset=False")
Copy link
Member Author

Choose a reason for hiding this comment

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

Using MAUDE was previously hard-coded into get_telem_values which limits its general-purpose utility. Instead we set the data source globally within the validate states app.

maude.set_logger_level(opt.log_level)

maude.conf.cache_msid_queries = True
Expand Down
5 changes: 0 additions & 5 deletions pytest.ini

This file was deleted.