PyThea 0.9.1__tar.gz → 0.11.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 (60) hide show
  1. {PyThea-0.9.1 → pythea-0.11.0}/CHANGELOG.md +33 -0
  2. {PyThea-0.9.1/PyThea.egg-info → pythea-0.11.0}/PKG-INFO +5 -3
  3. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/PyThea_app.py +103 -53
  4. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/_version.py +2 -2
  5. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/callbacks.py +2 -1
  6. pythea-0.11.0/PyThea/data/sample_data.py +35 -0
  7. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/geometrical_models.py +12 -6
  8. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/modules.py +25 -11
  9. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/pythea_cli.py +21 -2
  10. pythea-0.11.0/PyThea/sunpy_dev/extern/sunkit_instruments/aia/utils.py +14 -0
  11. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/sunpy_dev/map/maputils.py +18 -11
  12. pythea-0.11.0/PyThea/test/Pythea_test.py +24 -0
  13. pythea-0.11.0/PyThea/test/__init__.py +0 -0
  14. pythea-0.11.0/PyThea/test/figure_hashes.json +6 -0
  15. pythea-0.11.0/PyThea/test/test_figures.py +169 -0
  16. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/test/test_imports.py +4 -1
  17. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/test/test_utils.py +47 -3
  18. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/utils.py +57 -12
  19. {PyThea-0.9.1 → pythea-0.11.0/PyThea.egg-info}/PKG-INFO +5 -3
  20. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/SOURCES.txt +4 -0
  21. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/requires.txt +4 -2
  22. {PyThea-0.9.1 → pythea-0.11.0}/environment.yml +4 -2
  23. {PyThea-0.9.1 → pythea-0.11.0}/requirements.txt +4 -2
  24. PyThea-0.9.1/PyThea/data/sample_data.py +0 -15
  25. PyThea-0.9.1/PyThea/test/Pythea_test.py +0 -8
  26. {PyThea-0.9.1 → pythea-0.11.0}/LICENSE.md +0 -0
  27. {PyThea-0.9.1 → pythea-0.11.0}/MANIFEST.in +0 -0
  28. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/__init__.py +0 -0
  29. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/config/__init__.py +0 -0
  30. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/config/app_styles.py +0 -0
  31. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/config/config_sliders.py +0 -0
  32. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/config/selected_bodies.py +0 -0
  33. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/config/selected_imagers.py +0 -0
  34. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/data/__init__.py +0 -0
  35. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/extensions/LICENSE_gcs_python.md +0 -0
  36. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/extensions/__init__.py +0 -0
  37. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/extensions/buttons.py +0 -0
  38. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/sunpy_dev/__init__.py +0 -0
  39. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/sunpy_dev/extern/__init__.py +0 -0
  40. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/sunpy_dev/extern/sunkit_instruments/__init__.py +0 -0
  41. {PyThea-0.9.1/PyThea/sunpy_dev/extern/sunkit_instruments/lasco → pythea-0.11.0/PyThea/sunpy_dev/extern/sunkit_instruments/aia}/__init__.py +0 -0
  42. {PyThea-0.9.1/PyThea/sunpy_dev/extern/sunkit_instruments/stereo → pythea-0.11.0/PyThea/sunpy_dev/extern/sunkit_instruments/lasco}/__init__.py +0 -0
  43. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/sunpy_dev/extern/sunkit_instruments/lasco/utils.py +0 -0
  44. {PyThea-0.9.1/PyThea/sunpy_dev/map → pythea-0.11.0/PyThea/sunpy_dev/extern/sunkit_instruments/stereo}/__init__.py +0 -0
  45. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/sunpy_dev/extern/sunkit_instruments/stereo/utils.py +0 -0
  46. {PyThea-0.9.1/PyThea/test → pythea-0.11.0/PyThea/sunpy_dev/map}/__init__.py +0 -0
  47. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/test/conftest.py +0 -0
  48. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/test/test_geometrical_models.py +0 -0
  49. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/test/test_remote_clients.py +0 -0
  50. {PyThea-0.9.1 → pythea-0.11.0}/PyThea/version.py +0 -0
  51. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/.DS_Store +0 -0
  52. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/dependency_links.txt +0 -0
  53. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/entry_points.txt +0 -0
  54. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/not-zip-safe +0 -0
  55. {PyThea-0.9.1 → pythea-0.11.0}/PyThea.egg-info/top_level.txt +0 -0
  56. {PyThea-0.9.1 → pythea-0.11.0}/README.md +0 -0
  57. {PyThea-0.9.1 → pythea-0.11.0}/README_pypi.md +0 -0
  58. {PyThea-0.9.1 → pythea-0.11.0}/pyproject.toml +0 -0
  59. {PyThea-0.9.1 → pythea-0.11.0}/setup.cfg +0 -0
  60. {PyThea-0.9.1 → pythea-0.11.0}/setup.py +0 -0
