Maps in sunpy are are 2-dimensional data associated with a coordinate system. In this guide, we will cover some of the basic functionality of maps. Once you’ve read through this guide check out Maps ( for a more thorough look at sunpy maps. There you can see what instruments are currently supported or you can access the code reference for each instrument-specific map subclass.

Creating maps

To make things easy, sunpy can download several example files which are used throughout the docs. These files have names like and To create a Map from the the sample AIA image type the following into your Python shell:

>>> import sunpy
>>> import
>>> import  

>>> my_map =  

The variable my_map is an AIAMap object. To create one from a local FITS file try the following:

>>> my_map ='/mydirectory/mymap.fits')   

sunpy should automatically detects the type of file (e.g. FITS), what instrument it is associated with (e.g. AIA, EIT, LASCO) and will automatically look in the appropriate places for the FITS keywords it needs to interpret the coordinate system. If the type of FITS file is not recognized then sunpy will try some default FITS keywords and return a GenericMap but results may vary. sunpy can also create maps from the jpg2000 files from

Creating Custom Maps

It is also possible to create maps using custom data (e.g. from a simulation or an observation from a data source that is not explicitly supported in sunpy.) To do this you need to provide with both the data array as well as appropriate meta information. The meta information is important as it informs the of the correct coordinate information associated with the data array. The meta information should be provided to in the form of a header as a dict or MetaDict.

The keys that are required for the header information follows the FITS standard. sunpy now provides a map header helper function to assist the user in creating a header that contains the correct meta information to generate a

The helper functionality includes a meta_keywords function that will return a dict of all the current meta keywords and their descriptions currently used by to make a map:

>>> from import meta_keywords

>>> meta_keywords() 
{'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'

There is also functionality also includes a utility function make_fitswcs_header that will return a header with the appropiate FITS keywords once the map data array and an astropy.coordinates.SkyCoord or sunpy.coordinates.frames is passed. The astropy.coordinates.SkyCoord is defined by the user, and contains information on the reference frame, reference coordinate and observer location. The function returns a sunpy.util.MetaDict. The astropy.coordinates.SkyCoord or sunpy.coordinates.frames must contain an observation time.

The make_fitswcs_header function also takes optional keywords arguments including reference_pixel and scale which describe the pixel coordinate at the reference coordinate (defined by the SkyCoord) and the spatial scale of the pixels, respectively. If neither of these are given their values default to the center of the data array and 1 arcsec, respectively.

Here’s an example of creating a header from some generic data and an astropy.coordinates.SkyCoord:

>>> 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 =, 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 keywords were not passed in the example above, the values of crpix and cdelt were set to the default values.

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:

>>> header =, coord,
...                                        reference_pixel=u.Quantity([5, 5]*u.pixel),
...                                        scale=u.Quantity([2, 2] *u.arcsec/u.pixel))
>>> 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 waveunit of the observation. Any of the keywords listed in header_helper.meta_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 and will be translated to the FITS standard: observtory, instrument,``telescope``, wavelength, exposure.

An example of creating a header with these additional keywords:

>>> header =, coord,
...                                        reference_pixel = u.Quantity([5, 5]*u.pixel),
...                                        scale = u.Quantity([2, 2] *u.arcsec/u.pixel),
...                                        telescope = 'Test case', instrument = 'UV detector',
...                                        wavelength = 1000*u.angstrom)
>>> header  
MetaDict([('wcsaxes', 2),
      ('crpix1', 5.0),
      ('crpix2', 5.0),
      ('cdelt1', <Quantity 2. arcsec2 / pix2>),
      ('cdelt2', <Quantity 2. arcsec2 / pix2>),
      ('cunit1', Unit("arcsec")),
      ('cunit2', Unit("arcsec")),
      ('ctype1', 'HPLN-TAN'),
      ('ctype2', 'HPLT-TAN'),
      ('crval1', 0.0),
      ('crval2', 0.0),
      ('date-obs', '2013-10-28T00:00:00.000'),
      ('hgln_obs', 0.0),
      ('hglt_obs', 4.7711570596394015),
      ('dsun_obs', 148644585949.4918),
      ('rsun_ref', 695700.0),
      ('rsun_obs', 965.3829548285768),
      ('instrume', 'Test case'),
      ('wavelnth', 1000),
      ('detector', 'UV detector'),
      ('waveunit', 'angstrom')])

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

>>> my_map =, header) 
>>> my_map.peek() 

