sunpy core Documentation#

sunpy is a community-developed, free and open-source solar data analysis environment for Python. It includes an interface for searching and downloading data from multiple data providers, data containers for image and time series data, commonly used solar coordinate frames and associated transformations, as well as other functionality needed for solar data analysis.

Tutorial

New users start here! Walkthrough on how to install sunpy and use the key features of the package.

How-to Guides

Snippets of code for accomplishing specific tasks with sunpy. Most useful for answering “How do I…” questions.

Example gallery

Examples including plots on accomplishing common tasks using sunpy.

Topic Guides

In-depth explanations of concepts and key topics. Most useful for answering “why” questions.

Reference

Technical description of the inputs, outputs, and behavior of each component of sunpy.

The sunpy tutorial#

Welcome to the introductory tutorial for the sunpy core package. sunpy is a community-developed, free and open-source solar data analysis environment. It is meant to provide the core functionality and tools to analyze solar data with Python.

Who this is for

This tutorial assumes you know how to run code in Python, but doesn’t make any assumptions beyond that. sunpy depends heavily on other packages in the scientific Python ecosystem, including Astropy, NumPy, and Matplotlib. This tutorial explains the basics of these packages needed to use sunpy, but is not a comprehensive tutorial for these packages and references their own introductory tutorials where relevant.

How to use this tutorial

This tutorial is designed to be read from start to finish. You can either read it without running any code, or copy and paste the code snippets as you read. Each chapter of the tutorial provides a self-contained set of codes.

Later chapters build on the concepts introduced in earlier chapters. The one exception is the two chapters that explore Maps and TimeSeries - these are independent, but depend on all earlier chapters.

Installation#

This is the first chapter in the sunpy tutorial, and by the end of it you should have a working installation of Python and sunpy. For further information and alternative methods for installing sunpy beyond the recommended approach outlined below, refer to Advanced Installation.

Installing Python#

There are many ways to install Python, but even if you have Python installed somewhere on your computer we recommend following these instructions anyway. That’s because we will create a new Python environment. As well as containing a Python installation, this environment provides an isolated place to install Python packages (like sunpy) without affecting any other current Python installation. If you already have Python and conda working you can skip the next section. However, if you are using Anaconda, we recommend you still install miniforge as described below.

Installing miniforge#

If you don’t already have a Python installation then we recommend installing Python with miniforge. This will install conda and automatically configure the default channel (a channel is a remote software repository) to be conda-forge, which is where sunpy is available.

First, download the installer for your system and architecture from the links below:

Then select your platform to install miniforge:

Linux & Mac Run the script downloaded above, with bash <filename>. The following should work:

bash Miniforge3-$(uname)-$(uname -m).sh

Once the installer has completed, close and reopen your terminal.

Double click the executable file downloaded from the links above.

Once the installer has completed you should have a new “miniforge Prompt” entry in your start menu.

In a new terminal (miniforge Prompt on Windows) run conda list to test that the install has worked.

Installing sunpy#

To install sunpy, start by launching a terminal (under a UNIX-like system) or the miniforge Prompt (under Windows). Now we will create and activate a new virtual environment to install sunpy into:

$ conda create --name sunpy
# Only run the following two lines
# if you have NOT installed miniforge or added conda-forge to your channels
# Do not run these lines if you are using Anaconda
$ conda config --add channels conda-forge
$ conda config --set channel_priority strict
$ conda activate sunpy

In this case the environment is named ‘sunpy’. Feel free to change this to a different environment name.

The benefit of using a virtual environment is that it allows you to install packages without affecting any other Python installation on your system. This also means you can work on multiple projects (research or coding) with different package requirements without them interfering with each other.

Now we have a fresh environment we can install sunpy:

$ conda install sunpy

This will install sunpy and all of its dependencies. If you want to install another package later, you can run conda install <package_name>.

Now we’ve got a working installation of sunpy, in the next few chapters we’ll look at some of the basic data structures sunpy uses for representing times, coordinates, and data with physical units.

Units#

In this section of the tutorial you will learn about representing physical units in sunpy. All functions in sunpy that accept or return numbers associated with physical quantities do so using astropy.units.Quantity objects. These objects represent a number (or an array of numbers) and a unit. This means sunpy is always explicit about the units associated with a value. Quantities and units are powerful tools for keeping track of variables with a physical meaning and make it straightforward to convert the same physical quantity into different units.

By the end of this section of the tutorial, you will learn how to create a Quantity, perform basic arithmetic with a Quantity, convert a Quantity to different units, and write a function that ensures the inputs have the correct units.

Adding Units to Data#

To use units we must first import them from Astropy. It is standard practice to import the units module as u:

>>> import astropy.units as u

We can create a Quantity by multiplying a number by a unit:

>>> length = 10 * u.meter
>>> length
<Quantity 10. m>

A Quantity can be decomposed into its unit and numerical value using the .unit and .value attributes:

>>> length.value  
10.0

>>> length.unit
Unit("m")

Arithmetic With Units#

Quantity objects propagate units through arithmetic operations:

>>> distance_start = 10 * u.mm
>>> distance_end = 23 * u.km
>>> displacement = distance_end - distance_start
>>> displacement
<Quantity 22.99999 km>

>>> time = 15 * u.minute
>>> speed = displacement / time
>>> speed
<Quantity 1.53333267 km / min>

However, operations with incompatible units raise an error:

>>> displacement + time
Traceback (most recent call last):
...
astropy.units.core.UnitConversionError: Can only apply 'add' function to quantities with compatible dimensions

Converting Units#

Quantity objects can also be converted to other units or unit systems:

>>> length.to(u.km)
<Quantity 0.01 km>

>>> length.cgs
<Quantity 1000. cm>

Unit Equivalencies#

It is commonplace to convert between units which are only compatible under certain assumptions. For example, in spectroscopy, spectral energy and wavelength are equivalent given the relation \(E=hc/\lambda\). If we try to convert a wavelength to energy using what we learned in the previous section, we get an exception because length and energy are, in general, not compatible units:

>>> length.to(u.keV)
Traceback (most recent call last):
...
astropy.units.core.UnitConversionError: 'm' (length) and 'keV' (energy/torque/work) are not convertible

However, we can perform this conversion using the spectral equivalency:

>>> length.to(u.keV, equivalencies=u.spectral())
<Quantity 1.23984198e-10 keV>

An equivalency common in solar physics is conversion of angular distances in the plane of the sky to physical distances on the Sun. To perform this conversion, sunpy provides solar_angle_equivalency, which requires specifying the location at which that angular distance was measured:

>>> from sunpy.coordinates import get_earth
>>> from sunpy.coordinates.utils import solar_angle_equivalency

>>> length.to(u.arcsec, equivalencies=solar_angle_equivalency(get_earth("2013-10-28")))
INFO: Apparent body location accounts for 495.82 seconds of light travel time [sunpy.coordinates.ephemeris]
<Quantity 1.38763748e-05 arcsec>

Note that in the above example we made use of sunpy.coordinates.get_earth. We will talk more about coordinates in the Coordinates section of this tutorial. For now, it is just important to know that this function returns the location of the Earth on 2013 October 28.

Dropping Units#

Not every package in the scientific Python ecosystem understands units. As such, it is sometimes necessary to drop the units before passing Quantity to such functions. As shown above, you can retrieve the just the numerical value of a Quantity:

>>> length.to_value()  
10.0
>>> length.to_value(u.km)  
0.01

Quantities as function arguments#

When calling a function that relies on inputs corresponding to physical quantities, there is often an implicit assumption that these input arguments are expressed in the expected units of that function. For instance, if we define a function to calculate speed as above, the inputs should correspond to a distance and a time:

>>> def speed(length, time):
...     return length / time

However, this assumes that the two arguments passed in have units consistent with distance and time without checking. The quantity_input decorator, combined with function annotations, enforces compatible units on the function inputs:

>>> @u.quantity_input
... def speed(length: u.m, time: u.s):
...     return length / time

Now when this function is called, if the inputs are not convertible to the units specified, an error will be raised stating that the units are incorrect or missing:

>>> speed(1*u.m, 10*u.m)
Traceback (most recent call last):
...
astropy.units.core.UnitsError: Argument 'time' to function 'speed' must be in units convertible to 's'.

>>> speed(1*u.m, 10)
...
Traceback (most recent call last):
...
TypeError: Argument 'time' to function 'speed' has no 'unit' attribute. ... pass in an astropy Quantity instead.

The units of the inputs need only be compatible with those in the function definition. For example, passing in a time in minutes still works even though we specified time: u.s:

>>> speed(1*u.m, 1*u.minute)
<Quantity 1. m / min>

Note that the units of the output are dependent on the units of the inputs. To ensure consistent units on the output of our function, we add an additional function annotation to force the output to always be converted to m/s before returning an answer:

>>> @u.quantity_input
... def speed(length: u.m, time: u.s) -> u.m/u.s:
...     return length / time
>>> speed(1*u.m, 1*u.minute)
<Quantity 0.01666667 m / s>

Times#

In this section of the tutorial, you will learn how times are represented and manipulated in sunpy. sunpy makes extensive use of the astropy.time module for this task. By the end of this section of the tutorial, you will learn how to parse times from different formats as well as construct and inspect time ranges.

Parsing Times#

Solar data is associated with a number of different time formats. To handle all these formats, sunpy has sunpy.time.parse_time() that accepts a variety of inputs, and returns a consistent Time object. You might have come across another way of storing time that’s built into Python itself, datetime.datetime. datetime does not provide support for common time formats used in solar physics or leap seconds, hence the use of astropy.time.Time throughout sunpy.

Here’s a few examples of using parse_time to create Time objects:

>>> from sunpy.time import parse_time

>>> parse_time('2007-05-04T21:08:12')
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>
>>> parse_time(894316092.00000000, format='utime')
<Time object: scale='utc' format='utime' value=894316092.0>

See the documentation for sunpy.time.parse_time for a full list of allowed arguments.

Time Ranges#

Another standard task in data analysis is dealing with pairs of times or time ranges. To deal with time ranges sunpy provides the sunpy.time.TimeRange object. A sunpy.time.TimeRange object can be created by providing a start time and an end time:

>>> from sunpy.time import TimeRange

>>> time_range = TimeRange('2010/03/04 00:10', '2010/03/04 00:20')

TimeRange makes use of sunpy.time.parse_time() so it can accept a wide variety of time formats. Alternatively, you can specify a start time and a duration:

>>> import astropy.units as u

>>> time_range = TimeRange('2010/03/04 00:10', 400 * u.second)

The time range objects provides a number of useful functions. For example, you can easily get the time at the center of your interval or the length of the interval:

>>> time_range.center
<Time object: scale='utc' format='isot' value=2010-03-04T00:13:20.000>
>>> time_range.seconds
<Quantity 400. s>

A time range can also be easily split into sub-intervals of equal length, for example to split a TimeRange object into two new TimeRange objects:

>>> time_range.split(2)
[   <sunpy.time.timerange.TimeRange object ...>
    Start: 2010-03-04 00:10:00
    End:   2010-03-04 00:13:20
    Center:2010-03-04 00:11:40
    Duration:0.002314814814814825 days or
           0.0555555555555558 hours or
           3.333333333333348 minutes or
           200.00000000000088 seconds
,    <sunpy.time.timerange.TimeRange object ...>
    Start: 2010-03-04 00:13:20
    End:   2010-03-04 00:16:40
    Center:2010-03-04 00:15:00
    Duration:0.002314814814814825 days or
           0.0555555555555558 hours or
           3.333333333333348 minutes or
           200.00000000000088 seconds
]

Check out the code reference for the sunpy.time.TimeRange object for more information.

Coordinates#

This section of the guide introduces how coordinates are represented in sunpy. sunpy makes use of the astropy.coordinates module for this task.

In much the same way as units are used for representing physical quantities, sunpy uses astropy.coordinates to represent points in physical space. This applies to both points in 3D space and projected coordinates in images.

The astropy coordinates module is primarily used through the SkyCoord class, which also makes use of the astropy units system:

>>> from astropy.coordinates import SkyCoord
>>> import astropy.units as u

To enable the use of the solar physics specific frames defined in sunpy we also need to import them:

>>> from sunpy.coordinates import frames

A SkyCoord object to represent a point on the Sun can then be created:

>>> coord = SkyCoord(70*u.deg, -30*u.deg, obstime="2017-08-01",
...                  frame=frames.HeliographicStonyhurst)
>>> coord
<SkyCoord (HeliographicStonyhurst: obstime=2017-08-01T00:00:00.000, rsun=695700.0 km): (lon, lat) in deg
    (70., -30.)>

This SkyCoord object can then be transformed to any other coordinate frame defined either in Astropy or sunpy (see Supported Coordinate Systems for a list of sunpy frames), for example to transform from the original Stonyhurst frame to a Helioprojective frame:

>>> coord.transform_to(frames.Helioprojective(observer="earth"))
<SkyCoord (Helioprojective: obstime=2017-08-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, km)
    (769.96270814, -498.89715922, 1.51668773e+08)>

It is also possible to convert three dimensional positions to astrophysical frames defined in Astropy, for example ICRS.

>>> coord.transform_to('icrs')
<SkyCoord (ICRS): (ra, dec, distance) in (deg, deg, km)
  (49.84856512, 0.05394699, 1417743.94689472)>

SkyCoord and all coordinate frames support array coordinates. These work the same as single-value coordinates, but they store multiple coordinates in a single object. When you’re going to apply the same operation to many different coordinates, this is a better choice than a list of SkyCoord objects, because it will be much faster than applying the operation to each SkyCoord in a for loop:

>>> coord = SkyCoord([-500, 400]*u.arcsec, [100, 200]*u.arcsec, frame=frames.Helioprojective)
>>> coord
<SkyCoord (Helioprojective: obstime=None, rsun=695700.0 km, observer=None): (Tx, Ty) in arcsec
    [(-500.,  100.), ( 400.,  200.)]>
>>> coord[0]
<SkyCoord (Helioprojective: obstime=None, rsun=695700.0 km, observer=None): (Tx, Ty) in arcsec
    (-500.,  100.)>

Observer Location#

Both Helioprojective and Heliocentric frames are defined based on the position of the observer. Therefore to transform either of these frames to a different frame the location of the observer must be known. The observer can be specified for a coordinate object using the observer argument to SkyCoord. For sunpy to calculate the location of Earth or another solar-system body, it must know the time associated with the coordinate; this is specified with the obstime argument.

Using the observer location it is possible to convert a coordinate as seen by one observer to a coordinate seen by another:

>>> hpc = SkyCoord(0*u.arcsec, 0*u.arcsec, observer="earth",
...                 obstime="2017-07-26",
...                 frame=frames.Helioprojective)

>>> hpc.transform_to(frames.Helioprojective(observer="venus",
...                                          obstime="2017-07-26"))
<SkyCoord (Helioprojective: obstime=2017-07-26T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'venus'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
    (-1285.47497992, 106.20918654, 0.72405937)>

Using Coordinates with Maps#

sunpy Map uses coordinates to specify locations on the image, and to plot overlays on plots of maps. When a Map is created, a coordinate frame is constructed from the header information. This can be accessed using .coordinate_frame:

>>> from astropy.coordinates import SkyCoord
>>> import astropy.units as u

>>> import sunpy.map
>>> from sunpy.data.sample import AIA_171_IMAGE  

>>> amap = sunpy.map.Map(AIA_171_IMAGE)  
>>> amap.coordinate_frame  
<Helioprojective Frame (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>)>

This can be used when creating a SkyCoord object to set the coordinate system to that image:

>>> coord = SkyCoord(100 * u.arcsec, 10*u.arcsec, frame=amap.coordinate_frame)  
>>> coord  
<SkyCoord (Helioprojective: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>): (Tx, Ty) in arcsec
    (100., 10.)>

The SkyCoord object can be converted to a pair of pixels using GenericMap.wcs.world_to_pixel:

>>> pixels = amap.wcs.world_to_pixel(coord)  
>>> pixels  
(array(551.7680511), array(515.18266871))

This SkyCoord object could also be used to plot a point on top of the map:

>>> import matplotlib.pyplot as plt

>>> fig = plt.figure()
>>> ax = plt.subplot(projection=amap)  
>>> amap.plot()  
<matplotlib.image.AxesImage object at ...>
>>> ax.plot_coord(coord, 'o')  
[<matplotlib.lines.Line2D object at ...]

(Source code, png, hires.png, pdf)

_images/coordinates-1.png

For more information on coordinates see Coordinates.

Acquiring Data#

This section of the tutorial introduces ways to obtain different kind of solar data from different places. The main tutorial below focuses on Fido, which is a generic search interface that sunpy provides. There are some data-provider specific tutorials that you might want to visit after going through this tutorial:

Finding and Downloading Data from JSOC#

Joint Science Operations Center (JSOC) contains data products from the Solar Dynamics Observatory, as well as certain other missions and instruments. These data are available from the JSOC database, which can be directly accessed by the online JSOC interface.

sunpy’s JSOC Client provides an easier interface to query for JSOC data and make export requests. It uses drms module as its backend.

There are two ways of downloading JSOC data. One way is using sunpy’s unified search interface, known as Fido. Fido supplies a single, easy and consistent way to to obtain most forms of solar physics data. An alternative way to fetch data from JSOC is by using the underlying JSOC Client. This option can be preferred when you need to separate the staging and downloading steps, which is not supported by Fido.

The JSOC stages data before you can download it, so a JSOC query is a three stage process. First you query the JSOC for records and a table of these records is returned. Then you can request these records to be staged for download and then you can download them. Fido combines the last two stages into a single call to fetch.

Setup#

sunpy’s Fido module is in sunpy.net.

It can be imported as follows:

>>> from sunpy.net import Fido, attrs as a

The JSOC client handles the particulars of how the data from the data provider is downloaded to your computer.

Warning

You must have an email address registered with JSOC before you are allowed to make a request. See this to register your email address.

Querying the JSOC#

To search for data in JSOC, your query needs at minimum, a “Series” name and a “PrimeKey”.

>>> print(a.jsoc.Series)
sunpy.net.jsoc.attrs.Series

The JSOC Series to Download.

            Attribute Name           Client             Full Name                                                Description
---------------------------------- ------ ---------------------------------- --------------------------------------------------------------------------------
aia_flatfield                      JSOC   aia.flatfield                      AIA flatfield
aia_lev1                           JSOC   aia.lev1                           AIA Level 1
aia_lev1_euv_12s                   JSOC   aia.lev1_euv_12s                   AIA Level 1, 12 second cadence
aia_lev1_uv_24s                    JSOC   aia.lev1_uv_24s                    AIA Level 1, 24 second cadence
aia_lev1_vis_1h                    JSOC   aia.lev1_vis_1h                    AIA Level 1, 3600 second cadence
aia_master_pointing3h              JSOC   aia.master_pointing3h              Master Pointing Parameters
aia_response                       JSOC   aia.response                       AIA instrument response table
aia_temperature_summary_300s       JSOC   aia.temperature_summary_300s       Temperature Statistics from AIA Housekeeping - Thermal Packet
hmi_b_135s                         JSOC   hmi.b_135s                         Full-disk Milne-Eddington inversion with the azimuth disambiguation informati...
    ...

Different PrimeKeys are supported by different Series, and you can find out the PrimeKeys supported in any Series by:

>>> import drms
>>> client = drms.Client()  
>>> print(client.pkeys('hmi.m_720s'))  
['T_REC', 'CAMERA']

The most common PrimeKey, that is supported by every Series is Time, that is denoted by T_REC or T_OBS. Hence, Time can always be passed as an attribute while building a query. Wavelength is another pre-defined attribute which is a PrimeKey. Other PrimeKeys which need to be passed should be manually passed in PrimeKey. This will be explained later in this tutorial.

Constructing a Basic Query#

Let’s start with a very simple query. We could ask for all “hmi.v_45s” series data between January 1st 2014 from 00:00 to 01:00:

>>> res = Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...                   a.jsoc.Series('hmi.v_45s'))  

This returns an UnifiedResponse object containing information on the available online files which fit the criteria specified by the attrs objects in the above call. It does not download the files.

To see a summary of results of our query, simply type the name of the variable set to the Fido search, in this case, res:

>>> res  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

81 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

         T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
----------------------- -------- ---------- -------- -------
2014.01.01_00:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:01:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:02:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:04:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:05:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:06:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:06:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:07:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
                    ...      ...        ...      ...     ...
2014.01.01_00:54:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:54:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:55:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:56:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:58:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:59:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
Length = 81 rows

Now, let’s break down the arguments of Fido.search to understand better what we’ve done. The first argument a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00') sets the start and end times for the query (any date/time format understood by sunpy’s parse_time can be used to specify dates and time). The Time attribute takes UTC time, as default. If you need to pass a Time in some other time scale, such as TAI, pass an astropy.time.Time object:

>>> import astropy.time

Then, the Time attribute can be passed as:

>>> a.Time(astropy.time.Time('2014-01-01T00:00:00', scale='tai'), astropy.time.Time('2014-01-01T01:00:00', scale='tai'))
<sunpy.net.attrs.Time(2014-01-01 00:00:00.000, 2014-01-01 01:00:00.000)>

The second argument:

>>> a.jsoc.Series('hmi.v_45s')
<sunpy.net.jsoc.attrs.Series(hmi.v_45s: Dopplergrams with a cadence of 45 seconds) object ...>

sets the series we are looking for.

So what is going on here? The notion is that a JSOC query has a set of attribute objects, imported as a.jsoc, that are specified to construct the query.

a.jsoc.Series() is compulsory to be provided in each of the jsoc queries. Apart from this, at least one PrimeKey must be passed (generally a.Time()).

Querying with other PrimeKeys#

Other than Time, one other PrimeKey is supported with in-built attribute. In case of AIA series, a.Wavelength() can be passed as a PrimeKey:

>>> import astropy.units as u
>>> res = Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...                               a.jsoc.Series('aia.lev1_euv_12s'),
...                               a.Wavelength(304*u.AA))  

Note that, only Time and Wavelength are in-built attributes here. If you need to pass any other PrimeKey, it should be passed like this:

>>> a.jsoc.PrimeKey('HARPNUM', '4864')
<sunpy.net.jsoc.attrs.PrimeKey object at ...>
('HARPNUM', '4864')

If 2 or more PrimeKeys need to be passed together:

>>> a.jsoc.PrimeKey('HARPNUM', '4864') & a.jsoc.PrimeKey('CAMERA', '2')
<AttrAnd([<sunpy.net.jsoc.attrs.PrimeKey object at ...>
('HARPNUM', '4864'), <sunpy.net.jsoc.attrs.PrimeKey object at ...>
('CAMERA', '2')])>

Also, note that the pre-defined PrimeKeys, Time and Wavelength can also be passed as above, but you need to specify the exact keyword for it:

>>> a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'), a.jsoc.PrimeKey('WAVELNTH', '161')
(<sunpy.net.attrs.Time(2014-01-01 00:00:00.000, 2014-01-01 01:00:00.000)>, <sunpy.net.jsoc.attrs.PrimeKey object at ...>
('WAVELNTH', '161'))

If the correct keyword is not specified, or the passed PrimeKey is not supported by the given series, a meaningful error will be thrown, which will give you the PrimeKeys supported by that series. Hence, by looking at the error, one can easily retry building the query with correct PrimeKeys.

Another important thing to note is that, Wavelength when passed through in-built attribute, should be passed as an astropy quantity. Specifying spectral units in arguments is necessary or an error will be raised. For more information on units, see units. But, when the same is passed through PrimeKey attribute, it should be passed as a string. All other PrimeKey values passed through PrimeKey attribute, must be passed as a string.

Manually specifying keyword data to fetch#

Upon doing Fido.search() as described above, only a limited set of keywords are returned in the response object. These default keywords are 'DATE', 'TELESCOP', 'INSTRUME', 'T_OBS' and 'WAVELNTH'.

If you want to get a manual set of keywords in the response object, you can pass the set of keywords using show() method.

>>> res = Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...                   a.jsoc.Series('hmi.v_45s'))  
>>> res.show('TELESCOP', 'INSTRUME', 'T_OBS')  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

81 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

TELESCOP  INSTRUME           T_OBS
-------- ---------- -----------------------
 SDO/HMI HMI_FRONT2 2014.01.01_00:00:37_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:01:22_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:02:07_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:02:52_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:03:37_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:04:22_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:05:07_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:05:52_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:06:37_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:07:22_TAI
     ...        ...                     ...
 SDO/HMI HMI_FRONT2 2014.01.01_00:53:52_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:54:37_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:55:22_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:56:07_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:56:52_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:57:37_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:58:22_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:59:07_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_00:59:52_TAI
 SDO/HMI HMI_FRONT2 2014.01.01_01:00:37_TAI
Length = 81 rows

Passing an incorrect keyword won’t throw an error, but the corresponding column in the table will not be displayed.

To display all of the columns, we can use show() without passing any arguments:

>>> res.show()  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

81 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

        DATE                DATE__OBS                DATE-OBS        ...                        CODEVER3                        CALVER64
-------------------- ----------------------- ----------------------- ... ------------------------------------------------------ --------
2014-01-05T17:46:02Z 2013-12-31T23:59:39.20Z 2013-12-31T23:59:39.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:47:10Z 2014-01-01T00:00:24.20Z 2014-01-01T00:00:24.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:48:18Z 2014-01-01T00:01:09.20Z 2014-01-01T00:01:09.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:49:25Z 2014-01-01T00:01:54.20Z 2014-01-01T00:01:54.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:50:34Z 2014-01-01T00:02:39.20Z 2014-01-01T00:02:39.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:51:42Z 2014-01-01T00:03:24.20Z 2014-01-01T00:03:24.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:52:50Z 2014-01-01T00:04:09.20Z 2014-01-01T00:04:09.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:53:59Z 2014-01-01T00:04:54.20Z 2014-01-01T00:04:54.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:55:08Z 2014-01-01T00:05:39.20Z 2014-01-01T00:05:39.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:56:16Z 2014-01-01T00:06:24.20Z 2014-01-01T00:06:24.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
                 ...                     ...                     ... ...                                                    ...      ...
2014-01-05T17:35:43Z 2014-01-01T00:52:54.20Z 2014-01-01T00:52:54.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:36:54Z 2014-01-01T00:53:39.20Z 2014-01-01T00:53:39.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:38:01Z 2014-01-01T00:54:24.20Z 2014-01-01T00:54:24.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:39:09Z 2014-01-01T00:55:09.20Z 2014-01-01T00:55:09.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:40:17Z 2014-01-01T00:55:54.20Z 2014-01-01T00:55:54.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:41:25Z 2014-01-01T00:56:39.20Z 2014-01-01T00:56:39.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:42:33Z 2014-01-01T00:57:24.20Z 2014-01-01T00:57:24.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:43:41Z 2014-01-01T00:58:09.20Z 2014-01-01T00:58:09.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:44:52Z 2014-01-01T00:58:54.20Z 2014-01-01T00:58:54.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
2014-01-05T17:46:03Z 2014-01-01T00:59:39.20Z 2014-01-01T00:59:39.20Z ... $Id: polcal.c,v 1.5 2013/12/22 22:54:08 couvidat Exp $     4370
Length = 81 rows

Using Segments#

In some cases, more than 1 file are present for the same set of query. These data are distinguished by what are called Segments. It is necessary to specify the Segment which you need to download. Providing a segment won’t have any affect on the response object returned, but this will be required later, while making an export request.

A list of supported segments of a series, say hmi.sharp_720s can be obtained by:

>>> client = drms.Client()  
>>> si = client.info('hmi.sharp_720s')  
>>> print(si.segments.index.values)  
['magnetogram' 'bitmap' 'Dopplergram' 'continuum' 'inclination' 'azimuth'
    'field' 'vlos_mag' 'dop_width' 'eta_0' 'damping' 'src_continuum'
    'src_grad' 'alpha_mag' 'chisq' 'conv_flag' 'info_map' 'confid_map'
    'inclination_err' 'azimuth_err' 'field_err' 'vlos_err' 'alpha_err'
    'field_inclination_err' 'field_az_err' 'inclin_azimuth_err'
    'field_alpha_err' 'inclination_alpha_err' 'azimuth_alpha_err' 'disambig'
    'conf_disambig']

Also, if you provide an incorrect segment name, it will throw a meaningful error, specifying which segment values are supported by the given series:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.sharp_720s'),
...             a.jsoc.Segment('image'))  
Traceback (most recent call last):
...
ValueError: Unexpected Segments were passed. The series hmi.sharp_720s contains the following Segments ['magnetogram', 'bitmap', 'Dopplergram', 'continuum', 'inclination', 'azimuth', 'field', 'vlos_mag', 'dop_width', 'eta_0', 'damping', 'src_continuum', 'src_grad', 'alpha_mag', 'chisq', 'conv_flag', 'info_map', 'confid_map', 'inclination_err', 'azimuth_err', 'field_err', 'vlos_err', 'alpha_err', 'field_inclination_err', 'field_az_err', 'inclin_azimuth_err', 'field_alpha_err', 'inclination_alpha_err', 'azimuth_alpha_err', 'disambig', 'conf_disambig']

To get files for more than 1 segment at the same time, chain a.jsoc.Segment() using AND operator:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.sharp_720s'),
...             a.jsoc.Segment('continuum') & a.jsoc.Segment('magnetogram'))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

61 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

            T_REC          TELESCOP  INSTRUME WAVELNTH CAR_ROT
----------------------- -------- --------- -------- -------
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
                    ...      ...       ...      ...     ...
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
Length = 61 rows

Using Keywords#

In some cases, you might want to filter out files based on key metadata, also called keywords.

A list of supported keywords of a series, say hmi.sharp_720s can be obtained by:

>>> client = drms.Client()  
>>> keywords = client.keys('hmi.sharp_720s')  
>>> print(keywords)  
['cparms_sg000', 'magnetogram_bzero', 'magnetogram_bscale', 'cparms_sg001', 'bitmap_bzero', 'bitmap_bscale', 'cparms_sg002', 'Dopplergram_bzero', 'Dopplergram_bscale', 'cparms_sg003', 'continuum_bzero', 'continuum_bscale', 'cparms_sg004', 'inclination_bzero', 'inclination_bscale', 'cparms_sg005', 'azimuth_bzero', 'azimuth_bscale', 'cparms_sg006', 'field_bzero', 'field_bscale', 'cparms_sg007', ... 'ERRJHT', 'ERRVF']

Each keyword needs to be compared to a value, e.g., a.jsoc.Keyword("bitmap_bzero") == 0 or a.jsoc.Keyword("bitmap_bzero") > 1.

An example of this is:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.sharp_720s'),a.jsoc.Keyword('bitmap_bzero') == 0) 
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

61 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

         T_REC          TELESCOP  INSTRUME WAVELNTH CAR_ROT
----------------------- -------- --------- -------- -------
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
                    ...      ...       ...      ...     ...
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
Length = 61 rows

You can pass multiple keywords and they will be chained together inside the query:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'), a.jsoc.Series('hmi.sharp_720s'),
...             a.jsoc.Keyword('bitmap_bzero') == 0, a.jsoc.Keyword('continuum_bscale') > 0) 
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

61 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

         T_REC          TELESCOP  INSTRUME WAVELNTH CAR_ROT
----------------------- -------- --------- -------- -------
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
                    ...      ...       ...      ...     ...
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:12:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:24:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:36:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_00:48:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_SIDE1   6173.0    2145
Length = 61 rows

If you provide a keyword without a comparison it will raise an error:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.sharp_720s'),
...             a.jsoc.Keyword('bitmap_bzero'))  
Traceback (most recent call last):
...
ValueError: Keyword 'bitmap_bzero' needs to have a comparison to a value.

If you provide an incorrect keyword name it will also raise a error:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.sharp_720s'),
...             a.jsoc.Keyword('bac') == 0)  
Traceback (most recent call last):
...
ValueError: Keyword: 'bac' is not supported by series: hmi.sharp_720s
Using Sample#

In case you need to query for data, at some interval of time, say every 10 min, you can pass it using Sample. In other words, if you need to query for “hmi.v_45s” series data between January 1st 2014 from 00:00 to 01:00 at 10 minute intervals, you can do:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.v_45s'), a.Sample(10*u.min))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

7 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

            T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
----------------------- -------- ---------- -------- -------
2014.01.01_00:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:10:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:20:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:30:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:39:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:49:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:59:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145

Note that the argument passed in a.Sample() must be an Astropy quantity, convertible into seconds.

Constructing complex queries#

Complex queries can be built using “OR” operators. Let’s look for 2 different series data at the same time:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.v_45s') | a.jsoc.Series('aia.lev1_euv_12s'))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

81 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

            T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
----------------------- -------- ---------- -------- -------
2014.01.01_00:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:01:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:02:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:04:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:05:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:06:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:06:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:07:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
                    ...      ...        ...      ...     ...
2014.01.01_00:54:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:54:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:55:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:56:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:58:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:59:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
Length = 81 rows

2107 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

        T_REC         TELESCOP INSTRUME WAVELNTH CAR_ROT
-------------------- -------- -------- -------- -------
2014-01-01T00:00:01Z  SDO/AIA    AIA_4       94    2145
2014-01-01T00:00:01Z  SDO/AIA    AIA_1      131    2145
2014-01-01T00:00:01Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:00:01Z  SDO/AIA    AIA_2      193    2145
2014-01-01T00:00:01Z  SDO/AIA    AIA_2      211    2145
2014-01-01T00:00:01Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:00:01Z  SDO/AIA    AIA_1      335    2145
2014-01-01T00:00:13Z  SDO/AIA    AIA_4       94    2145
2014-01-01T00:00:13Z  SDO/AIA    AIA_1      131    2145
2014-01-01T00:00:13Z  SDO/AIA    AIA_3      171    2145
                    ...      ...      ...      ...     ...
2014-01-01T00:59:49Z  SDO/AIA    AIA_2      211    2145
2014-01-01T00:59:49Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:59:49Z  SDO/AIA    AIA_1      335    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_4       94    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_1      131    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_3      171    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_2      193    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_2      211    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_4      304    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_1      335    2145
Length = 2107 rows

The two series names are joined together by the operator |. This is the “OR” operator. Think of the above query as setting a set of conditions which get passed to the JSOC.

Let’s say you want all the “hmi.v_45s” data from two separate days:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00') |
...             a.Time('2014-01-02T00:00:00', '2014-01-02T01:00:00'),
...             a.jsoc.Series('hmi.v_45s'))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

81 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

            T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
----------------------- -------- ---------- -------- -------
2014.01.01_00:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:01:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:02:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:04:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:05:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:06:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:06:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:07:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
                    ...      ...        ...      ...     ...
2014.01.01_00:54:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:54:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:55:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:56:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:58:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:59:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
Length = 81 rows

81 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

            T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
----------------------- -------- ---------- -------- -------
2014.01.02_00:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:01:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:02:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:03:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:03:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:04:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:05:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:06:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:06:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:07:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
                    ...      ...        ...      ...     ...
2014.01.02_00:54:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:54:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:55:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:56:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:57:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:57:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:58:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_00:59:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_01:00:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.02_01:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
Length = 81 rows

Each of the arguments in this query style can be thought of as setting conditions that the returned records must satisfy.

It should be noted that AND operator is supported by some of the attributes only. The attributes which support “&” are PrimeKey and Segment. Using “&” with any other attributes will throw an error.

Downloading data#

To download the files located by search, you can download them by fetch:

>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...             a.jsoc.Series('hmi.v_45s') | a.jsoc.Series('aia.lev1_euv_12s'),
...             a.jsoc.Notify('solar@example.com')  
>>> downloaded_files = Fido.fetch(res)  

To export a request for download, you must have used the sunpy.net.jsoc.attrs.Notify attribute at search time to specify your email address.

Note

Only complete searches can be downloaded from JSOC This means that no slicing operations performed on the results object will affect the number of files downloaded.

Using JSOCClient for complex usage#

Fido interface uses JSOCClient in its backend, and combines the last 2 stages the JSOC process into one. You can directly use the JSOC client to make queries, instead of the Fido client. This will allow you to separate the 3 stages of the JSOC process, and perform it individually, hence allowing a greater control over the whole process.

Setup#

sunpy’s JSOC module is in net. It can be imported as follows:

>>> from sunpy.net import jsoc
>>> client = jsoc.JSOCClient()  

This creates your client object.

Making a query#

Querying JSOC using the JSOC client is very similar to what we were doing with Fido. As above, we have to make sure we have an email address registered with JSOC before you are allowed to make a request. See this to register your email address. We can add an email address to the search query with the sunpy.net.jsoc.attrs.Notify attribute. Please note you can search without this but right now, you can not add the email address after the search:

>>> res = client.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
...                     a.jsoc.Series('hmi.v_45s'),
...                     a.jsoc.Notify('sunpy@sunpy.org'))  

Apart from the function name, everything is the same. You need to pass the same values in the search as you did in search. Complex queries can be built in a similar way, and all other things are the same.

Staging the request#

JSOC is a 3-stage process, and after getting the query results, we need to stage a request for the data to be downloaded. Only then, can we download them. The download request can be staged like this:

>>> requests = client.request_data(res)  
>>> print(requests)  
<ExportRequest id="JSOC_20170713_1461", status=0>

The function request_data stages the request. It returns a drms.client.ExportRequest object, which has many attributes. The most important ones are id and status. Only when the status is 0, we can move to the third step, i.e., downloading the data.

If you are making more than 1 query at a time, it will return a list of ExportRequest objects. Hence, access the list elements accordingly. You can get the id and status of the request (if it is not a list) by:

>>> requests.id  
JSOC_20170713_1461
>>> requests.status  
0
Downloading data#

Once the status code is 0 you can download the data using the get_request method:

>>> res = client.get_request(requests)  

This returns a Results instance which can be used to watch the progress of the download:

>>> res.wait(progress=True)   

Searching the Heliophysics Event Knowledgebase#

The Heliophysics Event Knowledgebase (HEK) is a repository of feature and event information about the Sun. Entries are generated both by automated algorithms and human observers. sunpy accesses this information through the hek module, which was developed through support from the European Space Agency Summer of Code in Space (ESA-SOCIS) 2011.

A simple query#

To search the HEK, you need a start time, an end time, and an event type. Times are specified as strings or Python datetime objects. Event types are specified as upper case, two letter strings, and are identical to the two letter abbreviations found at the HEK website, http://www.lmsal.com/hek/VOEvent_Spec.html.

>>> from sunpy.net import attrs as a
>>> from sunpy.net import Fido

>>> tstart = '2011/08/09 07:23:56'
>>> tend = '2011/08/09 12:40:29'
>>> event_type = 'FL'
>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type))  

tstart and tend defines the start and end times of the query, and event_type specifies the event type which in this example we are searching for flares defined as FL. Fido.search goes out to the web, contacts the HEK, and queries it for the information you have requested. Event data for ALL flares available in the HEK within the time range 2011/08/09 07:23:56 UT - 2011/08/09 12:40:20 UT will be returned, regardless of which feature recognition method used to detect the flare.

Let’s break down the arguments of Fido.search. The first argument:

>>> a.Time(tstart,tend)  

sets the start and end times for the query.

The second argument:

>>> a.hek.EventType(event_type)  

sets the type of event to look for. Since we have defined event_type = 'FL', this sets the query to look for flares. We could have also set the flare event type using the syntax:

>>> a.hek.FL  

There is more information on the attributes below.

The result#

So, how many flare detections did the query turn up?

The result object returned by Fido.search is a UnifiedResponse object which contains all the results from any clients used in the search. The first thing we need to do is access the results from the HEK client, the only ones for the query we gave:

>>> len(result['hek'])  
19

This object is an astropy.table.Table object with the columns which correspond to the parameters listed at http://www.lmsal.com/hek/VOEvent_Spec.html.

You can inspect all results very simply:

>>> result['hek']  

Remember, the HEK query we made returns all the flares in the time-range stored in the HEK, regardless of the feature recognition method. The HEK parameter which stores the the feature recognition method is called “frm_name”. We can select just this column:

>>> result["hek"]["frm_name"]  
<QueryResponseColumn name='frm_name' dtype='str32' length=19>
                          asainz
                          asainz
                          asainz
                          asainz
                          asainz
                          asainz
                          asainz
               SSW Latest Events
                            SWPC
Flare Detective - Trigger Module
Flare Detective - Trigger Module
                            SWPC
               SSW Latest Events
Flare Detective - Trigger Module
Flare Detective - Trigger Module
Flare Detective - Trigger Module
Flare Detective - Trigger Module
Flare Detective - Trigger Module
Flare Detective - Trigger Module

It is likely each flare on the Sun was actually detected multiple times by many different methods.

More complex queries#

There are two key features you need to know in order to make more complex queries. Firstly, the attribute module - attrs.hek - describes all the parameters stored by the HEK as listed in http://www.lmsal.com/hek/VOEvent_Spec.html, and the HEK client makes these parameters searchable.

To explain this, let’s have a closer look at attrs.hek. By using the help command; scroll down to section DATA you will see:

>>> help(a.hek) 
Help on module sunpy.net.hek.attrs in sunpy.net.hek:

NAME
    sunpy.net.hek.attrs

DESCRIPTION
    Attributes that can be used to construct HEK queries. They are different to
    the VSO ones in that a lot of them are wrappers that conveniently expose
    the comparisons by overloading Python operators. So, e.g., you are able
    to say AR & AR.NumSpots < 5 to find all active regions with less than 5 spots.
    As with the VSO query, you can use the fundamental logic operators AND and OR
    to construct queries of almost arbitrary complexity. Note that complex queries
    result in multiple requests to the server which might make them less efficient.

CLASSES
...

You’ll see that one of the attributes is a flare object:

FL = <sunpy.net.hek.attrs.FL object>

We can replace a.hek.EventType('FL') with a.hek.FL - they do the same thing, setting the query to look for flare events. Both methods of setting the event type are provided as a convenience

Let’s look further at the FRM attribute:

>>> help(a.hek.FRM) 
Help on FRM in module sunpy.net.hek.attrs object:

class FRM(builtins.object)
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables
 |
 |  __weakref__
 |      list of weak references to the object
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  Contact = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  HumanFlag = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  Identifier = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  Institute = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  Name = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  ParamSet = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  SpecificID = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  URL = <sunpy.net.hek.attrs._StringParamAttrWrapper object>
 |
 |  VersionNumber = <sunpy.net.hek.attrs._StringParamAttrWrapper object>

Let’s say I am only interested in those flares identified by the SSW Latest Events tool. One can retrieve those entries only from the HEK with the following command:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type), a.hek.FRM.Name == 'SSW Latest Events')  
>>> len(result[0])  
2

We can also retrieve all the entries in the time range which were not made by SSW Latest Events with the following command:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type), a.hek.FRM.Name != 'SSW Latest Events')  
>>> len(result[0])  
19

We are using Python’s comparison operators to filter the returns from Fido. Other comparisons are possible. For example, let’s say I want all the flares that have a peak flux of over 4000.0:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type), a.hek.FL.PeakFlux > 4000.0)  
>>> len(result[0])  
1

Multiple comparisons can be included. For example, let’s say I want all the flares with a peak flux above 1000 AND west of 800 arcseconds from disk center of the Sun:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type), a.hek.Event.Coord1 > 800, a.hek.FL.PeakFlux > 1000)  
>>> len(result[0])  
7

Multiple comparison operators can be used to filter the results back from the HEK.

The second important feature is that the comparisons we’ve made above can be combined using Python’s logical operators. This makes complex queries easy to create. However, some caution is advisable. Let’s say we want all the flares west of 50 arcseconds OR have a peak flux over 1000:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type), (a.hek.Event.Coord1 > 50) or (a.hek.FL.PeakFlux > 1000))  

and as a check:

>>> result["hek"]["fl_peakflux"] 
<QueryResponseColumn name='fl_peakflux' dtype='object' length=17>
   None
   None
   None
   None
   None
   None
   None
2326.86
1698.83
   None
   None
2360.49
3242.64
1375.93
6275.98
923.984
1019.83

>>> result["hek"]["event_coord1"] 
<QueryResponseColumn name='event_coord1' dtype='float64' length=17>
 51.0
 51.0
 51.0
924.0
924.0
924.0
 69.0
883.2
883.2
 69.0
 69.0
883.2
883.2
883.2
883.2
883.2
883.2

Note that some of the fluxes are returned as “None”. This is because some feature recognition methods for flares do not report the peak flux. However, because the location of “event_coord1” is greater than 50, the entry from the HEK for that flare detection is returned.

Let’s say we want all the flares west of 50 arcseconds AND have a peak flux over 1000:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type), (a.hek.Event.Coord1 > 50) and (a.hek.FL.PeakFlux > 1000))  

>>> result["hek"]["fl_peakflux"] 
<QueryResponseColumn name='fl_peakflux' dtype='float64' length=7>
2326.86
1698.83
2360.49
3242.64
1375.93
6275.98
1019.83
>>> result["hek"]["event_coord1"] 
<QueryResponseColumn name='event_coord1' dtype='float64' length=7>
883.2
883.2
883.2
883.2
883.2
883.2
883.2

In this case none of the peak fluxes are returned with the value None. Since we are using an and logical operator we need a result from the (a.hek.FL.PeakFlux > 1000) filter. Flares that have None for a peak flux cannot provide this, and so are excluded. The None type in this context effectively means “Don’t know”; in such cases the client returns only those results from the HEK that definitely satisfy the criteria passed to it.

Getting data for your event#

The sunpy.net.hek2vso module allows you to take an HEK event and acquire VSO records specific to that event.

>>> from sunpy.net import hek2vso

>>> h2v = hek2vso.H2VClient()  

There are several ways to use this capability. For example, you can pass in a list of HEK results and get out the corresponding VSO records. Here are the VSO records returned via the tenth result from the HEK query in Section 2 above:

>>> result = Fido.search(a.Time(tstart,tend), a.hek.EventType(event_type))  
>>> vso_records = h2v.translate_and_query(result[0][10])  
>>> len(vso_records[0])  
31

result[0][10] is the HEK entry generated by the “Flare Detective” automated flare detection algorithm running on the AIA 193 angstrom waveband. The VSO records are for full disk AIA 193 angstrom images between the start and end times of this event. The translate_and_query function uses exactly that information supplied by the HEK in order to find the relevant data for that event. Note that the VSO does not generate records for all solar data, so it is possible that an HEK entry corresponds to data that is not accessible via the VSO.

You can also go one step further back, passing in a list of HEK attribute objects to define your search, the results of which are then used to generate their corresponding VSO records:

>>> vso_query = h2v.full_query((a.Time('2011/08/09 07:00:00', '2011/08/09 07:15:00'), a.hek.EventType('FL')))  

The full capabilities of the HEK query module can be used in this function (see above).

Finally, for greater flexibility, it is possible to pass in a list of HEK results and create the corresponding VSO query attributes.

>>> vso_query = hek2vso.translate_results_to_query(result[0][10])  
>>> vso_query[0]  
[<sunpy.net.attrs.Time(2011-08-09 07:22:44.000, 2011-08-09 07:28:56.000)>, <sunpy.net.attrs.Source(SDO: The Solar Dynamics Observatory.) object at ...>, <sunpy.net.attrs.Instrument(AIA: Atmospheric Imaging Assembly) object at ...>, <sunpy.net.attrs.Wavelength(193.0, 193.0, 'Angstrom')>]

This function allows users finer-grained control of VSO queries generated from HEK results.

This guide outlines how to search for and download data using the Fido interface for search and download. Fido is a unified interface for searching and fetching solar physics data irrespective of the underlying client or web service through which the data is obtained. It therefore supplies a single, easy, and consistent way to obtain most forms of solar physics data.

The Fido object is in sunpy.net. All the examples in this guide use Fido, so lets start by importing it:

>>> from sunpy.net import Fido, attrs as a

Fido supports a number of different remote data sources. To see a list the Fido object can be printed:

>>> print(Fido)
sunpy.net.Fido

Fido is a unified data search and retrieval tool.

It provides simultaneous access to a variety of online data sources, some
cover multiple instruments and data products like the Virtual Solar
Observatory and some are specific to a single source.

For details of using `~sunpy.net.Fido` see :ref:`sunpy-tutorial-acquiring-data-index`.


      Client                                                    Description
----------------- -------------------------------------------------------------------------------------------------------
CDAWEBClient      Provides access to query and download from the Coordinated Data Analysis Web (CDAWeb).
EVEClient         Provides access to Level 0CS Extreme ultraviolet Variability Experiment (EVE) data.
GBMClient         Provides access to data from the Gamma-Ray Burst Monitor (GBM) instrument on board the Fermi satellite.
XRSClient         Provides access to several GOES XRS files archive.
SUVIClient        Provides access to data from the GOES Solar Ultraviolet Imager (SUVI).
GONGClient        Provides access to the Magnetogram products of NSO-GONG synoptic Maps.
LYRAClient        Provides access to the LYRA/Proba2 data archive.
NOAAIndicesClient Provides access to the NOAA solar cycle indices.
NOAAPredictClient Provides access to the NOAA SWPC predicted sunspot Number and 10.7 cm radio flux values.
SRSClient         Provides access to the NOAA SWPC solar region summary data.
NoRHClient        Provides access to the Nobeyama RadioHeliograph (NoRH) averaged correlation time series data.
RHESSIClient      Provides access to the RHESSI observing summary time series data.
HEKClient         Provides access to the Heliophysics Event Knowledgebase (HEK).
HECClient         Provides access to the HELIO webservices.
JSOCClient        Provides access to the JSOC Data Export service.
VSOClient         Provides access to query and download from Virtual Solar Observatory (VSO).

Searching for Data#

To search for data with Fido, you need to specify attributes to search with. Examples of generic search attributes that work across many different data sources are:

Some other attributes are client specific, and are found under client specific submodules, e.g. attrs.vso and attrs.jsoc. The full list of attributes can be found in the attrs submodule reference.

Some search attributes need one or more values specifying, for example Time needs at least a start and an end date to specify a time range:

>>> a.Time('2012/3/4', '2012/3/6')
<sunpy.net.attrs.Time(2012-03-04 00:00:00.000, 2012-03-06 00:00:00.000)>

For attributes that can take a range of different values, printing the attribute lists the values sunpy knows about. These values are updated with every release of sunpy, so may not be always up to date! As an example:

>>> print(a.Instrument)
sunpy.net.attrs.Instrument

Specifies the Instrument name for the search.

       Attribute Name          Client          Full Name                                           Description
--------------------------- ----------- ------------------------ --------------------------------------------------------------------------------
aia                         VSO         AIA                      Atmospheric Imaging Assembly
bcs                         VSO         BCS                      Bragg Crystal Spectrometer
be_continuum                VSO         BE-Continuum             INAF-OACT Barra Equatoriale Continuum Instrument
be_halpha                   VSO         BE-Halpha                INAF-OACT Barra Equatoriale Hα Instrument
bigbear                     VSO         Big Bear                 Big Bear Solar Observatory, California TON and GONG+ sites
caii                        VSO         CAII                     Kanzelhöhe Ca II k Instrument
cds                         VSO         CDS                      Coronal Diagnostic Spectrometer
celias                      VSO         CELIAS                   Charge, Element, and Isotope Analysis System
...

This is a full list of known values, a description, and which clients support those values (if you want to search using a specific data source). Printing attributes like this is supported for most attributes, including client specific ones. These attributes also support tab-completion to auto to auto-fill the attribute name, for example typing a.jsoc.aia_f<TAB> in a jupyter notebook will show you the available attributes that start with “aia_f”.

To search for data use the Fido.search method:

>>> result = Fido.search(a.Time('2012/3/4', '2012/3/6'), a.Instrument.lyra, a.Level.two) 

this returns an UnifiedResponse object containing all the search results that match the search attributes. This does not download the files; we’ll learn how to do that later in Downloading data.

To see a summary of the results print the result variable that came back from the previous search:

>>> print(result)  
Results from 1 Provider:

3 Results from the LYRAClient:
Source: http://proba2.oma.be/lyra/data/bsd

       Start Time               End Time        Instrument ... Provider Level
----------------------- ----------------------- ---------- ... -------- -----
2012-03-04 00:00:00.000 2012-03-04 23:59:59.999       LYRA ...      ESA     2
2012-03-05 00:00:00.000 2012-03-05 23:59:59.999       LYRA ...      ESA     2
2012-03-06 00:00:00.000 2012-03-06 23:59:59.999       LYRA ...      ESA     2

Queries can be made more flexible or specific by adding more attrs objects to the Fido search. As an example, specific passbands can be searched for by supplying a Quantity to the a.Wavelength attribute:

>>> import astropy.units as u

>>> Fido.search(a.Time('2012/3/4', '2012/3/4'), a.Instrument.aia,
...             a.Wavelength(171*u.angstrom))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 67.789 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2012-03-04 00:00:00.000 2012-03-04 00:00:01.000    SDO ...    FULLDISK 64.64844

Data of a given cadence can also be specified using the a.Sample attribute:

>>> Fido.search(a.Time('2012/3/4', '2012/3/6'), a.Instrument.aia,
...             a.Wavelength(171*u.angstrom), a.Sample(120*u.minute))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

25 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 1.695 Gbyte

       Start Time               End Time        Source Instrument   Wavelength   Provider  Physobs  Wavetype Extent Width Extent Length Extent Type   Size
                                                                     Angstrom                                                                        Mibyte
----------------------- ----------------------- ------ ---------- -------------- -------- --------- -------- ------------ ------------- ----------- --------
2012-03-04 00:00:00.000 2012-03-04 00:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 02:00:00.000 2012-03-04 02:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 04:00:00.000 2012-03-04 04:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 06:00:00.000 2012-03-04 06:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 08:00:00.000 2012-03-04 08:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 10:00:00.000 2012-03-04 10:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 12:00:00.000 2012-03-04 12:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 14:00:00.000 2012-03-04 14:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-04 16:00:00.000 2012-03-04 16:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
                    ...                     ...    ...        ...            ...      ...       ...      ...          ...           ...         ...      ...
2012-03-05 06:00:00.000 2012-03-05 06:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 08:00:00.000 2012-03-05 08:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 10:00:00.000 2012-03-05 10:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 12:00:00.000 2012-03-05 12:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 14:00:00.000 2012-03-05 14:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 16:00:00.000 2012-03-05 16:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 18:00:00.000 2012-03-05 18:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 20:00:00.000 2012-03-05 20:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-05 22:00:00.000 2012-03-05 22:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2012-03-06 00:00:00.000 2012-03-06 00:00:01.000    SDO        AIA 171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
Length = 25 rows

To search for data from multiple instruments, wavelengths, times etc., use the pipe | operator which joins queries using a logical “OR” operator. In this example we’ll search for LYRA or RHESSI data in a given time range:

>>> Fido.search(a.Time('2012/3/4', '2012/3/4 02:00'),
...             a.Instrument.lyra | a.Instrument.rhessi)  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 3 Providers:

2 Results from the LYRAClient:
Source: http://proba2.oma.be/lyra/data/bsd

       Start Time               End Time        Instrument  Physobs   Source Provider Level
----------------------- ----------------------- ---------- ---------- ------ -------- -----
2012-03-04 00:00:00.000 2012-03-04 23:59:59.999       LYRA irradiance PROBA2      ESA     2
2012-03-04 00:00:00.000 2012-03-04 23:59:59.999       LYRA irradiance PROBA2      ESA     3

1 Results from the RHESSIClient:
Source: https://hesperia.gsfc.nasa.gov/hessidata

       Start Time               End Time        Instrument      Physobs       Source Provider
----------------------- ----------------------- ---------- ------------------ ------ --------
2012-03-04 00:00:00.000 2012-03-04 23:59:59.999     RHESSI summary_lightcurve RHESSI     NASA

3 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time               End Time        Source Instrument   Wavelength   Provider  Physobs  Extent Type   Size
                                                                       keV                                       Mibyte
----------------------- ----------------------- ------ ---------- -------------- -------- --------- ----------- --------
2012-03-03 22:57:40.000 2012-03-04 00:33:20.000 RHESSI     RHESSI 3.0 .. 17000.0     LSSP intensity PARTIAL_SUN -0.00098
2012-03-04 00:33:20.000 2012-03-04 01:45:40.000 RHESSI     RHESSI 3.0 .. 17000.0     LSSP intensity PARTIAL_SUN -0.00098
2012-03-04 01:45:40.000 2012-03-04 02:09:00.000 RHESSI     RHESSI 3.0 .. 17000.0     LSSP intensity PARTIAL_SUN -0.00098

Working with Search Results#

Fido.search can make multiple queries to multiple clients in one search. This means that the results of a call to search can contain many sets of records, called responses, from many clients. The results of a search are represented in a UnifiedResponse object, which provides access to all the response tables and allows some operations to be performed on all the results at once. UnifiedResponse acts both like a two dimensional array, where the first dimension is the response index and the second index is the row index, and a dictionary where you can index the responses by the name of the client.

For example, the following code returns a response containing LYRA data from the LYRAClient, and EVE data from the VSOClient:

>>> results = Fido.search(a.Time("2012/1/1", "2012/1/2"), a.Level.two,
...                       a.Instrument.lyra | a.Instrument.eve)  
>>> results  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

2 Results from the LYRAClient:
Source: http://proba2.oma.be/lyra/data/bsd

       Start Time               End Time        Instrument  Physobs   Source Provider Level
----------------------- ----------------------- ---------- ---------- ------ -------- -----
2012-01-01 00:00:00.000 2012-01-01 23:59:59.999       LYRA irradiance PROBA2      ESA     2
2012-01-02 00:00:00.000 2012-01-02 23:59:59.999       LYRA irradiance PROBA2      ESA     2

50 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time               End Time        Source Instrument   Wavelength   Provider  Physobs   Extent Type   Size
                                                                     Angstrom                                     Mibyte
----------------------- ----------------------- ------ ---------- -------------- -------- ---------- ----------- --------
2012-01-01 00:00:00.000 2012-01-01 01:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 00:00:00.000 2012-01-01 01:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 01:00:00.000 2012-01-01 02:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 01:00:00.000 2012-01-01 02:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 02:00:00.000 2012-01-01 03:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 02:00:00.000 2012-01-01 03:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 03:00:00.000 2012-01-01 04:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 03:00:00.000 2012-01-01 04:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 04:00:00.000 2012-01-01 05:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
                    ...                     ...    ...        ...            ...      ...        ...         ...      ...
2012-01-01 20:00:00.000 2012-01-01 21:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 20:00:00.000 2012-01-01 21:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 21:00:00.000 2012-01-01 22:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 21:00:00.000 2012-01-01 22:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 22:00:00.000 2012-01-01 23:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 22:00:00.000 2012-01-01 23:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 23:00:00.000 2012-01-02 00:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-01 23:00:00.000 2012-01-02 00:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
2012-01-02 00:00:00.000 2012-01-02 01:00:00.000    SDO        EVE 93.0 .. 1033.0     LASP irradiance    FULLDISK -0.00098
2012-01-02 00:00:00.000 2012-01-02 01:00:00.000    SDO        EVE 60.0 .. 1060.0     LASP irradiance    FULLDISK -0.00098
Length = 50 rows

If you then wanted to inspect just the LYRA data for the whole time range specified in the search, you would index this response to see just the results returned by the LYRAClient:

>>> results[0, :]  
<sunpy.net.dataretriever.client.QueryResponse object at ...>
       Start Time               End Time        Instrument ... Provider Level
----------------------- ----------------------- ---------- ... -------- -----
2012-01-01 00:00:00.000 2012-01-01 23:59:59.999       LYRA ...      ESA     2
2012-01-02 00:00:00.000 2012-01-02 23:59:59.999       LYRA ...      ESA     2

Or, equivalently:

>>> results["lyra"]  
<sunpy.net.dataretriever.client.QueryResponse object at ...>
       Start Time               End Time        Instrument ... Provider Level
----------------------- ----------------------- ---------- ... -------- -----
2012-01-01 00:00:00.000 2012-01-01 23:59:59.999       LYRA ...      ESA     2
2012-01-02 00:00:00.000 2012-01-02 23:59:59.999       LYRA ...      ESA     2

Normal slicing operations work as with any other Python sequence, e.g. results[1, ::10] to access every tenth file in the result returned by the second client.

Note that the first (response) index is still necessary even if results are only found for a single client. So in this case the first result would be results[0, 0] rather than results[0] (the latter would return all results from the first - and only - client and is therefore the same as results).

As we have seen above the UnifiedResponse object contains many response tables which make up the search results. Each of the responses are QueryResponseTable objects, which are astropy.table objects meaning that you can interact with them and filter them like any other tabular data. This can be used to interact with results which are metadata only, i.e. searches from the HEK, or it can be used to reduce the number of files downloaded by Fido.fetch.

For example if we did a query for some AIA and HMI data:

>>> results = Fido.search(a.Time("2020/01/01", "2020/01/01 00:01"), a.Instrument.aia | a.Instrument.hmi)  
>>> results  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

41 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 2.779 Gbyte

       Start Time               End Time        Source Instrument    Wavelength    Provider  Physobs  Wavetype Extent Width Extent Length Extent Type   Size
                                                                      Angstrom                                                                         Mibyte
----------------------- ----------------------- ------ ---------- ---------------- -------- --------- -------- ------------ ------------- ----------- --------
2020-01-01 00:00:00.000 2020-01-01 00:00:01.000    SDO        AIA   335.0 .. 335.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:04.000 2020-01-01 00:00:05.000    SDO        AIA   193.0 .. 193.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:05.000 2020-01-01 00:00:06.000    SDO        AIA   304.0 .. 304.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:05.000 2020-01-01 00:00:06.000    SDO        AIA 4500.0 .. 4500.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:06.000 2020-01-01 00:00:07.000    SDO        AIA   131.0 .. 131.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:09.000 2020-01-01 00:00:10.000    SDO        AIA   171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:09.000 2020-01-01 00:00:10.000    SDO        AIA   211.0 .. 211.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:11.000 2020-01-01 00:00:12.000    SDO        AIA     94.0 .. 94.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:12.000 2020-01-01 00:00:13.000    SDO        AIA   335.0 .. 335.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
                    ...                     ...    ...        ...              ...      ...       ...      ...          ...           ...         ...      ...
2020-01-01 00:00:47.000 2020-01-01 00:00:48.000    SDO        AIA     94.0 .. 94.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:48.000 2020-01-01 00:00:49.000    SDO        AIA   335.0 .. 335.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:52.000 2020-01-01 00:00:53.000    SDO        AIA 1700.0 .. 1700.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:52.000 2020-01-01 00:00:53.000    SDO        AIA   193.0 .. 193.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:53.000 2020-01-01 00:00:54.000    SDO        AIA   304.0 .. 304.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:54.000 2020-01-01 00:00:55.000    SDO        AIA   131.0 .. 131.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:57.000 2020-01-01 00:00:58.000    SDO        AIA   171.0 .. 171.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:57.000 2020-01-01 00:00:58.000    SDO        AIA   211.0 .. 211.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:00:59.000 2020-01-01 00:01:00.000    SDO        AIA     94.0 .. 94.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
2020-01-01 00:01:00.000 2020-01-01 00:01:01.000    SDO        AIA   335.0 .. 335.0     JSOC intensity   NARROW         4096          4096    FULLDISK 64.64844
Length = 41 rows

3 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time               End Time        Source Instrument    Wavelength    Provider      Physobs       Wavetype Extent Width Extent Length Extent Type   Size
                                                                      Angstrom                                                                                  Mibyte
----------------------- ----------------------- ------ ---------- ---------------- -------- ------------------ -------- ------------ ------------- ----------- --------
2020-01-01 00:00:22.000 2020-01-01 00:00:23.000    SDO        HMI 6173.0 .. 6174.0     JSOC          intensity   NARROW         4096          4096    FULLDISK -0.00098
2020-01-01 00:00:22.000 2020-01-01 00:00:23.000    SDO        HMI 6173.0 .. 6174.0     JSOC LOS_magnetic_field   NARROW         4096          4096    FULLDISK -0.00098
2020-01-01 00:00:22.000 2020-01-01 00:00:23.000    SDO        HMI 6173.0 .. 6174.0     JSOC       LOS_velocity   NARROW         4096          4096    FULLDISK -0.00098

The VSO client returns a lot of information about the records, so the first thing we can do is show only the columns we are interested in. We can inspect all the available column names in all the responses with the all_colnames property:

>>> results.all_colnames  
['End Time', 'Extent Length', 'Extent Type', 'Extent Width', 'Instrument', 'Physobs', 'Provider', 'Size', 'Source', 'Start Time', 'Wavelength', 'Wavetype', 'fileid']

And then we can pick which ones to see with the show() method:

>>> results.show("Start Time", "Instrument", "Physobs", "Wavelength")  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

41 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time       Instrument  Physobs     Wavelength
                                                 Angstrom
----------------------- ---------- --------- ----------------
2020-01-01 00:00:00.000        AIA intensity   335.0 .. 335.0
2020-01-01 00:00:04.000        AIA intensity   193.0 .. 193.0
2020-01-01 00:00:05.000        AIA intensity   304.0 .. 304.0
2020-01-01 00:00:05.000        AIA intensity 4500.0 .. 4500.0
2020-01-01 00:00:06.000        AIA intensity   131.0 .. 131.0
2020-01-01 00:00:09.000        AIA intensity   171.0 .. 171.0
2020-01-01 00:00:09.000        AIA intensity   211.0 .. 211.0
2020-01-01 00:00:11.000        AIA intensity     94.0 .. 94.0
2020-01-01 00:00:12.000        AIA intensity   335.0 .. 335.0
                    ...        ...       ...              ...
2020-01-01 00:00:47.000        AIA intensity     94.0 .. 94.0
2020-01-01 00:00:48.000        AIA intensity   335.0 .. 335.0
2020-01-01 00:00:52.000        AIA intensity 1700.0 .. 1700.0
2020-01-01 00:00:52.000        AIA intensity   193.0 .. 193.0
2020-01-01 00:00:53.000        AIA intensity   304.0 .. 304.0
2020-01-01 00:00:54.000        AIA intensity   131.0 .. 131.0
2020-01-01 00:00:57.000        AIA intensity   171.0 .. 171.0
2020-01-01 00:00:57.000        AIA intensity   211.0 .. 211.0
2020-01-01 00:00:59.000        AIA intensity     94.0 .. 94.0
2020-01-01 00:01:00.000        AIA intensity   335.0 .. 335.0
Length = 41 rows

3 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time       Instrument      Physobs          Wavelength
                                                          Angstrom
----------------------- ---------- ------------------ ----------------
2020-01-01 00:00:22.000        HMI          intensity 6173.0 .. 6174.0
2020-01-01 00:00:22.000        HMI LOS_magnetic_field 6173.0 .. 6174.0
2020-01-01 00:00:22.000        HMI       LOS_velocity 6173.0 .. 6174.0

To give an example of filtering post-search, let’s only return the rows in the table which are line-of-sight magnetograms from HMI or the 94Å passband from AIA. You can also always do this filtering with the a.Physobs and a.Wavelength attrs in the search command.

First we split the results in to a table for AIA and a table for HMI:

>>> aia, hmi = results  

We can use boolean indexing to match the value of the "Physobs" column:

>>> hmi_los = hmi[hmi["Physobs"] == "LOS_magnetic_field"]  
>>> hmi_los.show("Start Time", "Instrument", "Wavelength", "Physobs")  
<sunpy.net.vso.table_response.VSOQueryResponseTable object at ...>
       Start Time       Instrument    Wavelength         Physobs
                                       Angstrom
----------------------- ---------- ---------------- ------------------
2020-01-01 00:00:22.000        HMI 6173.0 .. 6174.0 LOS_magnetic_field

To match the "Wavelength" column we need to account for the fact that VSO results return a wavelength range of [min, max] so we match the min:

>>> aia_94 = aia[aia["Wavelength"][:, 0] == 94 * u.AA]  
>>> aia_94.show("Start Time", "Instrument", "Wavelength", "Physobs")  
<sunpy.net.vso.table_response.VSOQueryResponseTable object at ...>
       Start Time       Instrument  Wavelength   Physobs
                                     Angstrom
----------------------- ---------- ------------ ---------
2020-01-01 00:00:11.000        AIA 94.0 .. 94.0 intensity
2020-01-01 00:00:23.000        AIA 94.0 .. 94.0 intensity
2020-01-01 00:00:35.000        AIA 94.0 .. 94.0 intensity
2020-01-01 00:00:47.000        AIA 94.0 .. 94.0 intensity
2020-01-01 00:00:59.000        AIA 94.0 .. 94.0 intensity

Warning

While you can reduce the number of columns and rows in the results, the fetch() method that downloads data may need certain columns to be present to successfully download the files. It is therefore highly recommended that if you are planning on downloading data you do not slice out columns, but instead use .show() to only display the ones you are interested in.

Downloading data#

Once you have located your files via a Fido.search, you can download them via Fido.fetch. Here we’ll just download the first file in the result:

>>> downloaded_files = Fido.fetch(results[0, 0]) 
>>> downloaded_files 
<parfive.results.Results object at ...>
['.../aia.lev1.335A_2020_01_01T00_00_00.64Z.image_lev1.fits']

This downloads the files to the location set in the sunpy config file. It also returns a parfive.Results object downloaded_files, which contains local file paths to all the downloaded data.

You can also explicitly specify the path to which you want the data downloaded:

>>> downloaded_files = Fido.fetch(results, path='/ThisIs/MyPath/to/Data/{file}')  

This downloads the query results into the directory /ThisIs/MyPath/to/Data, naming each downloaded file with the filename {file} obtained from the client. You can also use other properties of the returned query to define the path where the data is saved. For example, to save the data to a subdirectory named after the instrument, use:

>>> downloaded_files = Fido.fetch(results, path='./{instrument}/{file}')  

You can see the list of options that can be specified in path for all the files to be downloaded with results.path_format_keys:

>>> sorted(results.path_format_keys()) 
['end_time', 'extent_length', 'extent_type', 'extent_width', 'fileid', 'instrument', 'physobs', 'provider', 'size', 'source', 'start_time', 'wavelength', 'wavetype']
Retrying Downloads#

If any files failed to download, the progress bar will show an incomplete number of files (i.e. 100/150) and the parfive.Results object will contain a list of the URLs that failed to transfer and the error associated with them. This can be accessed with the .errors attribute or by printing the Results object:

>>> print(downloaded_files.errors)  

The transfer can be retried by passing the parfive.Results object back to Fido.fetch:

>>> downloaded_files = Fido.fetch(downloaded_files)  

doing this will append any newly downloaded file names to the list and replace the .errors list with any errors that occurred during the second attempt.

Maps#

In this section of the tutorial, you will learn about the Map object. Map objects hold two-dimensional data along with the accompanying metadata. They can be used with any two-dimensional data array with two spatial axes and FITS-compliant metadata. As you will see in this tutorial, the primary advantage of using a Map object over a bare NumPy array is the ability to perform coordinate aware operations on the underlying array, such as rotating the Map to remove the roll angle of an instrument or cropping a Map to a specific field of view. Additionally, because Map is capable of ingesting data from many different sources, it provides a common interface for any two-dimensional data product.

By the end of this tutorial, you will learn how to create a Map, access the underlying data and metadata, convert between physical coordinates and pixel coordinates of a Map, and the basics of visualizing a Map. Additionally, you will learn how to combine multiple Maps into a MapSequence or a CompositeMap.

Note

In this section and in Timeseries, we will use the sample data included with sunpy. These data are primarily useful for demonstration purposes or simple debugging. These files have names like sunpy.data.sample.AIA_171_IMAGE and sunpy.data.sample.RHESSI_IMAGE and are automatically downloaded to your computer as you need them. Once downloaded, these sample data files will be paths to their location on your computer.

Creating Maps#

To create a Map from a sample Atmospheric Imaging Assembly (AIA) image,

>>> import sunpy.map
>>> import sunpy.data.sample  

>>> sunpy.data.sample.AIA_171_IMAGE  
PosixPath('.../AIA20110607_063302_0171_lowres.fits')
>>> my_map = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)  

In many cases sunpy automatically detects the type of file as well as the the instrument associated with it. In this case, we have a FITS file from the AIA instrument onboard the Solar Dynamics Observatory (SDO). To make sure this has all worked correctly, we can take a quick look at my_map,

>>> my_map  
<sunpy.map.sources.sdo.AIAMap object at ...>
SunPy Map
---------
Observatory:                 SDO
Instrument:          AIA 3
Detector:            AIA
Measurement:                 171.0 Angstrom
Wavelength:          171.0 Angstrom
Observation Date:    2011-06-07 06:33:02
Exposure Time:               0.234256 s
Dimension:           [1024. 1024.] pix
Coordinate System:   helioprojective
Scale:                       [2.402792 2.402792] arcsec / pix
Reference Pixel:     [511.5 511.5] pix
Reference Coord:     [3.22309951 1.38578135] arcsec
array([[ -95.92475  ,    7.076416 ,   -1.9656711, ..., -127.96519  ,
        -127.96519  , -127.96519  ],
       [ -96.97533  ,   -5.1167884,    0.       , ...,  -98.924576 ,
        -104.04137  , -127.919716 ],
       [ -93.99607  ,    1.0189276,   -4.0757103, ...,   -5.094638 ,
         -37.95505  , -127.87541  ],
       ...,
       [-128.01454  , -128.01454  , -128.01454  , ..., -128.01454  ,
        -128.01454  , -128.01454  ],
       [-127.899666 , -127.899666 , -127.899666 , ..., -127.899666 ,
        -127.899666 , -127.899666 ],
       [-128.03072  , -128.03072  , -128.03072  , ..., -128.03072  ,
        -128.03072  , -128.03072  ]], dtype=float32)

This should show a representation of the data as well as some of its associated attributes. If you are in a Jupyter Notebook, this will show a rich HTML view of the table along with several quick-look plots. Otherwise, you can use the quicklook() method to see these quick-look plots.

>>> my_map.quicklook()  
<sunpy.map.sources.sdo.AIAMap object at 0x7f6f1b742560>
Observatory SDO
Instrument AIA 3
Detector AIA
Measurement 171.0 Angstrom
Wavelength 171.0 Angstrom
Observation Date 2011-06-07 06:33:02
Exposure Time 0.234256 s
Dimension [1024. 1024.] pix
Coordinate System helioprojective
Scale [2.402792 2.402792] arcsec / pix
Reference Pixel [511.5 511.5] pix
Reference Coord [3.22309951 1.38578135] arcsec
Image colormap uses histogram equalization
Click on the image to toggle between units

Inspecting Map Metadata#

The metadata for a Map is exposed via attributes on the Map. These attributes can be accessed by typing my_map.<attribute-name>. For example, to access the date of the observation,

>>> my_map.date  
<Time object: scale='utc' format='isot' value=2011-06-07T06:33:02.770>

Notice that this is an Time object which we discussed in the previous Times section of the tutorial. Similarly, we can access the exposure time of the image,

>>> my_map.exposure_time  
<Quantity 0.234256 s>

Notice that this returns an Quantity object which we discussed in the previous Units section of the tutorial. The full list of attributes can be found in the reference documentation for GenericMap. These metadata attributes are all derived from the underlying FITS metadata, but are represented as rich Python objects, rather than simple strings or numbers.

Map Data#

The data in a Map is stored as a numpy.ndarray object and is accessible through the data attribute:

>>> my_map.data  
array([[ -95.92475  ,    7.076416 ,   -1.9656711, ..., -127.96519  ,
    -127.96519  , -127.96519  ],
   [ -96.97533  ,   -5.1167884,    0.       , ...,  -98.924576 ,
    -104.04137  , -127.919716 ],
   [ -93.99607  ,    1.0189276,   -4.0757103, ...,   -5.094638 ,
     -37.95505  , -127.87541  ],
   ...,
   [-128.01454  , -128.01454  , -128.01454  , ..., -128.01454  ,
    -128.01454  , -128.01454  ],
   [-127.899666 , -127.899666 , -127.899666 , ..., -127.899666 ,
    -127.899666 , -127.899666 ],
   [-128.03072  , -128.03072  , -128.03072  , ..., -128.03072  ,
    -128.03072  , -128.03072  ]], dtype=float32)

This array can then be indexed like any other NumPy array. For example, to get the 0th element in the array:

>>> my_map.data[0, 0]  
-95.92475

The first index corresponds to the y direction and the second to the x direction in the two-dimensional pixel coordinate system. For more information about indexing, please refer to the numpy documentation.

Data attributes like dimensionality and type are also accessible as attributes on my_map:

>>> my_map.dimensions  
PixelPair(x=<Quantity 1024. pix>, y=<Quantity 1024. pix>)
>>> my_map.dtype  
dtype('float32')

Additionally, there are several methods that provide basic summary statistics of the data:

>>> my_map.min()  
-129.78036
>>> my_map.max()  
192130.17
>>> my_map.mean()  
427.02252

Coordinates, and the World Coordinate System#

In Coordinates, you learned how to define coordinates with SkyCoord using different solar coordinate frames. The coordinate frame of a Map is provided as an attribute,

>>> my_map.coordinate_frame  
<Helioprojective Frame (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>)>

This tells us that the coordinate system of the image is Helioprojective (HPC) and that it is defined by an observer at a particular location. This observer coordinate is also provided as an attribute,

>>> my_map.observer_coordinate  
<SkyCoord (HeliographicStonyhurst: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>

This tells us the location of the spacecraft, in this case SDO, when it recorded this particular observation, as derived from the FITS metadata.

Map has several additional coordinate-related attributes that provide the coordinates of the center and corners of the Map,

>>> my_map.center  
<SkyCoord (Helioprojective: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>): (Tx, Ty) in arcsec
    (3.22309951, 1.38578135)>
>>> my_map.bottom_left_coord  
<SkyCoord (Helioprojective: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>): (Tx, Ty) in arcsec
    (-1228.76466158, -1224.62447509)>
>>> my_map.top_right_coord  
<SkyCoord (Helioprojective: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>): (Tx, Ty) in arcsec
    (1235.21095899, 1227.39598836)>

But what if we wanted to know what pixel these physical coordinates correspond to? Each Map has an associated World Coordinate System, or WCS, which is derived from the underlying metadata and expressed as an astropy.wcs.WCS object. The WCS is accessible as an attribute:

>>> my_map.wcs  
WCS Keywords

Number of WCS axes: 2
CTYPE : 'HPLN-TAN'  'HPLT-TAN'
CRVAL : 0.00089530541880571  0.00038493926472939
CRPIX : 512.5  512.5
PC1_1 PC1_2  : 0.99999706448085  0.0024230207763071
PC2_1 PC2_2  : -0.0024230207763071  0.99999706448085
CDELT : 0.00066744222222222  0.00066744222222222
NAXIS : 1024  1024

WCS is a fairly complex topic, but all we need to know for now is that the WCS provides the transformation between the pixel coordinates of the image and physical or “world” coordinates. In particular, we will only focus on two methods: world_to_pixel and pixel_to_world. First, let’s find the pixel location corresponding to the center of the Map,

>>> center_pixel = my_map.wcs.world_to_pixel(my_map.center)  
>>> center_pixel  
(array(511.5), array(511.5))

Notice that these coordinates are not necessarily integers. The corresponding pixel-to-world transformation should then give us back our center coordinate from above,

>>> my_map.wcs.pixel_to_world(center_pixel[0], center_pixel[1])  
<SkyCoord (Helioprojective: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>): (Tx, Ty) in arcsec
    (3.22309951, 1.38578135)>

As another example, if we transform the center of the lower-left pixel to a world coordinate, it should correspond to bottom left coordinate from above,

>>> my_map.wcs.pixel_to_world(0, 0)  
<SkyCoord (Helioprojective: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>): (Tx, Ty) in arcsec
    (-1228.76466158, -1224.62447509)>

These two methods are extremely useful when trying to understand which pixels correspond to which physical coordinates or when trying to locate the same physical location in images taken by separate spacecraft.

Visualizing Maps#

In the Creating Maps section, you learned how to generate a quicklook summary of a Map. However, the Map object also has a plot() method that allows for more fine-grained control over how the Map is visualized and is especially useful for generating publication-quality plots. In this section of the tutorial, you will learn how to build up an increasingly detailed visualization of a Map, including adjusting the colormap and normalization and and overlaying coordinates and contours.

Basic Plotting#

First, let’s create a basic plot of our Map, including a colorbar,

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax)
plt.colorbar()
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-2.png

Note

We imported matplotlib.pyplot in order to create the figure and the axis we plotted on our map onto. Under the hood, sunpy uses matplotlib to visualize the image meaning that plots built with sunpy can be further customized using matplotlib. However, for the purposes of this tutorial, you do not need to be familiar with Matplotlib. For a series of detailed examples showing how to customize your Map plots, see the Plotting section of the Example Gallery as well as the documentation for astropy.visualization.wcsaxes.

Note that the title and colormap have been set by sunpy based on the observing instrument and wavelength. Furthermore, the tick and axes labels have been automatically set based on the coordinate system of the Map.

Looking at the plot above, you likely notice that the resulting image is a bit dim. To fix this, we can use the clip_interval keyword to automatically adjust the colorbar limits to clip out the dimmest 1% and the brightest 0.5% of pixels.

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax, clip_interval=(1, 99.5)*u.percent)
plt.colorbar()
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-3.png
Changing the Colormap and Normalization#

Historically, particular colormaps are assigned to images based on what instrument they are from and what wavelength is being observed. By default, sunpy will select the colormap based on the available metadata. This default colormap is available as an attribute,

>>> my_map.cmap.name  
'sdoaia171'

When visualizing a Map, you can change the colormap using the cmap keyword argument. For example, you can use the ‘inferno’ colormap from matplotlib:

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax, cmap='inferno', clip_interval=(1,99.5)*u.percent)
plt.colorbar()
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-4.png

Note

sunpy provides specific colormaps for many different instruments. For a list of all colormaps provided by sunpy, see the documentation for sunpy.visualization.colormaps.

The normalization, or the mapping between the data values and the colors in our colormap, is also determined based on the underlying metadata. Notice that in the plots we’ve made so far, the ticks on our colorbar are not linearly spaced. Just like in the case of the colormap, we can use a normalization other than the default by passing a keyword argument to the plot() method. For example, we can use a logarithmic normalization instead:

import matplotlib.colors

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(norm=matplotlib.colors.LogNorm())
plt.colorbar()
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-5.png

Note

You can also view or make changes to the default settings through the sunpy.map.GenericMap.plot_settings dictionary. See Editing the colormap and normalization of a Map for an example of of how to change the default plot settings.

Overlaying Contours and Coordinates#

When plotting images, we often want to highlight certain features or overlay certain data points. There are several methods attached to Map that make this task easy. For example, we can draw contours around the brightest 0.5% percent of pixels in the image:

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax, clip_interval=(1,99.5)*u.percent)
my_map.draw_contours([2, 5, 10, 50, 90] * u.percent, axes=ax)
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-6.png

Additionally, the solar limb, as determined by the location of the observing instrument at the time of the observation, can be easily overlaid on an image:

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax, clip_interval=(1,99.5)*u.percent)
my_map.draw_limb(axes=ax, color='C0')
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-7.png

We can also overlay a box denoting a particular a region of interest as expressed in world coordinates using the the coordinate frame of our image:

roi_bottom_left = SkyCoord(Tx=-300*u.arcsec, Ty=-100*u.arcsec, frame=my_map.coordinate_frame)
roi_top_right = SkyCoord(Tx=200*u.arcsec, Ty=400*u.arcsec, frame=my_map.coordinate_frame)
fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax, clip_interval=(1,99.5)*u.percent)
my_map.draw_quadrangle(roi_bottom_left, top_right=roi_top_right, axes=ax, color='C0')
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-8.png

Because our visualization knows about the coordinate system of our Map, it can transform any coordinate to the coordinate frame of our Map and then use the underlying WCS that we discussed in the Coordinates, and the World Coordinate System section to translate this to a pixel position. This makes it simple to plot any coordinate on top of our Map using the plot_coord() method. The following example shows how to plot some points on our Map, including the center coordinate of our Map:

coords = SkyCoord(Tx=[100,1000] * u.arcsec, Ty=[100,1000] * u.arcsec, frame=my_map.coordinate_frame)

fig = plt.figure()
ax = fig.add_subplot(projection=my_map)
my_map.plot(axes=ax, clip_interval=(1,99.5)*u.percent)
ax.plot_coord(coords, 'o')
ax.plot_coord(my_map.center, 'X')
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-9.png

Note

Map visualizations can be heavily customized using both matplotlib and astropy.visualization.wcsaxes. See the Plotting section of the Example Gallery for more detailed examples of how to customize Map visualizations.

Cropping Maps and Combining Pixels#

In analyzing images of the Sun, we often want to choose a smaller portion of the full disk to look at more closely. Let’s use the region of interest we defined above to crop out that portion of our image:

my_submap = my_map.submap(roi_bottom_left, top_right=roi_top_right)

fig = plt.figure()
ax = fig.add_subplot(projection=my_submap)
my_submap.plot(axes=ax)
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-10.png

Additionally, we also may want to combine multiple pixels into a single pixel (a “superpixel”) to, for example, increase our signal-to-noise ratio. We can accomplish this with the superpixel method by specifying how many pixels, in each dimension, we want our new superpixels to contain. For example, we can combine 4 pixels in each dimension such that our new superpixels contain 16 original pixels:

my_super_submap = my_submap.superpixel((5,5)*u.pixel)

fig = plt.figure()
ax = fig.add_subplot(projection=my_super_submap)
my_super_submap.plot(axes=ax)
plt.show()

(Source code, png, hires.png, pdf)

_images/maps-11.png

Note

Map provides additional methods for manipulating the underlying image data. See the reference documentation for GenericMap for a complete list of available methods as well as the Map section of the Example Gallery for more detailed examples.

Map Sequences#

While GenericMap can only contain a two-dimensional array and metadata corresponding to a single observation, a MapSequence is comprised of an ordered list of maps. By default, the Maps are ordered by their observation date, from earliest to latest date. A MapSequence can be created by supplying multiple existing maps:

>>> another_map = sunpy.map.Map(sunpy.data.sample.EIT_195_IMAGE)  
>>> map_seq = sunpy.map.Map([my_map, another_map], sequence=True)  

A map sequence can be indexed in the same manner as a list. For example, the following returns the same information as in Creating Maps:

>>> map_seq.maps[0]   
<sunpy.map.sources.sdo.AIAMap object at ...>
SunPy Map
---------
Observatory:                 SDO
Instrument:          AIA 3
Detector:            AIA
Measurement:                 171.0 Angstrom
Wavelength:          171.0 Angstrom
Observation Date:    2011-06-07 06:33:02
Exposure Time:               0.234256 s
Dimension:           [1024. 1024.] pix
Coordinate System:   helioprojective
Scale:                       [2.402792 2.402792] arcsec / pix
Reference Pixel:     [511.5 511.5] pix
Reference Coord:     [3.22309951 1.38578135] arcsec
array([[ -95.92475  ,    7.076416 ,   -1.9656711, ..., -127.96519  ,
        -127.96519  , -127.96519  ],
       [ -96.97533  ,   -5.1167884,    0.       , ...,  -98.924576 ,
        -104.04137  , -127.919716 ],
       [ -93.99607  ,    1.0189276,   -4.0757103, ...,   -5.094638 ,
         -37.95505  , -127.87541  ],
       ...,
       [-128.01454  , -128.01454  , -128.01454  , ..., -128.01454  ,
        -128.01454  , -128.01454  ],
       [-127.899666 , -127.899666 , -127.899666 , ..., -127.899666 ,
        -127.899666 , -127.899666 ],
       [-128.03072  , -128.03072  , -128.03072  , ..., -128.03072  ,
        -128.03072  , -128.03072  ]], dtype=float32)

MapSequences can hold maps that have different shapes. To test if all the maps in a MapSequence have the same shape:

>>> map_seq.all_maps_same_shape()  
True

It is often useful to return the image data in a MapSequence as a single three dimensional NumPy ndarray:

>>> map_seq_array = map_seq.as_array()  

Since all of the maps in our sequence of the same shape, the first two dimensions of our combined array will be the same as the component maps while the last dimension will correspond to the number of maps in the map sequence. We can confirm this by looking at the shape of the above array.

>>> map_seq_array.shape  
(1024, 1024, 2)

Warning

MapSequence does not automatically perform any coalignment between the maps comprising a sequence. For information on coaligning images and compensating for solar rotation, see this section of the Example Gallery as well as the sunkit_image.coalignment module.

Timeseries#

In this section of the tutorial, you will learn about the TimeSeries object. TimeSeries objects hold time-dependent data with their accompanying metadata. They can be used with multiple one-dimensional arrays which are all associated with a common time axis. Much like the Map object, the TimeSeries object can handle generic data, but also provides instrument specific data loading and plotting capabilities. Importantly, TimeSeries allows you to select specific time ranges or combine multiple TimeSeries in a metadata-aware way.

By the end of this tutorial, you will learn how to create a TimeSeries, extract the data and metadata, and easily visualize the TimeSeries. Additionally, you will learn how to truncate a TimeSeries to a specific window in time as well as combine multiple TimeSeries objects.

Note

In this section and in Maps, we will use the sample data included with sunpy. These data are primarily useful for demonstration purposes or simple debugging. These files have names like sunpy.data.sample.EVE_TIMESERIES and sunpy.data.sample.GOES_XRS_TIMESERIES and are automatically downloaded to your computer as you need them. Once downloaded, these sample data files will be paths to their location on your computer.

Creating a TimeSeries#

To create a TimeSeries from some sample GOES XRS data:

>>> import sunpy.timeseries
>>> import sunpy.data.sample  

>>> sunpy.data.sample.GOES_XRS_TIMESERIES  
PosixPath('.../go1520110607.fits')
>>> my_timeseries = sunpy.timeseries.TimeSeries(sunpy.data.sample.GOES_XRS_TIMESERIES)  

In many cases, sunpy will automatically detect the type of the file as well as the instrument associated with it. In this case, we have a FITS file containing an X-ray light curve as observed by the the XRS instrument on the GOES satellite.

Note

Time series data are stored in a variety of file types (e.g. FITS, csv, CDF), and so it is not always possible to detect the source. sunpy ships with a number of known instrumental sources, and can also load CDF files that conform to the Space Physics Guidelines for CDF.

To make sure this has all worked correctly, we can take a quick look at my_timeseries,

>>> my_timeseries  
<sunpy.timeseries.sources.goes.XRSTimeSeries object at ...>
SunPy TimeSeries
----------------
Observatory:                 GOES-15
Instrument:          <a href=https://www.swpc.noaa.gov/products/goes-x-ray-flux target="_blank">X-ray Detector</a>
Channel(s):          xrsa<br>xrsb
Start Date:          2011-06-07 00:00:00
End Date:            2011-06-07 23:59:58
Center Date:                 2011-06-07 11:59:58
Resolution:          2.048 s
Samples per Channel:                 42177
Data Range(s):               xrsa   3.64E-06<br>xrsb   2.54E-05
Units:               W / m2
                                       xrsa          xrsb
2011-06-06 23:59:59.961999893  1.000000e-09  1.887100e-07
2011-06-07 00:00:02.008999944  1.000000e-09  1.834600e-07
2011-06-07 00:00:04.058999896  1.000000e-09  1.860900e-07
2011-06-07 00:00:06.104999900  1.000000e-09  1.808400e-07
2011-06-07 00:00:08.151999950  1.000000e-09  1.860900e-07
...                                     ...           ...
2011-06-07 23:59:49.441999912  1.000000e-09  1.624800e-07
2011-06-07 23:59:51.488999844  1.000000e-09  1.624800e-07
2011-06-07 23:59:53.538999915  1.000000e-09  1.598500e-07
2011-06-07 23:59:55.584999919  1.000000e-09  1.624800e-07
2011-06-07 23:59:57.631999850  1.000000e-09  1.598500e-07

[42177 rows x 2 columns]

This should show a table of information taken from the metadata and a preview of your data. If you are in a Jupyter Notebook, this will show a rich HTML version that includes plots of the data. Otherwise, you can use the quicklook() method to see this quick-look plot,

>>> my_timeseries.quicklook()  
<sunpy.timeseries.sources.goes.XRSTimeSeries object at 0x7f6f165880d0>
Observatory GOES-15
Instrument X-ray Detector
Channel(s) xrsa
xrsb
Start Date 2011-06-07 00:00:00
End Date 2011-06-07 23:59:58
Center Date 2011-06-07 11:59:58
Resolution 2.048 s
Samples per Channel 42177
Data Range(s) xrsa 3.64E-06
xrsb 2.54E-05
Units W / m2
Click here for histograms

TimeSeries Data#

We can easily check which columns are contained in the TimeSeries,

>>> my_timeseries.columns  
['xrsa', 'xrsb']

“xrsa” denotes the short wavelength channel of the XRS data which contains emission between 0.5 and 4 Angstrom. To pull out the just the data corresponding to this column, we can use the quantity() method:

>>> my_timeseries.quantity('xrsa') 
<Quantity [1.e-09, 1.e-09, 1.e-09, ..., 1.e-09, 1.e-09, 1.e-09] W / m2>

Notice that this is a Quantity object which we discussed in Units. Additionally, the timestamp associated with each point and the time range of the observation are accessible as attributes,

>>> my_timeseries.time  
<Time object: scale='utc' format='iso' value=['2011-06-06 23:59:59.962' '2011-06-07 00:00:02.009'
 '2011-06-07 00:00:04.059' ... '2011-06-07 23:59:53.539'
 '2011-06-07 23:59:55.585' '2011-06-07 23:59:57.632']>
>>> my_timeseries.time_range  
   <sunpy.time.timerange.TimeRange object at ...>
    Start: 2011-06-06 23:59:59
    End:   2011-06-07 23:59:57
    Center:2011-06-07 11:59:58
    Duration:0.9999730324069096 days or
           23.99935277776583 hours or
           1439.9611666659498 minutes or
           86397.66999995698 seconds

Notice that these return a astropy.time.Time and sunpy.time.TimeRange, both of which we covered in Times.

Inspecting TimeSeries Metadata#

A TimeSeries object also includes metadata associated with that observation. Some of this metadata is exposed via attributes on the TimeSeries. For example, to find out which observatory observed this data,

>>> my_timeseries.observatory  
'GOES-15'

Additionally, to find out which instrument this timeseries data came from,

>>> my_timeseries.source  
'xrs'

All of the metadata can also be accessed using the meta attribute,

>>> my_timeseries.meta 
|-------------------------------------------------------------------------------------------------|
|TimeRange                  | Columns         | Meta                                              |
|-------------------------------------------------------------------------------------------------|
|2011-06-06T23:59:59.961999 | xrsa            | simple: True                                      |
|            to             | xrsb            | bitpix: 8                                         |
|2011-06-07T23:59:57.631999 |                 | naxis: 0                                          |
|                           |                 | extend: True                                      |
|                           |                 | date: 26/06/2012                                  |
|                           |                 | numext: 3                                         |
|                           |                 | telescop: GOES 15                                 |
|                           |                 | instrume: X-ray Detector                          |
|                           |                 | object: Sun                                       |
|                           |                 | origin: SDAC/GSFC                                 |
|                           |                 | ...                                               |
|-------------------------------------------------------------------------------------------------|

Warning

A word of caution: many data sources provide little to no meta data so this variable might be empty. See A Detailed Look at the TimeSeries Metadata for a more detailed explanation of how metadata on TimeSeries objects is handled.

Visualizing TimeSeries#

The sunpy TimeSeries object has its own built-in plot methods so that it is easy to quickly view your time series. To create a plot,

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
my_timeseries.plot(axes=ax)
plt.show()

(Source code, png, hires.png, pdf)

_images/timeseries-2.png

Note

For additional examples of building more complex visualization with TimeSeries, see the examples in Time Series.

Adding Columns#

TimeSeries provides the add_column method which will either add a new column or update a current column if the colname is already present. This can take numpy array or preferably an Astropy Quantity value. For example:

>>> values = my_timeseries.quantity('xrsa') * 2 
>>> my_timeseries = my_timeseries.add_column('xrsa*2', values) 
>>> my_timeseries.columns 
['xrsa', 'xrsb', 'xrsa*2']

Adding a column is not done in place, but instead returns a new TimeSeries with the new column added. Note that the values will be converted into the column units if an Astropy Quantity is given. Caution should be taken when adding a new column because this column won’t have any associated MetaData entry.

Truncating a TimeSeries#

It is often useful to truncate an existing TimeSeries object to retain a specific time range. This is easily achieved by using the truncate method. For example, to trim our GOES data into a period of interest use:

>>> from sunpy.time import TimeRange

>>> tr = TimeRange('2012-06-01 05:00', '2012-06-01 06:30')
>>> my_timeseries_trunc = my_timeseries.truncate(tr) 

This takes a number of different arguments, such as the start and end dates (as datetime or string objects) or a TimeRange as used above. Note that the truncated TimeSeries will have a truncated TimeSeriesMetaData object, which may include dropping metadata entries for data totally cut out from the TimeSeries. If you want to truncate using slice-like values you can, for example taking every 2nd value from 0 to 10000 can be done using:

>>> my_timeseries_trunc = my_timeseries.truncate(0, 100000, 2) 

Concatenating TimeSeries#

It’s common to want to combine a number of TimeSeries together into a single TimeSeries. In the simplest scenario this is to combine data from a single source over several time ranges, for example if you wanted to combine the daily GOES data to get a week or more of constant data in one TimeSeries. This can be performed using the TimeSeries factory with the concatenate=True keyword argument:

>>> concatenated_timeseries = sunpy.timeseries.TimeSeries(filepath1, filepath2, source='XRS', concatenate=True)  

Note, you can list any number of files, or a folder or use a glob to select the input files to be concatenated. It is possible to concatenate two TimeSeries after creating them using the concatenate method. For example:

>>> concatenated_timeseries = goes_timeseries_1.concatenate(goes_timeseries_2) 

This will result in a TimeSeries identical to if you had created them in one step. A limitation of the TimeSeries class is that it is not always possible to determine the source observatory or instrument of a given file. Thus some sources need to be explicitly stated (using the keyword argument) and so, it is not possible to concatenate files from multiple sources. To do this, you can still use the concatenate method, which will create a new TimeSeries with all the rows and columns of the source and concatenated TimeSeries in one:

>>> eve_ts = sunpy.timeseries.TimeSeries(sunpy.data.sample.EVE_TIMESERIES, source='eve') 
>>> concatenated_timeseries = my_timeseries.concatenate(eve_ts) 

Note that the more complex TimeSeriesMetaData object now has 2 entries and shows details on both:

>>> concatenated_timeseries.meta 
    |-------------------------------------------------------------------------------------------------|
|TimeRange                  | Columns         | Meta                                              |
|-------------------------------------------------------------------------------------------------|
|2011-06-06T23:59:59.961999 | xrsa            | simple: True                                      |
|            to             | xrsb            | bitpix: 8                                         |
|2011-06-07T23:59:57.631999 |                 | naxis: 0                                          |
|                           |                 | extend: True                                      |
|                           |                 | date: 26/06/2012                                  |
|                           |                 | numext: 3                                         |
|                           |                 | telescop: GOES 15                                 |
|                           |                 | instrume: X-ray Detector                          |
|                           |                 | object: Sun                                       |
|                           |                 | origin: SDAC/GSFC                                 |
|                           |                 | ...                                               |
|-------------------------------------------------------------------------------------------------|
|2011-06-07T00:00:00.000000 | XRS-B proxy     | data_list: 20110607_EVE_L0CS_DIODES_1m.txt        |
|            to             | XRS-A proxy     | created: Tue Jun  7 23:59:10 2011 UTC             |
|2011-06-07T23:59:00.000000 | SEM proxy       | origin: SDO/EVE Science Processing and Operations |
|                           | 0.1-7ESPquad    | units: W/m^2 for irradiance, dark is counts/(0.25s|
|                           | 17.1ESP         | source: SDO-EVE ESP and MEGS-P instruments, http:/|
|                           | 25.7ESP         | product: Level 0CS, 1-minute averaged SDO-EVE Sola|
|                           | 30.4ESP         | version: 2.1, code updated 2011-May-12            |
|                           | 36.6ESP         | missing data: -1.00e+00                           |
|                           | darkESP         | hhmm: hour and minute in UT                       |
|                           | 121.6MEGS-P     | xrs-b proxy: a model of the expected XRS-B 0.1-0.8|
|                           | ...             | ...                                               |
|-------------------------------------------------------------------------------------------------|

How-To Guides#

These how-to guides provide examples of how to perform specific tasks with sunpy. They are recipes that do not provide much in-depth explanation and assume you have some knowledge of what sunpy is and how it works. If you’re starting fresh you might want to check out the The sunpy tutorial first.

Access coordinate components#

Individual coordinates can be accessed via attributes on the SkyCoord object, but the names of the components of the coordinates can depend on the the frame and the chosen representation (e.g., Cartesian versus spherical).

Helioprojective#

For the helioprojective frame, the theta_x and theta_y components are accessed Tx and Ty, respectively:

>>> from astropy.coordinates import SkyCoord
>>> import astropy.units as u

>>> from sunpy.coordinates import frames

>>> c = SkyCoord(-500*u.arcsec, 100*u.arcsec, frame=frames.Helioprojective)
>>> c.Tx
<Longitude -500. arcsec>
>>> c.Ty
<Latitude 100. arcsec>

Heliocentric#

Heliocentric is typically used with Cartesian components:

>>> c = SkyCoord(-72241.0*u.km, 361206.1*u.km, 589951.4*u.km, frame=frames.Heliocentric)
>>> c.x
<Quantity -72241. km>
>>> c.y
<Quantity 361206.1 km>
>>> c.z
<Quantity 589951.4 km>

HeliographicStonyhurst and HeliographicCarrington#

Both of the heliographic frames have the components of latitude, longitude and radius:

>>> c = SkyCoord(70*u.deg, -30*u.deg, 1*u.AU, frame=frames.HeliographicStonyhurst)
>>> c.lat
<Latitude -30. deg>
>>> c.lon
<Longitude 70. deg>
>>> c.radius
<Distance 1. AU>

Heliographic Stonyhurst, when used with Cartesian components, is known as Heliocentric Earth Equatorial (HEEQ). Here’s an example of how to use HeliographicStonyhurst for HEEQ coordinates:

>>> c = SkyCoord(-72241.0*u.km, 361206.1*u.km, 589951.4*u.km,
...              representation_type='cartesian', frame=frames.HeliographicStonyhurst)
>>> c.x
<Quantity -72241. km>
>>> c.y
<Quantity 361206.1 km>
>>> c.z
<Quantity 589951.4 km>

How to create a sunpy Map#

One of the primary goals of the Map interface is to make it as easy as possible to create a Map. As such, you can pass many different kinds of inputs to Map. These are listed below.

File name#

If you have a FITS file, this is the easiest and recommended way to create a Map. This can be either a string or a Path.

>>> import pathlib

>>> import sunpy.map
>>> import sunpy.data.sample

>>> my_map = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)  
>>> my_map = sunpy.map.Map('file.fits')  
>>> my_map = sunpy.map.Map(pathlib.Path('file.fits'))  
>>> sub_dir = pathlib.Path('local_dir/sub_dir')
>>> my_map = sunpy.map.Map(sub_dir / 'another_file.fits')   

Directory containing FITS files#

If there is more than one FITS file in the directory, this will return a list of Map objects.

>>> my_maps = sunpy.map.Map('local_dir/sub_dir')   
>>> my_maps = sunpy.map.Map(sub_dir)   

Array and astropy.io.fits.Header#

If needed, this way can be used to modify the header before passing it to Map.

>>> import astropy.io.fits

>>> with astropy.io.fits.open(sunpy.data.sample.AIA_171_IMAGE) as hdul:
...     data = hdul[1].data
...     header = hdul[1].header  
>>> my_map = sunpy.map.Map(data, header)  

These data header pairs can also be passed as a tuple,

>>> my_map = sunpy.map.Map((data, header))  

Data array and a MetaDict object#

This includes any base class of MetaDict, including dict or collections.OrderedDict.

>>> import sunpy.util.metadata

>>> meta = sunpy.util.metadata.MetaDict(header)  
>>> my_map = sunpy.map.Map(data, meta)   

Data array and an astropy.wcs.WCS object#

>>> import astropy.wcs

>>> wcs = astropy.wcs.WCS(header=header)  
>>> my_map = sunpy.map.Map(data, wcs)  

Glob patterns#

If the glob pattern matches more than one FITS file, this will return a list of Map objects.

>>> my_map = sunpy.map.Map('eit_*.fits')   

URL#

>>> sample_data_url = 'http://data.sunpy.org/sunpy/v1/AIA20110607_063302_0171_lowres.fits'
>>> my_map = sunpy.map.Map(sample_data_url)  

Combinations of any of the above#

These can either be in a list or as separate arguments. As with the case of a directory or glob pattern, this will return multiple Map objects.

>>> my_map = sunpy.map.Map(['file1.fits', 'file2.fits', 'file3.fits', 'directory1/'])  
>>> my_map = sunpy.map.Map((data, header), data, meta, 'file1.fits', sample_data_url, 'eit_*.fits')  

How to draw a rectangle on a Map#

sunpy provides a convenient method called draw_quadrangle() to draw rectangles on maps. In this guide, we will demonstrate four different methods to draw a rectangle on a sunpy.map.Map.

Specify corners with a single SkyCoord#

We will use one SkyCoord to represent the two opposite corners.

import matplotlib.pyplot as plt

import astropy.units as u
from astropy.coordinates import SkyCoord

import sunpy.data.sample
import sunpy.map

aia_map = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)

fig = plt.figure()
ax = fig.add_subplot(projection=aia_map)

aia_map.plot(axes=ax, clip_interval=(1, 99.99)*u.percent)

coords = SkyCoord(Tx=(100, 500) * u.arcsec, Ty=(200, 500) * u.arcsec,frame=aia_map.coordinate_frame)
aia_map.draw_quadrangle(coords, axes=ax, edgecolor="blue")

plt.show()

(Source code, png, hires.png, pdf)

_images/create_rectangle_on_map-1.png

Specify corners with separate SkyCoord#

We will use two SkyCoord to represent the two opposite corners.

fig = plt.figure()
ax = fig.add_subplot(projection=aia_map)

aia_map.plot(axes=ax, clip_interval=(1, 99.99)*u.percent)

bottom_left = SkyCoord(100 * u.arcsec, 200 * u.arcsec, frame=aia_map.coordinate_frame)
top_right = SkyCoord(500 * u.arcsec, 500 * u.arcsec, frame=aia_map.coordinate_frame)
aia_map.draw_quadrangle(bottom_left, axes=ax, top_right=top_right, edgecolor="green")

plt.show()

(Source code, png, hires.png, pdf)

_images/create_rectangle_on_map-2.png

Specify one corner with a width and height#

We will use one SkyCoord to represent the bottom left and supply a width and height to complete the rectangle.

fig = plt.figure()
ax = fig.add_subplot(projection=aia_map)

aia_map.plot(axes=ax, clip_interval=(1, 99.99)*u.percent)

bottom_left = SkyCoord(100 * u.arcsec, 200 * u.arcsec, frame=aia_map.coordinate_frame)
width = 400 * u.arcsec
height = 300 * u.arcsec
aia_map.draw_quadrangle(bottom_left, axes=ax, width=width, height=height, edgecolor="yellow")

plt.show()

(Source code, png, hires.png, pdf)

_images/create_rectangle_on_map-3.png

Using pixel coordinates#

We will use a SkyCoord to work out the pixel coordinates instead of using coordinates as we do above.

fig = plt.figure()
ax = fig.add_subplot(projection=aia_map)
aia_map.plot(axes=ax, clip_interval=(1, 99.99)*u.percent)

bottom_left = aia_map.wcs.pixel_to_world(551 * u.pixel, 594 * u.pixel)
top_right = aia_map.wcs.pixel_to_world(717 * u.pixel, 719 * u.pixel)
aia_map.draw_quadrangle(bottom_left, axes=ax, top_right=top_right, edgecolor="red")

plt.show()

(Source code, png, hires.png, pdf)

_images/create_rectangle_on_map-4.png

Create Coordinate Objects#

The easiest interface to work with coordinates is through the SkyCoord class:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> from sunpy.coordinates import frames

>>> coord = SkyCoord(-100*u.arcsec, 500*u.arcsec, frame=frames.Helioprojective)
>>> coord = SkyCoord(x=-72241.0*u.km, y=361206.1*u.km, z=589951.4*u.km, frame=frames.Heliocentric)
>>> coord = SkyCoord(70*u.deg, -30*u.deg, frame=frames.HeliographicStonyhurst)
>>> coord
<SkyCoord (HeliographicStonyhurst: obstime=None, rsun=695700.0 km): (lon, lat) in deg
    (70., -30.)>

It is also possible to use strings to specify the frame but in that case make sure to explicitly import sunpy.coordinates as it registers sunpy’s coordinate frames the Astropy coordinates framework:

>>> import sunpy.coordinates

>>> coord = SkyCoord(-100*u.arcsec, 500*u.arcsec, frame='helioprojective', observer='earth')
>>> coord
<SkyCoord (Helioprojective: obstime=None, rsun=695700.0 km, observer=earth): (Tx, Ty) in arcsec
    (-100., 500.)>

SkyCoord and all coordinate frames support array coordinates. These work the same as single-value coordinates, but they store multiple coordinates in a single object. When you’re going to apply the same operation to many different coordinates, this is a better choice than a list of SkyCoord objects, because it will be much faster than applying the operation to each SkyCoord in a for loop:

>>> coord = SkyCoord([-500, 400]*u.arcsec, [100, 200]*u.arcsec, frame=frames.Helioprojective)
>>> coord
<SkyCoord (Helioprojective: obstime=None, rsun=695700.0 km, observer=None): (Tx, Ty) in arcsec
    [(-500.,  100.), ( 400.,  200.)]>
>>> coord[0]
<SkyCoord (Helioprojective: obstime=None, rsun=695700.0 km, observer=None): (Tx, Ty) in arcsec
    (-500.,  100.)>

Create Custom Maps by Hand#

It is possible to create Maps using custom data, e.g., from a simulation or an observation from a data source that is not explicitly supported by sunpy. To do this, you need to provide sunpy.map.Map with both the data array as well as appropriate metadata. The metadata informs sunpy.map.Map of the correct coordinate information associated with the data array and should be provided to sunpy.map.Map in the form of a header as a dict or MetaDict. See this Generating a map from data array for a brief demonstration of generating a Map from a data array.

The keys required for the header information follow the FITS standard. sunpy provides a Map header helper function to assist the user in creating a header that contains the correct meta information needed to create a Map. The utility function make_fitswcs_header() will return a header with the appropriate FITS keywords once the Map data array and an astropy.coordinates.SkyCoord or sunpy.coordinates.frames is provided. All the metadata keywords that a Map will parse along with their description are listed in the Meta Keywords at the end of this page.

make_fitswcs_header() also takes optional keyword arguments including reference_pixel and scale that describe the pixel coordinate at the reference coordinate (defined by the SkyCoord) and the spatial scale of the pixels, respectively. Here’s an example of creating a header from some generic data and an astropy.coordinates.SkyCoord:

>>> import numpy as np
>>> from astropy.coordinates import SkyCoord
>>> import astropy.units as u

>>> from sunpy.coordinates import frames
>>> from sunpy.map.header_helper import make_fitswcs_header

>>> data = np.arange(0, 100).reshape(10, 10)
>>> reference_coord = SkyCoord(0*u.arcsec, 0*u.arcsec, obstime = '2013-10-28', observer = 'earth', frame = frames.Helioprojective)
>>> header = make_fitswcs_header(data, reference_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
mjdref: 0.0
date-obs: 2013-10-28T00:00:00.000
rsun_ref: 695700000.0
dsun_obs: 148644585949.49
hgln_obs: 0.0
hglt_obs: 4.7711570596394
naxis: 2
naxis1: 10
naxis2: 10
pc1_1: 1.0
pc1_2: -0.0
pc2_1: 0.0
pc2_2: 1.0
rsun_obs: 965.3829548285768

From this we can see now that the function returned a sunpy.util.MetaDict that populated the standard FITS keywords with information provided by the passed astropy.coordinates.SkyCoord, and the data array. Since the reference_pixel and scale keywords were not passed in the example above, the values of the “CRPIXn” and “CDELTn” keys were set to the center of the data array and 1, respectively, by default.

These keywords can be passed to the function in the form of an astropy.units.Quantity with associated units. Here’s another example of passing reference_pixel and scale to the function:

>>> reference_pixel = u.Quantity([5, 5], u.pixel)
>>> scale = u.Quantity([2, 2], u.arcsec/u.pixel)
>>> header = make_fitswcs_header(data, reference_coord, reference_pixel=reference_pixel, scale=scale)
>>> for key, value in header.items():
...     print(f"{key}: {value}")
wcsaxes: 2
crpix1: 6.0
crpix2: 6.0
cdelt1: 2.0
cdelt2: 2.0
cunit1: arcsec
cunit2: arcsec
ctype1: HPLN-TAN
ctype2: HPLT-TAN
crval1: 0.0
crval2: 0.0
lonpole: 180.0
latpole: 0.0
mjdref: 0.0
date-obs: 2013-10-28T00:00:00.000
rsun_ref: 695700000.0
dsun_obs: 148644585949.49
hgln_obs: 0.0
hglt_obs: 4.7711570596394
naxis: 2
naxis1: 10
naxis2: 10
pc1_1: 1.0
pc1_2: -0.0
pc2_1: 0.0
pc2_2: 1.0
rsun_obs: 965.3829548285768

As we can see, a list of WCS and observer meta information is contained within the generated headers, however we may want to include other meta information including the observatory name, the wavelength and wavelength unit of the observation. Any valid FITS WCS keywords can be passed to the make_fitswcs_header() and will then populate the returned MetaDict header. Furthermore, the following observation keywords can be passed to the make_fitswcs_header function: observatory, instrument, telescope, wavelength, exposure.

An example of creating a header with these additional keywords:

>>> header = make_fitswcs_header(data, reference_coord, reference_pixel=reference_pixel, scale=scale,
...                              telescope='Test case',
...                              instrument='UV detector',
...                              wavelength=1000*u.angstrom)
>>> for key, value in header.items():
...     print(f"{key}: {value}")
wcsaxes: 2
crpix1: 6.0
crpix2: 6.0
cdelt1: 2.0
cdelt2: 2.0
cunit1: arcsec
cunit2: arcsec
ctype1: HPLN-TAN
ctype2: HPLT-TAN
crval1: 0.0
crval2: 0.0
lonpole: 180.0
latpole: 0.0
mjdref: 0.0
date-obs: 2013-10-28T00:00:00.000
rsun_ref: 695700000.0
dsun_obs: 148644585949.49
hgln_obs: 0.0
hglt_obs: 4.7711570596394
instrume: UV detector
telescop: Test case
wavelnth: 1000.0
waveunit: Angstrom
naxis: 2
naxis1: 10
naxis2: 10
pc1_1: 1.0
pc1_2: -0.0
pc2_1: 0.0
pc2_2: 1.0
rsun_obs: 965.3829548285768

From these header MetaDict’s that are generated, we can now create a custom map:

>>> import sunpy.map

>>> my_map = sunpy.map.Map(data, header)
>>> my_map
<sunpy.map.mapbase.GenericMap object at ...>
SunPy Map
---------
Observatory:                 Test case
Instrument:          UV detector
Detector:
Measurement:                 1000.0 Angstrom
Wavelength:          1000.0 Angstrom
Observation Date:    2013-10-28 00:00:00
Exposure Time:               Unknown
Dimension:           [10. 10.] pix
Coordinate System:   helioprojective
Scale:                       [2. 2.] arcsec / pix
Reference Pixel:     [5. 5.] pix
Reference Coord:     [0. 0.] arcsec
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
       [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
       [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
       [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
       [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
       [70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
       [80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
       [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])
Meta Keywords#

Keyword

Description

cunit1

Units of the coordinate increments along naxis1 e.g. arcsec (required)

cunit2

Units of the coordinate increments along naxis2 e.g. arcsec (required)

crval1

Coordinate value at reference point on naxis1 (required)

crval2

Coordinate value at reference point on naxis2 (required)

cdelt1

Spatial scale of pixels for naxis1, i.e. coordinate increment at reference point

cdelt2

Spatial scale of pixels for naxis2, i.e. coordinate increment at reference point

crpix1

Pixel coordinate at reference point naxis1

crpix2

Pixel coordinate at reference point naxis2

ctype1

Coordinate type projection along naxis1 of data e.g. HPLT-TAN

ctype2

Coordinate type projection along naxis2 of data e.g. HPLN-TAN

hgln_obs

Heliographic longitude of observation

hglt_obs

Heliographic latitude of observation

dsun_obs

distance to Sun from observation in metres

rsun_obs

radius of Sun in meters from observation

dateobs

date of observation e.g. 2013-10-28 00:00

date_obs

date of observation e.g. 2013-10-28 00:00

rsun_ref

reference radius of Sun in meters

solar_r

radius of Sun in meters from observation

radius

radius of Sun in meters from observation

crln_obs

Carrington longitude of observation

crlt_obs

Heliographic latitude of observation

solar_b0

Solar B0 angle

detector

name of detector e.g. AIA

exptime

exposure time of observation, in seconds e.g 2

instrume

name of instrument

wavelnth

wavelength of observation

waveunit

unit for which observation is taken e.g. angstom

obsrvtry

name of observatory of observation

telescop

name of telescope of observation

lvl_num

FITS processing level

crota2

Rotation of the horizontal and vertical axes in degrees

PC1_1

Matrix element PCi_j describing the rotation required to align solar North with the top of the image.

PC1_2

Matrix element PCi_j describing the rotation required to align solar North with the top of the image.

PC2_1

Matrix element PCi_j describing the rotation required to align solar North with the top of the image.

PC2_2

Matrix element PCi_j describing the rotation required to align solar North with the top of the image.

CD1_1

Matrix element CDi_j describing the rotation required to align solar North with the top of the image.

CD1_2

Matrix element CDi_j describing the rotation required to align solar North with the top of the image.

CD2_1

Matrix element CDi_j describing the rotation required to align solar North with the top of the image.

CD2_2

Matrix element CDi_j describing the rotation required to align solar North with the top of the image.

Create Custom TimeSeries#

Sometimes you will have data that you want to transform into a TimeSeries. You can use the factory to create a GenericTimeSeries from a variety of data sources currently including pandas.DataFrame and astropy.table.Table.

Creating a TimeSeries from a Pandas DataFrame#

A TimeSeries object must be supplied with some data when it is created. The data can either be in your current Python session, in a local file, or in a remote file. Let’s create some data and pass it into a TimeSeries object:

>>> import numpy as np

>>> intensity = np.sin(np.arange(0, 12 * np.pi, ((12 * np.pi) / (24*60))))

This creates a basic numpy array of values representing a sine wave. We can use this array along with a suitable time storing object (such as Astropy time or a list of datetime objects) to make a Pandas DataFrame. A suitable list of times must contain the same number of values as the data, this can be created using:

>>> import datetime

>>> base = datetime.datetime.today()
>>> times = [base - datetime.timedelta(minutes=x) for x in range(24*60, 0, -1)]

The Pandas DataFrame will use the dates list as the index:

>>> from pandas import DataFrame

>>> data = DataFrame(intensity, index=times, columns=['intensity'])

This DataFrame can then be used to construct a TimeSeries:

>>> import astropy.units as u

>>> import sunpy.timeseries as ts

>>> header = {'key': 'value'}
>>> units = {'intensity': u.W/u.m**2}
>>> ts_custom = ts.TimeSeries(data, header, units)

Creating Custom TimeSeries from an Astropy Table#

A Pandas DataFrame is the underlying object used to store the data within a TimeSeries, so the above example is the most lightweight to create a custom TimeSeries, but being scientific data it will often be more convenient to use an Astropy Table to create a TimeSeries. An advantage of this method is it allows you to include metadata and Astropy Quantity values, which are both supported in tables, without additional arguments. For example:

>>> from astropy.time import Time
>>> from astropy.table import Table

>>> base = datetime.datetime.today()
>>> times = [base - datetime.timedelta(minutes=x) for x in range(24*60, 0, -1)]
>>> intensity = u.Quantity(np.sin(np.arange(0, 12 * np.pi, ((12 * np.pi) / (24*60)))), u.W/u.m**2)
>>> tbl_meta = {'t_key':'t_value'}
>>> table = Table([times, intensity], names=['time', 'intensity'], meta=tbl_meta)
>>> table.add_index('time')
>>> ts_table = ts.TimeSeries(table)

Note that due to the properties of the Time object, this will be a mixin column which since it is a single object, limits the versatility of the Table a little. For more on mixin columns see the Astropy docs. The units will be taken from the table quantities for each column, the metadata will simply be the table.meta dictionary. You can also explicitly add metadata and units, these will be added to the relevant dictionaries using the dictionary update method, with the explicit user-given values taking precedence:

>>> from collections import OrderedDict

>>> from sunpy.util.metadata import MetaDict

>>> meta = MetaDict({'key':'value'})
>>> units = OrderedDict([('intensity', u.W/u.m**2)])
>>> ts_table = ts.TimeSeries(table, meta, units)

Fixing incorrect metadata#

There will be times where you will come across a FITS files with either incorrect, missing or unparsable metadata and reading these files into Map will cause an error. Therefore, to load these files into a Map, you will need to correct the metadata beforehand.

In the example below, the units in the FITS header, as controlled by the CUNIT1 and CUNIT2 keywords, are incorrect. Before loading the file into a Map, we will correct these keywords to have the correct units.

>>> from astropy.io import fits

>>> from sunpy.map import Map
>>> import sunpy.data.sample

>>> filepath = sunpy.data.sample.AIA_171_IMAGE  
>>> data, header = fits.getdata(filepath, header=True)  
>>> # Note that it is case insensitive for the keys
>>> header['cunit1'] = 'arcsec'  
>>> header['cunit2'] = 'arcsec'  
>>> updated_map = Map(data, header)  

This applies for any keyword in the FITS standard.

Manipulate grids lines when plotting Maps#

Underneath the hood, sunpy.map.GenericMap.plot uses WCSAxes to have to ability to plot images in world coordinates. This means that sometimes the standard matplotlib methods to manipulate grid lines may not work as expected. So you have to access the WCSAxes object to manipulate the grid lines.

Astropy have a guide on how to manipulate grid lines in WCSAxes. Here we provide a solar example of how to manipulate grid lines when plotting a GenericMap.

Turning off the Helioprojective grid#

By default sunpy.map.GenericMap.plot draws a grid tracing the lines of helioprojective latitude and longitude, as below:

import matplotlib.pyplot as plt
import astropy.units as u

import sunpy.map
from sunpy.data.sample import HMI_LOS_IMAGE

smap = sunpy.map.Map(HMI_LOS_IMAGE)

fig = plt.figure()
ax = fig.add_subplot(111, projection=smap)

smap.plot(axes=ax)

plt.show()

(Source code, png, hires.png, pdf)

_images/manipulate_grid_lines-1.png

In some cases it may be desirable to remove this grid. Since the underlying axes is a WCSAxes, you will need to access the grid method on the axes’ object coord attribute:

fig = plt.figure()
ax = fig.add_subplot(111, projection=smap)

smap.plot(axes=ax)

ax.coords[0].grid(draw_grid=False)  # Disable grid for 1st axis
ax.coords[1].grid(draw_grid=False)  # Disable grid for 2nd axis

plt.show()

(Source code, png, hires.png, pdf)

_images/manipulate_grid_lines-2.png

Changing the appearance of Helioprojective grid#

As the Helioprojective grid is applied by WCSAxes, if you want to change the appeerance of the grid lines, you will need to access the grid method on the axes’ object coord attribute and pass the desired keywords to it:

fig = plt.figure()
ax = fig.add_subplot(111, projection=smap)

smap.plot(axes=ax)

ax.coords[0].grid(color='yellow', linestyle='solid', alpha=0.5)
ax.coords[1].grid(color='red', linestyle='dotted', alpha=0.5)

plt.show()

(Source code, png, hires.png, pdf)

_images/manipulate_grid_lines-3.png

Changing the appearance of Heliographic grid#

sunpy.map.GenericMap.draw_grid allows users to overlay a Heliographic grid on their solar image plot. This method does not explicitly provide many options for the appearance of the grid lines. Instead it accepts keyword arguments and transparently passes them onto the underlying infrastructure. Therefore to change the width of the lines to say, 0.25, and their style to say, dotted, do the following:

fig = plt.figure()
ax = fig.add_subplot(111, projection=smap)

smap.plot(axes=ax)

smap.draw_grid(axes=ax, linewidth=0.25, linestyle="dotted")

plt.show()

(Source code, png, hires.png, pdf)

_images/manipulate_grid_lines-4.png

Parse times with parse_time#

>>> import time
>>> from datetime import date, datetime

>>> import numpy as np
>>> import pandas

>>> from sunpy.time import parse_time

The following examples show how to use sunpy.time.parse_time to parse various time formats, including both strings and objects, into an astropy.time.Time object.

Strings#

>>> parse_time('1995-12-31 23:59:60')
<Time object: scale='utc' format='isot' value=1995-12-31T23:59:60.000>

This also works with the scale= keyword argument (See this list: for the list of all allowed scales):

>>> parse_time('2012:124:21:08:12', scale='tai')
<Time object: scale='tai' format='isot' value=2012-05-03T21:08:12.000>

Tuples#

>>> parse_time((1998, 11, 14))
<Time object: scale='utc' format='isot' value=1998-11-14T00:00:00.000>
>>> parse_time((2001, 1, 1, 12, 12, 12, 8899))
<Time object: scale='utc' format='isot' value=2001-01-01T12:12:12.009>

time.struct_time#

>>> parse_time(time.gmtime(0))
<Time object: scale='utc' format='isot' value=1970-01-01T00:00:00.000>

datetime.datetime and datetime.date#

>>> parse_time(datetime(1990, 10, 15, 14, 30))
<Time object: scale='utc' format='datetime' value=1990-10-15 14:30:00>
>>> parse_time(date(2023, 4, 22))
<Time object: scale='utc' format='iso' value=2023-04-22 00:00:00.000>

pandas time objects#

pandas.Timestamp, pandas.Series and pandas.DatetimeIndex

>>> parse_time(pandas.Timestamp(datetime(1966, 2, 3)))
<Time object: scale='utc' format='datetime64' value=1966-02-03T00:00:00.000000000>
>>> parse_time(pandas.Series([[datetime(2012, 1, 1, 0, 0), datetime(2012, 1, 2, 0, 0)],
...                           [datetime(2012, 1, 3, 0, 0), datetime(2012, 1, 4, 0, 0)]]))
<Time object: scale='utc' format='datetime' value=[[datetime.datetime(2012, 1, 1, 0, 0) datetime.datetime(2012, 1, 2, 0, 0)]
                                                   [datetime.datetime(2012, 1, 3, 0, 0) datetime.datetime(2012, 1, 4, 0, 0)]]>
>>> parse_time(pandas.DatetimeIndex([datetime(2012, 1, 1, 0, 0),
...                                  datetime(2012, 1, 2, 0, 0),
...                                  datetime(2012, 1, 3, 0, 0),
...                                  datetime(2012, 1, 4, 0, 0)]))
<Time object: scale='utc' format='datetime' value=[datetime.datetime(2012, 1, 1, 0, 0)
                                                   datetime.datetime(2012, 1, 2, 0, 0)
                                                   datetime.datetime(2012, 1, 3, 0, 0)
                                                   datetime.datetime(2012, 1, 4, 0, 0)]>

numpy.datetime64#

>>> parse_time(np.datetime64('2014-02-07T16:47:51.008288123'))
<Time object: scale='utc' format='isot' value=2014-02-07T16:47:51.008>
>>> parse_time(np.array(['2014-02-07T16:47:51.008288123', '2014-02-07T18:47:51.008288123'],
...                     dtype='datetime64'))
<Time object: scale='utc' format='isot' value=['2014-02-07T16:47:51.008' '2014-02-07T18:47:51.008']>

Formats handled by astropy.time.Time#

See this list of all the allowed formats.

>>> parse_time(1234.0, format='jd')
<Time object: scale='utc' format='jd' value=1234.0>
>>> parse_time('B1950.0', format='byear_str')
<Time object: scale='tt' format='byear_str' value=B1950.000>

anytim output#

Format output by the anytim routine in SolarSoft (see the documentation for TimeUTime for more information):

>>> parse_time(662738003, format='utime')
<Time object: scale='utc' format='utime' value=662738003.0>

anytim2tai output#

Format output by the anytim2tai routine in SolarSoft (see the documentation for TimeTaiSeconds for more information):

>>> parse_time(1824441848, format='tai_seconds')
<Time object: scale='tai' format='tai_seconds' value=1824441848.0>

Use the Remote Data Manager#

Often, data prep or analysis functions require files to be downloaded from a remote server. The remote data manager handles the usage of remote data files including file verification using hashes. For example, say a function, test_function, requires the remote data file: predicted-sunspot-radio-flux.txt, which has the SHA256 hash “4c85b04a5528aa97eb84a087450eda0421c71833820576330bba148564089b11”. To ensure that this exact version of the file is downloaded when test_function is called, use the require decorator.

>>> from sunpy.data import manager

>>> @manager.require('test_file',
...                  ['http://data.sunpy.org/sample-data/predicted-sunspot-radio-flux.txt'],
...                  '4c85b04a5528aa97eb84a087450eda0421c71833820576330bba148564089b11')
... def test_function():
...     pass

The first time the function is called, the file will be downloaded and then cached such that subsequent function calls will not trigger a download.

>>> test_function()  # The file will be downloaded  
>>> test_function()  # No download  

To access the downloaded file inside the function, use the get() method,

>>> @manager.require('test_file',
...                  ['http://data.sunpy.org/sample-data/predicted-sunspot-radio-flux.txt'],
...                  '4c85b04a5528aa97eb84a087450eda0421c71833820576330bba148564089b11')
... def test_function():
...     return manager.get('test_file')

To call a function that uses the data manager, but skip the hash check, use the skip_hash_check context manager.

>>> with manager.skip_hash_check():
...     test_function()  
PosixPath('.../sunpy/data_manager/predicted-sunspot-radio-flux.txt')

To replace the required file with another version, use the override_file context manager.

>>> with manager.override_file('test_file', 'http://data.sunpy.org/sample-data/AIA20110319_105400_0171.fits'):
...     test_function()  
PosixPath('.../sunpy/data_manager/AIA20110319_105400_0171.fits')

Search for multiple wavelengths with Fido#

Use the Wavelength to search for a particular wavelength:

>>> from astropy import units as u

>>> from sunpy.net import Fido
>>> from sunpy.net import attrs as a

>>> time_range = a.Time("2022-02-20 00:00:00", "2022-02-20 00:00:30")
>>> aia_search = Fido.search(time_range,
...                          a.Instrument.aia,
...                          a.Wavelength(171*u.angstrom))  
>>> aia_search  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

2 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 135.578 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:09.000 2022-02-20 00:00:10.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:21.000 2022-02-20 00:00:22.000    SDO ...    FULLDISK 64.64844

The “|” operator can be used to combine multiple wavelengths:

>>> aia_search = Fido.search(time_range,
...                          a.Instrument.aia,
...                          a.Wavelength(171*u.angstrom) | a.Wavelength(193*u.angstrom))  
>>> aia_search  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

2 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 135.578 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:09.000 2022-02-20 00:00:10.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:21.000 2022-02-20 00:00:22.000    SDO ...    FULLDISK 64.64844

3 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 203.366 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:04.000 2022-02-20 00:00:05.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:16.000 2022-02-20 00:00:17.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:28.000 2022-02-20 00:00:29.000    SDO ...    FULLDISK 64.64844

When searching for more than two wavelengths, it is more practical to use the sunpy.net.attrs.AttrOr() function:

>>> wavelengths = [94, 131, 171, 193, 211]*u.angstrom
>>> aia_search = Fido.search(time_range,
...                         a.Instrument.aia,
...                         a.AttrOr([a.Wavelength(wav) for wav in wavelengths]))  
>>> aia_search  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 5 Providers:

2 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 135.578 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:11.000 2022-02-20 00:00:12.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:23.000 2022-02-20 00:00:24.000    SDO ...    FULLDISK 64.64844

3 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 203.366 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:06.000 2022-02-20 00:00:07.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:18.000 2022-02-20 00:00:19.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:30.000 2022-02-20 00:00:31.000    SDO ...    FULLDISK 64.64844

2 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 135.578 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:09.000 2022-02-20 00:00:10.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:21.000 2022-02-20 00:00:22.000    SDO ...    FULLDISK 64.64844

3 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 203.366 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:04.000 2022-02-20 00:00:05.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:16.000 2022-02-20 00:00:17.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:28.000 2022-02-20 00:00:29.000    SDO ...    FULLDISK 64.64844

2 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 135.578 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2022-02-20 00:00:09.000 2022-02-20 00:00:10.000    SDO ...    FULLDISK 64.64844
2022-02-20 00:00:21.000 2022-02-20 00:00:22.000    SDO ...    FULLDISK 64.64844

Search the Virtual Solar Observatory (VSO)#

To search the Virtual Solar Observatory (VSO) for SDO AIA data in all channels over a given time range, use the timerange (Time) and the instrument (Instrument) attrs,

>>> import astropy.units as u

>>> from sunpy.net import Fido
>>> from sunpy.net import attrs as a

>>> time_range = a.Time('2020/03/04 00:00:15', '2020/03/04 00:00:30')
>>> result = Fido.search(time_range, a.Instrument.aia)  
>>> result  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

11 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 745.677 Mbyte

       Start Time               End Time        Source  ... Extent Type   Size
                                                        ...              Mibyte
----------------------- ----------------------- ------  ... ----------- --------
2020-03-04 00:00:16.000 2020-03-04 00:00:17.000    SDO  ...    FULLDISK 64.64844
2020-03-04 00:00:17.000 2020-03-04 00:00:18.000    SDO  ...    FULLDISK 64.64844
2020-03-04 00:00:18.000 2020-03-04 00:00:19.000    SDO  ...    FULLDISK 64.64844
                    ...                     ...    ...  ...         ...      ...
2020-03-04 00:00:28.000 2020-03-04 00:00:29.000    SDO  ...    FULLDISK 64.64844
2020-03-04 00:00:29.000 2020-03-04 00:00:30.000    SDO  ...    FULLDISK 64.64844
2020-03-04 00:00:30.000 2020-03-04 00:00:31.000    SDO  ...    FULLDISK 64.64844

You can also search for specific physical observables using Physobs. For example, you can search for line-of-sight (LOS) magnetic field measurements from HMI in the same time range,

>>> Fido.search(time_range, a.Instrument.hmi, a.Physobs.los_magnetic_field)  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:26.000 2020-03-04 00:00:27.000    SDO ...    FULLDISK -0.00098

You can also use relational operators when constructing queries. For example, the AIA query above can also be expressed using the AND (&) operator,

>>> Fido.search(time_range & a.Instrument.aia)  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

11 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 745.677 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:16.000 2020-03-04 00:00:17.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:17.000 2020-03-04 00:00:18.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:18.000 2020-03-04 00:00:19.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:21.000 2020-03-04 00:00:22.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:21.000 2020-03-04 00:00:22.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:23.000 2020-03-04 00:00:24.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:24.000 2020-03-04 00:00:25.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:28.000 2020-03-04 00:00:29.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:28.000 2020-03-04 00:00:29.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:29.000 2020-03-04 00:00:30.000    SDO ...    FULLDISK 64.64844
2020-03-04 00:00:30.000 2020-03-04 00:00:31.000    SDO ...    FULLDISK 64.64844

Additionally, multiple operators can be chained together to, for example, search for only the 171 channel,

>>> Fido.search(time_range & a.Instrument.aia & a.Wavelength(171*u.angstrom))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 67.789 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:21.000 2020-03-04 00:00:22.000    SDO ...    FULLDISK 64.64844

The OR operator (|) can also be used to construct queries. For example, to search for AIA data in this same time range from both the 94 and 171 channels,

>>> Fido.search(time_range,
...             a.Instrument.aia,
...             a.Wavelength(171*u.angstrom) | a.Wavelength(94*u.angstrom))  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 2 Providers:

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 67.789 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:21.000 2020-03-04 00:00:22.000    SDO ...    FULLDISK 64.64844

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 67.789 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:23.000 2020-03-04 00:00:24.000    SDO ...    FULLDISK 64.64844

These relational operators are particularly useful when searching a given time interval for multiple instruments. For example, to find the HMI LOS magnetic field data and the AIA 94 and 171 data in the given time interval,

>>> aia_params = a.Instrument.aia & (a.Wavelength(171*u.angstrom) | a.Wavelength(94*u.angstrom))
>>> hmi_params = a.Instrument.hmi & a.Physobs.los_magnetic_field
>>> Fido.search(time_range, aia_params | hmi_params)  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 3 Providers:

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 67.789 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:21.000 2020-03-04 00:00:22.000    SDO ...    FULLDISK 64.64844

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search
Total estimated size: 67.789 Mbyte

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:23.000 2020-03-04 00:00:24.000    SDO ...    FULLDISK 64.64844

1 Results from the VSOClient:
Source: http://vso.stanford.edu/cgi-bin/search

       Start Time               End Time        Source ... Extent Type   Size
                                                       ...              Mibyte
----------------------- ----------------------- ------ ... ----------- --------
2020-03-04 00:00:26.000 2020-03-04 00:00:27.000    SDO ...    FULLDISK -0.00098

Transform between coordinate frames#

Both SkyCoord and BaseCoordinateFrame instances have a transform_to method. This can be used to transform the frame to any other frame, either implemented in sunpy or in Astropy (see also Transforming between Systems). An example of transforming the center of the solar disk to Carrington coordinates is:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord

>>> from sunpy.coordinates import frames

>>> coord = SkyCoord(0*u.arcsec, 0*u.arcsec, frame=frames.Helioprojective, obstime="2017-07-26",
...              observer="earth")
>>> coord
<SkyCoord (Helioprojective: obstime=2017-07-26T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty) in arcsec
    (0., 0.)>
>>> coord.transform_to(frames.HeliographicCarrington)
<SkyCoord (HeliographicCarrington: obstime=2017-07-26T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, AU)
    (283.95956776, 5.31701821, 0.00465047)>

It is also possible to transform to any coordinate system implemented in Astropy. This can be used to find the position of the solar limb in AltAz equatorial coordinates:

>>> from astropy.coordinates import EarthLocation, AltAz

>>> time = '2017-07-11 15:00'
>>> greenbelt = EarthLocation(lat=39.0044*u.deg, lon=-76.8758*u.deg)
>>> greenbelt_frame = AltAz(obstime=time, location=greenbelt)
>>> west_limb = SkyCoord(900*u.arcsec, 0*u.arcsec, frame=frames.Helioprojective,
...                      observer=greenbelt.get_itrs(greenbelt_frame.obstime), obstime=time)  
>>> west_limb.transform_to(greenbelt_frame)  
<SkyCoord (AltAz: obstime=2017-07-11 15:00:00.000, location=(1126916.53031967, -4833386.58391627, 3992696.62211575) m, pressure=0.0 hPa, temperature=0.0 deg_C, relative_humidity=0.0, obswl=1.0 micron): (az, alt, distance) in (deg, deg, m)
    (111.40782056, 57.1660434, 1.51859559e+11)>

Quick Reference

The following table is meant as a quick reference. For more complete code examples, see the how-to guides above.

How do I…

Solution

create a map from a FITS file

my_map = Map('file.fits')

save a map to a FITS file

my_map.save('another_file.fits')

get a quicklook summary of a map

my_map.quicklook(), my_map.peek()

plot a map

my_map.plot()

access the underlying data array of a map

my_map.data

access the map metadata

my_map.meta

make a copy of the map data array

my_map.data.copy()

make a copy of the whole map

copy.deepcopy(my_map)

access the observer location

my_map.observer_coordinate

remove the roll angle

my_map.rotate()

plot the solar limb

my_map.draw_limb()

overlay a heliographic grid

my_map.draw_grid()

create a time series

my_timeseries = TimeSeries('file.nc')

plot a time series

my_timeseries.plot()

concatenate two time series together

my_timeseries.concatenate(another_timeseries)

convert a time series to a pandas.DataFrame

my_timeseries.to_dataframe()

convert a time series to an astropy.table.Table

my_timeseries.to_table()

parse the string representation of a timestamp

parse_time()

calculate the Carrington rotation number at a given time

carrington_rotation_number()

calculate the time corresponding to a given Carrington rotation

carrington_rotation_time()

see all of the available solar constants

sunpy.sun.constants.print_all()

Topic Guides#

These topic guides provide a set of in-depth explanations for various parts of the sunpy core package. They assumes you have some knowledge of what sunpy is and how it works - if you’re starting fresh you might want to check out the Tutorial first.

How to use the topic guides

The topic guides are designed to be read in a standalone manner, without running code at the same time. Although there are code snippets in various parts of each topic guide, these are present to help explanation, and are not structured in a way that they can be run as you are reading a topic guide.

Coordinates#

The sunpy.coordinates sub-package contains:

  • A robust framework for working with solar-physics coordinate systems

  • Functions to obtain the locations of solar-system bodies (sunpy.coordinates.ephemeris)

  • Functions to calculate Sun-specific coordinate information (sunpy.coordinates.sun)

The sunpy coordinate framework extends the Astropy coordinates framework.

This page contains an overview of how coordinates work in sunpy. See the following pages for detailed discussion of specific topics:

Calculating Carrington longitude#

Carrington coordinates are a heliographic coordinate system, where the longitude is determined assuming that the Sun has rotated at a sidereal period of ~25.38 days starting at zero longitude at a specific time on 1853 November 9. For a given observer at a given observation time, the center of the disk of the Sun as seen by that observer (also known as the apparent [1] sub-observer point) has a Carrington latitude and longitude. When the observer is at Earth, the apparent sub-Earth [2] Carrington latitude and longitude are also known as B0 and L0, respectively.

Quick summary#

sunpy calculates the sub-observer Carrington longitude in a manner that enables co-alignment of images of the Sun’s surface from different observatories at different locations/velocities in the solar system, but that results in a ~20 arcsecond discrepancy with The Astronomical Almanac.

Observer effects#

An observer will perceive the locations and orientations of solar-system objects different from how they truly are. There are two primary effects for why the apparent sub-observer point is not the same as the “true” sub-observer point.

Light travel time#

It takes time for light to travel from the Sun to any observer (e.g., ~500 seconds for an observer at Earth). Thus, the farther the observer is from the Sun, the farther back in time the Sun will appear to be. This effect is sometimes called “planetary aberration”.

As an additional detail, the observer is closer to the sub-observer point on the Sun’s surface than to the center of the Sun (i.e., by the radius of the Sun), which corresponds to ~2.3 lightseconds.

Observer motion#

The motion of an observer will shift the apparent location of objects, and this effect is called “stellar aberration”. A more subtle question is whether observer motion also shifts the apparent sub-observer Carrington longitude.

Stellar aberration causes the apparent ecliptic longitude of an observer to be different from its true ecliptic longitude. This shift in ecliptic longitude also means a shift in the heliographic longitude of the observer. For example, for an Earth observer, the apparent heliographic longitude of Earth is ~0.006 degrees (~20 arcseconds) less than its true heliographic longitude. For some purposes, this shift in the heliographic longitude of the observer should also shift the apparent sub-observer Carrington longitude.

However, shifting the sub-observer Carrington longitude due to observer motion is not always appropriate. For certain types of observations (e.g., imagery of features on the surface of the Sun), stellar aberration does not shift the positions of solar features relative to the center of the disk of the Sun; everything shifts in tandem. Thus, for the purposes of co-aligning images from different observatories, it is critical to exclude any correction for observer motion.

Background information#
The IAU definition#

Since the original definition of Carrington coordinates [3], there have been a number of refinements of the definition, but there have been inconsistencies with how those definitions are interpreted. Possible points of contention include the reference epoch, the rotation period, and the handling of observer effects.

The most recent definition was essentially laid down in 2007 by the IAU [4]:

  • The reference epoch is J2000.0 TDB (2000 January 1 12:00 TDB). The difference between Barycentric Dynamical Time (TDB) and Terrestrial Time (TT) is very small, but both are appreciably different from Coordinated Universal Time (UTC).

  • The longitude of the prime meridian (W0) at the reference epoch is 84.176 degrees. This longitude is the “true” longitude, without accounting for any effects that would modify the apparent prime meridian for an observer (e.g., light travel time).

  • The sidereal rotation rate is exactly 14.1844 degrees per day. This is close to, but not exactly the same as, a sidereal rotation period of 25.38 days or a mean synodic period of 27.2753 days.

  • The ICRS coordinates of the Sun’s north pole of rotation is given as a right ascension of 286.13 degrees and a declination of 63.87 degrees.

The tuning of W0#

Importantly, the IAU parameters (specifically, W0) were tuned following an investigation by The Astronomical Almanac [5]. The prescription of The Astronomical Almanac included both the correction for light travel time and the correction for stellar aberration due to Earth’s motion. When using this prescription, The Astronomical Almanac found that a W0 value of 84.176 degrees minimized the differences between the modern approach and earlier approaches. However, for those purposes where one should use sub-observer Carrington longitudes without the stellar-aberration correction, there will be a discrepancy compared to The Astronomical Almanac and to calculations made using earlier approaches.

The approach in sunpy#

sunpy determines apparent sub-observer Carrington longitude by including the correction for the difference in light travel time, but excluding any correction for stellar aberration due to observer motion. The exclusion of a stellar-aberration correction is appropriate for purposes such as image co-alignment, where Carrington longitudes must be consistently associated with features on the Sun’s surface.

Comparisons to other sources#
Compared to The Astronomical Almanac and older methods of calculation#

The Astronomical Almanac publishes the apparent sub-Earth Carrington longitude (L0), and these values include the stellar-aberration correction. Consequently, sunpy values will be consistently ~0.006 degrees (~20 arcseconds) greater than The Astronomical Almanac values, although these discrepancies are not always apparent at the printed precision (0.01 degrees).

Since The Astronomical Almanac specifically tuned the IAU parameters to minimize the discrepancies with older methods of calculation under their prescription that includes the stellar-aberration correction, sunpy values will also be ~20 arcseconds greater than values calculated using older methods. Be aware that older methods of calculation may not have accounted for the variable light travel time between the Sun and the Earth, which can cause additional discrepancies of up to ~5 arcseconds.

The Astronomical Almanac does not appear to account for the difference in light travel time between the sub-Earth point on the Sun’s surface and the center of the Sun (~2.3 lightseconds), which results in a fixed discrepancy of ~1.4 arcseconds in L0. However, this additional discrepancy is much smaller than the difference in treatment of stellar aberration.

Compared to SPICE#

In SPICE, the apparent sub-observer Carrington longitude can be calculated using the function subpnt. subpnt allows for two types of aberration correction through the input argument abcorr:

  • “LT”, which is just the correction for light travel time

  • “LT+S”, which is the correction for light travel time (“LT”) plus the correction for observer motion (“S”)

sunpy calculates the apparent sub-observer Carrington longitude in a manner equivalent to specifying “LT” (as opposed to “LT+S”). The discrepancy between sunpy values and SPICE values is no greater than 0.01 arcseconds.

Compared to JPL Horizons#

In JPL Horizons, one can request the “Obs sub-long & sub-lat”. JPL Horizons appears to start from the IAU parameters and to include the correction for light travel time but not the correction for observer motion (i.e., equivalent to specifying “LT” to subpnt in SPICE). The discrepancy between sunpy values and JPL Horizons values is no greater than 0.01 arcseconds.

Compared to SunSPICE#

In SunSPICE, one can convert to and from the “Carrington” coordinate system using the function convert_sunspice_coord. However, these Carrington longitudes are “true” rather than “apparent” because the observer is not specified, so there are no corrections for light travel time or for observer motion. For example, for an Earth observer, sunpy values will be consistently greater than SunSPICE’s coordinate transformation by ~0.82 degrees.

Footnotes#

Differential rotation using coordinate frames#

Normally, coordinates refer to a point in inertial space (relative to the barycenter of the solar system). Transforming to a different observation time does not move the point itself, but if the coordinate frame origin and/or axis change with time, the coordinate representation is updated to account for this change. In solar physics an example of a frame that changes with time is the HeliographicStonyhurst (HGS) frame. Its origin moves with the center of the Sun, and its orientation rotates such that the longitude component of Earth is zero at any given time. A coordinate in a HGS frame of reference transformed to a HGS frame defined a day later will have a different longitude:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord

>>> from sunpy.coordinates import HeliographicStonyhurst

>>> hgs_coord = SkyCoord(0*u.deg, 0*u.deg, radius=1*u.au, frame='heliographic_stonyhurst', obstime="2001-01-01")
>>> new_frame = HeliographicStonyhurst(obstime="2001-01-02")
>>> new_hgs_coord = hgs_coord.transform_to(new_frame)
>>> hgs_coord.lon, new_hgs_coord.lon
(<Longitude 0. deg>, <Longitude -1.01372559 deg>)

but when transformed to an inertial frame of reference we can see that these two coordinates refer to the same point in space:

>>> hgs_coord.transform_to('icrs')
<SkyCoord (ICRS): (ra, dec, distance) in (deg, deg, AU)
    (101.79107615, 26.05004621, 0.99601156)>
>>> new_hgs_coord.transform_to('icrs')
<SkyCoord (ICRS): (ra, dec, distance) in (deg, deg, AU)
    (101.79107615, 26.05004621, 0.99601156)>

To evolve a coordinate in time such that it accounts for the rotational motion of the Sun, one can use the RotatedSunFrame “metaframe” class as described below. This machinery will take into account the latitude-dependent rotation rate of the solar surface, also known as differential rotation. Multiple models for differential rotation are supported (see diff_rot() for details).

Note

RotatedSunFrame is a powerful metaframe, but can be tricky to use correctly. We recommend users to first check if the simpler propagate_with_solar_surface() context manager is sufficient for their needs.

In addition, one may want to account for the translational motion of the Sun as well, and that can be achieved by also using the context manager transform_with_sun_center() for desired coordinate transformations.

Basics of the RotatedSunFrame class#

The RotatedSunFrame class allows one to specify coordinates in a coordinate frame prior to an amount of solar (differential) rotation being applied. That is, the coordinate will point to a location in inertial space at some time, but will use a coordinate system at a different time to refer to that point, while accounting for the differential rotation between those two times.

RotatedSunFrame is not itself a coordinate frame, but is instead a “metaframe”. A new frame class is created on the fly corresponding to each base coordinate frame class. This tutorial will refer to these new classes as RotatedSun* frames.

Creating coordinates#

RotatedSunFrame requires two inputs: the base coordinate frame and the duration of solar rotation. The base coordinate frame needs to be fully specified, which means a defined obstime and, if relevant, a defined observer. Note that the RotatedSun* frame that is created in this example is appropriately named RotatedSunHeliographicStonyhurst:

>>> from sunpy.coordinates import RotatedSunFrame
>>> import sunpy.coordinates.frames as frames

>>> base_frame = frames.HeliographicStonyhurst(obstime="2001-01-01")
>>> rs_hgs = RotatedSunFrame(base=base_frame, duration=1*u.day)
>>> rs_hgs
<RotatedSunHeliographicStonyhurst Frame (base=<HeliographicStonyhurst Frame (obstime=2001-01-01T00:00:00.000, rsun=695700.0 km)>, duration=1.0 d, rotation_model=howard)>

Once a RotatedSun* frame is created, it can be used in the same manner as other frames. Here, we create a SkyCoord using the RotatedSun* frame:

>>> rotated_coord = SkyCoord(0*u.deg, 0*u.deg, frame=rs_hgs)
>>> rotated_coord
<SkyCoord (RotatedSunHeliographicStonyhurst: base=<HeliographicStonyhurst Frame (obstime=2001-01-01T00:00:00.000, rsun=695700.0 km)>, duration=1.0 d, rotation_model=howard): (lon, lat) in deg
    (0., 0.)>

Transforming this into the original heliographic Stonyhurst frame, we can see that the longitude is equal to the original zero degrees, plus an extra offset to account for one day of differential rotation:

>>> rotated_coord.transform_to(base_frame).lon
<Longitude 14.32632838 deg>

Instead of explicitly specifying the duration of solar rotation, one can use the keyword argument rotated_time. The duration will be automatically calculated from the difference between rotated_time and the obstime value of the base coordinate frame. Here, we also include coordinate data in the supplied base coordinate frame:

>>> rs_hgc = RotatedSunFrame(base=frames.HeliographicCarrington(10*u.deg, 20*u.deg, observer="earth",
...                                                        obstime="2020-03-04 00:00"),
...                          rotated_time="2020-03-06 12:00")
>>> rs_hgc
<RotatedSunHeliographicCarrington Coordinate (base=<HeliographicCarrington Frame (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>)>, duration=2.5 d, rotation_model=howard): (lon, lat) in deg
    (10., 20.)>

A RotatedSun* frame containing coordinate data can be supplied to SkyCoord as normal:

>>> SkyCoord(rs_hgc)
<SkyCoord (RotatedSunHeliographicCarrington: base=<HeliographicCarrington Frame (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>)>, duration=2.5 d, rotation_model=howard): (lon, lat) in deg
    (10., 20.)>

The above examples used the default differential-rotation model, but any of the models available through sunpy.physics.differential_rotation.diff_rot() are selectable. For example, instead of the default (“howard”), one can specify “allen” using the keyword argument rotation_model. Note the slight difference in the “real” longitude compared to the output above:

>>> allen = RotatedSunFrame(base=frames.HeliographicCarrington(10*u.deg, 20*u.deg, observer="earth",
...                                                               obstime="2020-03-04 00:00"),
...                            rotated_time="2020-03-06 12:00", rotation_model="allen")
>>> allen.transform_to(allen.base)
<HeliographicCarrington Coordinate (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, km)
    (45.22266666, 20., 695700.)>
Transforming coordinate arrays#

For another transformation example, we define a meridian with a Carrington longitude of 100 degrees, plus 1 day of differential rotation. Again, the coordinates are already differentially rotated in inertial space; the RotatedSun* frame allows one to represent the coordinates in a frame prior to the differential rotation:

>>> meridian = RotatedSunFrame([100]*11*u.deg, range(-75, 90, 15)*u.deg,
...                            base=frames.HeliographicCarrington(observer="earth", obstime="2001-01-01"),
...                            duration=1*u.day)
>>> meridian
<RotatedSunHeliographicCarrington Coordinate (base=<HeliographicCarrington Frame (obstime=2001-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>)>, duration=1.0 d, rotation_model=howard): (lon, lat) in deg
    [(100., -75.), (100., -60.), (100., -45.), (100., -30.), (100., -15.),
    (100.,   0.), (100.,  15.), (100.,  30.), (100.,  45.), (100.,  60.),
    (100.,  75.)]>

An easy way to “see” the differential rotation is to transform the coordinates to the base coordinate frame. Note that the points closer to the equator (latitude of 0 degrees) have evolved farther in longitude than the points at high latitudes:

>>> meridian.transform_to(meridian.base)
<HeliographicCarrington Coordinate (obstime=2001-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, km)
    [(110.755047, -75., 695700.), (111.706972, -60., 695700.),
    (112.809044, -45., 695700.), (113.682163, -30., 695700.),
    (114.17618 , -15., 695700.), (114.326328,   0., 695700.),
    (114.17618 ,  15., 695700.), (113.682163,  30., 695700.),
    (112.809044,  45., 695700.), (111.706972,  60., 695700.),
    (110.755047,  75., 695700.)]>

In the specific case of HeliographicCarrington, this frame rotates with the Sun, but in a non-differential manner. The Carrington longitude approximately follows the rotation of the Sun. One can transform to the coordinate frame of 1 day in the future to see the difference between Carrington rotation and differential rotation. Note that equator rotates slightly faster than the Carrington rotation rate (its longitude is now greater than 100 degrees), but most latitudes rotate slower than the Carrington rotation rate:

>>> meridian.transform_to(frames.HeliographicCarrington(observer="earth", obstime="2001-01-02"))
<HeliographicCarrington Coordinate (obstime=2001-01-02T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, km)
    [( 96.71777552, -75.1035280, 695509.61226612),
    ( 97.60193088, -60.0954217, 695194.47689542),
    ( 98.68350999, -45.0808511, 694918.44538999),
    ( 99.54760854, -30.0611014, 694697.75301952),
    (100.03737064, -15.0375281, 694544.31380180),
    (100.18622957, -0.01157236, 694467.21969767),
    (100.03737064,  15.0151761, 694471.58239044),
    ( 99.54760854,  30.0410725, 694557.27090716),
    ( 98.68350999,  45.0645144, 694719.82847332),
    ( 97.60193088,  60.0838908, 694951.31065278),
    ( 96.71777552,  75.0975847, 695238.51302901)]>

Be aware that transformations with a change in obstime will also contend with a translation of the center of the Sun. Note that the radius component above is no longer precisely on the surface of the Sun. For precise transformations of solar features, one should also use the context manager transform_with_sun_center() to account for the translational motion of the Sun. Using the context manager, the radius component stays as the solar radius as desired:

>>> from sunpy.coordinates import transform_with_sun_center

>>> with transform_with_sun_center():
...     print(meridian.transform_to(frames.HeliographicCarrington(observer="earth", obstime="2001-01-02")))
<HeliographicCarrington Coordinate (obstime=2001-01-02T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, km)
    [( 96.570646, -75., 695700.), ( 97.52257 , -60., 695700.),
    ( 98.624643, -45., 695700.), ( 99.497762, -30., 695700.),
    ( 99.991779, -15., 695700.), (100.141927,   0., 695700.),
    ( 99.991779,  15., 695700.), ( 99.497762,  30., 695700.),
    ( 98.624643,  45., 695700.), ( 97.52257 ,  60., 695700.),
    ( 96.570646,  75., 695700.)]>
Transforming multiple durations of rotation#

Another common use case for differential rotation is to track a solar feature over a sequence of time steps. Let’s track an active region that starts at Helioprojective coordinates (-123 arcsec, 456 arcsec), as seen from Earth, and we will look both backwards and forwards in time. When duration is an array, the base coordinate will be automatically upgraded to an array if it is a scalar. We specify a range of durations from -5 days to +5 days, stepping at 1-day increments:

>>> durations = range(-5, 6, 1)*u.day
>>> ar_start = frames.Helioprojective(-123*u.arcsec, 456*u.arcsec,
...                              obstime="2001-01-01", observer="earth")
>>> ar = RotatedSunFrame(base=ar_start, duration=durations)
>>> ar
<RotatedSunHelioprojective Coordinate (base=<Helioprojective Frame (obstime=2001-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>)>, duration=[-5. -4. -3. -2. -1.  0.  1.  2.  3.  4.  5.] d, rotation_model=howard): (Tx, Ty) in arcsec
    [(-123., 456.), (-123., 456.), (-123., 456.), (-123., 456.),
    (-123., 456.), (-123., 456.), (-123., 456.), (-123., 456.),
    (-123., 456.), (-123., 456.), (-123., 456.)]>

Let’s convert to the base coordinate frame to reveal the motion of the active region over time:

>>> ar.transform_to(ar.base)
<Helioprojective Coordinate (obstime=2001-01-01T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
    [(-865.549563, 418.102848, 0.982512),
    (-794.67361 , 429.259359, 0.981549),
    (-676.999492, 439.158483, 0.980695),
    (-519.354795, 447.212391, 0.980001),
    (-330.98304 , 452.940564, 0.979507),
    (-123.      , 456.      , 0.979244),
    (  92.27676 , 456.207078, 0.979226),
    ( 302.081349, 453.54936 , 0.979455),
    ( 493.984308, 448.186389, 0.979917),
    ( 656.653862, 440.439434, 0.980585),
    ( 780.541211, 430.770974, 0.981419)]>

Be aware that these coordinates are represented in the Helioprojective coordinates as seen from Earth at the base time. Since the Earth moves in its orbit around the Sun, one may be more interested in representing these coordinates as they would been seen by an Earth observer at each time step. Since the destination frame of the transformation will now have arrays for obstime and observer, one actually has to construct the initial coordinate with an array for obstime (and observer) due to a limitation in Astropy. Note that the active region moves slightly slower across the disk of the Sun because the Earth orbits in the same direction as the Sun rotates, thus reducing the apparent rotation of the Sun:

>>> ar_start_array = frames.Helioprojective([-123]*len(durations)*u.arcsec,
...                                    [456]*len(durations)*u.arcsec,
...                                    obstime=["2001-01-01"]*len(durations), observer="earth")
>>> ar_array = RotatedSunFrame(base=ar_start_array, duration=durations)
>>> earth_hpc = frames.Helioprojective(obstime=ar_array.rotated_time, observer="earth")
>>> ar_array.transform_to(earth_hpc)
<Helioprojective Coordinate (obstime=['2000-12-27 00:00:00.000' '2000-12-28 00:00:00.000'
'2000-12-29 00:00:00.000' '2000-12-30 00:00:00.000'
'2000-12-31 00:00:00.000' '2001-01-01 00:00:00.000'
'2001-01-02 00:00:00.000' '2001-01-03 00:00:00.000'
'2001-01-04 00:00:00.000' '2001-01-05 00:00:00.000'
'2001-01-06 00:00:00.000'], rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (Tx, Ty, distance) in (arcsec, arcsec, AU)
    [(-853.35712 , 420.401517, 0.982294),
    (-771.20926 , 429.298481, 0.981392),
    (-650.31062 , 437.85932 , 0.980601),
    (-496.634378, 445.519914, 0.97996 ),
    (-317.863549, 451.731964, 0.9795  ),
    (-123.      , 456.      , 0.979244),
    (  78.103714, 457.916782, 0.979203),
    ( 275.263157, 457.194475, 0.97938 ),
    ( 458.500759, 453.689226, 0.979764),
    ( 618.572111, 447.417202, 0.980336),
    ( 747.448484, 438.560811, 0.981067)]>
Transforming into RotatedSun frames#

So far, all of the examples show transformations with the RotatedSun* frame as the starting frame. The RotatedSun* frame can also be the destination frame, which can be more intuitive in some situations and even necessary in some others (due to API limitations). Let’s use a coordinate from earlier, which represents the coordinate in a “real” coordinate frame:

>>> coord = rs_hgc.transform_to(rs_hgc.base)
>>> coord
<HeliographicCarrington Coordinate (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, km)
    (45.13354448, 20., 695700.)>

If we create a RotatedSun* frame for a different base time, we can represent that same point using coordinates prior to differential rotation:

>>> rs_frame = RotatedSunFrame(base=frames.HeliographicCarrington(observer="earth",
...                                                          obstime=coord.obstime),
...                            rotated_time="2020-03-06 12:00")
>>> rs_frame
<RotatedSunHeliographicCarrington Frame (base=<HeliographicCarrington Frame (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>)>, duration=2.5 d, rotation_model=howard)>

>>> new_coord = coord.transform_to(rs_frame)
>>> new_coord
<RotatedSunHeliographicCarrington Coordinate (base=<HeliographicCarrington Frame (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>)>, duration=2.5 d, rotation_model=howard): (lon, lat, radius) in (deg, deg, km)
    (10., 20., 695700.)>

There coordinates are stored in the RotatedSun* frame, but it can be useful to “pop off” this extra layer and retain only the coordinate representation in the base coordinate frame. There is a convenience method called as_base() to do exactly that. Be aware the resulting coordinate does not point to the same location in inertial space, despite the superficial similarity. Essentially, the component values have been copied from one coordinate frame to a different coordinate frame, and thus this is not merely a transformation between coordinate frames:

>>> new_coord.as_base()
<HeliographicCarrington Coordinate (obstime=2020-03-04T00:00:00.000, rsun=695700.0 km, observer=<HeliographicStonyhurst Coordinate for 'earth'>): (lon, lat, radius) in (deg, deg, km)
    (10., 20., 695700.)>
Example uses of RotatedSunFrame#

Here are the examples in our gallery that use RotatedSunFrame:

Comparing differential-rotation models

Comparing differential-rotation models

Differentially rotating a coordinate

Differentially rotating a coordinate

Overlaying differentially rotated gridlines

Overlaying differentially rotated gridlines

Coordinates with velocity information#

Velocity information can be added to any coordinate [1]. When the coordinate is transformed to a different coordinate frame, the velocity vector will be transformed as appropriate. Be aware that the transformation framework does not take any velocity information into account when transforming the position vector.

Creating a SkyCoord with velocity#

Velocity information can be added as keyword arguments to SkyCoord. For sunpy’s frames, the names of the velocities components are the names of the position components prepended by “d_”, e.g.,:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord

>>> import sunpy.coordinates

>>> sc = SkyCoord(lon=10*u.deg, lat=20*u.deg, distance=1*u.AU,
...               d_lon=3*u.arcmin/u.week, d_lat=4*u.arcmin/u.d, d_distance=5*u.km/u.min,
...               frame='heliocentricinertial', obstime='2021-01-01')
>>> sc
<SkyCoord (HeliocentricInertial: obstime=2021-01-01T00:00:00.000): (lon, lat, distance) in (deg, deg, AU)
    (10., 20., 1.)
 (d_lon, d_lat, d_distance) in (arcsec / s, arcsec / s, km / s)
    (0.00029762, 0.00277778, 0.08333333)>

See Working with Velocities in Astropy Coordinates for ways to add velocity information to existing coordinates.

Querying velocity information#

sunpy has functions to query the positions of planets or other objects (e.g., get_body_heliographic_stonyhurst()). For any of these functions, if include_velocity=True is specified, the returned coordinate will include velocity information, e.g.,:

>>> from sunpy.coordinates import get_body_heliographic_stonyhurst

>>> get_body_heliographic_stonyhurst('mercury', '2021-01-01', include_velocity=True)
<HeliographicStonyhurst Coordinate (obstime=2021-01-01T00:00:00.000, rsun=695700.0 km): (lon, lat, radius) in (deg, deg, AU)
    (-156.46460438, -1.38836399, 0.43234904)
 (d_lon, d_lat, d_radius) in (arcsec / s, arcsec / s, km / s)
    (0.09138133, 0.00720229, -7.2513617)>
Transforming velocities#

The transformation of the velocity vector between two coordinate frames takes into account two effects:

  • The change in the direction of the velocity vector due to a change in the orientation of the axes between the two frames.

  • The “induced” velocity due to the time dependence of the frames themselves.

Orientation change#

To illustrate the orientation change, let’s start with the SkyCoord created at the beginning, which was defined in the HeliocentricInertial frame. We transform to Astropy’s HCRS frame, which is a different inertial [2] frame that is also centered at the Sun:

>>> sc_hcrs = sc.transform_to('hcrs')
>>> sc_hcrs
<SkyCoord (HCRS: obstime=2021-01-01T00:00:00.000): (ra, dec, distance) in (deg, deg, AU)
    (80.95428245, 44.31500877, 1.)
 (pm_ra_cosdec, pm_dec, radial_velocity) in (mas / yr, mas / yr, km / s)
    (-8828397.4990187, 87659732.13232313, 0.08333338)>

Even though the velocity vectors are oriented very differently in their respective spherical coordinates, their amplitudes are essentially the same:

>>> sc.velocity.norm()
<Quantity 2.02654081 km / s>
>>> sc_hcrs.velocity.norm()
<Quantity 2.02654083 km / s>
Induced velocity#

To illustrate “induced” velocity, consider the HeliographicStonyhurst frame, which is defined such that the Earth is always at zero degrees longitude. That is, this frame rotates around the Sun over time to “follow” the Earth. Accordingly, the longitude component of Earth’s velocity vector will be negligible in this frame:

>>> from sunpy.coordinates import get_earth

>>> earth = get_earth('2021-01-01', include_velocity=True)
>>> earth
<SkyCoord (HeliographicStonyhurst: obstime=2021-01-01T00:00:00.000, rsun=695700.0 km): (lon, lat, radius) in (deg, deg, AU)
    (0., -3.02983361, 0.98326486)
 (d_lon, d_lat, d_radius) in (arcsec / s, arcsec / s, km / s)
    (1.82278759e-09, -0.00487486, -0.01720926)>

Transforming this coordinate to the HeliocentricInertial frame, which does not rotate over time, confirms that the Earth is moving in inertial space at the expected ~1 degree/day in heliographic longitude:

>>> earth.heliocentricinertial
<SkyCoord (HeliocentricInertial: obstime=2021-01-01T00:00:00.000): (lon, lat, distance) in (deg, deg, AU)
    (24.55623543, -3.02983361, 0.98326486)
 (d_lon, d_lat, d_distance) in (arcsec / s, arcsec / s, km / s)
    (0.0422321, -0.00487486, -0.01720925)>
>>> earth.heliocentricinertial.d_lon.to('deg/d')
<Quantity 1.01357048 deg / d>
Transforming over time#

As the transformation framework is currently implemented, transforming between frames with different values of obstime takes into account any time dependency for the definitions of the frames, but does not incorporate any notion of the coordinate itself moving in inertial space. This behavior does not change even if there is velocity information attached to the coordinate. For example, if we take the same coordinate created earlier for Earth, and transform it to one day later:

>>> from sunpy.coordinates import HeliographicStonyhurst

>>> earth.transform_to(HeliographicStonyhurst(obstime=earth.obstime + 1*u.day))
<SkyCoord (HeliographicStonyhurst: obstime=2021-01-02T00:00:00.000, rsun=695700.0 km): (lon, lat, radius) in (deg, deg, AU)
    (-1.01416251, -3.02979409, 0.98326928)
 (d_lon, d_lat, d_radius) in (arcsec / s, arcsec / s, km / s)
    (-1.19375277e-05, -0.00487485, -0.01743006)>

Note that the location of the Earth in the new frame is ~ -1 degree in longitude, as opposed to zero degrees. That is, this coordinate represents the location of Earth on 2021 January 1 using axes that are defined using the location of Earth on 2021 January 2.

Footnotes#

Coordinates and WCS#

The sunpy.coordinates sub-package provides a mapping between FITS-WCS CTYPE convention and the coordinate frames as defined in sunpy.coordinates. This is used via the astropy.wcs.utils.wcs_to_celestial_frame function, with which the sunpy frames are registered upon being imported. This list is used by for example, wcsaxes to convert from astropy.wcs.WCS objects to coordinate frames.

The sunpy.map.GenericMap class creates astropy.wcs.WCS objects as amap.wcs, however, it adds some extra attributes to the WCS object to be able to fully specify the coordinate frame. It adds heliographic_observer and rsun.

If you want to obtain a un-realized coordinate frame corresponding to a GenericMap object you can do the following:

>>> import sunpy.map
>>> from sunpy.data.sample import AIA_171_IMAGE  

>>> amap = sunpy.map.Map(AIA_171_IMAGE)  
>>> amap.observer_coordinate  
<SkyCoord (HeliographicStonyhurst: obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>

which is equivalent to:

>>> from astropy.wcs.utils import wcs_to_celestial_frame 

>>> wcs_to_celestial_frame(amap.wcs)  
<Helioprojective Frame (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07T06:33:02.770, rsun=696000.0 km): (lon, lat, radius) in (deg, deg, m)
    (-0.00406308, 0.04787238, 1.51846026e+11)>)>

The role of rsun in coordinate transformations#

In the case of HeliographicCarrington, one can specify observer='self' to indicate that the coordinate itself should be used as the observer for defining the coordinate frame.

It is possible to convert from a Helioprojective frame with one observer location to another Helioprojective frame with a different observer location. The transformation requires the coordinate to be 3D, so if the initial coordinate is only 2D, the default assumption maps that 2D coordinate to the surface of the Sun, as defined by the rsun frame attribute. The conversion can be performed as follows:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord

>>> from sunpy.coordinates import frames
>>> import sunpy.coordinates

>>> hpc1 = SkyCoord(0*u.arcsec, 0*u.arcsec, observer="earth", obstime="2017-07-26", frame=frames.Helioprojective)
>>> hpc_out = sunpy.coordinates.Helioprojective(observer="venus", obstime="2017-07-26")
>>> hpc2 = hpc1.transform_to(hpc_out)

For example if you have two maps, named aia and stereo:

>>> hpc1 = SkyCoord(0*u.arcsec, 0*u.arcsec, frame=aia.coordinate_frame)  
>>> hpc2 = hpc1.transform_to(stereo.coordinate_frame)  

Design of the Coordinates Sub-Package#

This sub-package works by defining a collection of “Frames” (sunpy.coordinates.frames), which exists on a transformation graph, where the transformations between the coordinate frames are then defined and registered with the transformation graph (sunpy.coordinates). It is also possible to transform SunPy frames to Astropy frames.

Positions within these “Frames” are stored as a “Representation” of a coordinate, a representation being a description of a point in a Cartesian, spherical or cylindrical system (see Using and Designing Coordinate Representations). A frame that contains a representation of one or many points is said to have been ‘realized’.

For a more in depth look at the design and concepts of the Astropy coordinates system see Overview of astropy.coordinates Concepts

Frames and SkyCoord#

The SkyCoord class is a high level wrapper around the astropy.coordinates sub-package. It provides an easier way to create and transform coordinates, by using string representations for frames rather than the classes themselves and some other usability improvements. For more information see the SkyCoord documentation.

The main advantage provided by SkyCoord is the support it provides for caching Frame attributes. Frame attributes are extra data specified with a frame, some examples in sunpy.coordinates are obstime or observer for observer location. Only the frames where this data is meaningful have these attributes, i.e., only the Helioprojective frames have observer. However, when you transform into another frame and then back to a projective frame using SkyCoord it will remember the attributes previously provided, and repopulate the final frame with them. If you were to do transformations using the Frames alone this would not happen.

Add a new rotation method to Map#

It is possible to select from a number of rotation methods when using sunpy.image.transform.affine_transform() and sunpy.map.GenericMap.rotate(). You can add a custom rotation method using the decorator sunpy.image.transform.add_rotation_function():

from sunpy.image.transform import add_rotation_function

@add_rotation_function("my_rotate", allowed_orders={0, 1, 3},
                       handles_clipping=False, handles_image_nans=False, handles_nan_missing=False)
def _rotation_mine(image, matrix, shift, order, missing, clip):
    # Rotation code goes here
    return rotated_image

See the docstring for add_rotation_function() for a detailed explanation of each of the decorator parameters and each of the required input parameters to the rotation function

Then you can do:

>>> from sunpy.map import Map
>>> from sunpy.data import sample

>>> hmi_map = sunpy.map.Map(sample.HMI_LOS_IMAGE)  
>>> rot_map = hmi_map.rotate(order=3, recenter=True, method="my_rotate")  

The available rotation methods are all implemented using the add_rotation_function() decorator, so you can look in sunpy/image/transform.py for examples of how to use this decorator.

Adding new data sources to Fido#

sunpy’s data search and retrieval tool (Fido) is designed to be extensible, so that new sources of data or metadata can be supported, either inside or outside the sunpy core package.

There are two ways of defining a new client, depending on the complexity of the web service. A “scraper” client inherits from GenericClient which provides helper methods for downloading from a list of URLs. If the service you want to add has easily accessible HTTP or FTP URLs that have a well defined folder and filename structure, this is probably the best approach. If the service you want to add requires making requests to an API with parameters for the search and getting a list of results in return, then you probably want to write a “full” client.

Before writing a new client, ensure you are familiar with how searches are specified by the sunpy.net.attr system, including combining them with logical operations. When choosing a name for your new client it should have the form <name>Client as sunpy will split the name of the class to extract the name of your client. The main place this is done is when constructing a UnifiedResponse object, where the name part can be used to index the response object.

Writing a new “scraper” client#

A “scraper” Fido client (also sometimes referred to as a “data retriever” client) is a Fido client which uses the URL Scraper to find files on remote servers. If the data provider you want to integrate does not provide a tree of files with predictable URLs then a “full” client is more likely to provide the functionality you need.

A new “scraper” client inherits from GenericClient and requires a minimum of these three components:

  • A class method register_values(); this registers the “attrs” that are supported by the client. It returns a dictionary where keys are the supported attrs and values are lists of tuples. Each tuple contains the “attr” value and its description.

  • A class attribute baseurl; this is a regular expression which is used to match the URLs supported by the client.

  • A class attribute pattern; this is a template used to extract the metadata from URLs matched by baseurl. The extraction uses the parse format.

For a simple example of a scraper client, we can look at the implementation of sunpy.net.dataretriever.sources.eve.EVEClient in sunpy. A version without documentation strings is reproduced below:

class EVEClient(GenericClient):
    baseurl = (r'http://lasp.colorado.edu/eve/data_access/evewebdata/quicklook/'
                r'L0CS/SpWx/%Y/%Y%m%d_EVE_L0CS_DIODES_1m.txt')
    pattern = '{}/SpWx/{:4d}/{year:4d}{month:2d}{day:2d}_EVE_L{Level:1d}{}'

    @classmethod
    def register_values(cls):
        from sunpy.net import attrs
        adict = {attrs.Instrument: [('EVE', 'Extreme ultraviolet Variability Experiment, which is part of the NASA Solar Dynamics Observatory mission.')],
                attrs.Physobs: [('irradiance', 'the flux of radiant energy per unit area.')],
                attrs.Source: [('SDO', 'The Solar Dynamics Observatory.')],
                attrs.Provider: [('LASP', 'The Laboratory for Atmospheric and Space Physics.')],
                attrs.Level: [('0', 'EVE: The specific EVE client can only return Level 0C data. Any other number will use the VSO Client.')]}
        return adict

This client scrapes all the URLs available under the base url http://lasp.colorado.edu/eve/data_access/evewebdata/quicklook/L0CS/SpWx/. Scraper is primarily focused on URL parsing based on time ranges, so the rest of the baseurl pattern specifies where in the pattern the time information is located, using strptime notation. The pattern attribute is used to populate the results table from the URLs matched with the baseurl. It includes some of the time definitions, as well as names of attrs (in this case “Level”). The supported time keys are: ‘year’, ‘month’, ‘day’, ‘hour’, ‘minute’, ‘second’, ‘millisecond’.

The attrs returned in the register_values() method are used to match your client to a search, as well as adding their values to the attr. This means that after this client has been imported, running print(a.Provider) will show that the EVEClient has registered a provider value of LASP. In addition to this, a sanitized, lower cased version of the value will be available for tab completing, e.g. a.Provider.lasp or a.Level.zero.

More Complex Clients#

Sometimes the attr values may not exist identically in the required URLs, and therefore can not be simply extracted with pattern. Say, for example, the Wavelength of a file is expressed in the URL as a passband by name; in this case conversion of the Quantity object to the pass band name would be needed. This is done addressed with the two following methods:

  • pre_search_hook() which will convert the passed attrs to their representation in the URL.

  • post_search_hook() which converts the retrieved metadata from a URL to the form in which they are desired to be represented in the response table.

A good example of the use of these two methods is the sunpy.net.dataretriever.sources.norh.NoRHClient in sunpy.

It may also be possible that the baseurl property needs to be customized based on attrs other than Time. Since Scraper doesn’t currently support generating directories that have non-time variables, the search() needs to be customized. The search method should in this case, generate a baseurl dependent on the values of these attrs, and then call super().search or Scraper for each baseurl generated. For an example of a complex modification of the search() method see the implementation of SUVIClient.search.

Customizing the Downloader#

There is no method for a client creator to override the parfive.Downloader that is used to fetch the files. This is because all downloads made by a single call to Fido.fetch share one instance of parfive.Downloader. However, it is possible to pass keywords parfive.Downloader.enqueue_file(), which is important if there is a need to customize the requests to a remote server, such as setting custom HTTP headers. This is done by setting the enqueue_file_kwargs attribute of the client class. One example from the sunpy.net.dataretriever.sources.noaa.SRSClient is:

class SRSClient(GenericClient):
    ...
    # Server does not support the normal aioftp passive command.
    enqueue_file_kwargs = {"passive_commands": ["pasv"]}
    ...

These keywords are passed to each call to parfive.Downloader.enqueue_file(), so they will affect all files that are added for download by your client.

Examples#

Suppose any file of a data archive can be described by this URL https://some-domain.com/%Y/%m/%d/satname_{SatelliteNumber}_{Level}_%y%m%d%H%M%S_{any-2-digit-number}.fits:

baseurl becomes r'https://some-domain.com/%Y/%m/%d/satname_(\d){2}_(\d){1}_(\d){12}_(\d){2}\.fits'.

Note all variables in the filename are converted to regex that will match any possible value for it. A character enclosed within () followed by a number enclosed within {} is used to match the specified number of occurrences of that special sequence. For example, %y%m%d%H%M%S is a twelve digit variable (with 2 digits for each item) and thus represented by r'(\d){12}'. Note that \ is used to escape the special character ..

pattern becomes '{}/{year:4d}/{month:2d}{day:2d}/satname_{SatelliteNumber:2d}_{Level:1d}_{:6d}{hour:2d}{minute:2d}{second:2d}_{:2d}.fits'. Note the sole purpose of pattern is to extract the information from matched URL, using parse. So the desired key names for returned dictionary should be written in the pattern within {}, and they should match with the attr.__name__.

register_values() can be written as:

@classmethod
def register_values(cls):

    from sunpy.net import attrs
    adict = {
    attrs.Instrument: [("SatName", "The description of Instrument")],
    attrs.Physobs: [('some_physobs', 'Phsyobs description')],
    attrs.Source: [('some_source', 'Source description')],
    attrs.Provider: [('some_provider', 'Provider description')],
    attrs.Level: [("1", "Level 1 data"), ("2", "Level 2 data")],
    attrs.SatelliteNumber: [("16", "Describe it"), ("17", "Describe it")]
    }

    return adict

Writing a “full” client#

In this section we will describe how to build a “full” Fido client. You should write a new “full” client if the data you are accessing can not be accessed via a URL template, for instance if you hit a web API with a query to return results for a search.

A new Fido client contains three major components:

  • A subclass of BaseClient which implements search, fetch, and _can_handle_query.

  • Zero or more new Attr classes to specify search parameters unique to your data source.

  • An instance of AttrWalker which can be used to walk the tree of Attr instances and convert them into a form useful to your client’s search method.

Search Attrs#

As described in attr the attr system allows the construction of complex queries by the user. To make these complex queries easily processable by the clients the AttrWalker converts these into a set of queries which can be processed separately. It does this by converting the input query to a set of queries which are ORed, but are complete queries. This means the list of queries is an OR of ANDs (technically called disjunctive normal form).

Each query in the list of ORs contains all the information about that query so for example if the user provided a query like

a.Time("2020/02/02", "2020/02/03") & (a.Instrument("AIA") | a.Instrument("HMI"))

it would be passed to the client as

(a.Time("2020/02/02", "2020/02/03") & a.Instrument("HMI")) | (a.Time("2020/02/02", "2020/02/03") & a.Instrument("AIA"))

So you can process each element of the OR in turn without having to consult any other part of the query.

If the query the user provided contains an OR statement you get passed an instance of AttrOr and each sub-element of that AttrOr will be AttrAnd (or a single other attr class). If the user query doesn’t contain an OR you get a single Attr instance or an AttrAnd.

For example you could get any of the following queries (using & for AND and | for OR):

  • (a.Instrument("AIA") & a.Time("2020/02/02", "2020/02/03")) | (a.Instrument("HMI") & a.Time("2020/02/02", "2020/02/03"))

  • a.Time("2020/02/02", "2020/02/03")

  • a.Instrument("AIA") & a.Time("2020/02/02", "2020/02/03")

  • (a.Time(..) & a.Instrument("AIA") & a.Wavelength(30*u.nm, 31*u.nm)) | (a.Time(..) & a.Instrument("AIA") & a.Wavelength(30*u.nm, 31*u.nm))

but you would not be passed queries which look like the following examples, even if that’s how the user specified them:

  • a.Time("2020/02/02", "2020/02/03") & (a.Instrument("AIA") | a.Instrument("HMI"))

  • a.Time(..) & (a.Instrument("AIA") | a.Instrument("AIA")) & a.Wavelength(30*u.nm, 31*u.nm))

The Attr Walker#

Given the potential complexity of these combined attrs, converting them into other forms, such as query parameters or JSON etc involves walking the tree and converting each attr to the expected format in a given way. This parsing and conversion of the query tree is deliberately not done using methods or attributes of the attrs themselves. The attrs should be independent of any client in their implementation, so they can be shared between the different Fido clients.

A class is provided to facilitate this conversion, AttrWalker. The AttrWalker class consists of three main components:

  • Creators: The create method is one of two generic functions for which a different function is called for each Attr type. The intended use for creators is to return a new object dependent on different attrs. It is commonly used to dispatch on AttrAnd and AttrOr.

  • Appliers: The apply method is the same as create in that it is a generic function. The only difference between it and create is its intended use. Appliers are generally used to modify an object returned by a creator with the values or information contained in other Attrs.

  • Converters: Adding a converter to the walker adds the function to both the creator and the applier. For the VSO client this is used to convert each supported attr into a ValueAttr which is then later processed by the appliers and creators. This pattern can be useful if you would otherwise have to repeat a lot of logic in each of the applier functions for each type of Attr you support.

An Example of AttrWalker#

In this example we will write a parser for some simple queries which uses AttrWalker to convert the query to a dict of URL query parameters for a HTTP GET request. Let’s imagine we have a web service which you can do a HTTP GET request to https://sfsi.sunpy.org/search for some imaginary data from an instrument called SFSI (Sunpy Fake Solar Instrument). This GET request takes three query parameters startTime, endTime and level, so a request might look something like: https://sfsi.sunpy.org/search?startTime=2020-01-02T00:00:00&endTime=2020-01-02T00:00:00&level=1. Which would search for level one data between 2020-01-01 and 2020-01-02.

As attrs has Time and Level we do not need to define any of our own attrs for this client. We do however want to write our own walker to convert them to the form out client’s search() method wants to send them to the server.

The first step is to setup the walker and define a creator method which will return a list of dicts, one for each independent search.

import sunpy.net.attrs as a
from sunpy.net.attr import AttrWalker, AttrAnd, AttrOr, DataAttr

walker = AttrWalker()

@walker.add_creator(AttrOr)
def create_or(wlk, tree):
    results = []
    for sub in tree.attrs:
        results.append(wlk.create(sub))

    return results

@walker.add_creator(AttrAnd, DataAttr)
def create_and(wlk, tree):
    result = dict()
    wlk.apply(tree, result)
    return [result]

The call wlk.apply(...) inside the creator will walk any nested attrs and add their values to the dictionary as defined by the applier registered to each attr type. If we want our client to support searching by a.Time and a.Level as in the URL example above, we would need to register an applier for each of these attrs.

@walker.add_applier(a.Time)
def _(wlk, attr, params):
    return params.update({'startTime': attr.start.isot,
                          'endTime': attr.end.isot})

@walker.add_applier(a.Level)
def _(wlk, attr, params):
    return params.update({'level': attr.value})

This combination of creators and appliers would allow support of any combination of queries consisting of a.Time and a.Level. Obviously, most clients would want to support more attrs than these two, and this could be done by adding more applier functions.

Adding “Attrs” to Registry#

Registering of “attrs” ensures discoverability of search attributes supported by the corresponding sunpy Client. For adding them to the Registry, we need to define a class method register_values() that returns a dictionary of registered values. This dictionary should have Attr classes as keys and a list of tuples corresponding to that key representing the possible values the key “attr” can take. Each tuple comprises of two elements. The first one is a value and the second element contains a brief description of that value. An example of writing register_values() for GenericClient is provided above. Please note that it can be defined in a similar way for full clients too.

An Example of register_values()#
@classmethod
def register_values(cls):

    from sunpy.net import attrs
    adict = {
    attrs.Instrument: [("LASCO", "Large Angle and Spectrometric Coronagraph")],
    attrs.Source: [('SOHO', 'Solar and Heliospheric Observatory')],
    attrs.Provider: [('SDAC', 'Solar Data Analysis Center')],
    attrs.Detector: [('C1', 'Coronograph 1'),
                     ('C2', 'Coronograph 2'),
                     ('C3', 'Coronograph 3')]
    }

    return adict
Registering custom attrs in the attrs namespace#

When you have custom attrs defined in a separate attrs module, you can add them to the namespace using the _attrs_module() class method. The method returns a tuple of length 2, where the first element is the target module name under which you want to add the custom attrs to the main attrs namespace. The second is the import path to the source module where the custom attrs are defined. Note that the source module here need not be an internal sunpy module, it could very well be external. An example for this can be seen as implemented in the JSOC client:

@classmethod
def _attrs_module(cls):
    return 'jsoc', 'sunpy.net.jsoc.attrs'

This adds all attrs that exist within sunpy.net.jsoc.attrs, such as Keyword, to attrs.jsoc. These can now be accessed via an import of the main attrs module, e.g., at a.jsoc.Keyword.

Writing a Search Method#

The search() method has the job of taking a set of user queries and returning an instance of QueryResponseTable containing the results.

The general flow of a search() method is:

  • Call your instance of an AttrWalker to convert the input into a form expected by your API.

  • Make as many requests to your API as needed to fulfill the query. Generally one per element of the outer sunpy.net.attrs.AttrOr.

  • Process the response from your API into an instance of QueryResponseTable.

To process the query with the AttrWalker, call the AttrWalker.create() method:

def search(self, query):
    queries = walker.create(query)

Assuming the walker is the one we defined above, queries would be a list of dicts with the attrs processed into query parameters for the API URL.

Note

If you want your search method to be able to be called independently of Fido, then you should accept a variable number of positional arguments (*args) and they should have the AND operator applied to them. This looks like:

def search(self, *args):
    query = attr.and_(args)
    queries = walker.create(query)

Once the walker has processed the query into a form designed to be passed to your API, your search() method then needs to iterate over these parameters, make the requests, and process the results into a table.

In the following example we pretend our client has a method _make_search(query_parameters) which takes the query parameters and makes a request to our API. We also pretend that the response is a json object in the form of a Python dictionary, which we want to put into the table.

def search(self, query):
    queries = walker.create(query)

    results = []
    for query_parameters in queries:
        results.append(self._make_search(query_parameters))

    return QueryResponseTable(results, client=self)

In reality, you probably want to post-process the results from your API before you put them in the table, they should be human readable first, with spaces and capitalization as appropriate.

Supporting file size estimates#

The base client has a method for automatically estimating the total size of files in a given query: total_size(). To enable to support for this, make sure the table returned by search has a column that contains filesizes as astropy quantities convertible to u.byte, and set the size_column class attribute to the name of this column.

The _can_handle_query method#

The next required method is _can_handle_query, this method tells Fido if your client might be able to return results for a given query. If this method returns True, your clients search() method will be called for that query. This method gets passed each query (in its independent form), and must either return True or False.

A simple example, which just checks the type of attrs and not their values would be

@classmethod
def _can_handle_query(cls, *query):
    query_attrs = set(type(x) for x in query)
    supported_attrs = {a.Time, a.Level}
    return supported_attrs.issuperset(query_attrs)

Note, that this method is a class method, it gets called without instantiating your client to speed up the dispatching.

Writing a Fetch Method#

The fetch() method of a Fido client is responsible for converting a set of search results (possibly sliced by the user) into a set of URLs to be downloaded. Due to the history of clients and how they were implemented in sunpy, some existing clients support use outside of the Fido wrapper, this makes them appear more complex. In this example we are going to write a fetch() method which is designed only to be called from Fido.

The parameters for such a method should be:

def fetch(self, query_results, *, path, downloader, **kwargs):
...

The parameters here are:

  • query_results which is an instance of QueryResponseTable or QueryResponseRow, these are the results the user wants to download.

  • path= This is the path that the user wants the file to be downloaded to, this can be a template string (i.e. expects to have .format() called on it).

  • downloader= This is a parfive.Downloader object which should be mutated by the fetch() method.

  • **kwargs It is very important that fetch() methods accept extra keyword arguments that they don’t use, as the user might be passing them to other clients via Fido.

Processing the query_results Argument#

The query_results argument can be of two types QueryResponseTable or QueryResponseRow, as the user can slice the results table down to a single row and then pass that to Fido.fetch(). If you do not wish to handle a single row any differently to a table, you can place the convert_row_to_table decorator on your fetch() method which will convert the argument to a length one table when it is a single row object.

The primary function of the fetch() method is for you to convert this results object into a set of URLs for Fido to download. This logic will be specific to your client.

Formatting the path= Argument#

The path argument may contain format sections which are processed column names from the response table. In addition to these it may contain the {file} format segment which is a placeholder for the filename. Each row of the results table has a response_block_map property which is a dictionary of valid format keys to values for that row.

In addition to the response_block_map your fetch method also needs to be able to generate a filename for the file. The simplest (but unlikely) scenario is that you know the filename for each file you are going to download before you do so, in this situation you would be able to generate the full filepath for each row of the response as follows

for row in query_results:
    filename = self._calculate_filename(row)
    filepath = path.format(file=filename, **row.response_block_map)

In the situation where you wish to be told the filename by the web server you are downloading the file from, it is a little more complex, you need to pass a callback function to parfive.Downloader.enqueue_file() which will calculate the full filename in the context of the download, where the headers can be inspected for the filename the web server provides.

The filename callback passed to parfive.Downloader.enqueue_file() accepts two arguments resp and url. resp is an aiohttp.ClientResponse object which is returned when parfive requests the URL. This response object allows us to inspect the headers of the response before the data is downloaded. url is the URL that was requested to generate the resp response.

To combine the formatting of the row with the extraction of the filename from the headers it is common to use functools.partial to generate many functions with different fixed parameters. In the following example we will define a function which takes 4 arguments which we will use to generate the filename for the row. This function will be called by parfive with the resp and url arguments.

def make_filename(path, row, resp, url):
    # Define a fallback filename based on the information in the search results
    name = f"row['ID'].fits"

    if resp:
        cdheader = resp.headers.get("Content-Disposition", None)
        if cdheader:
        _, params = sunpy.util.net.parse_header(cdheader)
        name = params.get('filename', "")

    return path.format(file=name, **row.response_block_map)

To reduce this function down to the two arguments expected we pre-specify the first two of these with partial before passing the function to enqueue_file inside the fetch() method. Our simple example above now becomes:

for row in query_results:
    filepath = partial(make_filename, path, row)

Where the path variable is a pathlib.Path object provided as the path argument to fetch().

Adding URLs to be Downloaded#

For each file you wish for Fido to download (normally one per row of the query_results) you need to call the parfive.Downloader.enqueue_file() of the downloader argument. Combining this with the simple example above it may look something like

for row in query_results:
    filename = self._calculate_filename(row)
    filepath = path.format(file=filename, **row.response_block_map)

    url = self._calculate_url(row)
    downloader.enqueue_file(url, filename=filepath)

If your filepath is a callback function, pass this to the filename= argument.

Your fetch method does not need to return anything, as long as enqueue_file is called for every file you want Fido to download.

Putting it all together#

An example client class may look something like

import sunpy.util.net

import sunpy.net.atrrs as a
from sunpy.net.attr import AttrWalker, AttrAnd, AttrOr, DataAttr
from sunpy.base_client import QueryResponseTable

walker = AttrWalker()

@walker.add_creator(AttrOr)
def create_or(wlk, tree):
    results = []
    for sub in tree.attrs:
        results.append(wlk.create(sub))

    return results


@walker.add_creator(AttrAnd, DataAttr)
def create_and(wlk, tree):
    result = dict()
    wlk.apply(tree, result)
    return [result]


@walker.add_applier(a.Time)
def _(wlk, attr, params):
    return params.update({'startTime': attr.start.isot,
                            'endTime': attr.end.isot})


@walker.add_applier(a.Level)
def _(wlk, attr, params):
    return params.update({'level': attr.value})


class ExampleClient(BaseClient):
    size_column = 'Filesize'

    def search(self, query):
        queries = walker.create(query)

        results = []
        for query_parameters in queries:
            results.append(self._make_search(query_parameters))

        return QueryResponseTable(results, client=self)

    def _make_filename(path, row, resp, url):
        # Define a fallback filename based on the information in the search results
        name = f"row['ID'].fits"

        if resp:
            cdheader = resp.headers.get("Content-Disposition", None)
            if cdheader:
            _, params = sunpy.util.net.parse_header(cdheader)
            name = params.get('filename', "")

        return path.format(file=name, **row.response_block_map)

    @convert_row_to_table
    def fetch(self, query_results, *, path, downloader, **kwargs):
        for row in query_results:
            filepath = partial(self._make_filename, path, row)

            url = f"https://sfsi.sunpy.org/download/{row['ID']}"
            downloader.enqueue_file(url, filename=filepath)

    @classmethod
    def _can_handle_query(cls, *query):
        query_attrs = set(type(x) for x in query)
        supported_attrs = {a.Time, a.Level}
        return supported_attrs.issuperset(query_attrs)

How “HISTORY” and “COMMENT” FITS Keys are Handled in sunpy#

In FITS files, there are often multiple entries for both the “HISTORY” and “COMMENT” keys. For example, when applying a prep routine to an image, a “HISTORY” entry may be added to the FITS header for every step in the prep pipeline. Because the metadata associated with each GenericMap acts like a dictionary, where each key must be unique, these repeated “HISTORY” and “COMMENT” keys cannot be represented as separate entries in MetaDict. Thus, when a FITS file with multiple “HISTORY” keys is read into a Map object, all the values corresponding to “HISTORY” are joined together with newline characters \n and stored as a single “history” entry in MetaDict. When writing the resulting Map to a FITS file, “history” is split along the \n characters and each entry is written to a separate “HISTORY” key in the resulting FITS header. The same is true for “COMMENT” keys.

See this pull request for additional details.

Advanced Installation#

Warning

This page has advanced instructions for installing sunpy. If you are new to Python or sunpy, please read the installation instructions first.

The SunPy Project maintains a range of affiliated packages that leverage the wider ecosystem of scientific Python packages for solar physics. This page focuses on how to install the sunpy core package, but these instructions should apply to most of the affiliated packages as well.

conda#

Full instructions for installing using conda are in Installation. This is the recommended way to install sunpy, because it creates a fresh Python environment that is independent from any other Python install or packages on your system, and allows the installation of non-python packages. The SunPy Project publishes many of it’s packages to the conda-forge channel. If you have an existing conda install, without the conda-forge channel added you can configure the conda-forge channel by following the instructions in the conda-forge documentation.

Updating a conda package#

You can update to the latest version of any package by running:

$ conda update <package_name>

pip#

This is for installing sunpy within a Python environment, where pip has been used to install all previous packages. You will want to make sure you are using a Python virtual environment.

Once the environment active, to acquire a full sunpy installation:

$ pip install "sunpy[all]"

Warning

If you get a PermissionError this means that you do not have the required administrative access to install new packages to your Python installation. Do not install sunpy or other Python packages using sudo. This error implies you have an incorrectly configured virtual environment or it is not activated.

We strive to provide binary wheels for all of our packages. If you are using a Python version or operating system that is missing a binary wheel, pip will try to compile the package from source and this is likely to fail without a C compiler (e.g., gcc or clang). Getting the compiler either from your system package manager or XCode (if you are using macOS) should address this.

If you have a reason to want a more minimal installation, you can install sunpy with no optional dependencies, however this means a lot of submodules will not import:

$ pip install "sunpy"

It is possible to select which “extra” dependencies you want to install, if you know you only need certain submodules:

$ pip install "sunpy[map,timeseries]"

The available options are: [asdf], [dask], [image], [jpeg2000], [map], [net], [timeseries], [visualization].

Updating a pip package#

You can update to the latest version of any package by running:

$ pip install --upgrade <package_name>

Logging system#

Overview#

The sunpy logging system is a subclass of AstropyLogger. Its purpose is to provide users the ability to decide which log and warning messages to show, to capture them, and to send them to a file.

All messages provided by sunpy make use of this 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: Indicates a more serious issue 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.

Configuring the logging system#

The default configuration for the logger is determined by the default sunpy configuration file. To make permanent changes to the logger configuration see the [logger] section of the sunpy configuration file (sunpyrc).

If you’d like to control the logger configuration for your current session first import the logger:

>>> from sunpy import log

or by:

>>> import logging
>>> log = logging.getLogger('sunpy')

The threshold level for messages can be set with:

>>> log.setLevel('DEBUG') 

This will display DEBUG and all messages with that level and above. If you’d like to see the fewest relevant messages you’d set the logging level to WARNING or above.

For other options such as whether to log to a file or what level of messages the log file should contain, see the the Sunpy configuration file (sunpyrc).

Context managers#

If you’d like to capture messages as they are generated you can do that with a context manager:

>>> with log.log_to_list() as log_list:  
...    # your code here  

Once your code is executed, log_list will be a Python list containing all of the sunpy messages during execution. This does not divert the messages from going to a file or to the screen. It is also possible to send the messages to a custom file with:

>>> with log.log_to_file('myfile.log'):  
...     # your code here  

which will save the messages to a local file called myfile.log.

Writing a new instrument Map class#

All instrument Map classes are subclasses of the generic GenericMap subclass. GenericMap expects to be provided the data array and a header dictionary and will parse the header metadata to the best of its ability based on common standards. The instrument subclass implements the instrument-specific code to parse the metadata, apply any necessary procedures on the data array, as well as defining other things such what color tables to use.

In practice, the instrument subclass is not directly accessed by users. The Map factory is the primary interface for creating Map objects. Any subclass of GenericMap which defines a method named is_datasource_for() will automatically be registered with the Map factory. The is_datasource_for() method is used by the Map factory to check if a file should use a particular instrument Map class. This function can run any test it needs to determine this. For example, it might check the value of the “INSTRUMENT” key in the metadata dictionary. The following example shows how this works and includes a sample doc string that is aligned with the documentation guidelines:

import sunpy.map

class NextGenerationTelescopeMap(sunpy.map.GenericMap):
  """
  NextGenerationTelescope Map.

  The Next Generation Telescope is a optical telescope on board the new space mission.
  It operates in low Earth orbit with an altitude of 600 km and an inclination of 28.5 degrees.
  It is designed to observe the mechanisms that are responsible for triggering the impulsive release of magnetic energy in the solar corona.
  It observes in the following 3 different passbands, in visible light, wavelength A, wavelength B, wavelength C.

  The focal plane consists of a detector with 4k x 4k pixels.
  The plate scale is 0.1 arcsec per pixel.
  The field of view is the whole Sun (1000 x 1000 arcsec).
  It makes images in each passband every 1 second except for when it is in eclipse which occurs every approximately 80 minutes.

  It began operating on 2100 November 1.

  Notes
  -----
  Due to failure of the filter wheel, 2 of the different passbands are no longer functional.

  References
  ----------
  * Mission Paper
  * Instrument Paper(s)
  * Data Archive
  * Mission documents
  """

    def __init__(self, data, header, **kwargs):

        # Will process the header according to FITS common standards
        super().__init__(data, header, **kwargs)

        # Any NextGenerationTelescope Instrument-specific manipulation.
        # Any metadata changes should be done by overloading
        # the corresponding attributes/methods.

    # Used by the Map factory to determine if this subclass should be used
    @classmethod
    def is_datasource_for(cls, data, header, **kwargs):
        """
        Determines if data, header corresponds to a NextGenerationTelescope image
        """
        # Returns True only if this is data and header from NextGenerationTelescope
        return header.get('instrume', '').startswith('NextGenerationTelescope')

A Detailed Look at the TimeSeries Metadata#

TimeSeries store metadata in a TimeSeriesMetaData object, this object is designed to be able to store multiple basic MetaDict (case-insensitive ordered dictionary) objects and able to identify the relevant metadata for a given cell in the data. This enables a single TimeSeries to be created by combining/concatenating multiple TimeSeries source files together into one and to keep a reliable track of all the metadata relevant to each cell, column or row. The metadata can be accessed by:

>>> import sunpy.timeseries
>>> import sunpy.data.sample  

>>> my_timeseries = sunpy.timeseries.TimeSeries(sunpy.data.sample.GOES_XRS_TIMESERIES)  
>>> meta = my_timeseries.meta 

You can easily get an overview of the metadata, this will show you a basic representation of the metadata entries that are relevant to this TimeSeries.

>>> meta 
|-------------------------------------------------------------------------------------------------|
|TimeRange                  | Columns         | Meta                                              |
|-------------------------------------------------------------------------------------------------|
|2011-06-06T23:59:59.961999 | xrsa            | simple: True                                      |
|            to             | xrsb            | bitpix: 8                                         |
|2011-06-07T23:59:57.631999 |                 | naxis: 0                                          |
|                           |                 | extend: True                                      |
|                           |                 | date: 26/06/2012                                  |
|                           |                 | numext: 3                                         |
|                           |                 | telescop: GOES 15                                 |
|                           |                 | instrume: X-ray Detector                          |
|                           |                 | object: Sun                                       |
|                           |                 | origin: SDAC/GSFC                                 |
|                           |                 | ...                                               |
|-------------------------------------------------------------------------------------------------|

The data within a TimeSeriesMetaData object is stored as a list of tuples, each tuple representing the metadata from a source file or provided when creating the time series. The tuple will contain a TimeRange telling us which rows the metadata applies to, a list of column name strings for which the metadata applies to and finally a MetaDict object for storing the key/value pairs of the metadata itself. Each time a TimeSeries is concatenated to the original a new set of rows and/or columns will be added to the DataFrame and a new entry will be added into the metadata. Note that entries are ordered chronologically based on start and generally it’s expected that no two TimeSeries will overlap on both columns and time range. For example, it is not good practice for alternate row values in a single column to be relevant to different metadata entries as this would make it impossible to uniquely identify the metadata relevant to each cell.

If you want the string that is printed then you can use the to_string method. This has the advantage of having optional keyword arguments that allows you to set the depth (number of rows for each entry) and width (total number of characters wide) to better fit your output. For example:

>>> meta.to_string(depth=20, width=99) 
"|-------------------------------------------------------------------------------------------------|\n|TimeRange                  | Columns         | Meta                                              |\n|-------------------------------------------------------------------------------------------------|\n|2011-06-06T23:59:59.961999 | xrsa            | simple: True                                      |\n|            to             | xrsb            | bitpix: 8                                         |\n|2011-06-07T23:59:57.631999 |                 | naxis: 0                                          |\n|                           |                 | extend: True                                      |\n|                           |                 | date: 26/06/2012                                  |\n|                           |                 | numext: 3                                         |\n|                           |                 | telescop: GOES 15                                 |\n|                           |                 | instrume: X-ray Detector                          |\n|                           |                 | object: Sun                                       |\n|                           |                 | origin: SDAC/GSFC                                 |\n|                           |                 | date-obs: 07/06/2011                              |\n|                           |                 | time-obs: 00:00:00.000                            |\n|                           |                 | date-end: 07/06/2011                              |\n|                           |                 | time-end: 23:59:57.632                            |\n|                           |                 | comment: Energy band information given in extensio|\n|                           |                 | history:                                          |\n|                           |                 | keycomments: {'SIMPLE': 'Written by IDL:  Tue Jun |\n|-------------------------------------------------------------------------------------------------|\n"

Similar to the TimeSeries, the metadata has some properties for convenient access to the global metadata details, including columns as a list of strings, and time_range of the data. Beyond this, there are properties to get lists of details for all the entries in the TimeSeriesMetaData object, including timeranges, columns (as a list of string column names) and metas. Similar to TimeSeries objects you can concatenate TimeSeriesMetaData objects, but generally you won’t need to do this as it is done automatically when actioned on the TimeSeries. Note that when truncating a TimeSeriesMetaData object you will remove any entries outside of the given TimeRange. You can also append a new entry (as a tuple or list), which will add the entry in the correct chronological position. It is frequently necessary to locate the metadata for a given column, row or cell which can be uniquely identified by both, to do this you can use the find method, by adding “colname” and/or time/row keyword arguments you get a TimeSeriesMetaData object returned which contains only the relevant entries. You can then use the metas property to get a list of just the relevant MetaDict objects. For example:

>>> tsmd_return = my_timeseries.meta.find(colname='xrsa', time='2012-06-01 00:00:33.904999') 
>>> tsmd_return.metas 
[]

Note, the colname and time keywords are optional, but omitting both just returns the original. A common use case for the metadata is to find out the instrument(s) that gathered the data and in this case you can use the get method. This method takes a single key string or list of key strings with the optional filters and will search for any matching values. This method returns another TimeSeriesMetaData object, but removes all unwanted key/value pairs. The result can be converted into a simple list of strings using the values method:

>>> tsmd_return = my_timeseries.meta.get('telescop', colname='xrsa') 
>>> tsmd_return.values() 
['GOES 15']

Note values removes duplicate strings and sorts the returned list. You can update the values for these entries efficiently using the update method which takes a dictionary argument and updates the values to each of the dictionaries that match the given colname and time filters, for example:

>>> my_timeseries.meta.update({'telescop': 'G15'}, colname='xrsa', overwrite=True) 

Here we have to specify the overwrite=True keyword parameter to allow us to overwrite values for keys already present in the MetaDict objects, this helps protect the integrity of the original metadata and without this set (or with it set to False) you can still add new key/value pairs. Note that the MetaDict objects are both case-insensitive for key strings and have ordered entries, where possible the order is preserved when updating values.

The role of “rsun” in sunpy#

Unlike a solid ball, the Sun is not defined by a single surface, and as such, the position from which a photon is emitted should best be defined not just with a 2 dimensional coordinate but with the inclusion of a height. The Solar radius is a common choice for this height, and is defined by the IAU as the distance from the center of the Sun to a layer in the photosphere where the optical depth is 2/3. However, different wavelengths of light can be emitted from different heights in the solar atmosphere, and so the radius at which a photon is emitted is not always the same as the solar radius. It is therefore a useful convention to define “rsun” attribute for a map, which is the radius at which the emission is presumed to originate. This has limitations: data will invariably consist of a range of wavelengths of light and even a single wavelength, especially for coronal emission, comes from a variety of heights The value of “rsun” is intended as an estimate for this height.

Data providers often include this information for end users via the fits keyword: “RSUN_REF”, where this keyword is not available, sunpy assumes the standard value of the Solar radius, as defined by sunpy.sun.constants. Whether “rsun” is set by the data provider or not, we emphasize that this is little more than an approximation. The exact value used for “rsun” has consequences for working in sunpy with coordinates and transforms, it provides 3 dimensional information about the location of a map pixel. For example, when loading a fits file with sunpy.map.Map a map.wcs is constructed with a value for “rsun” collected for use in coordinate transformations. When manipulating these coordinates, idiosyncrasies and unexpected behavior might be encountered, these can be preempted by an understanding of behaviors surrounding “rsun”.

Transforming between Helioprojective frames#

It is possible to convert from a Helioprojective frame with one observer location to another Helioprojective frame with a different observer location. The transformation requires the coordinate to be 3D, so if the initial coordinate is only 2D, the default assumption maps that 2D coordinate to the surface of the Sun, at a radius defined by the “rsun” frame attribute. The conversion can be performed as follows:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord

>>> import sunpy.coordinates
>>> from sunpy.coordinates import frames

>>> hpc1 = SkyCoord(0*u.arcsec, 0*u.arcsec, observer="earth", obstime="2017-07-26", frame=frames.Helioprojective)
>>> hpc_out = sunpy.coordinates.Helioprojective(observer="venus", obstime="2017-07-26")
>>> hpc2 = hpc1.transform_to(hpc_out)

An example with two maps, named aia and stereo:

>>> hpc1 = SkyCoord(0*u.arcsec, 0*u.arcsec, frame=aia.coordinate_frame)  
>>> hpc2 = hpc1.transform_to(stereo.coordinate_frame)  

Reprojecting between frames with different “rsun”#

When the values of “rsun” from two wcs instances are different, issues with reprojecting between those frames can be encountered. reproject_to() by default, enforces a round-trip behavior, the idea being that you should only trust the reprojection if the 2D coordinates from each observer both resolve to the same 3D point (within a pixel volume). When the values for “rsun” are different, that criterion fails towards the limb. In other cases, this furthers results in banding as the criterion fails, then succeeds and then fails again.

Changing the value of “rsun” can fix this issue. Take the example Reprojecting Images to Different Observers. If we run the reprojection twice, once before fixing the discrepancy in the “rsun_ref” metadata and once after:

from matplotlib import pyplot as plt
from sunpy.map import Map
from sunpy.data.sample import AIA_193_JUN2012, STEREO_A_195_JUN2012

plt.rcParams['figure.figsize'] = (16, 8)

map_aia = Map(AIA_193_JUN2012)
map_euvi = Map(STEREO_A_195_JUN2012)

outmap1 = map_euvi.reproject_to(map_aia.wcs)
map_euvi.meta['rsun_ref'] = map_aia.meta['rsun_ref']
outmap2 = map_euvi.reproject_to(map_aia.wcs)

fig = plt.figure()
ax1 = fig.add_subplot(121, projection=outmap1); outmap1.plot(axes=ax1); plt.title('Without rsun Fix')
ax2 = fig.add_subplot(122, projection=outmap2); outmap2.plot(axes=ax2); plt.title('With rsun Fix')

plt.show()

(Source code, png, hires.png, pdf)

_images/rsun-1.png

We can see the difference in the appearance of the reprojected maps near the limb.

Reference#

sunpy (sunpy)#

sunpy Package#

sunpy#

An open-source Python library for Solar Physics data analysis.

Functions#

self_test(*[, package, online, online_only, ...])

system_info()

Prints ones' system info in an "attractive" fashion.

print_config()

Print current configuration options.

Coordinates (sunpy.coordinates)#

This sub-package contains:

The SunPy coordinate framework extends the Astropy coordinates framework. The coordinates guide provides in depth discussion of the structure and concepts underlying the coordinates framework.

Supported Coordinate Systems#

Coordinate system

Abbreviation

SunPy/Astropy equivalent

Notes

Heliocentric Aries Ecliptic (Mean)

HAE (also HEC)

Astropy’s HeliocentricMeanEcliptic

Heliocentric Cartesian

HCC

Heliocentric

Heliocentric Earth Ecliptic

HEE

HeliocentricEarthEcliptic

Heliocentric Earth Equatorial

HEEQ (also HEQ)

HeliographicStonyhurst

Use a Cartesian representation

Heliocentric Inertial

HCI

HeliocentricInertial

Heliocentric Radial

HCR

similar to Heliocentric

Use a cylindrical representation, but with a 90-degree offset in psi

Heliocentric/Heliographic Radial-Tangential-Normal

HGRTN

similar to Heliocentric

The axes are permuted, with HCC X, Y, Z equivalent respectively to HGRTN Y, Z, X

Heliographic Carrington

HGC

HeliographicCarrington

Heliographic Stonyhurst

HGS

HeliographicStonyhurst

Helioprojective Cartesian

HPC

Helioprojective

Geocentric Earth Equatorial (Mean)

GEI

GeocentricEarthEquatorial

Geographic

GEO

Astropy’s ITRS

The precise geographic definitions may differ

Geocentric Solar Ecliptic

GSE

GeocentricSolarEcliptic

Geomagnetic

MAG

Geomagnetic

Solar Magnetic

SM

SolarMagnetic

GeocentricSolarMagnetospheric

GSM

GeocentricSolarMagnetospheric

For a description of these coordinate systems, see Thompson (2006) and Franz & Harper (2002) (and corrected version).

Reference/API#

sunpy.coordinates Package#

This subpackage contains:

The diagram below shows all of Sun-based and Earth-based coordinate systems available through sunpy.coordinates, as well as the transformations between them. Each frame is labeled with the name of its class and its alias (useful for converting other coordinates to them using attribute-style access).

The frames colored in cyan are implemented in astropy.coordinates, and there are other astronomical frames that can be transformed to that are not shown below (see astropy.coordinates.builtin_frames).

digraph AstropyCoordinateTransformGraph { node [style=filled fillcolor=lightcyan] graph [rankdir=LR] ICRS [shape=oval label="ICRS\n`icrs`"]; CIRS [shape=oval label="CIRS\n`cirs`"]; GCRS [shape=oval label="GCRS\n`gcrs`"]; HCRS [shape=oval label="HCRS\n`hcrs`"]; AltAz [shape=oval label="AltAz\n`altaz`"]; HeliocentricMeanEcliptic [shape=oval label="HeliocentricMeanEcliptic\n`heliocentricmeanecliptic`"]; HeliocentricTrueEcliptic [shape=oval label="HeliocentricTrueEcliptic\n`heliocentrictrueecliptic`"]; Astropy [shape=box3d style=filled fillcolor=lightcyan label="Other frames\nin Astropy"]; ITRS [shape=oval label="ITRS\n`itrs`"]; PrecessedGeocentric [shape=oval label="PrecessedGeocentric\n`precessedgeocentric`"]; GeocentricMeanEcliptic [shape=oval label="GeocentricMeanEcliptic\n`geocentricmeanecliptic`"]; GeocentricTrueEcliptic [shape=oval label="GeocentricTrueEcliptic\n`geocentrictrueecliptic`"]; HeliographicStonyhurst [fillcolor=white shape=oval label="HeliographicStonyhurst\n`heliographic_stonyhurst`"]; Geomagnetic [fillcolor=white shape=oval label="Geomagnetic\n`geomagnetic`"]; HeliocentricEarthEcliptic [fillcolor=white shape=oval label="HeliocentricEarthEcliptic\n`heliocentricearthecliptic`"]; GeocentricEarthEquatorial [fillcolor=white shape=oval label="GeocentricEarthEquatorial\n`geocentricearthequatorial`"]; HeliographicCarrington [fillcolor=white shape=oval label="HeliographicCarrington\n`heliographic_carrington`"]; Heliocentric [fillcolor=white shape=oval label="Heliocentric\n`heliocentric`"]; HeliocentricInertial [fillcolor=white shape=oval label="HeliocentricInertial\n`heliocentricinertial`"]; Helioprojective [fillcolor=white shape=oval label="Helioprojective\n`helioprojective`"]; GeocentricSolarEcliptic [fillcolor=white shape=oval label="GeocentricSolarEcliptic\n`geocentricsolarecliptic`"]; SolarMagnetic [fillcolor=white shape=oval label="SolarMagnetic\n`solarmagnetic`"]; GeocentricSolarMagnetospheric [fillcolor=white shape=oval label="GeocentricSolarMagnetospheric\n`geocentricsolarmagnetospheric`"]; ICRS -> CIRS[ color = "#d95f02" ]; ICRS -> GCRS[ color = "#d95f02" ]; ICRS -> HCRS[ color = "#555555" ]; ICRS -> AltAz[ color = "#d95f02" ]; ICRS -> HeliocentricMeanEcliptic[ color = "#555555" ]; ICRS -> HeliocentricTrueEcliptic[ color = "#555555" ]; ICRS -> Astropy[ color = "#000000" ]; CIRS -> AltAz[ color = "#d95f02" ]; CIRS -> ICRS[ color = "#d95f02" ]; CIRS -> CIRS[ color = "#d95f02" ]; CIRS -> GCRS[ color = "#d95f02" ]; CIRS -> ITRS[ color = "#d95f02" ]; AltAz -> CIRS[ color = "#d95f02" ]; AltAz -> ICRS[ color = "#d95f02" ]; AltAz -> AltAz[ color = "#d95f02" ]; AltAz -> ITRS[ color = "#d95f02" ]; GCRS -> ICRS[ color = "#d95f02" ]; GCRS -> HCRS[ color = "#d95f02" ]; GCRS -> GCRS[ color = "#d95f02" ]; GCRS -> CIRS[ color = "#d95f02" ]; GCRS -> PrecessedGeocentric[ color = "#d95f02" ]; GCRS -> GeocentricMeanEcliptic[ color = "#d95f02" ]; GCRS -> GeocentricTrueEcliptic[ color = "#d95f02" ]; HCRS -> ICRS[ color = "#555555" ]; HCRS -> HCRS[ color = "#555555" ]; HCRS -> HeliographicStonyhurst[ color = "#d95f02" ]; ITRS -> CIRS[ color = "#d95f02" ]; ITRS -> ITRS[ color = "#d95f02" ]; ITRS -> AltAz[ color = "#d95f02" ]; ITRS -> Geomagnetic[ color = "#d95f02" ]; PrecessedGeocentric -> GCRS[ color = "#d95f02" ]; PrecessedGeocentric -> PrecessedGeocentric[ color = "#d95f02" ]; GeocentricMeanEcliptic -> GCRS[ color = "#d95f02" ]; GeocentricMeanEcliptic -> GeocentricMeanEcliptic[ color = "#d95f02" ]; HeliocentricMeanEcliptic -> ICRS[ color = "#555555" ]; HeliocentricMeanEcliptic -> HeliocentricMeanEcliptic[ color = "#555555" ]; HeliocentricMeanEcliptic -> HeliocentricEarthEcliptic[ color = "#d95f02" ]; HeliocentricMeanEcliptic -> GeocentricEarthEquatorial[ color = "#d95f02" ]; GeocentricTrueEcliptic -> GCRS[ color = "#d95f02" ]; GeocentricTrueEcliptic -> GeocentricTrueEcliptic[ color = "#d95f02" ]; HeliocentricTrueEcliptic -> ICRS[ color = "#555555" ]; HeliocentricTrueEcliptic -> HeliocentricTrueEcliptic[ color = "#555555" ]; HeliographicStonyhurst -> HeliographicCarrington[ color = "#d95f02" ]; HeliographicStonyhurst -> Heliocentric[ color = "#d95f02" ]; HeliographicStonyhurst -> HCRS[ color = "#d95f02" ]; HeliographicStonyhurst -> HeliographicStonyhurst[ color = "#d95f02" ]; HeliographicStonyhurst -> HeliocentricInertial[ color = "#d95f02" ]; HeliographicCarrington -> HeliographicStonyhurst[ color = "#d95f02" ]; HeliographicCarrington -> HeliographicCarrington[ color = "#d95f02" ]; Heliocentric -> Helioprojective[ color = "#d95f02" ]; Heliocentric -> HeliographicStonyhurst[ color = "#d95f02" ]; Heliocentric -> Heliocentric[ color = "#d95f02" ]; Helioprojective -> Heliocentric[ color = "#d95f02" ]; Helioprojective -> Helioprojective[ color = "#d95f02" ]; HeliocentricEarthEcliptic -> HeliocentricMeanEcliptic[ color = "#d95f02" ]; HeliocentricEarthEcliptic -> HeliocentricEarthEcliptic[ color = "#d95f02" ]; HeliocentricEarthEcliptic -> GeocentricSolarEcliptic[ color = "#d95f02" ]; GeocentricSolarEcliptic -> HeliocentricEarthEcliptic[ color = "#d95f02" ]; GeocentricSolarEcliptic -> GeocentricSolarEcliptic[ color = "#d95f02" ]; HeliocentricInertial -> HeliographicStonyhurst[ color = "#d95f02" ]; HeliocentricInertial -> HeliocentricInertial[ color = "#d95f02" ]; GeocentricEarthEquatorial -> HeliocentricMeanEcliptic[ color = "#d95f02" ]; GeocentricEarthEquatorial -> GeocentricEarthEquatorial[ color = "#d95f02" ]; Geomagnetic -> ITRS[ color = "#d95f02" ]; Geomagnetic -> Geomagnetic[ color = "#d95f02" ]; Geomagnetic -> SolarMagnetic[ color = "#d95f02" ]; SolarMagnetic -> Geomagnetic[ color = "#d95f02" ]; SolarMagnetic -> SolarMagnetic[ color = "#d95f02" ]; SolarMagnetic -> GeocentricSolarMagnetospheric[ color = "#d95f02" ]; GeocentricSolarMagnetospheric -> SolarMagnetic[ color = "#d95f02" ]; GeocentricSolarMagnetospheric -> GeocentricSolarMagnetospheric[ color = "#d95f02" ]; Astropy -> ICRS[ color = "#000000" ]; overlap=false rankdir=LR {rank=same; ICRS; HCRS; Astropy} }

  • AffineTransform:

  • FunctionTransform:

  • FunctionTransformWithFiniteDifference:

  • StaticMatrixTransform:

  • DynamicMatrixTransform:

Built-in Frame Classes#

ICRS

A coordinate or frame in the ICRS system.

AltAz

A coordinate or frame in the Altitude-Azimuth system (Horizontal coordinates) with respect to the WGS84 ellipsoid.

GCRS

A coordinate or frame in the Geocentric Celestial Reference System (GCRS).

CIRS

A coordinate or frame in the Celestial Intermediate Reference System (CIRS).

ITRS

A coordinate or frame in the International Terrestrial Reference System (ITRS).

HCRS

A coordinate or frame in a Heliocentric system, with axes aligned to ICRS.

PrecessedGeocentric

A coordinate frame defined in a similar manner as GCRS, but precessed to a requested (mean) equinox.

GeocentricMeanEcliptic

Geocentric mean ecliptic coordinates.

HeliocentricMeanEcliptic

Heliocentric mean ecliptic coordinates.

GeocentricTrueEcliptic

Geocentric true ecliptic coordinates.

HeliocentricTrueEcliptic

Heliocentric true ecliptic coordinates.

Functions#

get_body_heliographic_stonyhurst(body[, ...])

Return a HeliographicStonyhurst frame for the location of a solar-system body at a specified time.

get_earth([time, include_velocity])

Return a SkyCoord for the location of the Earth at a specified time in the HeliographicStonyhurst frame.

get_horizons_coord(body[, time, id_type, ...])

Queries JPL HORIZONS and returns a SkyCoord for the location of a solar-system body at a specified time.

propagate_with_solar_surface([rotation_model])

Context manager for coordinate transformations to automatically apply solar differential rotation for any change in observation time.

solar_frame_to_wcs_mapping(frame[, projection])

For a given frame, this function returns the corresponding WCS object.

solar_wcs_frame_mapping(wcs)

This function registers the coordinates frames to their FITS-WCS coordinate type values in the astropy.wcs.utils.wcs_to_celestial_frame registry.

transform_with_sun_center()

Context manager for coordinate transformations to ignore the motion of the center of the Sun.

Classes#

BaseHeliographic(*args, **kwargs)

Base class for HeliographicCarrington (HGC) and HeliographicStonyhurst (HGS) frames.

BaseMagnetic(*args, **kwargs)

Base class for frames that rely on the Earth's magnetic model (MAG, SM, and GSM).

GeocentricEarthEquatorial(*args, **kwargs)

A coordinate or frame in the Geocentric Earth Equatorial (GEI) system.

GeocentricSolarEcliptic(*args, **kwargs)

A coordinate or frame in the Geocentric Solar Ecliptic (GSE) system.

GeocentricSolarMagnetospheric(*args, **kwargs)

A coordinate or frame in the GeocentricSolarMagnetospheric (GSM) system.

Geomagnetic(*args, **kwargs)

A coordinate or frame in the Geomagnetic (MAG) system.

Heliocentric(*args, **kwargs)

A coordinate or frame in the Heliocentric system, which is observer-based.

HeliocentricEarthEcliptic(*args, **kwargs)

A coordinate or frame in the Heliocentric Earth Ecliptic (HEE) system.

HeliocentricInertial(*args, **kwargs)

A coordinate or frame in the Heliocentric Inertial (HCI) system.

HeliographicCarrington(*args, **kwargs)

A coordinate or frame in the Carrington Heliographic (HGC) system.

HeliographicStonyhurst(*args, **kwargs)

A coordinate or frame in the Stonyhurst Heliographic (HGS) system.

Helioprojective(*args, **kwargs)

A coordinate or frame in the Helioprojective Cartesian (HPC) system, which is observer-based.

NorthOffsetFrame(*args, **kwargs)

A frame which is offset from another frame such that it shares the same origin, but has its "north pole" (i.e., the Z axis) in a different direction.

RotatedSunFrame(*args, **kwargs)

A frame that applies solar rotation to a base coordinate frame.

SolarMagnetic(*args, **kwargs)

A coordinate or frame in the Solar Magnetic (SM) system.

SunPyBaseCoordinateFrame(*args, **kwargs)

Base class for sunpy coordinate frames.

Class Inheritance Diagram#

Inheritance diagram of sunpy.coordinates.frames.BaseHeliographic, sunpy.coordinates.frames.BaseMagnetic, sunpy.coordinates.frames.GeocentricEarthEquatorial, sunpy.coordinates.frames.GeocentricSolarEcliptic, sunpy.coordinates.frames.GeocentricSolarMagnetospheric, sunpy.coordinates.frames.Geomagnetic, sunpy.coordinates.frames.Heliocentric, sunpy.coordinates.frames.HeliocentricEarthEcliptic, sunpy.coordinates.frames.HeliocentricInertial, sunpy.coordinates.frames.HeliographicCarrington, sunpy.coordinates.frames.HeliographicStonyhurst, sunpy.coordinates.frames.Helioprojective, sunpy.coordinates.metaframes.NorthOffsetFrame, sunpy.coordinates.metaframes.RotatedSunFrame, sunpy.coordinates.frames.SolarMagnetic, sunpy.coordinates.frames.SunPyBaseCoordinateFrame

sunpy.coordinates.ephemeris Module#

Ephemeris calculations using SunPy coordinate frames

Functions#

get_body_heliographic_stonyhurst(body[, ...])

Return a HeliographicStonyhurst frame for the location of a solar-system body at a specified time.

get_earth([time, include_velocity])

Return a SkyCoord for the location of the Earth at a specified time in the HeliographicStonyhurst frame.

get_horizons_coord(body[, time, id_type, ...])

Queries JPL HORIZONS and returns a SkyCoord for the location of a solar-system body at a specified time.

sunpy.coordinates.spice Module#

Bridge module to use the SkyCoord API for SPICE computations.

Note

This module requires the optional dependency spiceypy to be installed.

The SPICE observation geometry information system is being increasingly used by space missions to describe the locations of spacecraft and the time-varying orientations of reference frames. While the spiceypy package provides a Python interface for performing SPICE computations, its API is very different from that of SkyCoord.

This module “wraps” spiceypy functionality so that relevant SPICE computations can be accessed using the SkyCoord API. When loading a set of kernels, a frame class and corresponding transformations are created for each SPICE frame. One can also query the location of a body as computed via SPICE or retrieve the field of view (FOV) of an instrument.

To facilitate the conversion of a SPICE-based coordinate to the built-in frames in sunpy.coordinates, every SPICE-based coordinate has the method to_helioprojective(). This method returns a coordinate in the Helioprojective frame with the observer frame attribute appropriately set.

Be aware that when converting a SPICE-based coordinate to/from a built-in frame, there can be small inconsistencies due to differing planetary ephemerides and models for various orientations.

Notes

  • 2D coordinates can be transformed only if the to/from frames have the same SPICE body ID as their centers.

  • Transformations of velocities are not yet supported.

  • SPICE frame names are rendered as uppercase, except for plus/minus characters, which are replaced with lowercase 'p'/'n' characters.

Examples

Coordinates computations using SPICE kernels

Coordinates computations using SPICE kernels
Functions#

get_body(body, time, *[, spice_frame, observer])

Get the location of a body via SPICE.

get_fov(instrument, time, *[, resolution])

Get the field of view (FOV) for an instrument via SPICE.

initialize(kernels)

Load one more more SPICE kernels and create corresponding frame classes.

install_frame(spice_frame)

Install a specified SPICE frame.

Classes#

SpiceBaseCoordinateFrame(*args, **kwargs)

Base class for all frames generated to represent SPICE frames.

Class Inheritance Diagram#

Inheritance diagram of sunpy.coordinates.spice.SpiceBaseCoordinateFrame

sunpy.coordinates.sun Module#

Sun-specific coordinate calculations

Functions#

angular_radius([t])

Return the angular radius of the Sun as viewed from Earth.

sky_position([t, equinox_of_date])

Returns the apparent position of the Sun (right ascension and declination) on the celestial sphere using the equatorial coordinate system, referred to the true equinox of date (as default).

carrington_rotation_number([t])

Return the Carrington rotation number.

carrington_rotation_time(crot[, longitude])

Return the time of a given Carrington rotation.

true_longitude([t])

Returns the Sun's true geometric longitude, referred to the mean equinox of date.

apparent_longitude([t])

Returns the Sun's apparent longitude, referred to the true equinox of date.

true_latitude([t])

Returns the Sun's true geometric latitude, referred to the mean equinox of date.

apparent_latitude([t])

Returns the Sun's apparent latitude, referred to the true equinox of date.

mean_obliquity_of_ecliptic([t])

Returns the mean obliquity of the ecliptic, using the IAU 2006 definition.

true_rightascension([t, equinox_of_date])

Returns the Sun's true geometric right ascension relative to Earth, referred to the mean equinox of date (as default).

true_declination([t, equinox_of_date])

Returns the Sun's true geometric declination relative to Earth, referred to the mean equinox of date (as default).

true_obliquity_of_ecliptic([t])

Returns the true obliquity of the ecliptic, using the IAU 2006 definition.

apparent_rightascension([t, equinox_of_date])

Returns the Sun's apparent right ascension relative to Earth, referred to the true equinox of date (as default).

apparent_declination([t, equinox_of_date])

Returns the Sun's apparent declination relative to Earth, referred to the true equinox of date (as default).

print_params([t])

Print out a summary of solar ephemeris.

B0([time])

Return the B0 angle for the Sun at a specified time, which is the heliographic latitude of the of the center of the disk of the Sun as seen from Earth.

L0([time, light_travel_time_correction, ...])

Return the L0 angle for the Sun at a specified time, which is the apparent Carrington longitude of the Sun-disk center as seen from Earth.

P([time])

Return the position (P) angle for the Sun at a specified time, which is the angle between geocentric north and solar north as seen from Earth, measured eastward from geocentric north.

earth_distance([time])

Return the distance between the Sun and the Earth at a specified time.

orientation(location[, time])

Return the orientation angle for the Sun from a specified Earth location and time.

eclipse_amount(observer, *[, moon_radius])

Return the percentage of the Sun that is eclipsed by the Moon.

sunpy.coordinates.utils Module#

Miscellaneous utilities related to coordinates

Functions#

get_rectangle_coordinates(bottom_left, *[, ...])

Specify a rectangular region of interest in longitude and latitude in a given coordinate frame.

solar_angle_equivalency(observer)

Return the equivalency to convert between a physical distance on the Sun and an angular separation as seen by a specified observer.

get_limb_coordinates(observer[, rsun, ...])

Get coordinates for the solar limb as viewed by a specified observer.

Classes#

GreatArc(start, end[, center, points])

Calculate the properties of a great arc at user-specified points between a start and end point on a sphere.

Reference/API for supporting coordinates modules#

The parts of the following modules that are useful to a typical user are already imported into the sunpy.coordinates namespace.

sunpy.coordinates.frames Module#

Common solar physics coordinate systems.

This submodule implements various solar physics coordinate frames for use with the astropy.coordinates module.

Classes#

SunPyBaseCoordinateFrame(*args, **kwargs)

Base class for sunpy coordinate frames.

BaseHeliographic(*args, **kwargs)

Base class for HeliographicCarrington (HGC) and HeliographicStonyhurst (HGS) frames.

BaseMagnetic(*args, **kwargs)

Base class for frames that rely on the Earth's magnetic model (MAG, SM, and GSM).

HeliographicStonyhurst(*args, **kwargs)

A coordinate or frame in the Stonyhurst Heliographic (HGS) system.

HeliographicCarrington(*args, **kwargs)

A coordinate or frame in the Carrington Heliographic (HGC) system.

Heliocentric(*args, **kwargs)

A coordinate or frame in the Heliocentric system, which is observer-based.

Helioprojective(*args, **kwargs)

A coordinate or frame in the Helioprojective Cartesian (HPC) system, which is observer-based.

HeliocentricEarthEcliptic(*args, **kwargs)

A coordinate or frame in the Heliocentric Earth Ecliptic (HEE) system.

GeocentricSolarEcliptic(*args, **kwargs)

A coordinate or frame in the Geocentric Solar Ecliptic (GSE) system.

HeliocentricInertial(*args, **kwargs)

A coordinate or frame in the Heliocentric Inertial (HCI) system.

GeocentricEarthEquatorial(*args, **kwargs)

A coordinate or frame in the Geocentric Earth Equatorial (GEI) system.

Geomagnetic(*args, **kwargs)

A coordinate or frame in the Geomagnetic (MAG) system.

SolarMagnetic(*args, **kwargs)

A coordinate or frame in the Solar Magnetic (SM) system.

GeocentricSolarMagnetospheric(*args, **kwargs)

A coordinate or frame in the GeocentricSolarMagnetospheric (GSM) system.

Class Inheritance Diagram#

Inheritance diagram of sunpy.coordinates.frames.SunPyBaseCoordinateFrame, sunpy.coordinates.frames.BaseHeliographic, sunpy.coordinates.frames.BaseMagnetic, sunpy.coordinates.frames.HeliographicStonyhurst, sunpy.coordinates.frames.HeliographicCarrington, sunpy.coordinates.frames.Heliocentric, sunpy.coordinates.frames.Helioprojective, sunpy.coordinates.frames.HeliocentricEarthEcliptic, sunpy.coordinates.frames.GeocentricSolarEcliptic, sunpy.coordinates.frames.HeliocentricInertial, sunpy.coordinates.frames.GeocentricEarthEquatorial, sunpy.coordinates.frames.Geomagnetic, sunpy.coordinates.frames.SolarMagnetic, sunpy.coordinates.frames.GeocentricSolarMagnetospheric

sunpy.coordinates.metaframes Module#

Coordinate frames that are defined relative to other frames

Classes#

NorthOffsetFrame(*args, **kwargs)

A frame which is offset from another frame such that it shares the same origin, but has its "north pole" (i.e., the Z axis) in a different direction.

RotatedSunFrame(*args, **kwargs)

A frame that applies solar rotation to a base coordinate frame.

Class Inheritance Diagram#

Inheritance diagram of sunpy.coordinates.metaframes.NorthOffsetFrame, sunpy.coordinates.metaframes.RotatedSunFrame

sunpy.coordinates.wcs_utils Module#
Functions#

solar_wcs_frame_mapping(wcs)

This function registers the coordinates frames to their FITS-WCS coordinate type values in the astropy.wcs.utils.wcs_to_celestial_frame registry.

solar_frame_to_wcs_mapping(frame[, projection])

For a given frame, this function returns the corresponding WCS object.

Attribution#

Some of this documentation was adapted from Astropy under the terms of the BSD License.

This sub-package was initially developed by Pritish Chakraborty as part of GSOC 2014 and Stuart Mumford.

Data (sunpy.data)#

sunpy.data contains ways to access sample data and small test files for running the sunpy test suite.

sunpy.data Package#

Variables#

manager

This class provides a remote data manager for managing remote files.

cache

Cache provides a way to download and cache files.

sunpy.data.sample Module#

This module provides the following sample data files. When a sample shortname is accessed, the corresponding file is downloaded if needed. All files can be downloaded by calling download_all().

Summary variables#

file_dict

Dictionary of all sample shortnames and, if downloaded, corresponding file locations on disk (otherwise, None)

file_list

List of disk locations for sample data files that have been downloaded

Sample shortnames#

Sample shortname

Name of downloaded file

AIA_094_IMAGE

AIA20110607_063305_0094_lowres.fits

AIA_131_IMAGE

AIA20110607_063301_0131_lowres.fits

AIA_1600_IMAGE

AIA20110607_063305_1600_lowres.fits

AIA_1600_VENUS_IMAGE

aia_lev1_1600a_2012_06_06t04_07_29_12z_image_lev1_lowres.fits

AIA_171_IMAGE

AIA20110607_063302_0171_lowres.fits

AIA_171_ROLL_IMAGE

aiacalibim5.fits

AIA_193_CUTOUT01_IMAGE

AIA20110607_063307_0193_cutout.fits

AIA_193_CUTOUT02_IMAGE

AIA20110607_063931_0193_cutout.fits

AIA_193_CUTOUT03_IMAGE

AIA20110607_064555_0193_cutout.fits

AIA_193_CUTOUT04_IMAGE

AIA20110607_065219_0193_cutout.fits

AIA_193_CUTOUT05_IMAGE

AIA20110607_065843_0193_cutout.fits

AIA_193_IMAGE

AIA20110607_063307_0193_lowres.fits

AIA_193_JUN2012

AIA20120601_000007_0193_lowres.fits

AIA_211_IMAGE

AIA20110607_063302_0211_lowres.fits

AIA_304_IMAGE

AIA20110607_063334_0304_lowres.fits

AIA_335_IMAGE

AIA20110607_063303_0335_lowres.fits

AIA_STEREOSCOPIC_IMAGE

aia_lev1_171a_2023_07_06t00_05_33_35z_image_lev1.fits

CALLISTO_SPECTRUM

BIR_20110607_062400_10.fit

EIT_195_IMAGE

eit_l1_20110607_203753.fits

EUVI_STEREOSCOPIC_IMAGE

20230706_000525_n4eua.fts

EVE_TIMESERIES

20110607_EVE_L0CS_DIODES_1m.txt

GBM_TIMESERIES

glg_cspec_n5_110607_v00.pha

GOES_XRS_TIMESERIES

go1520110607.fits

HMI_LOS_IMAGE

HMI20110607_063211_los_lowres.fits

LOFAR_IMAGE

LOFAR_70MHZ_20190409_131136.fits

LYRA_LEVEL3_TIMESERIES

lyra_20110607-000000_lev3_std.fits

NORH_TIMESERIES

tca110607.fits

RHESSI_IMAGE

hsi_image_20110607_063300.fits

RHESSI_TIMESERIES

hsi_obssumm_20110607_025.fits

SRS_TABLE

20110607SRS.txt

STEREO_A_195_JUN2012

20120601_000530_n4eua.fits

STEREO_B_195_JUN2012

20120601_000530_n4eub.fits

SWAP_LEVEL1_IMAGE

swap_lv1_20110607_063329.fits

Functions#

download_all([force_download])

Download all sample data at once that has not already been downloaded.

sunpy.data.test Package#

This package contains all of SunPy’s test data.

Functions#

get_test_filepath(filename, **kwargs)

Return the full path to a test file in the data/test directory.

get_test_data_filenames()

Return a list of all test files in data/test directory.

get_dummy_map_from_header(filename)

Generate a dummy Map from header-only test data.

write_header_file_from_image_file(...[, hdu])

Convert a FITS file containing an image and a header to a plaintext file containing only the string representation of the header.

sunpy.data.data_manager Package#

Classes#

Cache(downloader, storage, cache_dir[, expiry])

Cache provides a way to download and cache files.

DataManager(cache)

This class provides a remote data manager for managing remote files.

ParfiveDownloader()

Concrete implementation of DownloaderBase using parfive.

SqliteStorage(path)

This provides a sqlite backend for storage.

Class Inheritance Diagram#

Inheritance diagram of sunpy.data.data_manager.cache.Cache, sunpy.data.data_manager.manager.DataManager, sunpy.data.data_manager.downloader.ParfiveDownloader, sunpy.data.data_manager.storage.SqliteStorage

sunpy.data.data_manager.downloader Module#

Classes#

DownloaderBase()

Base class for remote data manager downloaders.

DownloaderError

Error to be raised when a download fails.

ParfiveDownloader()

Concrete implementation of DownloaderBase using parfive.

Class Inheritance Diagram#

Inheritance diagram of sunpy.data.data_manager.downloader.DownloaderBase, sunpy.data.data_manager.downloader.DownloaderError, sunpy.data.data_manager.downloader.ParfiveDownloader

sunpy.data.data_manager.storage Module#

Storage module contains the abstract implementation of storage for sunpy.data.data_manager.Cache and a concrete implementation using sqlite.

Classes#

StorageProviderBase()

Base class for remote data manager storage providers.

SqliteStorage(path)

This provides a sqlite backend for storage.

InMemStorage()

This provides a storage stored in memory.

Class Inheritance Diagram#

Inheritance diagram of sunpy.data.data_manager.storage.StorageProviderBase, sunpy.data.data_manager.storage.SqliteStorage, sunpy.data.data_manager.storage.InMemStorage

Image processing (sunpy.image)#

sunpy.image contains routines to process images (i.e. numpy arrays). The routines in this submodule are generally exposed through map-specific functions in other places.

sunpy.image Package#

sunpy.image.resample Module#

Image resampling methods.

Functions#

resample(orig, dimensions[, method, center, ...])

Returns a new numpy.ndarray that has been resampled up or down.

reshape_image_to_4d_superpixel(img, ...)

Re-shape the two dimension input image into a a four dimensional array whose first and third dimensions express the number of original pixels in the "x" and "y" directions that form one superpixel.

sunpy.image.transform Module#

Functions for geometrical image transformation and warping.

Functions#

add_rotation_function(name, *, ...)

Decorator to add a rotation function to the registry of selectable implementations.

affine_transform(image, rmatrix[, order, ...])

Rotates, shifts and scales an image.

Input/output (sunpy.io)#

sunpy.io contains readers for files that are commonly used in solar physics.

Unified File Readers#

sunpy.io Package#
Functions#

detect_filetype(filepath)

Attempts to determine the type of file a given filepath is.

read_file(filepath[, filetype])

Automatically determine the filetype and read the file.

read_file_header(filepath[, filetype])

Reads the header from a given file.

write_file(fname, data, header[, filetype])

Write a file from a data & header pair using one of the defined file types.

sunpy.io.ana Module#

This module provides an ANA file Reader.

This is a modified version of pyana.

Warning

The reading and writing of ana files is not supported under Windows.

By default, this module is not installed on platforms other than Linux (x86-64) and macOS (x86-64 and ARM64). See the installation guide for more info.

Functions#

read(filename[, debug])

Loads an ANA file and returns the data and a header in a list of (data, header) tuples.

get_header(filename[, debug])

Loads an ANA file and only return the header consisting of the dimensions, size (defined as the product of all dimensions times the size of the datatype, this not relying on actual filesize) and comments.

write(filename, data[, comments, compress, ...])

Saves a 2D numpy.array as an ANA file and returns the bytes written or NULL.

Special File Readers#

sunpy.io.special.genx Module#

This module implements a solarsoft genx file reader.

Functions#

read_genx(filename)

solarsoft genx file reader.

sunpy has a custom reader for NOAA SWPC Solar Region Summary (SRS) files:

sunpy.io.special.srs Module#

This module implements a SRS File Reader.

Functions#

read_srs(filepath)

Parse a SRS table from NOAA SWPC.

asdf (Advanced Scientific Data Format)#

ASDF is a modern file format designed to meet the needs of the astronomy community. It has deep integration with Python, SunPy, and Astropy, as well as implementations in other languages. It can be used to store known Python objects in a portable, well defined file format. It is primarily useful for storing complex Astropy and SunPy objects in a way that can be loaded back into the same form as they were saved. It is designed to be an archive file format, with human readable metadata and a simple on-disk layout.

sunpy currently implements support for saving Map and coordinate frame objects into asdf files. As asdf tightly integrates into Python, saving a map to an asdf file will save the metadata, data and mask. In comparison, the mask is not currently saved to FITS. The following code shows to to save and load a sunpy Map to an asdf file

>>> import asdf
>>> import sunpy.map
>>> from sunpy.data.sample import AIA_171_IMAGE  
>>> aiamap = sunpy.map.Map(AIA_171_IMAGE)  
>>> tree = {'amap': aiamap}  
>>> with asdf.AsdfFile(tree) as asdf_file:  
...     asdf_file.write_to("sunpy_map.asdf")  
>>> input_asdf = asdf.open("sunpy_map.asdf")  
>>> input_asdf['amap']  
  <sunpy.map.sources.sdo.AIAMap object at ...>
  SunPy Map
  ---------
  Observatory:                 SDO
  Instrument:          AIA 3
  Detector:            AIA
  Measurement:                 171.0 Angstrom
  Wavelength:          171.0 Angstrom
  Observation Date:    2011-06-07 06:33:02
  Exposure Time:               0.234256 s
  Dimension:           [1024. 1024.] pix
  Coordinate System:   helioprojective
  Scale:                       [2.402792 2.402792] arcsec / pix
  Reference Pixel:     [511.5 511.5] pix
  Reference Coord:     [3.22309951 1.38578135] arcsec
  array([[ -95.92475  ,    7.076416 ,   -1.9656711, ..., -127.96519  ,
          -127.96519  , -127.96519  ],
         [ -96.97533  ,   -5.1167884,    0.       , ...,  -98.924576 ,
          -104.04137  , -127.919716 ],
         [ -93.99607  ,    1.0189276,   -4.0757103, ...,   -5.094638 ,
           -37.95505  , -127.87541  ],
         ...,
         [-128.01454  , -128.01454  , -128.01454  , ..., -128.01454  ,
          -128.01454  , -128.01454  ],
         [-127.899666 , -127.899666 , -127.899666 , ..., -127.899666 ,
          -127.899666 , -127.899666 ],
         [-128.03072  , -128.03072  , -128.03072  , ..., -128.03072  ,
          -128.03072  , -128.03072  ]], dtype=float32)
 >>> input_asdf.close()  

When saving a Map to ASDF all maps are saved as a GenericMap and not a specific source class. This comes with some trade-offs. If you are using custom map sources defined outside of the sunpy core package, and these sources are imported after asdf has been invoked for the first time (used, not just imported), then they will not be registered with the asdf converter. Also if the custom map subclass is not registered with sunpy.map.Map upon loading of the map, it will be returned as a GenericMap. This approach has been chosen despite these limitations so that once a map is saved to an ASDF file it can always be loaded back into a map rather than the asdf library returning it as a Python dictionary. It also follows the philosophy of the way maps are saved and loaded in the FITS format, where the components of the Map are serialised and the way meta data is handled depends solely on the contents of the .meta attribute.

Internal API#

These are readers and other utilities that are used internally by sunpy to read files. They are not intended to be used directly by users and we do not guarantee that they will work for all files of a given type nor will the API be stable.

sunpy.io._fits Module#

This module provides a FITS file reader for internal use.

We instead recommend users use the astropy.io.fits module, which provides more generic functionality to read FITS files.

Notes

  1. FITS files allow comments to be attached to every value in the header. This is implemented in this module as a KEYCOMMENTS dictionary in the sunpy header. To add a comment to the file on write, add a comment to this dictionary with the same name as a key in the header (upcased).

  2. Due to the way fits works with images, the header dictionary may differ depending on whether is accessed before or after the fits[0].data is requested. If the header is read before the data then the original header will be returned. If the header is read after the data has been accessed then the data will have been scaled and a modified header reflecting these changes will be returned: BITPIX may differ and BSCALE and B_ZERO may be dropped in the modified version.

  3. The verify(‘silentfix+warn’) call attempts to handle violations of the FITS standard. For example, nan values will be converted to “nan” strings. Attempting to cast a astropy.io.fits.Header to a dictionary while it contains invalid header tags will result in an error so verifying it early on makes the header easier to work with later.

Functions#

header_to_fits(header)

Convert a header dict to a Header.

read(filepath[, hdus, memmap])

Read a fits file.

get_header(afile)

Read a fits file and return just the headers for all HDU's.

write(fname, data, header[, hdu_type])

Take a data header pair and write a FITS file.

extract_waveunit(header)

Attempt to read the wavelength unit from a given FITS header.

format_comments_and_history(input_header)

Combine COMMENT and HISTORY cards into single entries.

sunpy.io.header Module#

This module provides a generic FileHeader object for the readers.

Classes#

FileHeader(*args, **kwargs)

FileHeader is designed to provide a consistent interface to all other sunpy classes that expect a generic file.

Class Inheritance Diagram#

Inheritance diagram of sunpy.io.header.FileHeader

sunpy.io._jp2 Module#

This module provides a JPEG 2000 file reader for internal use.

It was developed to read JPEG 2000 files created by the Helioviewer Project and not as a general JPEG 2000 file reader.

Functions#

read(filepath, **kwargs)

Reads a JPEG2000 file.

get_header(filepath)

Reads the header from the file.

write(fname, data, header, **kwargs)

Take a data header pair and write a JPEG2000 file.

sunpy.io._file_tools Module#

This module provides a generic file reader for internal use.

Functions#

read_file(filepath[, filetype])

Automatically determine the filetype and read the file.

read_file_header(filepath[, filetype])

Reads the header from a given file.

write_file(fname, data, header[, filetype])

Write a file from a data & header pair using one of the defined file types.

detect_filetype(filepath)

Attempts to determine the type of file a given filepath is.

CDF (common data format)#

CDF files are commonly used to store timeseries data observed by instruments taking in-situ measurements of plasmas throughout the heliosphere. sunpy provides support for reading in CDF files that conform to the Space Physics Guidelines for CDF.

sunpy.io._cdf Module#

This module provides a CDF file reader for internal use.

Functions#

read_cdf(fname)

Read a CDF file that follows the ISTP/IACG guidelines.

Maps (sunpy.map)#

sunpy Map objects are constructed using the special factory class:

class sunpy.map.map_factory.MapFactory(default_widget_type=None, additional_validation_functions=[], registry=None)[source]#

A factory for generating coordinate aware 2D images.

This factory takes a variety of inputs, such as file paths, wildcard patterns or (data, header) pairs.

Depending on the input different return types are possible.

Parameters:
  • *inputs – Inputs to parse for map objects. See the examples section for a detailed list of accepted inputs.

  • sequence (bool, optional) – Return a sunpy.map.MapSequence object comprised of all the parsed maps.

  • composite (bool, optional) – Return a sunpy.map.CompositeMap object comprised of all the parsed maps.

Returns:

Examples

>>> import sunpy.map
>>> from astropy.io import fits
>>> import sunpy.data.sample  
>>> mymap = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)  

sunpy.map Package#

Map objects are constructed using the special factory class: Map. All sunpy Maps are derived from sunpy.map.GenericMap, all the methods and attributes are documented in that class.

The result of a call to Map will be either a GenericMap object if no instrument matches, or a subclass of GenericMap which deals with a specific source of data, e.g., AIAMap or LASCOMap (see sunpy.map Package to see a list of all of them).

sunpy.map Package#
Functions#

all_coordinates_from_map(smap)

Returns the coordinates of the center of every pixel in a map.

all_corner_coords_from_map(smap)

Returns the coordinates of the pixel corners in a map.

all_pixel_indices_from_map(smap)

Returns pixel pair indices of every pixel in a map.

contains_coordinate(smap, coordinates)

Checks whether a coordinate falls within the bounds of a map.

contains_full_disk(smap)

Checks if a map contains the full disk of the Sun.

contains_limb(smap)

Checks if a map contains any part of the solar limb or equivalently whether the map contains both on-disk and off-disk pixels.

contains_solar_center(smap)

Returns True if smap contains the solar center.

coordinate_is_on_solar_disk(coordinates)

Checks if the helioprojective Cartesian coordinates are on the solar disk.

is_all_off_disk(smap)

Checks if none of the coordinates in the GenericMap are on the solar disk.

is_all_on_disk(smap)

Checks if all of the coordinates in the GenericMap are on the solar disk.

map_edges(smap)

Returns the pixel locations of the edges of an input map.

on_disk_bounding_coordinates(smap)

Returns the the bottom left and top right coordinates of the smallest rectangular region that contains all the on disk coordinates of the input map.

pixelate_coord_path(smap, coord_path, *[, ...])

Return the pixel coordinates for every pixel that intersects with a coordinate path.

sample_at_coords(smap, coordinates)

Samples the data in a map at given series of coordinates.

solar_angular_radius(coordinates)

Calculates the solar angular radius as seen by the observer.

Classes#

CompositeMap(map1 [,map2,..])

A Composite Map class

GenericMap(data, header[, plot_settings])

A Generic spatially-aware 2D data array

MapMetaValidationError

MapSequence(*args[, sortby, derotate])

A series of Maps in a single object.

PixelPair(x, y)

Variables#

Map

A factory for generating coordinate aware 2D images.

Class Inheritance Diagram#

Inheritance diagram of sunpy.map.compositemap.CompositeMap, sunpy.map.mapbase.GenericMap, sunpy.map.mapbase.MapMetaValidationError, sunpy.map.mapsequence.MapSequence, sunpy.map.mapbase.PixelPair

Header helpers#

The header_helper sub-module contains helper functions for generating FITS-WCS headers from Python objects.

sunpy.map.header_helper Module#
Functions#

make_fitswcs_header(data, coordinate[, ...])

Function to create a FITS-WCS header from a coordinate object (SkyCoord) that is required to create a GenericMap.

get_observer_meta(observer[, rsun])

Function to get observer meta from coordinate frame.

make_heliographic_header(date, ...[, ...])

Construct a FITS-WCS header for a full-Sun heliographic (Carrington or Stonyhurst) coordinate frame.

Instrument Map Classes#

Defined in sunpy.map.sources are a set of GenericMap subclasses which convert the specific metadata and other differences in each instruments data to the standard GenericMap interface. These ‘sources’ also define things like the colormap and default normalization for each instrument. These subclasses also provide a method, which describes to the Map factory which data and metadata pairs match its instrument.

sunpy.map.sources Package#
Functions#

from_helioviewer_project(meta)

Test determining if the given metadata contains Helioviewer Project sourced data.

source_stretch(meta, fits_stretch)

Assign the correct source-dependent image stretching function.

Classes#

AIAMap(data, header, **kwargs)

AIA Image Map.

CORMap(data, header, **kwargs)

STEREO-SECCHI CORonograph Image Map.

EITMap(data, header, **kwargs)

SOHO EIT Image Map.

EUIMap(data, header, **kwargs)

EUI Image Map

EUVIMap(data, header, **kwargs)

STEREO-SECCHI EUVI Image Map

GONGHalphaMap(data, header[, plot_settings])

GONG H-Alpha Map.

GONGSynopticMap(data, header[, plot_settings])

GONG Synoptic Map.

HIMap(data, header, **kwargs)

STEREO-SECCHI Heliospheric Imager (HI) Map.

HMIMap(data, header, **kwargs)

HMI Image Map.

HMISynopticMap(data, header, **kwargs)

SDO/HMI Synoptic Map.

KCorMap(data, header, **kwargs)

K-Cor Image Map.

LASCOMap(data, header, **kwargs)

SOHO LASCO Image Map

MDIMap(data, header, **kwargs)

SOHO MDI Image Map

MDISynopticMap(data, header, **kwargs)

SOHO MDI synoptic magnetogram Map.

RHESSIMap(data, header, **kwargs)

RHESSI Image Map.

SJIMap(data, header[, plot_settings])

A 2D IRIS Slit Jaw Imager Map.

SOTMap(data, header, **kwargs)

Hinode SOT Image Map definition.

SUVIMap(data, header, **kwargs)

SUVI Image Map.

SWAPMap(data, header, **kwargs)

PROBA2 SWAP Image Map.

SXTMap(data, header, **kwargs)

Yohkoh SXT Image Map

TRACEMap(data, header, **kwargs)

TRACE Image Map

WISPRMap(data, header, **kwargs)

WISPR Map

XRTMap(data, header, **kwargs)

Hinode XRT map definition.

Class Inheritance Diagram#

Inheritance diagram of sunpy.map.sources.sdo.AIAMap, sunpy.map.sources.stereo.CORMap, sunpy.map.sources.soho.EITMap, sunpy.map.sources.solo.EUIMap, sunpy.map.sources.stereo.EUVIMap, sunpy.map.sources.gong.GONGHalphaMap, sunpy.map.sources.gong.GONGSynopticMap, sunpy.map.sources.stereo.HIMap, sunpy.map.sources.sdo.HMIMap, sunpy.map.sources.sdo.HMISynopticMap, sunpy.map.sources.mlso.KCorMap, sunpy.map.sources.soho.LASCOMap, sunpy.map.sources.soho.MDIMap, sunpy.map.sources.soho.MDISynopticMap, sunpy.map.sources.rhessi.RHESSIMap, sunpy.map.sources.iris.SJIMap, sunpy.map.sources.hinode.SOTMap, sunpy.map.sources.suvi.SUVIMap, sunpy.map.sources.proba2.SWAPMap, sunpy.map.sources.yohkoh.SXTMap, sunpy.map.sources.trace.TRACEMap, sunpy.map.sources.psp.WISPRMap, sunpy.map.sources.hinode.XRTMap

Remote data (sunpy.net)#

sunpy.net contains a lot of different code for accessing various solar physics related web services. This submodule contains many layers. Most users should use Fido, which is an interface to multiple sources including all the sources implemented in dataretriever as well as vso and jsoc. Fido can be used like so:

>>> from sunpy.net import Fido, attrs as a
>>> results = Fido.search(a.Time("2012/1/1", "2012/1/2"), a.Instrument.lyra)  
>>> files = Fido.fetch(results)  

sunpy.net Package#

Classes#

Scraper(pattern[, regex])

A Scraper to scrap web data archives based on dates.

Variables#

Fido

Fido is a unified data search and retrieval tool.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.scraper.Scraper

sunpy.net.attrs Module#

‘attrs’ are parameters which can be composed together to specify searches to sunpy.net.Fido. They can be combined by using logical and (&) and logical or (|) operations to construct very complex queries.

For example you could combine two instruments using or (|) with a time specification and a sample cadence using:

>>> import astropy.units as u
>>> from sunpy.net import Fido, attrs as a
>>> a.Time("2011/01/01", "2011/01/02") & (a.Instrument.aia | a.Instrument.hmi) & a.Sample(1*u.day))  

In addition to the core attrs defined here, other sunpy clients also provide attrs specific to them, under:

Classes#

Time(start[, end, near])

Specify the time range of the query.

Instrument(value)

Specifies the Instrument name for the search.

Wavelength(wavemin[, wavemax])

Level(value)

Specifies the data processing level to search for.

ExtentType(value)

The type of Extent; for example, "FULLDISK", "SYNOPTIC", "LIMB", etc.

Sample(value)

Time interval for data sampling.

Detector(value)

The detector from which the data comes from.

Resolution(value)

Resolution level of the data.

Physobs(value)

Specifies the physical observable the VSO can search for.

Source(value)

Data sources that Fido can search with.

Provider(value)

Specifies the data provider to search for data using Fido.

AttrAnd(attrs)

Attribute representing attributes ANDed together.

AttrOr(attrs)

Attribute representing attributes ORed together.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.attrs.Time, sunpy.net.attrs.Instrument, sunpy.net.attrs.Wavelength, sunpy.net.attrs.Level, sunpy.net.attrs.ExtentType, sunpy.net.attrs.Sample, sunpy.net.attrs.Detector, sunpy.net.attrs.Resolution, sunpy.net.attrs.Physobs, sunpy.net.attrs.Source, sunpy.net.attrs.Provider, sunpy.net.attrs.AttrAnd, sunpy.net.attrs.AttrOr

sunpy.net.fido_factory Module#

This module provides the Fido instance of sunpy.net.fido_factory.UnifiedDownloaderFactory it also provides the UnifiedResponse class which Fido.search returns and the parfive.Results class that is returned by Fido.fetch.

Classes#

UnifiedResponse(*results)

The object used to store results from search.

UnifiedDownloaderFactory([...])

Fido is a unified data search and retrieval tool.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.fido_factory.UnifiedResponse, sunpy.net.fido_factory.UnifiedDownloaderFactory

VSO#
sunpy.net.vso Package#
Classes#

VSOClient([url, port, api])

Provides access to query and download from Virtual Solar Observatory (VSO).

VSOQueryResponseTable([data, masked, names, ...])

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.vso.vso.VSOClient, sunpy.net.vso.table_response.VSOQueryResponseTable

sunpy.net.vso.attrs Module#

Attributes that can be used to construct VSO queries.

Attributes are the fundamental building blocks of queries that, together with the two operations of AND and OR (and in some rare cases XOR) can be used to construct complex queries. Most attributes can only be used once in an AND-expression, if you still attempt to do so it is called a collision. For a quick example think about how the system should handle Instrument(‘aia’) & Instrument(‘eit’).

Classes#

Extent(x, y, width, length, atype)

Specify the spatial field-of-view of the query.

Field(fielditem)

A subclass of the value attribute.

Pixels(value)

Pixels are (currently) limited to a single dimension (and only implemented for SDO data) We hope to change this in the future to support TRACE, Hinode and other investigations where this changed between observations.

Filter(value)

This attribute is a placeholder for the future.

Quicklook(value)

Retrieve 'quicklook' data if available.

PScale(value)

Pixel Scale (PSCALE) is in arc seconds.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.vso.attrs.Extent, sunpy.net.vso.attrs.Field, sunpy.net.vso.attrs.Pixels, sunpy.net.vso.attrs.Filter, sunpy.net.vso.attrs.Quicklook, sunpy.net.vso.attrs.PScale

Dataretriever#
sunpy.net.dataretriever Package#

The sunpy.net.dataretriever submodule is a framework for downloading data from “simple” web sources such as HTTP or FTP servers. Although it could be used for more complex services as well. Following the example of sunpy.map and sunpy.timeseries this module provides a base class GenericClient from which specific services can subclass. All these subclasses are then registered with the sunpy.net.Fido factory class, so do not need to be called individually.

Classes#

EVEClient()

Provides access to Level 0CS Extreme ultraviolet Variability Experiment (EVE) data.

GBMClient()

Provides access to data from the Gamma-Ray Burst Monitor (GBM) instrument on board the Fermi satellite.

GONGClient()

Provides access to the Magnetogram products of NSO-GONG synoptic Maps.

GenericClient()

Base class for simple web clients for the data retriever module.

LYRAClient()

Provides access to the LYRA/Proba2 data archive.

NOAAIndicesClient()

Provides access to the NOAA solar cycle indices.

NOAAPredictClient()

Provides access to the NOAA SWPC predicted sunspot Number and 10.7 cm radio flux values.

NoRHClient()

Provides access to the Nobeyama RadioHeliograph (NoRH) averaged correlation time series data.

QueryResponse([data, masked, names, dtype, ...])

RHESSIClient()

Provides access to the RHESSI observing summary time series data.

SRSClient()

Provides access to the NOAA SWPC solar region summary data.

SUVIClient()

Provides access to data from the GOES Solar Ultraviolet Imager (SUVI).

XRSClient()

Provides access to several GOES XRS files archive.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.dataretriever.sources.eve.EVEClient, sunpy.net.dataretriever.sources.fermi_gbm.GBMClient, sunpy.net.dataretriever.sources.gong.GONGClient, sunpy.net.dataretriever.client.GenericClient, sunpy.net.dataretriever.sources.lyra.LYRAClient, sunpy.net.dataretriever.sources.noaa.NOAAIndicesClient, sunpy.net.dataretriever.sources.noaa.NOAAPredictClient, sunpy.net.dataretriever.sources.norh.NoRHClient, sunpy.net.dataretriever.client.QueryResponse, sunpy.net.dataretriever.sources.rhessi.RHESSIClient, sunpy.net.dataretriever.sources.noaa.SRSClient, sunpy.net.dataretriever.sources.goes.SUVIClient, sunpy.net.dataretriever.sources.goes.XRSClient

sunpy.net.dataretriever.attrs.goes Module#
Classes#

SatelliteNumber(value)

The GOES Satellite Number

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.dataretriever.attrs.goes.SatelliteNumber

JSOC#
sunpy.net.jsoc Package#
Classes#

Cutout(bottom_left[, top_right, width, ...])

Select a cutout region.

JSOCClient()

Provides access to the JSOC Data Export service.

JSOCResponse(*args, **kwargs)

Keyword(value)

Allows comparison filtering of the JSOC Keywords.

Notify(value)

An email address to get a notification to when JSOC has staged your request.

PrimeKey(label, value)

Prime Keys

Protocol(value)

The type of download to request one of ("FITS", "JPEG", "MPG", "MP4", or "as-is").

Segment(value)

Segments choose which files to download when there are more than one present for each record e.g. 'image'.

Series(value)

The JSOC Series to Download.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.jsoc.attrs.Cutout, sunpy.net.jsoc.jsoc.JSOCClient, sunpy.net.jsoc.jsoc.JSOCResponse, sunpy.net.jsoc.attrs.Keyword, sunpy.net.jsoc.attrs.Notify, sunpy.net.jsoc.attrs.PrimeKey, sunpy.net.jsoc.attrs.Protocol, sunpy.net.jsoc.attrs.Segment, sunpy.net.jsoc.attrs.Series

sunpy.net.jsoc.attrs Module#
Classes#

Series(value)

The JSOC Series to Download.

Protocol(value)

The type of download to request one of ("FITS", "JPEG", "MPG", "MP4", or "as-is").

Notify(value)

An email address to get a notification to when JSOC has staged your request.

Segment(value)

Segments choose which files to download when there are more than one present for each record e.g. 'image'.

PrimeKey(label, value)

Prime Keys

Cutout(bottom_left[, top_right, width, ...])

Select a cutout region.

Keyword(value)

Allows comparison filtering of the JSOC Keywords.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.jsoc.attrs.Series, sunpy.net.jsoc.attrs.Protocol, sunpy.net.jsoc.attrs.Notify, sunpy.net.jsoc.attrs.Segment, sunpy.net.jsoc.attrs.PrimeKey, sunpy.net.jsoc.attrs.Cutout, sunpy.net.jsoc.attrs.Keyword

HEK#
sunpy.net.hek Package#
Classes#

HEKClient([url])

Provides access to the Heliophysics Event Knowledgebase (HEK).

HEKRow(table, index)

Handles the response from the HEK.

HEKTable([data, masked, names, dtype, meta, ...])

A container for data returned from HEK searches.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.hek.hek.HEKClient, sunpy.net.hek.hek.HEKRow, sunpy.net.hek.hek.HEKTable

sunpy.net.hek.attrs Module#

Attributes that can be used to construct HEK queries. They are different to the VSO ones in that a lot of them are wrappers that conveniently expose the comparisons by overloading Python operators. So, e.g., you are able to say AR & AR.NumSpots < 5 to find all active regions with less than 5 spots. As with the VSO query, you can use the fundamental logic operators AND and OR to construct queries of almost arbitrary complexity. Note that complex queries result in multiple requests to the server which might make them less efficient.

Classes#

Contains(*types)

EventType(item)

HEKAttr(name, operator, value)

This ensures the attr inspect magic works for registering in the client.

HEKComparisonParamAttrWrapper(name)

SpatialRegion([x1, y1, x2, y2, sys])

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.hek.attrs.Contains, sunpy.net.hek.attrs.EventType, sunpy.net.hek.attrs.HEKAttr, sunpy.net.hek.attrs.HEKComparisonParamAttrWrapper, sunpy.net.hek.attrs.SpatialRegion

sunpy.net.hek2vso Package#

This module provides a translation layer between the HEK and the VSO. It allows you to acquire records of data that are available via the VSO, based on the data in HEK event entries.

Warning

This module is in beta and maybe unstable.

Functions#

translate_results_to_query(results)

Formulate VSO queries from HEK results.

vso_attribute_parse(phrase)

Parses VSO attributes from a HEK result.

Classes#

H2VClient()

Class to handle HEK to VSO translations

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.hek2vso.hek2vso.H2VClient

CDAWeb#
sunpy.net.cdaweb Package#
Functions#

get_datasets(observatory)

Get a list of datasets for a given observatory.

get_observatory_groups()

Get a list of observatory IDs for each observatory in CDAWeb.

Classes#

CDAWEBClient()

Provides access to query and download from the Coordinated Data Analysis Web (CDAWeb).

Dataset(value)

Dataset ID.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.cdaweb.cdaweb.CDAWEBClient, sunpy.net.cdaweb.attrs.Dataset

HELIO#
sunpy.net.helio Package#

A Module for accessing the HELIO web service

Classes#

HECClient([link])

Provides access to the HELIO webservices.

HECResponse([data, masked, names, dtype, ...])

A container for data returned from HEC searches.

Chaincode(origin, chaincode, **kwargs)

A tool to infer some information from chaincodes produced by HELIO Feature Catalogue or Heliophysics Events Knowledgebase.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.helio.hec.HECClient, sunpy.net.helio.hec.HECResponse, sunpy.net.helio.chaincode.Chaincode

sunpy.net.helio.attrs Module#
Classes#

MaxRecords(value)

The maximum number of desired records.

TableName(value)

The table to query from

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.helio.attrs.MaxRecords, sunpy.net.helio.attrs.TableName

Internal Classes and Functions#

These classes and functions are designed to be used to help develop new clients for sunpy.net.Fido.

sunpy.net.base_client Module#
Functions#

convert_row_to_table(func)

A wrapper to convert any QueryResponseRow objects to QueryResponseTable objects.

Classes#

QueryResponseColumn([data, name, dtype, ...])

A column subclass which knows about the client of the parent table.

BaseQueryResponse()

An Abstract Base Class for results returned from BaseClient.

QueryResponseRow(table, index)

A row subclass which knows about the client of the parent table.

QueryResponseTable([data, masked, names, ...])

A class to represent tables of heterogeneous data.

BaseClient()

This defines the Abstract Base Class for each download client.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.base_client.QueryResponseColumn, sunpy.net.base_client.BaseQueryResponse, sunpy.net.base_client.QueryResponseRow, sunpy.net.base_client.QueryResponseTable, sunpy.net.base_client.BaseClient

sunpy.net.dataretriever.client Module#
Classes#

QueryResponse([data, masked, names, dtype, ...])

GenericClient()

Base class for simple web clients for the data retriever module.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.dataretriever.client.QueryResponse, sunpy.net.dataretriever.client.GenericClient

sunpy.net.attr Module#

Allow representation of queries as logic expressions. This module makes sure that attributes that are combined using the two logic operations AND (&) and OR (|) always are in disjunctive normal form, that is, there are only two levels ­- the first being disjunction and the second being conjunction. In other words, every combinations of attributes looks like this: (a AND b AND c) OR (d AND e).

Walkers are used to traverse the tree that results from combining attributes. They are implemented using functools.singledispatch modified to dispatch on the second argument to the function.

Please note that & is evaluated first, so A & B | C is equivalent to (A & B) | C.

Functions#

and_(*args)

Trick operator precedence.

or_(*args)

Trick operator precedence.

Classes#

Attr()

This is the base for all attributes.

DataAttr(*args, **kwargs)

A base class for attributes classes which contain data.

DummyAttr()

Empty attribute.

SimpleAttr(value)

An attribute that only has a single value.

Range(min_, max_)

An attribute that represents a range of a value.

AttrAnd(attrs)

Attribute representing attributes ANDed together.

AttrOr(attrs)

Attribute representing attributes ORed together.

ValueAttr(attrs)

AttrWalker()

Traverse the Attr tree and convert it to a different representation.

AttrComparison(name, operator, value)

Allows a Attr to have a value and a comparison operator.

ComparisonParamAttrWrapper(name)

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.attr.Attr, sunpy.net.attr.DataAttr, sunpy.net.attr.DummyAttr, sunpy.net.attr.SimpleAttr, sunpy.net.attr.Range, sunpy.net.attrs.AttrAnd, sunpy.net.attrs.AttrOr, sunpy.net.attr.ValueAttr, sunpy.net.attr.AttrWalker, sunpy.net.attr.AttrComparison, sunpy.net.attr.ComparisonParamAttrWrapper

sunpy.net.scraper Module#

This module provides a web scraper.

Classes#

Scraper(pattern[, regex])

A Scraper to scrap web data archives based on dates.

Class Inheritance Diagram#

Inheritance diagram of sunpy.net.scraper.Scraper

Physics (sunpy.physics)#

sunpy.physics contains routines to calculate various physical parameters and models for the Sun.

sunpy.physics Package#

sunpy.physics.differential_rotation Module#

Functions#

diff_rot(duration, latitude[, rot_type, ...])

Deprecated since version 6.0.

solar_rotate_coordinate(coordinate[, ...])

Given a coordinate on the Sun, calculate where that coordinate maps to as seen by a new observer at some later or earlier time, given that the input coordinate rotates according to the solar rotation profile.

differential_rotate(smap[, observer, time])

Warp a GenericMap to take into account both solar differential rotation and the changing location of the observer.

Solar properties (sunpy.sun)#

sunpy.sun contains constants, parameters and models of the Sun.

sunpy.sun.constants Module#

This module provides fundamental solar physical constants. The following constants are available:

Name

Value

Unit

Description

mass

1.98847542e+30

kg

Solar mass

radius

695700000

m

Nominal solar radius

luminosity

3.828e+26

W

Nominal solar luminosity

mean distance

1.49597871e+11

m

Astronomical Unit

perihelion distance

1.471e+11

m

Perihelion Distance

aphelion distance

1.521e+11

m

Aphelion Distance

age

4.6e+09

year

Age of the Sun

solar flux unit

1e-22

W m**-2 Hz**-1

Solar flux unit

visual magnitude

-26.75

Apparent visual magnitude

average angular size

959.63

arcsec

Semidiameter

surface area

6.087e+18

m**2

Surface area

average density

1409

kg m**-3

Mean density

surface gravity

274

m s**-2

Surface gravity

moment of inertia

5.7e+54

kg m**-2

Moment of inertia

volume

1.4122e+27

m**3

Volume

escape velocity

617700

m s**-1

Escape velocity at surface

oblateness

8.01

marcsec

oblateness

metallicity

0.0122

Metallicity

sunspot cycle

11.4

year

Average duration of sunspot cycle

average intensity

20090000

W m**-2 sr**-1

Mean Intensity

effective temperature

5778

K

Effective black-body temperature

mass conversion rate

4.3e+09

kg s**-1

Mass conversion rate

center density

162200

kg m**-3

Center density

center temperature

15710000

K

Center temperature

absolute magnitude

4.83

Absolute magnitude

mean energy production

0.0001937

J kg**-1

mean energy production

ellipticity

5e-05

ellipticity

GM

132712000

km**3 s**-2

standard gravitational parameter

W_0

84.176

deg

longitude of the prime meridian (epoch J2000.0)

sidereal rotation rate

14.1844

deg day**-1

sidereal rotation rate

first Carrington rotation (JD TT)

2398167.4

day

first Carrington rotation (JD TT)

mean synodic period

27.2752612

day

mean synodic period

alpha_0

286.13

deg

right ascension (RA) of the north pole (epoch J2000.0)

delta_0

63.87

deg

declination of the north pole (epoch J2000.0)

Functions#

get(key)

Retrieve a constant by key.

find([sub])

Return list of constants keys containing a given string.

print_all()

Provides a table of the complete list of constants.

Variables#

spectral_classification

Spectral classification

au

Astronomical Unit

mass

Solar mass

equatorial_radius

Nominal solar radius

volume

Volume

surface_area

Surface area

average_density

Mean density

equatorial_surface_gravity

Surface gravity

effective_temperature

Effective black-body temperature

luminosity

Nominal solar luminosity

mass_conversion_rate

Mass conversion rate

escape_velocity

Escape velocity at surface

sfu

Solar flux unit

average_angular_size

Semidiameter

sidereal_rotation_rate

sidereal rotation rate

first_carrington_rotation

Time of the start of the first Carrington rotation

mean_synodic_period

mean synodic period

sunpy.sun.models Module#

Solar Physical Models#

This module contains models of the Sun from various sources:

  • interior: QTable of the structure of the solar interior as defined in Table 7 of Turck-Chieze et al. (1988)

  • evolution: QTable of the evolution of the Sun over time as defined in Table 6 of Turck-Chieze et al. (1988)

  • differential_rotation(): Function for calculating solar differential rotation for different models

References

Functions#

differential_rotation(duration, latitude, *)

Computes the change in longitude over a duration for a given latitude.

Variables#

interior

A class to represent tables of heterogeneous data.

evolution

A class to represent tables of heterogeneous data.

Time (sunpy.time)#

sunpy.time contains helpers for converting strings to astropy.time.Time objects and handling common operations on these objects. As well as this a TimeRange object is provided for representing a period of time and performing operations on that range.

sunpy.time Package#

Functions#

find_time(string, format)

Return iterator of occurrences of date formatted with format in string.

is_time(time_string[, time_format])

Returns true if the input is a valid date/time representation.

is_time_equal(t1, t2)

Work around for astropy/astropy#6970.

is_time_in_given_format(time_string, time_format)

Tests whether a time string is formatted according to the given time format.

julian_centuries([t])

Returns the number of Julian centuries since J1900.0 (noon on 1900 January 0).

parse_time(time_string, *[, format])

Takes a time input and will parse and return a astropy.time.Time.

Classes#

TimeRange(a[, b, format])

A class to create and handle time ranges.

TimeTaiSeconds(val1, val2, scale, precision, ...)

SI seconds from 1958-01-01 00:00:00, which includes UTC leap seconds.

TimeUTime(val1, val2, scale, precision, ...)

UT seconds from 1979-01-01 00:00:00 UTC, ignoring leap seconds.

Class Inheritance Diagram#

Inheritance diagram of sunpy.time.timerange.TimeRange, sunpy.time.timeformats.TimeTaiSeconds, sunpy.time.timeformats.TimeUTime

Timeseries (sunpy.timeseries)#

One of the core classes in sunpy is a timeseries. A number of instruments are supported through subclasses of the base GenericTimeSeries class. See Instrument TimeSeries Classes for a list of them.

A timeseries can be created by calling TimeSeries.

Instrument TimeSeries Classes#

The generic method to create an instrument-specific TimeSeries is to call TimeSeries with a file path and the instrument-specific source keyword argument. In some cases the source can be determined automatically if a FITS file is being loaded.

The following example shows the factory loading a sample file:

>>> import sunpy.timeseries as ts
>>> import sunpy.data.sample  
>>> goes = ts.TimeSeries(sunpy.data.sample.GOES_XRS_TIMESERIES, source='XRS')  

The TimeSeries factory will load the file and create the timeseries instance. The following instrument classes are supported:

sunpy.timeseries Package#
Classes#

TimeSeriesMetaData([meta, timerange, colnames])

Used to store metadata for TimeSeries that enables multiple sunpy.timeseries.TimeSeries metadata to be concatenated in an organized fashion.

GenericTimeSeries(data[, meta, units])

A generic time series object.

Variables#

TimeSeries

A factory for generating solar timeseries objects.

sunpy.timeseries.sources Package#

This module provides a collection of datasource-specific TimeSeries classes.

Each mission should have its own file with one or more classes defined. Typically, these classes will be subclasses of the sunpy.timeseries.TimeSeries.

Classes#

ESPTimeSeries(data[, meta, units])

SDO EVE/ESP Level 1 data.

EVESpWxTimeSeries(data[, meta, units])

SDO EVE LightCurve for level 0CS data.

GBMSummaryTimeSeries(data[, meta, units])

Fermi/GBM Summary lightcurve TimeSeries.

XRSTimeSeries(data[, meta, units])

GOES XRS Time Series.

LYRATimeSeries(data[, meta, units])

Proba-2 LYRA Lightcurve TimeSeries.

NOAAIndicesTimeSeries(data[, meta, units])

NOAA Solar Cycle monthly indices.

NOAAPredictIndicesTimeSeries(data[, meta, units])

NOAA Solar Cycle Predicted Progression.

NoRHTimeSeries(data, header, units, **kwargs)

Nobeyama Radioheliograph Correlation lightcurve TimeSeries.

RHESSISummaryTimeSeries(data[, meta, units])

RHESSI X-ray Summary lightcurve TimeSeries.

Class Inheritance Diagram#

Inheritance diagram of sunpy.timeseries.sources.eve.ESPTimeSeries, sunpy.timeseries.sources.eve.EVESpWxTimeSeries, sunpy.timeseries.sources.fermi_gbm.GBMSummaryTimeSeries, sunpy.timeseries.sources.goes.XRSTimeSeries, sunpy.timeseries.sources.lyra.LYRATimeSeries, sunpy.timeseries.sources.noaa.NOAAIndicesTimeSeries, sunpy.timeseries.sources.noaa.NOAAPredictIndicesTimeSeries, sunpy.timeseries.sources.norh.NoRHTimeSeries, sunpy.timeseries.sources.rhessi.RHESSISummaryTimeSeries

CDF files#

GenericTimeSeries can load a single CDF file, or a list of CDF files if concatenate=True is passed.

Units#

The physical units of different columns in CDF files do not conform to a standard that astropy.units understands. sunpy internally stores a set of common mappings from unit strings to Unit, but you may see a warning about unrecognised unit strings when reading a CDF file. To register the correct unit definition astropy.units.add_enabled_units() can be used. For example, to register ‘deg K’ as representing Kelvin and ‘#/cc’ as 1/cm^3:

>>> import astropy.units as u
>>> _ = u.add_enabled_units([u.def_unit('deg K', represents=u.K), u.def_unit('#/cc', represents=u.cm**-3)])

Utilities (sunpy.util)#

sunpy.util Package#

Functions#

deprecated(since[, message, name, ...])

Used to mark a function or class as deprecated.

dict_keys_same(list_of_dicts)

Makes sure that a list of dictionaries all have the same keys.

expand_list(inp)

Expand a list of lists or tuples.

expand_list_generator(inp)

find_dependencies([package, extras])

List installed and missing dependencies.

fix_duplicate_notes(subclass_doc, cls_doc)

Returns a new documentation string such that there are notes section duplication in in Map subclasses.

get_keywords(func)

Returns a set of keyword names from func's signature.

get_set_methods(obj)

Returns a set of keyword names that can be handled by an object's set_... methods.

get_width()

Gets the width of the current terminal.

hash_file(path)

Returns the SHA-256 hash of a file.

missing_dependencies_by_extra([package, ...])

Get all the specified extras for a package and report any missing dependencies.

replacement_filename(path)

Return a replacement path if input path is currently in use.

sunpycontextmanager(func)

A decorator that tracks the entry and exit of a context manager, setting the key's value to True on entry and False on exit.

system_info()

Prints ones' system info in an "attractive" fashion.

unique(itr[, key])

Return only unique elements of a sequence.

warn_connection(msg[, stacklevel])

Raise a SunpyConnectionWarning.

warn_deprecated(msg[, stacklevel])

Raise a SunpyDeprecationWarning.

warn_metadata(msg[, stacklevel])

Raise a SunpyMetadataWarning.

warn_user(msg[, stacklevel])

Raise a SunpyUserWarning.

Classes#

MetaDict(*args[, save_original])

A class to hold metadata associated with a sunpy.map.Map derivative.

NoMapsInFileError

An error raised when a file is opened and no maps are found.

SunpyConnectionWarning

A warning class to indicate a connection warning.

SunpyDeprecationWarning

A warning class to indicate a deprecated feature.

SunpyMetadataWarning

Warning class for cases metadata is missing.

SunpyPendingDeprecationWarning

A warning class to indicate a soon-to-be deprecated feature.

SunpyUserWarning

The primary warning class for Sunpy.

SunpyWarning

The base warning class from which all Sunpy warnings should inherit.

Class Inheritance Diagram#

Inheritance diagram of sunpy.util.metadata.MetaDict, sunpy.util.exceptions.NoMapsInFileError, sunpy.util.exceptions.SunpyConnectionWarning, sunpy.util.exceptions.SunpyDeprecationWarning, sunpy.util.exceptions.SunpyMetadataWarning, sunpy.util.exceptions.SunpyPendingDeprecationWarning, sunpy.util.exceptions.SunpyUserWarning, sunpy.util.exceptions.SunpyWarning

sunpy.util.config Module#

This module provides SunPy’s configuration file functionality.

Functions#

load_config()

Read the "sunpyrc" configuration file.

copy_default_config([overwrite])

Copies the default sunpy config file to the user's config directory.

print_config()

Print current configuration options.

sunpy.util.datatype_factory_base Module#

This module provides the base registration factory used for all SunPy data/net factories.

Classes#

BasicRegistrationFactory([...])

Generalized registerable factory type.

NoMatchError

Exception for when no candidate class is found.

MultipleMatchError

Exception for when too many candidate classes are found.

ValidationFunctionError

Exception for when no candidate class is found.

Class Inheritance Diagram#

Inheritance diagram of sunpy.util.datatype_factory_base.BasicRegistrationFactory, sunpy.util.datatype_factory_base.NoMatchError, sunpy.util.datatype_factory_base.MultipleMatchError, sunpy.util.datatype_factory_base.ValidationFunctionError

sunpy.util.net Module#

This module provides general net utility functions.

Functions#

parse_header(line)

Parse a Content-type like header.

slugify(text[, delim])

Slugify given unicode text.

get_content_disposition(content_disposition)

Get the content disposition filename from given header.

get_filename(sock, url)

Get filename from given urlopen object and URL.

get_system_filename(sock, url[, default])

Get filename from given urlopen object and URL.

download_file(url, directory[, default, ...])

Download a file from a url into a directory.

download_fileobj(opn, directory[, url, ...])

Download a file from a url into a directory.

sunpy.util.xml Module#

This module provides XML helper functions.

Functions#

xml_to_dict(xmlstring)

Converts an XML string to a Python dictionary.

node_to_dict(node)

Scans through the children of the node and makes a dictionary from the content.

get_node_text(node)

Scans through all children of Element node and gathers the text.

Classes#

NotTextNodeError

Class Inheritance Diagram#

Inheritance diagram of sunpy.util.xml.NotTextNodeError

sunpy.util.sphinx Package#

Helpers and extensions for sphinx.

This subpackage contains two sphinx directives:

  • .. generate:: which includes raw output generated by a Python script into a .. raw:: block. This can be used to for example, include HTML output into the built docs.

  • .. changelog:: which renders the latest changelog with towncrier and includes it in the documentation.

To use them add 'sunpy.util.sphinx.generate' and 'sunpy.util.sphinx.changelog' to the extensions list in your conf.py file.

sunpy.util.sphinx.generate Module#
Classes#

Generate(name, arguments, options, content, ...)

Custom directive to include raw output generated using supplied Python code

Class Inheritance Diagram#

Inheritance diagram of sunpy.util.sphinx.generate.Generate

sunpy.util.functools Module#

This file defines wrappers and variants of things in the functools standard lib.

Functions#

seconddispatch(func)

A variant of functools.singledispatch which dispatches on type of the second argument.

Visualization (sunpy.visualization)#

sunpy.visualization contains plotting helpers and functions.

sunpy.visualization Package#

Functions#

axis_labels_from_ctype(ctype, unit)

Returns axis labels for the given coordinate type and unit.

peek_show(func)

A decorator to place on peek() methods to show the figure.

sunpy.visualization.colormaps Package#

The following colormaps are provided by this module.

(Source code, png, hires.png, pdf)

_images/visualization-1.png
  • ‘goes-rsuvi94’

  • ‘goes-rsuvi131’

  • ‘goes-rsuvi171’

  • ‘goes-rsuvi195’

  • ‘goes-rsuvi284’

  • ‘goes-rsuvi304’

  • ‘sdoaia94’

  • ‘sdoaia131’

  • ‘sdoaia171’

  • ‘sdoaia193’

  • ‘sdoaia211’

  • ‘sdoaia304’

  • ‘sdoaia335’

  • ‘sdoaia1600’

  • ‘sdoaia1700’

  • ‘sdoaia4500’

  • ‘sohoeit171’

  • ‘sohoeit195’

  • ‘sohoeit284’

  • ‘sohoeit304’

  • ‘soholasco2’

  • ‘soholasco3’

  • ‘sswidlsoholasco2’

  • ‘sswidlsoholasco3’

  • ‘stereocor1’

  • ‘stereocor2’

  • ‘stereohi1’

  • ‘stereohi2’

  • ‘yohkohsxtal’

  • ‘yohkohsxtwh’

  • ‘hinodexrt’

  • ‘hinodesotintensity’

  • ‘trace171’

  • ‘trace195’

  • ‘trace284’

  • ‘trace1216’

  • ‘trace1550’

  • ‘trace1600’

  • ‘trace1700’

  • ‘traceWL’

  • ‘hmimag’

  • ‘irissji1330’

  • ‘irissji1400’

  • ‘irissji1600’

  • ‘irissji2796’

  • ‘irissji2832’

  • ‘irissji5000’

  • ‘irissjiFUV’

  • ‘irissjiNUV’

  • ‘irissjiSJI_NUV’

  • ‘kcor’

  • ‘rhessi’

  • ‘std_gamma_2’

  • ‘euvi171’

  • ‘euvi195’

  • ‘euvi284’

  • ‘euvi304’

  • ‘solar orbiterfsi174’

  • ‘solar orbiterfsi304’

  • ‘solar orbiterhri_euv174’

  • ‘solar orbiterhri_lya1216’

Functions#

show_colormaps([search])

Displays a plot of the custom color maps supported in SunPy.

sunpy.visualization.colormaps.color_tables Module#

This module provides dictionaries for generating LinearSegmentedColormap, and a dictionary of these dictionaries.

Functions#

aia_color_table(wavelength)

Returns one of the fundamental color tables for SDO AIA images.

sswidl_lasco_color_table(number)

Returns one of the SSWIDL-defined color tables for SOHO LASCO images.

eit_color_table(wavelength)

Returns one of the fundamental color tables for SOHO EIT images.

sxt_color_table(sxt_filter)

Returns one of the fundamental color tables for Yokhoh SXT images.

xrt_color_table()

Returns the color table used for all Hinode XRT images.

trace_color_table(measurement)

Returns one of the standard color tables for TRACE JP2 files.

sot_color_table(measurement)

Returns one of the standard color tables for SOT files (following osdc convention).

hmi_mag_color_table()

Returns an alternate HMI Magnetogram color table; from Stanford University/JSOC.

suvi_color_table(wavelength)

Returns one of the fundamental color tables for SUVI images.

rhessi_color_table()

std_gamma_2()

euvi_color_table(wavelength)

solohri_lya1216_color_table()

sunpy.visualization.animator Package#

Classes#

MapSequenceAnimator(mapsequence[, annotate])

Create an interactive viewer for a MapSequence.

Class Inheritance Diagram#

Inheritance diagram of sunpy.visualization.animator.mapsequenceanimator.MapSequenceAnimator

sunpy.visualization.wcsaxes_compat Module#

This module provides functions to make WCSAxes work in SunPy.

Functions#

is_wcsaxes(axes)

Tests a matplotlib.axes.Axes object to see if it is an instance of WCSAxes.

gca_wcs(wcs[, fig, slices])

Get the current axes, or create a new WCSAxes if fig has no axes.

get_world_transform(axes)

Get the transformation to world coordinates.

default_wcs_grid(axes)

Apply some default WCSAxes grid formatting.

wcsaxes_heliographic_overlay(axes[, ...])

Create a heliographic overlay using WCSAxes.

sunpy.visualization.drawing Module#

This module provides functions that draw on Astropy’s WCSAxes.

Functions#

limb(axes, observer, *[, rsun, resolution])

Draws the solar limb as seen by the specified observer.

equator(axes, *[, rsun, resolution])

Draws the solar equator as seen by the axes observer.

prime_meridian(axes, *[, rsun, resolution])

Draws the solar prime meridian (zero Carrington longitude) as seen by the axes observer.

Customizing sunpy#

The sunpyrc file#

The sunpy core package uses a sunpyrc configuration file to customize certain properties. You can control a number of key features of sunpy such as where your data will download to. sunpy looks for the sunpyrc file in a platform specific directory, which you can see the path for by running:

>>> import sunpy
>>> sunpy.print_config()  
FILES USED:
  ...

CONFIGURATION:
  [general]
  time_format = %Y-%m-%d %H:%M:%S
  working_dir = ...

  [downloads]
  download_dir = ...
  remote_data_manager_dir = ...
  cache_expiry = 10
  sample_dir = ...

  [database]
  url = sqlite:////...

  [logger]
  log_level = INFO
  use_color = True
  log_warnings = True
  log_exceptions = False
  log_to_file = False
  log_file_level = INFO
  log_file_format = %(asctime)s, %(origin)s, %(levelname)s, %(message)s

Do not edit the default file (the first in the “FILES USED:” list above) directly as every time you install or update sunpy, this file will be overwritten.

To maintain your personal customizations place a copy of the default “sunpyrc” file into the user configuration path. To find this path, use:

>>> from sunpy.extern.appdirs import AppDirs
>>> AppDirs('sunpy', 'sunpy').user_config_dir  

You can use sunpy.util.config.copy_default_config to write the default config into the correct place.

The user configuration path can also be set using an environment variable SUNPY_CONFIGDIR.

Depending on your system, it may be useful to have a site-wide configuration file. If it is used, it will be on the “FILES USED:” list below the default file. To find your system’s site configuration path for sunpy, use:

>>> from sunpy.extern.appdirs import AppDirs
>>> AppDirs('sunpy', 'sunpy').site_config_dir  

In Unix, the site and user configuration paths follow the XDG specifications.

The site configuration is applied before your personal user configuration, thus your configuration file will override the site configuration settings. For this reason, when you create or edit your personal configuration file, you may want to replicate the site configuration items into your own configuration file, or comment out the items in your configuration file that are set in the site configuration file.

See below for the example config file.

Dynamic settings#

You can also dynamically change the default settings in a python script or interactively from the python shell. All of the settings are stored in a Python ConfigParser instance called sunpy.config, which is global to the sunpy package. Settings can be modified directly, for example:

import sunpy
sunpy.config.set('downloads', 'download_dir', '/home/user/Downloads')
A sample sunpyrc file#

(download)

;
; SunPy Configuration
;
; This is a sample sunpy configuration file - you can find a copy
; of it on your system in site-packages/sunpy/data/sunpyrc. If you edit it
; there, please note that it will be overridden in your next install.
; If you want to keep a permanent local copy that will not be
; over-written, we use AppDirs https://pypi.org/project/appdirs/
; to find the right place for each OS to place it.
; So if you open the link, you can find the user_config_dir
; or print(sunpy.util.config.CONFIG_DIR) to find it on your own system.
; Note that any relative filepaths specified in the SunPy configuration file
; will be relative to SunPy's working directory.

;;;;;;;;;;;;;;;;;;;
; General Options ;
;;;;;;;;;;;;;;;;;;;
[general]

; The SunPy working directory is the parent directory where all generated
; and download files will be stored.
; Default Value: <user's home directory>/sunpy
; working_dir = /home/$USER/sunpy

; Time Format to be used for displaying time in output (e.g. graphs)
; The default time format is based on ISO8601 (replacing the T with space)
; note that the extra '%'s are escape characters
time_format = %Y-%m-%d %H:%M:%S

;;;;;;;;;;;;;
; Downloads ;
;;;;;;;;;;;;;
[downloads]

; Location to save download data to. Path should be specified relative to the
; SunPy working directory.
; Default value: data/
download_dir = data

; Location to save remote data manager (sunpy.data.manager) data to.
; Path should be specified relative to the SunPy working directory.
; Default value: data_manager/
remote_data_manager_dir = data_manager

; Time interval for cache expiry in days.
; Default value: 10
cache_expiry = 10

; Location where the sample data will be downloaded. If not specified, will be
; downloaded to platform specific user data directory.
; The default directory is specified by appdirs (https://github.com/ActiveState/appdirs)
; sample_dir = /data/sample_data

;;;;;;;;;;;;
; Logger   ;
;;;;;;;;;;;;
[logger]

# Threshold for the logging messages. Logging messages that are less severe
# than this level will be ignored. The levels are 'DEBUG', 'INFO', 'WARNING',
# 'ERROR'
log_level = INFO

# Whether to use color for the level names
use_color = True

# Whether to log warnings.warn calls
log_warnings = True

# Whether to log exceptions before raising them
log_exceptions = False

# Whether to always log messages to a log file
log_to_file = False

# The file to log messages to
# log_file_path = sunpy.log

# Threshold for logging messages to log_file_path
log_file_level = INFO

# Format for log file entries
log_file_format = %(asctime)s, %(origin)s, %(levelname)s, %(message)s

Troubleshooting and Bugs#

Obtaining sunpy version#

To find out your sunpy version number, import it and print the __version__ attribute:

>>> import sunpy   
>>> sunpy.__version__   

System Info#

To quickly collect information on your system, you can use our convenience function system_info which you can run through:

>>> import sunpy   
>>> sunpy.util.system_info()   

The output should look something like:

==========================================================
 sunpy Installation Information

 Sunday, 18. November 2012 11:06PM UT
==========================================================

###########
 General
###########
OS: Mac OS X 10.8.2 (i386)
Python: 2.7.3 (64bit)

####################
 Required libraries
####################
sunpy: 0.1
NumPy: 1.6.2
SciPy: 0.10.1
Matplotlib: 1.2.x
PyFITS: 3.0.8
pandas: 0.8.1

#######################
 Recommended libraries
#######################
beautifulsoup4: 4.1.1
PyQt: 4.9.4
SUDS: 0.4'

This information is especially useful if you are running into a bug and need help.

Making use of the logger#

For information on configuring and using sunpy's logging system, a useful tool for troubleshooting, see Logger Objects.

sunpy install location#

You can find what directory sunpy is installed in by importing it and printing the __file__ attribute:

>>> import sunpy   
>>> sunpy.__file__   

.sunpy directory location#

Each user should have a .sunpy/ directory which should contain a sunpyrc file. To locate your .sunpy/ directory, use sunpy.print_config():

>>> import sunpy as sun   
>>> sun.print_config()   

We use appdirs to work out the location depending on your operating system.

If you would like to use a different configuration directory, you can do so by specifying the location in your SUNPY_CONFIGDIR environment variable.

Reporting Bugs#

If you are having a problem with sunpy, search the mailing list or the github issue tracker. It is possible that someone else has already run into your problem.

If not, please provide the following information in your e-mail to the mailing list or to the github issue tracker:

  • your operating system; (Linux/UNIX users: post the output of uname -a)

  • sunpy version:

    >>> import sunpy   
    >>> sunpy.util.system_info()   
    
  • how you obtained sunpy.

  • any customizations to your sunpyrc file (see Customizing sunpy).

  • Please try to provide a minimal, standalone Python script that demonstrates the problem. This is the critical step. If you can’t post a piece of code that we can run and reproduce your error, the chances of getting help are significantly diminished. Very often, the mere act of trying to minimize your code to the smallest bit that produces the error will help you find a bug in your code that is causing the problem.

SSWIDL/sunpy Cheat Sheet#

SolarSoft (SSWIDL) is a popular IDL software library for solar data analysis, and in fact, many parts of sunpy are inspired by data structures and functions in SSWIDL. Though IDL and Python are very different it sometimes helps to consider how to translate simple tasks between the two languages. The primary packages which provide much of the functionality for scientific data analysis in Python are NumPy and SciPy. In the following we assume that those packages are available to you and that you are imported Numpy is imported as np with the following import statement:

import numpy as np
import scipy as sp

In the following examples, a and b could be arrays. For python the arrays must be numpy arrays which can be created simply through:

np.array(a)

where a is a python list of numbers.

Relational Operators

IDL

Python

a EQ b

a == b

a LT b

a < b

a GT b

a > b

a GE b

a >= b

a LE b

a <= b

a NE b

a != b

Logical Operators

IDL

Python

a and b

a and b

a or b

a or b

Math Functions

IDL

Python

cos(a)

np.cos(a)

alog(a)

np.log(a)

alog10(a)

np.alog(a)

exp(a)

np.exp(a)

Math Constants

IDL

Python

!pi

np.pi

exp(1)

np.e

Arrays Sequences

IDL

Python

indgen(10)

np.arange(0,10)

findgen(10)

np.arange(0,10,dtype=np.float)

Array Creation

IDL

Python

dblarr(3,5)

np.zeros((3,5))

intarr(3,5)

np.zeros((3,5),dtype=np.int)

dblarr(3,5)+1

np.ones((3,5))

intarr(3,5)+9

np.zeros((3,5),dtype=np.int) + 9

boolarr(10)

np.zeros(10,dtype=bool)

identity(3)

np.identity(3)

Many more examples can be found on this page

Release History#

A new version of the sunpy core package is released twice a year, with target release months of May and November.

  • Versions x.0 are long term support (LTS) releases, supported for 12 months until the next LTS release.

  • Versions x.1 are non-LTS releases, supported for 6 months until the next LTS release.

Bugfix releases (versions x.y.1, x.y.2, etc.) are released monthly, with a target of the last Friday of each month.

Full Changelog#

6.0.dev521+gaabc56841 (2024-05-19)#

Breaking Changes#
Deprecations#
Removals#
  • sunpy.database has been removed. (#7320)

  • sunpy.map.header_helper.meta_keywords has been removed. (#7337)

  • sunpy.net.helioviewer.HelioviewerClient has been removed. Use the hvpy package instead. (#7338)

  • There was a private “Maxwell” unit within sunpy.map to register it before astropy had support for it. This has now been removed in favour of using the astropy version. (#7383)

New Features#
  • sunpy.io.read_file() will now try to detect the filetype based on the content and then fallback to using the file extension. (#6736)

  • It is now possible to read the comments in a header from a JPEG2000 file. (#6841)

  • Added the ability for sunpy.map.Map to load files from a generator. (#7024)

  • Added the ability to pass clip_interval to sunpy.map.MapSequence.plot. (#7253)

  • Add support for the fill keyword in draw_contours() to allow for filled contours. (#7281)

  • get_horizons_coord() now supports time arrays with up to 10,000 elements. (#7319)

  • Add an example of plotting a rectangle on a map with a rotation angle relative to the axes (Drawing a rotated rectangle on a map). (#7348)

  • Added testing and explicit support for Python 3.12. (#7351)

  • Added warning when importing a submodule without installing that submodules extra dependencies. (#7369)

  • Added a warning message for rsun mismatch in reproject_to() method. (#7370)

  • Added a new optional extra group to install “opencv” if you want to it for affine transforms.

    pip install sunpy[opencv] (`#7383 <https://github.com/sunpy/sunpy/pull/7383>`__)
    
  • Increased minimum versions for:

    • asdf >= 2.12.0

    • asdf-astropy >= 0.2.0

    • astropy >= 5.2.0

    • beautifulsoup4 >= 4.11.0

    • cdflib >= 0.4.4

    • dask >= 2022.5.2

    • h5netcdf > =1.0.0

    • h5py >= 3.7.0

    • lxml >= 4.9.0

    • opencv-python >= 4.6.0.66

    • pandas >= 1.4.0

    • python >= 3.10

    • reproject >= 0.9.0

    • requests >= 2.28.0

    • scikit-image >= 0.19.0

    • scipy >= 1.8.0

    • spiceypy >= 5.0.0

    • tqdm >= 4.64.0

    • zeep >= 4.1.0 (#7383)

  • sunpy.map.GenericMap.draw_contours() don’t run internal transform code if transform keyword is provided. (#7427)

  • Update ASDF schemas for upcoming ASDF standard 1.6.0. (#7432)

  • Add a new map source GONGHalphaMap for GONG H-Alpha data. (#7451)

  • Allow units to be passed to make_fitswcs_header as strings. (#7454)

  • sunpy.net.jsoc.JSOCClient queries now return the SUMS directory paths as the segment key value in the results table. (#7469)

Bug Fixes#
  • Long object names are no longer truncated in the logging output of get_horizons_coord(). (#7319)

  • When calling sunpy.map.GenericMap.rotate() on an integer data array, with missing set to NaN (the default value), the method will now itself raise an informative error message instead deferring to NumPy to raise the error. (#7344)

  • Fixed the appearance of a double “Notes” heading in Map subclasses. (#7376)

  • Map with UINT8 data will now not error on plotting due to normalization. We now skip adding a normalization. (#7422)

  • When calling reproject_to() along with both context managers propagate_with_solar_surface() and assume_spherical_screen() now raises a warning. (#7437)

  • Fix a bug which caused Fido.search to crash due to SSL certificate verification error for the HECClient now returns no results and logs a warning in this case. (#7446)

  • Fixed the sanitization of the names of files downloaded via VSO so that periods are no longer replaced and case is no longer forced to be lowercase. (#7453)

  • The creation of the series string for a JSOC query was not adding the correct escape characters for comparison values for keywords. This was causing the JSOC to error. (#7467)

  • The EVE L0CS client now uses the new URLs for the data from LASP. (#7483)

  • JPEG2000 files are now saved with the correct orientation. Previously they would be vertically flipped when saved. (#7486)

  • Fixed a very minor inaccuracy in three sunpy.map utility functions (contains_full_disk(), coordinate_is_on_solar_disk(), and is_all_off_disk()) resulting from the accidental use of the small-angle approximation. (#7512)

  • The rotate() function now correctly updates the NAXISi. (#7522)

  • Fixed an inaccuracy in the implementation of HeliocentricEarthEcliptic and GeocentricSolarEcliptic such that the Earth was not exactly in the XY plane, but rather had an error of up ~10 meters. (#7530)

  • The maximum records in HECClient now are 20000. (#7540)

  • Fixed a bug with any coordinate transformation starting in GeocentricEarthEquatorial (GEI) returning output with AU as the length unit, rather than preserving the length unit of the initial coordinate. (#7545)

  • Fixed a bug that interfered with astropy.wcs.utils.celestial_frame_to_wcs() when working with a custom subclass of SunPyBaseCoordinateFrame. (#7594)

  • Fixed bug where conversion of results from the HEKClient to Astropy Time failed when some values where empty or missing for the values of event_strattime, event_endtime or event_peaktime (#7627)

Documentation#
  • Added a how-to guide for manipulating grid lines on GenericMap. (#6978)

  • Created a how to guide on fixing metadata that is either missing or incorrect before passing the header into the Map class. (#7262)

  • Fixed the usage of superpixel() in Resampling Maps. (#7316)

  • Added Clarification on setting JSOC Email. (#7329)

  • Added explanation text to Plot positions on a blank map about the offset between “(0, 0)” in helioprojective coordinates and the heliographic equator. (#7352)

  • Convert draw rectangle gallery example into a how-to guide(How to draw a rectangle on a Map) (#7435)

  • Fix a VSO doctest due to VSO now returning level one EIT data. (#7483)

  • Add an example gallery entry demonstrating how to use the coordinates framework to compute intersections between instrument lines of sight and a simulation domain. (#7491)

Internal Changes#
  • sunpy.net.jsoc.JSOCClient.fetch() called drms API that passed a progress keyword which added extra print statements to the console. This has been removed in drms 0.7.0, which had breaking API changes within this release. As a result, we increased the minimum required version of drms to 0.7.1.

    This specifically refers to the following information that was printed to the console by default:

    "Export request pending. [id=X, status=X]" "Waiting for X seconds..." "Request not found on server, X retries left."

    These were handled by drms and are now logging messages.

    If you want to silence these messages, you can set the logging level to WARNING or higher.

    import logging
    drms_logger = logging.getLogger("drms")
    drms_logger.setLevel(logging.WARNING)
    
    from sunpy.net import fido, attrs
    

    Note, you have to do it before you import fido. (#7307)

  • The requests package is a now formally a core dependency. requests was already commonly installed as an implied dependency of sunpy.net or for building documentation. (#7319)

  • The function get_horizons_coord() no longer calls the astroquery package, so astroquery is no longer a dependency. (#7319)

  • Notify checks that a valid email address has been given as a value. (#7342)

  • The delim_whitespace keyword in pandas.read_csv is deprecated and was updated with sep='\s+'. This should have no affect on the output of the code. (#7350)

  • Fixed an environment-specific failure of a unit test for sunpy.coordinates.Helioprojective.is_visible(). (#7356)

  • Moved to pyproject.toml and removed setup.py and setup.cfg. (#7384)

  • pyerfa is now a new direct dependency. It has been an indirect dependency from sunpy 3.1, over two years ago. (#7397)

  • Increased Python minimum version to be >= 3.10. (#7402)

  • Fixed an unnecessary division computation when performing a unsupported division operation using a Map. (#7551)

5.1.0 (2023-11-20)#

New Features#
  • Added the ability to skip over errors raised for invalid fits files when passing a list of files to map using the existing keyword argument silence_errors. (#7018)

  • Added a sunpy.coordinates.Helioprojective.is_visible() method to return whether the coordinate is visible (i.e., not obscured from the observer assuming that the Sun is an opaque sphere). (#7118)

  • Added a keyword option (quiet) for get_body_heliographic_stonyhurst() to silence the normal reporting of the light-travel-time correction when observer is specified. (#7142)

  • Added the function sunpy.coordinates.sun.eclipse_amount() to calculate the solar-eclipse amount for an observer. (#7142)

  • Add a keyword (map_center_longitude) to make_heliographic_header() for centering the heliographic map at a longitude other than zero longitude. (#7143)

  • The minimum required version of Glymur (an optional dependency for reading JPEG2000 files) has been increase to 0.9.1. (#7164)

  • Added new default colormap scalings for WISPR Maps. Plots are now clipped at zero, and AsinhStretch is used for the scaling to ensure coronal details are visible despite the much-brighter stars. Parsing of the detector and level fields of the FITS headers is also improved. (#7180)

  • When creating a coordinate or coordinate frame without specifying obstime, the obstime value from the observer frame attribute will be used if present. (#7186)

  • Added a GONG synoptic map class which fixes non-compliant FITS metadata (#7220)

  • Added the module sunpy.coordinates.spice to enable the use of the SkyCoord API to perform computations using SPICE kernels. (#7237)

  • Added three coordinate frames that depend on the orientation of Earth’s magnetic dipole: Geomagnetic (MAG), SolarMagnetic (SM), and GeocentricSolarMagnetospheric (GSM). (#7239)

Bug Fixes#
  • Fix RHESSI (RHESSIClient) fallback server detection. (#7092)

  • Fix bug in get_horizons_coord() when specifying a time range via a dictionary that could cause the returned times to be slightly different from the supplied times. (#7106)

  • Updated the url of the GBMClient to match on files other than those that end with version 0 (i.e., V0.pha). (#7148)

  • When directly instantiating a WCS from a FITS header that contains both Stonyhurst and Carrington heliographic coordinates for the observer location, the Stonyhurst coordinates will now be prioritized. This behavior is now consistent with the Map class, which has always prioritized Stonyhurst coordinates over Carrington coordinates. (#7188)

  • Fixed a bug with sample_at_coords() where sampling outside the bounds of the map would sometimes not error and instead return strange pixel values. (#7206)

  • Improved code when loading CDF files to improve performance and avoid raising of pandas performance warnings. (#7247)

  • Fixed a bug with sunpy.map.GenericMap.plot() where setting norm to None would result in an error. (#7261)

Documentation#
Deprecations#
Removals#
  • sunpy.map.extract_along_coord() has been removed. Instead, use pixelate_coord_path(), and then pass its output to sample_at_coords(). pixelate_coord_path uses a different line algorithm by default, but you can specify bresenham=True as an argument to use the same line algorithm as extract_along_coord. (#7200)

  • sunpy.visualisation.limb.draw_limb() has been removed. Use sunpy.visualization.drawing.limb() instead. (#7202)

  • Removed GenericTimeSeries.index. Use GenericTimeseries.time instead as a direct replacement. (#7203)

  • Removed the deprecated sunpy.io.cdf submodule, which is not intended to be user facing. (#7240)

  • Removed the deprecated sunpy.io.jp2, which is not intended to be user facing. (#7241)

  • Removed the deprecated sunpy.io.file_tools, which is not intended to be user facing. (#7242)

  • The deprecated sunpy.data.download_sample_data() has been removed Use sunpy.data.sample.download_all() instead. (#7250)

Internal Changes#
  • Removed the Binder configuration and link in README. This is because the configuration was untested, and does not currently work. (#7062)

  • Add a Dependabot config file to auto-update GitHub action versions. (#7068)

  • Add tests to check whether various Map methods preserve laziness when operating on Maps backed by a dask.array.Array. (#7100)

  • Added missing support to find GOES-18 XRS data in XRSClient. (#7108)

  • Raise an error with a helpful message when sunpy.map.GenericMap.plot() is called with a non-boolean value for the annotate keyword, because the user is probably trying to specify the axes. (#7163)

  • Fixed our ASDF manifest having the incorrect ID. (#7282)

  • Fix example formatting in a few asdf schemas. (#7292)

  • Pinned the drms requirement to < 0.7 to avoid breaking changes in drms version 0.7. (#7308)

5.0.0 (2023-06-14)#

Breaking Changes#
  • XRSClient now provides the re-processed GOES-XRS 8-15 data from NOAA. These files are now all NetCDF and not FITS files. (#6737)

  • Changed the output of sunpy.map.sample_at_coords() to return the sampled values as Quantity with the appropriate units instead of merely numbers. (#6882)

Deprecations#
  • Using sunpy.map.header_helper.meta_keywords is deprecated. Please see Meta Keywords for the list of metadata keywords used by Map. (#6743)

  • The utility function sunpy.map.extract_along_coord is deprecated. Use sunpy.map.pixelate_coord_path(), and then pass its output to sunpy.map.sample_at_coords(). (#6840)

  • Parsing SDO/EVE level 0CS average files is deprecated, and will be removed in sunpy 6.0. Parsing this data is untested, and we cannot find a file to test it with. If you know where level 0CS ‘averages’ files can be found, please get in touch at https://community.openastronomy.org/c/sunpy/5. (#6857)

  • Fully deprecated sunpy.database, with an expected removal version of sunpy 6.0. (#6869)

  • sunpy.io.cdf, sunpy.io.file_tools and sunpy.io.jp2 sub-modules have been deprecated, and will be removed in version 5.1. This because they are designed for internal use only, and removing it from the public API gives the developers more flexibility to modify it without impacting users. (#6895)

New Features#
  • A pure Python sunpy wheel is now published on PyPI with each release. pip will now default to installing the pure Python wheel instead of the source distribution on platforms other than Linux (x86-64) and macOS (x86-64 and ARM64). This should mean simpler and faster installs on such platforms, which includes the Raspberry Pi as well as some cloud computing services.

    This wheel does not contain the sunpy.io.ana compiled extension. If you need this extension (not available on Windows) you can install the sunpy source distribution with pip install --no-binary sunpy "sunpy[all]". (#6175)

  • Added three tutorials which replicate CompositeMap functionality (Overlaying Two Maps, Combining off-limb and disk maps, Creating a Composite Plot with Three Maps). (#6459)

  • exposure_time now looks for the exposure time in the XPOSURE key first and then the EXPTIME key. (#6557)

  • make_fitswcs_header now includes the keyword argument detector for setting the DETECTOR FITS keyword in the resulting header. (#6558)

  • Adds two tutorials that demonstrate how to use LASCO data in overlaying maps (Overlay an AIA image on a LASCO C2 coronagraph) and how to create a custom mask for a LASCO C2 image (Creating a mask for LASCO C2 data). (#6576)

  • Able to run the sunpy tests doing python -m sunpy.tests.self_test. (#6600)

  • Able to detect gzip-compressed FITS files even if they don’t have the .gz extension in the filename. detect_filetype now looks for the right file signature while checking for gzipped FITS files. (#6693)

  • Added AttrAnd and AttrOr to the namespace in sunpy.net.attrs. This allows users to to avoid | or & when creating a query a larger query. (#6708)

  • SUVIClient now provides GOES-18 SUVI data. (#6737)

  • The minimum required versions of several core dependencies have been updated:

    • Python 3.9

    • astropy 5.0.1

    • numpy 1.21.0

    The minimum required versions of these optional dependencies has also been updated:

    • Matplotlib 3.5.0

    • dask 2021.4.0

    • pandas 1.2.0

    • scikit-image 0.18.0

    • scipy 1.7.0 (#6742)

  • Added the utility function sunpy.map.pixelate_coord_path() to fully pixelate a coordinate path according to the pixels of a given map. (#6840)

  • The minimum version of h5netcdf required by sunpy has been bumped to version 0.11.0. (#6859)

  • Able to download files from REST/TAP Data Providers from the VSO. (#6887)

  • Adding data unit into html repr for sunpy.map.Map (#6902)

  • Joined HISTORY keys with newline characters when parsing HISTORY cards from FITS header. (#6911)

  • Added the ability to query for the GOES-XRS 1 minute average data with the XRSClient. (#6925)

  • Increased minimum version of parfive to 2.0.0.

    We are aware the change in the parfive minimum version is a release earlier than our dependency policy allows for. However, due to significant issues that parfive v2.0.0 solves and changes to remote servers, we have decided to increase it to improve the user experience when downloading files. (#6942)

Bug Fixes#
  • Fixed the incorrect calculation in make_fitswcs_header() of the rotation matrix from a rotation angle when the pixels are non-square. (#6597)

  • Return code from self_test is now non-zero if it stops due to missing dependencies. (#6600)

  • Fixed an issue with loading old EIT fits files with sunpy.map.Map where the date could not be parsed. (#6605)

  • Fixed a bug where the exposure_time returned None when the exposure time key was set to zero. (#6637)

  • Fixed a bug that prevented specifying a BaseCoordinateFrame (as opposed to a SkyCoord) to sunpy.map.GenericMap.draw_quadrangle(). (#6648)

  • HMI JPEG2000 files from Helioviewer could not be loaded due to a bug in setting the plotting normalization. This has been fixed. (#6710)

  • The data_manager was not raising failed downloads correctly and would continue as if the file existed locally. Now it will raise any errors from parfive. (#6711)

  • XRTMap will now set the unit for XRT files if the BUNIT key is missing. (#6725)

  • XRSClient update use the new url for which the GOES-XRS 8-15 data is provided by NOAA. (#6737)

  • Updated sunpy.database to be compatible with SQLAlchemy versions >=2.0 (#6749)

  • When using autoalign=True when plotting maps, the result was misaligned by half a pixel. (#6796)

  • sunpy.map.GenericMap.submap() can now handle a BaseCoordinateFrame as input. (#6820)

  • Multi-line HISTORY and COMMENT keys metadata dictionaries are now correctly split into multiple history and comment cards when writing a FITS file. (#6911)

  • Pass in “max_splits” to Parfive to prevent multi connections to JSOC for JSOC only queries. (#6921)

  • When converting an astropy.wcs.WCS object to a solar coordinate frame the DATE-AVG key will be used before the DATE-OBS key, previously only DATE-OBS was checked. (#6995)

  • sunpy.map.GenericMap.rotation_matrix now applies the default values if any FITS rotation matrix keywords are missing from the header. (#7004)

  • Modified sunpy.io.special.srs.read_srs() to correctly handle uppercase SRS files and supplementary sections occurring after the main data sections (I, IA, II). (#7035)

Documentation#
Internal Changes#
  • When determining which VSO servers to use for queries, VSOClient will now attempt to check if the cgi endpoint referenced by the WDSL file is accessible, and try the next endpoint if it can’t be reached. This should mean that a small category of connection issues with the VSO are now automatically bypassed. (#6362)

4.1.0 (2022-11-11)#

Breaking Changes#
  • Updated the sample data file, AIA_171_ROLL_IMAGE to be rice compressed instead of gzip compressed. This means that the data is now stored in the second HDU. (#6221)

Deprecations#
  • Passing positional arguments to all timeseries peek() methods is now deprecated, and will raise an error in sunpy 5.1. Pass the arguments with keywords (e.g. title='my plot title') instead. (#6310)

  • Using sunpy.timeseries.GenericTimeSeries.index` is deprecated. Use time to get an astropy Time object, or ts.to_dataframe().index to get the times as a pandas DataTimeIndex. (#6327)

  • Deprecated the sunpy.visualization.limb module. The sunpy.visualization.limb.draw_limb function has been moved into drawing as limb(). (#6332)

  • The sunpy.net.helioviewer module is deprecated and will be removed in version 5.1. The Helioviewer Project now maintains a replacement Python library called hvpy. As such, in consultation with the Helioviewer Project, we have decided to deprecate the HelioviewerClient class. (#6404)

  • Passing the algorithm, return_footprint arguments as positional arguments is deprecated. Pass them as keyword arguments (e.g. ..., return_footprint=True, ...) instead. (#6406)

  • sunpy.data.download_sample_data() is now deprecated. Use sunpy.data.sample.download_all() instead. (#6426)

  • The sunpy.database module is no longer actively maintained and has a number of outstanding issues. It is anticipated that sunpy.database will be formally deprecated in sunpy 5.0 and removed in sunpy 6.0. If you are using sunpy.database and would like to see a replacement, please join the discussion thread at https://community.openastronomy.org/t/deprecating-sunpy-database/495. (#6498)

Removals#
New Features#
  • Updated and expanded the HTML representation for TimeSeries. (#5951)

  • When reading CDF files, any columns with a floating point data type now have their masked values converted to NaN. (#5956)

  • Add support for saving GenericMap as JPEG 2000 files. (#6153)

  • Add a function sunpy.map.extract_along_coord that, for a given set of coordinates, finds each array index that crosses the line traced by those coordinates and returns the value of the data array of a given map at those array indices. (#6189)

  • Three new maps have been added to the sample data from STEREO A and STEREO B at 195 Angstrom, and AIA at 193 Angstrom. These images are from a time when the three spacecraft were equally spaced around the Sun, and therefore form near complete instantaneous coverage of the solar surface.

    Users upgrading to this version will find this three files download when they use the sample data for the first time. (#6197)

  • Added a SDO/AIA 1600 file of the Venus transit to the sunpy sample data. (#6242)

  • Created the sunpy.visualization.drawing module which includes new equator() and prime_meridian() functions. (#6251)

  • Expose GOES quality flags in order to allow filtering corrupt values when using the XRSTimeSeries. (#6260)

  • All TimeSeries plotting methods now consistently set the same formatter and locator for the x-axis. (#6264)

  • sunpy.timeseries.GenericTimeSeries.peek() now takes a title argument to set the title of the plot. (#6304)

  • Added the sunpy.timeseries.GenericTimeSeries.time property to get the times of a timeseries as a Time object. (#6327)

  • Added the Plotting the solar equator and prime meridian example to the Example Gallery. (#6332)

  • Added a new function sunpy.map.header_helper.make_heliographic_header() to help with generating FITS-WCS headers in Carrington or Stonyhurst coordinate systems that span the entire solar surface. (#6415)

  • Sample data files provided through sunpy.data.sample are now downloaded individually on demand rather than being all downloaded upon import of that module. To download all sample data files, call sunpy.data.sample.download_all(). (#6426)

  • XRSTimeSeries is now able to parse the primary detector information from the GOES-R XRS data if available. (#6454)

  • sunpy.net.Scraper now includes treats files as spanning a full interval equal to the smallest increment specified in the file pattern. For example, a pattern like "%Y.txt" that only contains a year specifier will be considered to span that full year.

    This means searches that fall entirely within the whole interval spanned by a pattern will return that file, where previously they did not. As an example, matching "%Y.txt" with TimeRange('2022-02-01', '2022-04-01') will now return ["2022.txt"] where previously no files were returned. (#6472)

  • Implemented site configuration for sunpyrc, and modified documentation for sunpy customization. (#6478)

  • make_fitswcs_header() now includes the keyword argument unit for setting the BUNIT FITS keyword in the resulting header. This will take precedence over any unit information attached to data. (#6499)

  • If the data argument to make_fitswcs_header() is an Quantity, the associated unit will be used to set the BUNIT FITS keyword in the resulting header. (#6499)

  • Added a 304 sample data file called AIA_304_IMAGE. (#6546)

Bug Fixes#
  • Fix a bug that prevented EUI maps with missing wavelength metadata loading. (#6199)

  • The sunpy.net.dataretriever.sources.noaa.SRSClient was not correctly setting the passive mode for FTP connection resulting in a permission error. This has been fixed. (#6256)

  • Fixed XRSTimeSeries inability to read leap-second files for GOES. It floors the leap-second timestamp to be 59.999, so that Python datetime does not raise an exception. (#6262)

  • Changed the default scaling for EUIMap from a linear stretch to a asinh stretch.

    To revert to the previous linear stretch do the following:

    from astropy.visualization import ImageNormalize, LinearStretch
    euimap.plot_settings["norm"] = ImageNormalize(stretch=LinearStretch()) (`#6285 <https://github.com/sunpy/sunpy/pull/6285>`__)
    
  • Fixed bugs when working with a coordinate frame where the observer is specified in HeliographicStonyhurst with a Cartesian representation, which is equivalent to Heliocentric Earth Equatorial (HEEQ). Now, the observer will always be converted to spherical representation when the coordinate frame is created. (#6311)

  • Fixed an error when Fido returns zero results from the VSO and some results from at least one other data source. This (now fixed) error is only present when using numpy version >= 1.23. (#6318)

  • If a level 1 XRT file does not specify the heliographic longitude of the spacecraft, a silent assumption is made that the spacecraft is at zero Stonyhurst heliographic longitude (i.e., the same longitude as Earth). (#6333)

  • The sample data retry was failing under parfive 2.0.0. (#6334)

  • Fixed bug that prevented RotatedSunFrame instances from being pickled. (#6342)

  • Fix a bug in loading XRSTimeSeries due to unsupported quality flag column names. (#6410)

  • Adds units (dimensionless units) to the quality columns in XRSTimeSeries. (#6423)

  • Refactored SXTMap to use ITRS observer coordinate information in header rather than incorrect HGS keywords. The SXTMap also now uses the default dsun property as this information can be derived from the (now corrected) observer coordinate. (#6436)

  • In sunpy.map.GenericMap.coordinate_system and sunpy.map.GenericMap.date, the default values will now be used if the expected key(s) used to derive those properties are empty. Previously, empty values of these keys were not treated as missing and thus the default values were not correctly filled in. (#6436)

  • Fixed a bug where the observer coordinate was incorrectly determined for KCorMap. (#6447)

  • Trying to download an empty search response from the JSOC now results in an empty results object. Previously the results object contained the path to the sunpy download directory. (#6449)

  • Removed an error when searching CDAWEB using sunpy.net.Fido and no results are returned. An empty response table is now returned. (#6450)

  • Fix a bug to parse the GOES “observatory” number in XRSTimeSeries for GOES 13, 14, 15 and for the 1 minute GOES-R data. (#6451)

  • Changed the default scaling for XRTMap from a linear stretch to LogStretch.

    To revert to the previous linear stretch do the following:

    from astropy.visualization import ImageNormalize, LinearStretch
    xrtmap.plot_settings["norm"] = ImageNormalize(stretch=LinearStretch()) (`#6480 <https://github.com/sunpy/sunpy/pull/6480>`__)
    
  • Fix the detector property of SOTMap to return “SOT”. (#6480)

  • The right-hand y-axis of the GOES-XRS timeseries plots with labelled flare classes now automatically scales with the left-hand y-axis. (#6486)

  • Add support for Python 3.11.

    The deprecated cgi.parse_header is now available as sunpy.util.net.parse_header. (#6512)

  • Fixed the metadata handling of resample() and superpixel() so that the CDELTi values are scaled and the PCi_j matrix (if used) is modified in the correct manner for asymmetric scaling. The previous approach of having the PCi_j matrix store all of the scaling resulted in non-intuitive behaviors when accessing the scale and rotation_matrix properties, and when de-rotating a map via rotate(). (#6571)

  • Fixd a bug with the sunpy.map.GenericMap.scale property for maps containing only the CDij matrix where the scale was not being determined from the CDij matrix. (#6573)

  • Fixed a bug with the sunpy.map.GenericMap.rotation_matrix property for maps using the CDij matrix formulism where the rotation matrix would be calculated incorrectly for non-square pixels. (#6573)

  • Fixed a bug where parse_time() would always disregard the remainder of a time string starting with the final period if it was followed by only zeros, which could affect the parsing of the time string. (#6581)

Documentation#
  • Improved annotations in the SRS active regions plotting example. (#6196)

  • Updated gallery examples that use STEREO data to use sample data instead of searching for and downloading data via Fido. (#6197)

  • Added the current bugfix release policy to the docs. (#6336)

  • The Maps and Timeseries have been reviewed and updated. (#6345)

  • Adds a pull request check list to the Developer’s Guide. (#6346)

  • Improved the plotting guide. (#6430)

  • Slight improvements to the downloading data with Fido part of the guide. (#6444)

  • Split the units and coordinate guides on to separate pages, and made minor improvements to them. (#6462)

  • Added a how-to guide conda_for_dependencies for using conda to set up an environment with the complete set of dependencies to use all optional features, build the documentation, and/or run the full test suite. The guide also describes how best to have an editable installation of sunpy in this environment. (#6524)

Internal Changes#
  • Added a columns keyword to each plot method for all sunpy.timeseries.GenericTimeSeries sources. (#6056)

  • Added a script in the sunpy/tools that will update all the Python libraries in sunpy/extern. (#6127)

  • Added automatic conversion of unit strings in CDF files to astropy unit objects for the following instruments: PSP/ISOIS, SOHO/CELIAS, SOHO/COSTEP-EPHIN, and SOHO/ERNE. (#6159)

  • Add an environment variable SUNPY_NO_BUILD_ANA_EXTENSION which when present will cause sunpy to not compile the ANA C extension when building from source. (#6166)

  • sunpy now uses the Limited Python API. Therefore, one binary distribution (wheel) per platform is now published and it is compatible with all Python versions sunpy supports. (#6171)

  • Add support for upcoming parfive 2.0 release. (#6243)

  • The primary sample-data URL will be changing from https://github.com/sunpy/sample-data/raw/master/sunpy/v1/ to https://github.com/sunpy/data/raw/main/sunpy/v1/. We expect GitHub to redirect from the old URL for sometime but will eventually expire it. The data.sunpy.org mirror will continue to be available. (#6289)

  • Add support for downloading sample data from more than two mirror locations. (#6295)

  • Timeseries data sources can now set the _peek_title class attribute to set the default plot title produced when .peek() is called and the user does not provide a custom title. (#6304)

  • All internal code for limb drawing now uses limb(). (#6332)

  • Add maintainer documentation on the backport bot (#6355)

  • Switched to using the standard matrix-multiplication operator (available in Python 3.5+) instead of a custom function. (#6376)

  • Fixed a colormap deprecation warning when importing the sunpy colormaps with Matplotlib 3.6. (#6379)

  • Removed custom tick label rotation from Lyra, EVE, and Norh timeseries sources, and grid drawing from NOAA and RHESSI sources. (#6385)

  • Added tests and test data for SXTMap (#6436)

  • Fixed a bug where the private attribute _default_observer_coordinate for GenericMap was being used even when there was sufficient observer metadata in the header. (#6447)

  • Tidy the GOES XRSTimesSeries tests and add two new XRS files to test. (#6460)

  • Added a pre-commit hook for codespell, and applied spelling fixes throughout the package. (#6574)

v4.0.0 (2022-05-06)#

Breaking Changes#
Deprecations#
  • Deprecate sunpy.image.coalignment as the code has now been moved to sunkit_image.coalignment with an identical API. This module will be removed in sunpy 4.1. (#5957)

  • The sunpy.map.GenericMap.shift method has been renamed to sunpy.map.GenericMap.shift_reference_coord and shift has been deprecated. (#5977)

  • The sunpy.map.GenericMap.shifted_value property has been deprecated. Modifications to the reference coordinate can be found in the CRVAL1 and CRVAL2 keys of sunpy.map.GenericMap.meta.modified_items. (#5977)

  • The sunpy.io.fits module is deprecated, as it was designed for internal use only. Use the astropy.io.fits module instead for more generic functionality to read FITS files. (#5983)

  • sunpy.physics.solar_rotation.mapsequence_solar_derotate is deprecated and will be removed in version 4.1. This function has been moved to sunkit_image.coalignment.mapsequence_coalign_by_rotation and has an identical API and functionality. (#6031)

  • sunpy.physics.solar_rotation.calculate_solar_rotate_shift is deprecated and will be removed in version 4.1. This function has been moved to sunkit_image.coalignment.calculate_solar_rotate_shift and has an identical API and functionality. (#6031)

  • Deprecated using sunpy.map.GenericMap.draw_limb on an Axes that is not a WCSAxes. (#6079)

New Features#
Bug Fixes#
  • Fixed reading CDF files when a column has no entries. If this is the case the column will be ignored, and a message logged at DEBUG level. (#5664)

  • Fixed the units of sunpy.map.sources.HMISynopticMap.scale and sunpy.map.sources.MDISynopticMap.scale. (#5682)

  • Fixed a bug where custom values in the plot_settings dictionary were not being propagated to new map instances created when calling map methods (e.g. .submap). (#5687)

  • Added automatic conversion of some common but non-standard unit strings in CDF files to astropy unit objects. If sunpy does not recognise the unit string for a particular column, units of u.dimensionless_unscaled are applied to that column and a warning raised.

    If you think a given unit should not be dimensionless and support should be added for it in sunpy, please raise an issue at sunpy/sunpy#issues. (#5692)

  • The default id_type in sunpy.coordinates.get_horizons_coord() is now None to match the default id_type in astroquery 0.4.4, which will search major bodies first, and if no major bodies are found, then search small bodies. For older versions of astroquery the default id_type used by get_horizons_coord() is still 'majorbody'. (#5707)

  • In consultation with JSOC, we now limit all JSOC downloads to one connection. This will override all connection user settings passed to the downloader. (#5714)

  • Updated the plot methods on some timeseries classes to correctly label and format the time axis. (#5720)

  • Fixed a long-standing bug where our logger could intercept Astropy warnings in addition to SunPy warnings, and thus could conflict with Astropy’s logger. (#5722)

  • Update asdf schemas so that references use URIs not tags as this is not supported by the new asdf extensions API. (#5723)

  • Increased the default maximum amount of records returned from HEC to 500 from 10. If the maximum number of records are returned, a message is shown. (#5738)

  • Reading a series of CDF files where at least one of them is empty no longer raises an error. A message for each empty file is logged at the DEBUG level. (#5751)

  • sunpy.map.header_helper.make_fitswcs_header() now includes a PC_ij matrix in the returned header if no rotation is specified. (#5763)

  • In the case where a map header has no PC_ij values, CROTA2 != 0, and CDELT1 != CDELT2, the calculation of the map rotation matrix has been fixed. This bug only affected maps with non-zero rotation, no PC matrix in the header, and un-equal scales along the two image axes. (#5766)

  • Maps created from resample() and superpixel() have been fixed in the case where the resampling was not square, and the PCi_j matrix (often a rotation matrix) was not a multiple of the identity matrix. When the PCi_j or CDi_j formalisms are used in the metadata these are now correctly modified, and the CDELT values are left unchanged. (#5786)

  • The __repr__ of several sunpy.database classes have been updated to remove angular brackets and add equals signs. As an example, '<DatabaseEntry(id 3)>' has changed to 'DatabaseEntry(id=3)' (#5790)

  • Fixed a bug when rotating a map by a matrix that is not purely a rotation. The likely way to inadvertently encounter this bug was when de-rotating a map with rectangular pixels that were not aligned with the coordinate axes. (#5803)

  • Fixed a bug where rotating a map while simultaneously scaling it could result in some of the map data being cropped out. (#5803)

  • Symmetric colorbar limits are no longer set on intensity images from MDI. (#5825)

  • Fixed plotting and peeking NORH timeseries data with pandas 1.4.0. (#5830)

  • In the case where sunpy.database.Database.fetch() successfully downloads only some of the search results, a sunpy.database.PartialFetchError is raised. This fixes a bug where the successful downloads would have been added to the database, but sometimes with incorrect metadata. (#5835)

  • When getting IRIS files from the VSO, Fido was incorrectly labelling them as XML files. (#5868)

  • HMIMap now looks for 'INSTRUME' instead of 'TELESCOP' in order to support Helioviewer JPEG2000 versions of HMI data which do not preserve the 'TELESCOP' keyword as expected in the JSOC standard. (#5886)

  • Fixes a bug where the cmap and norm keyword arguments were ignored when calling plot. (#5889)

  • Fix parsing of the GOES/XRS netcdf files to ignore leap seconds. (#5915)

  • Fixed compatibility with h5netcdf>0.14 when loading GOES netcdf files. (#5920)

  • Fixed bugs with the rebinning and per-keV calculation for Fermi/GBM summary lightcurves (GBMSummaryTimeSeries). (#5943)

  • Fixed the unintentionally slow parsing of Fermi/GBM files (GBMSummaryTimeSeries). (#5943)

  • Fixes a bug in SJIMap where undefined variable was used when parsing the wavelength. Also fixes the unit parsing by removing the “corrected” string from the BUNIT keyword as “corrected DN” cannot be parsed as a valid FITS unit. (#5968)

  • Fixed unit handling issue with GenericMap and lowercasing the unit before it submits it to astropy.units. (#5970)

  • Fixed reading CDF files when a variable has more than 2 dimensions. If this is the case the variable will be ignored, and a user warning is provided. (#5975)

  • Fixed sunpy.system_info so it returns the extra group when an optional dependency is missing. (#6011)

  • Relax condition check for a HMI Synoptic map source. (#6018)

  • VSOClient was not passing **kwargs through each download method. (#6052)

  • Fixed the inability to rotate images and maps with byte ordering that is different from the native byte order of the system (e.g., big-endian values on a little-endian system) for certain interpolation orders when internally using scikit-image. (#6064)

  • Fixed a crash for dask arrays when displaying the GenericMap html representation. (#6088)

  • Constructing the color map name for a KCorMap no longer requires the “detector” key in the metadata. This allows for reading files that are missing this keyword, as in the KCor JPEG2000 files. (#6112)

  • We now correctly pass keyword arguments in our internal FITS reader to astropy.io.fits.open. (#6123)

Documentation#
  • Fixed various plotting issues with the gallery example Drawing the AIA limb on a STEREO EUVI image. (#5534)

  • Improved the gallery example AIA to STEREO coordinate conversion to better illustrate how coordinate transformations interact with submaps and coordinate plotting. (#5534)

  • Tidy the API Reference section of the documentation and improve the landing page for the docs. (#5623)

  • Add info about loading CDF files to the API documentation. (#5735)

  • Added a known issues entry about scikit-image package version pinning. (#5865)

  • Edited entries in the example gallery to have a consistent plotting style. Added said style guidelines to the example gallery page in the dev guide. (#5870)

  • Added the gallery example Reprojecting to a Map Projection with a Custom Origin, which specifically showcases the azimuthal equidistant projection (also known as the Postel projection). (#5961)

  • Remove the part of the SJIMap docstring that says it only works on L1 as the data work for L2 and the level checking was not being enforced. (#5968)

  • Updated the timeseries documentation to make it clear that you can pass in a numpy array. (#6024)

Internal Changes#
  • Sped up the parsing of results from the VSO. For large queries this significantly reduces the time needed to perform a query to the VSO. (#5681)

  • sunpy.map.GenericMap.wcs now checks that the scale property has the correct units whilst constructing the WCS. (#5682)

  • Added packaging as a core dependency as distutils is now deprecated. (#5713)

  • SunpyWarning is no longer a subclass of AstropyWarning. (#5722)

  • Running the tests now requires the pytest-xdist package. By default tests are not run in parallel, but can be configured to do so using pytest-xdist command line options. (#5827)

  • Migrate the asdf infrastructure to the new style converters etc added in asdf 2.8.0. This makes sure sunpy will be compatible with the upcoming asdf 3.0 release. (#6057)

  • Declare in our dependencies that we are not compatible with asdf 3.0.0 until we are. (#6077)

  • Improved performance of the code that parses dates in clients that use the Scraper to get available files. (#6101)

3.1.0 (2021-10-29)#

Breaking Changes#
  • sunpy.timeseries.sources.NOAAIndicesTimeSeries.peek() accepts plot_type as an argument instead of type. (#5200)

  • Fill values are now set to numpy.nan in sunpy.timeseries.sources.noaa file parsers. They were previously set to a fill value of -1. (#5363)

  • sunpy.map.GenericMap.date now looks for more metadata than just DATE-OBS, using new FITS keywords defined in version 4 of the standard. sunpy.map.GenericMap.date now returns, in order of preference:

    1. The DATE-OBS FITS keyword

    2. date_average

    3. date_start

    4. date_end

    5. The current time.

    If DATE-OBS is present alongside DATE-AVG or DATE-BEG and DATE-END, this results in a behaviour change to favour the new (more precisely defined) keywords. It is recommended to use date_average, date_start, or date_end instead if you need one of these specific times. (#5449)

  • sunpy.io.fits.get_header no longer automatically tries to add the WAVEUNIT keyword if it isn’t present in the header. To replicate the original behaviour do:

    header = sunpy.io.fits.get_header(...)
    waveunit = sunpy.io.fits.extract_waveunit(header)
    if waveunit is not None:
        header['WAVEUNIT'] = waveunit
    

    The sunpy.map.GenericMap.waveunit property still uses sunpy.io.fits.extract_waveunit` to try and get the waveunit if the WAVEUNIT key isn’t present. (#5501)

  • sunpy.map.GenericMap.wcs no longer passes the whole .meta dictionary to astropy.wcs.WCS when constructing .wcs. Instead each metadata value is manually taken from various map properties, which allows fixes to be made to the WCS without modifying the original map header. We think that wcs correctly sets all the keys needed for a full WCS header, but if you find anything missing please open an issue on the sunpy issue tracker. (#5501)

Deprecations#
  • sunpy.util.scraper.Scraper has been moved into sunpy.net, please update your imports to be from sunpy.net import Scraper. (#5364)

  • Using “neighbour” as a resampling method in sunpy.image.resample.resample() is deprecated. Use “nearest” instead, which has the same effect. (#5480)

  • The sunpy.visualization.animator subpackage has been spun out into the standalone mpl-animators package, with the exception of MapSequenceAnimator. Please update your imports to replace sunpy.visualization.animator with mpl_animators.

    This is primarily because the ndcube package now relies on the animator classes as well as sunpy. (#5619)

Removals#
  • The deprecated sunpy.roi.chaincode.Chaincode has been removed in favour of sunpy.net.helio.Chaincode. (#5304)

  • The deprecated sunpy.roi.roi was removed, there is no direct replacement but astropy-regions is something to consider. (#5304)

  • The deprecated sunpy.instr has been removed, please use sunkit_instruments. (#5304)

  • The deprecated sunpy.map.GenericMap.size has been removed, please use sunpy.map.GenericMap.data.size. (#5304)

  • The deprecated ability to read txt files from sunpy.timeseries.sources.noaa.NOAAIndicesTimeSeries and sunpy.timeseries.sources.noaa.NOAAPredictIndicesTimeSeries has been removed as the data provided by NOAA is now provided as JSON files. (#5304)

  • Removed various deprecated methods on our Fido clients and responses:

    1. UnifiedResponse.build_table, UnifiedResponse.tables, UnifiedResponse.responses, UnifiedResponse.get_response and UnifiedResponse.blocks as UnifiedResponse is now an astropy.table.Table that is sliceable.

    2. UnifiedResponse.response_block_properties as UnifiedResponse.path_format_keys was added as a better replacement.

    3. HECClient.time_query as you can now use Fido.search directly.

    4. sunpy.net.jsoc.attrs.Keys was not used for querying JSOC.

    5. sunpy.net.jsoc.JSOCClient.search_metadata as the functionality this provided was merged into sunpy.net.jsoc.JSOCClient.search.

    6. sunpy.net.vso.VSOClient.link as better search support in the client replaces this method. (#5304)

  • The deprecated sunpy.map.GenericMap.draw_rectangle() has been removed, the replacement is sunpy.map.GenericMap.draw_quadrangle() (#5304)

  • sunpy now errors if the unused .rsun or .heliographic_observer attributes are set on a WCS. (#5348)

  • Support for passing non-unit levels to sunpy.map.GenericMap.draw_contours() when map data has units set has been removed, and with now raise an error. (#5352)

  • The origin argument to sunpy.map.GenericMap.world_to_pixel() and sunpy.map.GenericMap.pixel_to_world() has been removed. (#5353)

  • Support for plotting or contouring GenericMap on axes that are not WCSAxes has been removed. To create a WCSAxes, use the projection argument when the axes is created, e.g. fig.add_subplot(111, projection=my_map). (#5354)

  • The following search attributes in sunpy.net.vso.attrs have been removed: ['Time', 'Instrument', 'Wavelength', 'Source', 'Provider', 'Level', 'Sample', 'Detector', 'Resolution', 'Physobs']. Use the equivalent attribute from sunpy.net.attrs instead. (#5355)

  • The default response format from the VSO client is now a table. (#5355)

  • sunpy.net.hek.attrs.Time has been removed, use sunpy.net.attrs.Time instead. (#5355)

New Features#
Bug Fixes#
  • sunpy.map.GenericMap.superpixel() now keeps the reference coordinate of the WCS projection the same as the input map, and updates the reference pixel accordingly. This fixes inconsistencies in the input and output world coordinate systems when a non-linear projection is used. (#5295)

  • Inputs to the dimensions and offset arguments to sunpy.map.GenericMap.superpixel() in units other than u.pix (e.g. `u.kpix) are now handled correctly. (#5301)

  • Fractional inputs to the dimensions and offset arguments to sunpy.map.GenericMap.superpixel() were previously rounded using int in the superpixel algorithm, but not assigned integer values in the new metadata. This has now been changed so the rounding is correctly reflected in the metadata. (#5301)

  • Remove runtime use of astropy.tests.helper.assert_quantity_allclose which introduces a runtime dependency on pytest. (#5305)

  • sunpy.map.GenericMap.resample() now keeps the reference coordinate of the WCS projection the same as the input map, and updates the reference pixel accordingly. This fixes inconsistencies in the input and output world coordinate systems when a non-linear projection is used. (#5309)

  • Fix saving GenericMap to an asdf file with version 2.8.0 of the asdf package. (#5342)

  • When the limb is entirely visible, sunpy.map.GenericMap.draw_limb() no longer plots an invisible patch for the hidden part of the limb and now returns None instead of the invisible patch. Similarly, when the limb is entirely invisible, no patch is drawn for the visible part and None is returned instead of the visible patch. (#5414)

  • sunpy.map.GenericMap.plot() now correctly sets axis labels based on the coordinate system of the axes, and not the coordinate system of the map being plotted. This was previously only an issue if using autoalign=True when the Map coordinate system was different to the axes coordinate system. (#5432)

  • sunpy.map.GenericMap.plot() no longer adds a unit string to the axis labels if the axes being plotted on is a WCSAxes. For a WCSAxes, angular units are indicated in the tick labels, and automatically change when the zoom level changes from e.g. degrees to arc-minutes. This could previously lead to situations where the axis label units were incorrect. (#5432)

  • Implement automatic fallback to helioviewer mirrors if API is non-functional. (#5440)

  • Fixed the incorrect value for the FITS WCS LONPOLE keyword when using make_fitswcs_header() for certain combinations of WCS projection and reference coordinate. (#5448)

  • The date returned by date for Solar Orbiter/EUI maps has been adjusted to be taken from the DATE-AVG keyword (the middle of the image acquisition period), instead of the DATE-OBS keyword (the beginning of the image acquisition period). This means the observer coordinate now has the correct date. (#5462)

  • The .unit attribute for HMI synoptic maps has been fixed. (#5467)

  • When “TAI” is in the date string, sunpy.map.GenericMap.date now only raises a warning if the TIMESYS keyword is present and different to “TAI”. Previously a warning was raised all the time when “TAI” was in the date string. (#5468)

  • Fixed a bug where the property sunpy.map.GenericMap.rsun_meters would always internally determine the observer location, even when it is not needed, particularly for Stonyhurst heliographic maps, which have no notion of an observer. Thus, when working with a Stonyhurst heliographic map, a user could get an irrelevant warning message about having to assume an observer location (Earth center). (#5478)

  • Fixed the unintended insertion of (assumed) observer location information when accessing the property sunpy.map.GenericMap.wcs for Stonyhurst heliographic maps. (#5478)

  • Fixed an incorrect value for the FITS WCS LONPOLE keyword when using make_fitswcs_header() for Helioprojective maps with certain values of latitude for the reference coordinate. (#5490)

  • A non-standard CROTA keyword included in a sunpy.map.sources.EUIMap FITS header is now renamed to the recommended CROTA2 so a warning is no longer raised. (#5493)

  • The plotting x-limits of sunpy.timeseries.sources.NOAAIndicesTimeSeries.plot() are now adjusted to only include finite points in the timeseries data. (#5496)

  • The Hinode/XRT map source now corrects the TIMESYS keyword, fixing the .wcs property that was previously broken for Hinode/XRT maps. (#5508)

  • Updated sunpy.map.CompositeMap.plot to support the linestyles and colors arguments, in addition to the existing linewidths argument. (#5521)

  • Fixed a bug where rotating a Map could result in an extremely small shift (at the numerical-precision level) in the mapping from world coordinates to pixels. (#5553)

  • Fixed a bug where rotating a Map that is missing observation-time metadata could result in an incorrect reference coordinate. (#5553)

  • Fix a bug where saving a helioprojective or heliocentric coordinate to an asdf file didn’t work due to a schema version mismatch if the observer location was a fully specified Stonyhurst heliographic coordinate. (#5584)

  • XRTMap uppercases the TIMESYS key before checking if the key needs to be fixed. (#5592)

  • Fixed passing a URL to sunpy.io.read_file() on windows. (#5601)

  • Fixed a bug where the date property on HMISynopticMap returned None if the DATE-OBS key was present. (#5648)

Documentation#
Internal Changes#
  • The Helioprojective frame now has the convenience property angular_radius to return the angular radius of the Sun as seen by the observer. (#5191)

  • Online tests can now report back status of remote urls and will XFAIL if the remote server is unreachable. (#5233)

  • Re-enabled the unit test to check for coordinates consistency with JPL HORIZONS when the matching ephemeris can be specified. (#5314)

  • The TimeSeries factory has been refactored to improve readability and maintainability of the internal code. (#5411)

  • sunpy.map.GenericMap.rsun_obs no longer emits a warning if the metadata it looks for is not present. Instead the standard photospheric radius is assumed and a log message emitted at the ‘info’ level. (#5416)

  • Nearest-neighbour and linear (the default for sunpy.map.GenericMap.resample()) resampling have been significantly sped up. (#5476)

  • sunpy.map.Map now raises a clear error when the map is constructed if units of either two axes are not angular units. (#5602)

3.0.1 (2021-07-03)#

Bug Fixes#
  • Fixed a bug where GenericMap used to break with keyword arguments. (#5392)

  • Fixed a bug where calling sunpy.map.GenericMap.draw_contours() on a different WCS could result in an unnecessary expansion of the plot limits. (#5398)

  • Fixed incorrect return values from all_corner_coords_from_map() if a rectangular map was provided. (#5419)

  • Do not trigger a pytest import in the asdf plugin for saving sunpy coordinate frames. (#5429)

  • Constructing a 2D coordinate in the HeliographicCarrington frame with observer='self' now raises an error upon creation. When specifying observer='self', the radius coordinate component serves as the Sun-observer distance that is necessary to fully define the Carrington heliographic coordinates. (#5358)

  • Fixed two bugs with handling the motion of the Sun when transforming between coordinate frames with a change in obstime. These bugs did not affect any results if the context manager transform_with_sun_center() had been used. (#5381)

  • Fixed a bug where the rsun frame attribute could be unintentionally reset to the default value during transformation. This bug primarily affected the transformation of a Helioprojective coordinate to a HeliographicStonyhurst frame. (#5395)

  • Fixed a bug where creating a HeliographicStonyhurst frame or a HeliographicCarrington frame from WCS information failed to make use of any specified rsun_ref value. (#5395)

  • SXTMap now always returns None for the wavelength attribute. Previously this raised an error. (#5401)

Added/Improved Documentation#
  • Simplified the “Downloading LASCO C2” gallery example by removing redundant modifications to the metadata before it is loaded by Map. (#5402)

  • Tided up the HMI synoptic map example by removing redundant code and correcting some of the comments. (#5413)

3.0.0 (2021-05-14)#

Backwards Incompatible Changes#
Deprecations and Removals#
  • Deprecated sunpy.map.GenericMap.draw_rectangle in favor of draw_quadrangle(). (#5236)

  • Using GenericMap plotting methods on an Axes that is not a WCSAxes is deprecated. This previously raised a warning, but is now formally deprecated, and will raise an error in sunpy 3.1. (#5244)

  • Deprecated sunpy.roi.chaincode.Chaincode and created a replacement at sunpy.net.helio.Chaincode.

    This replacement has the following changes:

    1. Added support for numpy array as an input (it was broken before).

    2. Renamed BoundingBox to boundingbox

    3. Renamed subBoundingBox to sub_boundingbox

    4. Now area and length raise NotImplementedError (#5249)

  • Deprecated sunpy.roi.roi, as it currently has no obvious use and has never seen any real development work. (#5249)

Features#
  • sunpy.coordinates.get_horizons_coord() can now be given a start time, end time, and number of intervals (or interval length) to query a evenly spaced set of times. See the documentation string for more information and an example. (#4698)

  • Added sunpy.map.GenericMap.draw_quadrangle() for drawing a quadrangle on a map. A quadrangle has edges that are aligned with lines of constant latitude and longitude, but these can be in a different coordinate system than that of the map. (#4809)

  • Added a longitude keyword argument to carrington_rotation_time() as an alternate way to specify a fractional Carrington rotation. (#4879)

  • Colorbar in sunpy.map.GenericMap.peek now has a unit label. (#4930)

  • The default axes used by BaseFuncAnimator.get_animation() is now BaseFuncAnimator.axes, instead of the currently active axes (accessed via. matplotlib.pyplot.gca()). The allows animations to be created on figures created directly using matplotlib.figure.Figure.

    To revert to the previous behaviour of using the current axes, give axes=plt.gca() to get_animation(). (#4968)

  • Added colormaps for Solar Orbiter EUI images. These are used automatically when an EUI image is loaded. (#5023)

  • Added the ability to dynamically scale sunpy.visualization.animator instances. By specifying the clip_interval keyword, it will now clip the minimum and maximum at each slider step to the specified interval. (#5025)

  • Added a sunpy.time.timerange.TimeRange.__contains__ method to sunpy.time.TimeRange that tests if two time ranges overlap. (#5093)

  • Added the ability to namespace files downloaded using sunpy.data.data_manager.manager.DataManager by prepending the file name with module name. (#5111)

  • Added a rigid rotation model to diff_rot() via rot_type=rigid, where the rotation rate does not vary with latitude. (#5132)

  • Added a save() method to sunpy.map.MapSequence that saves each map of the sequence. (#5145)

  • The allowable level inputs to sunpy.map.GenericMap.contour() and sunpy.map.GenericMap.draw_contours() have been consolidated. Both methods now accept - Scalars, if the map has no units - Quantities, if the map has units - Percentages (#5154)

  • Added support for corrected NOAA SWPC solar region summary data files. (#5173)

  • Updated sunpy.util.sysinfo.system_info to return all optional dependencies of sunpy. (#5175)

  • sunpy.map.Map now supports the EUI instrument on Solar Orbiter. (#5210)

  • HeliographicStonyhurst and HeliographicCarrington now have an rsun frame attribute to specify the radius of the Sun, which defaults to the photospheric radius defined in sunpy.sun.constants. This frame attribute is used when converting a 2D coordinate (longitude and latitude, with no specified radial distance) to a 3D coordinate by setting the radial distance to rsun (i.e., the assumption is that the coordinate is on the surface of the Sun). (#5211)

  • Enhanced sunpy.map.GenericMap.draw_limb() so that the solar limb can be plotted on axes that correspond to a different map (e.g., with a different observer). The part of the limb that is not visible to the axes’s observer because it is on the far side of the Sun is shown as dotted rather than solid. (#5237)

  • MetaDict now saves a copy of the metadata on creation, which can be accessed using the original_meta property. Three new properties have also been added to query any changes that have been made to metadata:

    As an example, my_map.meta.modified_items will return a dictionary mapping keys to their original value and current value. (#5241)

  • Added sunpy.map.contains_coordinate() which provides a quick way to see if a world coordinate is contained within the array bounds of a map. (#5252)

  • Added an optional keyword argument autoalign to sunpy.map.GenericMap.plot() for plotting a map to axes that correspond to a different WCS. See Auto-Aligning AIA and HMI Data During Plotting. (#5255)

  • sunpy.map.CompositeMap.plot() now properly makes use of WCS information to position and orient maps when overlaying them. (#5255)

Bug Fixes#
  • Fixed the drawing methods of sunpy.map.GenericMap (e.g., ~sunpy.map.GenericMap.draw_rectangle) so that any text labels will appear in the legend. (#5019)

  • Fixed bug in sunpy.until.scraper.Scraper which caused URL patterns containing backslashes to be incorrectly parsed on Windows. (#5022)

  • Constructing a MetaDict is now more lenient, and accepts any class that inherits from collections.abc.Mapping. This fixes a regression where headers read with astropy.io.fits raised an error when passed to individual map sources. (#5047)

  • Added warning to sunpy.map.GenericMap.rotate() when specified missing value is not compatible with the number type of the data array. (#5051)

  • Prevented some colormaps being accidentally modified depending on the order and method through which they were accessed. (#5054)

  • Reverted change for sunpy.map.GenericMap.draw_limb that made it use “add_artist” as it was changing the FOV of the plotted image. (#5069)

  • Fixed a bug where some RotatedSunFrame transformations could fail with an observer=None error. (#5084)

  • Fixed bug where sunpy.data.data_manager.DataManager would fail to recover upon deleting the sqlite database file. (#5089)

  • Fixed a bug where coordinate frames were considered different due to an unintended time difference during time handling at the level of numerical precision (i.e., tens of picoseconds). This resulted in the unexpected use of transformation machinery when transforming a coordinate to its own coordinate frame. (#5127)

  • Fixed a bug with failing downloads in 2010 with the SRSClient. (#5159)

  • If the property sunpy.map.GenericMap.rsun_obs needs to calculate the solar angular radius from header information, it now properly uses the rsun_ref keyword if it is present and does not emit any warning. (#5172)

  • Added a “rsun_obs” keyword to the output of sunpy.map.header_helper.make_fitswcs_header() if the coordinate argument has a “rsun” frame attribute. (#5177)

  • Fixed small inaccuracies in the grid plotted by draw_grid() for maps that specify a radius of the Sun that is different from the constant in sunpy.sun.constants. (#5211)

  • Fixed sunpy.map.GenericMap.draw_contours() so that the contours from a map can be plotted on axes with a different coordinate system. (#5239)

  • When using the cylindrical representation of Heliocentric to work in the Heliocentric Radial coordinate frame, the psi component now goes from 0 to 360 degrees instead of -180 to 180 degrees. (#5242)

  • Changed MDIMap to use the “CONTENT” keyword to identify the measurement, similar to HMIMap, and removed the special-case nickname. This fixes the broken title on plots. (#5257)

  • sunpy.coordinates.solar_frame_to_wcs_mapping() now sets the observer auxiliary information when a HeliographicCarrington frame with observer='self' is passed. (#5264)

  • Calling sunpy.map.header_helper.make_fitswcs_header() with a HeliographicCarrington coordinate that with observer='self' set now correctly sets the observer information in the header. (#5264)

  • sunpy.map.GenericMap.superpixel() now keeps the reference coordinate of the WCS projection the same as the input map, and updates the reference pixel accordingly. This fixes inconsistencies in the input and output world coordinate systems when a non-linear projection is used. (#5295)

  • Inputs to the dimensions and offset arguments to sunpy.map.GenericMap.superpixel() in units other than u.pix (e.g. `u.kpix) are now handled correctly. (#5301)

  • Fractional inputs to the dimensions and offset arguments to sunpy.map.GenericMap.superpixel() were previously rounded using int in the superpixel algorithm, but not assigned integer values in the new metadata. This has now been changed so the rounding is correctly reflected in the metadata. (#5301)

  • Remove runtime use of astropy.tests.helper.assert_quantity_allclose which introduces a runtime dependency on pytest. (#5305)

  • sunpy.map.GenericMap.resample() now keeps the reference coordinate of the WCS projection the same as the input map, and updates the reference pixel accordingly. This fixes inconsistencies in the input and output world coordinate systems when a non-linear projection is used. (#5309)

  • Fix saving GenericMap to an asdf file with version 2.8.0 of the asdf package. (#5342)

Added/Improved Documentation#
Trivial/Internal Changes#
  • Replaced the old test runner with a new version that adds a dependency check before the test suite is run. (#4596)

  • The testing suite now raises a warning if the pyplot figure stack is not empty prior to running a test, and it closes all open figures after finishing each test. (#4969)

  • Improved performance when moving the slider in sunpy.visualisation.animator.ArrayAnimatorWCS. (#4971)

  • Added some basic logging to HEK searches, at the ‘debug’ logging level. (#5020)

  • Refactored RotatedSunFrame transformations for improved performance. (#5084)

  • Re-ordered keyword-only arguments of sunpy.map.GenericMap.draw_rectangle to match sunpy.map.GenericMap.submap(). (#5091)

  • Significantly sped up calls to parse_time() for string arguments. This will have knock on effects, including improved performance of querying the VSO. (#5108)

  • Added tests for sunpy.visualization.animator.mapsequenceanimator and sunpy.map.MapSequence.plot(). (#5125)

  • The CROTA keywords are no longer set on sunpy.map.GenericMap.wcs, as the PC_ij keywords are always set and the FITS standard says that these keywords must not co-exist. (#5166)

  • Temporarily disabled the unit test to check for coordinates consistency with JPL HORIZONS due to the inability to choose a matching ephemeris. (#5203)

  • wcsaxes_heliographic_overlay() now accepts obstime and rsun optional arguments. This function is not typically called directly by users. (#5211)

  • GenericMap plotting methods now have consistent argument checking for the axes argument, and will raise the same warnings or errors for similar axes input. (#5223)

  • Calling sunpy.map.GenericMap.plot() on a WCSAxes with a different World Coordinate System (WCS) to the map now raises a warning, as the map data axes may not correctly align with the coordinate axes. This happens if an Axes is created with a projection that is a different map to the one being plotted. (#5244)

  • Re-enabled the unit test to check for coordinates consistency with JPL HORIZONS when the matching ephemeris can be specified. (#5314)

2.1.0 (2020-02-21)#

Backwards Incompatible Changes#
  • Support for Python 3.6 and Numpy 1.15 has been dropped in line with NEP 29. The minimum supported version of Astropy is now 4.0, and the minimum version of scipy is now 1.2. (#4284)

  • Changed sunpy.coordinates.sun.B0() return type from Angle to Latitude. (#4323)

  • An error is now raised if vmin or vmax are passed to to sunpy.map.GenericMap.plot and they are already set on the map norm. This is consistent with upcoming Matplotlib changes. (#4328)

  • Previously slicing the result of Fido.search() (a UnifiedResponse object) so that it had a length of one returned another UnifiedResponse object. Now it will return a QueryResponseTable object, which is a subclass of astropy.table.Table. (#4358)

  • The .size property of a coordinate frame with no associated data will now raise an error instead of returning 0. (#4577)

  • The following Map methods have had support for specific positional arguments removed. They must now be passed as keyword arguments (i.e. m.method(keyword_arg=value)).

    • submap(): width, height.

    • sunpy.map.GenericMap.draw_rectangle: width, height, axes, top_right. (#4616)

  • The sunpy specific attributes .heliographic_observer and .rsun are no longer set on the WCS returned by sunpy.map.GenericMap.wcs. (#4620)

  • Due to upstream changes, the parsing logic for the HECClient now returns strings and not bytes for get_table_names(). (#4643)

  • Reduced the selection of dependent packages installed by default via pip, which means that some of our sub-packages will not fully import when sunpy is installed with pip install "sunpy". You can install all dependencies by specifying pip install "sunpy[all]", or you can install sub-package-specific dependencies by specifying, e.g., [map] or [timeseries]. (#4662)

  • The class inheritance for RotatedSunFrame and the frames it creates has been changed in order to stop depending on unsupported behavior in the underlying machinery. The return values for some isinstance()/issubclass() calls will be different, but the API for RotatedSunFrame is otherwise unchanged. (#4691)

  • Fix a bug in submap where only the top right and bottom left coordinates of the input rectangle in world coordinates were considered when calculating the pixel bounding box. All four corners are once again taken into account now, meaning that submap correctly returns the smallest pixel box which contains all four corners of the input rectangle.

    To revert to the previous 2.0.0 behaviour, first convert the top right and bottom left coordinates to pixel space before calling submap with:

    top_right = smap.wcs.world_to_pixel(top_right) * u.pix
    bottom_left = smap.wcs.world_to_pixel(bottom_left) * u.pix
    smap.submap(bottom_left=bottom_left, top_right=top_right)
    

    This will define the rectangle in pixel space. (#4727)

  • VSO results where the size was -1 (missing data) now return None rather than -1 to be consistent with other missing data in the VSO results. (#4798)

  • All result objects contained within the results of a Fido.search() (a UnifiedResponse object) are now QueryResponseTable objects (or subclasses thereof). These objects are subclasses of astropy.table.Table and can therefore be filtered and inspected as tabular objects, and the modified tables can be passed to Fido.fetch.

    This, while a breaking change for anyone accessing these response objects directly, will hopefully make working with Fido search results much easier. (#4798)

  • Results from the NOAAIndicesClient and the NOAAPredictClient no longer has Start Time or End Time in their results table as the results returned from the client are not dependent upon the time parameter of a search. (#4798)

  • The sunpy.net.vso.QueryResponse.search method has been removed as it has not worked since the 1.0 release of sunpy. (#4798)

  • The sunpy.net.hek.hek.HEKColumn class has been removed, the HEKTable class now uses the standard astropy.table.Column class. (#4798)

  • The keys used to format file paths in Fido.fetch have changed. They are now more standardised across all the clients, as they are all extracted from the names of the columns in the results table.

    For results from the VSO the keys are no longer separated with ., and are based on the displayed column names. For results from the dataretriever clients the only main change is that the keys are now lower case, where they were capitalized before. You can use the .sunpy.net.fido_factory.UnifiedResponse.path_format_keys method to see all the possible keys for a particular search. (#4798)

  • The time returned from carrington_rotation_number() has been changed from the TT scale to the more common UTC scale. To undo this change, use time_out = time_out.tt on the outputted time. (#4819)

  • response_block_properties has been renamed to .BaseQueryResponse.path_format_keys, on the return objects from all search() methods on all clients and from Fido.search(). (#4798)

Removals#
  • Removed deprecated functions:

    • sunpy.coordinates.frames.Helioprojective.calculate_distance, alternative is sunpy.coordinates.frames.Helioprojective.make_3d.

    • sunpy.image.coalignment.repair_image_nonfinite - if you wish to repair the image, this has to be done manually before calling the various sunpy.image.coalignment functions.

    • The repair_nonfinite keyword argument to calculate_shift and calculate_match_template_shift has been removed.

    • sunpy.instr.lyra.download_lytaf_database - this just downloaded the file at http://proba2.oma.be/lyra/data/lytaf/annotation_ppt.db, which can be done manually.

    • sunpy.util.net.check_download_file, no alternative.

    • sunpy.visualization.animator.ImageAnimatorWCS, alternative is sunpy.visualization.animator.ArrayAnimatorWCS. (#4350)

  • Removed deprecated function sunpy.instr.aia.aiaprep. Alternative is register for converting AIA images from level 1 to level 1.5. (#4485)

  • sunpy.cm has been removed. All of the functionality in this module can now be found in sunpy.visualization.colormaps. (#4488)

  • sunpy.test.hash has been removed, the functionality has been moved into the pytest-mpl package. (#4605)

  • sunpy.util.multimethod has been removed. (#4614)

  • The lytaf_path argument (which previously did nothing) has been removed from - sunpy.instr.lyra.remove_lytaf_events_from_timeseries - sunpy.instr.lyra.get_lytaf_events - sunpy.instr.lyra.get_lytaf_event_types (#4615)

Deprecations#
  • Deprecated sunpy.net.vso.attrs.Source and sunpy.net.vso.attrs.Provider. They are now sunpy.net.attrs.Source and sunpy.net.attrs.Provider respectively. (#4321)

  • Deprecated the use of the sunpy.map.GenericMap.size property, use sunpy.map.Map.data.size instead. (#4338)

  • sunpy.net.helio.HECClient.time_query is deprecated, search is the replacement. (#4358)

  • sunpy.net.jsoc.attrs.Keys is deprecated; all fields are returned by default and can be filtered post search. (#4358)

  • sunpy.net.hek.attrs.Time is deprecated; Time should be used instead. (#4358)

  • Support for sunpy.coordinates.wcs_utils.solar_wcs_frame_mapping() to use the .heliographic_observer and .rsun attributes on a WCS is deprecated. (#4620)

  • The origin argument to sunpy.map.GenericMap.pixel_to_world and sunpy.map.GenericMap.world_to_pixel is deprecated.

    • If passing 0, not using the origin argument will have the same effect.

    • If passing 1, manually subtract 1 pixel from the input to pixel_to_world, or manually add 1 pixel to the output of world_to_pixel, and do not use the origin argument. (#4700)

  • The .VSOClient.link method is deprecated as it is no longer used. (#4789)

  • The .UnifiedResponse.get_response, .UnifiedResponse.tables and .UnifiedResponse.responses attributes of .UnifiedResponse have been deprecated as they are no longer needed now the object returns the table objects it contains when sliced. (#4798)

  • sunpy.net.vso.VSOClient.search() has a new keyword argument response_type= which controls the return type from the search() method. In sunpy 2.1 and 3.0 it will default to the "legacy" response format, in 3.1 it will default to the new "table" response format, and the "legacy" format may be deprecated and removed at a later date.

    Searches made with Fido will use the new "table" response format, so this only affects users interacting with the VSOClient object directly. (#4798)

Features#
  • For sunpy.map.GenericMap.quicklook() and sunpy.map.MapSequence.quicklook() (also used for the HTML representation shown in Jupyter notebooks), the histogram is now shaded corresponding to the colormap of the plotted image. Clicking on the histogram will toggle an alternate version of the histogram. (#4931)

  • Add an SRS_TABLE file to the sample data, and use it in the magnetogram plotting example. (#4993)

  • Added a sunpy.map.GenericMap.contour() method to find the contours on a map. (#3909)

  • Added a context manager (assume_spherical_screen()) to interpret Helioprojective coordinates as being on the inside of a spherical screen instead of on the surface of the Sun. (#4003)

  • Added sunpy.map.sources.HMISynopticMap for handling the Synoptic maps from HMI. (#4053)

  • Added a MDISynopticMap map source class. (#4054)

  • Created GONGClient for accessing magnetogram synoptic map archives of NSO-GONG. (#4055)

  • All coordinate frames will now show the velocity if it exists in the underlying data. (#4102)

  • The ephemeris functions get_body_heliographic_stonyhurst(), get_earth(), and get_horizons_coord() can now optionally return the body’s velocity as part of the output coordinate. (#4102)

  • MetaDict now maintains coherence between its keys and their corresponding keycomments. Calling del on a MetaDict object key is now case-insensitive. (#4129)

  • Allow sunpy.visualization.animator.ArrayAnimatorWCS to disable ticks for a coordinate, by setting ticks: False in the coord_params dictionary. (#4270)

  • Added a show() method for BaseQueryResponse which returns Table with specified columns for the Query Response. (#4309)

  • Added _extract_files_meta method in sunpy.util.scraper.Scraper which allows scraper to extract metadata from the file URLs retrieved for a given time range. (#4313)

  • Refactoring of dataretriever which adds these capabilities to QueryResponse:

    • Any attr shall not be defaulted to a hard-coded value in all subclasses of GenericClient; thus records for all possible attrs shall be returned if it is not specified in the query.

    • QueryResponse can now show more columns; thus all metadata extractable from matching file URLs shall be shown and for a client, non-supported attrs shall not be shown in the response tables. (#4321)

  • New class attributes added to GenericClient:

    • baseurl and pattern which are required to define a new simple client.

    • optional and required which are a set of optional and required attrs respectively; which generalizes _can_handle_query(). (#4321)

  • Additions in sunpy.util.scraper to support the refactoring of GenericClient: - sunpy.util.scraper.Scraper.findDatewith_extractor that parses the url using extractor to return its start time. - A matcher in sunpy.util.scraper.Scraper._extract_files_meta which validates the extracted metadata by using the dictionary returned from _get_match_dict(). (#4321)

  • Added methods pre_search_hook() and post_search_hook() which helps to translate the attrs for scraper before and after the search respectively. (#4321)

  • sunpy.timeseries.sources.RHESSISummaryTimeSeries.peek has had the following minor changes:

    • Colors from the default matplotlib color cycle are now used (but the colors remain qualitatively the same)

    • The default matplotlib linewidth is now used

    • It is now possible to pass in a user specified linewidth

    • Seconds have been added to the x-axis labels (previously it was just hours and minutes) (#4326)

  • HECClient and HEKClient now inherit BaseClient which makes them compatible with the UnifiedDownloaderFactory (Fido). (#4358)

  • MaxRecords and TableName added as “attrs” for HELIO searches. (#4358)

  • Add the ability to download new GOES 16 & 17 data alongside the reprocessed GOES 13, 14 and 15 data via the GOES-XRS Fido client. (#4394)

  • sunpy.net.jsoc.JSOCClient.request_data now support additional parameter “method” which allows user to download staged data as single .tar file. (#4405)

  • Added sunpy.util.get_timerange_from_exdict which finds time range for a URL using its metadata. Added sunpy.util.scraper.Scraper.isvalid_time that checks whether the file corresponds to a desired time range. (#4419)

  • Colormap data has been moved to individual .csv files in the sunpy/visualization/colormaps/data directory. (#4433)

  • Added solar_angle_equivalency to convert between a physical distance on the Sun (e.g., km) to an angular separation as seen by an observer (e.g., arcsec). (#4443)

  • sunpy.map.Map instances now have their .unit attribute set from the 'BUNIT' FITS keyword. If the keyword cannot be parsed, or is not present the unit is set to None. (#4451)

  • The sunpy.map.GenericMap.wcs property is now cached, and will be recomputed only if changes are made to the map metadata. This improves performance of a number of places in the code base, and only one warning will now be raised about WCS fixes for a given set of metadata (as opposed to a warning each time .wcs is accessed) (#4467)

  • Extended concatenate() and concatenate() to allow iterables. (#4499)

  • Enable RotatedSunFrame to work with non-SunPy frames (e.g., HeliocentricMeanEcliptic). (#4577)

  • Add support for pathlib.Path objects to be passed to sunpy.timeseries.TimeSeries. (#4589)

  • Add support for GOES XRS netcdf files to be read as a sunpy.timeseries.sources.XRSTimeSeries. (#4592)

  • Add Cutout attr for requesting cutouts from JSOC via JSOCClient and Fido. (#4595)

  • sunpy now sets auxiliary parameters on sunpy.map.GenericMap.wcs using the astropy.wcs.Wcsprm.aux attribute. This stores observer information, along with the reference solar radius if present. (#4620)

  • The HeliographicCarrington frame now accepts the specification of observer='self' to indicate that the coordinate itself is also the observer for the coordinate frame. This functionality greatly simplifies working with locations of observatories that are provided in Carrington coordinates. (#4659)

  • Add two new colormaps (rhessi and std_gamma_2) that are used for plotting RHESSI maps. (#4665)

  • If either ‘CTYPE1’ or ‘CTYPE2’ are not present in map metadata, sunpy now assumes they are ‘HPLN-TAN’ and ‘HPLT-TAN’ (previously it assumed ‘HPLN- ‘ and ‘HPLT- ‘). In addition, a warning is also now raised when this assumption is made. (#4702)

  • Added a new all_corner_coords_from_map function to get the coordinates of all the pixel corners in a GenericMap. (#4776)

  • Added support for “%Y/%m/%dT%H:%M” to sunpy.time.parse_time(). (#4791)

  • Added the STEREO EUVI instrument specific colormaps called” ‘euvi171’, ‘euvi195’, ‘euvi284’, ‘euvi304’. (#4822)

Bug Fixes#
  • sunpy.map.GenericMap.date now has its time scale set from the ‘TIMESYS’ FITS keyword, if it is present. If it isn’t present the time scale defaults to ‘UTC’, which is unchanged default behaviour, so this change will only affect maps with a ‘TIMESYS’ keyword that is not set to ‘UTC’. (#4881)

  • Fixed the sunpy.net.dataretriever.sources.noaa.SRSClient which silently failed to download the SRS files when the tarball for the previous years did not exist. Client now actually searches for the tarballs and srs files on the ftp archive before returning them as results. (#4904)

  • No longer is the WAVEUNIT keyword injected into a data source if it is missing from the file’s metadata. (#4926)

  • Map sources no longer overwrite FITS metadata keywords if they are present in the original metadata. The particular map sources that have been fixed are SJIMap, KCorMap, RHESSIMap, EITMap, EUVIMap, SXTMap. (#4926)

  • Fixed a handling bug in sunpy.map.GenericMap.draw_rectangle when the rectangle is specified in a different coordinate frame than that of the map. A couple of other minor bugs in sunpy.map.GenericMap.draw_rectangle were also fixed. (#4929)

  • Improved error message from sunpy.net.Fido.fetch() when no email has been supplied for JSOC data. (#4950)

  • Fixed a bug when transforming from RotatedSunFrame to another frame at a different observation time that resulted in small inaccuracies. The translational motion of the Sun was not being handled correctly. (#4979)

  • Fixed two bugs with differential_rotate() and solar_rotate_coordinate() that resulted in significant inaccuracies. Both functions now ignore the translational motion of the Sun. (#4979)

  • The ability to to filter search results from the VSOClient was broken. This has now been restored. (#4011)

  • Fixed a bug where transformation errors were not getting raised in some situations when a coordinate frame had obstime set to the default value of None and SkyCoord was not being used. Users are recommended to use SkyCoord to manage coordinate transformations unless they have a specific reason not to. (#4267)

  • Fixed a bug in _get_url_for_timerange which returned incorrect URLs because of not using **kwargs in the client’s _get_overlap_urls() method. (#4288)

  • Data products from NOAAIndicesClient and NOAAPredictClient have been updated to download new JSON files. The old text files which the data used to come in no longer exist. The new JSON files for NOAAIndicesClient now do not have the following columns: - Geomagnetic Observed and Smoothed - Sunspot Numbers Ratio (RI/SW)

    Both sunpy.timeseries.sources.noaa.NOAAIndicesTimeSeries and sunpy.timeseries.sources.noaa.NOAAPredictIndicesTimeSeries have been updated to support the new JSON files. Loading the old text files is still supported, but support for this will be removed in a future version of sunpy. (#4340)

  • Fixed a bug due to which sunpy.net.helio.parser.wsdl_retriever ignored previously discovered Taverna links. (#4358)

  • The flare class labels in GOES peek() plots are now drawn at the center of the flare classes. Previously they were (ambiguously) drawn on the boundaries. (#4364)

  • sunpy.map.GenericMap.rsun_obs no longer assumes the observer is at Earth if

    rsun_obs was not present in the map metadata. The sun-observer distance is now taken directly from the observer coordinate. If the observer coordinate is not present, this defaults to the Earth, retaining previous behaviour. (#4375)

  • Nanosecond precision is now retained when using parse_time with a Timestamp. (#4409)

  • Fixed a bug where SunPy could not be successfully imported if the default text encoding of the running environment was unable to handle non-ASCII characters. (#4422)

  • sunpy.net.dataretriever.sources.noaa.SRSClient now correctly returns zero results for queries in the future or before 1996, which is when data is first available. (#4432)

  • Fixes issue where NAXISn is not updated after invoking GenericMap.resample() (#4445)

  • The floating point precision of input to sunpy.image.transform.affine_transform is now preserved. Previously all input was cast to numpy.float64, which could cause large increases in memory use for 32 bit data. (#4452)

  • Fixed affine_transform() to scale images to [0, 1] before passing them to skimage.transform.warp() and later rescale them back. (#4477)

  • Several warnings.simplefilter('always', Warning) warning filters in sunpy.timeseries have been removed. (#4511)

  • All calculations of the angular radius of the Sun now use the same underlying code with the accurate calculation. The previous inaccuracy was a relative error of ~0.001% (0.01 arcseconds) for an observer at 1 AU, but could be as large as ~0.5% for Parker Solar Probe perihelia. (#4524)

  • Fixed an issue in sunpy.time.TimeRange.get_dates() where the function would return the wrong number of days if less than 24 hours had passed (#4529)

  • Several functions in sunpy.map now properly check if the provided coordinate is in the expected Helioprojective frame. (#4552)

  • Fixes a bug which occurs in setting the ylims by sunpy.visualization.animator.line.LineAnimator when there are non-finite values in the data array to be animated. (#4554)

  • Clear rotation metadata for SOHO/LASCO Helioviewer JPEG2000 images, as they are already rotated correctly. (#4561)

  • The max_conn argument to Fido.fetch() is now correctly respected by the JSOC client. Previously the JSOC client would default to 4 connections no matter what the value passed to Fido.fetch() was. (#4567)

  • sunpy.time.parse_time() now correctly parses lists of time strings that have one of the built in sunpy time formats. (#4590)

  • Fixes the SRSClient to search for files of correct queried time and now allows a path keyword to be downloaded in fetch. (#4600)

  • Fixed sunpy.net.helio.parser.wsdl_retriever, which previously ignored discovered Taverna links. (#4601)

  • The transformations between HCRS and HeliographicStonyhurst have been re-implemented to enable the proper transformations of velocities. All ephemeris functions (e.g., get_body_heliographic_stonyhurst()) now return properly calculated velocities when include_velocity=True is specified. (#4613)

  • The maximum number of connections opened by the JSOC downloader has been reduced from 4 to 2. This should prevent downloads of large numbers of files crashing. (#4624)

  • Fixed a significant performance bug that affected all coordinate transformations. Transformations have been sped up by a factor a few. (#4663)

  • Fixed a bug with the mapping of a WCS header to a coordinate frame if the observer location is provided in Carrington coordinates. (#4669)

  • sunpy.io.fits.header_to_fits now excludes any keys that have associated NaN values, as these are not valid in a FITS header, and throws a warning if this happens. (#4676)

  • Fixed an assumption in sunpy.map.GenericMap.pixel_to_world that the first data axis is longitude, and the second is latitude. This will affect you if you are using data where the x/y axes are latitude/longitude, and now returns correct values in methods and properties that call pixel_to_world, such as bottom_left_coord, top_right_coord, center. (#4700)

  • Added a warning when a 2D Helioprojective coordinate is upgraded to a 3D coordinate and the number type is lower precision than the native Python float. This 2D->3D upgrade is performed internally when transforming a 2D Helioprojective coordinate to any other coordinate frame. (#4724)

  • All columns from a sunpy.net.vso.VSOClient.search will now be shown. (#4788)

  • The search results object returned from Fido.search (UnifiedResponse) now correctly counts all results in it’s file_num property. Note that because some Fido clients now return metadata only results, this is really the number of records and does not always correspond to the number of files that would be downloaded. (#4798)

  • Improved the file processing logic for EVE L0CS files, which may have fixed a bug where the first line of data was parsed incorrectly. (#4805)

  • Fixing the CROTA meta keyword in EUVI FITS to CROTAn standard. (#4846)

Added/Improved Documentation#
Documentation Fixes#
Trivial/Internal Changes#
  • Fido.fetch now always specifies a path= argument of type pathlib.Path to the fetch method of the client. This path will default to the configured sunpy download dir, will have the user directory expanded, will have the {file} placeholder and will be tested to ensure that it is writeable. (#4949)

  • Added information on what went wrong when sunpy.map.GenericMap.wcs fails to parse a FITS header into a WCS. (#4335)

  • Fixed the Helioprojective docstring to be clear about the names of the coordinate components. (#4351)

  • Raise a better error message if trying to load a FITS file that contains only one dimensional data. (#4426)

  • The following functions in sunpy.map have had their performance greatly increased, with runtimes typically improving by a factor of 20x. This has been achieved by improving many of the checks so that they only require checking the edge pixels of a map as opposed to all of the pixels.

  • Improved the output when you print a sunpy Map. (#4464)

  • Creating a MetaDict with dictionary keys that are not strings now raises as user-friendly ValueError which prints all the non-compliant keys. (#4476)

  • Maps created directly via. sunpy.map.GenericMap now have their metadata automatically converted to a MetaDict, which is the same current behaviour of the sunpy.map.Map factory. (#4476)

  • If the top_right corner given to sunpy.map.GenericMap.submap() is below or to the right of the bottom_left corner, a warning is no longer raised (as the rectangle is still well defined), but a message is still logged at the debug level to the sunpy logger. (#4491)

  • Added test support for Python 3.9 (no wheels yet). (#4569)

  • sunpy.sun functions now make use of the GeocentricTrueEcliptic frame to simplify internal calculations, but the returned values are unchanged. (#4584)

  • Change the format of the time returned from carrington_rotation_number() from 'jd' to 'iso', so printing the Time returned will now print an ISO timestamp instead of the Julian days. (#4819)

  • The listings for the sample data (sunpy.data.sample) are now sorted. (#4838)

  • Changed the implementation of a hypothesis-based test so that it does not raise an error with hypothesis 6.0.0. (#4852)

2.0.0 (2020-06-12)#

Backwards Incompatible Changes#
  • The frames HeliographicStonyhurst and HeliographicCarrington now inherit from the new base class BaseHeliographic. This changes means that isinstance(frame, HeliographicStonyhurst) is no longer True when frame is HeliographicCarrington. (#3595)

  • aia_color_table, eit_color_table and suvi_color_table now only take astropy.units quantities instead of strings. (#3640)

  • sunpy.map.Map is now more strict when the metadata of a map cannot be validated, and an error is now thrown instead of a warning if metadata cannot be validated. In order to load maps that previously loaded without error you may need to pass silence_errors=True to sunpy.map.Map. (#3646)

  • Fido.search will now return results from all clients which match a query, you no longer have to make the query specific to a single client. This means that searches involving the ‘eve’ and ‘rhessi’ instruments will now potentially also return results from the VSO. For RHESSIClient you can now specify a.Physobs("summary_lightcurve") to only include the summary lightcurve data products not provided by the VSO. (#3770)

  • The objects returned by the search methods on VSOClient, JSOCClient and GenericClient have been changed to be based on sunpy.net.base_client.BaseQueryResponse. This introduces a few subtle breaking changes for people using the client search methods directly (not Fido.search), or people using sunpy.net.fido_factory.UnifiedResponse.get_response. When slicing an instance of QueryResponse it will now return an instance of itself, QueryResponse.blocks can be used to access the underlying records. Also, the .client attribute of the response no longer has to be the instance of the class the search was made with, however, it often is. (#3770)

  • HeliographicCarrington is now an observer-based frame, where the observer location (specifically, the distance from the Sun) is used to account for light travel time when determining apparent Carrington longitudes. Coordinate transformations using this frame now require an observer to be specified. (#3782)

  • To enable the precise co-alignment of solar images from different observatories, the calculation of Carrington coordinates now ignores the stellar-aberration correction due to observer motion. For an Earth observer, this change results in an increase in Carrington longitude of ~20 arcseconds. See Calculating Carrington longitude for more information. (#3782)

  • Fixed a bug where some of the coordinate transformations could raise ValueError instead of ConvertError when the transformation could not be performed. (#3894)

  • Astropy 3.2 is now the minimum required version of that dependency. (#3936)

Deprecations and Removals#
Features#
  • Added RotatedSunFrame for defining coordinate frames that account for solar rotation. (#3537)

  • Added a context manager (transform_with_sun_center) to ignore the motion of the center of the Sun for coordinate transformations. (#3540)

  • Updated the gallery example titled ‘Downloading and plotting an HMI magnetogram’ to rotate the HMI magnetogram such that solar North is pointed up. (#3573)

  • Creates a function named sunpy.map.sample_at_coords that samples the data from the map at the given set of coordinates. (#3592)

  • Enabled the discovery of search attributes for each of our clients. (#3637)

  • Printing sunpy.net.attrs.Instrument or other “attrs” will show all attributes that exist under the corresponding “attr”. (#3637)

  • Printing sunpy.net.Fido will print out all the clients that Fido can use. (#3637)

  • Updates draw_grid to allow disabling the axes labels and the ticks on the top and right axes. (#3673)

  • Creates a tables property for UnifiedResponse, which allows to access the BaseQueryResponse as an Table, which then can be used for indexing of results. (#3675)

  • Change the APIs for sunpy.map.GenericMap.draw_rectangle and sunpy.map.GenericMap.submap() to be consistent with each other and to use keyword-only arguments for specifying the bounding box. (#3677)

  • Updates the observer_coordinate property to warn the user of specific missing metadata for each frame. Omits warning about frames where all metadata is missing or all meta is present. (#3692)

  • Added sunpy.util.config.copy_default_config that copies the default config file to the user’s config directory. (#3722)

  • sunpy.database now supports adding database entries and downloading data from HEK query (#3731)

  • Added a helper function (get_rectangle_coordinates) for defining a rectangle in longitude and latitude coordinates. (#3737)

  • Add a .data property in GenericTimeSeries, so that users are encouraged to use to_dataframe() to get the data of the timeseries. (#3746)

  • It is now possible to turn on or off various corrections in L0() (the apparent Carrington longitude of Sun-disk center as seen from Earth). (#3782)

  • Made skimage.transform import lazy to reduce import time of sunpy.image.transform by ~50% (#3818)

  • Add support for parfive 1.1. This sets a limit on the number of open connections to JSOC when downloading files to 10. (#3822)

  • Fido clients (subclasses of sunpy.net.base_client.BaseClient) can now register their own attrs modules with sunpy.net.attrs. This allows clients which require attr classes specific to that client to register modules that can be used by the user i.e. a.vso. It also allows clients implemented externally to sunpy to register attrs. (#3869)

  • Added the methods sunpy.map.GenericMap.quicklook() and sunpy.map.MapSequence.quicklook() to display an HTML summary of the instance, including interactive controls. When using Jupyter notebooks, this HTML summary is automatically shown instead of a text-only representation. (#3951)

  • Added _localfilelist method in sunpy.util.scraper.Scraper to scrap local data archives. (#3994)

  • Added extra constants to sunpy.sun.constants:

  • Adds to sunpy.util.scraper.Scraper the ability to include regular expressions in the URL passed. (#4107)

Bug Fixes#
  • Added support for passing TimeSeriesMetaData object to timeseries_factory and associated validation tests. (#3639)

  • Now when GenericMap fails to load a file, the filename that failed to load will now be part of the error message. (#3727)

  • Work around incorrect Content-Disposition headers in some VSO downloads, which were leading to mangled filenames. (#3740)

  • Fido.search can now service queries without a.Time being specified. This is currently only used by the sunpy.net.jsoc.JSOCClient. (#3770)

  • Fixed a bug with the calculation of Carrington longitude as seen from Earth where it was using an old approach instead of the current approach (for example, the varying Sun-Earth distance is now taken into account). The old approach resulted in errors no greater than 7 arcseconds in Carrington longitude when using L0 and HeliographicCarrington. (#3772)

  • Updated sunpy.map.CompositeMap.plot to support a linewidths argument. (#3792)

  • Fix a bug in sunpy.net.jsoc.JSOCClient where requesting data for export would not work if a non-time primekey was used. (#3825)

  • Add support for passing paths of type pathlib.Path in sunpy.net.jsoc.JSOCClient.fetch. (#3838)

  • Add explicit support for dealing with download urls for files, under ‘as-is’ protocol in sunpy.net.jsoc.JSOCClient.get_request. (#3838)

  • Updated the method used to filter time in the VSO post-search filtering function. (#3840)

  • Fix failing of fetching of the indexed JSOCResponses using sunpy.net.fido_factory.UnifiedDownloaderFactory.fetch. (#3852)

  • Prevented sunpy.map.GenericMap.plot modifying in-place any items passed as imshow_kwargs. (#3867)

  • Changed the format of DATE-OBS in sunpy.map.GenericMap.wcs from iso to isot (ie. with a “T” between the date and time) to conform with the FITS standard. (#3872)

  • Fixed a minor error (up to ~10 arcseconds) in the calculation of the Sun’s position angle (sunpy.coordinates.sun.P()). (#3886)

  • HEKClient was returning HTML and not JSON. (#3899)

  • Updated to HTTPS for HEK. (#3917)

  • The accuracy of the output of sunpy.coordinates.ephemeris.get_horizons_coord() is significantly improved. (#3919)

  • Fixed a bug where the longitude value for the reference coordinate in the Map repr would be displayed with the unintended longitude wrapping. (#3959)

  • It is now possible to specify a local file path to sunpy.data.data_manager.DataManager.override_file without having to prefix it with file://. (#3970)

  • Closed the session in the destructor of VSOClient thus solving the problem of socket being left open (#3973)

  • Fixed a bug of where results of VSO searches would have inconsistent ordering in sunpy.net.vso.vso.QueryResponse by always sorting the results by start time. (#3974)

  • Fixes two bugs in sunpy.util.deprecated: correctly calculates the removal version and does not override the default and/or alternative functionality message. Providing a custom deprecation message now suppresses any mention of the removal version. Additionally, a pending keyword argument is provided to denote functions/classes that are pending deprecation. (#3982)

  • Correctly generate labels for sliders in ~sunpy.visualization.animator.ArrayAnimatorWCS when the number of pixel dimensions and the number of world dimensions are not the same in the WCS. (#3990)

  • Updated VSOClient.response_block_properties to check if “None” is in the return. (#3993)

  • Fix a bug with sunpy.visualization.animator.ArrayAnimatorWCS where animating a line with a masked array with the whole of the initial line masked out the axes limits for the x axis were not correctly set. (#4001)

  • Fixed passing in a list of URLs into sunpy.map.GenericMap, before it caused an error due to the wrong type being returned. (#4007)

  • Fixed a bug with transform_with_sun_center() where the global variable was sometimes restored incorrectly. This bug was most likely encountered if there was a nested use of this context manager. (#4015)

  • Fixes a bug in fido_factory to allow path=”./” in fido.fetch(). (#4058)

  • Prevented sunpy.io.fits.header_to_fits modifying the passed header in-place. (#4067)

  • Strip out any unknown unicode from the HEK response to prevent it failing to load some results. (#4088)

  • Fixed a bug in get_body_heliographic_stonyhurst() that resulted in a error when requesting an array of locations in conjunction with enabling the light-travel-time correction. (#4112)

  • sunpy.map.GenericMap.top_right_coord and center have had their definitions clarified, and both have had off-by-one indexing errors fixed. (#4121)

  • Fixed sunpy.map.GenericMap.submap() when scaled pixel units (e.g. u.mpix) are used. (#4127)

  • Fixed bugs in sunpy.util.scraper.Scraper.filelist that resulted in error when the HTML page of URL opened by the scraper contains some “a” tags without “href” attribute and resulted in incorrect file urls when any href stores filepath relative to the URL’s domain instead of just a filename. (#4132)

  • Fixed inconsistencies in how submap behaves when passed corners in pixel and world coordinates. The behavior for submaps specified in pixel coordinates is now well-defined for pixels on the boundary of the rectangle and is consistent for all boundaries. Previously pixels on the lower left boundary were included, but excluded on the upper and right boundary. This means the shape of a submap may now be 1 pixel larger in each dimension. Added several more tests for submap for a range of cutout sizes in both pixel and world coordinates. (#4134)

  • sunpy.map.on_disk_bounding_coordinates now fully propagates the coordinate frame of the input map to the output coordinates. Previously only the observer coordinate, and no other frame attributes, were propagated. (#4141)

  • Fix an off-by-one error in the reference pixel returned by sunpy.map.header_helper.make_fitswcs_header(). (#4152)

  • sunpy.map.GenericMap.reference_pixel now uses zero-based indexing, in order to be consistent with the rest of the sunpy.map API. (#4154)

  • Previously sunpy.map.GenericMap.resample with method='linear' was using an incorrect and constant value to fill edges when upsampling a map. Values near the edges are now correctly extrapolated using the fill_value=extrapolate option to scipy.interpolate.interp1d. (#4164)

  • Fixed a bug where passing an int or list via the hdus keyword argument to sunpy.io.fits.read threw an exception because the list of HDU objects was no longer of type HDUList. (#4183)

  • Fix attr printing when the attr registry is empty for that attr (#4199)

  • Improved the accuracy of angular_radius() by removing the use of the small-angle approximation. The inaccuracy had been less than 5 milliarcseconds. (#4239)

  • Fixed a bug with the observer frame attribute for coordinate frames where an input that was not supplied as a SkyCoord would sometimes result in a transformation error. (#4266)

Improved Documentation#
  • Fixed an issue with the scaling of class-inheritance diagrams in the online documentation by blocking the versions of graphviz containing a bug. (#3548)

  • A new example gallery example “Plotting a difference image” has been added, which can be used for base difference or running difference images. (#3627)

  • Removed obsolete Astropy Helpers submodule section in CONTRIBUTING.rst; Also removed mentions of astropy_helpers in all files of the project. (#3676)

  • Corrected misleading TimeSeriesMetaData documentation about optional parameters. (#3680)

  • Added an example for world_to_pixel function in the Units & Coordinates guide. (#3776)

  • Added a page describing how SunPy calculates Carrington longitudes. (#3782)

  • Changed padding value of an example in the example gallery to fix the overlap of titles and x-label axes. (#3835)

  • More information and links about how to create changelogs. (#3856)

  • Clarified some inputs to sunpy.map.GenericMap.plot. (#3866)

  • Changed quoted sentence (that we suggest authors add to their research papers) in CITATION.rst (#3896)

  • Add example of how to use SunPy’s HEK client to search for the GOES flare event list. (#3953)

  • Improved the doc layout of sunpy.data.sample. (#4034)

  • Made improvements to STEREO starfield gallery example. (#4039)

  • Improved the documentation of sunpy.map.GenericMap.resample. (#4043)

  • Updated the STEREO starfield example to use all of the information in the star catalog. (#4116)

  • Mini-galleries are now easier to create in the documentation thanks to a custom Sphinx directive (minigallery). The page Differential rotation using coordinate frames has an example of a mini-gallery at the bottom. (#4124)

  • Added sunpy.visualization.colormaps.color_tables to the docs. (#4182)

  • Made minor improvements to the map histogramming example. (#4205)

  • Add a warning to sunpy.io docs to recommend not using it for FITS (#4208)

Trivial/Internal Changes#
  • Removed un-used and un-tested code paths in the private _remove_lytaf_events function in sunpy.instr.lyra. (#3570)

  • Removed astropy_helpers and this means that python setup.py <test,build_docs> no longer works. So if you want to:

    • Run the tests: Use tox -e <env name> or call pytest directly

    • Build the docs: Use tox -e docs or cd into the docs folder and run make html or sphinx-build docs docs/_build/html -W -b html -d docs/_build/.doctrees (#3598)

  • Cleaned up test warnings in sunpy.coordinates. (#3652)

  • Fix Python version for requiring importlib_resources (#3683)

  • sunpy.net.attr.AttrWalker no longer uses sunpy.util.multimethod.MultiMethod it uses a derivative of functools.singledispatch sunpy.util.functools.seconddispatch which dispatches on the second argument. (#3714)

  • Errors from a VSO search will now be raised to the user. (#3719)

  • Fixed the transformation test for NorthOffsetFrame, which would intermittently fail. (#3775)

  • earth_distance() is now computed without using coordinate transformations for better performance. (#3782)

  • Created a helper function for testing the equality/closeness of longitude angles (i.e., angles with wrapping). (#3804)

  • Bump the astropy version figure tests are run with from 3.1.2 to 3.2.3 (#3925)

  • Used urllib.parse.urlsplit in sunpy.util.scraper for file scraping functionalities. (#3956)

  • Added sunpy.net.base_client.BaseClient.check_attr_types_in_query as a helper method to check if a query contains a set of required attributes, and is a subset of optional attributes. (#3979)

  • Removes appending login details for ftp urls from scraper. (#4020)

  • Re-factored the sunpy.map.Map factory to dispatch argument parsing based on type. (#4037)

  • Improved the error message raised by the Map factory when a map matches multiple source map types. (#4052)

  • Added log messages when the sample data fails to download. (#4137)

  • Remove an Astropy 3.1 compatibility wrapper for Quantity.to_string. (#4172)

  • Refactor the sphinx config to no longer depend on astropy-sphinx and more closely match the new sunpy package template (#4188)

1.1.0 (2020-01-10)#

Backwards Incompatible Changes#
  • The sunpy.net.vso.vso.get_online_vso_url function has been broken into two components, the new sunpy.net.vso.vso.get_online_vso_url function takes no arguments (it used to take three) and now only returns an online VSO mirror or None. The construction of a zeep.Client object is now handled by sunpy.net.vso.vso.build_client which has a more flexible API for customising the zeep.Client interface. (#3330)

  • Importing sunpy.timeseries.timeseriesbase no longer automatically imports Matplotlib. (#3376)

  • sunpy.timeseries.sources.NOAAIndicesTimeSeries.peek() now checks that the type argument is a valid string, and raises a ValueError if it isn’t. (#3378)

  • Observer-based coordinate frames (Heliocentric and Helioprojective) no longer assume a default observer (Earth) if no observer is specified. These frames can now be used with no observer specified, but most transformations cannot be performed for such frames. This removal of a default observer only affects sunpy.coordinates, and has no impact on the default observer in sunpy.map. (#3388)

  • The callback functions provided to BaseFuncAnimator button_func keyword argument now take two positional arguments rather than one. The function signature is now (animator, event) where the first arg is the animator object, and the second is the matplotlib mouse event. (#3407)

  • The colormap stored in SunPy’s Map subclasses (ie. map.plot_settings['cmap']) can now be colormap string instead of the full matplotlib.colors.Colormap object. To get the full Colormap object use the new attribute map.cmap. (#3412)

  • Fix a warning in sunpy.map.GenericMap.rotate where the truth value of an array was being calculated. This changes the behaviour of rotate when the angle= parameter is not an Quantity object to raise TypeError rather than ValueError. (#3456)

Deprecations and Removals#
  • Removed the step of reparing images (replacing non-finite entries with local mean) before coaligning them. The user is expected to do this themselves before coaligning images. If NaNs/non-finite entries are present, a warning is thrown. The function sunpy.image.coalignment.repair_image_nonfinite is deprecated. (#3287)

  • The method to convert a Helioprojective frame from 2D to 3D has been renamed from calculate_distance to make_3d. This method is not typically directly called by users. (#3389)

  • sunpy.visualization.animator.ImageAnimatorWCS is now deprecated in favour of ArrayAnimatorWCS. (#3407)

  • sunpy.cm has been moved to sunpy.visualization.colormaps and will be removed in a future version. (#3410)

Features#
  • Add a new sunpy.data.manager and sunpy.data.cache for dealing with versioned remote data within functions. Please see the Remote Data Manager guide. (#3124)

  • Added the coordinate frames HeliocentricEarthEcliptic (HEE), GeocentricSolarEcliptic (GSE), HeliocentricInertial (HCI), and GeocentricEarthEquatorial (GEI). (#3212)

  • Added SunPy Map support for GOES SUVI images. (#3269)

    • Support APE14 for ImageAnimatorWCS in SunPy’s visualization module (#3275)

  • Add ability to disable progressbars when downloading files using sunpy.net.helioviewer and edited docstrings to mention this feature. (#3280)

  • Adds support for searching and downloading SUVI data. (#3301)

  • Log all VSO XML requests and responses to the SunPy logger at the DEBUG level. (#3330)

  • Transformations between frames in sunpy.coordinates can now provide detailed debugging output. Set the logging level to DEBUG to enable this output. (#3339)

  • Added the sunpy.coordinates.sun.carrington_rotation_time function to compute the time of a given Carrington rotation number. (#3360)

  • A new method has been added to remove columns from a sunpy.timeseries.GenericTimeSeries. (#3361)

  • Add shape property to TimeSeries. (#3380)

  • Added ASDF schemas for the new coordinate frames (GeocentricEarthEquatorial, GeocentricSolarEcliptic, HeliocentricEarthEcliptic, HeliocentricInertial). See the gallery for an example of using asdf to save and load a coordinate frame. (#3398)

  • sunpy.visualization.animator.ArrayAnimatorWCS was added which uses the WCS object to get the coordinates of all axes, including the slider labels. It also provides the ability to customise the plot by specifying arguments to WCSAxes methods and supports animation of WCS aware line plots with Astroy 4.0. (#3407)

  • The returned list of Map objects is now sorted by filename when passing a directory or glob pattern to MapFactory. (#3408)

  • Single character wildcards and character ranges can now be passed as glob patterns to Map. (#3408)

  • Map now accepts filenames and directories as pathlib.Path objects. (#3408)

  • GenericMap objects now have a .cmap attribute, which returns the full Colormap. object. (#3412)

  • sunpy.io.write_file() now accepts Path objects as filename inputs. (#3469)

  • sunpy.map.header_helper.make_fitswcs_header() now accepts a tuple representing the shape of an array as well as the actual array as the data argument. (#3483)

  • Made a couple of module imports lazy to reduce the import time of sunpy.map by ~40%. (#3495)

  • sunpy.map.GenericMap.wcs now uses the full FITS header to construct the WCS. This adds support for instruments with more complex projections, such as WISPR, however does mean that Map will be more sensitive to incorrect or invalid FITS headers. If you are using custom headers with SunPy Map you might encounter issues relating to this change. (#3501)

  • sunpy.visualization.animator.BaseFuncAnimator now takes an optional slider_labels keyword argument which draws text labels in the center of the sliders. (#3504)

  • Added a more helpful error message when trying to load a file or directory that doesn’t exist with sunpy.map.Map. (#3568)

  • Add __repr__ for MapSequence objects so that users can view the critical information of all the Map objects, in a concise manner. (#3636)

Bug Fixes#
  • Fixed accuracy issues with the calculations of Carrington longitude (L0) and Carrington rotation number (carrington_rotation_number). (#3178)

  • Updated sunpy.map.header_helper.make_fitswcs_header() to be more strict on the inputs it accepts. (#3183)

  • Fix the calculation of rsun_ref in make_fitswcs_header() and and ensure that the default reference pixel is indexed from 1. (#3184)

  • Fixed the missing transformation between two HeliographicCarrington frames with different observation times. (#3186)

  • sunpy.map.sources.AIAMap and sunpy.map.sources.HMIMap will no longer assume the existence of certain header keys. (#3217)

  • sunpy.map.header_helper.make_fitswcs_header() now supports specifying the map projection rather than defaulting to TAN. (#3218)

  • Fix the behaviour of sunpy.coordinates.frames.Helioprojective.calculate_distance if the representation isn’t Spherical. (#3219)

  • Fixed a bug where the longitude of a coordinate would not wrap at the expected angle following a frame transformation. (#3223)

  • Fixed a bug where passing a time or time interval to the differential rotation function threw an error because the new observer was not in HGS. (#3225)

  • Fixed bug where get_horizons_coord was unable to accept Time arrays as input. (#3227)

  • Fix the ticks on the default heliographic grid overlay so they are not white (and normally invisible) by default. (#3235)

  • Fixed a bug with sunpy.net.hek.HEKClient when the results returned were a mixed dataset. (#3240)

  • Fix sunpy.physics.differential_rotation.differential_rotate to rotate in the correct direction and to account for the rotation of the heliographic coordinate frame with time. (#3245)

  • Fixed a bug with the handling of changing observation times for transformations between HCRS and HeliographicStonyhurst, which also indirectly affected other transformations when changing observation times. (#3246)

  • Fixed all coordinate transformations to properly handle a change in observation time. (#3247)

  • Fixed the handling of coordinates with velocity information when transforming between Astropy frames and SunPy frames. (#3247)

  • Fixed sunpy.physics.solar_rotation.calculate_solar_rotate_shift so that it does not calculate a shift between the reference layer and itself, which would sometimes incorrectly result in a shift of a pixel due to numerical precision. (#3255)

  • Stop crash when LineAnimator axes_ranges entry given as 1D array when data is >1D, i.e. as an independent axis. (#3283)

  • Fixed a sunpy.coordinates bug where a frame using the default observer of Earth could have its observer overwritten during a transformation. (#3291)

  • Fixed a bug where the transformation from Helioprojective to Heliocentric used the Sun-observer distance from the wrong frame when shifting the origin, and thus might not give the correct answer if the observer was not the same for the two frames. (#3291)

  • Fixed a bug with the transformations between Heliocentric and HeliographicStonyhurst when the frame observation time was not the same as the observer observation time. The most common way to encounter this bug was when transforming from Helioprojective to any non-observer-based frame while also changing the observation time. (#3291)

  • VSO client fetch should not download when wait keyword argument is specified. (#3298)

  • Fixed a bug with solar_frame_to_wcs_mapping that assumed that the supplied frame was a SunPy frame. (#3305)

  • Fixed bugs with solar_frame_to_wcs_mapping if the input frame does not include an observation time or an observer. (#3305)

  • GreatArc now accounts for the start and end points of the arc having different observers. (#3334)

  • Fixed situations where 2D coordinates provided to HeliographicStonyhurst and HeliographicCarrington were not converted to 3D as intended. Furthermore, the stored data will always be the post-conversion, 3D version. (#3351)

  • Fix off by one error in sunpy.map.header_helper.make_fitswcs_header() where when using the default reference_pixel=None keyword argument the pixel coordinate of the reference pixel was off by +1. (#3356)

  • Updated both GOES XRS and LYRA dataretriever clients to use sunpy.util.scraper.Scraper, to make sure that files are actually on the servers being queried. (#3367)

  • Fixing the ordering of lon and lat inputs into make_fitswcs_header (#3371)

  • Updated the URL for Fermi spacecraft-pointing files to use an HTTPS connection to HEASARC. (#3381)

  • Fixed a bug where permission denied errors when downloading files are very verbose by adding an error message in fetch. (#3417)

  • Fixed a malformed call to astropy.time.Time in a test, which resulted in an incorrect time scale (UTC instead of TT). (#3418)

  • Fix incorrect files being included in the tarball, and docs missing from the tarball (#3423)

  • Fixed a bug where clipping behavior had been enabled by default in the plotting normalizers for Map objects. Clipping needs to be disabled to make use of the over/under/masked colors in the colormap. (#3427)

  • Fix a bug with observer based frames that prevented a coordinate with an array of obstimes being transformed to other frames. (#3455)

  • sunpy.map.GenericMap will no longer raise a warning if the position of the observer is not known for frames that don’t need an observer, i.e. heliographic frames. (#3462)

  • Apply os.path.expanduser to sunpy.map.map_factory.MapFactory input before passing to glob.glob (#3477)

  • Fix multiple instances of sunpy.map sources assuming the type of FITS Header values. (#3497)

  • Fixed a bug with NorthOffsetFrame where non-spherical representations for the north pole produced an error. (#3517)

  • Fixed map.__repr__ when the coordinate system information contained in the CUNIT1/2 metadata is not set to a known value. (#3569)

  • Fixed bugs with some coordinate transformations when obstime is None on the destination frame but can be assumed to be the same as the obstime of the source frame. (#3576)

  • Updated sunpy.map.mapsequence.MapSequence so that calling _derotate() raises NotImplementedError. Added associated tests. (#3613)

  • Fixed pandas plotting registration in sunpy.timeseries. (#3633)

  • Correctly catch and emit a warning when converting a map metadata to a FITS header and it contains a keyword with non-ascii characters. (#3645)

Improved Documentation#
  • Clean up the docstring for sunpy.physics.differential_rotation.solar_rotate_coordinate to make the example clearer. (#2708)

  • Added new gallery examples and cleaned up various gallery examples. (#3181)

  • Cleaned and expanded upon the docstrings for each Fido Client. (#3220)

  • Added clarifying hyperlinks to the gallery example getting_lasco_observer_location to link to astroquery docs page. (#3228)

  • Added more details to docstrings in sunpy.coordinates.frames. (#3262)

  • Added a link to package maintainer list in the API Stability page. (#3281)

  • Improved the contributing guide by updating commands and highlighting text. (#3394)

  • Removing .fits from the end of path kwargs in sunpy.net.fido_factory.UnifiedDownloaderFactory.fetch docs to change output file extension from {file}.fits.fits to {file}.fits. (#3399)

  • A new example gallery section “Using SunPy with Other Packages” has been added, which contains a set of new examples using the reproject with solar data. (#3405)

  • Added a table of supported coordinate systems and other miscellaneous improvements to the coordinates documentation. (#3414)

  • Clarified the meaning of sunpy.map.GenericMap.dsun. (#3430)

  • Fixed the plots with multiple subplots in the Map user guide to properly use wcsaxes and to be appropriately sized. (#3454)

  • Fixed various issues with the gallery example of saving/loading coordinates using asdf. (#3473)

  • Added sunpy.__citation__ with a BibTex entry for citing sunpy. (#3478)

  • Added an example showing how to display two maps and fade between them. (#3488)

  • Clarified the meaning of some GenericMap observer properties. (#3585)

  • Added inherited members of sunpy.map classes to the docs. (#3587)

  • Fixed documentation of sunpy.database.Database.search by adding Returns docstring. (#3593)

  • Updated the docstring for the parameter sortby in MapSequence with the default value, valid value and how to disable sorting. (#3601)

  • Updated the tour guide to reflect that the time series is not random data. (#3603)

  • Fixes bold type and extra line breaks of remote data manager example. (#3615)

Trivial/Internal Changes#
  • Allow running our sphinx-gallery examples as Jupyter notebooks via Binder (#3256)

  • Improve error messages and type checking in sunpy.visualization.animator.image.ImageAnimatorWCS. (#3346)

  • Copy the library distro into sunpy/extern: replaces the deprecated platform/linux_distribution (#3396)

  • The version of Matplotlib used to generate figure tests has been bumped from 3.0.3 to 3.1.1. (#3406)

  • Corrected spelling of ‘plotting’ in timeseries method (changed ‘ploting’ to ‘plotting’). (#3429)

  • Switched to “importlib_metadata” to get package version to speed up import of SunPy. (#3449)

  • Fix tests for sunpy.data.data_manager and ensure they are correctly executed with pytest. (#3550)

1.0.0 (2019-06-01)#

Backwards Incompatible Changes#
  • Move the matplotlib animators from sunpy.visualisation.imageanimator and sunpy.visualization.mapcubeanimator to sunpy.visualization.animator. (#2515)

  • Make sunpy.time.parse_time return astropy.time.Time instead of datetime.datetime. (#2611)

  • The properties and methods of sunpy.time.TimeRange returns astropy.time.Time and astropy.time.TimeDelta instead of datetime.datetime and datetime.timedelta respectively. (#2638)

  • The sunpy.instr.goes module now accepts and returns sunpy.timeseries.sources.XRSTimeSeries objects only. (#2666)

  • obstime keyword param of sunpy.instr.goes._goes_lx takes a non-scalar astropy.time.Time object instead of numpy.ndarray. The precision of times contained in sunpy.timeseries has been increased to 9 from 6. (#2676)

  • Removed sunpy.net.jsoc.attrs.Time because it served the same purpose as sunpy.net.attrs.Time after the switch to astropy.time.Time. (#2694)

  • Remove unused **kwargs within TimeSeries functions. (#2717)

  • Rotation matrices inside map objects were previously stored as numpy matrices, but are now stored as numpy arrays, as numpy will eventually remove their matrix datatype. See https://docs.scipy.org/doc/numpy/user/numpy-for-matlab-users.html for more information. (#2719)

  • The sunpy.cm.show_colormaps function now accepts the keyword ‘search’ instead of ‘filter’. (#2731)

  • The keyword arguments to all client .fetch methods have been changed to support the new parfive downloader and to ensure consistency across all Fido clients. (#2797)

  • 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. We have enforced that several keywords (observatory,instrument,detector,measurement) need to be defined otherwise the functions cannot return any data. (#2801)

  • Maps no longer assume that the pixel units are arcseconds if the units aren’t explicitly set. In addition to this if critical metadata is missing from when creating a map, the map will fail to initialize and will raise an error. (#2847)

  • axis_ranges kwarg of sunpy.visualization.animator.base.ArrayAnimator, sunpy.visualization.animator.image.ImageAnimator and sunpy.visualization.animator.line.LineAnimator now must be entered as None, [min, max] or pixel edges of each array element. Previously, pixel centers were expected. This change removes ambiguity in interpretation and ensures the extent of the plot can always be accurately derived. (#2867)

  • All keywords have been added (with defaults) to each ~sunpy.net.helioviewer.HelioviewerClient function. This means that there will be some changes to the style of the PNG screenshot that is returned. Returns for the JPEG 2000 and the other functions should be the same but not guaranteed. (#2883)

  • Changed sunpy.sun.models.interior and sunpy.sun.models.evolution from pandas.DataFrame to astropy.table.QTable (#2936)

  • Minimum numpy version is now >=1.14.5 (#2954)

  • Removed sunpy.time.julian_day, sunpy.time.julian_centuries, sunpy.time.day_of_year, sunpy.time.break_time, sunpy.time.get_day. (#2999)

  • Updated the solar values in sunpy.sun.constants to IAU 2015 values. (#3001)

  • Renamed eccentricity_sunearth_orbit to eccentricity_sun_earth_orbit. (#3001)

  • Renamed sunpy.image.rescale to sunpy.image.resample. (#3044)

  • Remove the basic_plot keyword argument from peek. An example has been added to the gallery showing how to make a plot like this. (#3109)

  • sunpy.map.GenericMap will no longer use the key solar_b0 as a value for heliographic latitude. (#3115)

  • sunpy.map.GenericMap now checks for a complete observer location rather than individually defaulting coordinates (lat, lon, distance) to Earth position. If any one of the three coordinates is missing from the header the observer will be defaulted to Earth and a warning raised. (#3115)

  • sunpy.sun.sun functions have been re-implemented using Astropy for significantly improved accuracy. Some functions have been removed. (#3137)

  • All of the functions in sunpy.sun.sun and all of the Sun-specific functions in sunpy.coordinates.ephemeris have been moved to the new module sunpy.coordinates.sun. (#3163)

Deprecations and Removals#
  • The deprecated sunpy.lightcurve, sunpy.wcs and sunpy.spectra modules have now been removed. (#2666)

  • sunpy.instr.rhessi.get_obssumm_dbase_file sunpy.instr.rhessi.get_obssum_filename, sunpy.instr.rhessi.get_obssumm_file have been removed. Fido should be used to download these files. (#2808)

  • Removed heliographic_solar_center in favour of sunpy.coordinates.get_sun_L0 and sunpy.coordinates.get_sun_B0 (#2830)

  • Removed GenericClient.query in favour of sunpy.net.dataretriever.GenericClient.search (#2830)

  • Removed sunearth_distance in favour of get_sunearth_distance (#2830)

  • Removed remove_lytaf_events_from_lightcurve in favour of sunpy.instr.lyra.remove_lytaf_events_from_timeseries (#2830)

  • Removed sunpy.cm.get_cmap in favour of plt.get_cmap (#2830)

  • Removed database.query in favour of sunpy.database.Database.search (#2830)

  • Removed sunpy.net.vso.InteractiveVSOClient (#2830)

  • Removed MapCube in favour of MapSequence (#2830)

  • Removed solar_north in favour of get_sun_P (#2830)

  • Removed database.download in favour of sunpy.database.Database.fetch (#2830)

  • Removed sunpy.map.GenericMap.pixel_to_data in favour of sunpy.map.GenericMap.pixel_to_world (#2830)

  • Removed GenericClient.get in favour of sunpy.net.dataretriever.GenericClient.fetch. This changes applies to the other clients as well. (#2830)

  • Removed Map.xrange and Map.yrange (#2830)

  • Removed sunpy.net.attrs.Wave in favour of sunpy.net.vso.attrs.Wavelength (#2830)

  • Removed JSOCClient.check_request in favour of drms.ExportRequest.status (#2830)

  • sunpy.net.vso.VSOClient.query_legacy and sunpy.net.vso.VSOClient.latest have been deprecated as we strongly recommend people use sunpy.net.Fido for all queries. (#2866)

  • The deprecated sunpy.physics.transforms module has been removed, it is replaced by sunpy.physics.solar_rotation and sunpy.physics.differential_rotation. (#2994)

  • Removed sunpy.sun.sun.solar_cycle_number because it was fundamentally flawed (#3150)

Features#
  • Change arguments to sunpy.test from offline= and online= to online and online_only. This matches the behavior of the figure keyword arguments and comes as a part of a move to using a modified version of the Astropy test runner. (#1983)

  • asdf schemas and tags were added for the SunPy coordinate frames and GenericMap allowing these objects to be saved to and restored from asdf files. (#2366)

  • The images from image tests are now saved in a local folder for easy access. (#2507)

  • sunpy.map.MapCube has been renamed to sunpy.map.MapSequence to better reflect its use as a collection of map objects. (#2603)

  • Net search attributes now support tab completion of values and display a table of possible values when printed, to allow easier discoverability of possible search values. (#2663)

  • Running the figure tests now creates a page showing the differences between the expected figures and the figures produced from running the tests. (#2681)

  • Add support for Dask arrays in sunpy.map.Map. The map factory now checks a whitelist of array types rather than strictly checking if the array is of type numpy.ndarray. (#2689)

  • Persist the name of a coordinate, i.e. “earth” even though a concrete coordinate object has been calculated and use this string representation to change the way the sunpy frames are printed. This is primarily to facilitate displaying the name of the body rather than the concrete coordinate when printing a SkyCoord. (#2723)

  • search now returns an astropy.table.Table instead of list of a dict. (#2759)

  • Add a downscaled HMI image to the sample data. (#2782)

  • Now able to create a sunpy.map.Map using an array and a astropy.wcs.WCS object. (#2793)

  • The download manager for Fido has been replaced with parfive. This provides advanced progress bars, proper handling of overwriting and the ability to retry failed downloads. (#2797)

  • sunpy.map.GenericMap can now save out rice compressed FITS files. (#2826)

  • Now any SunPyDeprecationWarnings will cause an error when using pytest. (#2830)

  • Added full Tox support for SunPy tests, documentation build and figure tests. (#2839)

  • Transition the sunpy.net.vso.VSOClient from using suds to zeep as the SOAP library. This is a more actively maintained library, and should provide better support for the VSOs https endpoints. This change should have no effect on the public API of the sunpy.net.vso.VSOClient. (#2866)

  • Provided access to the Helioviewer header information using ~sunpy.net.helioviewer.HelioviewerClient.get_jp2_header function. (#2904)

  • Add a new WSDL URL and port to support SunPy use of VSO instance at SDAC. (#2912)

  • Add support for COSMO K-Coronograph (KCOR) FITS data. (#2916)

  • Add logger messaging system based on AstropyLogger, cleaned up all warnings, removed all print statements. (#2980)

  • The function sunpy.image.coalignment.get_correlation_shifts now issues an error when the number of dimensions are not correct instead of a warning and returning None. (#2980)

  • The default location of the sunpy sample data has changed to be in the platform specific data directory as provided by appdirs. (#2993)

  • Add timeseries support for EVE/ESP level 1 data in sunpy.timeseries.sources (#3032)

  • The default style for Map plots have changed to reflect the changes in Astropy 3.2. (#3054)

  • sunpy.coordinates.ephemeris.get_body_heliographic_stonyhurst can now account for light travel time when computing the (apparent) body position, as long as the observer location is provided. (#3055)

  • Added a helper function (sunpy.map.header_helper.make_fitswcs_header()) that allows users to create a meta header for custom created sunpy.map.GenericMap. (#3083)

  • Map plotting now accepts the optional keyword clip_interval for specifying a percentile interval for clipping. For example, if the interval (5%, 99%) is specified, the bounds of the z axis are chosen such that the lowest 5% of pixels and the highest 1% of pixels are excluded. (#3100)

  • The new function get_horizons_coord enables querying JPL HORIZONS for the locations of a wide range of solar-system bodies, including spacecraft. (#3113)

Bug Fixes#
  • Fix the bug that prevented VSO queries for HMI data from downloading file without specifying a.Physobs. (#2621)

  • Fix sunpy.map.mapcube.MapCube.plot. The code had not been updated to support the changes to the wcsaxes helper functions. (#2627)

  • Replace all use of the deprecated sunpy.cm.get_cmap with matplotlib.cm.get_cmap to prevent deprecation warnings being raised. (#2635)

  • Fix generation of the coordinate transformation graph with Astropy 3.1.dev (#2636)

  • Prevent helioviewer from erroring when downloading file to a directory that does not exist. It will now create the directory when required. (#2642)

  • Fix transformations into/out of Heliographic Stonyhurst frame when the coordinate representation is Cartesian. (#2646)

  • Running the figure tests with setup.py test now saves the figures and the hashes to the same directory as setup.py. (#2658)

  • sunpy.instr.fermi.met_to_utc now returns the correct utc time which takes into account the leap seconds that have passed. (#2679)

  • Support passing Python file objects to sunpy.io.fits.write. (#2688)

  • Added DRMS to setup.py so sunpy[all] installs it as a dependency. (#2693)

  • Fix eve 0cs timeseries separator regex to support Python 3.7 (#2697)

  • Fix the bug which crashes LASCOMap for when ‘date-obs’ is reformatted again from a self applied function. (#2700)

  • Change all instances of quantity_allclose to astropy.units.allclose this prevents pytest being needed to import sunpy.coordinates on Astropy 3 (#2701)

  • Fix RHESSI obssum file downloading to include the final day in the time range. (#2714)

  • Raise an error when transforming between HPC and HCC frames if the observer is not the same. (#2725)

  • Replaces the existing LASCO C2 and C3 color maps with new ones that perform better with JP2 and Level 0.5, 1 data. (#2731)

  • Do not attempt to save a FITS header comment for a keyword which is not in the header. This prevents an error on saving some maps after the metadata had been modified but not the comments. (#2748)

  • Add support for HMIMap objects as input to sunpy.instr.aia.aiaprep. (#2749)

  • User can convert between HPC and HCC coordinates with different observers. This is implemented by automatically transforming the coordinate into HGS and then changing observer, and then transforming back to HCC. (#2754)

  • Changed default file type for Helioviewer to prevent decode errors. (#2771)

  • Increase figure size to avoid cutting off longer colormap names in sunpy.cm.show_colormaps. (#2824)

  • The sample data directory will no longer be created until files are downloaded to it. (#2836)

  • Timeseries and lightcurve will now respect updated config values for download directory. (#2844)

  • Always use _default_wrap_angle rather than hard coding a wrap angle in the init of a sunpy coordinate frame (#2853)

  • Ensure imageanimators only slice arrays with integers (#2856)

  • Fixed sunpy.io.fits.write to handle the keyword COMMENT correctly. (#2880)

  • If Carrington longitude (“crln_obs”) is found in the FITS header, Map converts this to the correct Heliographic longitude. (#2946)

  • sunpy.net.helio.hec.HECClient.time_query now resolves the correct input time format. (#2969)

  • Fixes the calculation of the solar rotation of coordinates and the differential rotation of sunpy.map.GenericMap. (#2972)

  • Added back the FERMI GBM client to sunpy.net.dataretriever. (#2983)

  • Fix bug in sunpy.net.hek which raised and error if a search returned zero results, now returns an empty sunpy.net.hek.HEKTable. (#3046)

  • AIAMap now uses the provided HAE coordinates instead of the provided HGS coordinates to determine the observer location. (#3056)

  • Correctly zero pad milliseconds in the sunpy.util.scraper.Scraper formatting to prevent errors when the millisecond value was less than 100. (#3063)

  • Fix sunpy.util.scraper.Scraper failing if a directory is not found on a remote server. (#3063)

  • Correctly extract observer location from MDI and EIT data (#3067)

  • Fix HGS <> HCRS test due to Ecliptic frame changes in astropy 3.2 (#3075)

  • Fixes bug when creating a timeseries from a URL and bug when creating a TimeSeries from older GOES/XRS fits files. (#3081)

  • Added rsun_obs. It returns a quantity in arcsec consistent with other sunpy.map.GenericMap and overwrites mapbase’s assumption of a photospheric limb as seen from Earth. (#3099)

  • Fixed bugs related to using plot and peek with the inline Matplotlib backend in Jupyter notebook. (#3103)

  • Make a correction to sunpy.coordinates.wcs_utils.solar_wcs_frame_mapping so that astropy.wcs.WCS objects are correctly converted to sunpy.coordinates.frames objects irrespective of the ordering of the axes. (#3116)

  • The solar_rotate_coordinate function returns a coordinate that accounts for the location of the new observer. (#3123)

  • Add support for rotation parameters to sunpy.map.header_helper.make_fitswcs_header(). (#3139)

  • Improve the implementation of differential_rotate the image warping when transforming Maps for differential rotation and change in observer position. (#3149)

  • Fix a bug where new helioviewer sources potentially cause ~sunpy.net.helioviewer.HelioviewerClient.data_sources to error. (#3162)

Improved Documentation#
  • Organise the gallery into sections based on example type and tidy up a little. (#2624)

  • Added gallery example showing the conversion of Helioprojective Coordinates to Altitude/Azimuth Coordinates to and back. (#2656)

  • Add contribution guidelines for the sunpy example gallery. (#2682)

  • Added a gallery example for “Downloading and plotting a HMI image” and “Creating a Composite map”. (#2746)

  • Added an example for sunpy.visualization.animator.ImageAnimatorWCS. (#2752)

  • Minor changes to the developer guide regarding sprint labels. (#2765)

  • Copyedited and corrected the solar cycles example. (#2770)

  • Changed “online” mark to “remote_data” and made formatting of marks consistent. (#2799)

  • Add a missing plot to the end of the units and coordinates guide. (#2813)

  • Added gallery example showing how to access the SunPy colormaps (#2865)

  • Added gallery example showing how to access the SunPy solar physics constants. (#2882)

  • Major clean up of the developer documentation. (#2951)

  • Overhaul of the install instructions for the guide section of our documentation. (#3147)

Trivial/Internal Changes#
  • parse_time now uses singledispatch underneath. (#2408)

  • Revert the handling of quantity_allclose now that astropy/astropy#7252 is merged. This also bumps the minimum astropy version to 3.0.2. (#2598)

  • Replace the subclasses of matplotlib Slider and Button in sunpy.visualization with partial functions. (#2613)

  • Sort the ana C source files before building to enable reproducible builds. (#2637)

  • We are now using towncrier to generate our changelogs. (#2644)

  • Moved figure tests to Python 3.6. (#2655)

  • Removed old metaclass used for Map and TimeSeries as we have now moved to Python 3.6. (#2655)

  • Updated astropy_helpers to v3.0.2. (#2655)

  • When running image tests, a comparison HTML page is now generated to show the generated images and expected images. (#2660)

  • Change to using pytest-cov for coverage report generation to enable support for parallel builds (#2667)

  • Use of textwrap to keep source code indented when multiline texts is used (#2671)

  • Fix misspelling of private attribute _default_heliographic_latitude in map. (#2730)

  • Miscellaneous fixes to developer docs about building sunpy’s documentation. (#2825)

  • Changed sunpy.instr.aia.aiaprep to update BITPIX keyword to reflect the float64 dtype. (#2831)

  • Remove warning from GenericMap.submap when using pixel Quantities as input. (#2833)

  • Remove the usage of six and all __future__ imports (#2837)

  • Fix SunPy Coordinate tests with Astropy 3.1 (#2838)

  • Stores entries from directories into database sorted by name. It adds mocks to the database user guide examples. (#2873)

  • Fix all DeprecationWarning: invalid escape sequence. (#2885)

  • Used unittest.mock for creating offline tests for simulating online tests for test_noaa.py (#2900)

  • Fix support for pip 19 and isolated builds (#2915)

  • Moved to using AppDirs as the place to host our configuration file. (#2922)

  • Users can now use fewer keywords in our ~sunpy.net.helioviewer.HelioviewerClient to access the available sources. Either by observatory and measurement or instrument and measurement as this much information is enough to get the source ID for most of the cases. (#2926)

  • Remove the pytest dependency on the GenericMap asdf tag. (#2943)

  • Fix initialization of VSOClient when no WSDL link is found. (#2981)

0.9.0#

New Features#
  • Added TimeUTime class to support utime. [#2409]

  • Example for fine-grained use of ticks and grids [#2435]

  • Maintiners Workflow Guide [#2411]

  • Decorator to append and/or prepend doc strings [#2386]

  • Adding python setup.py test --figure-only [#2557]

  • Fido.fetch now accepts pathlib.Path objects for path attribute.[#2559]

  • The HeliographicStonyhurst coordinate system can now be specified using a cartesian system, which is sometimes known as the “Heliocentric Earth equatorial” (HEEQ) coordinate system. [#2437]

API Changes#
  • sunpy.coordinates.representation has been removed. Longitude wrapping is now done in the constructor of the frames. [#2431]

  • Propagation of obstime in the coordinate frame transformation has changed, this means in general when transforming directly between frames (not SkyCoord) you will have to specify obstime in more places. [#2461]

  • Transforming between Heliographic Stonyhurst and Carrington now requires that obstime be defined and the same on both the input and output frames. [#2461]

  • Removed the figure return from .peek() [#2487]

Bug Fixes#
  • Improve TimeSeriesBase docstring [#2399]

  • Validate that pytest-doctestplus is installed [#2388]

  • Fix use of self.wcs in plot in mapbase [#2398]

  • Updated docstring with pointer to access EVE data for other levels [#2402]

  • Fix broken links and redirections in documentation [#2403]

  • Fixes Documentation changes due to NumPy 1.14 [#2404]

  • Added docstrings to functions in download.py [#2415]

  • Clean up database doc [#2414]

  • rhessi.py now uses sunpy.io instead of astropy.io [#2416]

  • Remove Gamma usage in Map [#2424]

  • Changed requirements to python-dateutil [#2426]

  • Clarify coordinate system definitions [#2429]

  • Improve Map Peek when using draw_grid [#2442]

  • Add HCC –> HGS test [#2443]

  • Testing the transformation linking SunPy and Astropy against published values [#2454]

  • Fixed title bug in sunpy.timeseries.rhessi [#2477]

  • Allow LineAnimator to accept a varying x-axis [#2491]

  • Indexing Bug Fix to LineAnimator [#2560]

  • Output sphinx warnings to stdout [#2553]

  • Docstring improvement for LineAnimator [#2514]

  • move the egg_info builds to circleci [#2512]

  • Added tests for TraceMap [#2504]

  • Fix HGS frame constructor and HPC calculate_distance with SkyCoord constructor. [#2463]

  • removed wavelnth keyword in meta desc of Maps to avoid using non standard FITS keyword like nan [#2456]

  • The documentation build now uses the Sphinx configuration from sphinx-astropy rather than from astropy-helpers.[#2494]

  • Migrate to hypothesis.strategies.datetimes [#2368]

  • Prevent a deprecation warning due to truth values of Quantity [#2358]

  • Print a warning when heliographic longitude is set to it’s default value of 0 [#2480]

  • parse_time now parses numpy.datetime64 correctly. [#2572]

0.8.5#

Bug Fixes#
  • Removed AstropyDeprecationWarning from sunpy.coordinates.representation [#2476]

  • Fix for NorthOffsetFrame under Astropy 3.0 [#2486]

  • Fix lightcurve tests under numpy dev [#2505]

  • Updated depecration link of radiospectra [#2481]

  • Fixed Padding values in some of the documentation pages [#2497]

  • Move documentation build to circleci [#2509]

  • Fix Issue #2470 hgs_to_hcc(heliogcoord, heliocframe) [#2502]

  • Fixing CompositeMap object so that it respects masked maps [#2492]

0.8.4#

Bug Fixes#
  • Improve detection of SkyCoord frame instantiation when distance is 1*u.one. This fixes a plotting bug with WCSAxes in Astropy 3.0 [#2465]

  • removed wavelnth keyword in meta desc of Maps to avoid using non standard FITS keyword like nan [#2427]

  • Change the default units for HPC distance from u.km to None. [#2465]

0.8.3#

Bug Fixes#
  • XRSClient now reports time ranges of files correctly. [#2364]

  • Make parse_time work with datetime64s and pandas series [#2370]

  • CompositeMap axes scaling now uses map spatial units [#2310]

  • Moved license file to root of repository and updated README file [#2326]

  • Fix docstring formatting for net.vso.attrs [#2309]]

  • Fix coloring of ticks under matplotlib 2.0 default style [#2320]

  • Always index arrays with tuples in ImageAnimator [#2320]

  • Added links to possible attrs for FIDO in guide [#2317] [#2289]

  • Updated GitHub Readme [#2281] [#2283]

  • Fix matplotlib / pandas 0.21 bug in examples [#2336]

  • Fixes the off limb enhancement example [#2329]

  • Changes to masking hot pixels and picking bright pixels examples [#2325] [#2319]

  • Travis CI fix for numpy-dev build [#2340]

  • Updated masking brightest pixel example [#2338]

  • Changed TRAVIS cronjobs [#2338]

  • Support array values for obstime for coordinates and transformations [#2342] [#2346]

  • Updated Gallery off limb enhance example [#2337]

  • Documentation fixes for VSO [#2354] [#2353]

  • All tests within the documentation have been fixed [#2343]

  • Change to using pytest-remotedata for our online tests [#2345]

  • Fixed upstream astropy/numpy documentation issues [#2359]

  • Documentation for Map improved [#2361]

  • Fix the output units of pixel_to_world [#2362]

  • Documentation for Database improved [#2355]

  • Added test for mapsave [#2365]

  • Documentation for Sun improved [#2369]

0.8.2#

Bug Fixes#
  • Shows a warning if observation time is missing [#2293]

  • Updates MapCube to access the correct properties of the namedtuple SpatialPair [#2297]

0.8.1#

Bug fixes#
  • Fixed TimeSeries test failures due to missing test files [#2273]

  • Refactored a GOES test to avoid a Py3.6 issue [#2276]

0.8.0#

New Features#
  • Solar differential rotation for maps and submaps included.

  • Solar rotation calculation and mapcube derotation now use sunpy coordinates.

  • Sample data now downloads automatically on import if not available and is now pluggable so can be used by affiliated packages. Shortcut names have been normalized and all LIGHTCURVE shortcuts have changed to TIMESERIES.

  • Calculation of points on an arc of a great circle connecting two points on the Sun.

  • Removed extract_time function from sunpy.time and also tests related to the function from sunpy.time.tests

  • User can now pass a custom time format as an argument inside sunpy.database.add_from_dir() in case the date-obs metadata cannot be read automatically from the files.

  • Add time format used by some SDO HMI FITS keywords

  • Now the sunpy.database.tables.display_entries() prints an astropy table.

  • Additional methods added inside the sunpy.database class to make it easier to display the database contents.

  • Remove unused sunpy.visualization.plotting module

  • Port the pyana wrapper to Python 3

  • Map.peek(basic_plot-True) no longer issues warnings

  • Remove the sunpy.map.nddata_compat module, this makes Map.data and Map.meta read only.

  • Add a NorthOffsetFrame class for generating HGS-like coordinate systems with a shifted north pole.

  • Remove deprecated VSOClient.show method.

  • Deprecate sunpy.wcs: sunpy.coordinates and sunpy.map now provide all that functionality in a more robust manner.

  • Added hdu index in sunpy.database.tables.DatabaseEntry as a column in the table.

  • Removed HelioviewerClient from the sunpy.net namespace. It should now be imported with from sunpy.net.helioviewer import HelioviewerClient.

  • Removed compatibility with standalone wcsaxes and instead depend on the version in astropy 1.3. SunPy now therefore depends on astropy>-1.3.

  • Update to TimeRange.__repr__; now includes the qualified name and id of the object.

  • A new sunpy.visualization.imageanimator.LineAnimator class has been added to animate 1D data. This has resulted in API change for the sunpy.visualization.imageanimator.ImageAnimator class. The updateimage method has been renamed to update_plot.

  • Drop support for Python 3.4.

  • SunPy now requires WCSAxes and Map.draw_grid only works with WCSAxes.

  • Helioprojective and HelioCentric frames now have an observer attribute which itself is a coordinate object (SkyCoord) instead of B0, L0 and D0 to describe the position of the observer.

  • GenericMap.draw_grid now uses WCSAxes, it will only work on a WCSAxes plot, this may be less performant than the previous implementation.

  • GenericMap.world_to_pixel and GenericMap.pixel_to_world now accept and return SkyCoord objects only.

  • GenericMap has a new property observer_coordinate which returns a SkyCoord describing the position of the observer.

  • GenericMap.submap now takes arguments of the form bottom_left and top_right rather than range_a and range_b. This change enables submap to properly handle rotated maps and take input in the form of SkyCoord objects.

  • When referring to physical coordinates Pair.x has been replaced with SpatialPair.axis1. This means values returned by GenericMap now differentiate between physical and pixel coordinates.

  • The physical radius of the Sun (length units) is now passed from Map into the coordinate frame so a consistent value is used when calculating distance to the solar surface in the HelioprojectiveFrame coordinate frame.

  • A new sunpy.visualization.imageanimator.ImageAnimatorWCS class has been added to animate N-Dimensional data with the associated WCS object.

  • Moved Docs to docs/ to follow the astropy style

  • Added SunPy specific warnings under util.

  • SunPy coordinate frames can now be transformed to and from Astropy coordinate frames

  • The time attribute for SunPy coordinate frames has been renamed from dateobs to obstime

  • Ephemeris calculations with higher accuracy are now available under sunpy.coordinates.ephemeris

  • Add support for SunPy coordinates to specify observer as a string of a major solar-system body, with the default being Earth. To make transformations using an observer specified as a string, obstime must be set.

  • Added VSO query result block level caching in the database module. This prevents re-downloading of files which have already been downloaded. Especially helpful in case of overlapping queries.

  • Change the default representation for the Heliographic Carrington frame so Longitude follows the convention of going from 0-360 degrees.

  • All Clients that are able to search and download data now have a uniform API that is search and fetch. The older functions are still there but are deprecated for 0.8.

Bug fixes#
  • Add tests for RHESSI instrument

  • Maps from Helioviewer JPEG2000 files now have correct image scaling.

  • Get and set methods for composite maps now use Map plot_settings.

  • Simplified map names when plotting.

  • Fix bug in wcs.convert_data_to_pixel where crpix[1] was used for both axes.

  • Fix some leftover instances of GenericMap.units

  • Fixed bugs in sun equations

  • sunpy.io.fits.read will now return any parse-able HDUs even if some raise an error.

  • VSOClient no longer prints a lot of XML junk if the query fails.

  • Fix Map parsing of some header values to allow valid float strings like ‘nan’ and ‘inf’.

  • Fix Map parsing of some header values to allow valid float strings like ‘nan’ and ‘inf’.

0.7.8#

  • The SunPy data directory “~/sunpy” is no longer created until it is used (issue #2018)

  • Change the default representation for the Heliographic Carrington frame so Longitude follows the convention of going from 0-360 degrees.

  • Fix for surface gravity unit.

  • Support for Pandas 0.20.1

0.7.7#

  • Fix errors with Numpy 1.12

0.7.6#

  • Add Astropy 1.3 Support

0.7.5#

  • Fix test failure (mapbase) with 1.7.4

  • Restrict supported Astropy version to 1.0<astropy<1.3

  • Add Figure test env to SunPy repo.

0.7.4#

  • Remove Map always forcing warnings on.

  • Map.center now uses Map.wcs to correctly handle rotation.

  • Fix link in coordinates documentation.

  • Update helioviewer URL to HTTPS (fixes access to Helioviewer).

  • Fix processing of TRACE and YOHKOH measurement properties.

  • Remove warnings when using Map.peek(basic_plot-True)

  • Update docstrings for HPC and HCC frames.

0.7.3#

  • Fix ConfigParser for Python 3.5.2 - This allows SunPy to run under Python 3.5.2

  • Fix incorrect ordering of keys in MapMeta

  • Add sunpy.util.scraper to the API documentation.

0.7.2#

  • Fixed bugs in sun equations

0.7.1#

  • Fix bug in wcs.convert_data_to_pixel where crpix[1] was used for both axes.

  • Fix some leftover instances of GenericMap.units

  • Fixed bugs in sun equations

  • Now the sunpy.database.tables.display_entries() prints an astropy table.

  • Additional methods added inside the sunpy.database class to make it easier to display the database contents.

  • sunpy.io.fits.read will now return any parse-able HDUs even if some raise an error.

  • VSOClient no longer prints a lot of XML junk if the query fails.

  • Remove unused sunpy.visualization.plotting module

  • Map.peek(basic_plot-True) no longer issues warnings

  • Remove the sunpy.map.nddata_compat module, this makes Map.data and Map.meta read only.

  • Add a NorthOffsetFrame class for generating HGS-like coordinate systems with a shifted north pole.

  • Remove deprecated VSOClient.show method.

  • Deprecate sunpy.wcs: sunpy.coordinates and sunpy.map now provide all that functionality in a more robust manner.

  • Added hdu index in sunpy.database.tables.DatabaseEntry as a column in the table.

  • Removed HelioviewerClient from the sunpy.net namespace. It should now be imported with from sunpy.net.helioviewer import HelioviewerClient.

  • Removed compatibility with standalone wcsaxes and instead depend on the version in astropy 1.3. SunPy now therefore depends on astropy>-1.3.

  • Update to TimeRange.__repr__; now includes the qualified name and id of the object.

  • Change the default representation for the Heliographic Carrington frame so Longitude follows the convention of going from 0-360 degrees.

  • Fix Map parsing of some header values to allow valid float strings like ‘nan’ and ‘inf’.

0.7.0#

  • Fixed test failures with numpy developer version.[#1808]

  • Added timeout parameter in sunpy.data.download_sample_data()

  • Fixed aiaprep to return properly sized map.

  • Deprecation warnings fixed when using image coalignment.

  • Sunpy is now Python 3.x compatible (3.4 and 3.5).

  • Added a unit check and warnings for map metadata.

  • Added IRIS SJI color maps.

  • Updated show_colormaps() with new string filter to show a subset of color maps.

  • Fixed MapCube animations by working around a bug in Astropy’s ImageNormalize

  • Remove vso.QueryResponse.num_records() in favour of len(qr)

  • Add a draw_rectangle helper to GenericMap which can plot rectangles in the native coordinate system of the map.

  • Added the ability to shift maps to correct for incorrect map location, for example.

  • Bug fix for RHESSI summary light curve values.

  • Mapcube solar derotation and coalignment now pass keywords to the routine used to shift the images, scipy.ndimage.interpolation.shift.

  • Add automatic registration of GenericMap subclasses with the factory as long as they define an is_datasource_for method.

  • Added functions flareclass_to_flux and flux_to_flareclass which convert between GOES flux to GOES class numbers (e.g. X12, M3.4).

  • Removed old sunpy.util.goes_flare_class()

  • Bug fix for RHESSI summary light curve values.

  • The MapCube.as_array function now returns a masked numpy array if at least one of the input maps in the MapCube has a mask.

  • Map superpixel method now respects maps that have masks.

  • Map superpixel method now accepts numpy functions as an argument, or any user-defined function.

  • Map superpixel method no longer has the restriction that the number of original pixels in the x (or y) side of the superpixel exactly divides the number of original pixels in the x (or y) side of the original map data.

  • sunpy.physics.transforms has been deprecated and the code moved into sunpy.physics.

  • Add the sunpy.coordinates module, this adds the core physical solar coordinates frame within the astropy coordinates framework.

  • Added ability of maps to draw contours on top of themselves (draw_contours)

  • Added concatenate functionality to lightcurve base class.

  • Fix Map to allow astropy.io.fits Header objects as valid input for meta arguments.

  • Added an examples gallery using sphinx-gallery.

  • API clean up to constants. Removed constant() function which is now replaced by get().

  • Prevent helioviewer tests from checking access to the API endpoint when running tests offline.

  • GenericMap.units is renamed to GenericMap.spatial_units to avoid confusion with NDData.unit.

  • GenericMap now has a coordinate_frame property which returns an astropy.coordinates frame with all the meta data from the map populated.

  • GenericMap now has a _mpl_axes method which allows it to be specified as a projection to matplotlib methods and will return a WCSAxes object with WCS projection.

0.6.5#

  • The draw_grid keyword of the peek method of Map now accepts booleans or astropy quantities.

  • Fix bug in wcs.convert_data_to_pixel where crpix[1] was used for both axes.

  • Fixed bugs in sun equations

0.6.4#

  • Bug fix for rhessi summary lightcurve values.

  • Fix docstring for pixel_to_data and data_to_pixel.

  • Fix the URL for the Helioviewer API. (This fixes Helioviewer.)

  • Fix the way reshape_image_to_4d_superpixel checks the dimension of the new image.

  • Fix Map to allow astropy.io.fits Header objects as valid input for meta arguments.

  • Prevent helioviewer tests from checking access to API when running tests in offline mode.

0.6.3#

  • Change setup.py extras to install suds-jurko not suds.

0.6.2#

  • Changed start of GOES 2 operational time range back to 1980-01-04 so data from 1980 can be read into GOESLightCurve object

  • Fix bug with numpy 1.10

  • update astropy_helpers

  • Added new sample data

0.6.1#

  • Fixed MapCube animations by working around a bug in Astropy’s ImageNormalize

  • Small fix to RTD builds for Affiliated packages

  • SunPy can now be installed without having to install Astropy first.

  • MapCubes processed with coalignment.apply_shifts now have correct metadata.

  • Multiple fixes for WCS transformations, especially with solar-x, solar-y CTYPE headers.

0.6.0#

  • Enforced the use of Astropy Quantities through out most of SunPy.

  • Dropped Support for Python 2.6.

  • Remove old style string formatting and other 2.6 compatibility lines.

  • Added vso like querying feature to JSOC Client.

  • Refactor the JSOC client so that it follows the .query() .get() interface of VSOClient and UnifedDownloader.

  • Provide __str__ and __repr__ methods on vso QueryResponse deprecate .show().

  • Downloaded files now keep file extensions rather than replacing all periods with underscores.

  • Update to TimeRange API, removed t1 and t0, start and end are now read-only attributes.

  • Added ability to download level3 data for lyra Light Curve along with corresponding tests.

  • Added support for gzipped FITS files.

  • Add STEREO HI Map subclass and color maps.

  • Map.rotate() no longer crops any image data.

  • For accuracy, default Map.rotate() transformation is set to bi-quartic.

  • sunpy.image.transform.affine_transform now casts integer data to float64 and sets NaN values to 0 for all transformations except scikit-image rotation with order <- 3.

  • CD matrix now updated, if present, when Map pixel size is changed.

  • Removed now-redundant method for rotating IRIS maps since the functionality exists in Map.rotate()

  • Provide __str__ and __repr__ methods on vso QueryResponse deprecate .show()

  • SunPy colormaps are now registered with matplotlib on import of sunpy.cm

  • sunpy.cm.get_cmap no longer defaults to ‘sdoaia94’

  • Added database url config setting to be setup by default as a sqlite database in the sunpy working directory

  • Added a few tests for the sunpy.roi module

  • Added capability for figure-based tests

  • Removed now-redundant method for rotating IRIS maps since the functionality exists in Map.rotate().

  • SunPy colormaps are now registered with matplotlib on import of sunpy.cm.

  • sunpy.cm.get_cmap no longer defaults to ‘sdoaia94’.

  • Added database url config setting to be setup by default as a sqlite database in the sunpy working directory.

  • Added a few tests for the sunpy.roi module.

  • Refactored mapcube co-alignment functionality.

  • Removed sample data from distribution and added ability to download sample files

  • Changed start of GOES 2 operational time range back to 1980-01-04 so data from 1980 can be read into GOESLightCurve object

  • Require JSOC request data calls have an email address attached.

  • Calculation of the solar rotation of a point on the Sun as seen from Earth, and its application to the de-rotation of mapcubes.

  • Downloaded files now keep file extensions rather than replacing all periods with underscores

  • Fixed the downloading of files with duplicate names in sunpy.database

  • Removed sample data from distribution and added ability to download sample files.

  • Added the calculation of the solar rotation of a point on the Sun as seen from Earth, and its application to the de-rotation of mapcubes.

  • Changed default for GOESLightCurve.create() so that it gets the data from the most recent existing GOES fits file.

  • Map plot functionality now uses the mask property if it is present, allowing the plotting of masked map data

  • Map Expects Quantities and returns quantities for most parameters.

  • Map now used Astropy.wcs for world <-> pixel conversions.

  • map.world_to_pixel now has a similar API to map.pixel_to_world.

  • map.shape has been replaced with map.dimensions, which is ordered x first.

  • map.rsun_arcseconds is now map.rsun_obs as it returns a quantity.

  • Map properties are now named tuples rather than dictionaries.

  • Improvement for Map plots, standardization and improved color tables, better access to plot variables through new plot_settings variable.

  • Huge improvements in Instrument Map doc strings. Now contain instrument descriptions as well as reference links for more info.

  • net.jsoc can query data series with time sampling by a Sample attribute implemented in vso.

  • MapCube.plot and MapCube.peek now support a user defined plot_function argument for customising the animation.

  • Added new sample data file, an AIA cutout file.

  • Moved documentation build directory to doc/build

0.5.5#

  • Changed default for GOESLightCurve.create() so that it gets the data from the most recent existing GOES fits file.

  • Improvements to the Map documentation.

  • Typo fixes in sunpy.wcs documentation.

0.5.4#

  • sunpy.image.transform.affine_transform now casts integer data to float64 and sets NaN values to 0 for all transformations except scikit-image rotation with order <- 3.

  • Updated SWPC/NOAA links due to their new website.

  • Exposed the raw AIA color tables in sunpy.cm.color_tables.

  • Fixes map compatibility with Astropy 1.0.x.

0.5.3#

  • Goes peek() plot now works with matplotlib 1.4.x

  • The ANA file reading C extensions will no longer compile under windows. Windows was not a supported platform for these C extensions previously.

0.5.2#

  • If no CROTA keyword is specified in Map meta data, it will now default to 0 as specified by the FITS WCS standard.

  • Map now correctly parses and converts the CD matrix, as long as CDELT is specified as well. (Fixes SWAP files)

  • Fix of HELIO webservice URLs

  • MapCube.plot() is now fixed and returns a matplotlib.animation.FuncAnimation object.

0.5.1#

  • MAJOR FIX: map.rotate() now works correctly for all submaps and off center rotations.

  • HELIO URL updated, queries should now work as expected.

  • All tabs removed from the code base.

  • All tests now use tempfile rather than creating files in the current directory.

  • Documentation builds under newer sphinx versions.

  • ANA and JP2 tests are skipped if dependencies are missing.

  • ANA tests are skipped on windows.

0.5.0#

  • Added additional functionality to the GOES module i.e. the ability to calculate GOES temperature and emission measure from GOES fluxes.

  • changed _maps attribute in MapCube to a non-hidden type

  • Added Nobeyama Radioheliograph data support to Lightcurve object.

  • Fixed some tests on map method to support Windows

  • Added a window/split method to time range

  • Updates to spectrogram documentation

  • Added method Database.add_from_hek_query_result to HEK database

  • Added method Database.download_from_vso_query_result

  • GOES Lightcurve now makes use of a new source of GOES data, provides metadata, and data back to 1981.

  • Removed sqlalchemy as a requirement for SunPy

  • Added support for NOAA solar cycle prediction in lightcurves

  • Some basic tests for GenericLightCurve on types of expected input.

  • Fix algorithm in sunpy.sun.equation_of_center

  • Added Docstrings to LightCurve methods.

  • Added tests for classes in sunpy.map.sources. Note that some classes (TRACE, RHESSI) were left out because SunPy is not able to read their FITS files.

  • Added functions that implement image coalignment with support for MapCubes.

  • Cleaned up the sunpy namespace, removed .units, /ssw and .sphinx. Also moved .coords .physics.transforms.

  • Added contains functionality to TimeRange module

  • Added t-‘now’ to parse_time to provide utcnow datetime.

  • Fixed time dependent functions (.sun) to default to t-‘now’

  • Fixed solar_semidiameter_angular_size

  • Improved line quality and performances issues with map.draw_grid()

  • Remove deprecated make_map command.

0.4.2#

  • Fixes to the operational range of GOES satellites

  • Fix the URL for HELIO queries.

0.4.1#

  • Fix map.rotate() functionality

  • Change of source for GOES data.

  • Fix EIT test data and sunpy FITS saving

  • Some documentation fixes

  • fix file paths to use os.path.join for platform independence.

0.4.0#

  • Major documentation refactor. A far reaching re-write and restructure.

  • Add a SunPy Database to store and search local data.

  • Add beta support for querying the HELIO HEC

  • Add beta HEK to VSO query translation.

  • Add the ability to download the GOES event list.

  • Add support for downloading and querying the LYTAF database.

  • Add support for ANA data.

  • Updated sun.constants to use astropy.constants objects which include units, source, and error instide. For more info check out https://docs.astropy.org/en/latest/constants/index.html

  • Add some beta support for IRIS data products

  • Add a new MapCubeAnimator class with interactive widgets which is returned by mapcube.peek().

  • The Glymur library is now used to read JPEG2000 files.

  • GOESLightCurve now supports all satellites.

  • Add support for VSO queries through proxies.

  • Fix apparent Right Ascension calculations.

  • LightCurve meta data member now an OrderedDict Instance

0.3.2#

  • Pass draw_limb arguments to patches.Circle

  • Pass graw_grid arguments to pyplot.plot()

  • Fix README code example

  • Fix Documentation links in potting guide

  • Update to new EVE data URL

  • Update LogicalLightcurve example in docs

  • Improved InteractiveVSOClient documentation

  • GOESLightCurve now fails politely if no data is available.

Known Bugs:

  • sunpy.util.unit_conversion.to_angstrom does not work if ‘nm’ is passed in.

0.3.1#

  • Bug Fix: Fix a regression in CompositeMap that made contor plots fail.

  • Bug Fix: Allow Map() to accept dict as metadata.

  • Bug Fix: Pass arguments from Map() to io.read_file.

0.3.0#

  • Removal of Optional PIL dependency

  • Parse_time now looks through nested lists/tuples

  • Draw_limb and draw_grid are now implemented on MapCube and CompositeMap

  • Calculations for differential rotation added

  • mapcube.plot() now runs a mpl animation with optional controls

  • A basic Region of Interest framework now exists under sunpy.roi

  • STEREO COR colour maps have been ported from solarsoft.

  • sunpy.time.timerange has a split() method that divides up a time range into n equal parts.

  • Added download progress bar

  • pyfits is deprecated in favor of Astropy

spectra:

  • Plotting has been refactorted to use a consistent interface

  • spectra now no-longer inherits from numpy.ndarray instead has a .data attribute.

Map: * map now no-longer inherits from numpy.ndarray instead has a .data attribute. * make_map is deprecated in favor of Map which is a new factory class * sunpy.map.Map is now sunpy.map.GenericMap * mymap.header is now mymap.meta * attributes of the map class are now read only, changes have to be made through map.meta * new MapMeta class to replace MapHeader, MapMeta is not returned by sunpy.io * The groundwork for GenericMap inheriting from astropy.NDData has been done, there is now a NDDataStandin class to provide basic functionality.

io: * top level file_tools improved to be more flexible and support multiple HDUs * all functions in sunpy.io now assume multiple HDUs, even JP2 ones. * there is now a way to override the automatic filetype detection * Automatic fits file detection improved * extract_waveunit added to io.fits for detection of common ways of storing wavelength unit in fits files.

  • A major re-work of all internal imports has resulted in a much cleaner namespace, i.e. sunpy.util.util is no longer used to import util.

  • Some SOHO and STEREO files were not reading properly due to a date_obs parameter.

  • Sunpy will now read JP2 files without a comment parameter.

  • Memory leak in Crotate patched

  • Callisto: Max gap between files removed

0.2.0#

  • Completely re-written plotting routines for most of the core datatypes.

  • JPEG 2000 support as an input file type.

  • Improved documentation for much of the code base, including re-written installation instructions.

  • New lightcurve object

    • LYRA support

    • GOES/XRS support

    • SDO/EVE support

  • New Spectrum and Spectrogram object (in development)

    • Spectrogram plotting routines

    • Callisto spectrum type and support

    • STEREO/SWAVES support

  • Map Object

    • Added support for LASCO, Yohkoh/XRT maps

    • A new CompositeMap object for overlaying maps

    • Resample method

    • Superpixel method

    • The addition of the rotate() method for 2D maps.

What’s New in sunpy 6.0?#

The SunPy Project is pleased to announce the 6.0 release of the sunpy core package.

On this page, you can read about some of the big changes in this release.

sunpy 6.0 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

PUT STATS HERE

Updates to minimum dependencies#

The minimum required version of Python has been updated to 3.10.

The minimum required versions of core dependencies have been updated:

  • astropy >= 5.2.0

The minimum required versions of these optional dependencies has also been updated:

  • asdf >= 2.12.0

  • asdf-astropy >= 0.2.0

  • beautifulsoup4 >= 4.11.0

  • cdflib >= 0.4.4

  • dask >= 2022.5.2

  • h5netcdf >= 1.0.0

  • h5py >= 3.7.0

  • lxml >= 4.9.0

  • opencv-python >= 4.6.0.66

  • pandas >= 1.4.0

  • reproject >= 0.9.0

  • requests >= 2.28

  • scikit-image >= 0.19.0

  • scipy >= 1.8.0

  • spiceypy >= 5.0.0

  • tqdm >= 4.64.0

  • zeep >= 4.1.0

Removal of sunpy.database#

The sunpy.database module has not been actively maintained for over a year now and has a number of outstanding issues. It has been deprecated since sunpy 4.1, and since there have been no major objections from the community since then we have completely removed sunpy.database in sunpy 6.0. If you are interested in seeing a replacement for sunpy.database, either inside sunpy or as a third-party package, please join the discussion thread at https://community.openastronomy.org/t/deprecating-sunpy-database/495.

Arguments for reproject_to()#

Arguments for this method have been changed to be keyword only after the target WCS argument. This was raising a warning since sunpy 4.1.

Removal of sunpy.net.helioviewer.HelioViewerClient#

sunpy.net.helioviewer has been deprecated since sunpy v4.1 and has now been removed. Users should instead use the hvpy package. This package provides a Python wrapper around the Helioviewer API and is maintained by the Helioviewer Project. The hvpy package supersedes the sunpy.net.helioviewer module.

Arguments for sunpy.timeseries.GenericTimeSeries.peek() are now keywords only#

The arguments for sunpy.timeseries.GenericTimeSeries.peek() have been changed to be keyword only. This means that you must now specify the arguments by name, rather than by position and has been done to make the API more consistent. This has been raising a warning since sunpy 4.1 and is now an error.

Fix filename sanitization for downloaded files from the VSO#

The VSOClient has been sanitizing filenames to ensure that they are valid on all platforms. However, we have now fixed the sanitization to be more conservative, to follow the NFKC Unicode normalization, which is the recommended normalization for filenames on most platforms. This now does not replace periods, does not change letter case and do not leave Unicode characters decomposed. An example of this is that the filename will now be replaced with instead of being left out of the filename. Another example is that the filename “aia.lev1.171A_2020_06_07T06_33_09.35Z.image_lev1.fits” used to be replaced with “aia_lev1_171a_2020_06_07t06_33_09_35z_image_lev1.fits” and now will not be.

Deprecate positional arguments in sunpy.map.GenericMap.plot()#

The arguments for sunpy.map.GenericMap.plot() have been changed to being keyword only. Pass them as keyword arguments (e.g., ..., title=True, ...) instead.

What’s New in sunpy 5.1?#

The SunPy Project is pleased to announce the 5.1 release of the sunpy core package.

On this page, you can read about some of the big changes in this release.

sunpy 5.1 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

This release of sunpy contains 361 commits in 102 merged pull requests closing 27 issues from 19 people, 7 of which are first-time contributors to sunpy.

  • 361 commits have been added since 5.0

  • 27 issues have been closed since 5.0

  • 102 pull requests have been merged since 5.0

  • 19 people have contributed since 5.0

  • 7 of which are new contributors

The people who have contributed to the code for this release are:

  • Alasdair Wilson

  • Albert Y. Shih

  • Brett J Graham *

  • David Stansby

  • Hannah Collier *

  • Jan Gieseler

  • Kurt McKee *

  • Laura Hayes

  • Nabil Freij

  • Paul Wright *

  • Samuel Badman *

  • Samuel J. Van Kooten *

  • Shane Maloney

  • Steven Christe

  • Stuart Mumford

  • Trestan Simon *

  • Will Barnes

Where a * indicates that this release contains their first contribution to sunpy.

Calculating the amount of solar eclipse#

In anticipation of the upcoming “Great North American Eclipse” on April 8, 2024, there is a new function sunpy.coordinates.sun.eclipse_amount() that returns how much of the Sun is occulted by the Moon at the specified observer location and time. The output can be used to determine the start/end times of partial eclipse and of totality.

Obtaining solar-eclipse information

Obtaining solar-eclipse information

Computations using SPICE kernels#

The SPICE observation geometry information system is being increasingly used by space missions to describe the locations of spacecraft and the time-varying orientations of reference frames. The new sunpy.coordinates.spice module enables the use of the SkyCoord API to perform SPICE computations such as the location of bodies or the transformation of a vector from one coordinate frame to another coordinate frame. Although SPICE kernels can define coordinate frames that are very similar to the frames that sunpy.coordinates already provides, there will very likely be slight differences. Using sunpy.coordinates.spice will ensure that the definitions are exactly what the mission specifies and that the results are identical to other implementations of SPICE (e.g., CSPICE or Icy).

Note

sunpy.coordinates.spice requires the optional dependency spiceypy to be installed.

Coordinates computations using SPICE kernels

Coordinates computations using SPICE kernels

Support for GONG Synoptic Data#

The Global Oscillation Network Group (GONG) are a network of observatories located around the Earth that produce photospheric magnetograms every hour. These magnetograms are used to derive synoptic maps which show a full-surface picture of the solar magnetic field. A new map source, GONGSynopticMap, has been added to support these synoptic data products. This means that GONG synoptic data products can now be loaded directly into Map objects. If you have pfsspy installed, this new map source will be used instead of the one built into pfsspy.

New Method for Determining Visibility of Helioprojective Coordinates#

A new method has been added to the Helioprojective frame to determine whether a coordinate is visible or not. Visibility is determined as whether or not the coordinate is behind the limb relative to the observer coordinate. See the documentation for is_visible() for more information.

Improved Support for WISPR Data#

The default colorbar limits and stretching for WISPRMap have been improved to better emphasize the coronal structures present in many images. Metadata handling has also been improved, including handling non-integer processing levels (e.g. “L2b”) and adding more intuitive labels for the different detectors (e.g. “inner” and “outer” instead of “1” and “2”).

What’s New in sunpy 5.0?#

The SunPy project is pleased to announce the 5.0 release of the sunpy core package.

On this page, you can read about some of the big changes in this release.

sunpy 5.0 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

This release of sunpy contains 1279 commits in 182 merged pull requests closing 115 issues from 36 people, 21 of which are first-time contributors.

  • 1279 commits have been added since 4.1

  • 115 issues have been closed since 4.1

  • 182 pull requests have been merged since 4.1

  • 36 people have contributed since 4.1

  • 21 of which are new contributors

The people who have contributed to the code for this release are:

  • Akash Verma

  • Akhoury Shauryam *

  • Akshit Tyagi *

  • Alasdair Wilson

  • Albert Y. Shih

  • Ansh Dixit *

  • Aritra Sinha *

  • Aryan Shukla *

  • Conor MacBride

  • Daniel Garcia Briseno

  • David Stansby

  • Ed Behn *

  • Jan Gieseler

  • Krish Agrawal

  • Laura Hayes

  • Lazar Zivadinovic

  • Marius Giger *

  • Matt Wentzel-Long *

  • Nabil Freij

  • Naveen Srinivasan *

  • Nick Murphy

  • Nischal Singh *

  • OussCHE *

  • Stuart Mumford

  • Suleiman Farah *

  • Syed Md Mihan Chistie *

  • Tan Jia Qing *

  • Timo Laitinen *

  • Will Barnes

  • William Jamieson *

  • ejm4567 *

  • pradeep *

  • ryuusama *

  • tal66 *

Where a * indicates that this release contains their first contribution to sunpy.

Updates to minimum dependencies#

The minimum required versions of several core dependencies have been updated:

  • Python 3.9

  • astropy 5.0.1

  • numpy 1.21.0

  • parfive 2.0.0

We are aware the change in the parfive minimum version is a release earlier than our dependency policy allows for. However, due to significant issues that parfive v2.0.0 solves and changes to remote servers, we have decided to increase it to improve the user experience when downloading files.

The minimum required versions of these optional dependencies has also been updated:

  • Matplotlib 3.5.0

  • dask 2021.4.0

  • h5netcdf 0.11.0

  • pandas 1.2.0

  • scikit-image 0.18.0

  • scipy 1.7.0

  • cdflib 0.3.20

New documentation structure#

There has been a major overhaul of the documentation structure. The documentation is now organised into four major distinct sections:

  • Tutorial, a step-by-step introduction to sunpy.

  • How-To Guides, a set of small self-contained guides for getting specific tasks done.

  • Topic Guides, in-depth explanations that detail how and why different parts of sunpy work.

  • Reference, a listing of the contents of the sunpy package.

OS agnostic packaging#

A pure Python sunpy distribution is now published on PyPI with each release. This distribution contains all the functionality of sunpy apart from the code for reading ANA files. pip will default to installing the pure Python distribution instead of the source distribution on platforms other than Linux (x86-64) and macOS (x86-64 and ARM64). This should mean simpler and faster installs on such platforms, which include the Raspberry Pi as well as some cloud computing services.

Getting pixels along a coordinate path#

A new function, sunpy.map.pixelate_coord_path(), has been added to return all pixels that are intersected by a given coordinate path. This replaces the now-deprecated sunpy.map.extract_along_coord. See the following pages for examples of how to use pixelate_coord_path(),

Extracting intensity of a map along a line

Extracting intensity of a map along a line

Drawing and using a Great Arc

Drawing and using a Great Arc

1 minute GOES data now available#

Searching for GOES data now returns 1 minute averaged data in addition to the high-cadence data for GOES 13-17. As with all Fido clients, the results of a search will return all available data. For example, searching over a single day will provide multiple results for the different satellites available and the different resolution data. This can be seen in the Resolution column below. Here, the flx1s refers to the high-cadence 1s data and the avg1m refers to the averaged 1 minute sampling data. In the past, sunpy only provided a search over the high-cadence data.

>>> from sunpy.net import Fido, attrs as a
>>> res = Fido.search(a.Time("2022-02-15", "2022-02-15"),
...                   a.Instrument("XRS"))  
>>> res  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

4 Results from the XRSClient:
Source: <8: https://umbra.nascom.nasa.gov/goes/fits
8-15: https://www.ncei.noaa.gov/data/goes-space-environment-monitor/access/science/
16-17: https://data.ngdc.noaa.gov/platforms/solar-space-observing-satellites/goes/

       Start Time               End Time        ... Provider Resolution
----------------------- ----------------------- ... -------- ----------
2022-02-15 00:00:00.000 2022-02-15 23:59:59.999 ...     NOAA      flx1s
2022-02-15 00:00:00.000 2022-02-15 23:59:59.999 ...     NOAA      avg1m
2022-02-15 00:00:00.000 2022-02-15 23:59:59.999 ...     NOAA      flx1s
2022-02-15 00:00:00.000 2022-02-15 23:59:59.999 ...     NOAA      avg1m

If you want to download just the 1 minute data, you can do so by specifying the resolution in the query by passing the a.Resolution attribute. If you want the 1s resolution data, you would instead pass a.Resolution("flx1s") instead.

>>> res = Fido.search(a.Time("2022-02-15", "2022-02-15"),
...                   a.Instrument("XRS"), a.Resolution("avg1m"))  
>>> res  
<sunpy.net.fido_factory.UnifiedResponse object at ...>
Results from 1 Provider:

2 Results from the XRSClient:
Source: <8: https://umbra.nascom.nasa.gov/goes/fits
8-15: https://www.ncei.noaa.gov/data/goes-space-environment-monitor/access/science/
16-17: https://data.ngdc.noaa.gov/platforms/solar-space-observing-satellites/goes/

       Start Time               End Time        ... Provider Resolution
----------------------- ----------------------- ... -------- ----------
2022-02-15 00:00:00.000 2022-02-15 23:59:59.999 ...     NOAA      avg1m
2022-02-15 00:00:00.000 2022-02-15 23:59:59.999 ...     NOAA      avg1m

sunpy.database deprecation#

The sunpy.database module is no longer actively maintained and has a number of outstanding issues. It has now been formally deprecated, and will be removed in sunpy 6.0. If you are using sunpy.database and would like to see a replacement, please join the discussion thread at https://community.openastronomy.org/t/deprecating-sunpy-database/495.

Deprecation of IO readers#

sunpy.io.cdf, sunpy.io.file_tools and sunpy.io.jp2 sub-modules have been deprecated, and will be removed in version 5.1.

These are designed for internal use only, and removing them from the public API gives the developers more flexibility to modify them without impacting users.

What’s New in sunpy 4.1?#

The SunPy project is pleased to announce the 4.1 release of the sunpy core package.

On this page, you can read about some of the big changes in this release.

sunpy 4.1 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

This release of sunpy contains 1408 commits in 191 merged pull requests closing 86 issues from 28 people, 18 of which are first-time contributors.

  • 1408 commits have been added since 4.0

  • 86 issues have been closed since 4.0

  • 191 pull requests have been merged since 4.0

  • 28 people have contributed since 4.0

  • 18 of which are new contributors

The people who have contributed to the code for this release are:

  • Akash Verma *

  • Alasdair Wilson

  • Albert Y. Shih

  • Alex Kaszynski *

  • Alex W *

  • Andy Tang *

  • Chris Bard *

  • Conor MacBride

  • Daniel Garcia Briseno *

  • David Stansby

  • Jan Gieseler *

  • Jia Qing *

  • Karthikeyan Singaravelan *

  • Krish Agrawal *

  • Laura Hayes

  • Lazar Zivadinovic

  • Marius Giger *

  • Matt Wentzel-Long *

  • Nabil Freij

  • Naveen Srinivasan *

  • Stuart Mumford

  • Timo Laitinen *

  • Will Barnes

  • William Jamieson *

  • William Russell *

  • tal66 *

Where a * indicates that this release contains their first contribution to sunpy.

Extracting map values along coordinate paths#

It is now easy to extract data values from a GenericMap along a curve specified by set of coordinates using the new sunpy.map.extract_along_coord function. This is done by applying Bresenham’s line algorithm between the consecutive coordinates, in pixel space, and then indexing the data array of the map at those points.

Easier reprojecting to Carrington and Stonyhurst coordinates#

sunpy.map.header_helper has a new make_heliographic_header() function that simplifies creating map headers that span the whole solar surface in Carrington or Stonyhurst coordinates. When used in combination with sunpy.map.GenericMap.reproject_to() this greatly simplifies reprojecting helioprojective maps into heliographic full-Sun maps.

Creating Carrington Maps

Creating Carrington Maps

Drawing the solar equator and prime meridian#

The new sunpy.visualization.drawing module can be used to draw the solar equator and prime meridian (zero Carrington longitude) on a plot with Astropy WCS Axes.

Plotting the solar equator and prime meridian

Plotting the solar equator and prime meridian

Sample data downloaded on demand#

The sample data files provided through sunpy.data.sample are now downloaded individually on demand rather than being all downloaded upon import of that module. All the sample data files can still be downloaded at once by calling sunpy.data.sample.download_all().

sunpy.database deprecation#

The sunpy.database module is no longer actively maintained and has a number of outstanding issues. It is anticipated that sunpy.database will be formally deprecated in sunpy 5.0 and removed in sunpy 6.0. If you are using sunpy.database and would like to see a replacement, please join the discussion thread at https://community.openastronomy.org/t/deprecating-sunpy-database/495.

sunpy.visualization.limb moved#

sunpy.visualization.limb.draw_limb has been moved into the sunpy.visualization.drawing module and renamed sunpy.visualization.drawing.limb(). The sunpy.visualization.limb module will remain working, but is deprecated and will be removed in version 5.1.

HelioviewerClient deprecated#

The Helioviewer Project now maintains a Python Wrapper called hvpy. As such, in consultation with the Helioviewer Project, the sunpy.net.helioviewer module is deprecated and will be removed in sunpy 5.1.

Changes to timeseries plotting#

To make plotting a timeseries as source-independent as possible, a number of source-specific plot customisations in peek() and plot() methods have been removed. See the changelog for full details on what has changed.

To harmonize different peek() and plot() signatures, all non-keyword arguments to these methods are deprecated. To avoid a warning pass all arguments with keywords (e.g. plot(title='my plot title')) instead.

Updated data downloader dependency#

The package that handles downloading data from remote sources, parfive, has had a recent major release to version 2.0. This new version comes with major usability improvements: removal of incomplete files and major error reporting upgrades.

sunpy users are encouraged to upgrade parfive to benefit from these improvements. To upgrade you can use pip:

$ pip install -U parfive

or conda:

$ conda update parfive

What’s New in SunPy 4.0?#

The SunPy project is pleased to announce the 4.0 release of the sunpy core package.

On this page, you can read about some of the big changes in this release.

SunPy 4.0 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

This release of sunpy contains 1310 commits in 182 merged pull requests closing 83 issues from 29 people, 19 of which are first-time contributors to sunpy.

  • 1310 commits have been added since 3.1

  • 83 issues have been closed since 3.1

  • 182 pull requests have been merged since 3.1

  • 29 people have contributed since 3.1

  • 19 of which are new contributors

The people who have contributed to the code for this release are:

  • Akash Verma *

  • Alasdair Wilson *

  • Albert Y. Shih

  • Alex Kaszynski *

  • Alex W *

  • Andy Tang *

  • Anubhav Sinha *

  • Conor MacBride

  • Daniel Garcia Briseno *

  • David Stansby

  • Devansh Shukla *

  • Jan Gieseler *

  • Jeffrey Aaron Paul

  • Jia Qing *

  • Karthikeyan Singaravelan *

  • Krish Agrawal *

  • Laura Hayes

  • Lazar Zivadinovic

  • Nabil Freij

  • Noah Altunian *

  • Rohan Sharma *

  • Samriddhi Agarwal

  • Stuart Mumford

  • Thomas Braccia *

  • Tim Gates *

  • Will Barnes

  • William Russell *

  • cbard *

  • pre-commit-ci[bot] *

Where a * indicates that this release contains their first contribution to sunpy.

Increase in required package versions#

We have bumped the minimum version of several packages we depend on; these are the new minimum versions:

  • python >= 3.8

Improvements to map and image rotation#

There have been significant improvements to sunpy.map.GenericMap.rotate() and sunpy.image.transform.affine_transform(). Most notably, there is now comprehensive support for having not-a-number (NaN) values in pixels, with NaNs now preserved across image rotation. Accordingly, the default behavior for these functions has been changed to use NaN for the pixels in the output image that are beyond the extent of the input image. To obtain the prior behavior, which used zero by default for such pixels, the missing needs to be explicitly set to zero.

The default rotation function used internally has been switched to scipy.ndimage.affine_transform() from skimage.transform.warp() due to changes in the latest release of scikit-image, although the differences between the rotation results are small. Since bicubic interpolation by scipy.ndimage.affine_transform() is sufficient in quality for the typical user, we have changed the default interpolation order for sunpy.map.GenericMap.rotate() to bicubic interpolation (order=3) instead of biquartic interpolation (order=4) for improved performance under default settings. sunpy.image.transform.affine_transform() has always defaulted to bicubic interpolation (order=3).

Also, there is now the option to rotate using OpenCV. The rotation function to use can be selected via the method keyword argument. New rotation functions beyond these three can be added using the new decorator add_rotation_function().

Improved return types of HEK queries#

The ‘event_endtime’, ‘event_starttime’ and ‘event_peaktime’ columns in a HEK query are now returned as Time objects.

Drawing Carrington coordinate grids#

It is now easy to draw Carrington coordinate grids on top of maps using sunpy.map.GenericMap.draw_grid() by specifying system='carrington'. See Plotting a coordinate grid for an example.

Better printing of metadata#

Printing a MetaDict now prints each entry on a new line, making it much easier to read:

>>> from sunpy.data.sample import AIA_171_IMAGE  
>>> from sunpy.map import Map
>>> m = Map(AIA_171_IMAGE)  
>>> print(m.meta)  
simple: True
bitpix: -32
naxis: 2
naxis1: 1024
naxis2: 1024
...

Deprecation of sunpy.image.coalignment#

The sunpy.image.coalignment module has been deprecated and will be removed in version 4.1. Users should instead use sunkit_image.coalignment which includes identical functionality and an identical API. The reason for deprecating and moving sunpy.image.coalignment is twofold. First, the scope of the core sunpy package has increasingly narrowed, with more analysis-specific functionality moved out to affiliated packages. Second, the module has seen little development in several years and by moving it to sunkit_image.coalignment, we hope to increase its visibility and attract a larger number of contributors.

Deprecation of sunpy.io.fits#

The sunpy.io.fits module is deprecated, and will be removed in version 4.1. This because it was designed for internal use only, and removing it from the public API gives the developers more flexibility to modify it without impacting users. The astropy.io.fits module can be used instead as a replacement that is designed to be user-facing.

Deprecation of sunpy.physics.solar_rotation#

sunpy.physics.solar_rotation.calculate_solar_rotate_shift and sunpy.physics.solar_rotation.mapsequence_solar_derotate have been deprecated and will be removed in version 4.1. Both of these functions have been moved to sunkit_image.coalignment and have identical functionality and API. Note that sunpy.physics.solar_rotation.mapsequence_solar_derotate has been renamed to mapsequence_coalign_by_rotation to more accurately reflect its functionality.

New dependency on asdf-astropy for ASDF support#

With the 4.0 release of sunpy the asdf plugin has been updated to support future versions of the asdf Python library. This has lead to no significant changes to how ASDF files are handled on save or load, however the plugin code is significantly simpler.

When updating sunpy to 4.0 it is important that the asdf-astropy package is installed if asdf is installed. If installing sunpy with sunpy[all] or sunpy[asdf] when using pip this will happen automatically, however, if you update sunpy with pip install -U sunpy and you have previously installed asdf (implicitly or explicitly) you will need to install the asdf-astropy package. If you have installed sunpy with conda, you don’t need to do anything as the conda package has been updated to depend on asdf and asdf-astropy. If asdf-astropy is not installed then sunpy’s asdf plugin will fail to load and emit a warning, this will happen every time for any ASDF file irrespective of if it contains a sunpy object in its tree.

Deprecation of shift and shifted_value#

The method sunpy.map.GenericMap.shift has been renamed sunpy.map.GenericMap.shift_reference_coord and shift has been deprecated. The method has been renamed to make it clear that it is the reference coordinate that is being shifted and not the image itself. Additionally, the sunpy.map.GenericMap.shifted_value property, which keeps track of the shifts applied by shift, has been deprecated. Users should instead use the CRVAL1 and CRVAL2 keys in sunpy.map.GenericMap.meta.modified_items to see how the reference coordinate has been modified. Note that shift_reference_coord does not modify shifted_value.

Contributors to this Release#

The people who have contributed to the code for this release are:

TODO: fill this in at release time.

Where a * indicates that this release contains their first contribution to SunPy.

What’s New in SunPy 3.1?#

The SunPy project is pleased to announce the 3.1 release of the sunpy core package.

On this page, you can read about some of the big changes in this release.

SunPy 3.1 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 610 commits have been added since 3.0

  • 56 issues have been closed since 3.0

  • 145 pull requests have been merged since 3.0

  • 16 people have contributed since 3.0

  • 8 of which are new contributors

Increase in required package versions#

We have bumped the minimum version of several packages we depend on; these are the new minimum versions for sunpy 3.1:

  • astropy >= 4.2

  • matplotlib >= 3.2.0

  • numpy >= 1.17.0

  • pandas >= 1.0.0

Increased in-situ data support#

Two new features have significantly increased support for in-situ data returned by heliospheric missions. See Getting data from CDAWeb for a full example of searching for, downloading, and loading a CDF file into sunpy.

New CDAWeb client#

A new Fido client to search the Coordinated Data Analysis Web (CDAWeb) has been added to sunpy.net. This allows one to search for and download data from many space physics missions that take in-situ data (including Parker Solar Probe and Solar Orbiter). In combination with the new CDF reading abilities of sunpy.timeseries.TimeSeries, this provides a full workflow for searching for, downloading, and analysing in-situ data contained within CDF files.

Support for .cdf files#

sunpy.timeseries.TimeSeries now supports loading CDF files if the external library cdflib is installed.

New limb drawing function#

The solar limb as seen from an arbitrary observer coordinate can now be drawn on a world coordinate system aware Axes using the sunpy.visualization.draw_limb function.

New WISPR map source#

A new map source for the WISPR instrument on Parker Solar Probe has been added. This improves the name of the map and adds correct information for the processing_level and exposure_time.

Changes to map metadata fixes#

The GenericMap map sources are primarily used to modify metadata values to either fix known incorrect values in a given data source, or make the metadata comply with the FITS standard. There are two ways in which the sunpy map sources can do this:

  1. Directly edit the values stored in the FITS metadata.

  2. Overload properties (e.g. sunpy.map.GenericMap.unit) such that they return the correct information without modifying the underlying metadata.

In previous versions of sunpy there has been no consistency in which of these two approaches is taken.

As of sunpy 3.1, the second approach is now consistently taken, and sunpy no longer edits any FITS metadata values when constructing a Map. GenericMap properties now consistently provide corrected information. For details of any corrections applied, the docstrings of different map sources (e.g., HMIMap - see Instrument Map Classes for a list of map sources) provide information on any assumptions made beyond the original FITS metadata when constructing the map properties.

For all maps, the following fixes are no longer made:

  • DATE-OBS is no longer replaced by DATE_OBS as a fallback

  • NAXIS, NAXIS1, NAXIS2, BITPIX are no longer populated if not present

  • BUNIT is no longer corrected to be a FITS compliant unit string

  • WAVEUNIT is no longer automatically populated from the header comments if it is not present.

For specific map sources, the following keywords are no longer modified or added:

  • KCorMap: OBSERVATORY, DETECTOR, WAVEUNIT, DSUN_OBS, HGLN_OBS

  • SWAPMap: OBSRVTRY, DETECTOR

  • RHESSIMap: CUNIT1, CUNIT2, CTYPE1, CTYPE2, WAVEUNIT, WAVELNTH

  • AIAMap: BUNIT, DETECTOR

  • HMIMap: DETECTOR, CRDER1, CRDER2

  • HMISynopticMap: CUNIT1, CUNIT2, CDELT1, CDELT2, DATE-OBS

  • EITMap: WAVEUNIT, CUNIT1, CUNIT2

  • LASCOMap: DATE-OBS, DATE_OBS, CROTA, CROTA1, CROTA2, CUNIT1, CUNIT2

  • MDIMap: CUNIT1, CUNIT2

  • MDISynopticMap: CUNIT1, CUNIT2, CDELT2, DATE-OBS, CRDER1, CRDER2

  • EUVIMap: WAVEUNIT, DATE-OBS, CROTA, CROTA2

  • CORMap: DATE-OBS

  • HIMap: DATE-OBS

  • SUVIMap: DETECTOR, TELESCOP

  • TRACEMap: DETECTOR, OBSRVTRY, CUNIT1, CUNIT2

  • SXTMap: DETECTOR, TELESCOP, DSUN_APPARENT

  • XRTMap: DETECTOR, TELESCOP, TIMESYS

  • SOTMap: DETECTOR, TELESCOP

  • SJIMap: DETECTOR, WAVEUNIT, WAVELNTH, CUNIT1, CUNIT2

  • EUIMap: CROTA, CROTA2

Changes to map date/time handling#

New date properties#

The properties date_start, date_end, and date_average have been added to be drawn from the relevant FITS metadata, if present in the map header. These are from new keywords defined in version 4 of the FITS standard, which have precise meanings compared to the previously ill-defined DATE-OBS.

Changes to date#

sunpy.map.GenericMap.date now looks for more metadata than just DATE-OBS. This property can return any one of the new properties (see above) depending on the metadata present in the map. It now draws from, in order of preference:

  1. The DATE-OBS FITS keyword

  2. date_average

  3. date_start

  4. date_end

  5. The current time.

If DATE-OBS is present alongside DATE-AVG or DATE-BEG and DATE-END, this results in a behaviour change to favour the new (more precisely defined) keywords. It is recommended to use date_average, date_start, or date_end instead if you need one of these specific times.

Addition of new time format TimeTaiSeconds#

The new TimeTaiSeconds format is the number of SI seconds from 1958-01-01 00:00:00, which includes UTC leap seconds. 1958-01-01 00:00:00 is the defined time when International Atomic Time (TAI) and Universal Time (UT) are synchronized.

This format is equivalent to the output of the SSW anytim2tai routine, and related routines, for times after 1972-01-01. Be aware that the SSW routines are not written to provide valid results for times before 1972-01-01.

This format is equivalent to TimeUnixTai, except that the epoch is 12 years earlier.

Propagating solar-surface coordinates in time#

There is now an easy-to-use context manager (propagate_with_solar_surface()) to enable coordinate transformations to take solar rotation into account. Normally, a coordinate refers to a point in inertial space, so transforming it to a different observation time does not move the point at all. Under this context manager, a coordinate will be treated as if it were referring to a point on the solar surface. Coordinate transformations with a change in observation time will automatically rotate the point in heliographic longitude for the time difference, with the amount of rotation depending on the specified differential-rotation model.

Differentially rotating a map

Differentially rotating a map

Convenient reprojection of maps#

Map objects now have the reproject_to() method to easily reproject the map to a new WCS. The returned map will be of type GenericMap, with no metadata preserved from the original map, so copy over any desired metadata from the original map. This method requires the optional package reproject to be installed.

Masking HMI based on the intensity of AIA

Masking HMI based on the intensity of AIA

Aligning AIA and HMI Data with Reproject

Aligning AIA and HMI Data with Reproject

Creating Carrington Maps

Creating Carrington Maps

Reprojecting Images to Different Observers

Reprojecting Images to Different Observers

Reprojecting Using a Spherical Screen

Reprojecting Using a Spherical Screen

Reprojecting to a Map Projection with a Custom Origin

Reprojecting to a Map Projection with a Custom Origin

Blending maps using mplcairo

Blending maps using mplcairo

Creating a Composite Plot with Three Maps

Creating a Composite Plot with Three Maps

Overlay an AIA image on a LASCO C2 coronagraph

Overlay an AIA image on a LASCO C2 coronagraph

Plotting the solar equator and prime meridian

Plotting the solar equator and prime meridian

Differentially rotating a map

Differentially rotating a map

JSOC keyword filtering with Fido#

Support for filtering searches with JSOC keywords has been added to Fido.search:

>>> from sunpy.net import Fido, attrs as a
>>> import astropy.units as u
>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
    a.jsoc.Series('aia.lev1_euv_12s'), a.Wavelength(304*u.AA), a.jsoc.Keyword("EXPTIME") > 1)
<sunpy.net.fido_factory.UnifiedResponse object at 0x7fe16a5d20d0>
Results from 1 Provider:

301 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

    T_REC         TELESCOP INSTRUME WAVELNTH CAR_ROT
-------------------- -------- -------- -------- -------
2014-01-01T00:00:01Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:00:13Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:00:25Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:00:37Z  SDO/AIA    AIA_4      304    2145
                ...      ...      ...      ...     ...
2014-01-01T00:59:25Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:59:37Z  SDO/AIA    AIA_4      304    2145
2014-01-01T00:59:49Z  SDO/AIA    AIA_4      304    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_4      304    2145
Length = 301 rows
>>> Fido.search(a.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'),
    a.jsoc.Series('aia.lev1_euv_12s'), a.Wavelength(304*u.AA), a.jsoc.Keyword("EXPTIME") == 1)
<sunpy.net.fido_factory.UnifiedResponse object at 0x7fe16a5d20d0>
Results from 1 Provider:

0 Results from the JSOCClient:
Source: http://jsoc.stanford.edu

Please be aware of two caveats:

  • We do not validate the value used for comparison.

  • Passing in a keyword without comparison to a value (e.g. ==0, < 10) will error.

Arithmetic operations with maps#

GenericMap objects now support arithmetic operations (i.e. addition, subtraction, multiplication, division) with array-like quantities. This includes scalar quantities as well as Numpy arrays. Notably, arithmetic operations between two GenericMap objects are not supported.

Contributors to this Release#

The people who have contributed to the code for this release are:

  • Alasdair Wilson *

  • Albert Y. Shih

  • Anubhav Sinha *

  • Conor MacBride

  • David Stansby

  • Devansh Shukla *

  • Jeffrey Aaron Paul

  • Nabil Freij

  • Noah Altunian *

  • Rohan Sharma *

  • Samriddhi Agarwal

  • Stuart Mumford

  • Thomas Braccia *

  • Tim Gates *

  • Will Barnes

Where a * indicates that this release contains their first contribution to SunPy.

What’s New in SunPy 3.0?#

Overview#

The SunPy project is pleased to announce the 3.0 release of the sunpy package.

On this page, you can read about some of the big changes in this release:

SunPy 3.0 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 526 commits have been added since 2.1

  • 53 issues have been closed since 2.1

  • 109 pull requests have been merged since 2.1

  • 26 people have contributed since 2.1

  • 10 of which are new contributors

Please find below a selection of what we consider to be the biggest changes or features with this release.

Improvements to Visualization of Maps#

In this release a number of Map visualisation methods have been improved to be more coordinate aware than previous releases. The first new feature is sunpy.map.GenericMap.draw_quadrangle() which replaces the old draw_rectangle method. draw_quadrangle() draws rectangles where the edges follow lines of constant longitude and latitude in the coordinate system in which the box is drawn.

AIA to STEREO coordinate conversion

AIA to STEREO coordinate conversion

Drawing a latitude-longitude quadrangle

Drawing a latitude-longitude quadrangle

Drawing a rotated rectangle on a map

Drawing a rotated rectangle on a map

Comparing differential-rotation models

Comparing differential-rotation models

HMI Showcase: Cutout

HMI Showcase: Cutout

The next change is to sunpy.map.GenericMap.draw_limb(), which now supports drawing the limb as seen by any observer on any image. This means that it is possible to visualize the sections of the limb which are visible in one map on another, or to draw the limb as seen from helioprojective map observer on a synoptic map. For a demonstration of this new functionality see the first figure in the Reprojecting Images to Different Observers example.

The last major change is to sunpy.map.GenericMap.plot(), which now has an autoalign=True keyword argument. This option when set to True will use pcolormesh() to transform the image being plotted the correct coordinates of the axes it is being plotted on. This allows for plots of two images with different projections to be visualized together correctly. It is worth noting that using autoalign=True is computationally expensive, and when used with interactive plots there is a significant performance penalty every time the plot is modified. See the Auto-Aligning AIA and HMI Data During Plotting example for details.

Improved Support for Solar Orbiter’s EUI Instrument in Map#

A new map source to support data from the Extreme Ultraviolet Imager (EUI) instrument on the Solar Orbiter (SolO) spacecraft has been added. This source improves the accuracy of the observer position by using the heliocentric inertial coordinates as well as correctly setting the processing level, exposure time and colormap. Data from EUI will automatically load using this source via sunpy.map.Map.

Inspect history of map metadata changes#

The .meta property of a GenericMap now keeps a record of the contents of the metadata (normally a FITS header) when it was created. This can be accessed via the original_meta property. This allows any changes made by sunpy or by the user directly to be tracked with the following properties:

See the new Map metadata modification example for details.

sunpy.instr moved to sunkit-instruments#

The sunpy.instr subpackage has been moved to a separate affiliated package called sunkit-instruments. This has been done to make the core package align with the goal that instrument specific analysis and processing code should live in affiliated packages.

Increase in required package versions#

We have bumped the minimum version of several packages we depend on; these are the new minimum versions for sunpy 3.0:

  • asdf>=2.6.0

  • astropy >= 4.1.0

  • beautifulsoup4>=4.8.0

  • dask[array]>=2.0.0

  • drms>=0.6.1

  • glymur>=0.8.18,!=0.9.0

  • h5netcdf>=0.8.1

  • matplotlib>=3.1.0

  • numpy >= 1.16.0

  • pandas>=0.24.0

  • parfive >= 1.2.0

  • python-dateutil>=2.8.0

  • scipy >= 1.3.0

  • scipy>=1.3.0

  • sqlalchemy>=1.3.4

  • tqdm>=4.32.1

  • zeep>=3.4.0

Contributors to this Release#

The people who have contributed to the code for this release are:

  • Abhijeet Manhas

  • Abhishek Pandey

  • Adwait Bhope *

  • Albert Y. Shih

  • Amarjit Singh Gaba *

  • Aryan Chouhan

  • David Stansby

  • Jeffrey Aaron Paul

  • Kateryna Ivashkiv

  • Kaustubh Chaudhari *

  • Kritika Ranjan

  • Laura Hayes

  • Megh Dedhia *

  • Monica Bobra

  • Mouloudi Mohamed Lyes *

  • Nabil Freij

  • Nakul Shahdadpuri

  • Ratul Das *

  • Samriddhi Agarwal *

  • Shane Maloney

  • Stuart Mumford

  • Tathagata Paul

  • Thomas A Caswell

  • Varun Bankar *

  • Will Barnes

  • Yukie Nomiya *

Where a * indicates that this release contains their first contribution to sunpy.

What’s New in SunPy 2.1?#

Overview#

The SunPy project is pleased to announce the 2.1 release of the sunpy package.

On this page, you can read about some of the big changes in this release:

SunPy 2.1 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 1148 commits have been added since 2.0

  • 202 issues have been closed since 2.0

  • 306 pull requests have been merged since 2.0

  • 38 people have contributed since 2.0

  • 19 of which are new contributors

Please find below a selection of what we consider to be the biggest changes or features with this release.

Support for GOES-16 and GOES-17 X-Ray Sensor (XRS) data#

We have now included support for the GOES-16 and GOES-17 XRS 1-second data. This data can now be queried and downloaded with sunpy.net.Fido and the files read in and analysed as a sunpy.timeseries.TimeSeries.

Flare times on a GOES XRS plot

Flare times on a GOES XRS plot

Retrieving and analyzing GOES X-Ray Sensor (XRS) data

Retrieving and analyzing GOES X-Ray Sensor (XRS) data

The TimeSeriesMetaData class

The TimeSeriesMetaData class

Fido Metadata#

Fido provides a unified interface to download data from several services but lacked support for search for metadata provided by catalogues or data providers. This has now changed and you can query JSOC, HEK and HEC with Fido:

>>> from sunpy.net import Fido
>>> from sunpy.net import attrs as a
>>> results = Fido.search(timerange,
                  a.hek.FL & (a.hek.FL.PeakFlux > 1000) |
                  a.jsoc.Series('hmi.m_45s'))
>>> results
Results from 2 Providers:

2 Results from the HEKClient:
                                                                                                                    gs_thumburl                                                                                                                       ...
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ...
http://sdowww.lmsal.com/sdomedia/ssw/ssw_client/data/ssw_service_100731_205448_25495028/www/EDS_FlareDetective-TriggerModule_20100801T033001-20100801T035225_AIA_171_S21W87_ssw_cutout_20100801_033013_AIA_171_S21W87_20100801_033012_context_0180.gif ...
http://sdowww.lmsal.com/sdomedia/ssw/ssw_client/data/ssw_service_100801_234037_25860951/www/EDS_FlareDetective-TriggerModule_20100801T033008-20100801T035232_AIA_193_S21W87_ssw_cutout_20100801_033020_AIA_193_S21W87_20100801_033019_context_0180.gif ...

1 Results from the JSOCClient:
        T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
----------------------- -------- ---------- -------- -------
2010.08.01_03:40:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2099

Alongside this, there is easier access to the results from a client by indexing the results by the client name:

>>> hek_results, jsoc_results = results['hek'], results['jsoc']

In the first example you can see the search results from HEK are not too useful by default. The results are truncated as the long urls for the thumbnail take up too many characters and HEK can return up to 100 columns of information. With a new method show() on the results itself, you directly select the columns to display:

>>> hek_results.show('event_peaktime', 'obs_instrument', 'fl_peakflux')
event_peaktime   obs_instrument fl_peakflux
------------------- -------------- -----------
2010-08-01T03:40:37            AIA     1027.64
2010-08-01T03:40:44            AIA     1441.78

To find the keys for all of the columns you can do:

>>> hek_results.keys()
['gs_thumburl',
'comment_count',
'hpc_bbox',
...
'area_unit',
'obs_lastprocessingdate',
'refs']

In addition, if you pass the entire search query into Fido.fetch() and it will ignore results that have no corresponding data files to retrieve.

Querying the GOES flare event list

Querying the GOES flare event list

Querying Metadata clients

Querying Metadata clients

Flare times on a GOES XRS plot

Flare times on a GOES XRS plot

Fido Results Refactor#

While working on the above change, several changes were made to the way that results are returned from sunpy.net.Fido.search and the search methods of the underlying clients.

We have tried to minimize any breaking changes here and we believe that for most users the difference between 2.0 and 2.1 will be minor.

The key highlights you will want to be aware of are:

  • Previously slicing the result of Fido.search() (a UnifiedResponse object) so that it had a length of one returned another UnifiedResponse object, now it will return a QueryResponseTable object, which is a subclass of astropy.table.Table.

  • All result objects contained within the results of a Fido.search() are now QueryResponseTable objects (or subclasses thereof). These objects are subclasses of astropy.table.Table and can therefore be filtered and inspected as tabular objects, and the modified tables can be passed to Fido.fetch().

  • The keys available to be used when formatting the path= argument to Fido.fetch() have changed. This is to standardise them over the results from more clients and make them easier to use. You can use the ~.UnifiedResponse.path_format_keys method to see all the possible keys for a particular search.

  • The search results object returned from Fido.search now correctly counts all results in its file_num property.

  • Results from the NOAAIndicesClient and the NOAAPredictClient no longer have Start Time or End Time in their results table as the results returned from the client are not dependent upon the time parameter of a search.

New synoptic map sources and clients#

MDISynopticMap and HMISynopticMap have been added as new data sources, and automatically fix common issues with FITS metadata from these sources. You do not need to change any code to use these, as sunpy automatically detects and uses the appropriate map sources for each file.

It is now possible to search for GONG synoptic maps within sunpy.net.Fido, using a.Instrument('GONG').

Requesting cutouts from the JSOC#

Fido can now be used to request cutouts from JSOC via the new a.jsoc.Cutout attr. This includes the ability to adjust the requested field of view to “track” a feature as it moves across the solar disk, perform sub-pixel image registration, and mask off-disk pixels.

Requesting cutouts of AIA images from the JSOC

Requesting cutouts of AIA images from the JSOC

Coordinates with velocities#

It is now supported to transform coordinates with attached velocities, and the various ephemeris functions can optionally include velocity information. Transformations between coordinate frames will account for both any change in orientation of the velocity vector and any induced velocity due to relative motion between the frames. For example, consider Mars’s position/velocity in HeliographicStonyhurst:

>>> from astropy.coordinates import SkyCoord
>>> from sunpy.coordinates import get_body_heliographic_stonyhurst
>>> mars = SkyCoord(get_body_heliographic_stonyhurst('mars', '2021-01-01',
...                                                  include_velocity=True))
>>> mars
<SkyCoord( HeliographicStonyhurst: obstime=2021-01-01T00:00:00.000): (lon, lat, radius) in (deg, deg, AU)
    (-34.46752135, 1.77496469, 1.50936573)
 (d_lon, d_lat, d_radius) in (arcsec / s, arcsec / s, km / s)
    (-0.00048971, 0.00060976, 19.54950062)>
>>> mars.velocity.norm()
<Quantity 19.56823076 km / s>

However, HeliographicStonyhurst is a non-inertial frame that rotates over time. By transforming this coordinate to HeliocentricInertial, we can see that Mars’s actual velocity is larger:

>>> mars.heliocentricinertial
<SkyCoord (HeliocentricInertial: obstime=2021-01-01T00:00:00.000): (lon, lat, distance) in (deg, deg, AU)
    (-9.91128592, 1.77496469, 1.50936573)
 (d_lon, d_lat, d_distance) in (arcsec / s, arcsec / s, km / s)
    (0.04174239, 0.00060976, 19.54950058)>
>>> mars.heliocentricinertial.velocity.norm()
<Quantity 49.68592218 km / s>

See Coordinates with velocity information for more information.

Alternatives for reprojecting a Helioprojective map#

The typical observation in Helioprojective coordinates does not contain full 3D information for the sources of emission, so an assumption needs to be made when transforming such coordinates to other coordinate frames. By default, SunPy assumes that the emission is coming from the surface of the Sun, which enables reprojections such as in the example Reprojecting Images to Different Observers. However, this assumption is not appropriate for some observations, e.g., from coronagraphs.

There is now a context manager (assume_spherical_screen()) to override the default assumption such that any 2D coordinates are interpreted as being on the inside of a large spherical screen. See the following example for how this context manager enables alternative reprojections.

Aligning AIA and HMI Data with Reproject

Aligning AIA and HMI Data with Reproject

Reprojecting Using a Spherical Screen

Reprojecting Using a Spherical Screen

Blending maps using mplcairo

Blending maps using mplcairo

Creating a Composite Plot with Three Maps

Creating a Composite Plot with Three Maps

Overlay an AIA image on a LASCO C2 coronagraph

Overlay an AIA image on a LASCO C2 coronagraph

Visualizing 3D stereoscopic images

Visualizing 3D stereoscopic images

Finding map contours#

The new sunpy.map.GenericMap.contour() method can be used to extract contours from a map. It returns contours as a SkyCoord, allowing contours to be easily overplotted on the original or other maps.

Finding contours of a map

Finding contours of a map

Performance improvements#

Several functions in sunpy.map have been significantly sped up with improved algorithms.

In addition, sunpy.map.GenericMap.wcs is now cached when the map metadata remains unchanged, significantly improving performance in applications which make multiple requests for the map WCS (e.g. plotting), and reducing the number of repeated warnings thrown when metadata is missing.

Increase in required package versions#

We have bumped the minimum version of several packages we depend on; these are the new minimum versions for sunpy 2.1:

  • python 3.7

  • astropy 4.0

  • scipy 1.2

  • parfive 1.1

  • drms 0.6.1

  • matplotlib 2.2.2

Contributors to this Release#

The people who have contributed to the code for this release are:

  • Abhijeet Manhas

  • Abhishek Pandey *

  • Adrian Price-Whelan

  • Albert Y. Shih

  • Aryan Chouhan *

  • Conor MacBride *

  • Daniel Ryan

  • David Pérez-Suárez

  • David Stansby

  • Dipanshu Verma *

  • Erik Tollerud *

  • Jai Ram Rideout

  • Jeffrey Aaron Paul *

  • Johan L. Freiherr von Forstner *

  • Kateryna Ivashkiv *

  • Koustav Ghosh *

  • Kris Akira Stern

  • Kritika Ranjan *

  • Laura Hayes

  • Lazar Zivadinovic

  • Nabil Freij

  • Rutuja Surve

  • Sashank Mishra

  • Shane Maloney

  • Shubham Jain *

  • SophieLemos *

  • Steven Christe

  • Stuart Mumford

  • Sudeep Sidhu *

  • Tathagata Paul *

  • Thomas A Caswell *

  • Will Barnes

  • honey

  • mridulpandey

  • nakul-shahdadpuri *

  • platipo *

  • resakra *

  • sophielemos *

Where a * indicates that this release contains their first contribution to SunPy.

What’s New in SunPy 2.0?#

Overview#

The SunPy project is pleased to announce the 2.0 release of the sunpy package. On this page, you can read about some of the big changes in this release:

SunPy 2.0 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 1044 commits have been added since 1.1

  • 144 issues have been closed since 1.1

  • 290 pull requests have been merged since 1.1

  • 33 people have contributed since 1.1

  • 16 new contributors

Increase in required package versions#

We have bumped the minimum version of several packages we depend on:

  • numpy>=1.15.0

  • scipy>=1.0.0

  • matplotlib>=2.2.2

  • astropy>=3.2

  • parfive>=1.1.0

Search Attributes#

To search with Fido, you need to specify attributes to search against. Before sunpy 2.0, you had to supply values, as the following example demonstrates:

>>> from sunpy.net import Fido, attrs as a
>>> Fido.search(a.Time('2012/3/4', '2012/3/6'), a.Instrument("norh"),
...             a.Wavelength(17*u.GHz))

There was no way to know if the value was correct, but now we have a extenstive list of supported values from the clients and servers we can request data from.

Using Instrument as an example, if you print the object:

>>> print(a.Instrument)
sunpy.net.attrs.Instrument

Specifies the Instrument name for the search.

       Attribute Name          Client          Full Name                                           Description
--------------------------- ----------- ------------------------ --------------------------------------------------------------------------------
aia                         VSO         AIA                      Atmospheric Imaging Assembly
bbi                         VSO         BBI                      None
bcs                         VSO         BCS                      Bragg Crystal Spectrometer
bic_hifi                    VSO         BIC-HIFI                 None
bigbear                     VSO         Big Bear                 Big Bear Solar Observatory, California TON and GONG+ sites
...

This will list the name of value you should use, what data source will supply that data and a description. Furthermore, you can use tab completion to auto-fill the attribute name, for example by typing a.Instrument.<TAB>.

So now you can do the following instead:

Fido.search(a.Time('2012/3/4', '2012/3/6'), a.Instrument.norh, a.Wavelength(17*u.GHz))

aiaprep is now deprecated#

With the release of the new aiapy package, sunpy.instr.aia.aiaprep will be removed in version 2.1. Equivalent functionality is provided by the register() function in aiapy. For more details, see the example on registering and aligning level 1 AIA images in the aiapy documentation.

Fixes and clarification to pixel indexing#

sunpy uses zero-based indexing when referring to pixels, where the center of the bottom left pixel of a map is at [0, 0] * u.pix. Several parts of the API have been updated to make sure this is consistently the case across the package. In particular:

  • sunpy.map.GenericMap.top_right_coord previously had an off-by-one error in the calculation of the top right coordinate. This has been fixed.

  • sunpy.map.GenericMap.center previously had an off-by-one error in the calculation of the coordinate of the center of a map. This has been fixed.

  • sunpy.map.GenericMap.reference_pixel now returns a zero-based reference pixel. This is one pixel less than the previously returned value. Note that this means the reference_pixel now does not have the same value as the FITS CRPIX values, which are one-based indices.

  • sunpy.map.header_helper.make_fitswcs_header() now correctly interprets the reference_pixel argument as being zero-based, in previous releases it incorrectly interpreted the reference_pixel as one-based.

Standardization of submap and sunpy.map.GenericMap.draw_rectangle#

Both submap and sunpy.map.GenericMap.draw_rectangle allow specification of “rectangles” in world (spherical) coordinates. In versions prior to 2.0 you passed the coordinates of the rectangle to draw_rectangle as a bottom left coordinate, and a height and width, but for submap you passed it as a bottom left and a top right. In 2.0 the way you call both methods has changed, to accept a bottom left and then either width and height or a top right coordinate. As part of this change, the top_right, width, and height arguments must always be keyword arguments, i.e. width=10*u.arcsec

This change allows you to give the same rectangle specification to submap as to sunpy.map.GenericMap.draw_rectangle. Which is especially useful when you wish to plot a cropped area of a map, along with it’s context in the parent map:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> import matplotlib.pyplot as plt

>>> import sunpy.map
>>> from sunpy.data.sample import AIA_171_IMAGE

>>> aia = sunpy.map.Map(AIA_171_IMAGE)

>>> bottom_left = SkyCoord(-100 * u.arcsec, -100 * u.arcsec, frame=aia.coordinate_frame)
>>> width = 500 * u.arcsec
>>> height = 300 * u.arcsec

>>> sub_aia = aia.submap(bottom_left, width=width, height=height)

>>> fig = plt.figure()
>>> ax1 = fig.add_subplot(1, 2, 1, projection=aia)
>>> aia.plot(axes=ax1)
>>> aia.draw_rectangle(bottom_left, width=width, height=height)

>>> ax2 = fig.add_subplot(1, 2, 2, projection=sub_aia)
>>> sub_aia.plot(axes=ax2)

Both these methods delegate the input parsing to a new utility function sunpy.coordinates.utils.get_rectangle_coordinates.

Graphical overview for Map and MapSequence#

There are new methods to produce graphical overviews for Map and MapSequence instances: quicklook() and quicklook(), respectively. This graphical overview opens the default web browser and uses HTML to show a table of metadata, a histogram of the pixel values in the data, and a histogram-equalized image of the data. Here’s an example of the output for a MapSequence instance:

<sunpy.map.mapsequence.MapSequence object at 0x7f6f15b25b40>
MapSequence of 3 elements, with maps from HMIMap, AIAMap, EITMap
Map at index 0
<sunpy.map.sources.sdo.HMIMap object at 0x7f6f15b9bfa0>
Observatory SDO
Instrument HMI FRONT2
Detector HMI
Measurement magnetogram
Wavelength 6173.0
Observation Date 2011-06-07 06:32:11
Exposure Time Unknown
Dimension [1024. 1024.] pix
Coordinate System helioprojective
Scale [2.01714 2.01714] arcsec / pix
Reference Pixel [511.5 511.5] pix
Reference Coord [-4.23431983 -0.12852412] arcsec
Image colormap uses histogram equalization
Click on the image to toggle between units
Bad pixels are shown in red: 321587 NaN
<sunpy.map.sources.sdo.AIAMap object at 0x7f6f15b25b10>
Observatory SDO
Instrument AIA 3
Detector AIA
Measurement 1600.0 Angstrom
Wavelength 1600.0 Angstrom
Observation Date 2011-06-07 06:33:05
Exposure Time 2.901358 s
Dimension [1024. 1024.] pix
Coordinate System helioprojective
Scale [2.402792 2.402792] arcsec / pix
Reference Pixel [511.5 511.5] pix
Reference Coord [3.22309951 1.38578135] arcsec
Image colormap uses histogram equalization
Click on the image to toggle between units
<sunpy.map.sources.soho.EITMap object at 0x7f6f15b9bd60>
Observatory SOHO
Instrument EIT
Detector EIT
Measurement 195.0 Angstrom
Wavelength 195.0 Angstrom
Observation Date 2011-06-07 20:37:52
Exposure Time 12.594 s
Dimension [1024. 1024.] pix
Coordinate System helioprojective
Scale [2.63 2.63] arcsec / pix
Reference Pixel [509.91 520.06] pix
Reference Coord [0. 0.] arcsec
Image colormap uses histogram equalization
Click on the image to toggle between units

If you are using Jupyter Notebook, there is no need to call these methods explicitly to see this graphical overview. If you type just the name of the instance, the graphical overview is shown within the notebook itself as a rich representation of the instance, instead of the typical text representation.

Differential rotation in the coordinate framework#

The rotation rate of solar features varies with heliographic latitude, this rotation is called “differential rotation”. SunPy has already included functionality in the sunpy.physics.differential_rotation module to transform coordinates and Maps to account for the rotation of the Sun. SunPy now provides differential-rotation functionality integrated directly into the coordinate framework using the RotatedSunFrame class. Here are examples of using this class:

Comparing differential-rotation models

Comparing differential-rotation models

Differentially rotating a coordinate

Differentially rotating a coordinate

Overlaying differentially rotated gridlines

Overlaying differentially rotated gridlines

A detailed write-up of how to use RotatedSunFrame can be found at the RotatedSunFrame documentation.

Changes to Carrington coordinates#

We have refined our approach for heliographic Carrington coordinates to best support high-resolution imagery of the Sun, including from observatories that are at distances from the Sun that is significantly different from 1 AU (e.g., Solar Orbiter). Our HeliographicCarrington coordinate frame is now expressly intended for the co-alignment of images of the Sun’s surface from different observatories. HeliographicCarrington now requires the specification of the observer location (Earth or otherwise) because the light travel time between the Sun and the observer is accounted for. SunPy output now matches the calculations by JPL Horizons and SPICE. There may be small differences compared to Carrington coordinates computed by groups that do not use modern parameter values or the same assumptions for the methodology.

Importantly, the Carrington longitude that is now calculated (including using sunpy.coordinates.sun.L0()) will not match earlier versions of SunPy. A detailed write-up of the calculation approach and comparisons to other resources can be found at carrington’s functionality documentation.

Download behind proxies#

With the release of parfive 1.1, sunpy has been patched to be able to utilize proxy servers when downloading files.

  • Proxy URL is read from the environment variables HTTP_PROXY or HTTPS_PROXY.

  • Proxy Authentication proxy_auth should be passed as a aiohttp.BasicAuth object, explicitly by the user.

  • Proxy Headers proxy_headers should be passed as dict object, explicitly by the user.

For example if you use a bash terminal:

$ HTTP_PROXY=http://user:password@proxyserver.com:3128
$ HTTPS_PROXY=https://user:password@proxyserver.com:3128
$ export HTTP_PROXY
$ export HTTPS_PROXY

these will be used to enable downloads through a proxy.

Citation update#

A paper discussing sunpy 1.0 was accepted in The Astrophysical Journal and you can find the bibtex for it by running:

>>> import sunpy
>>> sunpy.__citation__

or accessing the website directly.

Previous update: sunpy 1.1#

In case you never updated to the intermediate release (sunpy 1.1) the whatsnew contains the major changes from that release: What’s New in SunPy 1.1?

What’s New in SunPy 1.1?#

Overview#

The SunPy project is pleased to announce the 1.1 release of the sunpy package.

The headline changes in 1.1 are:

  • The coordinates subpackage now supports four additional coordinate frames (HCI, HEE, GSE, and GEI).

  • A new subpackage sunpy.data.data_manager has been added to support versioned data for functions and methods.

  • Support in sunpy.map and sunpy.net for the SUVI instrument on GOES satellites.

  • Initial support for WISPR data from Parker Solar Probe in sunpy.map.

  • The import times for sunpy and some subpackages are significantly shorter, with no loss of functionality.

On this page, you can read about some of the big changes in this release:

SunPy 1.1 also includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 1137 commits have been added since 1.0

  • 106 issues have been closed since 1.0

  • 242 pull requests have been merged since 1.0

  • 24 people have contributed since 1.0

  • 10 new contributors

Supported versions of Python#

Like SunPy 1.0, 1.1 comes with support for Python versions 3.6, 3.7 and support for 3.8 has been added.

New coordinate frames#

The coordinates subpackage now supports four additional coordinate frames of interest to solar physics:

The following transformation graph illustrates how all of these coordinate frames can be transformed to any other frame in sunpy.coordinates or astropy.coordinates:

digraph { ICRS [label=ICRS] HCRS [label=HCRS] HeliocentricMeanEcliptic [label="Heliocentric Aries Ecliptic (HAE)"] HeliographicStonyhurst [label="Heliographic Stonyhurst (HGS)\nHeliocentric Earth Equatorial (HEEQ)"] HeliocentricEarthEcliptic [label="Heliocentric Earth Ecliptic (HEE)"] GeocentricEarthEquatorial [label="Geocentric Earth Equatorial (GEI)"] HeliographicCarrington [label="Heliographic Carrington (HGC)"] Heliocentric [label="Heliocentric Cartesian (HCC)"] HeliocentricInertial [label="Heliocentric Inertial (HCI)"] Helioprojective [label="Helioprojective Cartesian (HPC)"] GeocentricSolarEcliptic [label="Geocentric Solar Ecliptic (GSE)"] ICRS -> HCRS ICRS -> HeliocentricMeanEcliptic HCRS -> ICRS HCRS -> HeliographicStonyhurst HeliocentricMeanEcliptic -> ICRS HeliocentricMeanEcliptic -> HeliocentricEarthEcliptic HeliocentricMeanEcliptic -> GeocentricEarthEquatorial HeliographicStonyhurst -> HeliographicCarrington HeliographicStonyhurst -> Heliocentric HeliographicStonyhurst -> HCRS HeliographicStonyhurst -> HeliocentricInertial HeliographicCarrington -> HeliographicStonyhurst Heliocentric -> Helioprojective Heliocentric -> HeliographicStonyhurst Helioprojective -> Heliocentric HeliocentricEarthEcliptic -> HeliocentricMeanEcliptic HeliocentricEarthEcliptic -> GeocentricSolarEcliptic GeocentricSolarEcliptic -> HeliocentricEarthEcliptic HeliocentricInertial -> HeliographicStonyhurst GeocentricEarthEquatorial -> HeliocentricMeanEcliptic subgraph cluster_astropy { color=blue fontcolor=blue penwidth=2 label=<<b>Frames implemented in Astropy</b>> ICRS HCRS HeliocentricMeanEcliptic astropy [label="Other Astropy frames" shape=box3d style=filled] geocentric [label="Earth-centered frames\n(including GEO)" shape=box3d style=filled] astropy -> ICRS geocentric -> ICRS ICRS -> astropy ICRS -> geocentric } subgraph cluster_sunpy { color=crimson fontcolor=crimson penwidth=2 label=<<b>Frames implemented in SunPy</b>> Helioprojective Heliocentric HeliographicStonyhurst HeliographicCarrington subgraph cluster_sunpy11 { color=chocolate fontcolor=chocolate label=<<b>Added in SunPy 1.1</b>> HeliocentricInertial HeliocentricEarthEcliptic GeocentricSolarEcliptic GeocentricEarthEquatorial } } newrank=true }

See our coordinates documentation for a table of the currently supported coordinate systems and the corresponding frame classes.

Manager for Versioned Data Files#

SunPy 1.1 provides a data manager for versioning and caching remote files. The objective of this is to provide a way for data required for functions, such as instrument correction routines, to depend on non-local data in a reliable way. The data manager also guarantees that a specific version of the code uses a specific data file, with the ability for users to specify updated files.

This works by providing the URL of a remote file and a SHA256 hash to the sunpy.data.manager.require decorator which can be added to functions that require these specific data files from remote sources. If the specified hash does not match that of the remote version, an exception is raised to make the user aware of any changes on the remote server or corruption of local files. Additionally, sunpy.data.cache can be used to avoid re-downloading files that already exist on a user’s local machine, thus saving disk space and internet bandwidth.

Support for SUVI Data#

The Solar Ultraviolet Imager (SUVI) is a EUV instrument imaging the full disk of the Sun in six passbands, and is onboard the latest of the Geostationary Operational Environmental Satellite (GOES) missions. SUVIMap provides SunPy map support for loading SUVI FITS image data, and the SUVIClient adds support to search for SUVI data hosted by NOAA via Fido. It supports searching for wavelength, level of data (level 2 data which consists of stacked level 1b images and original level 1b files), as well as GOES satellite number (>= GOES 16).

Initial Support for WISPR Images#

Following the first data release from Parker Solar Probe, SunPy 1.1 supports loading WISPR imaging data into a GenericMap. Due to the complex projections in the WISPR data this involved changing the way sunpy converts FITS headers into astropy.wcs.WCS objects. It is expected that sunpy 2.0 will include more complete support for WISPR data.

A plot from SunPy of a WISPR level 3 file.

Speeding up import times#

We know that the initial import of sunpy or its subpackages can feel like it takes a long time, particularly on slower machines. Some of that import time can be the result of importing other modules or external packages that are required for specialized functionality that a user may not ever actually use. We have identified the most egregious cases and deferred those imports of dependencies to when they are actually needed. For example, the initial import of sunpy.map is now ~40% faster, with no loss of functionality. We will continue to look for ways to improve import times.

Notable Breaking Changes or Removed functionality#

  • Importing sunpy.timeseries no longer automatically imports Matplotlib. (#3376)

  • sunpy.timeseries.sources.NOAAIndicesTimeSeries.peek now checks that the type argument is a valid string, and raises a ValueError if it isn’t. (#3378)

  • Observer-based coordinate frames (Heliocentric and Helioprojective) no longer assume a default observer (Earth) if no observer is specified. These frames can now be used with no observer specified, but most transformations cannot be performed for such frames. This removal of a default observer only affects sunpy.coordinates, and has no impact on the default observer in sunpy.map. (#3388)

  • The colormap stored in SunPy’s Map subclasses (ie. map.plot_settings['cmap']) can now be colormap string instead of the full matplotlib.colors.Colormap object. To get the full Colormap object use the new attribute map.cmap. (#3412)

  • Fix a warning in sunpy.map.GenericMap.rotate where the truth value of an array was being calculated. This changes the behavior of rotate when the angle= parameter is not an Quantity object to raise TypeError rather than ValueError. (#3456)

  • Removed the step of repairing images (replacing non-finite entries with local mean) before coaligning them. The user is expected to do this themselves before coaligning images. If NaNs/non-finite entries are present, a warning is thrown. The function sunpy.image.coalignment.repair_image_nonfinite is deprecated. (#3287)

  • The method to convert a Helioprojective frame from 2D to 3D has been renamed from sunpy.coordinates.frames.Helioprojective.calculate_distance to make_3d. This method is not typically directly called by users. (#3389)

  • sunpy.visualization.animator.ImageAnimatorWCS is now deprecated in favour of sunpy.visualization.animator.ArrayAnimatorWCS. (#3407)

  • sunpy.cm has been moved to sunpy.visualization.colormaps and will be removed in a future version. (#3410)

Full Change Log#

To see a detailed list of all changes in version v1.1, including changes in API, please see the Full Changelog.

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 python datetime 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 while Time 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 python datetime.

    >>> 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 return Time and all functions which returned datetime.timedelta now return astropy.time.TimeDelta. For example, the properties of sunpy.time.TimeRange which used to return datetime.datetime and datetime.timedelta now return astropy.time.Time and astropy.time.TimeDelta.

  • Changes to parse_time

    parse_time has been reduced to a tiny wrapper over Time. The API of parse_time is almost the same as Time, however, parse_time supports conversion of a few more formats than Time, which are numpy.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.

Parfive progress bars in a Jupyter Notebook Parfive progress bars in a terminal

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 using Time (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 to sunpy.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 and sunpy.visualization.mapcubeanimator to sunpy.visualization.animator.

  • axis_ranges kwarg of sunpy.visualization.animator.ArrayAnimator, sunpy.visualization.animator.ImageAnimator and sunpy.visualization.animator.LineAnimator now must be entered as None, [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 as sunpy.net.attrs.Time after the switch to astropy.time.Time.

  • The deprecated sunpy.lightcurve (replaced by sunpy.timeseries), sunpy.wcs and sunpy.spectra (replaced by the radiospectra 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.

What’s New in SunPy 0.9?#

Overview#

SunPy 0.9 brings improved support for downloading data from the JSOC and bugfixes compared to the 0.8.x series of releases. The 0.9.x series will be the last series of SunPy releases to support Python 2. This is because Python 2 will not be maintained after 2019. The 0.9.x series will receive bugfixs only up until the and of life of Python 2 (around 18 months). No new functionality will be added to the 0.9.x series, which will also be the last version to include sunpy.spectra, sunpy.lightcurve and sunpy.wcs, all of which were deprecated in 0.8.

SunPy 1.0 and higher will support Python 3 only. All new functionality will be available only in SunPy 1.0 and higher.

On this page, you can read about some of the big changes in this release:

SunPy 0.9 includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 807 commits have been added since 0.8

  • 310 issues have been closed since 0.8

  • 147 pull requests have been merged since 0.8

  • 34 people have contributed since 0.8

  • 19 new contributors

There have been numerous improvements to large parts of SunPy, notably in the content of SunPy’s documentation, and the continuous integration testing of SunPy. In addition, SunPy coordinate system functionality has been improved, and the transformation between SunPy specific coordinate systems and those implemented by Astropy is now tested. The sunpy.map.CompositeMap object has received numerous bugfixes, improving its functionality. Bugfixes for various animation functionality have also been implemented.

Supported versions of Python#

SunPy is tested against Python 2.7, 3.5 and 3.6.

Improvements to JSOC functionality#

JSOC search capabilities have been improved. It is now possible to search using any JSOC prime key, to search the metadata only, or to search by any series. SunPy’s JSOC functionality uses the DRMS library, which is now a SunPy affiliated package. We would like to thank Kolja Glogowski for the DRMS package and for his help with the JSOC project.

When using a JSOC query in Fido, you must provide a JSOC series and at least one PrimeKey (for example a start time and end time):

>>> from sunpy.net import Fido, attrs as a

>>> result = Fido.search(a.jsoc.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'), a.jsoc.Series('hmi.v_45s'), a.jsoc.Notify('me@email.org')))
>>> result
<sunpy.net.fido_factory.UnifiedResponse object at 0x7fca7050d6a0>
Results from 1 Provider:

81 Results from the JSOCClient:
         T_REC          TELESCOP  INSTRUME  WAVELNTH CAR_ROT
         str23            str7     str10    float64   int64
----------------------- -------- ---------- -------- -------
2014.01.01_00:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:01:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:02:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:03:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:04:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
                    ...      ...        ...      ...     ...
2014.01.01_00:56:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:57:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:58:30_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_00:59:15_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:00_TAI  SDO/HMI HMI_FRONT2   6173.0    2145
2014.01.01_01:00:45_TAI  SDO/HMI HMI_FRONT2   6173.0    2145

>>> result = Fido.search(a.jsoc.Time('2014-01-01T00:00:00', '2014-01-01T01:00:00'), a.jsoc.Series('aia.lev1_euv_12s'), a.jsoc.Notify('me@email.org'), a.jsoc.PrimeKey('WAVELNTH', '171'))

>>> result
<sunpy.net.fido_factory.UnifiedResponse object at 0x7fca2981e1d0>
Results from 1 Provider:

301 Results from the JSOCClient:
       T_REC         TELESCOP INSTRUME WAVELNTH CAR_ROT
       str20           str7     str5    int64    int64
-------------------- -------- -------- -------- -------
2014-01-01T00:00:01Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:00:13Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:00:25Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:00:37Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:00:49Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:01:01Z  SDO/AIA    AIA_3      171    2145
                 ...      ...      ...      ...     ...
2014-01-01T00:58:49Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:59:01Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:59:13Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:59:25Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:59:37Z  SDO/AIA    AIA_3      171    2145
2014-01-01T00:59:49Z  SDO/AIA    AIA_3      171    2145
2014-01-01T01:00:01Z  SDO/AIA    AIA_3      171    2145

Data is downloaded using:

>>> files = Fido.fetch(result)

which returns a set of filepaths to the downloaded data.

For more information on accessing JSOC data using SunPy please consult Finding and Downloading Data from JSOC.

Renamed/removed functionality#

sunpy.coordinates.representations#

The package sunpy.coordinates.representations has been removed.

Full change log#

To see a detailed list of all changes in version v0.9, including changes in API, please see the Full Changelog.

What’s New in SunPy 0.8?#

Overview#

SunPy 0.8 is a major release that adds significant new functionality since the 0.7.x series of releases.

On this page, you can read about some of the big changes in this release:

In addition to these major changes, SunPy 0.8 includes a large number of smaller improvements and bug fixes, which are described in the Full Changelog.

By the numbers:

  • 1442 commits have been added since 0.7

  • 163 issues have been closed since 0.7

  • 200 pull requests have been merged since 0.7

  • 35 distinct people have contributed code

  • 17 new contributors

Supported versions of Python#

SunPy is tested against Python 2.7, 3.5 and 3.6. SunPy no longer supports Python 3.4.

New data downloading interface#

The new package Fido is the primary interface to search for and download observational data. Fido is a unified interface for searching and fetching solar physics data irrespective of the underlying client or webservice through which the data is obtained, e.g. VSO, JSOC, etc. It therefore supplies a single, easy and consistent way to obtain most forms of solar physics data. It unifies data search and download from multiple sources such as the VSO and non-VSO sources:

>>> from sunpy.net import Fido, attrs as a

>>> attrs_time = a.Time('2005/01/01 00:10', '2005/01/01 00:15')
>>> result = Fido.search(attrs_time, a.Instrument.eit)
>>> result
<sunpy.net.fido_factory.UnifiedResponse object at 0x7f581489bb70>
Results from 1 Provider:

1 Results from the VSOClient:
    Start Time [1]       End Time [1]    Source Instrument   Type   Wavelength [2]
                                                                      Angstrom
    str19               str19         str4     str3      str8      float64
------------------- ------------------- ------ ---------- -------- --------------
2005-01-01 00:12:10 2005-01-01 00:12:22   SOHO        EIT FULLDISK 195.0 .. 195.0

>>> attrs_time = a.Time('2016/10/22 00:00', '2016/10/25 23:59')
>>> result = Fido.search(attrs_time, a.Instrument.lyra)
>>> result
<sunpy.net.fido_factory.UnifiedResponse object at 0x11dfd8048>
Results from 1 Provider:

4 Results from the LYRAClient:
     Start Time           End Time      Source Instrument Wavelength
       str19               str19         str6     str4       str3
------------------- ------------------- ------ ---------- ----------
2016-10-22 00:00:00 2016-10-25 23:59:00 Proba2       lyra        nan
2016-10-22 00:00:00 2016-10-25 23:59:00 Proba2       lyra        nan
2016-10-22 00:00:00 2016-10-25 23:59:00 Proba2       lyra        nan
2016-10-22 00:00:00 2016-10-25 23:59:00 Proba2       lyra        nan

Data is downloaded using:

>>> files = Fido.fetch(result)

which returns a set of filepaths to the data.

New coordinate package#

The SunPy coordinates package describes locations in physical space, and coordinate frames. It provides a way to transform coordinates from one frame like helioprojective to another such as heliographic.

Coordinates can be defined very simply:

>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> from sunpy.coordinates import frames
>>> a = SkyCoord(200*u.arcsec, 300*u.arcsec, frame=frames.Helioprojective)
>>> a
<SkyCoord (Helioprojective: obstime=None, rsun=695508.0 km, observer=earth): (Tx, Ty) in arcsec
    ( 200.,  300.)>
>>> b = SkyCoord(-20*u.degree, -56*u.degree, frame=frames.HeliographicStonyhurst)
>>> b
<SkyCoord (HeliographicStonyhurst: obstime=None): (lon, lat, radius) in (deg, deg, km)
    (-20., -56.,  695508.)>

The coordinate a is in the helioprojective coordinate system, and the coordinate b is in the heliographic Stonyhurst system.

Maps can also be used to define coordinate frames:

>>> import sunpy.map
>>> import sunpy.data.sample
>>> aia_map = sunpy.map.Map(sunpy.data.sample.AIA_171_IMAGE)
>>> c = SkyCoord(-45*u.arcsec, 600*u.arcsec, frame=aia_map.coordinate_frame)
>>> c
<SkyCoord (Helioprojective: obstime=2011-06-07 06:33:02.770000, rsun=696000000.0 m, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07 06:33:02.770000): (lon, lat, radius) in (deg, deg, m)
    ( 0.,  0.048591,   1.51846026e+11)>): (Tx, Ty) in arcsec
    (-45.,  600.)>

The coordinate c is now defined with respect to the coordinate frame derived from the map. The observer attribute:

>>> c.observer
<HeliographicStonyhurst Coordinate (obstime=2011-06-07 06:33:02.770000): (lon, lat, radius) in (deg, deg, m)
    ( 0.,  0.048591,   1.51846026e+11)>

defines the location from which the coordinate was observed.

Transformation between solar physics coordinate systems#

Transformation between solar physics coordinate frames is simple:

>>> c.transform_to(frames.HeliographicStonyhurst)
<SkyCoord (HeliographicStonyhurst: obstime=2011-06-07 06:33:02.770000): (lon, lat, radius) in (deg, deg, km)
    (-3.51257477,  39.27459767,  696000.00000088)>
Transformation to astropy coordinate systems#

Solar physics coordinates can also be transformed into astrophysical coordinates. For example, to convert to the International Celestial Reference System (ICRS):

>>> c.transform_to('icrs')
<SkyCoord (ICRS): (ra, dec, distance) in (deg, deg, km)
    ( 224.85859731,  10.52568476,  998439.00599877)>
Specification of observer at any major solar system body#

Major solar system bodies can be used to specify observer locations in SkyCoord:

>>> d = SkyCoord(-45*u.arcsec, 600*u.arcsec, observer='Mars', obstime='2011-06-07 06:33:02', frame=frames.Helioprojective)
>>> d
<SkyCoord (Helioprojective: obstime=2011-06-07 06:33:02, rsun=695508.0 km, observer=<HeliographicStonyhurst Coordinate (obstime=2011-06-07 06:33:02): (lon, lat, radius) in (deg, deg, AU)
    ( 135.78519602,  4.47598707,  1.43448427)>): (Tx, Ty) in arcsec
    (-45.,  600.)>

New timeseries data object#

The TimeSeries object is used to represent columns of time-ordered scalar values, and is source-aware, just like the Map object. This object supersedes the LightCurve object, which is now deprecated in 0.8.

The TimeSeries object can be instantiated by passing in a file:

>>> import sunpy.timeseries
>>> import sunpy.data.sample
>>> goes = sunpy.timeseries.TimeSeries(sunpy.data.sample.GOES_XRS_TIMESERIES)

TimeSeries objects can have more than one column:

>>> goes.columns
['xrsa', 'xrsb']

and have convenient plotting methods.

import sunpy.timeseries
import sunpy.data.sample
goes = sunpy.timeseries.TimeSeries(sunpy.data.sample.GOES_XRS_TIMESERIES)
goes.peek()

(Source code, png, hires.png, pdf)

_images/0-8-1.png

TimeSeries objects have a ‘meta’ property that stores the metadata of the timeseries:

>>> goes.meta
|-------------------------------------------------------------------------------------------------|
|TimeRange                  | Columns         | Meta                                              |
|-------------------------------------------------------------------------------------------------|
|2011-06-06 23:59:59.961999 | xrsa            | simple: True                                      |
|            to             | xrsb            | bitpix: 8                                         |
|2011-06-07 23:59:57.631999 |                 | naxis: 0                                          |
|                           |                 | extend: True                                      |
|                           |                 | date: 26/06/2012                                  |
|                           |                 | numext: 3                                         |
|                           |                 | telescop: GOES 15                                 |
|                           |                 | instrume: X-ray Detector                          |
|                           |                 | object: Sun                                       |
|                           |                 | origin: SDAC/GSFC                                 |
|                           |                 | ...                                               |
|-------------------------------------------------------------------------------------------------|

and the data can be accessed as a pandas dataframe using:

>>> goes.data
                                    xrsa          xrsb
2011-06-06 23:59:59.961999  1.000000e-09  1.887100e-07
2011-06-07 00:00:02.008999  1.000000e-09  1.834600e-07
2011-06-07 00:00:04.058999  1.000000e-09  1.860900e-07
...
2011-06-07 23:59:55.584999  1.000000e-09  1.624800e-07
2011-06-07 23:59:57.631999  1.000000e-09  1.598500e-07

Data sources that do not provide FITS files need to have a source keyword to help with the identification and interpretation of the data:

>>> eve = sunpy.timeseries.TimeSeries(sunpy.data.sample.EVE_TIMESERIES, source='EVE')

Differential rotation of maps#

Maps can now be transformed using solar differential rates using from sunpy.physics.differential_rotation import diffrot_map.

Renamed/removed functionality#

Several sub-packages have been moved or removed, and these are described in the following sections.

sunpy.lightcurve#

The package sunpy.lightcurve has been deprecated in favor of timeseries, and will be removed in a future version of SunPy.

sunpy.physics.transforms#

The modules in sunpy.physics.transforms have been moved to physics.

sunpy.net#

HelioviewerClient has been removed from the sunpy.net namespace. It should now be imported with from sunpy.net.helioviewer import HelioviewerClient.

Full change log#

To see a detailed list of all changes in version v0.8, including changes in API, please see the Full Changelog.

Acknowledging or Citing SunPy#

If you use SunPy in your scientific work, we would appreciate citing it in your publications. The continued growth and development of SunPy is dependent on the community being aware of SunPy.

Citing SunPy in Publications#

Please add the following line within your methods, conclusion or acknowledgements sections:

This research used version X.Y.Z (software citation) of the SunPy open source software package (project citation).

The project citation should be to the SunPy paper, and the software citation should be the specific Zenodo DOI for the version used in your work.

@ARTICLE{sunpy_community2020,
  doi = {10.3847/1538-4357/ab4f7a},
  url = {https://iopscience.iop.org/article/10.3847/1538-4357/ab4f7a},
  author = {{The SunPy Community} and Barnes, Will T. and Bobra, Monica G. and Christe, Steven D. and Freij, Nabil and Hayes, Laura A. and Ireland, Jack and Mumford, Stuart and Perez-Suarez, David and Ryan, Daniel F. and Shih, Albert Y. and Chanda, Prateek and Glogowski, Kolja and Hewett, Russell and Hughitt, V. Keith and Hill, Andrew and Hiware, Kaustubh and Inglis, Andrew and Kirk, Michael S. F. and Konge, Sudarshan and Mason, James Paul and Maloney, Shane Anthony and Murray, Sophie A. and Panda, Asish and Park, Jongyeob and Pereira, Tiago M. D. and Reardon, Kevin and Savage, Sabrina and Sipőcz, Brigitta M. and Stansby, David and Jain, Yash and Taylor, Garrison and Yadav, Tannmay and Rajul and Dang, Trung Kien},
  title = {The SunPy Project: Open Source Development and Status of the Version 1.0 Core Package},
  journal = {The Astrophysical Journal},
  volume = {890},
  issue = {1},
  pages = {68-},
  publisher = {American Astronomical Society},
  year = {2020}
}

You can also get this information with sunpy.__citation__.

Other SunPy publications#

The SunPy v1.0.8 code was reviewed by The Journal of Open Source Software (JOSS).

@ARTICLE{Mumford2020,
  doi = {10.21105/joss.01832},
  url = {https://doi.org/10.21105/joss.01832},
  year = {2020},
  publisher = {The Open Journal},
  volume = {5},
  number = {46},
  pages = {1832},
  author = {Stuart Mumford and Nabil Freij and Steven Christe and Jack Ireland and Florian Mayer and V. Hughitt and Albert Shih and Daniel Ryan and Simon Liedtke and David Pérez-Suárez and Pritish Chakraborty and Vishnunarayan K and Andrew Inglis and Punyaslok Pattnaik and Brigitta Sipőcz and Rishabh Sharma and Andrew Leonard and David Stansby and Russell Hewett and Alex Hamilton and Laura Hayes and Asish Panda and Matt Earnshaw and Nitin Choudhary and Ankit Kumar and Prateek Chanda and Md Haque and Michael Kirk and Michael Mueller and Sudarshan Konge and Rajul Srivastava and Yash Jain and Samuel Bennett and Ankit Baruah and Will Barnes and Michael Charlton and Shane Maloney and Nicky Chorley and Himanshu  and Sanskar Modi and James Mason and Naman9639  and Jose Rozo and Larry Manley and Agneet Chatterjee and John Evans and Michael Malocha and Monica Bobra and Sourav Ghosh and Airmansmith97  and Dominik Stańczak and Ruben De Visscher and Shresth Verma and Ankit Agrawal and Dumindu Buddhika and Swapnil Sharma and Jongyeob Park and Matt Bates and Dhruv Goel and Garrison Taylor and Goran Cetusic and Jacob  and Mateo Inchaurrandieta and Sally Dacie and Sanjeev Dubey and Deepankar Sharma and Erik Bray and Jai Rideout and Serge Zahniy and Tomas Meszaros and Abhigyan Bose and André Chicrala and Ankit  and Chloé Guennou and Daniel D'Avella and Daniel Williams and Jordan Ballew and Nick Murphy and Priyank Lodha and Thomas Robitaille and Yash Krishan and Andrew Hill and Arthur Eigenbrot and Benjamin Mampaey and Bernhard Wiedemann and Carlos Molina and Duygu Keşkek and Ishtyaq Habib and Joseph Letts and Juanjo Bazán and Quinn Arbolante and Reid Gomillion and Yash Kothari and Yash Sharma and Abigail Stevens and Adrian Price-Whelan and Ambar Mehrotra and Arseniy Kustov and Brandon Stone and Trung Dang and Emmanuel Arias and Fionnlagh Dover and Freek Verstringe and Gulshan Kumar and Harsh Mathur and Igor Babuschkin and Jaylen Wimbish and Juan Buitrago-Casas and Kalpesh Krishna and Kaustubh Hiware and Manas Mangaonkar and Matthew Mendero and Mickaël Schoentgen and Norbert Gyenge and Ole Streicher and Rajasekhar Mekala and Rishabh Mishra and Shashank Srikanth and Sarthak Jain and Tannmay Yadav and Tessa Wilkinson and Tiago Pereira and Yudhik Agrawal and Jamescalixto  and Yasintoda  and Sophie Murray},
  title = {SunPy: A Python package for Solar Physics},
  journal = {Journal of Open Source Software}
}

A paper about version v0.5 of SunPy was published in Computational Science & Discovery.

Acknowledging SunPy in Posters and Talks#

Please include the Sunpy logo on the title, conclusion slide, or about page. For websites please link the image to sunpy.org. Other versions of the logo are available in the sunpy-logo repository.

Known Issues#

This page documents commonly known issues, issues here is defined broadly and refers to oddities or specifics of how sunpy or the Python ecosystem works that could anyone catch out.

Disagreement between astropy.time.Time and SSW for pre-1972 dates#

Conversions between time scales for pre-1972 dates may not agree between astropy.time.Time and SSW. Prior to 1972, the length of a Universal Time (UT) second was different from the length of an SI second, and astropy.time.Time correctly handles this difference. However, SSW does not (at least in the commonly used routines), and thus SSW conversions for pre-1972 dates between UT and International Atomic Time (TAI, based on SI seconds) can be incorrect by multiple seconds. The SSW routine utc2tai notes that its output is invalid for pre-1972 dates, but other SSW routines are missing such a disclaimer.

Reference issue.

JSOCClient time filtering#

The JSOC API filters on T_OBS for some series, instead of T_REC which is what is used most of the time. There is not anything we can do to fix this on our side. The specific section of the SDO users guide explaining this.

Reference issue.

Current status of sub-packages#

sunpy has variations of stability across different sub-packages. This document summarizes the current status of the sunpy sub-packages, so that users understand where they might expect changes in future, and which sub-packages they can safely use for production code. For help or to discuss specific sub-packages, refer to the maintainer list to find who to contact.

The classification is as follows:

Planned
Actively developed, be prepared for possible significant changes.
Reasonably stable, any significant changes/additions will generally include backwards-compatibility.
Mature. Additions/improvements possible, but no major changes planned.
Pending deprecation. Might be deprecated in a future version.
Deprecated. Might be removed in a future version.

The current planned and existing sub-packages are:

Sub-Package   Comments
sunpy.coordinates Any future changes should largely maintain backwards compatibility.
sunpy.data Should see no major changes.
sunpy.image Any future changes should largely maintain backwards compatibility.
sunpy.io Should see no major changes.
sunpy.map Any future changes should largely maintain backwards compatibility.
sunpy.net Any future changes should largely maintain backwards compatibility.
sunpy.physics While it has not changed recently, it could be subject to major revisions in future.
sunpy.sun Sub-module ``constants`` is stable. Sub-module ``models`` will likely have major changes.
sunpy.time Any future changes should largely maintain backwards compatibility.
sunpy.timeseries Any future changes should largely maintain backwards compatibility.
sunpy.util This sub-module is not user facing, but any changes will still undergo a deprecation period.
sunpy.visualization Any future changes should largely maintain backwards compatibility.

Taken with love from the Astropy project.

Developer’s Guide#

This section contains the various guidelines to be followed by anyone working on sunpy.

Getting started

Newcomers’ Guide#

If you have come across this page, you just might be new to the SunPy project - thanks for your interest in contributing to SunPy! SunPy is an open project that encourages anyone to contribute in any way possible. The people who help develop or contribute to SunPy are varied in ability and experience, with the vast majority being volunteers.

If you have any questions, comments, or just want to say hello, we have an active chat room, a mailing list and a community forum.

How to Contribute to sunpy#

Not Code#

A common misconception (which applies to any package) is that all we really want is some Python code, in fact, we do not require only code contributions! If you do not have the time or the desire to code, we have severals of areas where we can use help.

Reporting Issues#

If you use sunpy and stumble upon a problem, the best way to report it is by opening an issue on our GitHub issue tracker. This way we can help you work around the problem and hopefully fix the problem!

You will need to sign into GitHub to report an issue and if you are not already a member of Github, you will have to join. Joining GitHub will make it easier to report and track issues in the future.

If you do not want to join Github, then another way to report your issue is email the SunPy developers list sunpy-dev@googlegroups.com.

When reporting an issue, please try to provide a short description of the issue with a small code sample, this way we can attempt to reproduce the error. Also provide any error output generated when you encountered the issue, we can use this information to debug the issue. For a good example of how to do this see issue #2879.

If there is functionality that is not currently available in sunpy you can make a feature request. Please write as much information as possible regarding the feature you would like to see in sunpy.

When you go to open either an issue or a feature request, there is a GitHub template that will guide you on the type of information we are seeking. Please be sure to read over the comments in the GitHub text box.

Documentation#

sunpy has online documentation and we try to make sure its as comprehensive as possible. This documentation contains the API of sunpy but also a user guide, an example gallery and developer documents.

However, documentation for any project is a living document. It is never complete and there are always areas that could be expanded upon or could do with proof reading to check if the text is easy to follow and understandable. If parts are confusing or difficult to follow, we would love suggestions or improvements!

Reviewing a Pull Request#

We at any one time have a variety of pull requests open and getting reviews is important. Generally the more people that can look over a pull request the better it will turn out and we encourage everyone to do so.

Code#

If you would prefer to code instead, the best way to start is to work on an existing and known issues. We have several repositories you can investigate. The main one is the sunpy repository with where all the known issues with sunpy are detailed. Each issue should have a series of labels that provide information about the nature of the issue. If you find an issue you’d like to work on, please make sure to add a comment to let people know that you are working on it! This will make it less likely that effort is duplicated.

Note

sunpy is Free and open-source software (FOSS), under the BSD-2 license. By contributing you are stating that you have the right to and agree to have your work distributed under the terms of this license.

This applies to everyone who wants to contribute during work time no matter who their employer is. You should start by checking if there is a Open Source Software Policy (e.g., Standford’s policy) for your work place. If not, OSS-Watch summaries what you will need to check and who to ask, however this resource is aimed at a UK readers. As an example, Standford’s guidance allows someone to contribute and open source their code. If you are unsure if your university or institution allows you to contribute under the BSD-2 license, you should contact the relevant department or administrator that deals with copyright at your institution.

If you are unsure where to start we suggest the Good First Issue label. These are issues that have been deemed a good way to be eased into sunpy and are achievable with little understanding of the sunpy codebase. Please be aware that this does not mean the issue is “easy”, just that you do not need to be aware of the underlying structure of sunpy.

We also tag issues for specific events such as Hacktoberfest under the Hacktoberfest label. The scope of the issues should be appropriate for that specific event. We do participate in several other events but right now we do not have dedicated labels. So please use the above labels for starting issues!

In addition, we have several other repositories that have open issues and you might find these more interesting than the main repository.

Python:

CSS/HTML:

If you already have code that you’ve developed or already have your own idea of code you’d like to work on please first have a look at the issue list to see if any existing issues are related to your idea. If you find nothing then create your own issue to stimulate the addition of your code and make sure to let people know about it chat room or by email. Creating an issue creates a permanent record. It may be possible your idea may be outside of the scope of the repository you are trying to contribute to and the issue comments are a great place to have that discussion where potential future contributors can also see.

Setting up a development environment#

The instructions in this following section are based upon three resources:

We strongly recommend that you read these links. These links are more in-depth than this guide but you will need to replace astropy with sunpy.

In order to start coding you will need a local Python environment and we would recommend using Anaconda or miniconda (shortened to conda from here on). This method will bypass your operating system Python packages and makes the entire process easier.

The first step is to install the version of conda that corresponds to your operating system and instructions are here. Next we will want to setup the conda environment and we will need to add the conda-forge channel as a prerequisite:

$ conda config --add channels conda-forge
# Note you might need to add python=<version> if a new release of Python has come out very recently.
# Typically it will take around 3 months before we can support the latest version of Python.
$ conda create -n sunpy-dev pip
$ conda activate sunpy-dev

This will create a new conda environment called “sunpy-dev” and install the latest version of pip from the conda-forge channel. The next step is get a development version of sunpy. This will require that git be installed. If you have a GitHub account, we suggest that you fork the sunpy repository (the fork button is to the top right) and use that url for the clone step. This will make submitting changes easier in the long term for you:

Warning

Do not clone the sunpy repository into $HOME/sunpy. Depending on the operating system this location is used to store downloaded data files. This will cause conflicts later on, so the last argument (sunpy-git) on the git clone line will become the local folder name of the cloned repository. Otherwise you are free to clone to any other location.

$ git clone https://github.com/<username>/sunpy.git sunpy-git
$ cd sunpy-git
# This adds the main sunpy repository as a remote called "upstream".
# This will help you keep your fork up to date with the main sunpy repository.
$ git remote add upstream https://github.com/sunpy/sunpy.git
# This retrieves the tags from the main sunpy repository, which are used for determining the version of your fork.
$ git fetch --tags upstream
$ pip install -e ".[dev]"

Note

If this does not work, it could be due to a missing C compiler (e.g., gcc or clang) that is required to build sunpy at install. Getting the compiler either from your system package manager, XCode or Anaconda should address this.

Now you have the latest version of sunpy installed and are ready to work on it using your favorite editor! Ideally, when you start making changes you want to create a git branch:

$ git checkout -b my_fix

You can change my_fix to anything you prefer. If you get stuck or want help, just ask here!

Checking the code you have written#

Now that you have written some code to address an issue. You will need to check two things:

  1. The changes you have made are correct, i.e., it fixes a bug or the feature works. This requires you to run the code either manually or by writing/running a test function. pytest is the framework we use for this.

  2. The changes you have made follow the correct coding style. We follow the PEP8 style for all Python code and depending on your setup, you can use a linter program to check your code. For documentation, we follow the numpydoc style.

We provide more more detail about our test suite and how to write tests, and how to create and style documentation.

Send it back to us#

Once you have some changes you would like to submit, you will need to commit the changes. This is a three stage process:

  1. Use git status to see that the only changes locally are the right ones.

  2. Use git add <path to file> to add the changes to git.

  3. Use git commit -m <message> to label those changes.

  4. Use git push to update your fork (copy) of sunpy on GitHub.

Here you replace <message> with some text of the work you have done. We strongly recommend having a good commit message and this commit guide is worth reading.

Next step is to open a pull request on GitHub. If you are new to pull requests, here is the GitHub guide that is a detailed walkthrough. Go to the “pull requests” tab on your fork and pressing the large green “New pull request” button. Now on the right side from the box marked “compare” you can select your branch. Do one final check to make sure the code changes look correct and then press the green “Create pull request” button.

When you open your pull request, we have a GitHub template that will guide you on what to write in the message box. Please fill this in and title the pull request. Now the final step is to press the green “Create pull request” button.

As soon as you do this, you will be greeted by a message from the “sunpy bot” as well as several continuous integration checks. These are explained on our Pull Request Review page. But what is important to know is that these run a series of tests to make sure that the changes do not cause any new errors. We strongly recommend that any code changes you have had, follow the PEP8 style and that you have ran the code locally to make sure any changes do not break any existing code. We provide an overview on how to run the test suite here. Now we (the sunpy community) can review the code and offer suggestions and once we are happy, we can merge in the pull request.

If you do not have time to finish what you started on or ran out of time during a sprint and do not want to submit a pull request, you can create a git patch instead:

$ git format-patch main --stdout > my_fix.patch

You can rename my_fix to something more relevant. This way, you still get acknowledged for the work you have achieved. Now you can email this patch to the Google Group .

Just remember, if you have any problems get in touch!

Summer of Code(s)#

If you are interested in a “Summer of Code” project with sunpy, we have information on our wiki which has guidelines, advice, application templates and more! Our projects are located on our umbrella’s organization website, OpenAstronomy.

Pull Request Check List#

The pull request (commonly referred to as a PR) check list below is an outline of the steps that should be taken when making a contribution to a SunPy repository on Github.

New to Git and Github? Check out the Newcomers’ Guide and Git Cheat Sheets.

If you would prefer a visual Git interface, you can try Github Desktop or GitKraken.

  1. Review and test changes locally on your machine (see Testing Guidelines).
    1. Double check that a pull request does not exist for the changes you are making. Ideally, check that there is an issue that details what you want to change and why.

    2. If you are contributing code, review the Coding Standards page.

    3. See the Developer’s Guide for guidelines regarding code tests, documentation, and other types of contributions.

    4. Have you tested your changes with the latest version of sunpy? If not, update your local copy from your remote repository on Github.

  2. Push your changes to Github.
    1. Create a new branch in your fork of sunpy.

    2. Give this branch a name that reflects the changes you are making.

    3. Create commits that describe the changes.

    4. Push your changes to the new branch on your remote fork.

  3. Compare your branch with sunpy/main.
    1. Resolve any conflicts that occur ideally with a git rebase.

  4. Create a pull request.
    1. Create a pull request from the branch you have created/updated.

    2. Add a description to the pull request that describes the changes you have made. Remember to delete the preamble within the message box.

    3. Link to any relevant issues, pull requests, or comments in your description.

  5. Add a changelog to your pull request.
    1. A changelog is a short record of the type of changes made in your pull request. Other users are the intended audience, and you can have multiple logs per pull request.

  6. Maintainers will review your pull request Pull Requests and GitHub Teams.
    1. Tweak anything that others highlight and push the changes to your branch. You can also commit suggestions either in bulk or single commits via the Github user interface.

    2. Discuss possible changes or improvements in the comments with the reviewers.

    3. Review the Continuous Integration (CI) What runs on our Continuous Integration tests and fix any errors or warnings that are found.
      1. If you are confused by an error that the continuous integration is giving you, submit a comment in your pull request.

  7. Ask questions if you get stuck or confused at any point!
    1. Open-source projects are about communication and collaboration.

    2. Join the SunPy Matrix Chat channel.

This guide is partially based on Astropy’s Development Workflow.

Using conda for Development Dependencies#

The conda package for sunpy specifies only those dependencies that are relevant for the typical user. A power user or a developer will instead need the complete set of dependencies to use all optional features, build the documentation, and/or run the full test suite. We provide a conda environment file (sunpy-dev-env.yml) in the base directory of the GitHub repository. You can create a conda environment (here named sunpy-dev) from this file:

$ conda env create -n sunpy-dev -f sunpy-dev-env.yml
$ conda activate sunpy-dev

Alternatively, if you want to add the complete set of dependencies to an existing conda environment, you can use this file to update that environment (here named existing-env):

$ conda env update -n existing-env -f sunpy-dev-env.yml

The above calls assume that you have already downloaded sunpy-dev-env.yml, either by itself or along with the whole repository. You can alternatively supply the URL for the file, as hosted on GitHub, to either of the above calls, e.g.:

$ conda env create -n sunpy-dev -f https://raw.githubusercontent.com/sunpy/sunpy/main/sunpy-dev-env.yml

Note

This conda environment file intentionally does not specify restrictions on release versions for any dependency.

This conda environment file specifies only the dependencies for sunpy, and not sunpy itself. Depending on your needs, continue with one of the following two ways to install sunpy in this conda environment.

Normal Installation of sunpy#

If you do not plan to modify the code of sunpy itself, you can simply install sunpy via conda:

$ conda install sunpy

Since the conda environment already has the complete set of dependencies for sunpy, this call should install only sunpy and no additional packages.

Editable Installation of sunpy#

If you plan to modify the code of sunpy itself, you will want to perform an “editable install” of your local repository, via pip, so that Python will link to your local repository. Normally it is discouraged to have an environment that mixes package installations via conda with package installations via pip because it can lead to environment states that confuse the conda solver. That is the reason why our instructions for new developers recommends that dependencies be installed exclusively via pip. However, some of the dependencies in the complete set are difficult or even impossible to install via pip alone, yet are straightforward to install via conda.

Using the above conda environment, combined with a little care, it is possible to minimize that chance for any conda/pip conflicts. From the base directory of your local repository, install sunpy with some additional pip options:

$ pip install --no-deps --no-build-isolation -e .

The --no-deps and --no-build-isolation options ensure that pip does not itself install any dependencies. Since the conda environment is designed to already have the complete set of dependencies, the pip installation should succeed.

You now have a conda environment with an editable installation of sunpy and with (nearly) all dependencies managed by conda. As you install other packages in this environment, a package that depends on sunpy will trigger conda to install sunpy. That is fine! This conda installation of sunpy will simply mask the pip installation of sunpy. All you need to do is to remove the conda installation with the --force option so that dependencies are left undisturbed:

$ conda remove --force sunpy

Once the conda installation of sunpy is removed, the pip installation of sunpy will automatically be accessible again.

Note

For those who use mamba instead of conda, most conda commands can be translated by simply substituting “mamba” for “conda”. However, mamba remove does not support the --force option, so you do in fact have to call conda remove.

As a tip, you can follow a similar procedure to incorporate editable installations of other packages (e.g., astropy) in a conda environment. You first install the package via conda to ensure its dependencies are present, then you remove the package alone without disturbing the dependencies, and finally you perform the editable install of the package from the base directory of your local repository:

$ conda install astropy
$ conda remove --force astropy
$ pip install --no-deps --no-build-isolation -e .

Conventions

Coding Standards#

The purpose of the page is to describe the standards that are expected of all the code in the SunPy project. All potential developers should read and abide by the following standards. Code which does not follow these standards closely will not be accepted.

We try to closely follow the coding style and conventions proposed by Astropy.

Language Standard#

  • All code must be compatible with Python 3.7 and later. Usage of six, __future__, and 2to3 is no longer acceptable.

  • The new Python 3 formatting style should be used (i.e. "{0:s}".format("spam") instead of "%s" % "spam").

  • The core package and affiliated packages should be importable with no dependencies other than components already in the sunpy core package, the Python Standard Library, and packages already required by the sunpy core package. Adding dependencies to sunpy core will be considered but are highly discouraged. Such optional dependencies should be recorded in the pyproject.toml file in the project.optional-dependencies section.

Coding Style/Conventions#

  • The code will follow the standard PEP8 Style Guide for Python Code. In particular, this includes using only 4 spaces for indentation, and never tabs.

  • Follow the existing coding style within a file and avoid making changes that are purely stylistic. Please try to maintain the style when adding or modifying code.

  • Following PEP8’s recommendation, absolute imports are to be used in general. We allow relative imports within a module to avoid circular import chains.

  • The import numpy as np, import matplotlib as mpl, and import matplotlib.pyplot as plt naming conventions should be used wherever relevant. from packagename import * should never be used (except in __init__.py)

  • Classes should either use direct variable access, or Python’s property mechanism for setting object instance variables.

  • Classes should use the builtin super function when making calls to methods in their super-class(es) unless there are specific reasons not to. super should be used consistently in all subclasses since it does not work otherwise.

  • Multiple inheritance should be avoided in general without good reason.

  • __init__.py files for modules should not contain any significant implementation code. __init__.py can contain docstrings and code for organizing the module layout.

Private code#

It is often useful to designate code as private, which means it is not part of the user facing API, only used internally by sunpy, and can be modified without a deprecation period. Any classes, functions, or variables that are private should either:

  • Have an underscore as the first character of their name, e.g., _my_private_function.

  • If you want to do that to entire set of functions in a file, name the file with a underscore as the first character, e.g., _my_private_file.py.

If these might be useful for other packages within the sunpy ecosphere, they should be made public.

Utilities in sunpy#

Within sunpy, it might be useful to have a set of utility classes or functions that are used by internally to help with certain tasks or to provide a certain level of abstraction. These should be placed either:

  • sunpy.{subpackage}.utils.py, if it is only used within that sub-package.

  • sunpy.util if it is used across multiple sub-packages.

These can be private (see section above) or public. The decision is up to the developer, but if these might be useful for other packages within the sunpy ecosphere, they should be made public.

Formatting#

We enforce a minimum level of code style with our continuous integration (the name is sunpy.sunpy (python_codestyle [linux]). This runs a tool called pre-commit.

The settings and tools we use for the pre-commit can be found in the file .pre-commit-config.yaml at the root of the sunpy git repository. Some of the checks are: * Checks (but doesn’t fix) various PEP8 issues with flake8. * Sort all imports in any Python files with isort. * Remove any unused variables or imports with autoflake.

We suggest you use “tox” (which is used to run the sunpy test suite) to run these tools without having to setup anything within your own Python virtual environment:

$ tox -e codestyle

What you will see is this output (heavily condensed):

codestyle create: /home/<USER>/GitHub/sunpy/.tox/codestyle
codestyle run-test: commands[0] | pre-commit install-hooks
codestyle run-test: commands[1] | pre-commit run --verbose --all-files --show-diff-on-failure
flake8...................................................................Passed
- hook id: flake8
- duration: 1.35s

0

Check for case conflicts.................................................Passed
- hook id: check-case-conflict
- duration: 0.08s
Trim Trailing Whitespace.................................................Failed
- hook id: trailing-whitespace
- duration: 0.08s
- exit code: 1
- files were modified by this hook

Fixing docs/dev_guide/code_standards.rst

pre-commit hook(s) made changes.
If you are seeing this message in CI, reproduce locally with: `pre-commit run --all-files`.
To run `pre-commit` as part of git workflow, use `pre-commit install`.
All changes made by hooks:
diff --git a/docs/dev_guide/code_standards.rst b/docs/dev_guide/code_standards.rst
index bed700d90..c6b5df977 100644
--- a/docs/dev_guide/code_standards.rst
+++ b/docs/dev_guide/code_standards.rst
@@ -59,6 +59,8 @@ Instead of installing this, you can use "tox" (which is used to run the sunpy te

    $ tox -e codestyle

+What you will see
+
If you want to setup the pre-commit locally, you can do the following::

    $ pip install pre-commit
diff --git a/docs/dev_guide/documentation.rst b/docs/dev_guide/documentation.rst
index 5cd914047..b1017f77a 100644
--- a/docs/dev_guide/documentation.rst
+++ b/docs/dev_guide/documentation.rst
@@ -39,9 +39,9 @@ If there are multiple code elements with the same name (e.g. ``peek()`` is a met

.. code-block:: rst

-    `GenericMap.peek` or `CompositeMap.peek`
+    `.GenericMap.peek` or `.CompositeMap.peek`

-These will show up as `GenericMap.peek` or `CompositeMap.peek`.
+These will show up as `.GenericMap.peek` or `.CompositeMap.peek`.
To still show only the last segment you can add a tilde as prefix:

ERROR: InvocationError for command /home/nabil/GitHub/sunpy/.tox/codestyle/bin/pre-commit run --verbose --all-files --show-diff-on-failure (exited with code 1)
___________________________________________________________________________________________ summary ___________________________________________________________________________________________
ERROR:   codestyle: commands failed

This will inform you of what checks failed and why, and what changes (if any) the command has made to your code.

If you want to setup the pre-commit locally, you can do the following:

$ pip install pre-commit

Now you can do:

$ pre-commit run --all-files

which will run the tools on all files in the sunpy git repository. The pre-commit tools can change some of the files, but in other cases it will report problems that require manual correction. If the pre-commit tool changes any files, they will show up as new changes that will need to be committed.

Automate#

Instead of running the pre-commit command each time you can install the git hook:

$ pre-commit install

which installs a command to .git/hooks/pre-commit which will run these tools at the time you do git commit and means you don’t have to run the first command each time. We only suggest doing the install step if you are comfortable with git and the pre-commit tool.

Documentation and Testing#

  • American English is the default language for all documentation strings and inline commands. Variables names should also be based on English words.

  • Documentation strings must be present for all public classes/methods/functions, and must follow the form outlined in the Documentation page. Additionally, examples or tutorials in the package documentation are strongly recommended.

  • Write usage examples in the docstrings of all classes and functions whenever possible. These examples should be short and simple to reproduce–users should be able to copy them verbatim and run them. These examples should, whenever possible, be in the doctests format and will be executed as part of the test suite.

  • Unit tests should be provided for as many public methods and functions as possible, and should adhere to the standards set in the Testing Guidelines document.

Data and Configuration#

  • We store test data in sunpy/data/test as long as it is less than about 100 kB. These data should always be accessed via the sunpy.data.test.get_test_filepath() and sunpy.data.test.get_test_data_filenames() functions.

  • We store data used for examples in the sample-data repository. This data should not be used for unit tests but can be within our documentation.

  • All persistent configuration should use the Customizing sunpy mechanism. Such configuration items should be placed at the top of the module or package that makes use of them, and supply a description sufficient for users to understand what the setting changes.

Standard output, warnings, and errors#

The built-in print(...) function should only be used for output that is explicitly requested by the user, for example print_header(...) or list_catalogs(...). Any other standard output, warnings, and errors should follow these rules:

  • For errors/exceptions, one should always use raise with one of the built-in exception classes, or a custom exception class. The nondescript Exception class should be avoided as much as possible, in favor of more specific exceptions (IOError, ValueError, etc.).

  • For warnings, one should always use the functions in sunpy.util.exceptions and not warnings.warn. This ensures we are always raising a sunpy specific warning type.

Including C Code#

  • C extensions are only allowed when they provide a significant performance enhancement over pure Python, or a robust C library already exists to provided the needed functionality.

  • The use of Cython is strongly recommended for C extensions.

  • If a C extension has a dependency on an external C library, the source code for the library should be bundled with sunpy, provided the license for the C library is compatible with the sunpy license. Additionally, the package must be compatible with using a system-installed library in place of the library included in sunpy.

  • In cases where C extensions are needed but Cython cannot be used, the PEP 7 Style Guide for C Code is recommended.

  • C extensions (Cython or otherwise) should provide the necessary information for building the extension.

Testing Guidelines#

This section describes the testing framework and format standards for tests in sunpy. Here we have heavily adapted the Astropy version, and it is worth reading that link.

The testing framework used by sunpy is the pytest framework, accessed through the pytest command.

Note

The pytest project was formerly called py.test, and you may see the two spellings used interchangeably.

Dependencies for testing#

Since the testing dependencies are not actually required to install or use sunpy, they are not included in “install_requires” in “pyproject.toml”.

Developers who want to run the test suite will need to install the testing packages using pip:

$ pip install -e ".[tests]"

If you want to see the current test dependencies, you check “extras_require” in “pyproject.toml”.

Running Tests#

There are currently two different ways to invoke the sunpy tests. However, we strongly suggest using tox as the default one. Each method uses the widely-used pytest framework and are detailed below.

tox#

The primary method is to use tox, which is a generic virtualenv management and test command line tool. We have several environments within our “tox.ini” file and you can list them:

$ tox -v -l

Then you can run any of them doing:

$ tox -e <name of env>

This will create a test environment in “.tox” and build, install sunpy and runs the entire test suite. This is the method that our continuous integration uses.

pytest#

The test suite can be run directly from the native pytest command. In this case, it is important for developers to be aware that they must manually rebuild any extensions by running pip install -e . before testing.

To run the entire suite with pytest:

$ pytest

will use the settings in pyproject.toml.

If you want to run one specific test file:

$ pytest sunpy/map/tests/test_mapbase.py

or one specific test in a test file:

$ pytest sunpy/map/tests/test_mapbase.py::<test_name>

(This does not work with tox and is a known issue.)

If a test errors, you can use pdb to create a debugging session at the moment the test fails:

$ pytest --pdb

If you see mention of:

UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown

You will have to either export MPLBACKEND = agg as an environmental variable or pass it as a command line pre-argument to pytest. This comes from the figure tests (see below).

self_test#

Another method is to use sunpy.self_test():

import sunpy
sunpy.self_test()

You will see something like the following in your terminal:

Starting sunpy self test...
Checking for packages needed to run sunpy:
All required and optional sunpy dependencies are installed.
Starting the sunpy test suite:
...

The tests will run and will report any fails.  You can report these through the `sunpy issue tracker <https://github.com/sunpy/sunpy/issues>`__ and we will strive to help.

It is possible to run this command in a situation where not all packages are installed. If this is the case, you will see the following when you run the test suite:

Starting sunpy self test...
Checking for packages needed to run sunpy:
The following packages are not installed for the sunpy[database] requirement:
* sqlalchemy
...
You do not have all the required dependencies installed to run the sunpy test suite.
If you want to run the sunpy tests install the 'tests' extra with `pip install "sunpy[tests]"`

This does not mean sunpy is broken, but you will need to install the extra packages to ensure a “complete” installation of sunpy and run the entire test suite. It is quite likely that you will run into not having the tests dependencies installed.

Remote data#

By default, no online tests are selected and so to run the online tests you have to:

$ tox -e py311-online

or:

$ pytest --remote-data=any

Figure tests#

In order to avoid changes in figures due to different package versions, we recommend using tox to run the figure tests:

$ tox -e py311-figure

This will ensure that any figures created are checked using the package versions that were used to create the original figure hashes. Running this will create a folder, “figure_test_images”, within your work folder (“<local clone location>/figure_test_images”), which is ignored by git. Inside this folder will be all the images created, as well as a json file with the hashes of the figures created by the test run. The current hashes are located within “sunpy/tests/figure_hashes_mpl_<ver>_ft_<ver>_astropy_<ver>.json” and this will be where you will need to update old hashes or create new figure entries if anything changes. The filenames are the versions of Matplotlib, freetype and astropy used. If these versions differ to your local setup, the figure tests will not run. In theory, the Python version does not change the results as we have pinned the packages that cause the hash to vary.

Running tests in parallel#

It is possible to speed up sunpy’s tests using the pytest-xdist plugin. This plugin can be installed using pip:

pip install pytest-xdist

Once installed, tests can be run in parallel using the --parallel commandline option. For example, to use 4 processes:

$ tox -e <name of environment> -- -n=4

or:

$ pytest -n 4 ./sunpy

Coverage reports#

sunpy can use pytest-cov generate test coverage reports and settings are stored in pyproject.toml. This plugin can be installed using pip:

$ pip install pytest-cov

To generate a test coverage report, use:

$ pytest --cov ./sunpy

This will print to the terminal a report of line coverage of our test suite. If you want to create a report in html, you can run:

$ pytest --cov-report xml:cov.xml --cov ./sunpy
$ coverage html
Writing tests#

pytest has the following test discovery rules:

* ``test_*.py`` or ``*_test.py`` files
* ``Test`` prefixed classes (without an ``__init__`` method)
* ``test_`` prefixed functions and methods

We use the first one for our test files, test_*.py and we suggest that developers follow this.

A rule of thumb for unit testing is to have at least one unit test per public function.

Simple example#

The following example shows a simple function and a test to test this function:

def func(x):
    """Add one to the argument."""
    return x + 1

def test_answer():
    """Check the return value of func() for an example argument."""
    assert func(3) == 5

If we place this in a test.py file and then run:

$ pytest test.py

The result is:

============================= test session starts ==============================
python: platform darwin -- Python 3.8.3 -- pytest-3.2.0
test object 1: /Users/username/tmp/test.py

test.py F

=================================== FAILURES ===================================
_________________________________ test_answer __________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test.py:5: AssertionError
=========================== 1 failed in 0.07 seconds ===========================

Sometimes the output from the test suite will have xfail meaning a test has passed although it has been marked as @pytest.mark.xfail), or skipped meaning a test that has been skipped due to not meeting some condition (online and figure tests are the most common).

You need to use the option -rs for skipped tests and -rx for xfailed tests, respectively. Or use -rxs for detailed information on both skipped and xfailed tests.

Where to put tests#

Each package should include a suite of unit tests, covering as many of the public methods/functions as possible. These tests should be included inside each package, e.g:

sunpy/map/tests/

“tests” directories should contain an __init__.py file so that the tests can be imported.

Online Tests#

There are some tests for functions and methods in sunpy that require a working connection to the internet. pytest is configured in a way that it iterates over all tests that have been marked as pytest.mark.remote_data and checks if there is an established connection to the internet. If there is none, the test is skipped, otherwise it is run.

Marking tests is pretty straightforward, use the decorator @pytest.mark.remote_data to mark a test function as needing an internet connection:

@pytest.mark.remote_data
def func(x):
    """Add one to the argument."""
    return x + 1

Tests that create files#

Tests may often be run from directories where users do not have write permissions so tests which create files should always do so in temporary directories. This can be done with the pytest tmpdir function argument or with Python’s built-in tempfile module.

Tests that use test data#

We store test data in “sunpy/data/test” as long as it is less than about 100 kB. These data should always be accessed via the sunpy.data.test.get_test_filepath() and sunpy.data.test.get_test_data_filenames() functions. This way you can use them when you create a test.

You can also use our sample data but this will have to be marked as an online test (see above):

import sunpy.data.sample

@pytest.mark.remote_data
def func():
    """Returns the file path for the sample data."""
    return sunpy.data.sample.AIA_131_IMAGE

Generally we do not run the tests on our sample data, so only do this if you have a valid reason.

Figure unit tests#

Note

The figure tests and the hashes they use are only checked on Linux and might be different on other platforms. We should suggest if you do not use a Linux, to add a fake hash to the json files and then CircleCi (ran on a PR) will tell you the real hash to use.

You can write sunpy unit tests that test the generation of Matplotlib figures by adding the decorator sunpy.tests.helpers.figure_test. Here is a simple example:

import matplotlib.pyplot as plt
from sunpy.tests.helpers import figure_test

@figure_test
def test_simple_plot():
    plt.plot([0,1])

The current figure at the end of the unit test, or an explicitly returned figure, has its hash (currently SHA256) compared against an established hash collection (more on this below). If the hashes do not match, the figure has changed, and thus the test is considered to have failed.

If you are adding a new figure test you will need to generate a new hash library:

$ tox -e py311-figure -- --mpl-generate-hash-library=sunpy/tests/figure_hashes_mpl_332_ft_261_astropy_42.json

The filename changes if the version of astropy or Matplotlib or freetype gets updated. So you might need to adjust this command. For the development figure tests:

$ tox -e py311-figure-devdeps -- --mpl-generate-hash-library=sunpy/tests/figure_hashes_mpl_dev_ft_261_astropy_dev.json

This will run the figure test suite and update the hashes stored.

If you want to check what the images look like, you can do:

$ tox -e py311-figure -- --mpl-generate-path=baseline

The images output from the tests will be stored in a folder called .tmp/py311-figure/baseline or baseline in the sunpy folder, so you can double check the test works as you expected.

doctests#

Code examples in the documentation will also be run as tests and this helps to validate that the documentation is accurate and up to date. sunpy uses the same system as Astropy, so for information on writing doctests see the astropy documentation.

You do not have to do anything extra in order to run any documentation tests. Within our pyproject.toml file we have set default options for pytest, such that you only need to run:

$ pytest <file to test>

to run any documentation test.

Bugs discovered#

In addition to writing unit tests new functionality, it is also a good practice to write a unit test each time a bug is found, and submit the unit test along with the fix for the problem. This way we can ensure that the bug does not re-emerge at a later time.

Documentation#

Overview#

All code must be documented and we follow these style conventions described here:

We recommend familiarizing yourself with this style.

Referring to other code#

To link to other methods, classes, or modules in sunpy you have to use backticks, for example:

`sunpy.map.GenericMap`

generates a link like this: sunpy.map.GenericMap.

We use the sphinx setting default_role = 'obj' so that you do not nor SHOULD NOT use the :class: qualifier, but :func:, :meth: are different (more on this below).

Often, you don’t want to show the full package and module name. As long as the target is unambiguous you can simply leave them out:

`.GenericMap`

and the link still works: GenericMap.

If there are multiple code elements with the same name (e.g. peek() is a method in multiple classes), you’ll have to extend the definition:

`.GenericMap.peek` or `.CompositeMap.peek`

These will show up as GenericMap.peek or CompositeMap.peek. To still show only the last segment you can add a tilde as prefix:

`~.GenericMap.peek` or `~.CompositeMap.peek`

will render as peek or peek.

Other packages can also be linked via intersphinx:

`numpy.mean`

will return this link: numpy.mean. This works for Python, Numpy and Astropy (full list is in docs/conf.py).

With Sphinx, if you use :func: or :meth:, it will add closing brackets to the link. If you get the wrong pre-qualifier, it will break the link, so we suggest that you double check if what you are linking is a method or a function.

:class:`numpy.mean()`
:meth:`numpy.mean()`
:func:`numpy.mean()`

will return two broken links (“class” and “meth”) but “func” will work.

sunpy-Specific Rules#
  • For all RST files, we enforce a one sentence per line rule and ignore the line length.

  • Standards on docstring length and style are enforced using docformatter:

docformatter -r -i  --pre-summary-newline --make-summary-multi-line
  • Heading style is the following for all RST files:

* with overline, for titles
= for sections
- for subsections
^ for subsubsections
" for paragraphs
  • Anchors for each page should follow this format: sunpy-<section>-<subsection>-<summary of title>., e.g., sunpy-tutorial-acquiring-data-index.

  • Use of .. code-block: is required for all code examples.

Documenting Data Sources#

Subclasses of GenericMap or TimeSeries must provide a detailed docstring providing an overview of the data source that the object represents. In order to maintain consistency and completeness, the following information must be provided by a data source docstring, if available, and preferably in the following order:

  • the name of the mission and instrument and the institution that built it

  • short description of the instrument (e.g. Cassegrain reflector, Wolter-1 grazing incidence x-ray, coronagraph) including the type of detector

  • description of the platform (e.g. satellite in 28 deg inclined orbit, a telescope on the summit of Mauna Kea in Hawaii)

  • description of the primary purpose or science goals of the instrument.

  • list of all wavelength(s) or passbands in appropriate units

  • description of the emission processes which dominate in those passbands

  • appropriate measurement properties such as field of view, angular resolution, time resolution

  • description of the operational concept (e.g. operates 24/7, observes from 7 am to 5 pm UT) including mention of unusual operations scenarios (e.g. calibration seasons, eclipse seasons)

  • the start and end of the data set

In addition, a reference section must be provided with links to the following resources, if available,

  • the mission web page

  • the instrument web page

  • relevant wikipedia page(s)

  • relevant user guide(s)

  • the mission paper and instrument paper

  • information to interpret metadata keywords such as FITS header reference

  • the data archive

Examples can be found in any class defined in any Python file in sunpy/map/sources/ such as AIAMap.

Sphinx#

All of the sunpy documentation (like this page) is built by Sphinx, which is a tool especially well-suited for documenting Python projects. Sphinx works by parsing files written using a a Mediawiki-like syntax called reStructuredText. In addition to parsing static files of reStructuredText, Sphinx can also be told to parse code comments. In fact, in addition to what you are reading right now, the Python documentation was also created using Sphinx.

Usage#

All of the sunpy documentation is contained in the “docs” folder and code documentation strings. The examples from the example gallery can be found in the “examples” folder.

In the root directory run:

tox -e build_docs-gallery

This will generate HTML documentation for sunpy in the “docs/_build/html” directory. You can open the “index.html” file to browse the final product. The gallery examples are located under “docs/_build/html/generated/gallery”. Sphinx builds documentation iteratively, only adding things that have changed.

If you want to build the documentation without executing the gallery examples, i.e. to reduce build times while working on other sections of the documentation you can run:

tox -e build_docs

If you’d like to start from scratch (i.e., remove the tox cache) then change to the docs/ directory and run:

make clean
cd ..
tox -r -e build_docs-gallery

To build the documentation in your current python environment you must have all the dependencies specified in pyproject.toml installed (pip install -e .[docs,docs-gallery]). Then change to the docs/ directory and run:

make html

For more information on how to use Sphinx, consult the Sphinx documentation.

Special Sphinx directives#
minigallery directive#

Sphinx will automatically record which functions, classes, etc. are used in each gallery example. In the documentation, you can insert a mini-gallery of the subset of the gallery examples that uses a particular function, class, etc. For example, the following RST block:

.. minigallery:: sunpy.coordinates.RotatedSunFrame

produces this mini-gallery:

Comparing differential-rotation models

Comparing differential-rotation models

Differentially rotating a coordinate

Differentially rotating a coordinate

Overlaying differentially rotated gridlines

Overlaying differentially rotated gridlines

If you want to specify more than one object, separate them by spaces. This is particularly useful if you need to cover multiple namespaces in which an object may be accessed, e.g.:

.. minigallery:: sunpy.coordinates.RotatedSunFrame sunpy.coordinates.metaframes.RotatedSunFrame
generate directive#

In rare circumstances, one may want to insert “raw” HTML directly into the pages written by Sphinx. For HTML that is statically available (i.e., already written in some form), one can use the “raw” directive. For HTML that is generated by Python code, sunpy provides the custom directive generate. Here’s an example RST block:

.. generate:: html
    :html_border:

    import os
    from sunpy.data.sample import file_dict
    print("<table>")
    for key, value in file_dict.items():
        print(f"<tr><th>{key}</th><td>{os.path.basename(value)}</td></tr>")
    print("</table>")

to insert the following HTML table:

AIA_094_IMAGEAIA20110607_063305_0094_lowres.fits
AIA_131_IMAGEAIA20110607_063301_0131_lowres.fits
AIA_1600_IMAGEAIA20110607_063305_1600_lowres.fits
AIA_1600_VENUS_IMAGEaia_lev1_1600a_2012_06_06t04_07_29_12z_image_lev1_lowres.fits
AIA_171_IMAGEAIA20110607_063302_0171_lowres.fits
AIA_171_ROLL_IMAGEaiacalibim5.fits
AIA_193_CUTOUT01_IMAGEAIA20110607_063307_0193_cutout.fits
AIA_193_CUTOUT02_IMAGEAIA20110607_063931_0193_cutout.fits
AIA_193_CUTOUT03_IMAGEAIA20110607_064555_0193_cutout.fits
AIA_193_CUTOUT04_IMAGEAIA20110607_065219_0193_cutout.fits
AIA_193_CUTOUT05_IMAGEAIA20110607_065843_0193_cutout.fits
AIA_193_IMAGEAIA20110607_063307_0193_lowres.fits
AIA_193_JUN2012AIA20120601_000007_0193_lowres.fits
AIA_211_IMAGEAIA20110607_063302_0211_lowres.fits
AIA_304_IMAGEAIA20110607_063334_0304_lowres.fits
AIA_335_IMAGEAIA20110607_063303_0335_lowres.fits
AIA_STEREOSCOPIC_IMAGEaia_lev1_171a_2023_07_06t00_05_33_35z_image_lev1.fits
CALLISTO_SPECTRUMBIR_20110607_062400_10.fit
EIT_195_IMAGEeit_l1_20110607_203753.fits
EUVI_STEREOSCOPIC_IMAGE20230706_000525_n4eua.fts
EVE_TIMESERIES20110607_EVE_L0CS_DIODES_1m.txt
GBM_TIMESERIESglg_cspec_n5_110607_v00.pha
GOES_XRS_TIMESERIESgo1520110607.fits
HMI_LOS_IMAGEHMI20110607_063211_los_lowres.fits
LOFAR_IMAGELOFAR_70MHZ_20190409_131136.fits
LYRA_LEVEL3_TIMESERIESlyra_20110607-000000_lev3_std.fits
NORH_TIMESERIEStca110607.fits
RHESSI_IMAGEhsi_image_20110607_063300.fits
RHESSI_TIMESERIEShsi_obssumm_20110607_025.fits
SRS_TABLE20110607SRS.txt
STEREO_A_195_JUN201220120601_000530_n4eua.fits
STEREO_B_195_JUN201220120601_000530_n4eub.fits
SWAP_LEVEL1_IMAGEswap_lv1_20110607_063329.fits
Troubleshooting#

Sphinx can be very particular about formatting, and the warnings and errors aren’t always obvious.

Below are some commonly-encountered warning/error messages along with a human-readable translation:

WARNING: Duplicate explicit target name: “xxx”.

If you reference the same URL, etc more than once in the same document sphinx will complain. To avoid, use double-underscores instead of single ones after the URL.

ERROR: Malformed table. Column span alignment problem at line offset n

Make sure there is a space before and after each colon in your class and function docs (e.g. attribute : type, instead of attribute: type). Also, for some sections (e.g. Attributes) numpydoc seems to complain when a description spans more than one line, particularly if it is the first attribute listed.

WARNING: Block quote ends without a blank line; unexpected unindent.

Lists should be indented one level from their parents.

ERROR: Unknown target name: “xxx”

In addition to legitimate errors of this type, this error will also occur when variables have a trailing underscore, e.g., xxx_.

WARNING: Explicit markup ends without a blank line; unexpected unindent.

This usually occurs when the text following a directive is wrapped to the next line without properly indenting a multi-line text block.

WARNING: toctree references unknown document ‘…’ / WARNING: toctree contains reference to nonexisting document

This pair of errors is due to the way numpydoc scrapes class members.

Pull Requests and GitHub Teams#

This document describes the standards required for a pull request to sunpy and an explanation of our automated tests.

Each pull request must meet the following criteria before it is considered for merge:

  • The code must be PEP 8 compliant and meet the ill-defined sunpy quality standards. We have these in the Coding Standards page.

  • The PR must contain a changelog entry if it changes the behavior of any code.

  • The test coverage should not decrease, and for new features should be at or very close to 100%.

  • All code must be properly documented. Each function and each class must have an associated documentation string in the correct format.

Review Process#

Before the “merge” button is clicked the following criteria must be met:

  • All the continuous integration must pass unless there is a known issue.

  • At least two members (excluding the PR author) of the “sunpy-developers” group must have approved the PR. Exceptions can be made for minor changes, see below.

  • All comments posted on the thread must be resolved.

It is important that approval for merging the PR is always done by explicitly approving the PR through the GitHub UI before merging, so a record of approval is left in the PR thread.

Minor changes#

If a PR only makes minor changes, it can be merged by the first reviewer, if they are confident they fully understand the changes. If this happens, the minor-change label should be added to the PR to indicate that it has been considered minor enough to need only one reviewer. The PR author can add this label as a suggestion, but the first reviewer can remove the label as part of their evaluation.

Exactly what constitutes a minor-change is left up to the the reviewer but some examples might include: - Improvements to existing documentation - Small bugfixes - Changes in code style rather than substance

As a guideline, minor-changes don’t include:

  • New features

  • New documentation pages

  • Changes to the public facing API

Continuous Integration#

Currently we have a variety of services that respond or activate on an opened pull request.

Comments from bots:

  • pep8speaks: Performs a PEP8 check on any submitted code. This is updated as the code changes.

Checks that appear at the bottom of a pull request:

PR checks

or at the top under the “Checks” tab:

PR checks tab
  • figure-tests (CircleCi): Runs two figure tests environments (“ci/circleci: py3_-figure”, “ci/circleci: py3_-figure-devdeps”).

  • figure_report (Giles): Show the final results and download updated hashes of the figure tests.

  • figure_report_devdeps (Giles): Show the final results and download updated hashes of the figure tests using development packages.

  • changelog: absent | found (Giles): If a changelog is needed, this will check and will pass if a changelog with the correct number is found.

  • docs/readthedocs.org:sunpy (Read the Docs): This builds our documentation. This primary check is to ensure the documentation has rendered correctly. Warnings are not checked on this build but under GitHub Actions (see below).

  • CI (GitHub Actions): Runs our test suite on multiple operating systems. If the minimal “CI / core” tests are successful, the indepth “CI / test”, documentation “CI / docs” test and remote data “CI / online” tests will be run. You will see multiple jobs within each group. Each job corresponds to a tox environment being run on a particular operating system.

  • codecov/patch (CodeCov): Checks how many lines of the code lack test coverage for the submitted code in the pull request.

  • codecov/project (CodeCov): Checks how many lines of the code lack test coverage in sunpy overall.

  • pre-commit.ci - pr: Checks the code style checks have passed. This CI will automatically fix style issues by commenting pre-commit.ci autofix on its own line in a comment on the PR.

It is common to see some of these checks fail. This can be happen due to a change that has broken a test (should be fixed) or a remote server has failed (might have to wait for it to come back). Therefore it is important to check why a task failed and if has a pre-existing issue, it can be safe to ignore a failing check on that pull request. However, you should try to ensure that as many checks pass before merging.

Understanding GitHub Actions#

The vast majority of our tests are run on GitHub Actions and this means you might have to navigate to the results if you want to check why the tests failed. The tests for GitHub Actions are split into multiple phases to reduce the number of builds running at one time. If your PR fails the minimal initial stage, the subsequent stages tests will not run.

The Azure checks on GitHub manifest:

PR checks tab

This is the main form. There will be one check per GitHub Actions job ran. The publish and notify jobs are skipped in PRs, and each stage has an additional “Load tox environments” job to configure set up the stage. The “Details” link will show you the log output of the particular check:

Summary of Azure outputs on Checks tab

On the left you should see the entire list of GitHub Actions checks. You can navigate between the jobs here. You can also see a flow diagram for the jobs by clicking on “Summary”.

For each of the jobs you can see each step that is undertaken. Normally the “Run tox” step will be red if the tests have failed. You will need to click on this so it will load the output from the test suite.

Our test suite is very verbose, so there will be a lot of text outputted. The important bits of information should be at the bottom as “pytest” prints out a test summary at the end. For example:

============================================================================= short test summary info =============================================================================
SKIPPED [1] d:\a\1\s\.tox\py37\lib\site-packages\pytest_doctestplus\plugin.py:178: unable to import module local('d:\\a\\1\\s\\.tox\\py37\\lib\\site-packages\\sunpy\\io\\setup_package.py')
SKIPPED [213] d:\a\1\s\.tox\py37\lib\site-packages\pytest_remotedata\plugin.py:87: need --remote-data option to run
SKIPPED [18] d:\a\1\s\.tox\py37\lib\site-packages\_pytest\doctest.py:387: all tests skipped by +SKIP option
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\map\sources\tests\test_source_type.py:21: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\map\sources\tests\test_source_type.py:30: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_ana.py:22: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_ana.py:31: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_ana.py:40: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_ana.py:49: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_ana.py:58: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_ana.py:67: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_filetools.py:54: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_filetools.py:73: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_filetools.py:106: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_filetools.py:115: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_filetools.py:122: ANA is not available.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_jp2.py:11: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_jp2.py:21: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\io\tests\test_jp2.py:31: Glymur can not be imported.
SKIPPED [1] .tox\py37\lib\site-packages\sunpy\net\tests\test_fido.py:298: Windows.
FAILED ..\..\.tox\py37\lib\site-packages\sunpy\timeseries\sources\noaa.py::sunpy.timeseries.sources.noaa.NOAAGoesSXRTimeSeries

If you want to find the full test output, you can search the tab for the name of the test out of the ~3 results, one will be that output.

SunPy GitHub Groups#

This document has already referred to two SunPy groups, namely “developers” and “maintainers” there is also a third primary SunPy group “owners”.

SunPy owners#

The SunPy owners group is the group of people who have total control over the SunPy GitHub organization. The SunPy board have control over who is in this group, it has been decided that generally it will be the Lead Developer and the SunPy board chair and vice-chair.

sunpy Maintainers#

This is the group of people who have push access to the main sunpy repository. The membership of this group is at the discretion of the Lead Developer, but shall generally be made up of people who have demonstrated themselves to be trust worthy and active contributors to the project.

This group has subgroups for each section of the repository that has maintainers. The members of these groups will automatically be requested to review all PRs which change files in that subpackage.

sunpy Developers#

The members of this group have “read” access to the sunpy repository. As all these repository are open anyway, what this effectively means is that these people can be assigned to issues. The members of this group are people who are involved in the development of sunpy at a good frequency, they are people who’s opinions have been demonstrated to be constructive and informative.

Use of quantities and units#

Much code perform calculations using physical quantities. SunPy uses astropy’s quantities and units implementation to store, express and convert physical quantities. New classes and functions should adhere to the SunPy project’s quantity and unit usage guidelines.

This document sets out SunPy’s reasons and requirements for the usage of quantities and units. Briefly, SunPy’s policy is that all user-facing function/object arguments which accept physical quantities as input **MUST* accept astropy quantities, and ONLY astropy quantities*.

Developers should consult the Astropy Quantities and Units page for the latest updates on using quantities and units. The astropy tutorial on quantities and units also provides useful examples on their capabilities.

Astropy provides the decorator quantity_input that checks the units of the input arguments to a function against the expected units of the argument. We recommend using this decorator to perform function argument unit checks. The decorator ensures that the units of the input to the function are convertible to that specified by the decorator, for example

>>> import astropy.units as u
>>> @u.quantity_input
... def myfunction(myangle: u.arcsec):
...     return myangle**2

This function only accepts arguments that are convertible to arcseconds. Therefore:

>>> myfunction(20 * u.degree)
<Quantity 400. deg2>

returns the expected answer but:

>>> myfunction(20 * u.km)
Traceback (most recent call last):
...
astropy.units.core.UnitsError: Argument 'myangle' to function 'myfunction' must be in units convertible to 'arcsec'.

raises an error.

The following is an example of a use-facing function that returns the area of a square, in units that are the square of the input length unit:

>>> @u.quantity_input
... def get_area_of_square(side_length: u.m):
...     """
...     Compute the area of a square.
...
...     Parameters
...     ----------
...     side_length : `~astropy.units.quantity.Quantity`
...         Side length of the square
...
...     Returns
...     -------
...     area : `~astropy.units.quantity.Quantity`
...         Area of the square.
...     """
...
...     return (side_length ** 2)

This more advanced example shows how a private function that does not accept quantities can be wrapped by a function that does:

>>> @u.quantity_input
... def some_function(length: u.m):
...     """
...     Does something useful.
...
...     Parameters
...     ----------
...     length : `~astropy.units.quantity.Quantity`
...         A length.
...
...     Returns
...     -------
...     length : `~astropy.units.quantity.Quantity`
...         Another length
...     """
...
...     # the following function either
...     # a] does not accept Quantities
...     # b] is slow if using Quantities
...     result = _private_wrapper_function(length.convert('meters').value)
...
...     # now convert back to a quantity
...     result = Quantity(result_meters, units_of_the_private_wrapper_function)
...
...     return result

In this example, the non-user facing function _private_wrapper_function requires a numerical input in units of meters, and returns a numerical output. The developer knows that the result of _private_wrapper_function is in the units units_of_the_private_wrapper_function, and sets the result of some_function to return the answer in those units.

Repo management

Workflow for Maintainers#

This page is for maintainers who can merge our own or other peoples’ changes into the upstream repository.

Seeing as how you’re a maintainer, you should be completely on top of the basic git workflow in Newcomers’ Guide and Astropy’s git workflow.

Integrating changes manually#

First, check out the “sunpy” repository. Being a maintainer, you’ve got read-write access.

It’s good to have your upstream remote have a scary name, to remind you that it’s a read-write remote:

$ git remote add upstream-rw git@github.com:sunpy/sunpy.git
$ git fetch upstream-rw

Let’s say you have some changes that need to go into trunk (upstream-rw/main).

The changes are in some branch that you are currently on. For example, you are looking at someone’s changes like this:

$ git remote add someone git://github.com/someone/sunpy.git
$ git fetch someone
$ git branch cool-feature --track someone/cool-feature
$ git checkout cool-feature

So now you are on the branch with the changes to be incorporated upstream. The rest of this section assumes you are on this branch.

If you prefer not to add remotes, you can make git fetch all pull requests opened to Sunpy. Locate the section for your git remote in the .git/config file. It looks like this:

[remote "upstream"]
        url = git@github.com:sunpy/sunpy.git
        fetch = +refs/heads/*:refs/remotes/upstream/*

Now add the line fetch = +refs/pull/*/head:refs/remotes/upstream/pr/* to this section. It ends up looking like this:

[remote "upstream"]
        url = git@github.com:sunpy/sunpy.git
        fetch = +refs/heads/*:refs/remotes/upstream/*
        fetch = +refs/pull/*/head:refs/remotes/upstream/pr/*

Now fetch all the pull requests:

$ git fetch upstream
From github.com:sunpy/sunpy
* [new ref]         refs/pull/1000/head -> upstream/pr/1000
* [new ref]         refs/pull/1002/head -> upstream/pr/1002
* [new ref]         refs/pull/1004/head -> upstream/pr/1004
* [new ref]         refs/pull/1009/head -> upstream/pr/1009

To check out a particular pull request:

$ git checkout pr/999
Branch pr/999 set up to track remote branch pr/999 from upstream.
Switched to a new branch 'pr/999'
When to remove or combine/squash commits#

In all cases, be mindful of maintaining a welcoming environment and be helpful with advice, especially for new contributors. It is expected that a maintainer would offer to help a contributor who is a novice git user do any squashing that that maintainer asks for, or do the squash themselves by directly pushing to the PR branch.

Pull requests must be rebased and at least partially squashed (but not necessarily squashed to a single commit) if large (approximately >10KB) non-source code files (e.g. images, data files, etc.) are added and then removed or modified in the PR commit history (The squashing should remove all but the last addition of the file to not use extra space in the repository).

Combining/squashing commits is encouraged when the number of commits is excessive for the changes made. The definition of “excessive” is subjective, but in general one should attempt to have individual commits be units of change, and not include reversions. As a concrete example, for a change affecting < 50 lines of source code and including a changelog entry, more than a two commits would be excessive. For a larger pull request adding significant functionality, however, more commits may well be appropriate.

As another guideline, squashing should remove extraneous information but should not be used to remove useful information for how a PR was developed. For example, 4 commits that are testing changes and have a commit message of just “debug” should be squashed. But a series of commit messages that are “Implemented feature X”, “added test for feature X”, “fixed bugs revealed by tests for feature X” are useful information and should not be squashed away without reason.

When squashing, extra care should be taken to keep authorship credit to all individuals who provided substantial contribution to the given PR, e.g. only squash commits made by the same author.

When to rebase#

Pull requests must be rebased (but not necessarily squashed to a single commit) if:

  • There are commit messages include offensive language or violate the code of conduct (in this case the rebase must also edit the commit messages)

Pull requests may be rebased (either manually or with the rebase and merge button) if:

  • There are conflicts with main

  • There are merge commits from upstream/main in the PR commit history (merge commits from PRs to the user’s fork are fine)

Asking contributors who are new to the project or inexperienced with using git is discouraged, as is maintainers rebasing these PRs before merge time, as this requires resetting of local git checkouts.

A few commits#

If there are only a few commits, consider rebasing to upstream:

# Fetch upstream changes
$ git fetch upstream-rw

# Rebase
$ git rebase upstream-rw/main
A long series of commits#

If there are a longer series of related commits, consider a merge instead:

$ git fetch upstream-rw
$ git merge --no-ff upstream-rw/main

Note the --no-ff above. This forces git to make a merge commit, rather than doing a fast-forward, so that these set of commits branch off trunk then rejoin the main history with a merge, rather than appearing to have been made directly on top of trunk.

Check the history#

Now, in either case, you should check that the history is sensible and you have the right commits:

$ git log --oneline --graph
$ git log -p upstream-rw/main..

The first line above just shows the history in a compact way, with a text representation of the history graph. The second line shows the log of commits excluding those that can be reached from trunk (upstream-rw/main), and including those that can be reached from current HEAD (implied with the .. at the end). So, it shows the commits unique to this branch compared to trunk. The -p option shows the diff for these commits in patch form.

Push to open pull request#

Now you need to push the changes you have made to the code to the open pull request:

$ git push git@github.com:<username>/sunpy.git HEAD:<name of branch>

You might have to add --force if you rebased instead of adding new commits.

Using Milestones and Labels#

Current milestone guidelines:

  • Only confirmed issues or pull requests that are release critical or for some other reason should be addressed before a release, should have a milestone. When in doubt about which milestone to use for an issue, do not use a milestone and ask other the maintainers.

Current labelling guidelines:

  • Issues that require fixing in main, but that also are confirmed to apply to supported stable version lines should be marked with a “Affects Release” label.

  • All open issues should have a “Priority <level>”, “Effort <level>” and “Package <level>”, if you are unsure at what level, pick higher ones just to be safe. If an issue is more of a question or discussion, you can omit these labels.

  • If an issue looks to be straightforward, you should add the “Good first issue” and “Hacktoberfest” label.

  • For other labels, you should add them if they fit, like if an issue affects the net submodule, add the “net” label or if it is a feature request etc.

Using Projects#

Projects allow us to layout current pull requests and issues in a manner that enables a more “meta” view regarding major releases. We categorize pull requests and issues into several levels of priorities and whether these can be classed as blockers before a release can be attempted. Further we can add general notes that someone deems important for a release.

Updating and Maintaining the Changelog#

The changelog will be read by users, so this description should be aimed at sunpy users instead of describing internal changes which are only relevant to the developers.

The current changelog is kept in the file “CHANGELOG.rst” at the root of the repository. You do not need to update this file as we use towncrier to update our changelog. This is built and embedded into our documentation.

Towncrier will automatically reflow your text, so it will work best if you stick to a single paragraph, but multiple sentences and links are OK and encouraged. You can install towncrier and then run towncrier --draft if you want to get a preview of how your change will look in the final release notes.

Instructions on how to write a changelog..

Releasing sunpy#

We have a step by step checklist on the SunPy Wiki on how to release the sunpy core package.

Dependency Support Policy#

Note

This policy is based on NEP-0029.

sunpy has a short list of core dependencies (Python, numpy, astropy, parfive) and a long list of optional dependencies. The minimum version of these packages that we enforce follows this policy.

  • Python: Released in the prior 42 months from the anticipated release date.

  • astropy: Released in the prior 12 months from the anticipated release date.

  • Everything else: Released in the prior 24 months from the anticipated release date.

Sponsored affiliated packages will support at least the sunpy LTS version at the time of their release.

For dependencies only needed to run our tests we will support versions released in the prior 12 months to the current date.

What runs on our Continuous Integration#

Overall the aim of the Continuous Integration is to provide a way for contributors to test their code on multiple platforms and help in the development process. While we can run many jobs, we aim to limit the selection depending in the context to ensure that the time taken is not wasted or ends up delaying a pull request or release.

The goal is that, the builds we do not run on a pull request should primarily be ones which are unlikely to be the fault of the change in the pull request if they fail.

Currently we have several stages of CI jobs, some run on a pull request and some will only run on a schedule.

  1. “core” - Pull Request, Scheduled and Release

    This runs a basic offline test suite on Linux for the latest version of Python we support. It ensures that basic functionality works and that we don’t have any regressions.

  2. “test” - Pull Request, Scheduled and Release

    This runs a basic test suite on Windows and Mac OS for older versions of Python we support. It ensures that basic functionality works and that we don’t have any regressions. This stage needs to wait for the “core” stage to complete. Furthermore, we run:

    • “oldestdeps” - Check the offline test suite with the oldest dependencies installed.

  3. “docs” - Pull Request, Scheduled and Release

    This runs a documentation build (without executing gallery examples) to test that the HTML documentation can be generated without any errors or warnings. The build is cached and the gallery examples are then tested during the “online” stage.

  4. “online” - Pull Request, Scheduled

    This runs a full test suite on Linux for a version of Python. This build can fail (due to a range of reasons) and can take up to 45 minutes to run. We want to ensure that the offline tests are passing before we consider running this. Therefore this stage needs to wait for the “core” stage to complete.

    In addition, we run the documentation build to execute the gallery examples which can fail due to the same reasons as the online build. The documentation build from the “docs” stage is cached and restored for this documentation build. Therefore this stage also needs to wait for the “docs” stage to complete.

    As these are not tested when we build the distribution or wheels, we skip these on a release. These should be checked based on the last commit for that release branch instead before a tag.

  5. “cron” - Scheduled

    Here we put builds that are useful to run on a schedule but not so useful on a pull request. This allows us to run more “exotic” or focused builds that day to day, should not affect a pull request.

    These are:

    • “base_deps” - Check that sunpy does not error if you only have the base dependencies installed.

    • “devdeps” - Check the offline test suite with the development dependencies installed. Likely to break due to upstream changes we can’t fix and have to wait to be resolved.

    • “conda” - Check the offline test suite when using conda instead of pip/pypi. Likely to break due to packaging upstream we can’t fix and have to wait to be resolved.

Making Changes to Released Versions#

When changes need to be made in a bugfix release of an already released version of sunpy this is done by a process called “backporting”. The process is as follows:

  • Open a Pull Request (PR) as normal targeting the main branch.

  • Apply a label to that PR (or get a maintainer to do it) of the format Backport X.Y.

  • Once the PR has been merged a bot will open a new PR with the same changes targeting the X.Y branch.

Managing backports is done by the sunpy maintainers, as a contributor you generally shouldn’t have to worry about it. The following documentation is for maintainers on how to interact with the backport bot.

Controlling the Backport Bot#

The backport bot in use on the sunpy repo is MeeseeksDev.

Upon merging a PR the bot will look for the existence of a label with the following template in the label description field on-merge: backport to X.Y.

If the decision to backport a PR is taken after the merge of the PR, then a command needs to be added to a comment on the PR: @MeeseeksDev backport [to] {branch}

Manual Backports#

If a backport fails, meeseeks will add a comment to the PR and add a label named “Still Needs Manual Backport”. If you then manually backport the PR, please remove this label when the backport PR is open so that the label is an accurate list of PRs in need of manual backporting.

When doing a manual backport please do not forget to put the PR number you are backporting in the description of the PR so that GitHub links the two PRs together. (This automatic linking does not happen if the number is in the title).

Warning

HEAVY WIP

Submodule and funding#

Here we list whatever parts of the SunPy project have been funded by external sources such as grants or Google Summer of Code (GSoC) and the like.

Database#

Google Summer of Code (2013)#

Parts of the now removed sunpy.database module.

Net#
Google Summer of Code (2014)#

fido_factory.py, vso.py, most of the dataretriever submodule

ESA Summer of Code in Space (2011)#

attr.py tools/hektemplate.py tools/hek_mkcls.py

Extending sunpy

sunpy’s Public API#

Convention in the Python ecosystem is to add an underscore to the start of a function to denote if a function or method is “private” e.g., _angular_radius. If it is considered to be private, there is no guarantee that the API or behavior will change with a warning, so external use of these functions or methods are strongly discouraged.

sunpy follows this convention but with one extra caveat. Within each python file, we have a __all__ that defines what is imported into the namespace if you do e.g., from sunpy.coordinates.sun import *. This is the “public” API of that module. These functions are the ones listed within our API documentation: Reference. If you do import sunpy.coordinates.sun, you can still access the “private” functions.

This means that all of the public API will follow the deprecation policy detailed below with the exception of sunpy.util which is considered to be for internal sunpy use only.

Deprecation Policy and Breaking Changes#

All public API within the SunPy project (the sunpy core package and stable affiliated packages) will enforce strict standards when it comes to either changing, updating or breaking the API.

Deprecations#

If you want to deprecate anything within in sunpy, you should do the following:

from sunpy.util.decorators import deprecated

@deprecated(since="3.1", message="We will be moving this", alternative="sunpy.net.Scraper")
class Scraper:

The deprecation warning has to be in one LTS release before the deprecated code can be removed. So in the above example, the warning will be in sunpy 3.1 but it can not be removed until sunpy 4.1 after the 4.0 LTS release.

There should be a “deprecation” changelog entry to accompany the deprecation warning. When the code is actually removed, a “removal” changelog should be added.

The same applies if you want to change the default value of a keyword argument for a function or method, e.g.:

from sunpy.util.exceptions import warn_deprecated

if response_format is None:
    response_format = "legacy"
    warn_deprecated("The default response format from the VSO client will "
                    "be changing to 'table' in version 3.1. "
                    "To remove this warning set response_format='legacy' "
                    "to maintain the old behaviour or response_format='table'"
                    " to use the new behaviour.")

Note

This is a summary of SEP-0009 which is the formal SunPy project deprecation policy.

Breaking Changes#

Every attempt is made to avoid breaking any public API in sunpy but in the case it does happen.

There should be a “breaking” changelog entry to accompany the change with as much detail on how to update a user’s code due to this breaking change.

Logging, Warnings, and Exceptions#

Overview#

sunpy makes use of a logging system to deal with messages (see Logger Objects). This provides the users and developers the ability to decide which messages to show, to capture them, and to optionally also send them to a file. The logger will log all messages issued directly to it but also warnings issued through warnings.warn as well as exceptions.

The logger is configured as soon as sunpy is imported. You can access it by importing it explicitly:

from sunpy import log

Messages can be issued directly to it with the following levels and in the following way:

log.debug("Detailed information, typically of interest only when diagnosing problems.")

log.info("A message conveying information about the current task, and confirming that
          things are working as expected.")

log.warning("An indication that something unexpected happened, or indicative of
             some problem in the near future (e.g. disk space low).

             The software is still working as expected.")
log.error("Due to a more serious problem, the software has not been able to
           perform some function but the task is still continuing.")

log.critical("A serious error, indicating that the program itself may be unable to
              continue running. A real error may soon by issued and the task will fail.")

The difference between logging a warning/error/critical compared to issuing a Python warning or raising an exception are subtle but important.

Use Python warnings.warn in library code if the issue is avoidable and the user code should be modified to eliminate the warning.

Use log.warning() if there is likely nothing the user can do about the situation, but the event should still be noted. An example of this might be if the input data are all zeros. This may be unavoidable or even by design but you may want to let the user know.

True exceptions (not log.error()) should be raised only when there is no way for the function to proceed.

Regardless of the type (log message or warning or exception) messages should be one or two complete sentences that fully describe the issue and end with a period.

Issuing Warnings#

sunpy warnings are provided by the sunpy.util module. The primary warning which should be used is sunpy.util.exceptions.SunpyUserWarning. For deprecation use sunpy.util.exceptions.SunpyDeprecationWarning or sunpy.util.exceptions.SunpyPendingDeprecationWarning.

These three warning types have corresponding functions to raise them:

>>> from sunpy.util.exceptions import warn_user
>>> from sunpy.util.exceptions import warn_deprecated
>>> from sunpy.util.exceptions import warn_metadata

These warning functions must be used to interact correctly with the logging system. A warning can be issued in the following way:

>>> from sunpy.util.exceptions import warn_user
>>> warn_user("You have been warned about something you did not do correctly.")  

See the section above for a discussion about the distinction between log.warn() and raising a warning.

Raising Exceptions#

Raising errors causes the program to halt. Likely the primary error that a sunpy developer will want to use is

  • ValueError: should be raised if the input is not what was expected and could not be used. Functions should not return anything (like None) in that case or issue a warning.

Exceptions are raised simply with:

>>> raise ValueError("The following error occurred.")  

For more information on exceptions see the Built-in Exceptions documentation.

Tests using the Remote Data Manager#

A pytest fixture (sunpy_cache) is provided for ease of mocking network requests when using cache. The following example demonstrates the usage of the fixture:

@manager.require('test_file',
                 ['http://data.sunpy.org/sample-data/predicted-sunspot-radio-flux.txt'],
                 '4c85b04a5528aa97eb84a087450eda0421c71833820576330bba148564089b11')
def test_function():
    return manager.get('test_file')

@pytest.fixture()
def local_cache(sunpy_cache):
    sunpy_cache = sunpy_cache('sunpy.test_module.cache')
    sunpy_cache.add('http://example.com/test_file',
                    'test_data_path')

The above snippet creates a pytest fixture called local_cache. This fixture can be used in wherever the files have to be mocked. An example is given below:

def test_test_function(local_cache):
    # inside this function the mocked cache is used

    # test_function uses 'http://example.com/test_file'
    assert test_function() == True