@@ -1,3 +1,36 @@
1
+ # v0.11.0 (17-April-2024)
2
+
3
+ ## Features
4
+ - Adds tests for ellipsoid location on AIA and STEREO COR images.
5
+ - Adds test_load_fitting_json_file to check the fitting file load and save.
6
+ - Improve PyThea tests on cli.
7
+
8
+ ## Major Changes
9
+ - Change the inputs for download_fits from string dates to timerange.
10
+
11
+ # v0.10.0 (17-April-2024)
12
+
13
+ ## Features
14
+ - Option added to choose the step for Running Difference or the background image for the Base Difference image processing.
15
+ - Fetures added in the app to plot limb or meridians from different observers and grid.
16
+ - Feture added in the app for manual change of the images colorbar limits.
17
+ - Feture added in the app to use median filter images processing.
18
+ - Tests added: A test to verify the existence of Pythea's database directory
19
+
20
+ ## Major Changes
21
+ - Updates sunpy dependencies to sunpy 5.1.2
22
+
23
+ ## Minor Changes
24
+ - Improvements on the pipeline of image download and processing in the app.
25
+ - Improvements and simplifications on the figures production in the app.
26
+ - Values of the colormap limits in the app are stored and are reused when changing the imager.
27
+
28
+ ## Bug Fixes
29
+ - Fixes a bug with test_database_dir_exists.
30
+ - Fixes a bug when corrupted fits files loaded and imager load skipped.
31
+ - Fixes a bug with AIA visuallization from missing date_average.
32
+ - Fixes a bug with supplementary imaging selection.
33
+
1
34
  # v0.9.1 (5-March-2024)
2
35
 
3
36
  ## Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyThea
3
- Version: 0.9.1
3
+ Version: 0.11.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
@@ -26,9 +26,10 @@ Requires-Dist: scipy
26
26
  Requires-Dist: aiapy
27
27
  Requires-Dist: astropy
28
28
  Requires-Dist: astroquery
29
+ Requires-Dist: jplephem
29
30
  Requires-Dist: numexpr
30
- Requires-Dist: sunpy==4.1.0
31
- Requires-Dist: parfive==1.5.1
31
+ Requires-Dist: sunpy==5.1.2
32
+ Requires-Dist: parfive==2.1.0
32
33
  Requires-Dist: pooch
33
34
  Requires-Dist: matplotlib
34
35
  Requires-Dist: seaborn
@@ -42,6 +43,7 @@ Requires-Dist: tqdm
42
43
  Requires-Dist: pyvista
43
44
  Requires-Dist: pytest-astropy
44
45
  Requires-Dist: pytest-sugar
46
+ Requires-Dist: pytest-mpl
45
47
 
46
48
  # PyThea: A software package to reconstruct the 3D structure of CMEs and shock waves
47
49
 