Inspecting maps

A map contains a number of data-associated attributes. To get a quick look at your map simply type:

>>> my_map =  
>>> my_map  
< 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 will show a representation of the data as well as some of its associated attributes. A number of other attributes are also available, for example the date, exposure_time, center and others (see GenericMap):

>>> map_date =  
>>> map_exptime = my_map.exposure_time  
>>> map_center =  

To get a list of all of the attributes check the documentation by typing:

>>> help(my_map)  

Many attributes and functions of the map classes accept and return Quantity or SkyCoord objects, please refer to Units and Coordinates in sunpy for more details.

The meta data for the map is accessed by

>>> header = my_map.meta  

This references the meta data dictionary with the header information as read from the source file.

Getting at the data

The data in a sunpy Map object is accessible through the data attribute. The data is implemented as a NumPy ndarray, so for example, to get the 0th element in the array

>>>[0, 0]  

One important fact to remember is that the first index is for the y direction while the second index is for the x direction. For more information about indexing please refer to the Numpy documentation.

Data attributes like dtype and dimensions are accessible through the SunPyGenericMap object

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

Here the dimensions attribute is similar to the shape attribute, however returning an Quantity.

If you’d like to use the data in a sunpy GenericMap object elsewhere, you can use either of the following:

>>> var =  
>>> var =  

Python makes use of pointers so if you want to alter the data and keep the original data in the map intact make sure to copy it.

To create a complete copy of a Map object that is entirely independent of the original, use the built-in copy.deepcopy method, like so:

>>> import copy   
>>> my_map_deepcopy = copy.deepcopy(my_map)   

A deepcopy ensures that any changes in the original Map object are not reflected in the copied object and vice versa. Note that this is different from simply copying the data of the Map object - this copies all of the other attributes and methods as well.

Some basic statistical functions on the data array are also passed through to Map objects:

>>> my_map.min()  
>>> my_map.max()  
>>> my_map.mean()  

but you can also access all the other ndarray functions and attributes by accessing the data array directly. For example:



As is true of all of the sunpy data objects, the sunpy GenericMap object (and all of its instrument-specific sub-classes) has its own built-in plot methods so that it is easy to quickly view your map. To create a plot just type:

>>> my_map.peek()   

This will open a matplotlib plot on your screen. In addition, to enable users to modify the plot it is possible to grab the matplotlib axes object by using the plot() command. This makes it possible to use the sunpy plot as the foundation for a more complicated figure. For a bit more information about this and some examples see Plotting in sunpy.


If the astropy.visualization.wcsaxes package is not used (it is used by default) the plot() and peek() methods assume that the data is not rotated, i.e. the solar y axis is oriented with the columns of the array. If this condition is not met (in the metadata), when the map is plotted a warning will be issued. You can create an oriented map by using rotate() before you plot the Map.

Plotting Keywords

For Map imshow does most of the heavy lifting in the background while sunpy makes a number of choices for you so that you don’t have to (e.g. colortable, plot title). Changing these defaults is made possible through two simple interfaces. You can pass any imshow keyword into the plot command to override the defaults for that particular plot. The following plot changes the default AIA color table to use an inverse Grey color table.

import matplotlib.pyplot as plt
smap =
fig = plt.figure()

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


You can view or make changes to the default settings through the dictionary. In the following example we change the title of the plot by changing the property.

import matplotlib.pyplot as plt
smap =
smap.plot_settings['title'] = "My Second sunpy Plot"
smap.plot_settings['cmap'] =
fig = plt.figure()

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


Colormaps and Normalization

Image data is generally shown in false color in order to better identify it or to better visualize structures in the image. Matplotlib handles this colormapping process through the colors module. This process involves two steps: the data array is first mapped onto the range 0-1 using an instance of Normalize or a subclass; then this number is mapped to a color using an instance of a subclass of a Colormap.

sunpy provides the colormaps for each mission as defined by the mission teams. The Map object chooses the appropriate colormap for you when it is created as long as it recognizes the instrument. To see what colormaps are available:

