We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Parsing GBM files for GBMSummaryTimeSeries takes too long
### Describe the performance issue

When reading in the Fermi GBM CSPEC and CTIME fits files to create a timeseries, the parsing takes a very long time. 
This can be up to 3 minutes for the CTIME files 😱 which have a very high cadence.

This is due to t he nested loop here:

https://github.com/sunpy/sunpy/blob/main/sunpy/timeseries/sources/fermi_gbm.py#L215


It loops over the time index which is really inefficient.

### To Reproduce

```python
>>> from sunpy import timeseries as ts
>>> import time

>>> t1 = time.time()
>>> gbm_cspec = ts.TimeSeries("glg_cspec_n0_220120_v00.pha")
>>> print("Time taken to read file", time.time() - t1)
Time taken to read file 11.445274829864502

>>> t1 = time.time()
>>> gbm_ctime = ts.TimeSeries("glg_ctime_n0_220120_v00.pha")
>>> print("Time taken to read file", time.time() - t1)
Time taken to read file 188.96947813034058 😱 
```



### Proposed fix

array divisions rather than looping, I'll make a PR now

--- END ISSUE ---

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

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sunpy/timeseries/sources/fermi_gbm.py b/sunpy/timeseries/sources/fermi_gbm.py
--- a/sunpy/timeseries/sources/fermi_gbm.py
+++ b/sunpy/timeseries/sources/fermi_gbm.py
@@ -31,7 +31,12 @@
 
     This summary lightcurve makes use of the CSPEC (daily version) data set which consists of the counts
     accumulated every 4.096 seconds in 128 energy channels for each of the 14 detectors.
+
     Note that the data is re-binned from the original 128 into the following 8 pre-determined energy channels.
+    The rebinning method treats the counts in each of the original 128 channels as
+    all having the energy of the average energy of that channel.  For example, the
+    counts in an 14.5--15.6 keV original channel would all be accumulated into the
+    15--25 keV rebinned channel.
 
     * 4-15 keV
     * 15-25 keV