@@ -18,23 +18,27 @@
18
18
  """
19
19
 
20
20
 
21
+ import datetime
22
+ from copy import copy
23
+
21
24
  import astropy.units as u
22
25
  import numpy as np
23
26
  import stqdm # See also https://github.com/tqdm/tqdm
24
27
  import streamlit as st
25
28
  from astropy.coordinates import Distance, SkyCoord, SphericalRepresentation
26
29
  from sunpy.coordinates import frames
30
+ from sunpy.net import attrs as a
27
31
 
28
32
  from PyThea.callbacks import load_or_delete_fittings
29
33
  from PyThea.config import (app_styles, config_sliders, selected_bodies,
30
34
  selected_imagers)
31
35
  from PyThea.geometrical_models import ellipsoid, gcs, spheroid
32
- from PyThea.modules import (date_and_event_selection, final_parameters_gmodel,
36
+ from PyThea.modules import (date_and_event_selection, figure_streamlit,
37
+ final_parameters_gmodel,
33
38
  fitting_and_slider_options_container,
34
- fitting_sliders, maps_clims)
35
- from PyThea.sunpy_dev.map.maputils import get_closest
36
- from PyThea.utils import (download_fits, make_figure, model_fittings,
37
- plot_bodies, plot_fitting_model,
39
+ fitting_sliders)
40
+ from PyThea.sunpy_dev.map.maputils import get_closest, maps_clims
41
+ from PyThea.utils import (download_fits, model_fittings, plot_fitting_model,
38
42
  single_imager_maps_process)
39
43
  from PyThea.version import version
40
44
 
@@ -185,24 +189,58 @@ def run():
185
189
  if 'imagers_list_' not in st.session_state:
186
190
  # imagers_list_ is used later when we download or filter the images
187
191
  st.session_state['imagers_list_'] = []
188
- image_mode = procoption_container.selectbox('Map sequence processing',
192
+ image_mode = procoption_container.selectbox('Map Sequence Processing',
189
193
  options=['Running Diff.', 'Base Diff.', 'Plain'], key='image_mode',
190
- on_change=delete_from_state, kwargs={'vars': ['map', 'imagers_list_']})
194
+ on_change=delete_from_state,
195
+ kwargs={'vars': ['map', 'imagers_list_', 'clim', 'clim_manual_low', 'clim_manual_high']})
196
+ if image_mode in ['Running Diff.', 'Base Diff.']:
197
+ diff_value = procoption_container.number_input('Select Step/Image for Running/Base Diff.', 1, 5, key='diff_value',
198
+ on_change=delete_from_state,
199
+ kwargs={'vars': ['map', 'imagers_list_', 'clim', 'clim_manual_low', 'clim_manual_high']})
200
+ else:
201
+ diff_value = None
202
+
203
+ procoption_container.number_input('Median Filter', 1, 6, 1, 1, key='images_median_filter')
191
204
 
192
205
  with st.sidebar.expander('Plot/View Options'):
193
206
  plotviewopt_container = st.container()
194
- clip_model = plotviewopt_container.checkbox('Clip plot on image limits', value=True)
207
+ plotviewopt_container.checkbox('Clip plot on image limits', value=True, key='clip_model')
195
208
  plt_supp_imagers = plotviewopt_container.checkbox('Supplementary Imaging', value=False)
196
- star_field = plotviewopt_container.checkbox('View Bodies or s/c')
197
- if star_field:
198
- bodies_list = plotviewopt_container.multiselect('Select Bodies', options=selected_bodies.bodies_dict.keys(),
199
- default=['Mercury', 'Venus', 'Jupiter'])
209
+ plotviewopt_container.checkbox('View Bodies or s/c', key='star_field')
210
+ if st.session_state.star_field:
211
+ plotviewopt_container.multiselect('Select Bodies', options=selected_bodies.bodies_dict.keys(),
212
+ default=['Mercury', 'Venus', 'Jupiter'],
213
+ key='bodies_list')
214
+ plotviewopt_container.checkbox('View Limb and Meridians', key='plot_solar_reference_lines_')
215
+ if st.session_state.plot_solar_reference_lines_:
216
+ markdown = '''
217
+ **Limb**: plots the solar limb as observed # noqa
218
+ for the selected observers. # noqa
219
+ **Central Meridian**: plots the central meridian # noqa
220
+ observed for the selected observers. # noqa
221
+ **CR Meridan+Equator**: plots the primary meridian # noqa
222
+ and equator of Carrington coordinate system. # noqa
223
+ '''.strip()
224
+ plotviewopt_container.selectbox('Select plot option',
225
+ options=['Limb from Obs.', 'Central Meridian from Obs.', 'Carr. Prime Meridian+Solar Equator',
226
+ 'Stonyhurst Grid', 'Carrington Grid'],
227
+ help=markdown,
228
+ key='plot_solar_reference_lines_mode')
229
+ disabled = False if st.session_state.plot_solar_reference_lines_mode in ['Limb from Obs.', 'Central Meridian from Obs.'] else True
230
+ plotviewopt_container.multiselect('Select Bodies',
231
+ label_visibility='collapsed',
232
+ options=selected_bodies.bodies_dict.keys(),
233
+ default=['Earth'], disabled=disabled,
234
+ key='plot_solar_reference_lines_bodies_list')
200
235
 
201
236
  #############################################################
202
237
  # Download and Process the Images
203
238
  # This part runs only if the map_ doesn't exits or the session_state.map_ does not contain all the imagers requested
204
239
  imager_added = list(set(imagers_list) - set(st.session_state['imagers_list_']))
205
240
  imager_removed = list(set(st.session_state['imagers_list_']) - set(imagers_list))
241
+ if 'map_colormap_limits' not in st.session_state:
242
+ st.session_state['map_colormap_limits'] = {}
243
+
206
244
  if 'map_' not in st.session_state or imager_added != []:
207
245
  st.session_state.map_ = {} if 'map_' not in st.session_state else st.session_state.map_
208
246
  st.session_state.map = {} if 'map' not in st.session_state else st.session_state.map
@@ -211,22 +249,28 @@ def run():
211
249
  for imager in progress_bar:
212
250
  progress_bar.desc = f'Downloaded {imager} images from VSO'
213
251
  if imager not in st.session_state.map_:
214
- st.session_state.map_[imager] = download_fits(st.session_state.date_process,
215
- imager, time_range=imaging_time_range)
252
+ timerange = a.Time(st.session_state.date_process + datetime.timedelta(hours=imaging_time_range[0]),
253
+ st.session_state.date_process + datetime.timedelta(hours=imaging_time_range[1]))
254
+ st.session_state.map_[imager] = download_fits(timerange, imager)
255
+ st.session_state.map_[imager] = single_imager_maps_process(st.session_state.map_[imager],
256
+ **selected_imagers.imager_dict[imager][1],
257
+ skip='sequence_processing')
216
258
  processed_images = single_imager_maps_process(st.session_state.map_[imager],
259
+ skip=['filter', 'prepare'],
217
260
  image_mode=image_mode,
218
- **selected_imagers.imager_dict[imager][1])
261
+ diff_num=diff_value)
219
262
  if processed_images != []:
220
263
  st.session_state.map[imager] = processed_images
264
+ st.session_state.map_colormap_limits[imager] = maps_clims(st.session_state.map[imager])
221
265
  else:
222
266
  st.session_state.imagers_list_.remove(imager)
267
+ st.session_state.map_colormap_limits.pop(imager, None)
268
+
223
269
  if imager_removed != []:
224
270
  st.session_state['imagers_list_'] = imagers_list
225
271
  for imager in imager_removed:
226
272
  st.session_state.map_.pop(imager, None)
227
273
 
228
- maps_clims(st, st.session_state.imagers_list_)
229
-
230
274
  if not st.session_state.imagers_list_:
231
275
  st.error('No images have been downloaded or processed.') # TODO: Explain better
232
276
  st.stop()
@@ -236,7 +280,8 @@ def run():
236
280
  st.markdown('### Multi-viewpoint imaging')
237
281
  col1, col2 = st.columns((1, 3))
238
282
  imager_select = col1.selectbox('Select an imager',
239
- options=st.session_state.imagers_list_)
283
+ options=st.session_state.imagers_list_,
284
+ on_change=delete_from_state, kwargs={'vars': ['clim', 'clim_manual_low', 'clim_manual_high']})
240
285
 
241
286
  maps_date = [getattr(maps, 'date_average', None) or getattr(maps, 'date', None) for maps in st.session_state.map[imager_select]]
242
287
  if len(maps_date) > 1:
@@ -248,14 +293,26 @@ def run():
248
293
 
249
294
  running_map = st.session_state.map[imager_select][maps_date.index(running_map_date)]
250
295
 
251
- qmin = st.session_state.map_clim[imager_select][0] # np.nanquantile(running_map.data, 0.20)
252
- qmax = st.session_state.map_clim[imager_select][1] # np.nanquantile(running_map.data, 0.80)
253
- col1, col2 = st.columns((1, 3))
254
- cmmin, cpmax = config_sliders.slider_image_pmclims[image_mode]
255
- clim = plotviewopt_container.slider('Images climits:',
256
- min_value=float(cmmin), max_value=float(cpmax),
257
- value=(float(qmin-10), float(qmax+10)),
258
- key='clim')
296
+ manual_clim = plotviewopt_container.checkbox('Provide clims values', on_change=delete_from_state, kwargs={'vars': ['clim']})
297
+ if manual_clim:
298
+ col1, col2 = plotviewopt_container.columns(2)
299
+ if 'clim_manual_low' not in st.session_state:
300
+ st.session_state.clim_manual_low = st.session_state.map_colormap_limits[imager_select][0]
301
+ qmin = col1.number_input('Low clim value', label_visibility='collapsed', key='clim_manual_low')
302
+ if 'clim_manual_high' not in st.session_state:
303
+ st.session_state.clim_manual_high = st.session_state.map_colormap_limits[imager_select][1]
304
+ qmax = col2.number_input('High clim value', label_visibility='collapsed', key='clim_manual_high')
305
+ st.session_state.map_colormap_limits[imager_select] = [qmin, qmax]
306
+ else:
307
+ qmin = st.session_state.map_colormap_limits[imager_select][0]
308
+ qmax = st.session_state.map_colormap_limits[imager_select][1]
309
+ cmmin, cpmax = config_sliders.slider_image_pmclims[image_mode]
310
+ if 'clim' not in st.session_state:
311
+ st.session_state.clim = [float(qmin), float(qmax)]
312
+ st.session_state.map_colormap_limits[imager_select] = \
313
+ plotviewopt_container.slider('Images colorbar limits:',
314
+ min_value=float(cmmin), max_value=float(cpmax),
315
+ key='clim')
259
316
 
260
317
  #############################################################
261
318
  # Finalize the geometrical model
@@ -290,35 +347,28 @@ def run():
290
347
 
291
348
  #############################################################
292
349
  # Plot main and supplement figure images
293
- fig, axis = make_figure(running_map, image_mode, clim=clim, clip_model=clip_model)
294
- if st.session_state.plot_mesh_mode == 'Skeleton':
295
- model.plot(axis, mode='Skeleton')
296
- elif st.session_state.plot_mesh_mode == 'Full':
297
- model.plot(axis, mode='Skeleton')
298
- model.plot(axis, mode='Full')
299
- elif st.session_state.plot_mesh_mode == 'Surface':
300
- model.plot(axis, only_surface=True)
301
-
302
- if star_field:
303
- plot_bodies(axis, bodies_list, running_map)
304
- axis.legend()
350
+ fig, axis = figure_streamlit(st, running_map, image_mode, imager_select, model)
305
351
  st.pyplot(fig)
306
352
 
307
- if plt_supp_imagers and len(st.session_state.imagers_list_) > 2:
308
- supl_imagers = st.select_slider('Select supplement imagers',
309
- options=st.session_state.imagers_list_,
310
- value=(st.session_state.imagers_list_[1],
311
- st.session_state.imagers_list_[-1]),
312
- key='supl_imagers')
313
- col1, col2 = st.columns(2)
314
- fig, axis = make_figure(get_closest(st.session_state.map[supl_imagers[0]],
315
- running_map_date), image_mode)
316
- model.plot(axis, mode='Skeleton')
317
- col1.pyplot(fig)
318
- fig, axis = make_figure(get_closest(st.session_state.map[supl_imagers[1]],
319
- running_map_date), image_mode)
320
- model.plot(axis, mode='Skeleton')
321
- col2.pyplot(fig)
353
+ if plt_supp_imagers:
354
+ if len(st.session_state.imagers_list_) < 3:
355
+ other_element = [element for element in st.session_state.imagers_list_ if element != imager_select][0]
356
+ fig, axis = figure_streamlit(st, get_closest(st.session_state.map[other_element], running_map_date), image_mode, other_element, model)
357
+ st.pyplot(fig)
358
+ else:
359
+ supl_imagers_list = copy(st.session_state.imagers_list_)
360
+ supl_imagers_list.remove(imager_select)
361
+ supl_imagers = st.select_slider('Select supplement imagers',
362
+ options=supl_imagers_list,
363
+ value=(supl_imagers_list[0],
364
+ supl_imagers_list[-1]),
365
+ key='supl_imagers')
366
+ col1, col2 = st.columns(2)
367
+ fig, axis = figure_streamlit(st, get_closest(st.session_state.map[supl_imagers[0]], running_map_date), image_mode, supl_imagers[0], model)
368
+ col1.pyplot(fig)
369
+ fig, axis = figure_streamlit(st, get_closest(st.session_state.map[supl_imagers[1]], running_map_date), image_mode, supl_imagers[1], model)
370
+ model.plot(axis, mode='Skeleton')
371
+ col2.pyplot(fig)
322
372
 
323
373
  #############################################################
324
374
  # Store the fitting
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.9.1'
16
- __version_tuple__ = version_tuple = (0, 9, 1)
15
+ __version__ = version = '0.11.0'
16
+ __version_tuple__ = version_tuple = (0, 11, 0)
@@ -39,7 +39,8 @@ def change_long_lat_sliders(st):
39
39
  if st.session_state.coord_system == 'HGS':
40
40
  center_ = st.session_state.center.transform_to(frames.HeliographicStonyhurst)
41
41
  elif st.session_state.coord_system == 'HGC':
42
- center_ = st.session_state.center.transform_to(frames.HeliographicCarrington)
42
+ center_ = st.session_state.center.transform_to(frames.HeliographicCarrington(observer='Earth',
43
+ obstime=st.session_state.center.obstime))
43
44
  st.session_state.longit = float(center_.lon.to_value(u.degree))
44
45
  st.session_state.latitu = float(center_.lat.to_value(u.degree))
45
46
 
@@ -0,0 +1,35 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ import pooch
5
+
6
+ database_dir = os.path.join(Path.home(), 'PyThea')
7
+ github_main_url = 'https://github.com/AthKouloumvakos/PyThea-sample-data'
8
+
9
+ aia_sample_data = pooch.create(
10
+ path=os.path.join(database_dir, 'sample_data'), # The cache folder
11
+ base_url=f'{github_main_url}/raw/main/data/', # The remote data url on Github
12
+ registry={
13
+ 'aia_lev1_1600a_2012_06_06t04_07_29_12z_image_lev1.fits': 'sha256:7e88d8a277ad3dd111c1bcff3c3936d6d5ef5186e05b4bfa7749ee43c05577d5',
14
+ 'aia_lev1_1600a_2016_05_09t11_35_51_12z_image_lev1.fits': 'sha256:1c3bb938e9bfb3178cecead1319a92fa661eb70469715a441a849a98039f0b61'
15
+ },
16
+ )
17
+
18
+ stereo_sample_data = pooch.create(
19
+ path=os.path.join(database_dir, 'sample_data'), # The cache folder
20
+ base_url=f'{github_main_url}/raw/main/data/', # The remote data url on Github
21
+ registry={
22
+ '20120622_235400_d4c2a.fts': 'sha256:940680fe8bea754c9859170585d8299b9b049b631155435a31ecacf5b702d9cf',
23
+ '20140221_235400_d4c2b.fts': 'sha256:13638dedeb2e510c9e2ef11d46b372acb16f9b9645ca1cabf4d0ef8444353adc',
24
+ '20070503_102018_s4c1a.fts': 'sha256:55cf6e3741833a82e41b46e2345bf85a0e5580a2832900eccc41f235d36fc31e',
25
+ '20070503_102018_s4c1b.fts': 'sha256:ca8f4aabaeb7e4bc6f4e5d4819057ddfe3f9f621716224d11acc80d701e3beb8'
26
+ },
27
+ )
28
+
29
+ json_fitting_file_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
+ 'FLX1p0D20211028T153500MEllipsoid.json': 'sha256:700687ed982317400ec409390571747694237de9688a2816baa7b1770b75c9b1',
34
+ },
35
+ )
@@ -283,7 +283,8 @@ class spheroid:
283
283
  """
