PyThea 0.12.0__tar.gz → 0.13.0__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 (65) hide show
  1. {pythea-0.12.0 → pythea-0.13.0}/CHANGELOG.md +14 -0
  2. {pythea-0.12.0 → pythea-0.13.0}/PKG-INFO +3 -3
  3. {pythea-0.12.0 → pythea-0.13.0}/PyThea/PyThea_app.py +68 -30
  4. {pythea-0.12.0 → pythea-0.13.0}/PyThea/_version.py +2 -2
  5. {pythea-0.12.0 → pythea-0.13.0}/PyThea/callbacks.py +40 -0
  6. {pythea-0.12.0 → pythea-0.13.0}/PyThea/config/selected_imagers.py +8 -0
  7. {pythea-0.12.0 → pythea-0.13.0}/PyThea/data/sample_data.py +9 -0
  8. pythea-0.13.0/PyThea/extensions/Parker_spirals/__init__.py +62 -0
  9. pythea-0.13.0/PyThea/extensions/Parker_spirals/utils.py +43 -0
  10. pythea-0.13.0/PyThea/extensions/hek/utils.py +118 -0
  11. {pythea-0.12.0 → pythea-0.13.0}/PyThea/geometrical_models.py +123 -30
  12. {pythea-0.12.0 → pythea-0.13.0}/PyThea/modules.py +169 -25
  13. {pythea-0.12.0 → pythea-0.13.0}/PyThea/sunpy_dev/map/maputils.py +8 -0
  14. pythea-0.13.0/PyThea/test/__init__.py +0 -0
  15. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/figure_hashes.json +2 -0
  16. pythea-0.13.0/PyThea/test/test_extension_utils.py +56 -0
  17. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/test_figures.py +71 -1
  18. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/test_utils.py +37 -5
  19. pythea-0.13.0/PyThea/utils.py +957 -0
  20. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/PKG-INFO +3 -3
  21. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/SOURCES.txt +5 -0
  22. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/requires.txt +2 -2
  23. {pythea-0.12.0 → pythea-0.13.0}/environment.yml +3 -3
  24. {pythea-0.12.0 → pythea-0.13.0}/requirements.txt +2 -2
  25. pythea-0.12.0/PyThea/utils.py +0 -562
  26. {pythea-0.12.0 → pythea-0.13.0}/.readthedocs.yaml +0 -0
  27. {pythea-0.12.0 → pythea-0.13.0}/LICENSE.md +0 -0
  28. {pythea-0.12.0 → pythea-0.13.0}/MANIFEST.in +0 -0
  29. {pythea-0.12.0 → pythea-0.13.0}/PyThea/__init__.py +0 -0
  30. {pythea-0.12.0 → pythea-0.13.0}/PyThea/config/__init__.py +0 -0
  31. {pythea-0.12.0 → pythea-0.13.0}/PyThea/config/app_styles.py +0 -0
  32. {pythea-0.12.0 → pythea-0.13.0}/PyThea/config/config_sliders.py +0 -0
  33. {pythea-0.12.0 → pythea-0.13.0}/PyThea/config/selected_bodies.py +0 -0
  34. {pythea-0.12.0 → pythea-0.13.0}/PyThea/data/__init__.py +0 -0
  35. {pythea-0.12.0 → pythea-0.13.0}/PyThea/extensions/LICENSE_gcs_python.md +0 -0
  36. {pythea-0.12.0 → pythea-0.13.0}/PyThea/extensions/__init__.py +0 -0
  37. {pythea-0.12.0 → pythea-0.13.0}/PyThea/extensions/buttons.py +0 -0
  38. {pythea-0.12.0/PyThea/sunpy_dev → pythea-0.13.0/PyThea/extensions/hek}/__init__.py +0 -0
  39. {pythea-0.12.0 → pythea-0.13.0}/PyThea/pythea_cli.py +0 -0
  40. {pythea-0.12.0/PyThea/sunpy_dev/extern → pythea-0.13.0/PyThea/sunpy_dev}/__init__.py +0 -0
  41. {pythea-0.12.0/PyThea/sunpy_dev/extern/sunkit_instruments → pythea-0.13.0/PyThea/sunpy_dev/extern}/__init__.py +0 -0
  42. {pythea-0.12.0/PyThea/sunpy_dev/extern/sunkit_instruments/aia → pythea-0.13.0/PyThea/sunpy_dev/extern/sunkit_instruments}/__init__.py +0 -0
  43. {pythea-0.12.0/PyThea/sunpy_dev/extern/sunkit_instruments/lasco → pythea-0.13.0/PyThea/sunpy_dev/extern/sunkit_instruments/aia}/__init__.py +0 -0
  44. {pythea-0.12.0 → pythea-0.13.0}/PyThea/sunpy_dev/extern/sunkit_instruments/aia/utils.py +0 -0
  45. {pythea-0.12.0/PyThea/sunpy_dev/extern/sunkit_instruments/stereo → pythea-0.13.0/PyThea/sunpy_dev/extern/sunkit_instruments/lasco}/__init__.py +0 -0
  46. {pythea-0.12.0 → pythea-0.13.0}/PyThea/sunpy_dev/extern/sunkit_instruments/lasco/utils.py +0 -0
  47. {pythea-0.12.0/PyThea/sunpy_dev/map → pythea-0.13.0/PyThea/sunpy_dev/extern/sunkit_instruments/stereo}/__init__.py +0 -0
  48. {pythea-0.12.0 → pythea-0.13.0}/PyThea/sunpy_dev/extern/sunkit_instruments/stereo/utils.py +0 -0
  49. {pythea-0.12.0/PyThea/test → pythea-0.13.0/PyThea/sunpy_dev/map}/__init__.py +0 -0
  50. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/Pythea_test.py +0 -0
  51. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/conftest.py +0 -0
  52. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/test_geometrical_models.py +0 -0
  53. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/test_imports.py +0 -0
  54. {pythea-0.12.0 → pythea-0.13.0}/PyThea/test/test_remote_clients.py +0 -0
  55. {pythea-0.12.0 → pythea-0.13.0}/PyThea/version.py +0 -0
  56. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/.DS_Store +0 -0
  57. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/dependency_links.txt +0 -0
  58. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/entry_points.txt +0 -0
  59. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/not-zip-safe +0 -0
  60. {pythea-0.12.0 → pythea-0.13.0}/PyThea.egg-info/top_level.txt +0 -0
  61. {pythea-0.12.0 → pythea-0.13.0}/README.md +0 -0
  62. {pythea-0.12.0 → pythea-0.13.0}/README_pypi.md +0 -0
  63. {pythea-0.12.0 → pythea-0.13.0}/pyproject.toml +0 -0
  64. {pythea-0.12.0 → pythea-0.13.0}/setup.cfg +0 -0
  65. {pythea-0.12.0 → pythea-0.13.0}/setup.py +0 -0
