Imaging example#

How to create visibility from pixel data and make images.

The example uses stixpy to obtain STIX pixel data and convert these into visibilities and xrayvisim to make the images.

Imports

import logging

import astropy.units as u
import matplotlib.pyplot as plt
import numpy as np
from sunpy.coordinates import HeliographicStonyhurst, Helioprojective
from sunpy.map import Map, make_fitswcs_header
from sunpy.time import TimeRange
from xrayvision.clean import vis_clean
from xrayvision.imaging import vis_to_image, vis_to_map
from xrayvision.mem import mem, resistant_mean

from stixpy.calibration.visibility import calibrate_visibility, create_meta_pixels, create_visibility
from stixpy.coordinates.frames import STIXImaging
from stixpy.coordinates.transforms import get_hpc_info
from stixpy.imaging.em import em
from stixpy.map.stix import STIXMap  # noqa
from stixpy.product import Product

logger = logging.getLogger(__name__)

Read science file as Product

cpd_sci = Product(
    "http://pub099.cs.technik.fhnw.ch/fits/L1/2021/09/23/SCI/solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits"
)
cpd_sci
Files Downloaded:   0%|          | 0/1 [00:00<?, ?file/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   0%|          | 0.00/48.4M [00:00<?, ?B/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   0%|          | 1.02k/48.4M [00:00<7:13:26, 1.86kB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   0%|          | 65.8k/48.4M [00:00<06:03, 133kB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   0%|          | 211k/48.4M [00:00<01:54, 422kB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   1%|          | 598k/48.4M [00:00<00:38, 1.23MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   3%|▎         | 1.28M/48.4M [00:00<00:18, 2.57MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:   5%|▌         | 2.65M/48.4M [00:01<00:08, 5.36MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  11%|█         | 5.41M/48.4M [00:01<00:03, 11.1MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  22%|██▏       | 10.7M/48.4M [00:01<00:01, 22.1MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  37%|███▋      | 17.7M/48.4M [00:01<00:00, 35.3MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  51%|█████     | 24.5M/48.4M [00:01<00:00, 44.5MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  65%|██████▍   | 31.2M/48.4M [00:01<00:00, 51.0MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  79%|███████▉  | 38.3M/48.4M [00:01<00:00, 56.6MB/s]

solo_L1_stix-sci-xray-cpd_20210923T152015-20210923T152639_V02_2109230030-62447.fits:  93%|█████████▎| 45.3M/48.4M [00:01<00:00, 60.2MB/s]


Files Downloaded: 100%|██████████| 1/1 [00:02<00:00,  2.46s/file]
Files Downloaded: 100%|██████████| 1/1 [00:02<00:00,  2.46s/file]

CompressedPixelData   <sunpy.time.timerange.TimeRange object at 0x7fa0c70b3610>
    Start: 2021-09-23 15:20:15
    End:   2021-09-23 15:26:39
    Center:2021-09-23 15:23:27
    Duration:0.004439814814814813 days or
           0.10655555555555551 hours or
           6.393333333333331 minutes or
           383.59999999999985 seconds
    DetectorMasks
    [0...697]: [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]

    PixelMasks
    [0...697]: [['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1']]

    EnergyEdgeMasks
    [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,_]

Read background file as Product

cpd_bkg = Product(
    "http://pub099.cs.technik.fhnw.ch/fits/L1/2021/09/23/SCI/solo_L1_stix-sci-xray-cpd_20210923T095923-20210923T113523_V02_2109230083-57078.fits"
)
cpd_bkg
Files Downloaded:   0%|          | 0/1 [00:00<?, ?file/s]

solo_L1_stix-sci-xray-cpd_20210923T095923-20210923T113523_V02_2109230083-57078.fits:   0%|          | 0.00/121k [00:00<?, ?B/s]

solo_L1_stix-sci-xray-cpd_20210923T095923-20210923T113523_V02_2109230083-57078.fits:   1%|          | 1.02k/121k [00:00<01:04, 1.86kB/s]

solo_L1_stix-sci-xray-cpd_20210923T095923-20210923T113523_V02_2109230083-57078.fits:  47%|████▋     | 57.2k/121k [00:00<00:00, 117kB/s]


Files Downloaded: 100%|██████████| 1/1 [00:01<00:00,  1.26s/file]
Files Downloaded: 100%|██████████| 1/1 [00:01<00:00,  1.26s/file]

CompressedPixelData   <sunpy.time.timerange.TimeRange object at 0x7fa0c719a490>
    Start: 2021-09-23 09:59:23
    End:   2021-09-23 11:35:23
    Center:2021-09-23 10:47:23
    Duration:0.06666666666666665 days or
           1.5999999999999996 hours or
           95.99999999999997 minutes or
           5759.999999999999 seconds
    DetectorMasks
    [0]: [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]

    PixelMasks
    [0]: [['1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1' '1']]

    EnergyEdgeMasks
    [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]

Set time and energy ranges which will be considered for the science and the background file

time_range_sci = ["2021-09-23T15:21:00", "2021-09-23T15:24:00"]
time_range_bkg = [
    "2021-09-23T09:00:00",
    "2021-09-23T12:00:00",
]  # Set this range larger than the actual observation time
energy_range = [28, 40] * u.keV

Create the meta pixel, A, B, C, D for the science and the background data

meta_pixels_sci = create_meta_pixels(
    cpd_sci, time_range=time_range_sci, energy_range=energy_range, flare_location=[0, 0] * u.arcsec, no_shadowing=True
)

meta_pixels_bkg = create_meta_pixels(
    cpd_bkg, time_range=time_range_bkg, energy_range=energy_range, flare_location=[0, 0] * u.arcsec, no_shadowing=True
)

Perform background subtraction

meta_pixels_bkg_subtracted = {
    **meta_pixels_sci,
    "abcd_rate_kev_cm": meta_pixels_sci["abcd_rate_kev_cm"] - meta_pixels_bkg["abcd_rate_kev_cm"],
    "abcd_rate_error_kev_cm": np.sqrt(
        meta_pixels_sci["abcd_rate_error_kev_cm"] ** 2 + meta_pixels_bkg["abcd_rate_error_kev_cm"] ** 2
    ),
}

Create visibilities from the meta pixels

vis = create_visibility(meta_pixels_bkg_subtracted)

Calibrate the visibilities

cal_vis = calibrate_visibility(vis)

Selected detectors 10 to 7

# order by sub-collimator e.g. 10a, 10b, 10c, 9a, 9b, 9c ....
isc_10_7 = [3, 20, 22, 16, 14, 32, 21, 26, 4, 24, 8, 28]
idx = np.argwhere(np.isin(cal_vis.meta["isc"], isc_10_7)).ravel()

Slice the visibilities to detectors 10 - 7

vis10_7 = cal_vis[idx]

Set up image parameters

imsize = [512, 512] * u.pixel  # number of pixels of the map to reconstruct
pixel = [10, 10] * u.arcsec / u.pixel  # pixel size in arcsec

Make a full disk back projection (inverse transform) map

bp_image = vis_to_image(vis10_7, imsize, pixel_size=pixel)

Obtain the necessary metadata to create a sunpy map in the STIXImaging frame

vis_tr = TimeRange(vis.meta["time_range"])
roll, solo_xyz, pointing = get_hpc_info(vis_tr.start, vis_tr.end)
solo = HeliographicStonyhurst(*solo_xyz, obstime=vis_tr.center, representation_type="cartesian")
coord = STIXImaging(0 * u.arcsec, 0 * u.arcsec, obstime=vis_tr.start, obstime_end=vis_tr.end, observer=solo)
header = make_fitswcs_header(
    bp_image, coord, telescope="STIX", observatory="Solar Orbiter", scale=[10, 10] * u.arcsec / u.pix
)
fd_bp_map = Map((bp_image, header))
Files Downloaded:   0%|          | 0/1 [00:00<?, ?file/s]

solo_L2_stix-aux-ephemeris_20210923_V02U.fits:   0%|          | 0.00/397k [00:00<?, ?B/s]

solo_L2_stix-aux-ephemeris_20210923_V02U.fits:   0%|          | 1.02k/397k [00:00<02:09, 3.06kB/s]

solo_L2_stix-aux-ephemeris_20210923_V02U.fits:   8%|▊         | 33.0k/397k [00:00<00:03, 96.7kB/s]

solo_L2_stix-aux-ephemeris_20210923_V02U.fits:  43%|████▎     | 170k/397k [00:00<00:00, 463kB/s]


Files Downloaded: 100%|██████████| 1/1 [00:00<00:00,  1.10file/s]
Files Downloaded: 100%|██████████| 1/1 [00:00<00:00,  1.10file/s]

Convert the coordinates and make a map in Helioprojective and rotate so “North” is “up”

hpc_ref = coord.transform_to(Helioprojective(observer=solo, obstime=vis_tr.center))  # Center of STIX pointing in HPC
header_hp = make_fitswcs_header(bp_image, hpc_ref, scale=[10, 10] * u.arcsec / u.pix, rotation_angle=90 * u.deg + roll)
hp_map = Map((bp_image, header_hp))
hp_map_rotated = hp_map.rotate()

Plot the both maps

fig = plt.figure(figsize=(12, 8))
ax0 = fig.add_subplot(1, 2, 1, projection=fd_bp_map)
ax1 = fig.add_subplot(1, 2, 2, projection=hp_map_rotated)
fd_bp_map.plot(axes=ax0)
fd_bp_map.draw_limb()
fd_bp_map.draw_grid()

hp_map_rotated.plot(axes=ax1)
hp_map_rotated.draw_limb()
hp_map_rotated.draw_grid()
2021-09-23 15:22:29,  2021-09-23 15:22:29
Files Downloaded:   0%|          | 0/1 [00:00<?, ?file/s]
Files Downloaded: 100%|██████████| 1/1 [00:00<00:00,  3.04file/s]
Files Downloaded: 100%|██████████| 1/1 [00:00<00:00,  3.04file/s]

<CoordinatesMap with 2 world coordinates:

  index aliases    type   unit    wrap   format_unit visible
  ----- ------- --------- ---- --------- ----------- -------
      0     lon longitude  deg 180.0 deg         deg     yes
      1     lat  latitude  deg      None         deg     yes

>

Estimate the flare location and plot on top of back projection map. Note the coordinates are automatically converted from the STIXImaging to Helioprojective

max_pixel = np.argwhere(fd_bp_map.data == fd_bp_map.data.max()).ravel() * u.pixel
# because WCS axes and array are reversed
max_stix = fd_bp_map.pixel_to_world(max_pixel[1], max_pixel[0])

ax0.plot_coord(max_stix, marker=".", markersize=50, fillstyle="none", color="r", markeredgewidth=2)
ax1.plot_coord(max_stix, marker=".", markersize=50, fillstyle="none", color="r", markeredgewidth=2)
[<matplotlib.lines.Line2D object at 0x7fa0c3477190>]

Use estimated flare location to create more accurate visibilities

meta_pixels_sci = create_meta_pixels(
    cpd_sci, time_range=time_range_sci, energy_range=energy_range, flare_location=max_stix, no_shadowing=True
)

meta_pixels_bkg_subtracted = {
    **meta_pixels_sci,
    "abcd_rate_kev_cm": meta_pixels_sci["abcd_rate_kev_cm"] - meta_pixels_bkg["abcd_rate_kev_cm"],
    "abcd_rate_error_kev_cm": np.sqrt(
        meta_pixels_sci["abcd_rate_error_kev_cm"] ** 2 + meta_pixels_bkg["abcd_rate_error_kev_cm"] ** 2
    ),
}

vis = create_visibility(meta_pixels_bkg_subtracted)
cal_vis = calibrate_visibility(vis, flare_location=max_stix)

Selected detectors 10 to 3 order by sub-collimator e.g. 10a, 10b, 10c, 9a, 9b, 9c ….

isc_10_3 = [3, 20, 22, 16, 14, 32, 21, 26, 4, 24, 8, 28, 15, 27, 31, 6, 30, 2, 25, 5, 23, 7, 29, 1]
idx = np.argwhere(np.isin(cal_vis.meta["isc"], isc_10_3)).ravel()

Create an xrayvsion visibility object

cal_vis.meta["offset"] = max_stix
vis10_3 = cal_vis[idx]

Set up image parameters

imsize = [129, 129] * u.pixel  # number of pixels of the map to reconstruct
pixel = [2, 2] * u.arcsec / u.pixel  # pixel size in arcsec

Create a back projection image with natural weighting

bp_nat = vis_to_image(vis10_3, imsize, pixel_size=pixel)

Create a back projection image with uniform weighting

bp_uni = vis_to_image(vis10_3, imsize, pixel_size=pixel, scheme="uniform")

Create a sunpy.map.Map with back projection

bp_map = vis_to_map(vis10_3, imsize, pixel_size=pixel)

Crete a clean image using the clean algorithm vis_clean

niter = 200  # number of iterations
gain = 0.1  # gain used in each clean iteration
beam_width = 20.0 * u.arcsec
clean_map, model_map, resid_map = vis_clean(
    vis10_3, imsize, pixel_size=pixel, gain=gain, niter=niter, clean_beam_width=20 * u.arcsec
)
2024-07-16T20:41:52Z INFO xrayvision.clean 124: Iter: 0, strength: 0.7704811709722035, location: (62, 65)
2024-07-16T20:41:52Z INFO xrayvision.clean 124: Iter: 25, strength: 0.17685250348061884, location: (81, 53)
2024-07-16T20:41:52Z INFO xrayvision.clean 124: Iter: 50, strength: 0.08367161699156966, location: (63, 87)
2024-07-16T20:41:52Z INFO xrayvision.clean 145: Largest residual negative

Create a sunpy map for the clean image in Helioprojective

header = make_fitswcs_header(
    clean_map.data,
    max_stix.transform_to(Helioprojective(obstime=vis_tr.center, observer=solo)),
    telescope="STIX",
    observatory="Solar Orbiter",
    scale=pixel,
    rotation_angle=90 * u.deg + roll,
)

clean_map = Map((clean_map.data, header))
plt.figure()
clean_map.rotate().plot()
2021-09-23 15:22:29
<matplotlib.image.AxesImage object at 0x7fa0c33e1f10>

Crete a map using the MEM GE algorithm mem

snr_value, _ = resistant_mean((np.abs(vis10_3.visibilities) / vis10_3.amplitude_uncertainty).flatten(), 3)
percent_lambda = 2 / (snr_value**2 + 90)
mem_map = mem(vis10_3, shape=imsize, pixel_size=pixel, percent_lambda=percent_lambda)
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 0, Chi2: 127.93058284978744
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 25, Chi2: 4.301970597826388
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 50, Chi2: 3.186769018989274
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 75, Chi2: 2.7423076128838018
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 100, Chi2: 2.4836161402644343
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 125, Chi2: 2.3153980537684267
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 150, Chi2: 2.1994253828533328
2024-07-16T20:41:57Z INFO xrayvision.mem 159: Iter: 175, Chi2: 2.113899821452196
/home/docs/checkouts/readthedocs.org/user_builds/stixpy/envs/v0.1.3/lib/python3.9/site-packages/astropy/units/quantity.py:1326: ComplexWarning: Casting complex values to real discards the imaginary part
  self.view(np.ndarray).__setitem__(i, self._to_own_unit(value))
2024-07-16T20:41:58Z INFO xrayvision.mem 510: Iter: 0, Obj function: (139.23212126829804+0j)
2024-07-16T20:42:02Z INFO xrayvision.mem 510: Iter: 25, Obj function: (5.1902301519118135+0j)
2024-07-16T20:42:06Z INFO xrayvision.mem 510: Iter: 50, Obj function: (4.826163622350658+0j)
2024-07-16T20:42:09Z INFO xrayvision.mem 510: Iter: 75, Obj function: (4.7990102763476346+0j)
2024-07-16T20:42:13Z INFO xrayvision.mem 510: Iter: 100, Obj function: (4.794721317122828+0j)

Crete a map using the EM algorithm EM

em_map = em(
    meta_pixels_bkg_subtracted["abcd_rate_kev_cm"],
    cal_vis,
    shape=imsize,
    pixel_size=pixel,
    flare_location=max_stix,
    idx=idx,
)

vmax = max([clean_map.data.max(), mem_map.data.max(), em_map.value.max()])
2024-07-16T20:42:15Z INFO stixpy.imaging.em 180: Iteration: 25, StdDeV: 0.05210401842903427, C-stat: 0.0322101423828504
2024-07-16T20:42:15Z INFO stixpy.imaging.em 180: Iteration: 50, StdDeV: 0.01550496359088871, C-stat: 0.02113078919193867
2024-07-16T20:42:15Z INFO stixpy.imaging.em 180: Iteration: 75, StdDeV: 0.002796184328660909, C-stat: 0.017429889982876513
2024-07-16T20:42:15Z INFO stixpy.imaging.em 180: Iteration: 100, StdDeV: 0.0005577500813556657, C-stat: 0.016355800816783846

Finally compare the images from each algorithm

fig, axes = plt.subplots(2, 2)
a = axes[0, 0].imshow(bp_nat.value)
axes[0, 0].set_title("Back Projection")
fig.colorbar(a)
b = axes[1, 0].imshow(clean_map.data, vmin=0, vmax=vmax)
axes[1, 0].set_title("Clean")
fig.colorbar(b)
c = axes[0, 1].imshow(mem_map.data, vmin=0, vmax=vmax)
axes[0, 1].set_title("MEM GE")
fig.colorbar(c)
d = axes[1, 1].imshow(em_map.value, vmin=0, vmax=vmax)
axes[1, 1].set_title("EM")
fig.colorbar(d)
fig.tight_layout()
plt.show()
Back Projection, MEM GE, Clean, EM

Total running time of the script: (0 minutes 35.766 seconds)

Gallery generated by Sphinx-Gallery