284
284
  Returns the spheroid parameters as `~pandas.DataFrame`.
285
285
  """
286
- center_ = self.center.transform_to(frames.HeliographicCarrington)
286
+ center_ = self.center.transform_to(frames.HeliographicCarrington(observer='Earth',
287
+ obstime=self.center.obstime))
287
288
  data_dict = {
288
289
  'hgln': self.center.lon.to_value(u.degree),
289
290
  'hglt': self.center.lat.to_value(u.degree),
@@ -303,7 +304,8 @@ class spheroid:
303
304
  '''
304
305
  Returns the Spheroid object and parameters in a printable sting array.
305
306
  '''
306
- center_ = self.center.transform_to(frames.HeliographicCarrington)
307
+ center_ = self.center.transform_to(frames.HeliographicCarrington(observer='Earth',
308
+ obstime=self.center.obstime))
307
309
  output = '<Spheroid object \n'
308
310
  output += 'HGLN = %3.2f degrees \n'%self.center.lon.to_value(u.degree)
309
311
  output += 'HGLT = %3.2f degrees \n'%self.center.lat.to_value(u.degree)
@@ -402,7 +404,8 @@ class ellipsoid(spheroid):
402
404
  """
403
405
  Returns the ellipsoid parameters as `~pandas.DataFrame`.
404
406
  """
405
- center_ = self.center.transform_to(frames.HeliographicCarrington)
407
+ center_ = self.center.transform_to(frames.HeliographicCarrington(observer='Earth',
408
+ obstime=self.center.obstime))
406
409
  data_dict = {
407
410
  'hgln': self.center.lon.to_value(u.degree),
408
411
  'hglt': self.center.lat.to_value(u.degree),
@@ -469,7 +472,8 @@ class ellipsoid(spheroid):
469
472
  '''
470
473
  Returns the Ellipsoid object and parameters in a printable sting array.
471
474
  '''
472
- center_ = self.center.transform_to(frames.HeliographicCarrington)
475
+ center_ = self.center.transform_to(frames.HeliographicCarrington(observer='Earth',
476
+ obstime=self.center.obstime))
473
477
  output = '<Ellipsoid object \n'
474
478
  output += 'HGLN = %3.2f degrees \n'%self.center.lon.to_value(u.degree)
475
479
  output += 'HGLT = %3.2f degrees \n'%self.center.lat.to_value(u.degree)
@@ -669,7 +673,8 @@ class gcs():
669
673
  """
670
674
  Returns the GCS parameters as `~pandas.DataFrame`.
671
675
  """
672
- center_ = self.center.transform_to(frames.HeliographicCarrington)
676
+ center_ = self.center.transform_to(frames.HeliographicCarrington(observer='Earth',
677
+ obstime=self.center.obstime))
673
678
  data_dict = {
674
679
  'hgln': self.center.lon.to_value(u.degree),
675
680
  'hglt': self.center.lat.to_value(u.degree),
@@ -689,7 +694,8 @@ class gcs():
689
694
  '''
690
695
  Returns the GCS object and parameters in a printable sting array.
691
696
  '''
692
- center_ = self.center.transform_to(frames.HeliographicCarrington)
697
+ center_ = self.center.transform_to(frames.HeliographicCarrington(observer='Earth',
698
+ obstime=self.center.obstime))
693
699
  output = '<GCS object \n'
694
700
  output += 'HGLN = %3.2f degrees \n'%self.center.lon.to_value(u.degree)
695
701
  output += 'HGLT = %3.2f degrees \n'%self.center.lat.to_value(u.degree)
@@ -21,12 +21,12 @@
21
21
  import datetime
22
22
 
23
23
  import astropy.units as u
24
- import numpy as np
25
24
 
26
25
  from PyThea.callbacks import change_fitting_sliders, change_long_lat_sliders
27
26
  from PyThea.config.config_sliders import sliders_dict as sd
28
27
  from PyThea.geometrical_models import ellipsoid, gcs, spheroid
29
- from PyThea.utils import get_hek_flare, model_fittings
28
+ from PyThea.utils import (get_hek_flare, make_figure, model_fittings,
29
+ plot_bodies, plot_solar_reference_lines)
30
30
 
31
31
 
32
32
  def date_and_event_selection(st):
@@ -184,13 +184,27 @@ def final_parameters_gmodel(st):
184
184
  return rcenter, height, alpha, kappa, tilt
185
185
 
186
186
 
187
- def maps_clims(st, imagers_list):
188
- if 'map_clim' not in st.session_state:
189
- st.session_state['map_clim'] = {}
187
+ def figure_streamlit(st, running_map, image_mode, imager, model):
190
188
 
191
- for imager in imagers_list:
192
- if imager not in st.session_state.map or st.session_state.map[imager] == []:
193
- pass
194
- else:
195
- map_ = st.session_state.map[imager][0]
196
- st.session_state.map_clim[imager] = [np.nanquantile(map_.data, 0.20), np.nanquantile(map_.data, 0.80)]
189
+ fig, axis = make_figure(running_map, image_mode,
190
+ clim=st.session_state.map_colormap_limits[imager],
191
+ clip_model=st.session_state.clip_model,
192
+ median_filter=st.session_state.images_median_filter)
193
+
194
+ if st.session_state.plot_mesh_mode == 'Skeleton':
195
+ model.plot(axis, mode='Skeleton')
196
+ elif st.session_state.plot_mesh_mode == 'Full':
197
+ model.plot(axis, mode='Skeleton')
198
+ model.plot(axis, mode='Full')
199
+ elif st.session_state.plot_mesh_mode == 'Surface':
200
+ model.plot(axis, only_surface=True)
201
+
202
+ if st.session_state.star_field:
203
+ plot_bodies(axis, st.session_state.bodies_list, running_map)
204
+ axis.legend()
205
+
206
+ if st.session_state.plot_solar_reference_lines_:
207
+ plot_solar_reference_lines(axis, st.session_state.plot_solar_reference_lines_bodies_list,
208
+ running_map, mode=st.session_state.plot_solar_reference_lines_mode)
209
+
210
+ return fig, axis
@@ -99,9 +99,28 @@ def update():
99
99
 
100
100
 
101
101
  @main.command('test')
102
- def main_test():
102
+ @click.option('--mpl', is_flag=True, default=False, help='Run the test including figure tests.')
103
+ @click.option('--remote-data', is_flag=True, default=False, help='Run the test including figure tests.')
104
+ @click.option('--all', is_flag=True, default=False, help='Run the test including figure tests.')
105
+ def main_test(mpl, remote_data, all):
103
106
  """Test PyThea."""
104
- pytest.main(['-W', 'ignore', '--pyargs', 'PyThea'])
107
+
108
+ test_directory = os.path.dirname(__file__)
109
+
110
+ print(f'Running a test of PyThea package located in: {test_directory}')
111
+
112
+ if mpl:
113
+ # Run a test of the package including figure tests
114
+ pytest.main(['-W', 'ignore', '--mpl', '--mpl-hash-library=figure_hashes.json', '--pyargs', f'{test_directory}'])
115
+ elif remote_data:
116
+ # Run a test of the package including remote-data tests
117
+ pytest.main(['-W', 'ignore', '--remote-data', '--pyargs', f'{test_directory}'])
118
+ elif all:
119
+ # Run a test of the package including remote-data tests
120
+ pytest.main(['-W', 'ignore', '--mpl', '--mpl-hash-library=figure_hashes.json', '--remote-data', '--pyargs', f'{test_directory}'])
121
+ else:
122
+ # Run a minimum test of the package (figure tests are always true and remote-data tests are skipped)
123
+ pytest.main(['-W', 'ignore', '--pyargs', f'{test_directory}'])
105
124
 
106
125
 
107
126
  if __name__ == '__main__':
@@ -0,0 +1,14 @@
1
+ from astropy.time import TimeDelta
2
+ from aiapy.calibrate import fix_observer_location, update_pointing
3
+
4
+ __all__ = ['prep_aia']
5
+
6
+
7
+ def prep_aia(map_sequence):
8
+ map_sequence = [update_pointing(tmap) for tmap in map_sequence]
9
+ map_sequence = [fix_observer_location(tmap) for tmap in map_sequence]
10
+
11
+ for map_ in map_sequence:
12
+ map_.meta['DATE-AVG'] = (map_.date + TimeDelta(map_.meta['exptime']/2, format='sec')).value
13
+
14
+ return map_sequence
@@ -8,8 +8,8 @@ import warnings
8
8
  import astropy.units as u
9
9
  import numpy as np
10
10
  import sunpy.map
11
- from aiapy.calibrate import fix_observer_location, update_pointing
12
11
 
12
+ import PyThea.sunpy_dev.extern.sunkit_instruments.aia.utils # noqa
13
13
  import PyThea.sunpy_dev.extern.sunkit_instruments.lasco.utils # noqa
14
14
  import PyThea.sunpy_dev.extern.sunkit_instruments.stereo.utils # noqa
15
15
 
@@ -17,17 +17,17 @@ __all__ = ['maps_sequence_processing', 'get_closest', 'normalize_exposure',
17
17
  'prepare_maps', 'difference_maps', 'mask_occulter']
18
18
 
19
19
 
20
- def maps_sequence_processing(map_sequence, seq_type='Plain'):
20
+ def maps_sequence_processing(map_sequence, **kwargs):
21
21
  """
