def setup_class(cls):
        # We set up some representations with, on purpose, copy=False,
        # so we can check that broadcasting is handled correctly.
        lon = Longitude(np.arange(0, 24, 4), u.hourangle)
        lat = Latitude(np.arange(-90, 91, 30), u.deg)

        # With same-sized arrays
        cls.s0 = SphericalRepresentation(
            lon[:, np.newaxis] * np.ones(lat.shape),
            lat * np.ones(lon.shape)[:, np.newaxis],
            np.ones(lon.shape + lat.shape) * u.kpc,
            copy=False)

        cls.diff = SphericalDifferential(
            d_lon=np.ones(cls.s0.shape) * u.mas / u.yr,
            d_lat=np.ones(cls.s0.shape) * u.mas / u.yr,
            d_distance=np.ones(cls.s0.shape) * u.km / u.s,
            copy=False)
        cls.s0 = cls.s0.with_differentials(cls.diff)

        # With unequal arrays -> these will be broadcasted.
        cls.s1 = SphericalRepresentation(lon[:, np.newaxis],
                                         lat,
                                         1. * u.kpc,
                                         differentials=cls.diff,
                                         copy=False)

        # For completeness on some tests, also a cartesian one
        cls.c0 = cls.s0.to_cartesian()
示例#2
0
def test_cirs_to_hadec():
    """
    Check the basic CIRS<->HADec transforms.
    """
    from astropy.coordinates import EarthLocation

    usph = golden_spiral_grid(200)
    dist = np.linspace(0.5, 1, len(usph)) * u.pc
    cirs = CIRS(usph, obstime='J2000')
    crepr = SphericalRepresentation(lon=usph.lon, lat=usph.lat, distance=dist)
    cirscart = CIRS(crepr,
                    obstime=cirs.obstime,
                    representation_type=CartesianRepresentation)

    loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m)
    hadecframe = HADec(location=loc, obstime=Time('J2005'))

    cirs2 = cirs.transform_to(hadecframe).transform_to(cirs)
    cirs3 = cirscart.transform_to(hadecframe).transform_to(cirs)

    # check round-tripping
    assert_allclose(cirs.ra, cirs2.ra)
    assert_allclose(cirs.dec, cirs2.dec)
    assert_allclose(cirs.ra, cirs3.ra)
    assert_allclose(cirs.dec, cirs3.dec)
示例#3
0
    def __new__(cls, *args, **kwargs):
        origin_frame = kwargs.pop('north', None)
        if origin_frame is None:
            raise TypeError(
                "Can't initialize an NorthOffsetFrame without a `north` keyword."
            )
        if hasattr(origin_frame, 'frame'):
            origin_frame = origin_frame.frame

        rep = origin_frame.spherical

        lon = rep.lon
        lat = rep.lat
        if lat > 0 * u.deg:
            lat = lat - 90 * u.deg
            rotation = None
        else:
            lon = lon - 180 * u.deg
            lat = -90 * u.deg - lat
            rotation = 180 * u.deg

        new_rep = SphericalRepresentation(lon=lon,
                                          lat=lat,
                                          distance=rep.distance)
        new_origin = origin_frame.realize_frame(new_rep)

        kwargs['origin'] = new_origin
        kwargs['rotation'] = rotation

        return SkyOffsetFrame(*args, **kwargs)
def test_cirs_to_altaz():
    """
    Check the basic CIRS<->AltAz transforms.  More thorough checks implicitly
    happen in `test_iau_fullstack`
    """
    from astropy.coordinates import EarthLocation

    ra, dec, dist = randomly_sample_sphere(200)
    cirs = CIRS(ra=ra, dec=dec, obstime='J2000')
    crepr = SphericalRepresentation(lon=ra, lat=dec, distance=dist)
    cirscart = CIRS(crepr,
                    obstime=cirs.obstime,
                    representation_type=CartesianRepresentation)

    loc = EarthLocation(lat=0 * u.deg, lon=0 * u.deg, height=0 * u.m)
    altazframe = AltAz(location=loc, obstime=Time('J2005'))

    cirs2 = cirs.transform_to(altazframe).transform_to(cirs)
    cirs3 = cirscart.transform_to(altazframe).transform_to(cirs)

    # check round-tripping
    assert_allclose(cirs.ra, cirs2.ra)
    assert_allclose(cirs.dec, cirs2.dec)
    assert_allclose(cirs.ra, cirs3.ra)
    assert_allclose(cirs.dec, cirs3.dec)
示例#5
0
def test_regression_6236():
    # sunpy changes its representation upon initialisation of a frame,
    # including via `realize_frame`. Ensure this works.
    class MyFrame(BaseCoordinateFrame):
        default_representation = CartesianRepresentation
        my_attr = QuantityAttribute(default=0, unit=u.m)

    class MySpecialFrame(MyFrame):
        def __init__(self, *args, **kwargs):
            _rep_kwarg = kwargs.get('representation_type', None)
            super().__init__(*args, **kwargs)
            if not _rep_kwarg:
                self.representation_type = self.default_representation
                self._data = self.data.represent_as(self.representation_type)

    rep1 = UnitSphericalRepresentation([0., 1] * u.deg, [2., 3.] * u.deg)
    rep2 = SphericalRepresentation([10., 11] * u.deg, [12., 13.] * u.deg,
                                   [14., 15.] * u.kpc)
    mf1 = MyFrame(rep1, my_attr=1. * u.km)
    mf2 = mf1.realize_frame(rep2)
    # Normally, data is stored as is, but the representation gets set to a
    # default, even if a different representation instance was passed in.
    # realize_frame should do the same. Just in case, check attrs are passed.
    assert mf1.data is rep1
    assert mf2.data is rep2
    assert mf1.representation_type is CartesianRepresentation
    assert mf2.representation_type is CartesianRepresentation
    assert mf2.my_attr == mf1.my_attr
    # It should be independent of whether I set the reprensentation explicitly
    mf3 = MyFrame(rep1, my_attr=1. * u.km, representation_type='unitspherical')
    mf4 = mf3.realize_frame(rep2)
    assert mf3.data is rep1
    assert mf4.data is rep2
    assert mf3.representation_type is UnitSphericalRepresentation
    assert mf4.representation_type is CartesianRepresentation
    assert mf4.my_attr == mf3.my_attr
    # This should be enough to help sunpy, but just to be sure, a test
    # even closer to what is done there, i.e., transform the representation.
    msf1 = MySpecialFrame(rep1, my_attr=1. * u.km)
    msf2 = msf1.realize_frame(rep2)
    assert msf1.data is not rep1  # Gets transformed to Cartesian.
    assert msf2.data is not rep2
    assert type(msf1.data) is CartesianRepresentation
    assert type(msf2.data) is CartesianRepresentation
    assert msf1.representation_type is CartesianRepresentation
    assert msf2.representation_type is CartesianRepresentation
    assert msf2.my_attr == msf1.my_attr
    # And finally a test where the input is not transformed.
    msf3 = MySpecialFrame(rep1,
                          my_attr=1. * u.km,
                          representation_type='unitspherical')
    msf4 = msf3.realize_frame(rep2)
    assert msf3.data is rep1
    assert msf4.data is not rep2
    assert msf3.representation_type is UnitSphericalRepresentation
    assert msf4.representation_type is CartesianRepresentation
    assert msf4.my_attr == msf3.my_attr
示例#6
0
    def obsgeo_to_frame(obsgeo, obstime):
        """
        Convert a WCS obsgeo property into an `~builtin_frames.ITRS` coordinate frame.

        Parameters
        ----------
        obsgeo : array-like
            A shape ``(6, )`` array representing ``OBSGEO-[XYZ], OBSGEO-[BLH]`` as
            returned by ``WCS.wcs.obsgeo``.

        obstime : time-like
            The time assiociated with the coordinate, will be passed to
            `~.builtin_frames.ITRS` as the obstime keyword.

        Returns
        -------
        `~.builtin_frames.ITRS`
            An `~.builtin_frames.ITRS` coordinate frame
            representing the coordinates.

        Notes
        -----

        The obsgeo array as accessed on a `.WCS` object is a length 6 numpy array
        where the first three elements are the coordinate in a cartesian
        representation and the second 3 are the coordinate in a spherical
        representation.

        This function priorities reading the cartesian coordinates, and will only
        read the spherical coordinates if the cartesian coordinates are either all
        zero or any of the cartesian coordinates are non-finite.

        In the case where both the spherical and cartesian coordinates have some
        non-finite values the spherical coordinates will be returned with the
        non-finite values included.

        """
        if (obsgeo is None or len(obsgeo) != 6
                or np.all(np.array(obsgeo) == 0)
                or np.all(~np.isfinite(obsgeo))):  # NOQA
            raise ValueError(
                f"Can not parse the 'obsgeo' location ({obsgeo}). "
                "obsgeo should be a length 6 non-zero, finite numpy array")

        # If the cartesian coords are zero or have NaNs in them use the spherical ones
        if np.all(obsgeo[:3] == 0) or np.any(~np.isfinite(obsgeo[:3])):
            data = SphericalRepresentation(*(obsgeo[3:] * (u.deg, u.deg, u.m)))

        # Otherwise we assume the cartesian ones are valid
        else:
            data = CartesianRepresentation(*obsgeo[:3] * u.m)

        return ITRS(data, obstime=obstime)
def test_atciqz_aticq(st):
    """Check replacements against erfa versions for consistency."""
    t, pos = st
    jd1, jd2 = get_jd12(t, 'tdb')
    astrom, _ = erfa.apci13(jd1, jd2)

    ra, dec = pos
    srepr = SphericalRepresentation(ra, dec, 1)
    ra = ra.value
    dec = dec.value
    assert_allclose(erfa.atciqz(ra, dec, astrom), atciqz(srepr, astrom))
    assert_allclose(erfa.aticq(ra, dec, astrom), aticq(srepr, astrom))
示例#8
0
def make_earth_vis_grids(nside=32, n_reps=1):
    from acis_taco import calc_earth_vis, RAD_EARTH, acis_taco
    hp = astropy_healpix.HEALPix(nside=nside, order='nested')
    npix = astropy_healpix.nside_to_npix(nside)
    print(f'npix={npix}')
    lons, lats = hp.healpix_to_lonlat(np.arange(npix))
    time0 = time.time()

    # Allow randomization between altitudes
    for i_rep in range(n_reps):
        vis_arrays = []
        # Randomize the ray-trace points for each rep
        acis_taco._RANDOM_SALT = None
        acis_taco.SPHERE_XYZ = acis_taco.random_hemisphere(acis_taco.N_SPHERE)
        acis_taco.SPHERE_X = acis_taco.SPHERE_XYZ[:, 0].copy()

        for i_alt, alt in enumerate(alts):

            srs = SphericalRepresentation(lon=lons,
                                          lat=lats,
                                          distance=alt + RAD_EARTH)
            xyzs = srs.to_cartesian()
            peb_xs = xyzs.x.to_value()
            peb_ys = xyzs.y.to_value()
            peb_zs = xyzs.z.to_value()
            vis = []
            for peb_x, peb_y, peb_z in zip(peb_xs, peb_ys, peb_zs):
                _, illums, _ = calc_earth_vis(
                    p_earth_body=[peb_x, peb_y, peb_z])
                vis.append(np.sum(illums))
            vis = np.array(vis)
            vis_arrays.append(vis)
            vis_grid = np.vstack(vis_arrays)
            if i_alt % 10 == 0:
                print(
                    f'alt={alt / 1000:.2f} km at dt={time.time() - time0:.1f}')

        ii = 1
        while True:
            filename = Path(f'earth_vis_grid_nside{nside}_rep{ii}.npy')
            if filename.exists():
                ii += 1
                continue
            else:
                print(f'Saving {filename}')
                np.save(filename, vis_grid)
                break

    return vis_grid
    def test_shape_setting(self):
        # Shape-setting should be on the object itself, since copying removes
        # zero-strides due to broadcasting.  We reset the objects at the end.
        self.s0.shape = (2, 3, 7)
        assert self.s0.shape == (2, 3, 7)
        assert self.s0.lon.shape == (2, 3, 7)
        assert self.s0.lat.shape == (2, 3, 7)
        assert self.s0.distance.shape == (2, 3, 7)
        assert self.diff.shape == (2, 3, 7)
        assert self.diff.d_lon.shape == (2, 3, 7)
        assert self.diff.d_lat.shape == (2, 3, 7)
        assert self.diff.d_distance.shape == (2, 3, 7)

        # this works with the broadcasting.
        self.s1.shape = (2, 3, 7)
        assert self.s1.shape == (2, 3, 7)
        assert self.s1.lon.shape == (2, 3, 7)
        assert self.s1.lat.shape == (2, 3, 7)
        assert self.s1.distance.shape == (2, 3, 7)
        assert self.s1.distance.strides == (0, 0, 0)

        # but this one does not.
        oldshape = self.s1.shape
        with pytest.raises(ValueError):
            self.s1.shape = (1, )
        with pytest.raises(AttributeError):
            self.s1.shape = (42, )
        assert self.s1.shape == oldshape
        assert self.s1.lon.shape == oldshape
        assert self.s1.lat.shape == oldshape
        assert self.s1.distance.shape == oldshape

        # Finally, a more complicated one that checks that things get reset
        # properly if it is not the first component that fails.
        s2 = SphericalRepresentation(self.s1.lon.copy(),
                                     self.s1.lat,
                                     self.s1.distance,
                                     copy=False)
        assert 0 not in s2.lon.strides
        assert 0 in s2.lat.strides
        with pytest.raises(AttributeError):
            s2.shape = (42, )
        assert s2.shape == oldshape
        assert s2.lon.shape == oldshape
        assert s2.lat.shape == oldshape
        assert s2.distance.shape == oldshape
        assert 0 not in s2.lon.strides
        assert 0 in s2.lat.strides
        self.setup()
    def setup(self):
        lon = Longitude(np.arange(0, 24, 4), u.hourangle)
        lat = Latitude(np.arange(-90, 91, 30), u.deg)

        # With same-sized arrays
        self.s0 = SphericalRepresentation(
            lon[:, np.newaxis] * np.ones(lat.shape),
            lat * np.ones(lon.shape)[:, np.newaxis],
            np.ones(lon.shape + lat.shape) * u.kpc)

        self.diff = SphericalDifferential(
            d_lon=np.ones(self.s0.shape) * u.mas / u.yr,
            d_lat=np.ones(self.s0.shape) * u.mas / u.yr,
            d_distance=np.ones(self.s0.shape) * u.km / u.s)
        self.s0 = self.s0.with_differentials(self.diff)

        # With unequal arrays -> these will be broadcasted.
        self.s1 = SphericalRepresentation(lon[:, np.newaxis],
                                          lat,
                                          1. * u.kpc,
                                          differentials=self.diff)

        # For completeness on some tests, also a cartesian one
        self.c0 = self.s0.to_cartesian()
示例#11
0
    def rotatedsun_to_reference(rotatedsun_coord, reference_frame):
        # Transform to HCI
        from_coord = rotatedsun_coord.base.realize_frame(rotatedsun_coord.data)
        hci_coord = from_coord.transform_to(HeliocentricInertial(obstime=reference_frame.obstime))
        oldrepr = hci_coord.spherical

        # Rotate the coordinate in HCI
        from sunpy.physics.differential_rotation import diff_rot
        log.debug(f"Applying {rotatedsun_coord.duration} of solar rotation")
        newlon = oldrepr.lon + diff_rot(rotatedsun_coord.duration,
                                        oldrepr.lat,
                                        rot_type=rotatedsun_coord.rotation_model,
                                        frame_time='sidereal')
        newrepr = SphericalRepresentation(newlon, oldrepr.lat, oldrepr.distance)

        # Transform back from HCI
        hci_coord = HeliocentricInertial(newrepr, obstime=reference_frame.obstime)
        return hci_coord.transform_to(reference_frame)
示例#12
0
文件: util.py 项目: TRASAL/darc
def hadec_to_radec(ha, dec, t):
    """
    Convert apparent HA, Dec to J2000 RA, Dec

    :param ha: hour angle with unit
    :param dec: declination with unit
    :param Time/str t: Observing time
    :return: SkyCoord object of J2000 coordinates
    """

    # Convert time to Time object if given as string
    if isinstance(t, str):
        t = Time(t)

    # create spherical representation of ITRS coordinates of given ha, dec
    itrs_spherical = SphericalRepresentation(WSRT_LOC.lon - ha, dec, 1.)
    # create ITRS object, which requires cartesian input
    coord = SkyCoord(itrs_spherical.to_cartesian(), frame='itrs', obstime=t)
    # convert to J2000
    return coord.icrs.ra, coord.icrs.dec
示例#13
0
def hadec_to_radec(ha, dec, t, lon=WSRT_LOC.lat):
    """
    Convert apparent HA, Dec to J2000 RA, Dec

    :param ha: hour angle with unit
    :param dec: declination with unit
    :param t: UT time (string or astropy.time.Time)
    :param lon: Longitude with unit (default: WSRT)
    :return: SkyCoord object of J2000 coordinates
    """

    # Convert time to Time object if given as string
    if isinstance(t, str):
        t = Time(t)

    # create spherical representation of ITRS coordinates of given ha, dec
    itrs_spherical = SphericalRepresentation(lon - ha, dec, 1.)
    # create ITRS object, which requires cartesian input
    coord = SkyCoord(itrs_spherical.to_cartesian(), frame='itrs', obstime=t)
    # convert to J2000
    return coord.icrs
示例#14
0
    def reference_to_rotatedsun(hgs_coord, rotatedsun_frame):
        int_frame = HeliographicStonyhurst(
            obstime=rotatedsun_frame.base.obstime)
        int_coord = hgs_coord.make_3d().transform_to(
            int_frame)  # obstime change handled here
        oldrepr = int_coord.spherical

        # Rotate the coordinate in HGS
        from sunpy.physics.differential_rotation import diff_rot
        log.debug(f"Applying {rotatedsun_frame.duration} of solar rotation")
        newlon = oldrepr.lon - diff_rot(
            rotatedsun_frame.duration,
            oldrepr.lat,
            rot_type=rotatedsun_frame.rotation_model,
            frame_time='sidereal')
        newrepr = SphericalRepresentation(newlon, oldrepr.lat,
                                          oldrepr.distance)

        # Transform from HGS
        new_coord = int_coord.realize_frame(newrepr).transform_to(
            rotatedsun_frame.base)
        return rotatedsun_frame.realize_frame(new_coord.data)
示例#15
0
                                                  0 * u.arcsec)], None),
                    ([UnitSphericalRepresentation(0 * u.deg,
                                                  0 * u.arcsec)], None),
                    ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], {
                        'obstime': '2011/01/01T00:00:00'
                    })]
"""
These are common 3D params, kwargs are frame specific
"""
three_D_parameters = [
    ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], None),
    ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], {
        'obstime': '2011/01/01T00:00:00'
    }), ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], {
        'representation': 'spherical'
    }), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], None),
    ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], None),
    ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], {
        'obstime': '2011/01/01T00:00:00'
    })
]

# ==============================================================================
# Helioprojective Tests
# ==============================================================================


@pytest.mark.parametrize('args, kwargs',
                         two_D_parameters + [(None, {
                             'Tx': 0 * u.deg,
                             'Ty': 0 * u.arcsec
示例#16
0
@pytest.mark.parametrize('frame', ['fk4', 'altaz'])
def test_skycoord(frame):

    c = SkyCoord([[1, 2], [3, 4]], [[5, 6], [7, 8]],
                 unit='deg', frame=frame,
                 obstime=Time('2016-01-02'),
                 location=EarthLocation(1000, 2000, 3000, unit=u.km))
    cy = load(dump(c))
    compare_coord(c, cy)


@pytest.mark.parametrize('rep', [
    CartesianRepresentation(1*u.m, 2.*u.m, 3.*u.m),
    SphericalRepresentation([[1, 2], [3, 4]]*u.deg,
                            [[5, 6], [7, 8]]*u.deg,
                            10*u.pc),
    UnitSphericalRepresentation(0*u.deg, 10*u.deg),
    SphericalCosLatDifferential([[1.], [2.]]*u.mas/u.yr,
                                [4., 5.]*u.mas/u.yr,
                                [[[10]], [[20]]]*u.km/u.s),
    CartesianDifferential([10, 20, 30]*u.km/u.s),
    CartesianRepresentation(
        [1, 2, 3]*u.m,
        differentials=CartesianDifferential([10, 20, 30]*u.km/u.s)),
    SphericalRepresentation(
        [[1, 2], [3, 4]]*u.deg, [[5, 6], [7, 8]]*u.deg, 10*u.pc,
        differentials={
            's': SphericalDifferential([[0., 1.], [2., 3.]]*u.mas/u.yr,
                                       [[4., 5.], [6., 7.]]*u.mas/u.yr,
                                       10*u.km/u.s)})])
示例#17
0
        # fix this.
        if attr == 'info.meta':
            if a1 is None:
                a1 = {}
            if a2 is None:
                a2 = {}

        assert np.all(a1 == a2)


# Testing FITS table read/write with mixins.  This is mostly
# copied from ECSV mixin testing.

el = EarthLocation(x=1 * u.km, y=3 * u.km, z=5 * u.km)
el2 = EarthLocation(x=[1, 2] * u.km, y=[3, 4] * u.km, z=[5, 6] * u.km)
sr = SphericalRepresentation([0, 1] * u.deg, [2, 3] * u.deg, 1 * u.kpc)
cr = CartesianRepresentation([0, 1] * u.pc, [4, 5] * u.pc, [8, 6] * u.pc)
sd = SphericalCosLatDifferential([0, 1] * u.mas / u.yr, [0, 1] * u.mas / u.yr,
                                 10 * u.km / u.s)
srd = SphericalRepresentation(sr, differentials=sd)
sc = SkyCoord([1, 2], [3, 4], unit='deg,deg', frame='fk4', obstime='J1990.5')
scc = sc.copy()
scc.representation_type = 'cartesian'
tm = Time([2450814.5, 2450815.5], format='jd', scale='tai', location=el)

# NOTE: in the test below the name of the column "x" for the Quantity is
# important since it tests the fix for #10215 (namespace clash, where "x"
# clashes with "el2.x").
mixin_cols = {
    'tm':
    tm,
示例#18
0
 def loc(self):
     sr = SphericalRepresentation(self.theta + self.phase0,
                                  self.inclination*np.cos(self.theta),
                                  self.r)
     return sr.to_cartesian() + self.parent_body.loc
示例#19
0
# Licensed under a 3-clause BSD style license - see LICENSE.rst

"""Test replacements for ERFA functions atciqz and aticq."""

import pytest
import erfa

from astropy.tests.helper import assert_quantity_allclose as assert_allclose
from astropy.time import Time
import astropy.units as u
from astropy.coordinates.builtin_frames.utils import get_jd12, atciqz, aticq
from astropy.coordinates import SphericalRepresentation

# Hard-coded random values
sph = SphericalRepresentation(lon=[15., 214.] * u.deg,
                              lat=[-12., 64.] * u.deg,
                              distance=[1, 1.])


@pytest.mark.parametrize('t', [Time("2014-06-25T00:00"),
                               Time(["2014-06-25T00:00", "2014-09-24"])])
@pytest.mark.parametrize('pos', [sph[0], sph])
def test_atciqz_aticq(t, pos):
    """Check replacements against erfa versions for consistency."""
    jd1, jd2 = get_jd12(t, 'tdb')
    astrom, _ = erfa.apci13(jd1, jd2)

    ra = pos.lon.to_value(u.rad)
    dec = pos.lat.to_value(u.rad)
    assert_allclose(erfa.atciqz(ra, dec, astrom), atciqz(pos, astrom))
    assert_allclose(erfa.aticq(ra, dec, astrom), aticq(pos, astrom))
示例#20
0
 def plot_star(self, fade_out=True):
     spots_spherical = SphericalRepresentation(
         self.phi * u.rad, (self.theta - np.pi / 2) * u.rad, 1 * R_sun)
     self.spots_spherical = spots_spherical
     fig, ax = plot_star(spots_spherical, fade_out=fade_out)
示例#21
0
def plot_star(spots_spherical, fade_out=False):
    """
    Parameters
    ----------
    spots_spherical : `~astropy.coordinates.SphericalRepresentation`
        Points in spherical coordinates that represent the positions of the
        star spots.
    """
    oldrcparams = matplotlib.rcParams
    matplotlib.rcParams['font.size'] = 18
    fig, ax = plt.subplots(2, 3, figsize=(16, 16))

    positive_x = ax[0, 0]
    negative_x = ax[1, 0]

    positive_y = ax[0, 1]
    negative_y = ax[1, 1]

    positive_z = ax[0, 2]
    negative_z = ax[1, 2]

    axes = [
        positive_z, positive_x, negative_z, negative_x, positive_y, negative_y
    ]
    axes_labels = ['+z', '+x', '-z', '-x', '+y', '-y']

    # Set black background
    plot_props = dict(xlim=(-1, 1), ylim=(-1, 1), xticks=(), yticks=())
    drange = np.linspace(-1, 1, 100)
    y = np.sqrt(1 - drange**2)
    bg_color = 'k'
    for axis in axes:
        axis.set(xticks=(), yticks=())
        axis.fill_between(drange, y, 1, color=bg_color)
        axis.fill_between(drange, -1, -y, color=bg_color)
        axis.set(**plot_props)
        axis.set_aspect('equal')

    # Set labels
    positive_x.set(xlabel='$\hat{z}$', ylabel='$\hat{y}$')  # title='+x',
    positive_x.xaxis.set_label_position("top")
    positive_x.yaxis.set_label_position("right")

    negative_x.set(xlabel='$\hat{z}$', ylabel='$\hat{y}$')  # title='-x',
    negative_x.xaxis.set_label_position("top")

    positive_y.set(xlabel='$\hat{z}$', ylabel='$\hat{x}$')
    positive_y.xaxis.set_label_position("top")

    negative_y.set(xlabel='$\hat{z}$', ylabel='$\hat{x}$')
    negative_y.xaxis.set_label_position("top")
    negative_y.yaxis.set_label_position("right")

    negative_z.set(xlabel='$\hat{y}$', ylabel='$\hat{x}$')  # title='-z',
    negative_z.yaxis.set_label_position("right")
    positive_z.set(xlabel='$\hat{y}$', ylabel='$\hat{x}$')  # title='+z',
    positive_z.yaxis.set_label_position("right")
    positive_z.xaxis.set_label_position("top")

    for axis, label in zip(axes, axes_labels):
        axis.annotate(label, (-0.9, 0.9),
                      color='w',
                      fontsize=14,
                      ha='center',
                      va='center')

    # Plot gridlines
    n_gridlines = 9
    print("theta grid spacing: {0} deg".format(180. / (n_gridlines - 1)))
    n_points = 35
    pi = np.pi

    thetaitude_lines = SphericalRepresentation(
        np.linspace(0, 2 * pi, n_points)[:, np.newaxis] * u.rad,
        np.linspace(-pi / 2, pi / 2, n_gridlines).T * u.rad,
        np.ones((n_points, 1))).to_cartesian()

    phigitude_lines = SphericalRepresentation(
        np.linspace(0, 2 * pi, n_gridlines)[:, np.newaxis] * u.rad,
        np.linspace(-pi / 2, pi / 2, n_points).T * u.rad,
        np.ones((n_gridlines, 1))).to_cartesian()

    for i in range(thetaitude_lines.shape[1]):
        for axis in [positive_z, negative_z]:
            axis.plot(thetaitude_lines.x[:, i],
                      thetaitude_lines.y[:, i],
                      ls=':',
                      color='silver')
        for axis in [positive_x, negative_x, positive_y, negative_y]:
            axis.plot(thetaitude_lines.y[:, i],
                      thetaitude_lines.z[:, i],
                      ls=':',
                      color='silver')

    for i in range(phigitude_lines.shape[0]):
        for axis in [positive_z, negative_z]:
            axis.plot(phigitude_lines.y[i, :],
                      phigitude_lines.x[i, :],
                      ls=':',
                      color='silver')
        for axis in [positive_x, negative_x, positive_y, negative_y]:
            axis.plot(phigitude_lines.y[i, :],
                      phigitude_lines.z[i, :],
                      ls=':',
                      color='silver')

    # Plot spots
    spots_cart = spots_spherical.to_cartesian()
    spots_x = spots_cart.x / R_sun
    spots_y = spots_cart.y / R_sun
    spots_z = spots_cart.z / R_sun

    if fade_out:
        n = float(len(spots_x))
        alpha_range = np.arange(n)

        alpha = (n - alpha_range) / n
    else:
        alpha = 0.5

    for spot_ind in range(spots_x.shape[1]):

        above_x_plane = spots_x[:, spot_ind] > 0
        above_y_plane = spots_y[:, spot_ind] > 0
        above_z_plane = spots_z[:, spot_ind] > 0
        below_x_plane = spots_x[:, spot_ind] < 0
        below_y_plane = spots_y[:, spot_ind] < 0
        below_z_plane = spots_z[:, spot_ind] < 0

        positive_x.plot(spots_y[above_x_plane, spot_ind],
                        spots_z[above_x_plane, spot_ind],
                        '.',
                        alpha=alpha)

        negative_x.plot(-spots_y[below_x_plane, spot_ind],
                        spots_z[below_x_plane, spot_ind],
                        '.',
                        alpha=alpha)

        positive_y.plot(-spots_x[above_y_plane, spot_ind],
                        spots_z[above_y_plane, spot_ind],
                        '.',
                        alpha=alpha)

        negative_y.plot(spots_x[below_y_plane, spot_ind],
                        spots_z[below_y_plane, spot_ind],
                        '.',
                        alpha=alpha)

        positive_z.plot(spots_x[above_z_plane, spot_ind],
                        spots_y[above_z_plane, spot_ind],
                        '.',
                        alpha=alpha)

        negative_z.plot(spots_x[below_z_plane, spot_ind],
                        -spots_y[below_z_plane, spot_ind],
                        '.',
                        alpha=alpha)
    matplotlib.rcParams = oldrcparams
    return fig, ax