@@ -1,3 +1,17 @@
1
+ # v0.13.0 (14-Jul-2024)
2
+
3
+ ## Features
4
+ - Adds imaging data from PSP/WISPR.
5
+ - Implements new feature of Parker spiral and HEK events visualization on the images.
6
+ - Adds a method to get directly the geometrical model from model_fittings.
7
+ - Adds tests for extensions, WISPR imaging.
8
+ - Adds and improves docstrings in utilities, modules, and geometrical models.
9
+ - Adds GCS and kinematic plots in documentation
10
+ - Adds function reference in the documentation
11
+
12
+ ## Minor Changes
13
+ - Changes some optional inputs for get_hek_flare, make_figure, plot_bodies, and plot_solar_reference_lines to be better utilised.
14
+
1
15
  # v0.12.0 (09-Jun-2024)
2
16
 
3
17
  ## Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyThea
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: PyThea: A software package to reconstruct the 3D structure of CMEs and shock waves
5
5
  Home-page: https://github.com/AthKouloumvakos/PyThea
6
6
  Author: Athanasios Kouloumvakos
@@ -20,11 +20,11 @@ Classifier: Topic :: Scientific/Engineering :: Physics
20
20
  Requires-Python: >=3.8, <3.11
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE.md
23
- Requires-Dist: numpy
23
+ Requires-Dist: numpy==1.26.4
24
24
  Requires-Dist: pandas
25
25
  Requires-Dist: scipy
26
26
  Requires-Dist: aiapy
27
- Requires-Dist: astropy
27
+ Requires-Dist: astropy==6.0.0
28
28
  Requires-Dist: astroquery
29
29
  Requires-Dist: jplephem
30
30
  Requires-Dist: numexpr