@@ -205,25 +210,20 @@
     count_data : `numpy.ndarray`
         The array of count data to rebin.
     """
-    # find the indices corresponding to some standard summary energy bins
+
+    # list of energy bands to sum between
     ebands = [4, 15, 25, 50, 100, 300, 800, 2000]
-    indices = []
-    for e in ebands:
-        indices.append(np.searchsorted(energy_bins['e_max'], e))
+    e_center = (energy_bins['e_min'] + energy_bins['e_max']) / 2
+    indices = [np.searchsorted(e_center, e) for e in ebands]
 
     summary_counts = []
-    for i in range(0, len(count_data['counts'])):
-        counts_in_bands = []
-        for j in range(1, len(ebands)):
-            counts_in_bands.append(
-                np.sum(count_data['counts'][i][indices[j - 1]:indices[j]]) /
-                (count_data['exposure'][i] *
-                 (energy_bins['e_max'][indices[j]] -
-                  energy_bins['e_min'][indices[j - 1]])))
-
-        summary_counts.append(counts_in_bands)
-
-    return summary_counts
+    for ind_start, ind_end in zip(indices[:-1], indices[1:]):
+        # sum the counts in the energy bands, and find counts/s/keV
+        summed_counts = np.sum(count_data["counts"][:, ind_start:ind_end], axis=1)
+        energy_width = (energy_bins["e_max"][ind_end - 1] - energy_bins["e_min"][ind_start])
+        summary_counts.append(summed_counts/energy_width/count_data["exposure"])
+
+    return np.array(summary_counts).T
 
 
 def _parse_detector(detector):

{"golden_diff": "diff --git a/sunpy/timeseries/sources/fermi_gbm.py b/sunpy/timeseries/sources/fermi_gbm.py\n--- a/sunpy/timeseries/sources/fermi_gbm.py\n+++ b/sunpy/timeseries/sources/fermi_gbm.py\n@@ -31,7 +31,12 @@\n \n     This summary lightcurve makes use of the CSPEC (daily version) data set which consists of the counts\n     accumulated every 4.096 seconds in 128 energy channels for each of the 14 detectors.\n+\n     Note that the data is re-binned from the original 128 into the following 8 pre-determined energy channels.\n+    The rebinning method treats the counts in each of the original 128 channels as\n+    all having the energy of the average energy of that channel.  For example, the\n+    counts in an 14.5--15.6 keV original channel would all be accumulated into the\n+    15--25 keV rebinned channel.\n \n     * 4-15 keV\n     * 15-25 keV\n@@ -205,25 +210,20 @@\n     count_data : `numpy.ndarray`\n         The array of count data to rebin.\n     \"\"\"\n-    # find the indices corresponding to some standard summary energy bins\n+\n+    # list of energy bands to sum between\n     ebands = [4, 15, 25, 50, 100, 300, 800, 2000]\n-    indices = []\n-    for e in ebands:\n-        indices.append(np.searchsorted(energy_bins['e_max'], e))\n+    e_center = (energy_bins['e_min'] + energy_bins['e_max']) / 2\n+    indices = [np.searchsorted(e_center, e) for e in ebands]\n \n     summary_counts = []\n-    for i in range(0, len(count_data['counts'])):\n-        counts_in_bands = []\n-        for j in range(1, len(ebands)):\n-            counts_in_bands.append(\n-                np.sum(count_data['counts'][i][indices[j - 1]:indices[j]]) /\n-                (count_data['exposure'][i] *\n-                 (energy_bins['e_max'][indices[j]] -\n-                  energy_bins['e_min'][indices[j - 1]])))\n-\n-        summary_counts.append(counts_in_bands)\n-\n-    return summary_counts\n+    for ind_start, ind_end in zip(indices[:-1], indices[1:]):\n+        # sum the counts in the energy bands, and find counts/s/keV\n+        summed_counts = np.sum(count_data[\"counts\"][:, ind_start:ind_end], axis=1)\n+        energy_width = (energy_bins[\"e_max\"][ind_end - 1] - energy_bins[\"e_min\"][ind_start])\n+        summary_counts.append(summed_counts/energy_width/count_data[\"exposure\"])\n+\n+    return np.array(summary_counts).T\n \n \n def _parse_detector(detector):\n", "issue": "Parsing GBM files for GBMSummaryTimeSeries takes too long\n### Describe the performance issue\n\nWhen reading in the Fermi GBM CSPEC and CTIME fits files to create a timeseries, the parsing takes a very long time. \r\nThis can be up to 3 minutes for the CTIME files \ud83d\ude31 which have a very high cadence.\r\n\r\nThis is due to t he nested loop here:\r\n\r\nhttps://github.com/sunpy/sunpy/blob/main/sunpy/timeseries/sources/fermi_gbm.py#L215\r\n\r\n\r\nIt loops over the time index which is really inefficient.\n\n### To Reproduce\n\n```python\r\n>>> from sunpy import timeseries as ts\r\n>>> import time\r\n\r\n>>> t1 = time.time()\r\n>>> gbm_cspec = ts.TimeSeries(\"glg_cspec_n0_220120_v00.pha\")\r\n>>> print(\"Time taken to read file\", time.time() - t1)\r\nTime taken to read file 11.445274829864502\r\n\r\n>>> t1 = time.time()\r\n>>> gbm_ctime = ts.TimeSeries(\"glg_ctime_n0_220120_v00.pha\")\r\n>>> print(\"Time taken to read file\", time.time() - t1)\r\nTime taken to read file 188.96947813034058 \ud83d\ude31 \r\n```\r\n\r\n\n\n### Proposed fix\n\narray divisions rather than looping, I'll make a PR now\n", "before_files": [{"content": "\"\"\"\nThis module FERMI GBM `~sunpy.timeseries.TimeSeries` source.\n\"\"\"\nfrom collections import OrderedDict\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\n\nimport astropy.units as u\nfrom astropy.time import TimeDelta\n\nimport sunpy.io\nfrom sunpy.time import parse_time\nfrom sunpy.timeseries.timeseriesbase import GenericTimeSeries\nfrom sunpy.util.metadata import MetaDict\nfrom sunpy.visualization import peek_show\n\n__all__ = ['GBMSummaryTimeSeries']\n\n\nclass GBMSummaryTimeSeries(GenericTimeSeries):\n    \"\"\"\n    Fermi/GBM Summary lightcurve TimeSeries.\n\n    The Gamma-ray Burst Monitor (GBM) is an instrument on board Fermi.\n    It is meant to detect gamma-ray bursts but also detects solar flares.\n    It consists of 12 Sodium Iodide (NaI) scintillation detectors and 2 Bismuth Germanate (BGO) scintillation detectors.\n    The NaI detectors cover from a few keV to about 1 MeV and provide burst triggers and locations.\n    The BGO detectors cover the energy range from about 150 keV to about 30 MeV.\n\n    This summary lightcurve makes use of the CSPEC (daily version) data set which consists of the counts\n    accumulated every 4.096 seconds in 128 energy channels for each of the 14 detectors.\n    Note that the data is re-binned from the original 128 into the following 8 pre-determined energy channels.\n\n    * 4-15 keV\n    * 15-25 keV\n    * 25-50 keV\n    * 50-100 keV\n    * 100-300 keV\n    * 300-800 keV\n    * 800-2000 keV\n\n    Examples\n    --------\n    >>> import sunpy.timeseries\n    >>> import sunpy.data.sample  # doctest: +REMOTE_DATA\n    >>> gbm = sunpy.timeseries.TimeSeries(sunpy.data.sample.GBM_TIMESERIES, source='GBMSummary')  # doctest: +REMOTE_DATA\n    >>> gbm.peek()   # doctest: +SKIP\n\n    References\n    ----------\n    * `Fermi Mission Homepage <https://fermi.gsfc.nasa.gov>`_\n    * `Fermi GBM Homepage <https://fermi.gsfc.nasa.gov/science/instruments/gbm.html>`_\n    * `Fermi Science Support Center <https://fermi.gsfc.nasa.gov/ssc/>`_\n    * `Fermi Data Product <https://fermi.gsfc.nasa.gov/ssc/data/access/>`_\n    * `GBM Instrument Papers <https://gammaray.nsstc.nasa.gov/gbm/publications/instrument_journal_gbm.html>`_\n    \"\"\"\n    # Class attribute used to specify the source class of the TimeSeries.\n    _source = 'gbmsummary'\n\n    def plot(self, axes=None, **kwargs):\n        \"\"\"\n        Plots the GBM timeseries.\n\n        Parameters\n        ----------\n        axes : `matplotlib.axes.Axes`, optional\n            The axes on which to plot the TimeSeries. Defaults to current axes.\n        **kwargs : `dict`\n            Additional plot keyword arguments that are handed to `~matplotlib.axes.Axes.plot`\n            functions.\n\n        Returns\n        -------\n        `~matplotlib.axes.Axes`\n            The plot axes.\n        \"\"\"\n        self._validate_data_for_plotting()\n        if axes is None:\n            axes = plt.gca()\n        data_lab = self.to_dataframe().columns.values\n        for d in data_lab:\n            axes.plot(self.to_dataframe().index, self.to_dataframe()[d], label=d, **kwargs)\n        axes.set_yscale(\"log\")\n        axes.set_xlabel('Start time: ' + self.to_dataframe().index[0].strftime('%Y-%m-%d %H:%M:%S UT'))\n        axes.set_ylabel('Counts/s/keV')\n        axes.legend()\n        return axes\n\n    @peek_show\n    def peek(self, title=None, **kwargs):\n        \"\"\"\n        Displays the GBM timeseries by calling\n        `~sunpy.timeseries.sources.fermi_gbm.GBMSummaryTimeSeries.plot`.\n\n        .. plot::\n\n            import sunpy.timeseries\n            import sunpy.data.sample\n            gbm = sunpy.timeseries.TimeSeries(sunpy.data.sample.GBM_TIMESERIES, source='GBMSummary')\n            gbm.peek()\n\n        Parameters\n        ----------\n        title : `str`, optional\n            The title of the plot.\n        **kwargs : `dict`\n            Additional plot keyword arguments that are handed to `~matplotlib.axes.Axes.plot`\n            functions.\n        \"\"\"\n        if title is None:\n            title = 'Fermi GBM Summary data ' + str(self.meta.get('DETNAM').values())\n        fig, ax = plt.subplots()\n        axes = self.plot(axes=ax, **kwargs)\n        axes.set_title(title)\n        fig.autofmt_xdate()\n        return fig\n\n    @classmethod\n    def _parse_file(cls, filepath):\n        \"\"\"\n        Parses a GBM CSPEC FITS file.\n\n        Parameters\n        ----------\n        filepath : `str`\n            The path to the file you want to parse.\n        \"\"\"\n        hdus = sunpy.io.read_file(filepath)\n        return cls._parse_hdus(hdus)\n\n    @classmethod\n    def _parse_hdus(cls, hdulist):\n        \"\"\"\n        Parses a GBM CSPEC `astropy.io.fits.HDUList`.\n\n        Parameters\n        ----------\n        hdulist : `str`\n            The path to the file you want to parse.\n        \"\"\"\n        header = MetaDict(OrderedDict(hdulist[0].header))\n        # these GBM files have three FITS extensions.\n        # extn1 - this gives the energy range for each of the 128 energy bins\n        # extn2 - this contains the data, e.g. counts, exposure time, time of observation\n        # extn3 - eclipse times?\n        energy_bins = hdulist[1].data\n        count_data = hdulist[2].data\n\n        # rebin the 128 energy channels into some summary ranges\n        # 4-15 keV, 15 - 25 keV, 25-50 keV, 50-100 keV, 100-300 keV, 300-800 keV, 800 - 2000 keV\n        # put the data in the units of counts/s/keV\n        summary_counts = _bin_data_for_summary(energy_bins, count_data)\n\n        # get the time information in datetime format with the correct MET adjustment\n        met_ref_time = parse_time('2001-01-01 00:00')  # Mission elapsed time\n        gbm_times = met_ref_time + TimeDelta(count_data['time'], format='sec')\n        gbm_times.precision = 9\n        gbm_times = gbm_times.isot.astype('datetime64')\n\n        column_labels = ['4-15 keV', '15-25 keV', '25-50 keV', '50-100 keV',\n                         '100-300 keV', '300-800 keV', '800-2000 keV']\n\n        # Add the units data\n        units = OrderedDict([('4-15 keV', u.ct / u.s / u.keV), ('15-25 keV', u.ct / u.s / u.keV),\n                             ('25-50 keV', u.ct / u.s / u.keV), ('50-100 keV', u.ct / u.s / u.keV),\n                             ('100-300 keV', u.ct / u.s / u.keV), ('300-800 keV', u.ct / u.s / u.keV),\n                             ('800-2000 keV', u.ct / u.s / u.keV)])\n        return pd.DataFrame(summary_counts, columns=column_labels, index=gbm_times), header, units\n\n    @classmethod\n    def is_datasource_for(cls, **kwargs):\n        \"\"\"\n        Determines if the file corresponds to a GBM summary lightcurve\n        `~sunpy.timeseries.TimeSeries`.\n        \"\"\"\n        # Check if source is explicitly assigned\n        if 'source' in kwargs.keys():\n            if kwargs.get('source', ''):\n                return kwargs.get('source', '').lower().startswith(cls._source)\n        # Check if HDU defines the source instrument\n        if 'meta' in kwargs.keys():\n            return kwargs['meta'].get('INSTRUME', '').startswith('GBM')\n\n\ndef _bin_data_for_summary(energy_bins, count_data):\n    \"\"\"\n    Rebin the 128 energy channels into some summary ranges and put the data in\n    the units of counts/s/keV.\n\n    Bin ranges used:\n    * 4-15 keV\n    * 15-25 keV\n    * 25-50 keV\n    * 50-100 keV\n    * 100-300 keV\n    * 300-800 keV\n    * 800-2000 keV\n\n    Parameters\n    ----------\n    energy_bins : `numpy.ndarray`\n        The array of energy bins to rebin.\n    count_data : `numpy.ndarray`\n        The array of count data to rebin.\n    \"\"\"\n    # find the indices corresponding to some standard summary energy bins\n    ebands = [4, 15, 25, 50, 100, 300, 800, 2000]\n    indices = []\n    for e in ebands:\n        indices.append(np.searchsorted(energy_bins['e_max'], e))\n\n    summary_counts = []\n    for i in range(0, len(count_data['counts'])):\n        counts_in_bands = []\n        for j in range(1, len(ebands)):\n            counts_in_bands.append(\n                np.sum(count_data['counts'][i][indices[j - 1]:indices[j]]) /\n                (count_data['exposure'][i] *\n                 (energy_bins['e_max'][indices[j]] -\n                  energy_bins['e_min'][indices[j - 1]])))\n\n        summary_counts.append(counts_in_bands)\n\n    return summary_counts\n\n\ndef _parse_detector(detector):\n    \"\"\"\n    Check and fix detector name strings.\n\n    Parameters\n    ----------\n    detector : `str`\n        The detector name to check.\n    \"\"\"\n    oklist = ['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9',\n              'n10', 'n11']\n    altlist = [str(i) for i in range(12)]\n    if detector in oklist:\n        return detector\n    elif detector in altlist:\n        return 'n' + detector\n    else:\n        raise ValueError('Detector string could not be interpreted')\n", "path": "sunpy/timeseries/sources/fermi_gbm.py"}], "after_files": [{"content": "\"\"\"\nThis module FERMI GBM `~sunpy.timeseries.TimeSeries` source.\n\"\"\"\nfrom collections import OrderedDict\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\n\nimport astropy.units as u\nfrom astropy.time import TimeDelta\n\nimport sunpy.io\nfrom sunpy.time import parse_time\nfrom sunpy.timeseries.timeseriesbase import GenericTimeSeries\nfrom sunpy.util.metadata import MetaDict\nfrom sunpy.visualization import peek_show\n\n__all__ = ['GBMSummaryTimeSeries']\n\n\nclass GBMSummaryTimeSeries(GenericTimeSeries):\n    \"\"\"\n    Fermi/GBM Summary lightcurve TimeSeries.\n\n    The Gamma-ray Burst Monitor (GBM) is an instrument on board Fermi.\n    It is meant to detect gamma-ray bursts but also detects solar flares.\n    It consists of 12 Sodium Iodide (NaI) scintillation detectors and 2 Bismuth Germanate (BGO) scintillation detectors.\n    The NaI detectors cover from a few keV to about 1 MeV and provide burst triggers and locations.\n    The BGO detectors cover the energy range from about 150 keV to about 30 MeV.\n\n    This summary lightcurve makes use of the CSPEC (daily version) data set which consists of the counts\n    accumulated every 4.096 seconds in 128 energy channels for each of the 14 detectors.\n\n    Note that the data is re-binned from the original 128 into the following 8 pre-determined energy channels.\n    The rebinning method treats the counts in each of the original 128 channels as\n    all having the energy of the average energy of that channel.  For example, the\n    counts in an 14.5--15.6 keV original channel would all be accumulated into the\n    15--25 keV rebinned channel.\n\n    * 4-15 keV\n    * 15-25 keV\n    * 25-50 keV\n    * 50-100 keV\n    * 100-300 keV\n    * 300-800 keV\n    * 800-2000 keV\n\n    Examples\n    --------\n    >>> import sunpy.timeseries\n    >>> import sunpy.data.sample  # doctest: +REMOTE_DATA\n    >>> gbm = sunpy.timeseries.TimeSeries(sunpy.data.sample.GBM_TIMESERIES, source='GBMSummary')  # doctest: +REMOTE_DATA\n    >>> gbm.peek()   # doctest: +SKIP\n\n    References\n    ----------\n    * `Fermi Mission Homepage <https://fermi.gsfc.nasa.gov>`_\n    * `Fermi GBM Homepage <https://fermi.gsfc.nasa.gov/science/instruments/gbm.html>`_\n    * `Fermi Science Support Center <https://fermi.gsfc.nasa.gov/ssc/>`_\n    * `Fermi Data Product <https://fermi.gsfc.nasa.gov/ssc/data/access/>`_\n    * `GBM Instrument Papers <https://gammaray.nsstc.nasa.gov/gbm/publications/instrument_journal_gbm.html>`_\n    \"\"\"\n    # Class attribute used to specify the source class of the TimeSeries.\n    _source = 'gbmsummary'\n\n    def plot(self, axes=None, **kwargs):\n        \"\"\"\n        Plots the GBM timeseries.\n\n        Parameters\n        ----------\n        axes : `matplotlib.axes.Axes`, optional\n            The axes on which to plot the TimeSeries. Defaults to current axes.\n        **kwargs : `dict`\n            Additional plot keyword arguments that are handed to `~matplotlib.axes.Axes.plot`\n            functions.\n\n        Returns\n        -------\n        `~matplotlib.axes.Axes`\n            The plot axes.\n        \"\"\"\n        self._validate_data_for_plotting()\n        if axes is None:\n            axes = plt.gca()\n        data_lab = self.to_dataframe().columns.values\n        for d in data_lab:\n            axes.plot(self.to_dataframe().index, self.to_dataframe()[d], label=d, **kwargs)\n        axes.set_yscale(\"log\")\n        axes.set_xlabel('Start time: ' + self.to_dataframe().index[0].strftime('%Y-%m-%d %H:%M:%S UT'))\n        axes.set_ylabel('Counts/s/keV')\n        axes.legend()\n        return axes\n\n    @peek_show\n    def peek(self, title=None, **kwargs):\n        \"\"\"\n        Displays the GBM timeseries by calling\n        `~sunpy.timeseries.sources.fermi_gbm.GBMSummaryTimeSeries.plot`.\n\n        .. plot::\n\n            import sunpy.timeseries\n            import sunpy.data.sample\n            gbm = sunpy.timeseries.TimeSeries(sunpy.data.sample.GBM_TIMESERIES, source='GBMSummary')\n            gbm.peek()\n\n        Parameters\n        ----------\n        title : `str`, optional\n            The title of the plot.\n        **kwargs : `dict`\n            Additional plot keyword arguments that are handed to `~matplotlib.axes.Axes.plot`\n            functions.\n        \"\"\"\n        if title is None:\n            title = 'Fermi GBM Summary data ' + str(self.meta.get('DETNAM').values())\n        fig, ax = plt.subplots()\n        axes = self.plot(axes=ax, **kwargs)\n        axes.set_title(title)\n        fig.autofmt_xdate()\n        return fig\n\n    @classmethod\n    def _parse_file(cls, filepath):\n        \"\"\"\n        Parses a GBM CSPEC FITS file.\n\n        Parameters\n        ----------\n        filepath : `str`\n            The path to the file you want to parse.\n        \"\"\"\n        hdus = sunpy.io.read_file(filepath)\n        return cls._parse_hdus(hdus)\n\n    @classmethod\n    def _parse_hdus(cls, hdulist):\n        \"\"\"\n        Parses a GBM CSPEC `astropy.io.fits.HDUList`.\n\n        Parameters\n        ----------\n        hdulist : `str`\n            The path to the file you want to parse.\n        \"\"\"\n        header = MetaDict(OrderedDict(hdulist[0].header))\n        # these GBM files have three FITS extensions.\n        # extn1 - this gives the energy range for each of the 128 energy bins\n        # extn2 - this contains the data, e.g. counts, exposure time, time of observation\n        # extn3 - eclipse times?\n        energy_bins = hdulist[1].data\n        count_data = hdulist[2].data\n\n        # rebin the 128 energy channels into some summary ranges\n        # 4-15 keV, 15 - 25 keV, 25-50 keV, 50-100 keV, 100-300 keV, 300-800 keV, 800 - 2000 keV\n        # put the data in the units of counts/s/keV\n        summary_counts = _bin_data_for_summary(energy_bins, count_data)\n\n        # get the time information in datetime format with the correct MET adjustment\n        met_ref_time = parse_time('2001-01-01 00:00')  # Mission elapsed time\n        gbm_times = met_ref_time + TimeDelta(count_data['time'], format='sec')\n        gbm_times.precision = 9\n        gbm_times = gbm_times.isot.astype('datetime64')\n\n        column_labels = ['4-15 keV', '15-25 keV', '25-50 keV', '50-100 keV',\n                         '100-300 keV', '300-800 keV', '800-2000 keV']\n\n        # Add the units data\n        units = OrderedDict([('4-15 keV', u.ct / u.s / u.keV), ('15-25 keV', u.ct / u.s / u.keV),\n                             ('25-50 keV', u.ct / u.s / u.keV), ('50-100 keV', u.ct / u.s / u.keV),\n                             ('100-300 keV', u.ct / u.s / u.keV), ('300-800 keV', u.ct / u.s / u.keV),\n                             ('800-2000 keV', u.ct / u.s / u.keV)])\n        return pd.DataFrame(summary_counts, columns=column_labels, index=gbm_times), header, units\n\n    @classmethod\n    def is_datasource_for(cls, **kwargs):\n        \"\"\"\n        Determines if the file corresponds to a GBM summary lightcurve\n        `~sunpy.timeseries.TimeSeries`.\n        \"\"\"\n        # Check if source is explicitly assigned\n        if 'source' in kwargs.keys():\n            if kwargs.get('source', ''):\n                return kwargs.get('source', '').lower().startswith(cls._source)\n        # Check if HDU defines the source instrument\n        if 'meta' in kwargs.keys():\n            return kwargs['meta'].get('INSTRUME', '').startswith('GBM')\n\n\ndef _bin_data_for_summary(energy_bins, count_data):\n    \"\"\"\n    Rebin the 128 energy channels into some summary ranges and put the data in\n    the units of counts/s/keV.\n\n    Bin ranges used:\n    * 4-15 keV\n    * 15-25 keV\n    * 25-50 keV\n    * 50-100 keV\n    * 100-300 keV\n    * 300-800 keV\n    * 800-2000 keV\n\n    Parameters\n    ----------\n    energy_bins : `numpy.ndarray`\n        The array of energy bins to rebin.\n    count_data : `numpy.ndarray`\n        The array of count data to rebin.\n    \"\"\"\n\n    # list of energy bands to sum between\n    ebands = [4, 15, 25, 50, 100, 300, 800, 2000]\n    e_center = (energy_bins['e_min'] + energy_bins['e_max']) / 2\n    indices = [np.searchsorted(e_center, e) for e in ebands]\n\n    summary_counts = []\n    for ind_start, ind_end in zip(indices[:-1], indices[1:]):\n        # sum the counts in the energy bands, and find counts/s/keV\n        summed_counts = np.sum(count_data[\"counts\"][:, ind_start:ind_end], axis=1)\n        energy_width = (energy_bins[\"e_max\"][ind_end - 1] - energy_bins[\"e_min\"][ind_start])\n        summary_counts.append(summed_counts/energy_width/count_data[\"exposure\"])\n\n    return np.array(summary_counts).T\n\n\ndef _parse_detector(detector):\n    \"\"\"\n    Check and fix detector name strings.\n\n    Parameters\n    ----------\n    detector : `str`\n        The detector name to check.\n    \"\"\"\n    oklist = ['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9',\n              'n10', 'n11']\n    altlist = [str(i) for i in range(12)]\n    if detector in oklist:\n        return detector\n    elif detector in altlist:\n        return 'n' + detector\n    else:\n        raise ValueError('Detector string could not be interpreted')\n", "path": "sunpy/timeseries/sources/fermi_gbm.py"}]}