Repository: sunpy/sunpy

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


Discussion:

Resolving patch:
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 @@ class GBMSummaryTimeSeries(GenericTimeSeries):
 
     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 @@ def _bin_data_for_summary(energy_bins, count_data):
     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):


Test patch:
diff --git a/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_421_animators_100.json b/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_421_animators_100.json
--- a/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_421_animators_100.json
+++ b/sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_421_animators_100.json
@@ -50,7 +50,7 @@
   "sunpy.net.tests.test_helioviewer.test_download_jp2_map": "a71654a4a7ceea689869c68f4cd7f0ebfc5e970caed73f2e73e677745c99e0e0",
   "sunpy.timeseries.tests.test_timeseriesbase.test_eve_peek": "2a22f93af44971411800e4fcd0d93d2128fe2b9e6b97e576e8372c2cd12d7d78",
   "sunpy.timeseries.tests.test_timeseriesbase.test_esp_peek": "795e73a848228b2dab4ce21623c4b81b59aa817ae82f537e4f0b49447812d838",
-  "sunpy.timeseries.tests.test_timeseriesbase.test_fermi_gbm_peek": "a0addeae9ec0eda509c976e9058b2d90fa748b2c3d4a64df43b3a064d85f34d9",
+  "sunpy.timeseries.tests.test_timeseriesbase.test_fermi_gbm_peek": "2b1043c62d9a95c663e738c3557644cba112587d19f713a0b5c05138c101f440",
   "sunpy.timeseries.tests.test_timeseriesbase.test_norh_peek": "0732b7b16f8edcc7d7a52241827b79380810a70b1b8a12b9b8dd1dbe1515c6fd",
   "sunpy.timeseries.tests.test_timeseriesbase.test_goes_peek": "fe27add6ae15b3c21228ff6e575982b92ba2574cf0b4accb3e4e40b025b9247a",
   "sunpy.timeseries.tests.test_timeseriesbase.test_lyra_peek": "afae6fdd0e3943d3496ad948229606e6c36a992571d86a74235def100e255a5b",
diff --git a/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json b/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json
--- a/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json
+++ b/sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev_animators_dev.json
@@ -50,7 +50,7 @@
   "sunpy.net.tests.test_helioviewer.test_download_jp2_map": "a71654a4a7ceea689869c68f4cd7f0ebfc5e970caed73f2e73e677745c99e0e0",
   "sunpy.timeseries.tests.test_timeseriesbase.test_eve_peek": "2a22f93af44971411800e4fcd0d93d2128fe2b9e6b97e576e8372c2cd12d7d78",
   "sunpy.timeseries.tests.test_timeseriesbase.test_esp_peek": "795e73a848228b2dab4ce21623c4b81b59aa817ae82f537e4f0b49447812d838",
-  "sunpy.timeseries.tests.test_timeseriesbase.test_fermi_gbm_peek": "a0addeae9ec0eda509c976e9058b2d90fa748b2c3d4a64df43b3a064d85f34d9",
+  "sunpy.timeseries.tests.test_timeseriesbase.test_fermi_gbm_peek": "2b1043c62d9a95c663e738c3557644cba112587d19f713a0b5c05138c101f440",
   "sunpy.timeseries.tests.test_timeseriesbase.test_norh_peek": "0732b7b16f8edcc7d7a52241827b79380810a70b1b8a12b9b8dd1dbe1515c6fd",
   "sunpy.timeseries.tests.test_timeseriesbase.test_goes_peek": "fe27add6ae15b3c21228ff6e575982b92ba2574cf0b4accb3e4e40b025b9247a",
   "sunpy.timeseries.tests.test_timeseriesbase.test_lyra_peek": "afae6fdd0e3943d3496ad948229606e6c36a992571d86a74235def100e255a5b",