22
- Returns a sequence of maps which is processed as running difference or base difference or plain images.
22
+ Returns a sequence of maps which is processed as plain images, running or base difference images.
23
23
 
24
24
  Parameters
25
25
  ----------
26
26
  map_sequence : `~sunpy.map.GenericMap`
27
27
  A list SunPy maps.
28
28
 
29
- seq_type : '~str'
30
- The type of sequence processing
29
+ image_mode : '~str'
30
+ The type of sequence processing: 'Running Diff.', 'Base Diff.', 'Plain'
31
31
 
32
32
  Returns
33
33
  -------
@@ -38,18 +38,21 @@ def maps_sequence_processing(map_sequence, seq_type='Plain'):
38
38
  if len(map_sequence) == 0:
39
39
  return []
40
40
 
41
+ seq_type = kwargs.get('image_mode', 'Plain')
42
+ diff_num = kwargs.get('diff_num', 1 if seq_type == 'Running Diff.' else 0 if seq_type == 'Base Diff.' else None)
43
+
41
44
  normalized = True if True in [tmap.exposure_time == 1.0*u.second for tmap in map_sequence] else False
42
45
  if not normalized:
43
46
  warnings.warn('Warning [maps_sequence_processing]: The exposure time of the maps is not normalized.')
44
47
 
