What’s New in SunPy 1.0?#
Overview#
The SunPy project is pleased to announce the 1.0 release of the sunpy package. This release is the result of over 14 months of work and marks a shift in the core library with the removal of a lot of legacy code and a focus on usability and stability. The headline changes in 1.0 are:
A complete transition of the whole code base to use
astropy.time.Time
, which was implemented by Vishnunarayan K I as part of Google Summer of Code 2018.A rewrite of how all the clients in
sunpy.net
download files from the internet. This means vastly improved progress bars, skipping downloads if files are present, and better visibility and retrying of failed downloads.A rewrite of the differential rotation and image warping code to correctly account for observer location using the Astropy coordinate functionality.
Removal of many deprecated functions and submodules; we have used the 1.0 release as a chance to clean out SunPy, reducing the number of lines of Python code in the package by almost 3,000!
The first release of SunPy to be Python 3 only, requiring Python 3.6+. This is because Python 2 will not be maintained after 2019.
On this page, you can read about some of the big changes in this release:
There have been numerous improvements to large parts of SunPy, notably in the content of the documentation, continuous integration and testing. SunPy 1.0 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.
By the numbers:
1913 commits have been added since 0.9
582 issues have been closed since 0.9
332 pull requests have been merged since 0.9
46 people have contributed since 0.9
25 new contributors
Supported versions of Python#
SunPy 1.0 has dropped Python 2 support as Python 2 is nearing the end of its support cycle. To port your code to Python 3, there is some good advice here and a breakdown of the relevant changes in Python 3 on the Python 3 for Scientists page. If you still need Python 2 support, SunPy 0.9.X will see bug fixes until the end of 2019, but no new features. As a result, SunPy’s minimum Python version has been raised to Python 3.6 and is routinely tested against Python 3.6 and 3.7.
Astropy Time is used everywhere#
SunPy now uses Astropy’s Time
object everywhere to represent time.
This comes with numerous benefits:
Support for non-UTC time scales. UTC as well as non-UTC time scales like TAI, TT, UT1 etc. can be used with
astropy.time.Time
.>>> t = Time('2012-06-18T02:00:05.453', scale='tai') >>> t <Time object: scale='tai' format='isot' value=2012-06-18T02:00:05.453>
Time
also provides easy conversion between different scales.>>> t.utc <Time object: scale='utc' format='isot' value=2012-06-18T01:59:31.453>
Support for high precision times.
Time
can provide sub-nanosecond precision for time objects while pythondatetime
was restricted to microseconds.>>> t = Time('2012-06-18T02:00:05.453123123') >>> t <Time object: scale='utc' format='isot' value=2012-06-18T02:00:05.453> >>> t.precision = 9 >>> t <Time object: scale='utc' format='isot' value=2012-06-18T02:00:05.453123123>
Support for leap seconds This was one of the biggest motivations for the transition to
astropy.time.Time
.datetime
has no support for leap seconds whileTime
supports them. A leap second is a one-second adjustment applied to UTC to keep it close to the mean solar time.>>> Time('2016-12-31T23:59:60') <Time object: scale='utc' format='isot' value=2016-12-31T23:59:60.000> >>> Time('2016-12-31T23:59:59') + 1 * u.s <Time object: scale='utc' format='isot' value=2016-12-31T23:59:60.000>
Support for numerous formats
Time
can parse numerous formats including pythondatetime
.>>> list(Time.FORMATS) ['jd', 'mjd', 'decimalyear', 'unix', 'cxcsec', 'gps', 'plot_date', 'datetime', 'iso', 'isot', 'yday', 'fits', 'byear', 'jyear', 'byear_str', 'jyear_str']
>>> import datetime >>> Time(datetime.datetime.now()) <Time object: scale='utc' format='datetime' value=2018-10-20 15:36:16.364089>
Changes in return values
All functions which previously returned
datetime.datetime
now returnTime
and all functions which returneddatetime.timedelta
now returnastropy.time.TimeDelta
. For example, the properties ofsunpy.time.TimeRange
which used to returndatetime.datetime
anddatetime.timedelta
now returnastropy.time.Time
andastropy.time.TimeDelta
.Changes to
parse_time
parse_time
has been reduced to a tiny wrapper overTime
. The API ofparse_time
is almost the same asTime
, however,parse_time
supports conversion of a few more formats thanTime
, which arenumpy.datetime64
,pandas.Series
,pandas.DatetimeIndex
, utime and a few other time string formats.
Improved file downloading capability#
The file download capability has been re-written to use the parfive package. This brings more visually appealing and informative progress bars, better reporting of download errors and the ability to re-download failed files.
It is possible to retry any downloads which fail with:
>>> files = Fido.fetch(results) # Some downloads fail
>>> files = Fido.fetch(files) # Retry the downloads which failed
Improvements to coordinates functionality#
Accurate Sun-specific coordinates calculations
Sun-specific coordinates calculations have been grouped together in
sunpy.coordinates.sun
, and the underlying implementations have been re-written to use Astropy rather than approximate expressions. Nearly all of the returned values now match published values in the Astronomical Almanac to published precision (e.g., the hundredth of an arcsecond for apparent right ascension). For times that are provided to these functions, the user should take care to specify whether the time is other than UT (e.g., TT), which can be done usingTime
(see above).Improved tools to get positions of bodies in the solar system
The existing function
get_body_heliographic_stonyhurst
has been enhanced to be able to correct for light travel time. When one specifies an observer, the function determines the emission time in the past that results in photons arriving at the observer at the observation time. The function then returns the location of the requested body at that emission time.>>> t = '2012-06-05 22:34:48.350' >>> without_correction = get_body_heliographic_stonyhurst('venus', t) >>> print(without_correction) <HeliographicStonyhurst Coordinate (obstime=2012-06-05T22:34:48.350): (lon, lat, radius) in (deg, deg, AU) (359.92620234, 0.02752007, 0.72602872)> >>> with_correction = get_body_heliographic_stonyhurst('venus', t, observer=get_earth(t)) INFO: Apparent body location accounts for 144.06 seconds of light travel time [sunpy.coordinates.ephemeris] >>> print(with_correction) <HeliographicStonyhurst Coordinate (obstime=2012-06-05T22:34:48.350): (lon, lat, radius) in (deg, deg, AU) (359.92355609, 0.02734159, 0.72602853)>
There is a new function
get_horizons_coord
that queries JPL HORIZONS for the location of solar-system bodies. JPL HORIZONS includes not only planets and other natural bodies in the solar system, but also major spacecraft. This function requires the Astroquery package and an Internet connection.Query the location of Venus
>>> get_horizons_coord('Venus barycenter', '2001-02-03 04:05:06') INFO: Obtained JPL HORIZONS location for Venus Barycenter (2) [sunpy.coordinates.ephemeris] <SkyCoord (HeliographicStonyhurst: obstime=2001-02-03T04:05:06.000): (lon, lat, radius) in (deg, deg, AU) (326.06844114, -1.64998481, 0.71915147)>
Query the location of the SDO spacecraft
>>> get_horizons_coord('SDO', '2011-11-11 11:11:11') INFO: Obtained JPL HORIZONS location for Solar Dynamics Observatory (spac [sunpy.coordinates.ephemeris] <SkyCoord (HeliographicStonyhurst: obstime=2011-11-11T11:11:11.000): (lon, lat, radius) in (deg, deg, AU) (0.01018888, 3.29640407, 0.99011042)>
Query the location of the SOHO spacecraft via its ID number (-21)
>>> get_horizons_coord(-21, '2004-05-06 11:22:33', 'id') INFO: Obtained JPL HORIZONS location for SOHO (spacecraft) (-21) [sunpy.coordinates.ephemeris] <SkyCoord (HeliographicStonyhurst: obstime=2004-05-06T11:22:33.000): (lon, lat, radius) in (deg, deg, AU) (0.2523461, -3.55863351, 0.99923086)>
Logging used to record SunPy notices#
All messages provided by SunPy use a new logging facility which is based on the Python logging module rather than print statements.
Messages can have one of several levels, in increasing order of importance:
DEBUG: Detailed information, typically of interest only when diagnosing problems.
INFO: A message conveying information about the current task, and confirming that things are working as expected.
WARNING: An indication that something unexpected happened, and that user action may be required.
ERROR: An indication that a more serious issue has occurred, where something failed but the task is continuing.
CRITICAL: A serious error, indicating that the program itself may be unable to continue running.
By default, all messages except for DEBUG messages are displayed. Messages can also be sent to a file and time stamped.
See the Logger Objects documentation for instructions on how to control the verbosity of the logger.
Improvements to differential rotation#
Applying the effect of solar differential rotation to coordinates now properly takes into account the changing position of the observer. For example, since the Earth moves, observers on the Earth must take into account the solar differential rotation of the Sun and the motion of the Earth when calculating a location on the Sun.
Support for applying solar differential rotation to coordinates.
Solar differential rotation of on-disk coordinates can be specified using either time or a new observer. If time is specified, then the new observer is assumed to be located on the Earth:
>>> import astropy.units as u >>> from astropy.coordinates import SkyCoord >>> from sunpy.coordinates import Helioprojective >>> from sunpy.physics.differential_rotation import solar_rotate_coordinate >>> from sunpy.time import parse_time >>> start_time = '2010-09-10 12:34:56' >>> duration = 25*u.hour >>> c = SkyCoord(-570*u.arcsec, 120*u.arcsec, obstime=start_time, frame=Helioprojective) >>> solar_rotate_coordinate(c, time=duration) <SkyCoord (Helioprojective: obstime=2010-09-11T13:34:56.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2010-09-11T13:34:56.000): (lon, lat, radius) in (deg, deg, AU) (-5.08888749e-14, 7.24318962, 1.00669016)>): (Tx, Ty, distance) in (arcsec, arcsec, km) (-363.04027419, 104.87807178, 1.499598e+08)>
Due to the ellipticity of the Earth’s orbit, the amount of solar rotation is different at different times in the year:
>>> start_time = '2010-06-10 12:34:56' >>> duration = 25*u.hour >>> c = SkyCoord(-570*u.arcsec, 120*u.arcsec, obstime=start_time, frame=Helioprojective) >>> solar_rotate_coordinate(c, time=duration) <SkyCoord (Helioprojective: obstime=2010-06-10T12:34:56.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2010-06-11T13:34:56.000): (lon, lat, radius) in (deg, deg, AU) (0., 0.58398742, 1.01539908)>): (Tx, Ty, distance) in (arcsec, arcsec, km) (-359.11576773, 117.18020622, 1.51263627e+08)>
The user can also explicitly specify an observer at a different time and location in space. The amount of solar rotation applied depends on the time difference between the observation time of the`~astropy.coordinates.SkyCoord` and the time of the observer:
>>> import astropy.units as u >>> from astropy.coordinates import SkyCoord >>> from sunpy.coordinates import Helioprojective, HeliographicStonyhurst >>> from sunpy.physics.differential_rotation import solar_rotate_coordinate >>> from sunpy.time import parse_time >>> start_time = parse_time('2010-06-10 12:34:56') >>> duration = 25*u.hour >>> c = SkyCoord(-570*u.arcsec, 120*u.arcsec, obstime=start_time, frame=Helioprojective) >>> new_observer = SkyCoord(lon=20*u.deg, lat=8*u.deg, radius=0.9*u.au, obstime=end_time, frame=HeliographicStonyhurst) >>> solar_rotate_coordinate(c, observer=new_observer) <SkyCoord (Helioprojective: obstime=2010-06-10T12:34:56.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2010-06-11T13:34:56.000): (lon, lat, radius) in (deg, deg, AU) (20., 8., 0.9)>): (Tx, Ty, distance) in (arcsec, arcsec, km) (-715.77862011, 31.87928146, 1.34122226e+08)>
Experimental support for applying solar differential rotation to maps.
Applying solar differential rotation to maps also accounts for changing observer position. This functionality is still experimental. For example, to differentially rotate a map back 23 hours:
>>> import astropy.units as u >>> import sunpy.map >>> from sunpy.data.sample import AIA_171_IMAGE >>> from sunpy.physics.differential_rotation import differential_rotate
>>> aia = sunpy.map.Map(AIA_171_IMAGE) >>> differential_rotate(aia, time=-23*u.hour)
differential_rotate
also accepts a new observer keyword. The amount of solar differential rotation is calculated using the time difference between the map date and observation time of the new observer. For example:>>> import astropy.units as u >>> import sunpy.map >>> from sunpy.data.sample import AIA_171_IMAGE
>>> from sunpy.physics.differential_rotation import differential_rotate >>> aia = sunpy.map.Map(AIA_171_IMAGE) >>> new_observer = SkyCoord(lon=-15*u.deg, lat=-4*u.deg, radius=1*u.au, obstime=aia.date-34*u.hour, frame=HeliographicStonyhurst) >>> differential_rotate(aia, observer=new_observer)
Map utility functions#
A set of new utility functions have been added to sunpy.map
which act on sunpy.map.GenericMap
instances.
For example, getting the world coordinates for every pixel:
>>> import sunpy.map
>>> from sunpy.data.sample import AIA_171_IMAGE
>>> import astropy.units as u
>>> from sunpy.physics.differential_rotation import differential_rotate
>>> from sunpy.map import contains_full_disk, all_coordinates_from_map
>>> aia = sunpy.map.Map(AIA_171_IMAGE)
>>> contains_full_disk(aia)
True
>>> coordinates = all_coordinates_from_map(aia) # The coordinates for every map pixel
>>> coordinates.shape
(1024, 1024)
or generating a new FITS header for a custom map:
>>> import numpy as np
>>> import astropy.units as u
>>> from sunpy.coordinates import frames
>>> from astropy.coordinates import SkyCoord
>>> data = np.arange(0,100).reshape(10,10)
>>> coord = SkyCoord(0*u.arcsec, 0*u.arcsec, obstime = '2013-10-28', observer = 'earth', frame = frames.Helioprojective)
>>> header = sunpy.map.header_helper.make_fitswcs_header(data, coord)
>>> for key, value in header.items():
... print(f"{key}: {value}")
wcsaxes: 2
crpix1: 5.5
crpix2: 5.5
cdelt1: 1.0
cdelt2: 1.0
cunit1: arcsec
cunit2: arcsec
ctype1: HPLN-TAN
ctype2: HPLT-TAN
crval1: 0.0
crval2: 0.0
lonpole: 180.0
latpole: 0.0
date-obs: 2013-10-28T00:00:00.000
hgln_obs: 0.0
hglt_obs: 4.7711570596394
dsun_obs: 148644585949.49176
rsun_ref: 695700.0
rsun_obs: 965.3723815059902
Config File Location Moved#
If you have customised your The sunpyrc file you will need to move it to the new config file location.
Your old file should be in ~/.sunpy/sunpyrc
file and the new location, which is now platform specific, can be found by running sunpy.print_config
.
We recommend that your take a look at the new file as available configuration options have increased.
Renamed/removed functionality#
This is just some of the renamed or removed functionality.
sunpy.sun.sun
functions have been re-implemented using Astropy for significantly improved accuracy and moved tosunpy.coordinates.sun
.Removed
sunpy.time.julian_day
,sunpy.time.julian_centuries
,sunpy.time.day_of_year
,sunpy.time.break_time
,sunpy.time.get_day
.Move the matplotlib animators from
sunpy.visualisation.imageanimator
andsunpy.visualization.mapcubeanimator
tosunpy.visualization.animator
.axis_ranges
kwarg ofsunpy.visualization.animator.ArrayAnimator
,sunpy.visualization.animator.ImageAnimator
andsunpy.visualization.animator.LineAnimator
now must be entered asNone
,[min, max]
or pixel edges of each array element.The Helioviewer client has been switched to using the newer Helioviewer API. This has meant that we have changed some of the keywords that were passed into client’s methods.
Removed
sunpy.net.jsoc.attrs.Time
because it served the same purpose assunpy.net.attrs.Time
after the switch toastropy.time.Time
.The deprecated
sunpy.lightcurve
(replaced bysunpy.timeseries
),sunpy.wcs
andsunpy.spectra
(replaced by theradiospectra
package) modules have now been removed.
Full change log#
To see a detailed list of all changes in version v1.0, including changes in API, please see the Full Changelog.