>>> import sunpy.visualization.colormaps as cm
>>> cm.cmlist.keys()
dict_keys(['goes-rsuvi94', 'goes-rsuvi131', 'goes-rsuvi171', 'goes-rsuvi195',
'goes-rsuvi284', 'goes-rsuvi304', 'sdoaia94', 'sdoaia131', 'sdoaia171',

The sunpy colormaps are registered with matplotlib so you can grab them like you would any other colormap:

>>> import matplotlib.pyplot as plt
>>> import sunpy.visualization.colormaps

You need to import sunpy.visualization.colormaps or for this to work:

>>> cmap = plt.get_cmap('sdoaia171')

The following plot shows off all of the colormaps.

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


These can be used with the standard commands to change the colormap. So for example if you wanted to plot an AIA image but use an EIT colormap, you would do so as follows.

import matplotlib.pyplot as plt

smap =
cmap = plt.get_cmap('sohoeit171')

fig = plt.figure()

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


or you can just change the colormap for the map itself as follows:

>>> smap.plot_settings['cmap'] = plt.get_cmap('sohoeit171')  

The normalization is also set automatically and is chosen so that all the data from minimum to maximum is displayed as best as possible for most cases. This means that it is never necessary to touch the data such as applying a function such sqrt or log to the data to make your plot look good. There are many normalizations available from matplotlib such as LogNorm. Other more exotic normalizations are also made available from Astropy. Just like the colormap the default normalization can be changed through the plot_settings dictionary or directly for the individual plot by passing a keyword argument. The following example shows the difference between a linear and logarithmic normalization on an AIA image.

import matplotlib.pyplot as plt
import matplotlib.colors as colors

smap =

fig = plt.figure(figsize=(4, 9))

ax1 = fig.add_subplot(2, 1, 1, projection=smap)
smap.plot(norm=colors.Normalize(), title='Linear normalization')

ax2 = fig.add_subplot(2, 1, 2, projection=smap)
smap.plot(norm=colors.LogNorm(), title='Logarithmic normalization')

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


Note how the color in the colorbar does not change since these two maps share the same colormap while the data values associated with each color do because the normalization is different.

Masking and Clipping Data

It is often necessary for the purposes of display or otherwise to ignore certain data in an image. For example, a large data value could be due to cosmic ray hits and should be ignored. The most straightforward way to ignore this kind of data in plots without altering the data is to clip it. This can be achieved very easily by using the clip_interval keyword. For example:

>>> import astropy.units as u
>>> smap.plot(clip_interval=(1, 99.5)*u.percent)  

This clips out the dimmest 1% of pixels and the brightest 0.5% of pixels. With those outlier pixels clipped, the resulting image makes better use of the full range of colors. If you’d like to see what areas of your images got clipped, you can modify the colormap:

>>> cmap = map.cmap  
>>> cmap.set_over('blue')  
>>> cmap.set_under('green')  

This will color the areas above and below in red and green respectively (similar to this example). You can use the following colorbar command to display these choices:

>>> plt.colorbar(extend='both')   

Here is an example of this put to use on an AIA image.

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


smap =
cmap = smap.cmap.copy()

fig = plt.figure(figsize=(12, 4))

ax1 = fig.add_subplot(1, 2, 1, projection=smap)
smap.plot(title='Without clipping')

ax2 = fig.add_subplot(1, 2, 2, projection=smap)
smap.plot(clip_interval=(1, 99.5)*u.percent, title='With clipping')

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


Another approach to clipping data is to specify explicit values for the minimum and maximum pixel values using the plotting keywords vmin and vmax.

Clipping excludes data that has extreme values, but there can be other forms of bad data. A mask is a boolean array and so can give you much more fine-grained control over what is not being displayed. A MaskedArray is a subclass of a numpy array so it has all of the same properties with the addition of an associated boolean array which holds the mask. See this example in our gallery.

Composite Maps and Overlaying Maps

The Map method described above can also handle a list of maps. If a series of maps are supplied as inputs, Map will return a list of maps as the output. However, if the ‘composite’ keyword is set to True, then a CompositeMap object is returned. This is useful if the maps are of a different type (e.g. different instruments). For example, to create a simple composite map:

>>> my_maps =,, composite=True)  

A CompositeMap is different from a regular sunpy GenericMap object and therefore different associated methods. To list which maps are part of your composite map use:

>>> my_maps.list_maps()  
[<class ''>, <class ''>]

The following code adds a new map (which must be instantiated first), sets its transparency to 25%, turns on contours from 50% to 90% for the second map, and then plots the result.

import matplotlib.pyplot as plt
my_maps =,, composite=True)
my_maps.set_alpha(2, 0.5)
my_maps.set_levels(1, [50, 60, 70, 80, 90], percent = True)

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


