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

Shiny #175

Merged
merged 21 commits into from
Aug 10, 2020
Merged

Shiny #175

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d42fda0
Improve interpolate_states and require astropy Table
taldcroft Jun 26, 2020
34bd12f
Move _tl_to_bs_cmds into update_cmds
taldcroft Jun 26, 2020
b6ca5de
Move decode_power into states
taldcroft Jun 26, 2020
36753ec
Use kadi interpolate_states not Chandra.cmd_states version
taldcroft Jun 26, 2020
de7d817
Remove final Chandra.cmd_states dependency from kadi
taldcroft Jun 26, 2020
a3df599
Fix compatibility with older astropy and use ECSV
taldcroft Jun 26, 2020
10fcfd1
Add regression test data for commanded states
taldcroft Jun 26, 2020
2c2c398
Remove unused pickle import
taldcroft Jun 26, 2020
b147356
Add tstart and tstop to states
taldcroft Jun 29, 2020
7794f88
Merge pull request #167 from sot/remove-chandra-cmd-states
taldcroft Jun 30, 2020
c0c626c
Change default remove_starcat to False
taldcroft Jun 30, 2020
49da5ba
Merge pull request #169 from sot/change-remove-starcat
taldcroft Jun 30, 2020
5ecab48
Convert bytestring to unicode and add time column
taldcroft Jun 30, 2020
2818024
Merge pull request #168 from sot/add-tstart-tstop
taldcroft Jun 30, 2020
f04ba7b
Add tstart and tstop to continuity so it looks like state0
taldcroft Jun 30, 2020
edc7b9e
Add CommandTable method as_list_of_dict()
taldcroft Jun 30, 2020
a41c345
Merge pull request #170 from sot/list-of-dict
taldcroft Jun 30, 2020
5a4ae12
Move continuity tstart/tstop into __dates__
taldcroft Jun 30, 2020
fa7d79f
Remove the continuity tstart/tstop completely, not needed
taldcroft Jun 30, 2020
bef9bdd
Fix async problem of event query within Jupyter notebook
taldcroft Aug 1, 2020
0473c68
Merge pull request #173 from sot/allow-unsafe-async
taldcroft Aug 1, 2020
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
48 changes: 40 additions & 8 deletions kadi/commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import numpy as np

from astropy.table import Table, Row, Column, vstack
from Chandra.Time import DateTime
from Chandra.Time import DateTime, date2secs
import pickle

from ..paths import IDX_CMDS_PATH, PARS_DICT_PATH
Expand Down Expand Up @@ -90,26 +90,35 @@ def get_cmds(start=None, stop=None, inclusive_stop=False, **kwargs):
>>> print(cmds)

:param start: DateTime format (optional) Start time, defaults to beginning
of available commands (2002:001) :param stop: DateTime format (optional)
Stop time, defaults to end of available commands :param kwargs: key=val
keyword argument pairs
of available commands (2002:001)
:param stop: DateTime format (optional) Stop time, defaults to end of available
commands
:param inclusive_stop: bool, include commands at exactly ``stop`` if True.
:param kwargs: key=val keyword argument pairs for filtering

:returns: :class:`~kadi.commands.commands.CommandTable` of commands
"""
cmds = _find(start, stop, inclusive_stop, **kwargs)
out = CommandTable(cmds)
out['params'] = None if len(out) > 0 else Column([], dtype=object)

# Convert 'date' from bytestring to unicode. This allows
# date2secs(out['date']) to work and will generally reduce weird problems.
out.convert_bytestring_to_unicode()

out.add_column(date2secs(out['date']), name='time', index=6)
out['time'].info.format = '.3f'

return out


def get_cmds_from_backstop(backstop, remove_starcat=True):
def get_cmds_from_backstop(backstop, remove_starcat=False):
"""
Initialize a ``CommandTable`` from ``backstop``, which can either
be a string file name or a backstop table from ``parse_cm.read_backstop``.