@@ -82,7 +82,8 @@ def footer_text():
82
82
  """)
83
83
  st.info('''
84
84
  More imaging data have been added:
85
- - SDO/AIA images from 211A channel have been added.
85
+ - SDO/AIA images from 211A channel.
86
+ - PSP/WISPR inner and outer telescope images.
86
87
  ''', icon='ℹ️')
87
88
  st.warning('''
88
89
  **NOTE: From PyThea >0.8.1 the JSON fitting files will be slightly different from the old ones.**
@@ -197,7 +198,7 @@ def run():
197
198
  key='imaging_time_range')
198
199
  select_timerange_form.form_submit_button(label='Submit',
199
200
  on_click=delete_from_state,
200
- kwargs={'vars': ['map', 'map_', 'imagers_list_']})
201
+ kwargs={'vars': ['map', 'map_', 'imagers_list_', 'hek_responses']})
201
202
 
202
203
  with st.sidebar.expander('Processing Options'):
203
204
  procoption_container = st.container()
@@ -222,32 +223,59 @@ def run():
222
223
  plotviewopt_container = st.container()
223
224
  plotviewopt_container.checkbox('Clip plot on image limits', value=True, key='clip_model')
224
225
  plt_supp_imagers = plotviewopt_container.checkbox('Supplementary Imaging', value=False)
225
- plotviewopt_container.checkbox('View Bodies or s/c', key='star_field')
226
- if st.session_state.star_field:
227
- plotviewopt_container.multiselect('Select Bodies', options=selected_bodies.bodies_dict.keys(),
228
- default=['Mercury', 'Venus', 'Jupiter'],
229
- key='bodies_list')
230
- plotviewopt_container.checkbox('View Limb and Meridians', key='plot_solar_reference_lines_')
231
- if st.session_state.plot_solar_reference_lines_:
232
- markdown = '''
233
- **Limb**: plots the solar limb as observed # noqa
234
- for the selected observers. # noqa
235
- **Central Meridian**: plots the central meridian # noqa
236
- observed for the selected observers. # noqa
237
- **CR Meridan+Equator**: plots the primary meridian # noqa
238
- and equator of Carrington coordinate system. # noqa
239
- '''.strip()
240
- plotviewopt_container.selectbox('Select plot option',
241
- options=['Limb from Obs.', 'Central Meridian from Obs.', 'Carr. Prime Meridian+Solar Equator',
242
- 'Stonyhurst Grid', 'Carrington Grid'],
243
- help=markdown,
244
- key='plot_solar_reference_lines_mode')
245
- disabled = False if st.session_state.plot_solar_reference_lines_mode in ['Limb from Obs.', 'Central Meridian from Obs.'] else True
246
- plotviewopt_container.multiselect('Select Bodies',
247
- label_visibility='collapsed',
248
- options=selected_bodies.bodies_dict.keys(),
249
- default=['Earth'], disabled=disabled,
250
- key='plot_solar_reference_lines_bodies_list')
226
+
227
+ #############################################################
228
+ # Magnetic Connectivity
229
+ st.sidebar.markdown('## Overlays')
230
+ with st.sidebar.expander('Bodies or s/c Location'):
231
+ bodies_container = st.container()
232
+ bodies_container.checkbox('View Bodies or s/c', key='star_field')
233
+ bodies_container.multiselect('Select Bodies', options=selected_bodies.bodies_dict.keys(),
234
+ default=['Mercury', 'Venus', 'Jupiter'],
235
+ key='bodies_list',
236
+ disabled=not st.session_state.star_field)
237
+ with st.sidebar.expander('Limb and Meridians'):
238
+ limb_meridians_container = st.container()
239
+ limb_meridians_container.checkbox('View Limb and Meridians', key='plot_solar_reference_lines_')
240
+ markdown = '''
241
+ **Limb**: plots the solar limb as observed
242
+ for the selected observers.
243
+ **Central Meridian**: plots the central meridian
244
+ observed for the selected observers.
245
+ **CR Meridan+Equator**: plots the primary meridian
246
+ and equator of Carrington coordinate system.
247
+ '''.strip()
248
+ limb_meridians_container.selectbox('Select plot option',
249
+ options=['Limb from Obs.', 'Central Meridian from Obs.', 'Carr. Prime Meridian+Solar Equator',
250
+ 'Stonyhurst Grid', 'Carrington Grid'],
251
+ help=markdown,
252
+ key='plot_solar_reference_lines_mode',
253
+ disabled=not st.session_state.plot_solar_reference_lines_)
254
+ disabled = False if (st.session_state.plot_solar_reference_lines_) and \
255
+ (st.session_state.plot_solar_reference_lines_mode in ['Limb from Obs.', 'Central Meridian from Obs.']) else True
256
+ limb_meridians_container.multiselect('Select Bodies',
257
+ label_visibility='collapsed',
258
+ options=selected_bodies.bodies_dict.keys(),
259
+ default=['Earth'], disabled=disabled,
260
+ key='plot_solar_reference_lines_bodies_list')
261
+ with st.sidebar.expander('Magnetic Connectivity'):
262
+ connectivity_container = st.container()
263
+ connectivity_container.checkbox('Plot Parker spirals', key='plot_parker_spirals')
264
+ connectivity_container.multiselect('Select bodies/spacecraft', options=selected_bodies.bodies_dict.keys(),
265
+ default=['Earth'], key='mag_bodies_list',
266
+ disabled=not st.session_state.plot_parker_spirals)
267
+ st.session_state.sw_speed_select = {}
268
+ for body in st.session_state.mag_bodies_list:
269
+ connectivity_container.number_input(f'{body} solar wind speed', min_value=200, max_value=800, value=350, step=50,
270
+ key=f'sw_speed_select_{body}',
271
+ disabled=not st.session_state.plot_parker_spirals)
272
+ st.session_state.sw_speed_select[body] = st.session_state[f'sw_speed_select_{body}'] * (u.km/u.second)
273
+ with st.sidebar.expander('HEK feature/events'):
274
+ hek_container = st.container()
275
+ hek_container.checkbox('Plot HEK feature/events', key='plot_hek', on_change=delete_from_state,
276
+ kwargs={'vars': ['hek_responses', ]})
277
+ hek_container.multiselect('Select HEK feature/events', options=['Active Regions', 'Coronal Holes', 'Flares'],
278
+ default=['Flares'], key='hek_list', disabled=not st.session_state.plot_hek)
251
279
 
252
280
  #############################################################
253
281
  # Download and Process the Images
@@ -299,13 +327,14 @@ def run():
299
327
  col1, col2 = st.columns((1, 3))
300
328
  imager_select = col1.selectbox('Select an imager',
301
329
  options=st.session_state.imagers_list_,
302
- on_change=delete_from_state, kwargs={'vars': ['clim', 'clim_manual_low', 'clim_manual_high']})
330
+ on_change=delete_from_state, kwargs={'vars': ['clim', 'clim_manual_low', 'clim_manual_high', 'hek_responses']})
303
331
 
304
332
  maps_date = [getattr(maps, 'date_average', None) or getattr(maps, 'date', None) for maps in st.session_state.map[imager_select]]
305
333
  if len(maps_date) > 1:
306
334
  running_map_date = col2.select_slider('Slide to the image time',
307
335
  options=maps_date, value=maps_date[0],
308
- key='running_map_date')
336
+ key='running_map_date',
337
+ on_change=delete_from_state, kwargs={'vars': ['hek_responses']})
309
338
  else:
310
339
  running_map_date = maps_date[0]
311
340
 
@@ -368,6 +397,15 @@ def run():
368
397
  fig, axis = figure_streamlit(st, running_map, image_mode, imager_select, model)
369
398
  st.pyplot(fig)
370
399
 
400
+ if st.session_state.plot_hek and st.session_state.hek_responses['Flares']:
401
+ st.markdown('**HEK Flare List:**')
402
+ st.write(st.session_state.hek_responses['Flares'].to_pandas())
403
+ st.markdown('*Flares with no location have been removed from the table.')
404
+ if st.session_state.plot_hek and st.session_state.hek_responses['Active Regions']:
405
+ st.markdown('**HEK Active Regions List:**')
406
+ st.write(st.session_state.hek_responses['Active Regions'].to_pandas())
407
+ st.markdown('*Active Regions without NOAA number have been removed from the table.')
408
+
371
409
  if plt_supp_imagers:
372
410
  if len(st.session_state.imagers_list_) < 3:
373
411
  other_element = [element for element in st.session_state.imagers_list_ if element != imager_select][0]
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.12.0'
16
- __version_tuple__ = version_tuple = (0, 12, 0)
15
+ __version__ = version = '0.13.0'
16
+ __version_tuple__ = version_tuple = (0, 13, 0)
@@ -3,10 +3,24 @@ from sunpy.coordinates import frames
3
3
 
4
4
 
5
5
  def load_or_delete_fittings(st):
6
+ """
7
+ Load or delete fitting parameters from the model fittings based on user selection.
8
+
9
+ Parameters
10
+ ----------
11
+ st : Streamlit session state object
12
+ Streamlit session state object for managing UI components.
13
+
14
+ Returns
15
+ -------
16
+ None
17
+ """
6
18
  selected_row = str(st.session_state.fitting_select)
7
19
  dataframe = st.session_state.model_fittings.parameters
20
+
8
21
  if st.session_state.fit_action == 'Select':
9
22
  pass
23
+
10
24
  elif st.session_state.fit_action == 'Load':
11
25
  if st.session_state.coord_system == 'HGS':
12
26
  st.session_state.longit = float(dataframe.loc[selected_row, 'hgln'])
@@ -27,6 +41,7 @@ def load_or_delete_fittings(st):
27
41
  # del st.session_state[key]
28
42
  st.session_state[key] = float(dataframe.loc[selected_row, key])
29
43
  del st.session_state.fit_action
44
+
30
45
  elif st.session_state.fit_action == 'Delete':
31
46
  st.session_state.model_fittings.parameters = dataframe.drop(st.session_state.fitting_select)
32
47
  del st.session_state.fitting_select
@@ -36,6 +51,18 @@ def load_or_delete_fittings(st):
36
51
 
37
52
 
38
53
  def change_long_lat_sliders(st):
54
+ """
55
+ Update longitude and latitude sliders based on the selected coordinate system.
56
+
57
+ Parameters
58
+ ----------
59
+ st : Streamlit session state object
60
+ Streamlit session state object for managing UI components.
61
+
62
+ Returns
63
+ -------
64
+ None
65
+ """
39
66
  if st.session_state.coord_system == 'HGS':
40
67
  center_ = st.session_state.center.transform_to(frames.HeliographicStonyhurst)
41
68
  elif st.session_state.coord_system == 'HGC':
@@ -46,9 +73,22 @@ def change_long_lat_sliders(st):
46
73
 
47
74
 
48
75
  def change_fitting_sliders(st):
76
+ """
77
+ Update fitting sliders based on the selected fitting method.
78
+
79
+ Parameters
80
+ ----------
81
+ st : Streamlit session state object
82
+ Streamlit session state object for managing UI components.
83
+
84
+ Returns
85
+ -------
86
+ None
87
+ """
49
88
  st.session_state.startup['fitting'] = False
50
89
  fit_opt = st.session_state.fit_args_prime
51
90
  st.session_state.fit_mode = fit_opt['type']
91
+
52
92
  if fit_opt['type'] == 'polynomial':
53
93
  st.session_state.polyfit_order = fit_opt['order']
54
94
  elif fit_opt['type'] == 'spline':
@@ -63,3 +63,11 @@ imager_dict['HI2A'] = {'fido': (a.Source('STEREO_A'), a.Detector.hi2),
63
63
  imager_dict['HI2B'] = {'fido': (a.Source('STEREO_B'), a.Detector.hi2),
64
64
  'process': {'dimensions': (1024*u.pixel, 1024*u.pixel), 'superpixel': 2},
65
65
  'source': 'STEREO_B', 'instrument': 'SECCHI', 'detector': 'HI2'}
66
+
67
+ imager_dict['WISPR1'] = {'fido': (a.Instrument.wispr, a.Detector.inner),
68
+ 'process': {'dimensions': (960*u.pixel, 1024*u.pixel), 'processing_level': 3, 'superpixel': 2},
69
+ 'source': 'PSP', 'instrument': 'WISPR', 'detector': 'Inner'}
70
+
71
+ imager_dict['WISPR2'] = {'fido': (a.Instrument.wispr, a.Detector.outer),
72
+ 'process': {'dimensions': (960*u.pixel, 1024*u.pixel), 'processing_level': 3, 'superpixel': 2},
73
+ 'source': 'PSP', 'instrument': 'WISPR', 'detector': 'Outer'}
@@ -26,6 +26,15 @@ stereo_sample_data = pooch.create(
26
26
  },
27
27
  )
28
28
 
29
+ wispr_sample_data = pooch.create(
30
+ path=os.path.join(database_dir, 'sample_data'), # The cache folder
31
+ base_url=f'{github_main_url}/raw/main/data/', # The remote data url on Github
32
+ registry={
33
+ 'psp_l3_wispr_20210808t103707_v1_1211.fits': 'sha256:85035693c4677e8fa8a3e61b8051d6f0a46e96806b5b0cbdb3efa77652308bda',
34
+ 'psp_l3_wispr_20210808t104010_v1_2222.fits': 'sha256:5e55e17f1aed7dc96f88b79f092947c58e6ee0def1b72822dbf1a696d8df7e6f'
35
+ },
36
+ )
37
+
29
38
  json_fitting_file_sample_data = pooch.create(
30
39
  path=os.path.join(database_dir, 'sample_data'), # The cache folder
31
40
  base_url=f'{github_main_url}/raw/main/data/', # The remote data url on Github
@@ -0,0 +1,62 @@
1
+ import astropy.units as u
2
+ import numpy as np
3
+ from astropy.coordinates import SkyCoord
4
+
5
+
6
+ def spiral(pos, sw_speed, date, omega=360. * u.degree / (25.38 * 24 * 60 * 60 * u.second)):
7
+ """
8
+ Calculates the Parker spiral coordinates based on the input position, solar wind speed, and date.
9
+
10
+ Parameters
11
+ ----------
12
+ pos : astropy.coordinates.SkyCoord
13
+ The initial position in heliographic Carrington coordinates.
14
+ sw_speed : astropy.units.Quantity
15
+ The solar wind speed (should be in velocity units, e.g., km/s).
16
+ date : astropy.time.Time
17
+ The observation time.
18
+ omega : astropy.units.Quantity, optional
19
+ The solar rotation rate in radians per second (default is sidereal period).
20
+
21
+ Returns
22
+ -------
23
+ astropy.coordinates.SkyCoord
24
+ The calculated spiral coordinates in heliographic Carrington coordinates.
25
+ """
26
+ r = np.arange(1, pos.radius.to_value(u.R_sun), 0.1)
27
+ r = np.append(r, pos.radius.to_value(u.R_sun)) * u.R_sun
28
+ alpha = omega * ((pos.radius - r) / sw_speed)
29
+ hg_coord = SkyCoord(pos.lon + alpha, pos.lat, r,
30
+ frame='heliographic_carrington',
31
+ observer='earth',
32
+ obstime=date)
33
+ return hg_coord
34
+
35
+
36
+ def footpoint(pos, sw_speed, date, r=1*u.R_sun, omega=360. * u.degree / (25.38 * 24 * 60 * 60 * u.second)):
37
+ """
38
+ Calculates the footpoint location based on the input position, solar wind speed, and date.
39
+
40
+ Parameters
41
+ ----------
42
+ pos : astropy.coordinates.SkyCoord
43
+ The initial position in heliographic Carrington coordinates.
44
+ sw_speed : astropy.units.Quantity
45
+ The solar wind speed (should be in velocity units, e.g., km/s).
46
+ date : astropy.time.Time
47
+ The observation time.
48
+ r : astropy.units.Quantity, optional
49
+ The radial distance for the footpoint (default is 1 solar radius).
50
+ omega : astropy.units.Quantity, optional
51
+ The solar rotation rate in radians per second (default is 2.7e-6 rad/s).
52
+
53
+ Returns
54
+ -------
55
+ astropy.coordinates.SkyCoord
56
+ The calculated footpoint coordinates in heliographic Carrington coordinates.
57
+ """
58
+ alpha = omega * ((pos.radius - r) / sw_speed)
59
+ hg_coord = SkyCoord(pos.lon + alpha, pos.lat, r,
60
+ frame='heliographic_carrington',
61
+ observer='earth', obstime=date)
62
+ return hg_coord
@@ -0,0 +1,43 @@
1
+ import astropy.units as u
2
+ from sunpy.coordinates import frames, get_horizons_coord
3
+
4
+ from PyThea.config.selected_bodies import bodies_dict
5
+ from PyThea.extensions import Parker_spirals
6
+
7
+
8
+ def plot_parker_spiral(axis, map, bodies, sw_speed=350 * (u.km / u.second)):
9
+ """
10
+ Plot Parker spiral for specified bodies on the given map axis.
11
+
12
+ Parameters
13
+ ----------
14
+ axis : matplotlib.axes.Axes
15
+ The axis on which to plot the Parker spiral.
16
+ map : sunpy.map.Map
17
+ The solar map on which the Parker spiral will be plotted.
18
+ bodies : list of str
19
+ List of bodies for which to plot the Parker spiral.
20
+ sw_speed : Quantity, optional
21
+ Solar wind speed for the Parker spiral (default: 350 km/s).
22
+
23
+ Returns
24
+ -------
25
+ None
26
+ """
27
+ for body in bodies:
28
+ pos = get_horizons_coord(bodies_dict[body][0], map.date_average, 'id')
29
+ pos = pos.transform_to(frames.HeliographicCarrington(observer='Earth', obstime=map.date_average))
30
+
31
+ spiral_coord = Parker_spirals.spiral(pos, sw_speed[body], map.date_average)
32
+
33
+ hpc_frame = frames.Helioprojective(observer=map.observer_coordinate, obstime=map.date_average)
34
+
35
+ spiral_coord_visible_index = spiral_coord.transform_to(hpc_frame).is_visible()
36
+
37
+ axis.plot_coord(spiral_coord[spiral_coord_visible_index], markersize=0, linewidth=1, color=bodies_dict[body][1])
38
+ if spiral_coord_visible_index[1]:
39
+ axis.plot_coord(spiral_coord[0], markersize=16, linewidth=10, marker='+', color=bodies_dict[body][1])
40
+ else:
41
+ axis.plot_coord(spiral_coord[0], markersize=16, linewidth=10, marker='+', color='white')
42
+
43
+ axis.plot_coord(spiral_coord[0], markersize=3, linewidth=4, marker='s', color=bodies_dict[body][1])
@@ -0,0 +1,118 @@
1
+ import astropy.units as u
2
+ import numpy as np
3
+ from astropy.coordinates import SkyCoord
4
+ from astropy.time import Time, TimeDelta
5
+ from sunpy.coordinates import frames
6
+ from sunpy.net import attrs as a
7
+ from sunpy.net import hek
8
+ from sunpy.physics.differential_rotation import solar_rotate_coordinate
9
+ from sunpy.time import parse_time
10
+
11
+
12
+ def plot_hek(axis, map, mode, time_range=[-2, 2], hek_responses=None):
13
+ """
14
+ Plots HEK (Heliophysics Event Knowledgebase) events on a given axis.
15
+
16
+ This function fetches and plots HEK events, such as Active Regions, Coronal Holes, or Flares, on a provided
17
+ matplotlib axis using data from a SunPy map object.
18
+
19
+ Parameters
20
+ ----------
21
+ axis : matplotlib.axes._subplots.AxesSubplot
22
+ The matplotlib axis to plot on.
23
+ map : sunpy.map.Map
24
+ The SunPy map object containing the data.
25
+ mode : str
26
+ The type of HEK events to plot ('Active Regions', 'Coronal Holes', or 'Flares').
27
+ time_range : list, optional
28
+ Time range in hours around the map date to search for events (default is [-2, 2]).
29
+ hek_responses : dict, optional
30
+ Pre-fetched HEK responses (default is None).
31
+
32
+ Returns
33
+ -------
34
+ responses: list
35
+ The list of HEK event responses.
36
+ """
37
+ hek_client = hek.HEKClient()
38
+ start_time = map.date_average + TimeDelta(time_range[0] * u.hour)
39
+ end_time = map.date_average + TimeDelta(time_range[1] * u.hour)
40
+
41
+ if mode == 'Active Regions':
42
+ if hek_responses is None or not hek_responses['Active Regions']:
43
+ responses = hek_client.search(a.Time(start_time, end_time),
44
+ a.hek.AR, a.hek.FRM.Name == 'HMI SHARP')
45
+
46
+ responses.keep_columns(['ar_noaanum', 'ar_mcintoshcls', 'ar_mtwilsoncls', 'hgs_x', 'hgs_y', 'event_starttime'])
47
+ indx = [i for i, x in enumerate(responses['ar_noaanum']) if x is None]
48
+ responses.remove_rows(indx)
49
+ responses = responses[['ar_noaanum', 'ar_mcintoshcls', 'ar_mtwilsoncls', 'hgs_x', 'hgs_y', 'event_starttime']]
50
+ dates = Time(responses['event_starttime'])
51
+ time_diff = np.abs(dates - map.date_average)
52
+ time_diff_seconds = np.array([td.sec for td in time_diff])
53
+ if len(np.unique(time_diff_seconds)) > 1:
54
+ responses.remove_rows(np.where(time_diff_seconds == np.max(time_diff_seconds)))
55
+
56
+ responses_ = hek_client.search(a.Time(start_time, end_time),
57
+ a.hek.AR, a.hek.FRM.Name == 'NOAA SWPC Observer')
58
+ responses_.keep_columns(['ar_noaanum', 'ar_mcintoshcls', 'ar_mtwilsoncls', 'hgs_x', 'hgs_y'])
59
+
60
+ responses['ar_mcintoshcls'] = np.full(responses['ar_noaanum'].shape[0], None)
61
+ responses['ar_mtwilsoncls'] = np.full(responses['ar_noaanum'].shape[0], None)
62
+
63
+ for rn, clm, clw in zip(responses_['ar_noaanum'], responses_['ar_mcintoshcls'], responses_['ar_mtwilsoncls']):
64
+ i = np.argwhere(responses['ar_noaanum'] == rn)
65
+ responses['ar_mcintoshcls'][i] = clm
66
+ responses['ar_mtwilsoncls'][i] = clw
67
+ print(responses)
68
+ else:
69
+ responses = hek_responses['Active Regions']
70
+
71
+ for i, response in enumerate(responses):
72
+ axis.annotate(i, (float(response['hgs_x']), float(response['hgs_y'])),
73
+ xytext=(float(response['hgs_x']), float(response['hgs_y'])),
74
+ xycoords=axis.get_transform('heliographic_stonyhurst'),
75
+ backgroundcolor='none',
76
+ color='tab:blue',
77
+ fontsize=8,
78
+ horizontalalignment='center', verticalalignment='center')
79
+ elif mode == 'Coronal Holes':
80
+ if hek_responses is None or not hek_responses['Coronal Holes']:
81
+ responses = hek_client.search(a.Time(start_time, end_time),
82
+ a.hek.CH, a.hek.FRM.Name == 'SPoCA')
83
+ else:
84
+ responses = hek_responses['Coronal Holes']
85
+
86
+ for response in responses:
87
+ p1 = response['hpc_boundcc'][9:-2]
88
+ p2 = p1.split(',')
89
+ p3 = [v.split(' ') for v in p2]
90
+ ch_date = parse_time(response['event_starttime'])
91
+ ch_boundary = SkyCoord(
92
+ [(float(v[0]), float(v[1])) * u.arcsec for v in p3],
93
+ obstime=ch_date, observer='earth',
94
+ frame=frames.Helioprojective)
95
+ rotated_ch_boundary = solar_rotate_coordinate(ch_boundary, time=map.date_average)
96
+ axis.plot_coord(rotated_ch_boundary, color='c')
97
+ elif mode == 'Flares':
98
+ if hek_responses is None or not hek_responses['Flares']:
99
+ responses = hek_client.search(a.Time(start_time, end_time),
100
+ a.hek.FL, a.hek.FRM.Name == 'SWPC')
101
+ responses.keep_columns(['event_starttime', 'event_peaktime', 'fl_goescls', 'hgs_x', 'hgs_y', 'ar_noaanum'])
102
+ responses.remove_rows(np.where((responses['hgs_x'] == 0) & (responses['hgs_y'] == 0)))
103
+ responses = responses[['event_starttime', 'event_peaktime', 'fl_goescls', 'hgs_x', 'hgs_y', 'ar_noaanum']]
104
+ else:
105
+ responses = hek_responses['Flares']
106
+
107
+ for i, response in enumerate(responses):
108
+ axis.annotate(i, (float(response['hgs_x']), float(response['hgs_y'])),
109
+ xytext=(float(response['hgs_x']), float(response['hgs_y'])),
110
+ xycoords=axis.get_transform('heliographic_stonyhurst'),
111
+ backgroundcolor='none',
112
+ color='tab:red',
113
+ fontsize=8,
114
+ horizontalalignment='center', verticalalignment='center')
115
+ else:
116
+ raise ValueError(f"Unsupported mode: {mode}. Use 'Coronal Holes' or 'Flares'.")
117
+
118
+ return responses