This is not a particularly pretty plot but it shows what sunpy can do!

Working with your map

Part of the philosophy of the map object is to provide most of the basic functionality that a scientist would want therefore a map also contains a number of map-specific methods such as resizing a map or grabbing a subview. To get a list of the methods available for a map type:

>>> help(my_map)  

and check out the methods section!


A MapSequence is an ordered list of maps. By default, the maps are ordered by their observation date, from earlier maps to later maps. A MapSequence can be created by supplying multiple existing maps:

>>> map1 =  
>>> map2 =  
>>> mc =[map1, map2], sequence=True)  

or by providing a directory full of image files:

>>> mc ='path/to/my/files/*.fits', sequence=True)   

The earliest map in the MapSequence can be accessed by simply indexing the maps list:

>>> mc.maps[0]   

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

>>> mc.all_maps_same_shape()  

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

>>> mc.as_array()   

Note that an array is returned only if all the maps have the same shape. If this is not true, an error (ValueError) is returned. If all the maps have nx pixels in the x-direction, and ny pixels in the y-direction, and there are n maps in the MapSequence, the ndarray array that is returned has shape (ny, nx, n). The data of the first map in the MapSequence appears in the ndarray in position [:, :, 0], the data of second map in position [:, :, 1], and so on. The order of maps in the MapSequence is reproduced in the returned ndarray.

The meta data from each map can be obtained using:

>>> mc.all_meta()   

This returns a list of map meta objects that have the same order as the maps in the MapSequence.

Coalignment of MapSequences

A typical data preparation step when dealing with time series of images is to coalign images taken at different times so that features in different images remain in the same place. A common approach to this problem is to take a representative template that contains the features you are interested in, and match that to your images. The location of the best match tells you where the template is in your image. The images are then shifted to the location of the best match. This aligns your images to the position of the features in your representative template.

sunpy provides a function to coalign the maps inside the MapSequence. The implementation of this functionality requires the installation of the scikit-image library, a commonly used image processing library. To coalign a MapSequence, simply import the function and apply it to your MapSequence:

>>> from sunpy.image.coalignment import mapsequence_coalign_by_match_template
>>> coaligned = mapsequence_coalign_by_match_template(mc)  

This will return a new MapSequence, coaligned to a template extracted from the center of the first map in the MapSequence, with the map dimensions clipped as required. The coalignment algorithm provides many more options for handling the coalignment of MapSequence type:

>>> help(mapsequence_coalign_by_match_template)   

for a full list of options and functionality.

If you just want to calculate the shifts required to compensate for solar rotation relative to the first map in the MapSequence without applying them, use:

>>> from sunpy.image.coalignment import calculate_match_template_shift
>>> shifts = calculate_match_template_shift(mc)  

This is the function used to calculate the shifts in MapSequence coalignment function above. Please see calculate_match_template_shift to learn more about its features. Shifts calculated using calculate_match_template_shift can be passed directly to the coalignment function.

Compensating for solar rotation in MapSequences

Often a set of solar image data consists of fixing the pointing of a field of view for some time and observing. Features on the Sun will rotate according to the Sun’s rotation.

A typical data preparation step when dealing with time series of these types of images is to shift the images so that features do not appear to move across the field of view. This requires taking in to account the rotation of the Sun. The Sun rotates differentially, depending on latitude, with features at the equator moving faster than features at the poles.

sunpy provides a function to shift images in MapSequence following solar rotation. This function shifts an image according to the solar differential rotation calculated at the latitude of the center of the field of view. The image is not differentially rotated. This function is useful for de-rotating images when the effects of differential rotation in the MapSequence can be ignored (for example, if the spatial extent of the image is small, or when the duration of the MapSequence is small; deciding on what ‘small’ means depends on your application).

To apply this form of solar derotation to a MapSequence, simply import the function and apply it to your MapSequence:

>>> from sunpy.physics.solar_rotation import mapsequence_solar_derotate
>>> derotated = mapsequence_solar_derotate(mc)  

For more info see mapsequence_solar_derotate.

If you just want to calculate the shifts required to compensate for solar rotation relative to the first map in the MapSequence without applying them, use:

>>> from sunpy.physics.solar_rotation import calculate_solar_rotate_shift
>>> shifts = calculate_solar_rotate_shift(mc)  

Please consult the docstring of the mapsequence_coalign_by_match_template function in order to learn about the features of this function.