:param backstop: str or Table
:param remove_starcat: remove star catalog command parameters (default=True)
:param remove_starcat: remove star catalog command parameters (default=False)
:returns: :class:`~kadi.commands.commands.CommandTable` of commands
"""
if isinstance(backstop, Path):
Expand All @@ -133,6 +142,7 @@ def get_cmds_from_backstop(backstop, remove_starcat=True):
out['tlmsid'] = np.chararray.encode(bs['tlmsid'])
out['scs'] = bs['scs'].astype(np.uint8)
out['step'] = bs['step'].astype(np.uint16)
out['time'] = date2secs(bs['date'])
# Set timeline_id to 0, does not match any real timeline id
out['timeline_id'] = np.zeros(n_bs, dtype=np.uint32)
out['vcdu'] = bs['vcdu'].astype(np.int32)
Expand All @@ -154,7 +164,14 @@ def get_cmds_from_backstop(backstop, remove_starcat=True):
params['event_type'] = params['type']
del params['type']

return CommandTable(out)
out = CommandTable(out)
out['time'].info.format = '.3f'

# Convert 'date' from bytestring to unicode. This allows
# date2secs(out['date']) to work and will generally reduce weird problems.
out.convert_bytestring_to_unicode()

return out


def _find(start=None, stop=None, inclusive_stop=False, **kwargs):
Expand Down Expand Up @@ -217,6 +234,7 @@ def _find(start=None, stop=None, inclusive_stop=False, **kwargs):
par_ok |= (idx_cmds['idx'] == idx)
ok &= par_ok
cmds = idx_cmds[ok]

return cmds


Expand Down Expand Up @@ -258,7 +276,7 @@ def __str__(self):

out = ('{} {} '.format(self['date'], self['type'])
+ ' '.join('{}={}'.format(key, self[key]) for key in keys
if key not in ('type', 'date')))
if key not in ('type', 'date', 'time')))
return out

def __sstr__(self):
Expand Down Expand Up @@ -354,3 +372,17 @@ def add_cmds(self, cmds):
out.sort(['date', 'step', 'scs'])

return out

def as_list_of_dict(self):
"""Convert CommandTable to a list of dict (ala Ska.ParseCM)

The command ``params`` are embedded as a dict for each command.

:return: list of dict
"""
self.fetch_params()

names = self.colnames
cmds_list = [{name: cmd[name] for name in names} for cmd in self]

return cmds_list
99 changes: 89 additions & 10 deletions kadi/commands/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@

from astropy.table import Table, Column

from Chandra.cmd_states import decode_power
from Chandra.Time import DateTime
from Chandra.Time import DateTime, date2secs, secs2date
import Chandra.Maneuver
from Quaternion import Quat
import Ska.Sun
Expand Down Expand Up @@ -920,6 +919,63 @@ def callback(cls, date, transitions, state, idx):
###################################################################
# ACIS transitions
###################################################################
def decode_power(mnem):
"""
Decode number of chips and feps from a ACIS power command
Return a dictionary with the number of chips and their identifiers

Example::

>>> decode_power("WSPOW08F3E")
{'ccd_count': 5,
'ccds': 'I0 I1 I2 I3 S3 ',
'clocking': 0,
'fep_count': 5,
'feps': '1 2 3 4 5 ',
'vid_board': 1}

:param mnem: power command string

"""
fep_info = {'fep_count': 0,
'ccd_count': 0,
'feps': '',
'ccds': '',
'vid_board': 1,
'clocking': 0}

# Special case WSPOW000XX to turn off vid_board
if mnem.startswith('WSPOW000'):
fep_info['vid_board'] = 0

# the hex for the commanding is after the WSPOW
powstr = mnem[5:]
if (len(powstr) != 5):
raise ValueError("%s in unexpected format" % mnem)

# convert the hex to decimal and "&" it with 63 (binary 111111)
fepkey = int(powstr, 16) & 63
# count the true binary bits
for bit in range(0, 6):
if (fepkey & (1 << bit)):
fep_info['fep_count'] = fep_info['fep_count'] + 1
fep_info['feps'] = fep_info['feps'] + str(bit) + ' '

# convert the hex to decimal and right shift by 8 places
vidkey = int(powstr, 16) >> 8

# count the true bits
for bit in range(0, 10):
if (vidkey & (1 << bit)):
fep_info['ccd_count'] = fep_info['ccd_count'] + 1
# position indicates I or S chip
if (bit < 4):
fep_info['ccds'] = fep_info['ccds'] + 'I' + str(bit) + ' '
else:
fep_info['ccds'] = fep_info['ccds'] + 'S' + str(bit - 4) + ' '

return fep_info


class ACISTransition(BaseTransition):
"""
Expand Down Expand Up @@ -1258,6 +1314,13 @@ def get_states(start=None, stop=None, state_keys=None, cmds=None, continuity=Non
# Final datestop far in the future
datestop[-1] = stop
out.add_column(Column(datestop, name='datestop'), 1)

# Add corresponding tstart, tstop
out.add_column(Column(date2secs(out['datestart']), name='tstart'), 2)
out.add_column(Column(date2secs(out['datestop']), name='tstop'), 3)
out['tstart'].info.format = '.3f'
out['tstop'].info.format = '.3f'

out['trans_keys'] = [st.trans_keys for st in states]

if reduce:
Expand Down Expand Up @@ -1313,9 +1376,10 @@ def reduce_states(states, state_keys, merge_identical=False):
has_transition |= has_transitions[key]

# Create output with only desired state keys and only states with a transition
out = states[['datestart', 'datestop'] + list(state_keys)][has_transition]
out['datestop'][:-1] = out['datestart'][1:]
out['datestop'][-1] = states['datestop'][-1]
out = states[['datestart', 'datestop', 'tstart', 'tstop'] + list(state_keys)][has_transition]
for dt in ('date', 't'):
out[f'{dt}stop'][:-1] = out[f'{dt}start'][1:]
out[f'{dt}stop'][-1] = states[f'{dt}stop'][-1]

trans_keys_list = [TransKeysSet() for _ in range(len(out))]
for key in state_keys:
Expand Down Expand Up @@ -1406,7 +1470,8 @@ def get_continuity(date=None, state_keys=None, lookbacks=(7, 30, 180, 1000)):
# the stop time and did not get processed.
continuity_transitions.extend(states.meta['continuity_transitions'])

colnames = set(states.colnames) - set(['datestart', 'datestop', 'trans_keys'])
colnames = set(states.colnames) - set(['datestart', 'datestop',
'tstart', 'tstop', 'trans_keys'])
for colname in colnames:
if states[colname][-1] is not None:
# Reduce states to only the desired state_key
Expand Down Expand Up @@ -1451,13 +1516,25 @@ def get_continuity(date=None, state_keys=None, lookbacks=(7, 30, 180, 1000)):
def interpolate_states(states, times):
"""Interpolate ``states`` table at given times.

:param states: states (np.recarray)
:param times: times (np.array or list)
:param states: states (astropy states Table)
:param times: times (np.array or any DateTime compatible input)

:returns: ``states`` view at ``times``
"""
indexes = np.searchsorted(states['tstop'], times)
return states[indexes]
from astropy.table import Column
if not isinstance(times, np.ndarray) or times.dtype.kind != 'f':
times = DateTime(times).secs

try:
tstops = states['tstop']
except (ValueError, KeyError):
tstops = date2secs(states['datestop'])

indexes = np.searchsorted(tstops, times)
out = states[indexes]
out.add_column(Column(secs2date(times), name='date'), index=0)

return out


def _unique(seq):
Expand Down Expand Up @@ -1515,5 +1592,7 @@ def get_chandra_states(main_args=None):
state_keys = opt.state_keys.split(',') if opt.state_keys else None
states = get_states(start, stop, state_keys, merge_identical=opt.merge_identical)
del states['trans_keys']
del states['tstart']
del states['tstop']

ascii.write(states, output=opt.outfile, format='fixed_width', delimiter='')
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
24 changes: 19 additions & 5 deletions kadi/commands/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Use data file from parse_cm.test for get_cmds_from_backstop test.
# This package is a dependency
import parse_cm.tests
from Chandra.Time import secs2date

# Import cmds module directly (not kadi.cmds package, which is from ... import cmds)
from .. import commands
Expand Down Expand Up @@ -65,7 +66,7 @@ def test_get_cmds_zero_length_result():
cmds = commands.get_cmds(date='2017:001:12:00:00')
assert len(cmds) == 0
assert cmds.colnames == ['idx', 'date', 'type', 'tlmsid', 'scs',
'step', 'timeline_id', 'vcdu', 'params']
'step', 'time', 'timeline_id', 'vcdu', 'params']