45
48
  smap = []
46
49
  if seq_type == 'Running Diff.':
47
- for i in range(1, len(map_sequence)):
48
- smap_diff = difference_maps(map_sequence[i], map_sequence[i-1])
50
+ for i in range(1+diff_num, len(map_sequence)):
51
+ smap_diff = difference_maps(map_sequence[i], map_sequence[i-diff_num])
49
52
  smap.append(smap_diff)
50
53
  if seq_type == 'Base Diff.':
51
54
  for i in range(1, len(map_sequence)):
52
- smap_diff = difference_maps(map_sequence[i], map_sequence[0])
55
+ smap_diff = difference_maps(map_sequence[i], map_sequence[diff_num])
53
56
  smap.append(smap_diff)
54
57
  if seq_type == 'Plain':
55
58
  for i in range(0, len(map_sequence)):
@@ -151,6 +154,7 @@ def filter_maps(map_sequence, **kwargs):
151
154
  sequence_final = sunpy.map.Map(map_sequence, sequence=True)
152
155
  else:
153
156
  sequence_final = []
157
+
154
158
  return sequence_final
155
159
 
156
160
 
@@ -180,12 +184,11 @@ def prepare_maps(map_sequence, **kwargs):
180
184
  return []
181
185
 
182
186
  detector = map_sequence[0].detector
