PageRenderTime 31ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/sunpy/timeseries/sources/fermi_gbm.py

https://github.com/ayshih/sunpy
Python | 253 lines | 214 code | 6 blank | 33 comment | 0 complexity | d8965f5b99b16d6e639e1154b9f77fe3 MD5 | raw file
  1. """
  2. This module FERMI GBM `~sunpy.timeseries.TimeSeries` source.
  3. """
  4. from collections import OrderedDict
  5. import matplotlib.pyplot as plt
  6. import numpy as np
  7. import pandas as pd
  8. import astropy.units as u
  9. from astropy.time import TimeDelta
  10. import sunpy.io
  11. from sunpy.time import parse_time
  12. from sunpy.timeseries.timeseriesbase import GenericTimeSeries
  13. from sunpy.util.metadata import MetaDict
  14. from sunpy.visualization import peek_show
  15. __all__ = ['GBMSummaryTimeSeries']
  16. class GBMSummaryTimeSeries(GenericTimeSeries):
  17. """
  18. Fermi/GBM Summary lightcurve TimeSeries.
  19. The Gamma-ray Burst Monitor (GBM) is an instrument on board Fermi.
  20. It is meant to detect gamma-ray bursts but also detects solar flares.
  21. It consists of 12 Sodium Iodide (NaI) scintillation detectors and 2 Bismuth Germanate (BGO) scintillation detectors.
  22. The NaI detectors cover from a few keV to about 1 MeV and provide burst triggers and locations.
  23. The BGO detectors cover the energy range from about 150 keV to about 30 MeV.
  24. This summary lightcurve makes use of the CSPEC (daily version) data set which consists of the counts
  25. accumulated every 4.096 seconds in 128 energy channels for each of the 14 detectors.
  26. Note that the data is re-binned from the original 128 into the following 8 pre-determined energy channels.
  27. The rebinning method treats the counts in each of the original 128 channels as
  28. all having the energy of the average energy of that channel. For example, the
  29. counts in an 14.5--15.6 keV original channel would all be accumulated into the
  30. 15--25 keV rebinned channel.
  31. * 4-15 keV
  32. * 15-25 keV
  33. * 25-50 keV
  34. * 50-100 keV
  35. * 100-300 keV
  36. * 300-800 keV
  37. * 800-2000 keV
  38. Examples
  39. --------
  40. >>> import sunpy.timeseries
  41. >>> import sunpy.data.sample # doctest: +REMOTE_DATA
  42. >>> gbm = sunpy.timeseries.TimeSeries(sunpy.data.sample.GBM_TIMESERIES, source='GBMSummary') # doctest: +REMOTE_DATA
  43. >>> gbm.peek() # doctest: +SKIP
  44. References
  45. ----------
  46. * `Fermi Mission Homepage <https://fermi.gsfc.nasa.gov>`_
  47. * `Fermi GBM Homepage <https://fermi.gsfc.nasa.gov/science/instruments/gbm.html>`_
  48. * `Fermi Science Support Center <https://fermi.gsfc.nasa.gov/ssc/>`_
  49. * `Fermi Data Product <https://fermi.gsfc.nasa.gov/ssc/data/access/>`_
  50. * `GBM Instrument Papers <https://gammaray.nsstc.nasa.gov/gbm/publications/instrument_journal_gbm.html>`_
  51. """
  52. # Class attributes used to specify the source class of the TimeSeries
  53. # and a URL to the mission website.
  54. _source = 'gbmsummary'
  55. _url = "https://gammaray.nsstc.nasa.gov/gbm/#"
  56. def plot(self, axes=None, columns=None, **kwargs):
  57. """
  58. Plots the GBM timeseries.
  59. Parameters
  60. ----------
  61. axes : `matplotlib.axes.Axes`, optional
  62. The axes on which to plot the TimeSeries. Defaults to current axes.
  63. columns : list[str], optional
  64. If provided, only plot the specified columns.
  65. **kwargs : `dict`
  66. Additional plot keyword arguments that are handed to `~matplotlib.axes.Axes.plot`
  67. functions.
  68. Returns
  69. -------
  70. `~matplotlib.axes.Axes`
  71. The plot axes.
  72. """
  73. self._validate_data_for_plotting()
  74. if axes is None:
  75. axes = plt.gca()
  76. if columns is None:
  77. columns = self._data.columns
  78. for d in columns:
  79. axes.plot(self._data.index, self._data[d], label=d, **kwargs)
  80. axes.set_yscale("log")
  81. axes.set_xlabel('Start time: ' + self._data.index[0].strftime('%Y-%m-%d %H:%M:%S UT'))
  82. axes.set_ylabel('Counts/s/keV')
  83. axes.legend()
  84. return axes
  85. @peek_show
  86. def peek(self, title=None, columns=None, **kwargs):
  87. """
  88. Displays the GBM timeseries by calling
  89. `~sunpy.timeseries.sources.fermi_gbm.GBMSummaryTimeSeries.plot`.
  90. .. plot::
  91. import sunpy.timeseries
  92. import sunpy.data.sample
  93. gbm = sunpy.timeseries.TimeSeries(sunpy.data.sample.GBM_TIMESERIES, source='GBMSummary')
  94. gbm.peek()
  95. Parameters
  96. ----------
  97. title : `str`, optional
  98. The title of the plot.
  99. columns : list[str], optional
  100. If provided, only plot the specified columns.
  101. **kwargs : `dict`
  102. Additional plot keyword arguments that are handed to `~matplotlib.axes.Axes.plot`
  103. functions.
  104. """
  105. if title is None:
  106. title = 'Fermi GBM Summary data ' + str(self.meta.get('DETNAM').values())
  107. fig, ax = plt.subplots()
  108. axes = self.plot(axes=ax, columns=columns, **kwargs)
  109. axes.set_title(title)
  110. fig.autofmt_xdate()
  111. return fig
  112. @classmethod
  113. def _parse_file(cls, filepath):
  114. """
  115. Parses a GBM CSPEC FITS file.
  116. Parameters
  117. ----------
  118. filepath : `str`
  119. The path to the file you want to parse.
  120. """
  121. hdus = sunpy.io.read_file(filepath)
  122. return cls._parse_hdus(hdus)
  123. @classmethod
  124. def _parse_hdus(cls, hdulist):
  125. """
  126. Parses a GBM CSPEC `astropy.io.fits.HDUList`.
  127. Parameters
  128. ----------
  129. hdulist : `str`
  130. The path to the file you want to parse.
  131. """
  132. header = MetaDict(OrderedDict(hdulist[0].header))
  133. # these GBM files have three FITS extensions.
  134. # extn1 - this gives the energy range for each of the 128 energy bins
  135. # extn2 - this contains the data, e.g. counts, exposure time, time of observation
  136. # extn3 - eclipse times?
  137. energy_bins = hdulist[1].data
  138. count_data = hdulist[2].data
  139. # rebin the 128 energy channels into some summary ranges
  140. # 4-15 keV, 15 - 25 keV, 25-50 keV, 50-100 keV, 100-300 keV, 300-800 keV, 800 - 2000 keV
  141. # put the data in the units of counts/s/keV
  142. summary_counts = _bin_data_for_summary(energy_bins, count_data)
  143. # get the time information in datetime format with the correct MET adjustment
  144. met_ref_time = parse_time('2001-01-01 00:00') # Mission elapsed time
  145. gbm_times = met_ref_time + TimeDelta(count_data['time'], format='sec')
  146. gbm_times.precision = 9
  147. gbm_times = gbm_times.isot.astype('datetime64')
  148. column_labels = ['4-15 keV', '15-25 keV', '25-50 keV', '50-100 keV',
  149. '100-300 keV', '300-800 keV', '800-2000 keV']
  150. # Add the units data
  151. units = OrderedDict([('4-15 keV', u.ct / u.s / u.keV), ('15-25 keV', u.ct / u.s / u.keV),
  152. ('25-50 keV', u.ct / u.s / u.keV), ('50-100 keV', u.ct / u.s / u.keV),
  153. ('100-300 keV', u.ct / u.s / u.keV), ('300-800 keV', u.ct / u.s / u.keV),
  154. ('800-2000 keV', u.ct / u.s / u.keV)])
  155. return pd.DataFrame(summary_counts, columns=column_labels, index=gbm_times), header, units
  156. @classmethod
  157. def is_datasource_for(cls, **kwargs):
  158. """
  159. Determines if the file corresponds to a GBM summary lightcurve
  160. `~sunpy.timeseries.TimeSeries`.
  161. """
  162. # Check if source is explicitly assigned
  163. if 'source' in kwargs.keys():
  164. if kwargs.get('source', ''):
  165. return kwargs.get('source', '').lower().startswith(cls._source)
  166. # Check if HDU defines the source instrument
  167. if 'meta' in kwargs.keys():
  168. return kwargs['meta'].get('INSTRUME', '').startswith('GBM')
  169. def _bin_data_for_summary(energy_bins, count_data):
  170. """
  171. Rebin the 128 energy channels into some summary ranges and put the data in
  172. the units of counts/s/keV.
  173. Bin ranges used:
  174. * 4-15 keV
  175. * 15-25 keV
  176. * 25-50 keV
  177. * 50-100 keV
  178. * 100-300 keV
  179. * 300-800 keV
  180. * 800-2000 keV
  181. Parameters
  182. ----------
  183. energy_bins : `numpy.ndarray`
  184. The array of energy bins to rebin.
  185. count_data : `numpy.ndarray`
  186. The array of count data to rebin.
  187. """
  188. # list of energy bands to sum between
  189. ebands = [4, 15, 25, 50, 100, 300, 800, 2000]
  190. e_center = (energy_bins['e_min'] + energy_bins['e_max']) / 2
  191. indices = [np.searchsorted(e_center, e) for e in ebands]
  192. summary_counts = []
  193. for ind_start, ind_end in zip(indices[:-1], indices[1:]):
  194. # sum the counts in the energy bands, and find counts/s/keV
  195. summed_counts = np.sum(count_data["counts"][:, ind_start:ind_end], axis=1)
  196. energy_width = (energy_bins["e_max"][ind_end - 1] - energy_bins["e_min"][ind_start])
  197. summary_counts.append(summed_counts/energy_width/count_data["exposure"])
  198. return np.array(summary_counts).T
  199. def _parse_detector(detector):
  200. """
  201. Check and fix detector name strings.
  202. Parameters
  203. ----------
  204. detector : `str`
  205. The detector name to check.
  206. """
  207. oklist = ['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9',
  208. 'n10', 'n11']
  209. altlist = [str(i) for i in range(12)]
  210. if detector in oklist:
  211. return detector
  212. elif detector in altlist:
  213. return 'n' + detector
  214. else:
  215. raise ValueError('Detector string could not be interpreted')