def test_get_cmds_inclusive_stop():
Expand All @@ -81,12 +82,22 @@ def test_get_cmds_inclusive_stop():
assert np.all(cmds['date'] == [start, stop])


def test_cmds_as_list_of_dict():
cmds = commands.get_cmds('2020:140', '2020:141')
cmds_list = cmds.as_list_of_dict()
assert isinstance(cmds_list, list)
assert isinstance(cmds_list[0], dict)
cmds_rt = commands.CommandTable(cmds)
assert set(cmds_rt.colnames) == set(cmds.colnames)
for name in cmds.colnames:
assert np.all(cmds_rt[name] == cmds[name])


def test_get_cmds_from_backstop_and_add_cmds():
bs_file = Path(parse_cm.tests.__file__).parent / 'data' / 'CR182_0803.backstop'
bs_cmds = commands.get_cmds_from_backstop(bs_file)
bs_cmds = commands.get_cmds_from_backstop(bs_file, remove_starcat=True)

cmds = commands.get_cmds(start='2018:182:00:00:00',
stop='2018:182:08:00:00')
cmds = commands.get_cmds(start='2018:182:00:00:00', stop='2018:182:08:00:00')

assert len(bs_cmds) == 674
assert len(cmds) == 56
Expand All @@ -95,6 +106,9 @@ def test_get_cmds_from_backstop_and_add_cmds():
for bs_col, col in zip(bs_cmds.itercols(), cmds.itercols()):
assert bs_col.dtype == col.dtype

assert np.all(secs2date(cmds['time']) == cmds['date'])
assert np.all(secs2date(bs_cmds['time']) == bs_cmds['date'])

new_cmds = cmds.add_cmds(bs_cmds)
assert len(new_cmds) == len(cmds) + len(bs_cmds)

Expand All @@ -104,7 +118,7 @@ def test_get_cmds_from_backstop_and_add_cmds():
assert np.all(bs_cmds['params'][ok] == {})

# Accept MP_STARCAT commands
bs_cmds = commands.get_cmds_from_backstop(bs_file, remove_starcat=False)
bs_cmds = commands.get_cmds_from_backstop(bs_file)
ok = bs_cmds['type'] == 'MP_STARCAT'
assert np.count_nonzero(ok) == 15
assert np.all(bs_cmds['params'][ok] != {})
Loading