diff --git a/gwsumm/config.py b/gwsumm/config.py index 3a904e31..7826f57e 100644 --- a/gwsumm/config.py +++ b/gwsumm/config.py @@ -307,21 +307,30 @@ def load_states(self, section='states'): """ from .state import (register_state, SummaryState, SummaryMetaState, ALLSTATE, generate_all_state, get_state) - # parse the [states] section into individual state definitions + # Parse the [states] section into individual state definitions. + # Each state definition is amended to the GWSummConfigParser as a new + # section with name and definition key-value pairs. try: states = dict(self.nditems(section)) except configparser.NoSectionError: self.add_section(section) states = {} for state in states: - if not (self.has_section('state-%s' % state) or - self.has_section('state %s' % state)): - section = 'state-%s' % state + if not (self.has_section(f'state-{state}') or + self.has_section(f'state {state}')): + section = f'state-{state}' self.add_section(section) self.set(section, 'name', state) self.set(section, 'definition', states[state]) - # parse each state section into a new state + # Parse each state section into a new state. + # Here we reset the states variable to an empty list because the + # previous code block added all of the states section into their own + # sections [state-]. We register those states and metastates, + # appending them also to the states list. Metastates are states that + # use another state definition, where they look for the key name. If + # key is not defined, then name is used instead. The key or name is + # expected to have an 'H1-' or 'L1-' prefix. states = [] for section in self.sections(): if re.match(r'state[-\s]', section): diff --git a/gwsumm/plot/builtin.py b/gwsumm/plot/builtin.py index 92800e63..138826aa 100644 --- a/gwsumm/plot/builtin.py +++ b/gwsumm/plot/builtin.py @@ -20,6 +20,7 @@ """ import os.path +import re import warnings from itertools import cycle @@ -498,19 +499,31 @@ def _draw(self): else: iterator = list(zip(self.channels, plotargs)) + # loop over the channels for chantuple in iterator: channel = chantuple[0] channel2 = chantuple[1] pargs = chantuple[-1] + # get the state or segment if self.state and not self.all_data: valid = self.state else: valid = SegmentList([self.span]) + # If the state is a metastate, then get the corresponding IFO- + # specific state from the global variables list if isinstance(valid, SummaryMetaState): - valid = globalv.STATES[ - f'{channel.ifo}-{valid.uses[0][3:]}'.lower()] + reg = re.compile(channel.ifo) + matching = list(filter(reg.match, valid.uses)) + assert len(matching) == 1, ( + f"Failed to find a unique state for {valid.name} " + f"metastate. Found {len(matching)} matching states in " + f"{valid.uses} for {channel.ifo}") + try: + valid = globalv.STATES[matching[0].lower()] + except KeyError: + raise if self.type == 'coherence-spectrum': data = get_coherence_spectrum( diff --git a/gwsumm/state/core.py b/gwsumm/state/core.py index 7c64798d..2ce40a28 100644 --- a/gwsumm/state/core.py +++ b/gwsumm/state/core.py @@ -352,11 +352,43 @@ def __str__(self): class SummaryMetaState(SummaryState): - """A meta state where different states may be used""" + """A meta state where different states may be used when processing a + `~gwsumm.tabs.DataTab`. - def __init__(self, name, known=SegmentList(), active=SegmentList(), + An example use ase is when one wants to plot two different state times on + the same plot. This currently has limitations as it expects the states to + be from different detectors. So when using this metastate, each value in + "uses" needs to be prefixed by "" + + Parameters + ---------- + name : `str` + name for this state + uses : `list` + list of strings for which states to use. Ex.: ['H1-quiet', 'L1-quiet'] + known : `~gwpy.segments.SegmentList`, optional + list of known segments + active : `~gwpy.segments.SegmentList`, optional + list of active segments + description : `str`, optional + text describing what this state means + definition : `str`, optional + logical combination of flags that define known and active segments + for this state (see :attr:`documentation ` + for details) + hours : `str`, optional + a string of the form "-," + key : `str`, optional + registry key for this state, defaults to :attr:`~SummaryState.name` + filename : `str`, optional + path to filename with segments + url : `str`, optional + URL to read the segments + """ + + def __init__(self, name, uses, known=SegmentList(), active=SegmentList(), description=None, definition=None, hours=None, key=None, - filename=None, url=None, uses=[]): + filename=None, url=None): super(SummaryMetaState, self).__init__( name=name, known=known, active=active, @@ -367,6 +399,21 @@ def __init__(self, name, known=SegmentList(), active=SegmentList(), @classmethod def from_ini(cls, config, section): + """Create a new `SummaryMetaState` from a section in a `ConfigParser`. + + Parameters + ---------- + config : :class:`~gwsumm.config.GWConfigParser` + customised configuration parser containing given section + section : `str` + name of section to parse + + Returns + ------- + `SummaryMetaState` + a new state, with attributes set from the options in the + configuration + """ config = GWSummConfigParser.from_configparser(config) # get parameters params = dict(config.nditems(section)) @@ -384,6 +431,9 @@ def fetch(self, config=GWSummConfigParser(), segmentcache=None, segdb_error='raise', datacache=None, datafind_error='raise', nproc=1, nds=None, **kwargs): + """Finalise this state by fetching the states this metastate uses, + either from global memory, or from the segment database + """ for idx, state in enumerate(self.uses): globalv.STATES[state.lower()].fetch( diff --git a/gwsumm/tabs/data.py b/gwsumm/tabs/data.py index 3cf81a78..c84f3737 100644 --- a/gwsumm/tabs/data.py +++ b/gwsumm/tabs/data.py @@ -357,6 +357,8 @@ def process(self, config=ConfigParser(), nproc=1, **stateargs): datafind_error=stateargs.get('datafind_error', 'raise'), nproc=nproc, nds=stateargs.get('nds', None)) vprint("States finalised [%d total]\n" % len(self.states)) + + # loop over states for this tab and print out information for state in self.states: if isinstance(state, SummaryMetaState): vprint(