183
- print(f'Preparing image sequence for {detector}.')
187
+ print(f'Preparing image sequence for {detector}. This could take a while...')
184
188
 
185
189
  # Prepare the maps before anything else
186
190
  if detector == 'AIA':
187
- map_sequence = [update_pointing(tmap) for tmap in map_sequence]
188
- map_sequence = [fix_observer_location(tmap) for tmap in map_sequence]
191
+ map_sequence = PyThea.sunpy_dev.extern.sunkit_instruments.aia.utils.prep_aia(map_sequence)
189
192
  elif detector == 'COR1':
190
193
  if 'polar' not in kwargs.keys():
191
194
  map_sequence = PyThea.sunpy_dev.extern.sunkit_instruments.stereo.utils.cor_polariz(map_sequence)
@@ -286,3 +289,7 @@ def mask_occulter(smap, apply_mask=True, mask_value=0):
286
289
  smap.data[mask_inner+mask_outer] = mask_value
287
290
  return sunpy.map.Map(smap.data, smap.meta)
288
291
  return mask_inner+mask_outer
292
+
293
+
294
+ def maps_clims(images):
295
+ return [np.nanquantile(images[1].data, 0.20)-10, np.nanquantile(images[1].data, 0.80)+10]
@@ -0,0 +1,24 @@
1
+ """
2
+ A place for the more generic tests that do not fit in the other categories
3
+
4
+ Note
5
+ ----
6
+ Before you start the test the PyThea pkg should be in the python path
7
+ export PYTHONPATH="${PYTHONPATH}:{top_level_dir_that_pythea_lives}/PyThea"
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+
13
+
14
+ def test_database_dir_exists():
15
+ """
16
+ Tests that Pythea's database directory exists.
17
+ """
18
+ # Skip the test if running in GitHub Actions
19
+ if os.environ.get('CI') == 'true':
20
+ print("Skipping test as it's running in GitHub Actions.")
21
+ return
22
+
23
+ database_dir = os.path.join(Path.home(), 'PyThea')
24
+ assert os.path.exists(database_dir), f"PyThea's database directory '{database_dir}' does not exist."
File without changes
@@ -0,0 +1,6 @@
1
+ {
2
+ "PyThea.test.test_figures.test_ellipsoid_on_AIA_venus": "f1ff774b483a8c934c9353c552278c8823216fc7d9dad4ccc00c74be3f30c4c3",
3
+ "PyThea.test.test_figures.test_ellipsoid_on_AIA_mercury": "f6afda1c769a061b7ab1fc64c4e9c0f4658cdb97c3be83828ba5d98bf1bb182c",
4
+ "PyThea.test.test_figures.test_ellipsoid_on_STEREO_COR1_venus": "18a3af72e9da5b60e435f83ee3ef3f8cd0eb437eaef463329552ff56968f12ff",
5
+ "PyThea.test.test_figures.test_ellipsoid_on_STEREO_COR2_three_planets": "231d5635f57c4d66556674a83f6cbcef7dc94b51846e977c4c2dc3dc244096ac"
6
+ }