gwsumm 2.2.0__tar.gz → 2.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. {gwsumm-2.2.0 → gwsumm-2.2.2}/PKG-INFO +1 -1
  2. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/_version.py +2 -2
  3. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/archive.py +2 -0
  4. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/channels.py +1 -1
  5. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/__init__.py +2 -0
  6. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/builtin.py +25 -11
  7. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/core.py +35 -1
  8. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/range.py +17 -0
  9. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/segments.py +223 -27
  10. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/segments.py +19 -10
  11. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/data.py +2 -1
  12. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_plot.py +3 -3
  13. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/triggers.py +26 -0
  14. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm.egg-info/PKG-INFO +1 -1
  15. {gwsumm-2.2.0 → gwsumm-2.2.2}/.codeclimate.yml +0 -0
  16. {gwsumm-2.2.0 → gwsumm-2.2.2}/.codecov.yml +0 -0
  17. {gwsumm-2.2.0 → gwsumm-2.2.2}/.flake8 +0 -0
  18. {gwsumm-2.2.0 → gwsumm-2.2.2}/.gitattributes +0 -0
  19. {gwsumm-2.2.0 → gwsumm-2.2.2}/.github/workflows/build.yml +0 -0
  20. {gwsumm-2.2.0 → gwsumm-2.2.2}/.github/workflows/lint.yml +0 -0
  21. {gwsumm-2.2.0 → gwsumm-2.2.2}/.gitignore +0 -0
  22. {gwsumm-2.2.0 → gwsumm-2.2.2}/.readthedocs.yaml +0 -0
  23. {gwsumm-2.2.0 → gwsumm-2.2.2}/CONTRIBUTING.md +0 -0
  24. {gwsumm-2.2.0 → gwsumm-2.2.2}/LICENSE +0 -0
  25. {gwsumm-2.2.0 → gwsumm-2.2.2}/MANIFEST.in +0 -0
  26. {gwsumm-2.2.0 → gwsumm-2.2.2}/README.rst +0 -0
  27. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/Makefile +0 -0
  28. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/_static/css/custom.css +0 -0
  29. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/_templates/autoclass/class.rst +0 -0
  30. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/_templates/autosummary/base.rst +0 -0
  31. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/_templates/autosummary/class.rst +0 -0
  32. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/_templates/autosummary/module.rst +0 -0
  33. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/_templates/layout.html +0 -0
  34. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/api/index.rst +0 -0
  35. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/automation.rst +0 -0
  36. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/cli.rst +0 -0
  37. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/conf.py +0 -0
  38. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/configuration/data.rst +0 -0
  39. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/configuration/format.rst +0 -0
  40. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/configuration/index.rst +0 -0
  41. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/configuration/tabs.rst +0 -0
  42. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/environment.yml +0 -0
  43. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/examples/first.png +0 -0
  44. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/examples/imc.png +0 -0
  45. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/images/screenshot.png +0 -0
  46. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/index.rst +0 -0
  47. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/make.bat +0 -0
  48. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/modes.rst +0 -0
  49. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/overview.rst +0 -0
  50. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/plots.rst +0 -0
  51. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/states.rst +0 -0
  52. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/tabs/api.rst +0 -0
  53. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/tabs/index.rst +0 -0
  54. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/tabs/modes.rst +0 -0
  55. {gwsumm-2.2.0 → gwsumm-2.2.2}/docs/tabs/websites.rst +0 -0
  56. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/__init__.py +0 -0
  57. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/__main__.py +0 -0
  58. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/batch.py +0 -0
  59. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/config/__init__.py +0 -0
  60. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/config/defaults.ini +0 -0
  61. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/config/matplotlib.ini +0 -0
  62. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/__init__.py +0 -0
  63. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/coherence.py +0 -0
  64. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/mathutils.py +0 -0
  65. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/range.py +0 -0
  66. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/spectral.py +0 -0
  67. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/timeseries.py +0 -0
  68. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/data/utils.py +0 -0
  69. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/globalv.py +0 -0
  70. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/__init__.py +0 -0
  71. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/bootstrap.py +0 -0
  72. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/html5.py +0 -0
  73. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/static.py +0 -0
  74. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/tests/__init__.py +0 -0
  75. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/tests/test_bootstrap.py +0 -0
  76. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/tests/test_html5.py +0 -0
  77. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/html/tests/test_static.py +0 -0
  78. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/io.py +0 -0
  79. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/mode.py +0 -0
  80. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/guardian/__init__.py +0 -0
  81. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/guardian/__main__.py +0 -0
  82. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/guardian/core.py +0 -0
  83. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/guardian/tests/__init__.py +0 -0
  84. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/guardian/tests/test_main.py +0 -0
  85. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/mixins.py +0 -0
  86. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/noisebudget.py +0 -0
  87. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/registry.py +0 -0
  88. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/sei.py +0 -0
  89. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/triggers/__init__.py +0 -0
  90. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/triggers/__main__.py +0 -0
  91. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/triggers/core.py +0 -0
  92. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/triggers/tests/__init__.py +0 -0
  93. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/triggers/tests/test_main.py +0 -0
  94. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/plot/utils.py +0 -0
  95. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/state/__init__.py +0 -0
  96. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/state/all.py +0 -0
  97. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/state/core.py +0 -0
  98. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/state/registry.py +0 -0
  99. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/__init__.py +0 -0
  100. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/builtin.py +0 -0
  101. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/core.py +0 -0
  102. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/etg.py +0 -0
  103. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/fscan.py +0 -0
  104. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/gracedb.py +0 -0
  105. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/guardian.py +0 -0
  106. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/management.py +0 -0
  107. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/misc.py +0 -0
  108. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/registry.py +0 -0
  109. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/sei.py +0 -0
  110. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tabs/stamp.py +0 -0
  111. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/__init__.py +0 -0
  112. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/common.py +0 -0
  113. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_archive.py +0 -0
  114. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_batch.py +0 -0
  115. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_channels.py +0 -0
  116. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_config.py +0 -0
  117. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_data.py +0 -0
  118. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_mode.py +0 -0
  119. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_tabs.py +0 -0
  120. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/tests/test_utils.py +0 -0
  121. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/units.py +0 -0
  122. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm/utils.py +0 -0
  123. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm.egg-info/SOURCES.txt +0 -0
  124. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm.egg-info/dependency_links.txt +0 -0
  125. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm.egg-info/entry_points.txt +0 -0
  126. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm.egg-info/requires.txt +0 -0
  127. {gwsumm-2.2.0 → gwsumm-2.2.2}/gwsumm.egg-info/top_level.txt +0 -0
  128. {gwsumm-2.2.0 → gwsumm-2.2.2}/pyproject.toml +0 -0
  129. {gwsumm-2.2.0 → gwsumm-2.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gwsumm
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: A python toolbox used by the LIGO Scientific Collaboration for detector characterisation
5
5
  Author-email: Alex Urban <alex.urban@ligo.org>, Duncan Macleod <duncan.macleod@ligo.org>
6
6
  Maintainer-email: Evan Goetz <evan.goetz@ligo.org>
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.2.0'
16
- __version_tuple__ = version_tuple = (2, 2, 0)
15
+ __version__ = version = '2.2.2'
16
+ __version_tuple__ = version_tuple = (2, 2, 2)
@@ -176,6 +176,8 @@ def write_data_archive(outfile, channels=True, timeseries=True,
176
176
 
177
177
  # moves the new file to the backup directory
178
178
  shutil.move(temp_outfile, outfile)
179
+ # Changes permission to allow reading from any user
180
+ os.chmod(outfile, 0o664)
179
181
 
180
182
  except Exception:
181
183
  # if it fails for any reason, raise and continue
@@ -32,7 +32,7 @@ from .utils import re_quote
32
32
 
33
33
  __author__ = 'Duncan Macleod <duncan.macleod@ligo.org>'
34
34
 
35
- NDS2_TYPES = Nds2ChannelType.names()
35
+ NDS2_TYPES = Nds2ChannelType.nds2names()
36
36
  CIS_URL = 'https://cis.ligo.org'
37
37
  re_channel = re.compile(r'[A-Z]\d:[a-zA-Z0-9]+' # core channel section L1:TEST
38
38
  r'(?:[-_][a-zA-Z0-9_]+)?' # underscore-delimited parts
@@ -80,6 +80,7 @@ from .segments import (
80
80
  ODCDataPlot,
81
81
  SegmentPiePlot,
82
82
  NetworkDutyPiePlot,
83
+ NetworkDutyBarPlot,
83
84
  SegmentBarPlot,
84
85
  SegmentHistogramPlot,
85
86
  )
@@ -98,6 +99,7 @@ from .range import (
98
99
  RangeSpectrogramDataPlot,
99
100
  RangeSpectrumDataPlot,
100
101
  RangeCumulativeSpectrumDataPlot,
102
+ RangeCumulativeHistogramPlot,
101
103
  SimpleTimeVolumeDataPlot,
102
104
  GWpyTimeVolumeDataPlot,
103
105
  )
@@ -710,15 +710,14 @@ class TimeSeriesHistogramPlot(DataPlot):
710
710
 
711
711
  # plot
712
712
  for ax, arr, pargs in zip(cycle(axes), data, histargs):
713
- # set range if not given
714
- if pargs.get('range') is None:
715
- pargs['range'] = self._get_range(
716
- data,
717
- # use range from first dataset if already calculated
718
- range=histargs[0].get('range'),
719
- # use xlim if manually set (user or INI)
720
- xlim=None if ax.get_autoscalex_on() else ax.get_xlim(),
721
- )
713
+ # set range
714
+ pargs['range'] = self._get_range(
715
+ data,
716
+ # use range from first dataset if already calculated
717
+ range=histargs[0].get('range'),
718
+ # use xlim if manually set (user or INI)
719
+ xlim=None if ax.get_autoscalex_on() else ax.get_xlim(),
720
+ )
722
721
 
723
722
  # plot histogram
724
723
  _, _, patches = ax.hist(arr, **pargs)
@@ -759,8 +758,23 @@ class TimeSeriesHistogramPlot(DataPlot):
759
758
  return self.finalize(outputfile=outputfile)
760
759
 
761
760
  def _get_range(self, data, range=None, xlim=None):
762
- if range is not None or xlim is not None:
763
- return range or xlim
761
+ if range is not None:
762
+ if range == 'autoscaling':
763
+ return None
764
+ else:
765
+ range = list(range)
766
+ try:
767
+ if range[0] == 'min':
768
+ range[0] = numpy.min(data)
769
+ if range[1] == 'max':
770
+ range[1] = numpy.max(data)
771
+ if range[0] < range[1]:
772
+ return range
773
+ except (ValueError, IndexError) as exc:
774
+ if not str(exc).startswith('zero-size array'):
775
+ raise
776
+ if xlim is not None:
777
+ return xlim
764
778
  try:
765
779
  return numpy.min(data), numpy.max(data)
766
780
  except ValueError as exc:
@@ -212,7 +212,41 @@ class DataPlot(SummaryPlot):
212
212
  #: plot() call style
213
213
  _single_call = False
214
214
  #: dict of default plotting kwargs
215
- defaults = {}
215
+ defaults = {
216
+ 'animation.convert_args': ['-layers', 'OptimizePlus'],
217
+ 'axes.axisbelow': False,
218
+ 'axes.edgecolor': 'gray',
219
+ 'axes.formatter.limits': [-3, 4],
220
+ 'axes.formatter.use_mathtext': True,
221
+ 'axes.grid': True,
222
+ 'axes.labelpad': 5.0,
223
+ 'axes.labelsize': 18.0,
224
+ 'axes.titlesize': 22.0,
225
+ 'contour.algorithm': 'mpl2014',
226
+ 'figure.figsize': [12.0, 6.0],
227
+ 'figure.labelsize': 'large',
228
+ 'figure.labelweight': 'normal',
229
+ 'font.sans-serif': ['Roboto'],
230
+ 'grid.alpha': 0.5,
231
+ 'grid.linewidth': 0.5,
232
+ 'legend.edgecolor': 'inherit',
233
+ 'legend.fancybox': False,
234
+ 'legend.fontsize': 10.0,
235
+ 'legend.handlelength': 1.0,
236
+ 'legend.numpoints': 2,
237
+ 'mathtext.bf': 'Roboto',
238
+ 'mathtext.cal': 'Calligraffiti',
239
+ 'mathtext.fontset': 'custom',
240
+ 'mathtext.it': 'Roboto:italic',
241
+ 'mathtext.rm': 'Roboto',
242
+ 'mathtext.sf': 'Roboto',
243
+ 'mathtext.tt': 'Roboto Slab',
244
+ 'savefig.transparent': True,
245
+ 'svg.fonttype': 'none',
246
+ 'text.parse_math': True,
247
+ 'xtick.labelsize': 14.0,
248
+ 'ytick.labelsize': 14.0,
249
+ }
216
250
  #: list of parameters parsed for `plot()` calls
217
251
  DRAW_PARAMS = list(putils.ARTIST_PARAMS)
218
252
 
@@ -153,6 +153,23 @@ class RangeDataHistogramPlot(RangePlotMixin, get_plot('histogram')):
153
153
  register_plot(RangeDataHistogramPlot)
154
154
 
155
155
 
156
+ class RangeCumulativeHistogramPlot(RangePlotMixin, get_plot('histogram')):
157
+ type = 'range-cumulative-histogram'
158
+ defaults = get_plot('histogram').defaults.copy()
159
+ defaults.update(RangePlotMixin.defaults.copy())
160
+ defaults.update({
161
+ 'xlabel': 'Angle-averaged range [Mpc]',
162
+ 'ylabel': 'Cumulative time duration',
163
+ 'log': False,
164
+ 'cumulative': True,
165
+ 'density': True,
166
+ 'range': (1, 'max'),
167
+ })
168
+
169
+
170
+ register_plot(RangeCumulativeHistogramPlot)
171
+
172
+
156
173
  class RangeSpectrogramDataPlot(RangePlotMixin, get_plot('spectrogram')):
157
174
  type = 'range-spectrogram'
158
175
  defaults = get_plot('spectrogram').defaults.copy()
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # Copyright (C) Duncan Macleod (2013)
3
+ # Evan Goetz (2023)
3
4
  #
4
5
  # This file is part of GWSumm.
5
6
  #
@@ -31,7 +32,7 @@ from dateutil.relativedelta import relativedelta
31
32
 
32
33
  from matplotlib import rcParams
33
34
  from matplotlib.artist import setp
34
- from matplotlib.colors import (rgb2hex, is_color_like)
35
+ from matplotlib.colors import (rgb2hex, is_color_like, TABLEAU_COLORS)
35
36
  from matplotlib.patches import Rectangle
36
37
 
37
38
  from glue import iterutils
@@ -1006,15 +1007,54 @@ class SegmentPiePlot(PiePlot, SegmentDataPlot):
1006
1007
  wedgeargs = self.parse_wedge_kwargs()
1007
1008
  plotargs = self.parse_plot_kwargs()
1008
1009
 
1010
+ # Boolean logic flags to determine if this code is currently running:
1011
+ # - before the span of interest (current time the code is running <
1012
+ # start of span)
1013
+ # - after the span of interest (current time the code is running >= end
1014
+ # of span)
1015
+ # - during the span of interest (any other time)
1016
+ # The flag is set to True for the appropriate noun (before, during, or
1017
+ # after).
1018
+ # These flags are then used to set plot titles and labels and determine
1019
+ # if there is any missing data.
1020
+ before = during = after = False
1021
+ if globalv.NOW < int(self.span[0]):
1022
+ before = True
1023
+ elif globalv.NOW >= int(self.span[1]):
1024
+ after = True
1025
+ else:
1026
+ during = True
1027
+
1009
1028
  # use state to generate suptitle with GPS span
1029
+ # this will be different depending on if `include_future` is given
1030
+ # as an option or whether running before, during, or after the time
1031
+ # interval requested
1010
1032
  if self.state:
1011
- self.pargs.setdefault(
1012
- 'suptitle',
1013
- '[%s-%s, state: %s]' % (self.span[0], self.span[1],
1014
- texify(str(self.state))))
1033
+ if future or after:
1034
+ self.pargs.setdefault(
1035
+ 'suptitle',
1036
+ (f'[{self.span[0]}-{self.span[1]}, '
1037
+ f'state: {texify(str(self.state))}]'))
1038
+ elif before:
1039
+ self.pargs.setdefault(
1040
+ 'suptitle',
1041
+ (f'[{self.span[0]}-{self.span[0]}, '
1042
+ f'state: {texify(str(self.state))}]'))
1043
+ else:
1044
+ self.pargs.setdefault(
1045
+ 'suptitle',
1046
+ (f'[{self.span[0]}-{globalv.NOW}, '
1047
+ f'state: {texify(str(self.state))}]'))
1015
1048
  else:
1016
- self.pargs.setdefault(
1017
- 'suptitle', '[%s-%s]' % (self.span[0], self.span[1]))
1049
+ if future or after:
1050
+ self.pargs.setdefault(
1051
+ 'suptitle', f'[{self.span[0]}-{self.span[1]}]')
1052
+ elif before:
1053
+ self.pargs.setdefault(
1054
+ 'suptitle', f'[{self.span[0]}-{self.span[0]}]')
1055
+ else:
1056
+ self.pargs.setdefault(
1057
+ 'suptitle', f'[{self.span[0]}-{globalv.NOW}]')
1018
1058
 
1019
1059
  # get segments
1020
1060
  data = []
@@ -1028,12 +1068,39 @@ class SegmentPiePlot(PiePlot, SegmentDataPlot):
1028
1068
  padding=self.padding).coalesce()
1029
1069
  data.append(float(abs(segs.active)))
1030
1070
 
1031
- # handle missing or future data
1071
+ # handle missing (undefined) segments
1072
+ # if running before then all the time is future because segments
1073
+ # haven't been generated
1074
+ # if running after then some segments may not cover the whole time
1075
+ # if during, it is somewhere in between
1032
1076
  total = float(sum(data))
1033
- if future or (total < alltime):
1034
- data.append(alltime - total)
1077
+ undefined = future_seg = 0
1078
+ if before:
1079
+ future_seg = alltime
1080
+ elif after:
1081
+ undefined = alltime - total
1082
+ elif during:
1083
+ future_seg = int(self.span[1]) - globalv.NOW
1084
+ undefined = alltime - future_seg - total
1085
+ current_total = globalv.NOW - int(self.span[0])
1086
+
1087
+ # figure out the extra pieces to include in the pie chart and labels
1088
+ # TODO: There is something messed up about "labels" and
1089
+ # "label" that should be cleaned up
1090
+ if undefined > 0:
1091
+ data.append(undefined)
1092
+ if 'labels' in plotargs:
1093
+ plotargs['labels'] = list(plotargs['labels']) + ['Undefined']
1094
+ elif 'label' in plotargs:
1095
+ plotargs['label'] = list(plotargs['label']) + ['Undefined']
1096
+ if 'colors' in plotargs:
1097
+ plotargs['colors'] = list(plotargs['colors']) + ['black']
1098
+ if future or before:
1099
+ data.append(future_seg)
1035
1100
  if 'labels' in plotargs:
1036
1101
  plotargs['labels'] = list(plotargs['labels']) + [' ']
1102
+ elif 'label' in plotargs:
1103
+ plotargs['label'] = list(plotargs['label']) + [' ']
1037
1104
  if 'colors' in plotargs:
1038
1105
  plotargs['colors'] = list(plotargs['colors']) + ['white']
1039
1106
 
@@ -1058,7 +1125,12 @@ class SegmentPiePlot(PiePlot, SegmentDataPlot):
1058
1125
  pclabels.append(label)
1059
1126
  else:
1060
1127
  try:
1061
- pc = d / (total if future else alltime) * 100
1128
+ if future or after:
1129
+ pc = d / alltime * 100
1130
+ elif during:
1131
+ pc = d / current_total * 100
1132
+ else:
1133
+ pc = 0.0
1062
1134
  except ZeroDivisionError:
1063
1135
  pc = 0.0
1064
1136
  pclabels.append(texify(
@@ -1150,28 +1222,45 @@ class NetworkDutyPiePlot(SegmentPiePlot):
1150
1222
  # construct compound flags for each network size
1151
1223
  flags = dict((f[:2], f) for f in self.flags)
1152
1224
  network = ''.join(sorted(set(flags)))
1153
- self.pargs.setdefault('title', '%s network duty factor' % network)
1225
+ self.pargs.setdefault('title', f'{network} network duty factor')
1154
1226
  networkflags = []
1155
1227
  colors = []
1156
1228
  labels = []
1229
+ # define an exclude DQ flag so that each subsequent time through
1230
+ # We exclude triple time from double time and double time from single
1231
+ # time
1157
1232
  exclude = DataQualityFlag()
1158
1233
  for i in list(range(len(flags)+1))[::-1]:
1159
1234
  name = self.NETWORK_NAME[i]
1160
- flag = '%s:%s' % (network, name)
1235
+ flag = f'{network}:{name}'
1161
1236
  networksegs = DataQualityFlag(flag, known=valid)
1237
+ # loop over the possible combinations inserting the flag to the
1238
+ # network segments dictionary
1162
1239
  for ifoset in combinations(flags, i):
1163
1240
  if not ifoset:
1164
- compound = '!%s' % '!'.join(list(flags.values()))
1241
+ compound = f"!{'!'.join(list(flags.values()))}"
1165
1242
  else:
1166
1243
  compound = '&'.join(flags[ifo] for ifo in ifoset)
1167
- segs = get_segments(compound, validity=valid, query=False,
1168
- padding=self.padding).coalesce()
1244
+ segs = get_segments(
1245
+ compound, validity=valid, query=False,
1246
+ padding=self.padding, ignore_undefined=True).coalesce()
1169
1247
  networksegs += segs
1248
+ # Final step in the loop for no detectors: if not wanting to plot
1249
+ # future times, then exclude the time from now to the end of the
1250
+ # span from the no detector network
1251
+ if (i == 0 and
1252
+ not self.pargs.get('include_future', False) and
1253
+ globalv.NOW < self.span[1]):
1254
+ exclude.active += SegmentList(
1255
+ [Segment(globalv.NOW, self.span[1])])
1256
+ # insert this flag into the segments global variable and exclude
1257
+ # any of the previous network (more detectors) time from this time
1170
1258
  globalv.SEGMENTS[flag] = networksegs.copy()
1171
1259
  globalv.SEGMENTS[flag].active -= exclude.active
1260
+ # update the segements of times to exclude
1172
1261
  exclude = networksegs
1173
1262
  networkflags.append(flag)
1174
- labels.append('%s interferometer' % name.title())
1263
+ labels.append(f'{name.title()} interferometer')
1175
1264
  colors.append(self.NETWORK_COLOR.get(name))
1176
1265
 
1177
1266
  self.pargs.setdefault('colors', colors)
@@ -1197,7 +1286,6 @@ class SegmentBarPlot(BarPlot, SegmentDataPlot):
1197
1286
  'color': GREEN,
1198
1287
  'edgecolor': 'green',
1199
1288
  'alpha': .6,
1200
- 'align': 'edge', # FIXME, updated for mpl 2.0, can simplify code
1201
1289
  }
1202
1290
  SCALE_UNIT = {
1203
1291
  None: 'seconds',
@@ -1208,17 +1296,24 @@ class SegmentBarPlot(BarPlot, SegmentDataPlot):
1208
1296
  }
1209
1297
 
1210
1298
  def draw(self, outputfile=None):
1299
+ # Check if ylabel has been previously defined
1300
+ # to avoid overwriting it
1301
+ if 'ylabel' in self.pargs:
1302
+ set_ylabel = False
1303
+ else:
1304
+ set_ylabel = True
1305
+
1211
1306
  plot = self.init_plot(projection='rectilinear')
1212
1307
  ax = plot.gca()
1213
1308
 
1214
1309
  if self.state:
1215
1310
  self.pargs.setdefault(
1216
1311
  'suptitle',
1217
- '[%s-%s, state: %s]' % (self.span[0], self.span[1],
1218
- texify(str(self.state))))
1312
+ f'[{self.span[0]}-{self.span[1]},'
1313
+ f'state: {texify(str(self.state))}]')
1219
1314
  else:
1220
1315
  self.pargs.setdefault(
1221
- 'suptitle', '[%s-%s]' % (self.span[0], self.span[1]))
1316
+ 'suptitle', f'[{self.span[0]}-{self.span[1]}]')
1222
1317
  suptitle = self.pargs.pop('suptitle', None)
1223
1318
  if suptitle:
1224
1319
  plot.suptitle(suptitle, y=0.993, va='top')
@@ -1228,11 +1323,13 @@ class SegmentBarPlot(BarPlot, SegmentDataPlot):
1228
1323
  self.pargs.setdefault('ylim', (0, 100))
1229
1324
  elif isinstance(scale, (int, float)):
1230
1325
  self.pargs.setdefault('ylim', (0, abs(self.span) / scale))
1231
- try:
1232
- self.pargs.setdefault('ylabel', 'Livetime [%s]'
1233
- % self.SCALE_UNIT[scale])
1234
- except KeyError:
1235
- self.pargs.setdefault('ylabel', 'Livetime')
1326
+
1327
+ if set_ylabel:
1328
+ try:
1329
+ self.pargs.setdefault('ylabel',
1330
+ f'Livetime [{self.SCALE_UNIT[scale]}]')
1331
+ except KeyError:
1332
+ self.pargs.setdefault('ylabel', 'Livetime')
1236
1333
 
1237
1334
  # extract plotting arguments
1238
1335
  sort = self.pargs.pop('sorted', False)
@@ -1263,7 +1360,7 @@ class SegmentBarPlot(BarPlot, SegmentDataPlot):
1263
1360
 
1264
1361
  # make bar chart
1265
1362
  width = plotargs.pop('width', .8)
1266
- x = numpy.arange(len(data)) - width/2.
1363
+ x = numpy.arange(len(data))
1267
1364
  ax.bar(x, data, width=width, **plotargs)
1268
1365
 
1269
1366
  # set labels
@@ -1287,6 +1384,105 @@ class SegmentBarPlot(BarPlot, SegmentDataPlot):
1287
1384
  register_plot(SegmentBarPlot)
1288
1385
 
1289
1386
 
1387
+ class NetworkDutyBarPlot(SegmentBarPlot):
1388
+
1389
+ """Special case of the `SegmentPiePlot` for network duty factors.
1390
+ """
1391
+ type = 'network-duty-segment-bar'
1392
+ NETWORK_NAME = {
1393
+ 0: 'no',
1394
+ 1: 'single',
1395
+ 2: 'double',
1396
+ 3: 'triple',
1397
+ 4: 'quadruple',
1398
+ 5: 'quintuple',
1399
+ 6: 'sextuple',
1400
+ }
1401
+ NETWORK_COLOR = GW_OBSERVATORY_COLORS.copy()
1402
+ defaults = SegmentBarPlot.defaults.copy()
1403
+ # remove SegmentBarPlot default colors as they overwrite
1404
+ # the ones defined later.
1405
+ defaults.pop('color')
1406
+ defaults.pop('edgecolor')
1407
+ defaults.update({
1408
+ 'title': 'Network duty factor',
1409
+ 'ylabel': 'Duty factor [%]',
1410
+ })
1411
+
1412
+ def draw(self):
1413
+ # get segments
1414
+ if self.state and not self.all_data:
1415
+ valid = self.state.active
1416
+ else:
1417
+ valid = SegmentList([self.span])
1418
+
1419
+ # construct compound flags for each network size
1420
+ flags = dict((f[:2], f) for f in self.flags)
1421
+ # construct all possible network combinations
1422
+ networks = {}
1423
+ for size in range(1, len(flags) + 1):
1424
+ ifocombs = combinations(sorted(set(flags)), size)
1425
+ for ifocomb in ifocombs:
1426
+ key = "".join(ifocomb)
1427
+ networks[key] = (size, ifocomb)
1428
+
1429
+ networkflags = []
1430
+ colors = []
1431
+ labels = []
1432
+ color_id = 0
1433
+ for network, values in networks.items():
1434
+ i = values[0]
1435
+ ifoset = values[1]
1436
+ name = self.NETWORK_NAME[i]
1437
+ if i == 1:
1438
+ # this avoid having a redundant X1:single
1439
+ # in the Segment information table
1440
+ flag = flags[network]
1441
+
1442
+ else:
1443
+ flag = f'{network}:{name}'
1444
+
1445
+ networksegs = DataQualityFlag(flag, known=valid)
1446
+ if not ifoset:
1447
+ compound = f"!{'!'.join(list(flags.values()))}"
1448
+ else:
1449
+ compound = '&'.join(flags[ifo] for ifo in ifoset)
1450
+
1451
+ segs = get_segments(compound, validity=valid, query=False,
1452
+ padding=self.padding).coalesce()
1453
+ networksegs += segs
1454
+ globalv.SEGMENTS[flag] = networksegs.copy()
1455
+ combined_flag = flag.split(':')[0]
1456
+ if flag.startswith(tuple(networks.keys())):
1457
+ networkflags.append(flag)
1458
+
1459
+ labels.append(combined_flag)
1460
+ if self.NETWORK_COLOR.get(combined_flag) is not None:
1461
+ colors.append(self.NETWORK_COLOR.get(combined_flag))
1462
+ else:
1463
+ # if color is not defined, use standard matplotlib colors
1464
+ colors.append(list(TABLEAU_COLORS.values())[color_id])
1465
+ if color_id < len(TABLEAU_COLORS) - 1:
1466
+ color_id += 1
1467
+ else:
1468
+ color_id = 0
1469
+
1470
+ self.pargs.setdefault('colors', colors)
1471
+ self.pargs.setdefault('edgecolor', colors)
1472
+ self.pargs.setdefault('labels', labels)
1473
+
1474
+ # reset flags and generate plot
1475
+ flags_ = self.flags
1476
+ outputfile = self.outputfile
1477
+ self.flags = networkflags
1478
+ out = super(NetworkDutyBarPlot, self).draw(outputfile=outputfile)
1479
+ self.flags = flags_
1480
+ return out
1481
+
1482
+
1483
+ register_plot(NetworkDutyBarPlot)
1484
+
1485
+
1290
1486
  class SegmentHistogramPlot(get_plot('histogram'), SegmentDataPlot):
1291
1487
  """Histogram of segment duration
1292
1488
  """
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  # Copyright (C) Duncan Macleod (2013)
3
+ # Evan Goetz (2023)
3
4
  #
4
5
  # This file is part of GWSumm.
5
6
  #
@@ -22,7 +23,6 @@
22
23
  import sys
23
24
  import operator
24
25
  import warnings
25
- from functools import reduce
26
26
  from collections import OrderedDict
27
27
  from configparser import (
28
28
  DEFAULTSECT,
@@ -34,7 +34,7 @@ from configparser import (
34
34
  from astropy.io.registry import IORegistryError
35
35
 
36
36
  from gwpy.segments import (DataQualityFlag, DataQualityDict,
37
- SegmentList, Segment)
37
+ SegmentListDict, SegmentList, Segment)
38
38
 
39
39
  from . import globalv
40
40
  from .utils import (
@@ -56,7 +56,8 @@ SEGDB_URLS = [
56
56
 
57
57
  def get_segments(flag, validity=None, config=ConfigParser(), cache=None,
58
58
  query=True, return_=True, coalesce=True, padding=None,
59
- segdb_error='raise', url=None, **read_kw):
59
+ ignore_undefined=False, segdb_error='raise', url=None,
60
+ **read_kw):
60
61
  """Retrieve the segments for a given flag
61
62
 
62
63
  Segments will be loaded from global memory if already defined,
@@ -97,6 +98,11 @@ def get_segments(flag, validity=None, config=ConfigParser(), cache=None,
97
98
  `(start, end)` padding with which to pad segments that are
98
99
  downloaded/read
99
100
 
101
+ ignore_undefined : `bool`, optional, default: `False`
102
+ Special case needed for network calculation compound flags so that
103
+ when this is True, DataQualityFlag.known values are set to the same
104
+ value as ``validity``
105
+
100
106
  segdb_error : `str`, optional, default: ``'raise'``
101
107
  how to handle errors returned from the segment database, one of
102
108
 
@@ -168,11 +174,12 @@ def get_segments(flag, validity=None, config=ConfigParser(), cache=None,
168
174
  for f in allflags:
169
175
  globalv.SEGMENTS.setdefault(f, DataQualityFlag(f))
170
176
 
171
- # read segments from global memory and get the union of needed times
177
+ # read segments from global memory and get the intersection of needed times
172
178
  try:
173
- old = reduce(
174
- operator.and_,
175
- (globalv.SEGMENTS.get(f, DataQualityFlag(f)).known for f in flags))
179
+ old = SegmentListDict()
180
+ for f in flags:
181
+ old[f] = globalv.SEGMENTS.get(f, DataQualityFlag(f)).known
182
+ old = SegmentList(old.intersection(flags))
176
183
  except TypeError:
177
184
  old = SegmentList()
178
185
  newsegs = validity - old
@@ -260,15 +267,17 @@ def get_segments(flag, validity=None, config=ConfigParser(), cache=None,
260
267
  for compound in flags:
261
268
  union, intersection, exclude, notequal = split_compound_flag(
262
269
  compound)
263
- if len(union + intersection) == 1:
264
- out[compound].description = globalv.SEGMENTS[f].description
265
- out[compound].padding = padding.get(f, (0, 0))
270
+ if len(f := (union + intersection)) == 1:
271
+ out[compound].description = globalv.SEGMENTS[f[0]].description
272
+ out[compound].padding = padding.get(f[0], (0, 0))
266
273
  for flist, op in zip([exclude, intersection, union, notequal],
267
274
  [operator.sub, operator.and_, operator.or_,
268
275
  not_equal]):
269
276
  for f in flist:
270
277
  pad = padding.get(f, (0, 0))
271
278
  segs = globalv.SEGMENTS[f].copy()
279
+ if ignore_undefined:
280
+ segs.known = validity
272
281
  if isinstance(pad, (float, int)):
273
282
  segs = segs.pad(pad, pad)
274
283
  elif pad is not None:
@@ -266,7 +266,8 @@ class DataTab(ProcessedTab, ParentTab):
266
266
  if cp.has_section(pdef):
267
267
  type_ = cp.get(pdef, 'type')
268
268
  PlotClass = get_plot(type_)
269
- elif (pdef not in ['range-histogram', 'segment-histogram'] and
269
+ elif (pdef not in ['range-histogram', 'segment-histogram',
270
+ 'range-cumulative-histogram'] and
270
271
  pdef.endswith('-histogram')):
271
272
  type_ = None
272
273
  etg, column = pdef.rsplit('-', 2)[:2]
@@ -27,7 +27,7 @@ from configparser import ConfigParser
27
27
 
28
28
  from gwpy.detector import ChannelList
29
29
  from gwpy.plot import Plot
30
- from gwpy.plot.tex import HAS_TEX
30
+ from gwpy.plot.tex import has_tex
31
31
  from gwpy.segments import Segment
32
32
 
33
33
  from .. import plot as gwsumm_plot
@@ -250,7 +250,7 @@ class TestDataPlot(TestSummaryPlot):
250
250
  assert plot.channels == [get_channel('X1:TEST'),
251
251
  get_channel('Y1:TEST')]
252
252
  assert plot.pargs == {'marker': 'o'}
253
- assert plot.rcParams == {'figure.figsize': (2, 1)}
253
+ assert plot.rcParams['figure.figsize'] == (2, 1)
254
254
 
255
255
  cp.set('plot', 'type', 'blah')
256
256
  with pytest.warns(UserWarning):
@@ -281,7 +281,7 @@ class TestDataPlot(TestSummaryPlot):
281
281
  pytest.param(
282
282
  True,
283
283
  [{'label': r'TEST\_WITH\_UNDERSCORE'}, {'label': r'TEST\_QUOTED'}],
284
- marks=pytest.mark.skipif(not HAS_TEX,
284
+ marks=pytest.mark.skipif(not has_tex(),
285
285
  reason='TeX is not available'),
286
286
  ),
287
287
  ])
@@ -243,6 +243,24 @@ def get_triggers(channel, etg, segments, config=GWSummConfigParser(),
243
243
  else:
244
244
  trigs = read_cache(segcache, SegmentList([segment]), etg,
245
245
  nproc=nproc, **read_kw)
246
+
247
+ # extend columns if possible to include bandwidth, central
248
+ # frequency, and duration
249
+ if trigs is not None:
250
+ if 'fstart' in columns and 'fend' in columns:
251
+ if 'bandwidth' not in columns:
252
+ trigs.add_column(trigs['fend'] - trigs['fstart'],
253
+ name='bandwidth')
254
+ if 'central_freq' not in columns:
255
+ trigs.add_column(
256
+ 0.5*(trigs['fstart'] + trigs['fend']),
257
+ name='central_freq')
258
+ if ('tstart' in columns and
259
+ 'tend' in columns and
260
+ 'duration' not in columns):
261
+ trigs.add_column(trigs['tend'] - trigs['tstart'],
262
+ name='duration')
263
+
246
264
  # record triggers
247
265
  if trigs is not None:
248
266
  # add metadata
@@ -260,6 +278,14 @@ def get_triggers(channel, etg, segments, config=GWSummConfigParser(),
260
278
  raise KeyError
261
279
  TableClass = get_etg_table(etg)
262
280
  except KeyError: # build simple table
281
+ if 'fstart' in columns and 'fend' in columns:
282
+ for colval in ['bandwidth', 'central_freq']:
283
+ if colval not in columns:
284
+ columns.extend([colval])
285
+ if ('tstart' in columns and
286
+ 'tend' in columns and
287
+ 'duration' not in columns):
288
+ columns.extend(['duration'])
263
289
  tab = EventTable(names=columns)
264
290
  else: # map to LIGO_LW table with full column listing
265
291
  tab = EventTable(lsctables.New(TableClass))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gwsumm
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: A python toolbox used by the LIGO Scientific Collaboration for detector characterisation
5
5
  Author-email: Alex Urban <alex.urban@ligo.org>, Duncan Macleod <duncan.macleod@ligo.org>
6
6
  Maintainer-email: Evan Goetz <evan.goetz@ligo.org>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes