import os.path
import matplotlib.pyplot as plt
from astropy.io import fits
import drms
%matplotlib inline
In this example, we select a record from the Space-weather Active Region Patch (SHARP) series, and download some of its data files. We use the flux-weighted center of SHARP 4315 to select the record that is closest to the central meridian. Additional metadata, like SDO's LoS velocity or WCS keywords are used when creating plots of the downloaded data.
series = 'hmi.sharp_720s'
sharpnum = 4315
segments = ['magnetogram', 'field', 'continuum', 'Dopplergram']
kwlist = ['T_REC', 'LON_FWT', 'OBS_VR', 'CROTA2',
'CRPIX1', 'CRPIX2', 'CDELT1', 'CDELT2', 'CRVAL1', 'CRVAL2']
Create DRMS client and query metadata.
Note that you need to register your email address at JSOC, in order to be able to submit export requests. This can be very easily done on the JSOC email registration webpage. You then need to replace the email='name@example.com'
argument below with your own registered email address.
c = drms.Client(email='name@example.com', verbose=True)
k = c.query('%s[%d]' % (series, sharpnum), key=kwlist, rec_index=True)
Find the record that is clostest to the central meridian, by using the minimum of the patch's absolute longitude:
rec_cm = k.LON_FWT.abs().argmin()
k_cm = k.loc[rec_cm]
t_cm = drms.to_datetime(k.T_REC[rec_cm])
print(rec_cm, '@', k.LON_FWT[rec_cm], 'deg')
print('Timestamp:', t_cm)
Before downloading the corresponding data files, we first check if the files were already downloaded. For this we create a list of file names and check if they already exist, before submitting the data export request.
t_cm_str = t_cm.strftime('%Y%m%d_%H%M%S_TAI')
fname_mask = '{series}.{sharpnum}.{tstr}.{segment}.fits'
fnames = {
s: fname_mask.format(
series=series, sharpnum=sharpnum, tstr=t_cm_str, segment=s)
for s in segments}
download_segments = []
for k, v in fnames.items():
if not os.path.exists(v):
download_segments.append(k)
We now submit the export request and download all files that were not downloaded in any previous run:
if download_segments:
exp_query = '%s{%s}' % (rec_cm, ','.join(download_segments))
r = c.export(exp_query)
dl = r.download('.')
In case the files were downloaded with additional keyword data in their headers, we need to take care of any non-standard FITS keywords, to avoid errors when reading these files using astropy.io.fits
. For this reason, we define a short function that takes care of these keywords and returns the data from the compressed data unit.
def read_fits_data(fname):
"""Reads FITS data and fixes/ignores any non-standard FITS keywords."""
hdulist = fits.open(fname)
hdulist.verify('silentfix+warn')
return hdulist[1].data
We can now finally read the data from the downloaded FITS files:
mag = read_fits_data(fnames['magnetogram'])
babs = read_fits_data(fnames['field'])
cont = read_fits_data(fnames['continuum'])
dopp = read_fits_data(fnames['Dopplergram'])
We start with subtracting SDO's LoS velocity from the Dopplergram:
dopp -= k_cm.OBS_VR
We now convert the coordinates of the patch from pixel coordinates to helioprojective-cartesian cordinates, by using the WCS keywords.
ny, nx = mag.shape
xmin = (1 - k_cm.CRPIX1)*k_cm.CDELT1 + k_cm.CRVAL1
xmax = (nx - k_cm.CRPIX1)*k_cm.CDELT1 + k_cm.CRVAL1
ymin = (1 - k_cm.CRPIX2)*k_cm.CDELT2 + k_cm.CRVAL2
ymax = (ny - k_cm.CRPIX2)*k_cm.CDELT2 + k_cm.CRVAL2
One issue, not handled by this transformation, is image rotation. For exact results, this would need us to either remap the data on a different grid, or use a rotated coordinate system for plotting the data. Luckily, the rotation angle of HMI images, i.e. CROTA2
, is usally very close to 180°. So we can well approximate the rotation by just flipping the image axes.
if abs(180 - k_cm.CROTA2) < 0.1:
mag = mag[::-1, ::-1]
babs = babs[::-1, ::-1]
cont = cont[::-1, ::-1]
dopp = dopp[::-1, ::-1]
xmin, xmax = -xmax, -xmin
ymin, ymax = -ymax, -ymin
else:
raise RuntimeError('CROTA2 = %.2f value not supported.' % k_cm.CROTA2)
We can now compute the extent of the image in arcsecs, which will be used when plotting the images.
extent = (xmin - abs(k_cm.CDELT1)/2, xmax + abs(k_cm.CDELT1)/2,
ymin - abs(k_cm.CDELT2)/2, ymax + abs(k_cm.CDELT2)/2)
We change some matplotlib options to make plot commands less verbose:
plt.rc('mathtext', default='regular')
plt.rc('image', origin='lower', interpolation='nearest', cmap='gray')
And finally create the plots and save the figure as PDF and PNG:
fig = plt.figure(figsize=(12, 6.75))
gs = plt.GridSpec(2, 5, width_ratios=[1, 0.1, 0.1, 1, 0.1])
ax = [fig.add_subplot(gsi, anchor='E')
for gsi in [gs[0, 0], gs[0, 3], gs[1, 0], gs[1, 3]]]
cax = [fig.add_subplot(gsi, anchor='W', aspect=15)
for gsi in [gs[0, 1], gs[0, 4], gs[1, 1], gs[1, 4]]]
ax_mag, cax_mag = ax[0], cax[0]
im_mag = ax_mag.imshow(mag/1e3, extent=extent, vmin=-1, vmax=1)
cb_mag = plt.colorbar(im_mag, cax_mag, label='$B_{\mathrm{los}}$ [kG]')
ax_cont, cax_cont = ax[1], cax[1]
im_cont = ax_cont.imshow(cont/1e3, extent=extent, vmax=61)
cb_cont = plt.colorbar(im_cont, cax_cont, label='$I_{\mathrm{c}}$ [kDN/s]')
ax_babs, cax_babs = ax[2], cax[2]
im_babs = ax_babs.imshow(babs/1e3, extent=extent, vmin=0.05, vmax=2.6)
cb_babs = plt.colorbar(im_babs, cax_babs, label='$|B|$ [kG]')
ax_dopp, cax_dopp = ax[3], cax[3]
im_dopp = ax_dopp.imshow(dopp/1e3, extent=extent, vmin=-1.3, vmax=1.3)
cb_dopp = plt.colorbar(im_dopp, cax_dopp, label='$v_{\mathrm{los}}$ [km/s]')
ax_mag.set_xticklabels([])
ax_cont.set_xticklabels([])
ax_cont.set_yticklabels([])
ax_dopp.set_yticklabels([])
ax_babs.set_xlabel('Solar X [arcsec]')
ax_dopp.set_xlabel('Solar X [arcsec]')
ax_mag.set_ylabel('Solar Y [arcsec]')
ax_babs.set_ylabel('Solar Y [arcsec]')
for axi in ax:
axi.set_xlim(-130, 141)
axi.set_ylim(-262, -86)
fig.subplots_adjust(
left=0.07, bottom=0.10, right=0.94, top=0.95, wspace=0.05, hspace=0.10)
plt.draw()
fig.savefig('sharp_imgdata.pdf', dpi=200)
fig.savefig('sharp_imgdata.png', dpi=200)
plt.show()