Download the notebook.
Introduction to crisscross: Extracting spectra from crowded regions¶
This notebook shows how to extract HETG spectra from a crowded region by running crisscross to handle confusion in two end-to-end examples:
- covers a single ObsID in depth and
- demonstrates how to run crisscross on multiple ObsIDs and combine the resulting spectra.
More information and examples on crisscross and its helper function clean_spec can be found on their respective ahelp pages (e.g., type "ahelp crisscross" and "ahelp clean_spec" on the command line in CIAO).
Crisscross Summary¶
crisscross and its helper tool clean_spec allow users to analyze Chandra High Energy Transmission Grating (HETG)
spectra that are extracted from a field of view with multiple astrophysical X-ray sources. The HETG
instrument will disperse events onto the ACIS CCDs from all sources in the field of view. If there are several
sufficiently-bright X-ray sources then there is a potential for 'confusion' to occur. Confusion is a term used
here for scenarios where standard HETG spectral extractions of a source may erroneously assign events (counts) from a different astrophysical source to the extracted spectrum. This can result in events from an unrelated astrophysical source 'confusing' the spectrum of an extracted source.
crisscross is used to identify when spectral confusion occurs for a list of input sources (or a single source)
and generates 'confusion tables' which identify the wavelengths in each source's spectrum that are likely to
include X-ray events from unrelated field astrophysical sources. The user can then choose to remove all events
from the source spectrum in the wavelength range where this confusion occurs. The helper program
clean_spec uses the crisscross output confusion tables and input HETG PHA spectra to generate 'cleaned' spectra
(and ARFs) where these events are removed. crisscross is intended for crowded X-ray fields with multiple
observations such as stellar clusters.
Running crisscross¶
First, packages and tools used later in the notebook have to be imported.
import os
import glob
import shutil
import numpy as np
import matplotlib.pyplot as plt
from ciao_contrib.runtool import *
from ciao_contrib.cda.data import download_chandra_obsids
from sherpa.astro.ui import *
from sherpa_contrib.load_hetg_resp import load_gratings_pha2
(1) Run crisscross for a single source on a single ObsID¶
This example uses ObsID 3, a crowded HETG observation of a pre-main sequence stellar cluster.
The data is downloaded with the download_chandra_obsid script. A return value of "True" means that the download was successful.
download_chandra_obsids([3])
[True]
The code below will extract the spectrum of a single source in the field of view using chandra_repro and the source coordinates. COUP 394 is a young, pre-main sequence star whose X-ray emission is generated by a hot corona. The commands below will produce only responses for the +1/-1 HEG/MEG orders. Users that wish to generate responses for more orders should use the 'tg_orders' parameter with chandra_repro.
Users that wish to see spectra from other sources in the field of view can modify the variables below before running the remaining cells.
src_RA = 83.79475306
src_DEC = -5.39575306
obsid = 3
src_root = 'COUP_394'
# run chandra_repro using the zeroth order source location for COUP 394
chandra_repro.punlearn()
chandra_repro.indir = obsid
chandra_repro.root = src_root
chandra_repro.tg_zo_position = f'{src_RA},{src_DEC}'
chandra_repro.clobber=False
print(chandra_repro)
a = chandra_repro()
Parameters for chandra_repro:
Required parameters:
indir = 3 Input directory
outdir = Output directory (default = $indir/repro)
Optional parameters:
root = COUP_394 Root for output filenames
badpixel = True Create a new bad pixel file?
process_events = True Create a new level=2 event file?
destreak = True Destreak the ACIS-8 chip?
set_ardlib = True Set ardlib.par with the bad pixel file?
check_vf_pha = False Clean ACIS background in VFAINT data?
pix_adj = default Pixel randomization: default|edser|none|randomize
tg_zo_position = 83.79475306,-5.39575306 Method to determine gratings 0th order location: evt2|detect|R.A. & Dec.
tg_orders = default List of orders to extract: none, default, or comma separated list
asol_update = True If necessary, apply boresight correction to aspect solution file?
pi_filter = True Apply PI background filter to HRC-S+LETG data?
patch_hrc_ssc = False Patch HRC Secondary Science Corruption?
cleanup = True Cleanup intermediate files on exit
clobber = False Clobber existing file
verbose = 1 Debug Level(0-5)
Lets take a look at the source's spectra using Sherpa. We can use the sherpa-contrib function load_gratings_pha2 to identify and load the pipeline response files (ARFs and RMFs) whose names are in the PHA2 spectrum header. Since the PHA2 file contains all spectra for 12 orders (-3, -2, -1, +1, +2, +3 for HEG and MEG each), sherpa data ids 1-12 will be used. If the directory does not include all 12 HETG responses then only those identified will be loaded; this causes the warning messages below.
clean() #run clean to clean out all datasets and model definitions in Sherpa to start fresh
original_pha2 = f'{obsid}/repro/{src_root}_repro_pha2.fits'
load_gratings_pha2(pha2_file=original_pha2, use_errors=True)
WARNING-- The identified number of ARFs [4] does not match the number of PHA spectra [12] in the PHA2 file. Only the responses found will be included. WARNING-- The identified number of RMFs [4] does not match the number of PHA spectra [12] in the PHA2 file. Only the responses found will be included. read background_up into a dataset from file 3/repro/COUP_394_repro_pha2.fits read background_down into a dataset from file 3/repro/COUP_394_repro_pha2.fits Multiple data sets have been input: 1-12
Now that the data are loaded, Sherpa can plot the brightest orders (HEG-1, HEG+1, MEG-1, MEG+1). Since there is not much data past 20 Å for this source, the plot limits the X-axis range.
# Spectral orders in the PHA2 files are heg-3, heg-2, heg-1, heg+1, heg+2, heg+3, meg-3, meg-2, meg-1, meg+1, meg+2, meg+3
# Python starts counting at 0, so indices [3,4,9,10] select (heg -1/+1 and meg-1/+1)
spec_ids = [3,4,9,10]
# Each id must be set to the 'wave' analysis to plot in angstrom (instead of keV)
for i in spec_ids:
set_analysis(i, 'wave')
bin_num=3
for i in spec_ids:
group_width(i, bin_num)
# Create an arm label for the subplot names
arm_label = ['heg-1','heg+1','meg-1','meg+1']
plt.figure(figsize=(9, 5))
plot("data", 3, "data", 4, "data", 9, "data", 10,
yerrorbars=False, color='red', linestyle='-', marker=None)
plt.subplots_adjust(left=0, hspace=0.5, wspace=0.3)
axes = plt.gcf().axes
# set the same x-limits for all panels:
for ax, arm in zip(axes, arm_label):
ax.set_xlim(0, 20)
ax.set_title(arm)
dataset 3: 1:21.48 Wavelength (Angstrom) dataset 4: 1:21.48 Wavelength (Angstrom) dataset 9: 1:41.96 Wavelength (Angstrom) dataset 10: 1:41.96 Wavelength (Angstrom) dataset 3: 1:21.48 Wavelength (Angstrom) (unchanged) dataset 4: 1:21.48 Wavelength (Angstrom) (unchanged) dataset 9: 1:41.96 Wavelength (Angstrom) (unchanged) dataset 10: 1:41.96 Wavelength (Angstrom) (unchanged)
Similarly, the spectral response for the source can be plotted using the ARF files. This shows the sensitivity of the detector at the extracted spectral location.
arm_label_arf = ['heg-1 response','heg+1 response','meg-1 response','meg+1 response']
plt.figure(figsize=(9, 5))
plot("arf", 3, "arf", 4, "arf", 9, "arf", 10, color='red')
axes = plt.gcf().axes
for ax, arm in zip(axes,arm_label_arf):
ax.set_xlim(0, 20)
ax.set_title(arm)
plt.subplots_adjust(left=0, hspace=0.5, wspace=0.3)
The event file can be viewed interactively with ds9. The following command will open ds9 in the separate window if this example is run on a local computer.
_ = os.system(f'ds9 -cmap heat {obsid}/repro/{src_root}_repro_evt2.fits -scale log -bin factor 4 &')
0
The extracted source is in a crowded field and confusion should be assessed. In other words, the dispersed spectrum of COUP 394 (green ds9 region-box if viewing the event file) intersects with many sources and other dispersed spectra. To identify potential confusion, crisscross will be run on the observation. This requires an input list of all X-ray sources potentially in the field of view.
Download source list for the tutorial: For this tutorial, a source list of X-ray sources in the field was prepared from Getman et al. (2005). To run this tutorial locally, https://cxc.cfa.harvard.edu/ciao/threads/crisscross/full_coup_src_list.tsv needs to be downloaded (e.g. right click and select "save as" on the link above) and saved in the same directory as the notebook.
For other fields, users will have to produce their own list of X-ray sources in the field that might cause confusion. Most of the Chandra HETG observations with more than a few sources in the field of view have been previously observed with Chandra imaging. Running CIAO's wavdetect on an archival non-HETG imaging observation is a good way to produce a source list. Otherwise, users will have to use another wavelength (telescope) or identify the 0th order sources in an HETG observation by eye and manually create a list.
An example of how to create a crisscross source list using wavdetect can be found on "ahelp crisscross".
The required inputs for this tutorial are a source list (which will be input to crisscross as the parameter main_list) and the evt2 file and RA/DEC of your HETG-extracted source. crisscross will identify if any of the sources in the source list produce erroneous counts in the extracted source COUP 394. Depending on the number of sources in the source list and the available hardware, crisscross could take a few minutes to run.
evt2_path = f'{obsid}/repro/{src_root}_repro_evt2.fits'
crisscross(infile=evt2_path, outdir='cc_tutorial', main_list='full_coup_src_list.tsv',
single_src_pos=f'{src_RA},{src_DEC}', single_src_root=src_root, clobber=True)
crisscross
infile = 3/repro/COUP_394_repro_evt2.fits
outdir = cc_tutorial
main_list = full_coup_src_list.tsv
subset_src_list =
single_src_pos = 83.79475306,-5.39575306
single_src_root = COUP_394
wavdetect_file = None
conf_table_level = confused
arf_ratios_dir = $ASCDS_CALIB
max_pntsrc_dist = 8
min_pntsrc_counts = 5
min_spec_counts = 3
min_spec_confuser_counts = 50
osip_frac = 1
spec_confuse_limit = 0.1
max_arm_dist = 8
min_arm_counts = 50
arm_nsig = 6
arm_confuse_limit = 0.1
meg_cutoff_low = 1
meg_cutoff_high = 32
heg_cutoff_low = 1
heg_cutoff_high = 16
highest_order = 3
min_tg_d = -0.00066
max_tg_d = 0.00066
verbose = 1
clobber = yes
mode = hl
CrissCross Time Start:
Thu Jun 18 11:43:53 2026
This run is for observation "3/repro/COUP_394_repro_evt2.fits".
No input wavdetect source fits table provided so running wavdetect on 3/repro/COUP_394_repro_evt2.fits with binsize=2.0, bands=broad and psfecf=0.9.
If you wish to use other wavdetect parameters please run wavdetect and provide a wavdetect source fits table with parameter 'wavdetect_file'.
The total number of X-ray field sources input is 1616. These will be assessed as potential sources of confusion for sources in 'subset_src_list' or 'single_src_pos'.
total elapsed time has been 1.03 minutes.
crisscross has now finished and produced the confusion table for COUP 394. dmlist (or any fits table reader) can show the identified confusion for each arm. Here, only the most relevant columns are selected for display.
cc_table_coup = f'cc_tutorial/output_dir_obsid_3/confusion_output_files/table_fits_data/confused_{src_root}_consolidated_obsID_3.fits'
# View the sources of confusion in the COUP 394 MEG arms
dmlist.punlearn()
cols_to_show = 'confuser_srcid, grating_type, order, confusion_type, confusion_wave, wave_low, wave_high, flag'
dmlist.infile = f'{cc_table_coup}[cols {cols_to_show}][flag=confused, order=-1,+1, grating_type=meg]'
dmlist.opt='data'
dmlist()
--------------------------------------------------------------------------------
Data for Table Block TABLE
--------------------------------------------------------------------------------
ROW confuser_srcid grating_type order confusion_type confusion_wave wave_low wave_high flag
1 696 meg 1 spec 2.6126691536 1.9454580533 3.2798802538 confused
2 1390 meg 1 spec 8.9880208912 8.3208097909 9.6552319914 confused
3 187 meg 1 pnt 8.9045904665 8.7486201313 9.0605608017 confused
4 515 meg -1 pnt 2.6398747791 2.6172583374 2.6624912208 confused
5 652 meg -1 pnt 4.6294335817 4.6025095872 4.6563575761 confused
6 1113 meg -1 pnt 11.2665089081 11.1864092180 11.3466085981 confused
7 187 meg -1 arm 4.4522952332 3.970 5.180 confused
8 187 meg -1 arm 8.9045904665 7.290 12.750 confused
9 187 meg 1 arm 2.2261476166 2.140 2.330 confused
10 187 meg 1 arm 2.9681968222 2.840 3.120 confused
11 187 meg 1 arm 4.4522952332 4.270 4.660 confused
12 652 meg -1 arm 1.0 21.840 31.990 confused
13 652 meg -1 arm 2.3147167908 2.250 2.390 confused
14 652 meg -1 arm 1.5431445272 1.50 1.590 confused
15 652 meg 1 arm 1.0 21.840 31.990 confused
16 652 meg 1 arm 4.6294335817 3.980 5.770 confused
17 652 meg 1 arm 2.3147167908 2.130 2.560 confused
18 1113 meg -1 arm 5.6332544540 5.380 5.930 confused
19 1113 meg -1 arm 3.7555029694 3.570 3.970 confused
20 1113 meg -1 arm 2.8166272270 2.690 2.970 confused
21 1113 meg 1 arm 5.6332544540 4.960 6.70 confused
# View the sources of confusion in the COUP 394 HEG arms
dmlist.punlearn()
dmlist.infile = f'{cc_table_coup}[cols {cols_to_show}][flag=confused, order=-1,+1, grating_type=heg]'
dmlist.opt='data'
dmlist.rows='1:50'
dmlist()
--------------------------------------------------------------------------------
Data for Table Block TABLE
--------------------------------------------------------------------------------
ROW confuser_srcid grating_type order confusion_type confusion_wave wave_low wave_high flag
1 654 heg -1 spec 2.2434677134 1.9098896723 2.5770457545 confused
2 696 heg -1 spec 2.5394510028 2.2058729617 2.8730290439 confused
3 896 heg -1 spec 4.0036047229 3.6700266818 4.3371827640 confused
4 1070 heg -1 spec 5.5581381601 5.2245601190 5.8917162012 confused
5 1089 heg -1 spec 5.5417734858 5.2081954447 5.8753515269 confused
6 1198 heg -1 spec 6.8990374745 6.5654594334 7.2326155156 confused
7 1231 heg -1 spec 12.7468867023 12.4133086612 13.0804647434 confused
8 1390 heg -1 spec 9.1588726939 8.8252946528 9.4924507350 confused
9 225 heg 1 pnt 2.2023245108 2.1570122855 2.2476367362 confused
10 571 heg -1 pnt 1.1120539065 0.86905072783987 1.3612955569 confused
11 160 heg 1 arm 1.1614327135 1.130 1.190 confused
12 225 heg -1 arm 1.1011622554 1.040 1.170 confused
13 225 heg -1 arm 2.2023245108 1.980 2.530 confused
14 225 heg -1 arm 16.0 13.890 31.990 confused
15 225 heg 1 arm 1.1011622554 1.080 1.120 confused
16 225 heg 1 arm 16.0 13.890 31.990 confused
crisscross identified some confusion (flag column 'confused'). The 'wave_low' and 'wave_high' columns indicate the wavelength range recommended to be ignored due to the likelihood of HETG confusion by source 'confuser_srcid'. The 'confusion_type' column shows which type of confusion (point source, spectral intersection, arm confusion, or read-out streak confusion) has been identified. For more information on each of these types of confusion, see "ahelp crisscross".
Next, the confused portions of the spectrum and responses are cleaned using the crisscross helper CIAO function clean_spec.
NOTE: clean_spec removes ALL counts in the confused regions and not just counts from the confusing source. However, crisscross has many tunable parameters to allow some level of potential confusion through based on the signal-to-noise ratio of the HETG source events compared to the confused events in the confused bandpass. If crisscross is unnecessarily removing counts it can be re-run with lower confusion threshold parameters. See "ahelp crisscross" for more info.
clean_spec will attempt to identify the relevant response files when provided with an HETG PHA2 file. This is important because clean_spec also zeros out the ARF for each region with confusion. The clean_spec tool creates a new 'cleaned' PHA2 file and associated ARFs and does not modify the original files. In this example, only 4 ARFs and spectra are cleaned because only 4 response files were created when running chandra_repro above (heg-1,+1, meg-1,+1). Even though only four orders have been cleaned, the new PHA2 file still contains the other 8 uncleaned (original) orders.
clean_spec(infile=original_pha2, conf_file=cc_table_coup,
spec_root=src_root, arf_file=None, resp_dir=None, clobber=True)
clean_spec
infile = 3/repro/COUP_394_repro_pha2.fits
conf_file = cc_tutorial/output_dir_obsid_3/confusion_output_files/table_fits_data/confused_COUP_394_consolidated_obsID_3.fits
spec_root = COUP_394
arf_file =
resp_dir =
verbose = 1
clobber = yes
mode = hl
Warning, no ARF response files provided in parameter arf_file. Attempting to find them.
WARNING-- The identified number of ARFs [4] does not match the number of PHA spectra [12] in the PHA2 file. Only the responses found will be included.
WARNING-- The output 'cleaned' PHA2 file will include all original orders but only those with matching ARFs will be cleaned.
clean_spec produced a new PHA2 file and four new ARF files. They are loaded in Sherpa starting with ID 13 to avoid overwriting the previously loaded (uncleaned) spectra. load_gratings_pha2 will find the responses again and load the new cleaned ARFs and the original RMFs. RMF files do not have to be changed because of confusion and thus the same RMF files are used for original and cleaned spectra.
cleaned_pha2 = f'{src_root}_obsid_{obsid}_cleaned.pha2'
load_gratings_pha2(pha2_file=cleaned_pha2, rmf_dir=f'{obsid}/repro/tg',
use_errors=True, dataset_id_start=13)
WARNING-- The identified number of ARFs [4] does not match the number of PHA spectra [12] in the PHA2 file. Only the responses found will be included. WARNING-- The identified number of RMFs [4] does not match the number of PHA spectra [12] in the PHA2 file. Only the responses found will be included. read background_up into a dataset from file COUP_394_obsid_3_cleaned.pha2 read background_down into a dataset from file COUP_394_obsid_3_cleaned.pha2 Multiple data sets have been input: 13-24
Now that cleaned spectra and responses have been prepared, a plot can compare the clean spectra to the original files that included confusion. This will demonstrate what crisscross and clean_spec have done.
#the new sherpa ids for the cleaned heg-1, heg+1, meg-1 and meg+1 spectra
spec_ids_clean = [15,16,21,22]
#set the analysis space to wavelength so it plots in Angstroms.
for i in spec_ids_clean:
set_analysis(i,'wave')
#bin num was set in earlier cell and should be the same here
for i in spec_ids_clean:
group_width(i, bin_num)
plt.figure(figsize=(9, 5))
#plot the original (confused) spectra in red
plot("data", 3, "data", 4, "data", 9, "data", 10,
color='red',
label=['confused','confused','confused','confused'],
yerrorbars=False, linestyle='-', marker=None)
#overplot the new 'cleaned' spectra in blue
plot("data", 15, "data", 16, "data", 21, "data", 22,
color='blue',
label=['cleaned','cleaned','cleaned','cleaned'],
overplot=True, yerrorbars=False, linestyle='-', marker=None)
# set the same x-limits for all panels:
axes = plt.gcf().axes
for ax, arm in zip(axes,arm_label):
ax.set_xlim(0,20)
ax.set_title(arm)
ax.legend()
#label the figure with color
fig = plt.gcf()
fig.text(0.30, 0.95, "Confused", ha='left', va='center', color='red', fontsize=16)
fig.text(0.425, .95, "vs", va='center', color='black', fontsize=16)
fig.text(0.565, .95, "Cleaned", ha='right', va='center', color='blue', fontsize=16)
plt.draw()
plt.subplots_adjust(left=0, hspace=0.5, wspace=0.3)
dataset 15: 1:21.48 Wavelength (Angstrom) dataset 16: 1:21.48 Wavelength (Angstrom) dataset 21: 1:41.96 Wavelength (Angstrom) dataset 22: 1:41.96 Wavelength (Angstrom) dataset 15: 1:21.48 Wavelength (Angstrom) (unchanged) dataset 16: 1:21.48 Wavelength (Angstrom) (unchanged) dataset 21: 1:41.96 Wavelength (Angstrom) (unchanged) dataset 22: 1:41.96 Wavelength (Angstrom) (unchanged)
In the confused regions the flux (blue) is set to zero compared to the red spectra while in the other regions the blue and red spectra are identical (and the blue line is plotted on top of the red line). A similar comparison can be made between the original (confused) and cleaned arfs:
plt.figure(figsize=(9, 5))
#original ARFs
plot("arf", 3, "arf", 4, "arf", 9, "arf", 10,
label=['confused','confused','confused','confused'],
color='red')
#cleaned ARFs
plot("arf", 15, "arf", 16, "arf", 21, "arf", 22,
label=['cleaned','cleaned','cleaned','cleaned'],
color='blue', overplot=True)
axes = plt.gcf().axes
for ax, arm in zip(axes,arm_label_arf):
ax.set_xlim(0, 20)
ax.set_title(arm)
plt.subplots_adjust(left=0, hspace=0.5, wspace=0.3)
The plot shows that several portions of each spectrum have been identified to have events associated with other sources in the field assigned to the extracted COUP 394 source (i.e., confusion). What looked like an emission line at 8.5 Å in the meg+1 spectrum turns out to be emission from a different source crossing the extracted spectrum. The narrow ranges of confusion are typically associated with point sources or spectral intersection whereas the large bandpasses of confusion are typically the result of arm confusion (e.g., another bright source falling on the dispersed arm of the extract source.)
The confusion tables list specific instances of confusion and the source causing them. The code below lists confusion the MEG arm data.
dmlist.punlearn
dmlist.infile = f'{cc_table_coup}[cols confuser_srcid, grating_type, order, confusion_type, confusion_wave, wave_low, wave_high, flag][flag=confused, order=-1,+1, grating_type=meg]'
dmlist.opt='data'
dmlist.rows=30
dmlist()
--------------------------------------------------------------------------------
Data for Table Block TABLE
--------------------------------------------------------------------------------
ROW confuser_srcid grating_type order confusion_type confusion_wave wave_low wave_high flag
1 696 meg 1 spec 2.6126691536 1.9454580533 3.2798802538 confused
2 1390 meg 1 spec 8.9880208912 8.3208097909 9.6552319914 confused
3 187 meg 1 pnt 8.9045904665 8.7486201313 9.0605608017 confused
4 515 meg -1 pnt 2.6398747791 2.6172583374 2.6624912208 confused
5 652 meg -1 pnt 4.6294335817 4.6025095872 4.6563575761 confused
6 1113 meg -1 pnt 11.2665089081 11.1864092180 11.3466085981 confused
7 187 meg -1 arm 4.4522952332 3.970 5.180 confused
8 187 meg -1 arm 8.9045904665 7.290 12.750 confused
9 187 meg 1 arm 2.2261476166 2.140 2.330 confused
10 187 meg 1 arm 2.9681968222 2.840 3.120 confused
11 187 meg 1 arm 4.4522952332 4.270 4.660 confused
12 652 meg -1 arm 1.0 21.840 31.990 confused
13 652 meg -1 arm 2.3147167908 2.250 2.390 confused
14 652 meg -1 arm 1.5431445272 1.50 1.590 confused
15 652 meg 1 arm 1.0 21.840 31.990 confused
16 652 meg 1 arm 4.6294335817 3.980 5.770 confused
17 652 meg 1 arm 2.3147167908 2.130 2.560 confused
18 1113 meg -1 arm 5.6332544540 5.380 5.930 confused
19 1113 meg -1 arm 3.7555029694 3.570 3.970 confused
20 1113 meg -1 arm 2.8166272270 2.690 2.970 confused
21 1113 meg 1 arm 5.6332544540 4.960 6.70 confused
From the table, it appears that the MEG+1 wavelength range of ~8-9.5 Å is affected by two sources of confusion. A 0th order point source (COUP 187) landed right on the dispersed spectrum and the dispersed spectrum of COUP 1390 also intersected with the extracted MEG+1 spectrum.
The events assigned to the HEG or MEG spectrum can be visually assessed to see how well crisscross identified confusion. This plotting method only works on evt2 files made for the extracted source of interest which in this case is COUP 394 since the grating coordiantes in the evt2 files are relative to that (a listing of the column names in the evt2 file is below).
# Use pycrates to read in the event file and inspect the columns.
from pycrates import read_file
evt2_file = f'{obsid}/repro/{src_root}_repro_evt2.fits'
evt_data = read_file(evt2_file)
evt_data.get_colnames()
['time', 'expno', 'rd(tg_r, tg_d)', 'chip(chipx, chipy)', 'tdet(tdetx, tdety)', 'det(detx, dety)', 'sky(x, y)', 'ccd_id', 'pha', 'pi', 'energy', 'grade', 'fltgrade', 'node_id', 'tg_m', 'tg_lam', 'tg_mlam', 'tg_srcid', 'tg_part', 'tg_smap', 'status']
The relevant HETG event values are copied to new arrays for plotting. HETG event files contain extra columns for events associated with the extracted spectrum compared to ACIS imaging observations.
# HETG wavelength assigned to each event
tg_lam = evt_data.tg_lam.values
# cross-dispersion distance (distance from 0th order) of each event
tg_d = evt_data.rd.tg_d.values
# dispersion order (e.g., +1,-1) assigned to each event
tg_m = evt_data.tg_m.values
# order multiplied with the wavelength for each event
tg_mlam = evt_data.tg_mlam.values
# ACIS CCD-derived energy of the event
energy = evt_data.energy.values
def filter_evt_crate(evt_crate, order_val, arm_par, tgd_par):
"""
Function for filtering a crate evt file to return the event rows that satisfy
the HETG order, arm type (meg/heg) and tg_d value conditions.
"""
crate_elements = (evt_crate.tg_m.values == order_val) & \
(evt_crate.tg_part.values == arm_par) & \
(np.abs(evt_crate.rd.tg_d.values) < tgd_par)
return(crate_elements)
This function filters the event file to retrieve order- and arm-specific event information for plotting.
#create the filters for plotting only the relevant orders and arm type
meg_p1 = filter_evt_crate(evt_data, 1, 2, 8E-3)
meg_m1 = filter_evt_crate(evt_data,-1, 2, 8E-3)
heg_p1 = filter_evt_crate(evt_data, 1, 1, 8E-3)
heg_m1 = filter_evt_crate(evt_data, -1, 1, 8E-3)
fig, ax = plt.subplots(
nrows=1, ncols=2, # 2 rows, 2 columns
figsize=(16, 5), # optional: figure size in inches
)
# MEG PLOT
ax[0].scatter(-1*tg_lam[meg_m1], tg_d[meg_m1], s=1, color='green')
ax[0].scatter(tg_lam[meg_p1], tg_d[meg_p1], s=1, color='blue')
ax[0].plot([-30,30], [6.4E-4, 6.4E-4], color='orange') #default extraction width for HETG spectra
ax[0].plot([-30,30], [-6.4E-4, -6.4E-4], color='orange')
ax[0].set_title('MEG -1 and +1')
# HEG PLOT
ax[1].scatter(-1*tg_lam[heg_m1], tg_d[heg_m1], s=1, color='green')
ax[1].scatter(tg_lam[heg_p1], tg_d[heg_p1], s=1, color='blue')
ax[1].set_title('HEG -1 and +1')
ax[1].plot([-15,15], [6.4E-4, 6.4E-4], color='orange')
ax[1].plot([-15,15], [-6.4E-4, -6.4E-4], color='orange')
for i in ax:
i.set_xlabel('m * Wavelength [Angstrom]')
i.set_ylabel('Cross Dispersion Angle [deg]')
The above plot shows the MEG (left) and HEG (right) events associated with the CIAO extraction of COUP 394 source. Specifically, the events within the orange box will be those that are included in the PHA2 spectrum. The X-axis denotes the order * wavelength, so photons seen in a negative dispersion order are plotted at negative numbers. The y-axis denotes the cross dispersion angle (tg_d) in degrees, essentially the distance between a count and the middle of the line of dispersed photons.
A single unconfused source should only show events within the orange box. The MEG+1 plot (right, blue) shows a strong vertical line at about 9 Å which is the location of a 0th order (point source) field star that happened to fall on the dispersed spectrum of the extracted source. The vertical nature of this feature is due to the size of the PSF of that source. Many of those 0th-order events match the appropriate energy (e.g., 9 Å) to erroneously be assigned to the MEG+1 spectrum of COUP 394. Other fainter 0th-order confusing sources can be seen in MEG-1 at approximately -11 and -5 Å. The two large horizontal features shown in the MEG plot at y~0.008 and -0.006 represent the dispersed spectra of other stars in the field which are close to the extracted source. However, only events with a small range in cross dispersion angle (~+/- 6.3E-4) and assigned to the extracted source with CIAO tools.
The HEG -1 plot shows many examples of spectral-intersection confusion (e.g., the dispersed arm of another source overlapping the extracted source at the wavelength expected by ACIS order sorting). These are seens as with slanted lines at approximately -6.5 and -9 Å.
These plots can be hard to read when many sources are present, so the next plot filters out only the events within the orragne regions. For those events, the energy detected by the ACIS CCDs is then shown on the y-axis.
# REDO the filtering with a smaller tg_d window to remove events outside default HETG extraction window
meg_p1 = filter_evt_crate(evt_data, 1, 2, 4.6E-4)
meg_m1 = filter_evt_crate(evt_data,-1, 2, 4.6E-4)
heg_p1 = filter_evt_crate(evt_data, 1, 1, 4.6E-4)
heg_m1 = filter_evt_crate(evt_data, -1, 1, 4.6E-4)
# Display all events assigned to orders -3,-2,-1,+1,+2,+3
all_evts_meg_filt = (np.abs(evt_data.tg_m.values) <= 3) & (evt_data.tg_part.values == 2)
all_evts_heg_filt = (np.abs(evt_data.tg_m.values) <= 3 ) & (evt_data.tg_part.values == 1)
# Create a 2×2 figure (4 sub‑plots)
fig, ax = plt.subplots(
nrows=1, ncols=2, # 2 rows, 2 columns
figsize=(16, 5), # optional: figure size in inches
)
ax[0].scatter(tg_mlam[all_evts_meg_filt], energy[all_evts_meg_filt], s=1, color='grey')
ax[0].scatter(tg_mlam[meg_m1], energy[meg_m1], s=1, color='green')
ax[0].scatter(tg_mlam[meg_p1], energy[meg_p1], s=1, color='blue')
ax[0].set_title('MEG -1 and +1')
ax[1].scatter(tg_mlam[all_evts_heg_filt], energy[all_evts_heg_filt], s=1, color='grey')
ax[1].scatter(tg_mlam[heg_m1], energy[heg_m1], s=1, color='green')
ax[1].scatter(tg_mlam[heg_p1], energy[heg_p1], s=1, color='blue')
ax[1].set_title('HEG -1 and +1')
for i in ax:
i.set_ylim(300,7500)
i.set_xlabel('m * Wavelength [Angstrom]')
i.set_ylabel('ACIS Energy [eV]')
The above plots are the so-called HETG 'banana plots' and help reveal the extent to which a source's arm/order suffers from confusion. The HETG gratings equation determines the wavelength of every event based on its position along the dispersion direction from the 0th order. This wavelength (energy) is then compared to the ACIS CCD-determined energy of the same source to ensure it is compatible with the gratings-derived energy (wavelength). Only events whose ACIS CCD energy is consistent with the gratings-determined energy are ultimately assigned to the extracted spectrum with standard CIAO tools. Shown here in gray are all events assigned to any of the -3, -2, -1, +1, +2, +3 orders. The green and blue events are -1/+1 events that fall within the 4.6E-4 cross dispersion direction denoted by the yellow bars in the previous plots.
Looking closer at the MEG+1 portion of the spectrum one can see the previously discussed 'point source confusion' at ~9 Å. It shows us as a vertical bar in all three positive orders because the 0th order contains events at all energies and not just at 9 Å. Also note the curved feature starting at ~13 Å in the MEG+3 order (grey) and bending ultimately towards the MEG+1 order at ~ 20 Å. This is a clear indication of arm confusion from a source somewhere else on the detector but in line with the dispersed spectrum of our extracted source. The confusing source would likely contaminate all portions of the MEG+1 spectrum >~ 20 Å.
crisscross and its helper tool clean_spec facilitate the analysis of spectra from crowded HETG fields. The next and final step would be to fit the spectra with a model and learn something about the underlying astrophysics. This is beyond the scope of this tutorial but there are other CIAO tools that help with this process.
(2) Run crisscross for a single source on multiple ObsIDs and combine the spectra¶
Most HETG observations are campaigns with multiple ObsIDs. crisscross can process multiple observations and generate confusion tables for multiple sources in each ObsID. Users should see the crisscross and clean_spec help files for more examples of how this is done. The following example explains how to assess confusion for a single source over multiple ObsIDs.
First, download two more Orion Nebula Cluster pre-main sequence star observations and re-process them so chandra_repro extracts spectra of COUP 394.
# NOTE -- download_chandra_obsid will only download ObsIDs that are not on disk yet, is it doesn't hurt or list it again.
obsid_list = [3, 22999, 23000]
download_chandra_obsids(obsid_list)
[True, True]
# Run chandra repro using the zeroth order source location for COUP 394
# Note: ObsID 3 was reprocessed above, so no need to do it again.
for i in [22999, 23000]:
chandra_repro.punlearn()
chandra_repro.indir = i
chandra_repro.root = src_root
chandra_repro.tg_zo_position = f'{src_RA},{src_DEC}'
chandra_repro.clobber=False
print(chandra_repro)
a = chandra_repro()
Parameters for chandra_repro:
Required parameters:
indir = 22999 Input directory
outdir = Output directory (default = $indir/repro)
Optional parameters:
root = COUP_394 Root for output filenames
badpixel = True Create a new bad pixel file?
process_events = True Create a new level=2 event file?
destreak = True Destreak the ACIS-8 chip?
set_ardlib = True Set ardlib.par with the bad pixel file?
check_vf_pha = False Clean ACIS background in VFAINT data?
pix_adj = default Pixel randomization: default|edser|none|randomize
tg_zo_position = 83.79475306,-5.39575306 Method to determine gratings 0th order location: evt2|detect|R.A. & Dec.
tg_orders = default List of orders to extract: none, default, or comma separated list
asol_update = True If necessary, apply boresight correction to aspect solution file?
pi_filter = True Apply PI background filter to HRC-S+LETG data?
patch_hrc_ssc = False Patch HRC Secondary Science Corruption?
cleanup = True Cleanup intermediate files on exit
clobber = False Clobber existing file
verbose = 1 Debug Level(0-5)
Parameters for chandra_repro:
Required parameters:
indir = 23000 Input directory
outdir = Output directory (default = $indir/repro)
Optional parameters:
root = COUP_394 Root for output filenames
badpixel = True Create a new bad pixel file?
process_events = True Create a new level=2 event file?
destreak = True Destreak the ACIS-8 chip?
set_ardlib = True Set ardlib.par with the bad pixel file?
check_vf_pha = False Clean ACIS background in VFAINT data?
pix_adj = default Pixel randomization: default|edser|none|randomize
tg_zo_position = 83.79475306,-5.39575306 Method to determine gratings 0th order location: evt2|detect|R.A. & Dec.
tg_orders = default List of orders to extract: none, default, or comma separated list
asol_update = True If necessary, apply boresight correction to aspect solution file?
pi_filter = True Apply PI background filter to HRC-S+LETG data?
patch_hrc_ssc = False Patch HRC Secondary Science Corruption?
cleanup = True Cleanup intermediate files on exit
clobber = False Clobber existing file
verbose = 1 Debug Level(0-5)
Instead of running crisscross with a single evt2 file, here we will use a list of the three ObsIDs. They are identified with glob.
#create a list of evt2 files
evt2_list = glob.glob(f'*/repro/{src_root}_repro_evt2.fits')
print(evt2_list)
['22999/repro/COUP_394_repro_evt2.fits', '3/repro/COUP_394_repro_evt2.fits', '23000/repro/COUP_394_repro_evt2.fits']
Crisscross output is saved to to a new directory.
crisscross(infile=evt2_list, outdir='cc_tutorial_multiobs',
main_list='full_coup_src_list.tsv',
single_src_pos=f'{src_RA},{src_DEC}',
single_src_root=src_root, clobber=True)
crisscross
infile = 22999/repro/COUP_394_repro_evt2.fits,3/repro/COUP_394_repro_evt2.fits,23000/repro/COUP_394_repro_evt2.fits
outdir = cc_tutorial_multiobs
main_list = full_coup_src_list.tsv
subset_src_list =
single_src_pos = 83.79475306,-5.39575306
single_src_root = COUP_394
wavdetect_file = None
conf_table_level = confused
arf_ratios_dir = $ASCDS_CALIB
max_pntsrc_dist = 8
min_pntsrc_counts = 5
min_spec_counts = 3
min_spec_confuser_counts = 50
osip_frac = 1
spec_confuse_limit = 0.1
max_arm_dist = 8
min_arm_counts = 50
arm_nsig = 6
arm_confuse_limit = 0.1
meg_cutoff_low = 1
meg_cutoff_high = 32
heg_cutoff_low = 1
heg_cutoff_high = 16
highest_order = 3
min_tg_d = -0.00066
max_tg_d = 0.00066
verbose = 1
clobber = yes
mode = hl
CrissCross Time Start:
Thu Jun 18 12:08:45 2026
This run is for observation "22999/repro/COUP_394_repro_evt2.fits".
No input wavdetect source fits table provided so running wavdetect on 22999/repro/COUP_394_repro_evt2.fits with binsize=2.0, bands=broad and psfecf=0.9.
If you wish to use other wavdetect parameters please run wavdetect and provide a wavdetect source fits table with parameter 'wavdetect_file'.
The total number of X-ray field sources input is 1616. These will be assessed as potential sources of confusion for sources in 'subset_src_list' or 'single_src_pos'.
total elapsed time has been 0.92 minutes.
CrissCross Time Start:
Thu Jun 18 13:16:19 2026
This run is for observation "3/repro/COUP_394_repro_evt2.fits".
No input wavdetect source fits table provided so running wavdetect on 3/repro/COUP_394_repro_evt2.fits with binsize=2.0, bands=broad and psfecf=0.9.
If you wish to use other wavdetect parameters please run wavdetect and provide a wavdetect source fits table with parameter 'wavdetect_file'.
The total number of X-ray field sources input is 1616. These will be assessed as potential sources of confusion for sources in 'subset_src_list' or 'single_src_pos'.
total elapsed time has been 1.06 minutes.
CrissCross Time Start:
Thu Jun 18 13:17:22 2026
This run is for observation "23000/repro/COUP_394_repro_evt2.fits".
No input wavdetect source fits table provided so running wavdetect on 23000/repro/COUP_394_repro_evt2.fits with binsize=2.0, bands=broad and psfecf=0.9.
If you wish to use other wavdetect parameters please run wavdetect and provide a wavdetect source fits table with parameter 'wavdetect_file'.
The total number of X-ray field sources input is 1616. These will be assessed as potential sources of confusion for sources in 'subset_src_list' or 'single_src_pos'.
total elapsed time has been 0.85 minutes.
To run clean_spec on multiple pha2 files the user needs to provide a list of pha2_files and the relevant crisscross confusion table files. Cleaned spectra are saved to a new directory.
pha2_list = sorted(glob.glob(f'*/repro/{src_root}_repro_pha2.fits'))
cc_table_list = sorted(glob.glob(f'cc_tutorial_multiobs/*/*/table_fits_data/*.fits'))
spec_dir = 'coup394_multiobs_spec'
os.makedirs(spec_dir, exist_ok=True)
for pha2, cc_table in zip(pha2_list, cc_table_list):
clean_spec(infile=pha2,
conf_file=cc_table,
spec_root=f'{spec_dir}/{src_root}',
arf_file=None, resp_dir=None, clobber=False)
Now there is a set of cleaned spectra from three ObsIDs, which will be combined with the CIAO tool combine_grating_spectra. This can be used for visualization purposes because it can combine multiple spectra of the same source from different obsIDs. It can also combine the +1 and -1 orders of the same arm. It should be noted that it is typically not best practice to fit combined spectra. Instead, users are encouraged to perform joint-fits on the individual spectra and use combined spectra for visualization purposes only. Moreover, caution should be taken when combining spectra from sources that undergo significant variability.
#make a directory to hold the combine_grating_spectra combined files.
comb_spec = 'combined_spec'
os.makedirs(comb_spec, exist_ok=True)
#combine the original 'confused' spectra
combine_grating_spectra.punlearn()
combine_grating_spectra.infile=f'*/repro/{src_root}*_pha2.fits'
combine_grating_spectra.arf=f'*/repro/tg/*.arf'
combine_grating_spectra.rmf=f'*/repro/tg/*.rmf'
combine_grating_spectra.outroot=f'{comb_spec}/{src_root}_original'
combine_grating_spectra.add_plusminus='yes'
combine_grating_spectra.order=1
combine_grating_spectra.clobber=False
print(combine_grating_spectra)
a = combine_grating_spectra()
Parameters for combine_grating_spectra:
Required parameters:
infile = */repro/COUP_394*_pha2.fits Source PHA1/PHA2 grating spectra to combine; enter list or '@stack'
outroot = combined_spec/COUP_394_original Root name for output file(s)
Optional parameters:
add_plusminus = True Combine +/- orders (yes) after summing signed-order and part?
garm = all Grating arm (HEG, MEG, LEG, or 'all')
order = 1 Grating diffraction order (1,2,3,-1,-2,-3, or 'all')
arf = */repro/tg/*.arf Source ARF files to combine; enter list or '@stack'
rmf = */repro/tg/*.rmf Source RMF files to combine; enter list or '@stack'
bkg_pha = Background PHA files to combine; enter list or '@stack'
bscale_method = counts How are BACKSCAL and background counts computed?
exposure_mode = sum Sum or average exposure times?
object = Specify the OBJECT keyword in the combined output
clobber = False OK to overwrite existing output file?
verbose = 1 Debug Level(0-5)
combine_grating_spectra looks for rmfs in the same directory structure as the PHA2 files so the original RMFs have to be copied to the new 'cleaned' directory to combine all the cleaned files and responses. This is usually managable but RMFs can be relatively large files, so soft links or other ways to minimize disk usage might be useful.
#ID the original RMFs and make a copy in the spec_dir
rmf_list = sorted(glob.glob(f'*/repro/tg/*.rmf'))
print(rmf_list)
for i in rmf_list:
shutil.copy2(i, spec_dir)
['22999/repro/tg/COUP_394_repro_heg_m1.rmf', '22999/repro/tg/COUP_394_repro_heg_p1.rmf', '22999/repro/tg/COUP_394_repro_meg_m1.rmf', '22999/repro/tg/COUP_394_repro_meg_p1.rmf', '23000/repro/tg/COUP_394_repro_heg_m1.rmf', '23000/repro/tg/COUP_394_repro_heg_p1.rmf', '23000/repro/tg/COUP_394_repro_meg_m1.rmf', '23000/repro/tg/COUP_394_repro_meg_p1.rmf', '3/repro/tg/COUP_394_repro_heg_m1.rmf', '3/repro/tg/COUP_394_repro_heg_p1.rmf', '3/repro/tg/COUP_394_repro_meg_m1.rmf', '3/repro/tg/COUP_394_repro_meg_p1.rmf']
#combine the cleaned spectra
combine_grating_spectra.punlearn()
combine_grating_spectra.infile=f'{spec_dir}/*.pha2'
combine_grating_spectra.arf=f'{spec_dir}/*.arf'
combine_grating_spectra.rmf=f'{spec_dir}/*.rmf'
combine_grating_spectra.outroot=f'{comb_spec}/{src_root}_cleaned'
combine_grating_spectra.add_plusminus='yes'
combine_grating_spectra.order=1
combine_grating_spectra.clobber=False
print(combine_grating_spectra)
a = combine_grating_spectra()
Parameters for combine_grating_spectra:
Required parameters:
infile = coup394_multiobs_spec/*.pha2 Source PHA1/PHA2 grating spectra to combine; enter list or '@stack'
outroot = combined_spec/COUP_394_cleaned Root name for output file(s)
Optional parameters:
add_plusminus = True Combine +/- orders (yes) after summing signed-order and part?
garm = all Grating arm (HEG, MEG, LEG, or 'all')
order = 1 Grating diffraction order (1,2,3,-1,-2,-3, or 'all')
arf = coup394_multiobs_spec/*.arf Source ARF files to combine; enter list or '@stack'
rmf = coup394_multiobs_spec/*.rmf Source RMF files to combine; enter list or '@stack'
bkg_pha = Background PHA files to combine; enter list or '@stack'
bscale_method = counts How are BACKSCAL and background counts computed?
exposure_mode = sum Sum or average exposure times?
object = Specify the OBJECT keyword in the combined output
clobber = False OK to overwrite existing output file?
verbose = 1 Debug Level(0-5)
The code below loads the COUP 394 spectra combined from three ObsIDs into sherpa and plots the comparison of the confused vs cleaned spectra again.
orig_heg = f'{comb_spec}/COUP_394_original_combo_heg_abs1.pha'
orig_meg = f'{comb_spec}/COUP_394_original_combo_meg_abs1.pha'
cleaned_heg = f'{comb_spec}/COUP_394_cleaned_combo_heg_abs1.pha'
cleaned_meg = f'{comb_spec}/COUP_394_cleaned_combo_meg_abs1.pha'
#run clean so we can override the previous example's sherpa environment
clean()
load_data(1,orig_heg, use_errors=True)
load_data(2,orig_meg, use_errors=True)
load_data(3,cleaned_heg, use_errors=True)
load_data(4,cleaned_meg, use_errors=True)
read ARF file combined_spec/COUP_394_original_combo_heg_abs1.arf read RMF file combined_spec/COUP_394_original_combo_heg_abs1.rmf read background file combined_spec/COUP_394_original_combo_heg_abs1_bkg.pha read ARF file combined_spec/COUP_394_original_combo_meg_abs1.arf read RMF file combined_spec/COUP_394_original_combo_meg_abs1.rmf read background file combined_spec/COUP_394_original_combo_meg_abs1_bkg.pha read ARF file combined_spec/COUP_394_cleaned_combo_heg_abs1.arf read RMF file combined_spec/COUP_394_cleaned_combo_heg_abs1.rmf read background file combined_spec/COUP_394_cleaned_combo_heg_abs1_bkg.pha read ARF file combined_spec/COUP_394_cleaned_combo_meg_abs1.arf read RMF file combined_spec/COUP_394_cleaned_combo_meg_abs1.rmf read background file combined_spec/COUP_394_cleaned_combo_meg_abs1_bkg.pha
#the sherpa ids of the combined datasets
combined_specs = [1,2,3,4]
#set the analysis to wavelength so we can plot in Angstrom
for i in combined_specs:
set_analysis(i, 'wave')
#bin the counts up for visualization purposes
bin_num_comb=3
for i in combined_specs:
group_width(i, bin_num_comb)
#setup the combined arm labels for plotting
arm_label_comb = ['three obsIDs +1/-1 combined heg','three obsIDs +1/-1 combined meg']
plt.figure(figsize=(9, 5))
#plot the original (confused) HEG and MEG spectra in red
plot("data", 1, "data", 2, color='red',label=['confused','confused'],
yerrorbars=False, linestyle='-', marker=None)
#overplot the new 'cleaned' HEG and MEG spectra in blue
plot("data", 3, "data", 4, color='blue',label=['cleaned','cleaned'],
overplot=True, yerrorbars=False, linestyle='-', marker=None)
# set the same x-limits for all panels:
axes = plt.gcf().axes
for ax, arm in zip(axes,arm_label_comb):
ax.set_xlim(0,20)
ax.set_title(arm)
#label the figure with color
fig = plt.gcf()
fig.text(0.30, 0.96, "Confused", ha='left', va='center', color='red', fontsize=16)
fig.text(0.425, .96, "vs", va='center', color='black', fontsize=16)
fig.text(0.565, .96, "Cleaned", ha='right', va='center', color='blue', fontsize=16)
plt.draw()
plt.subplots_adjust(left=0, hspace=0.5, wspace=0.3)
dataset 1: 1:21.48 Wavelength (Angstrom) dataset 2: 1:41.96 Wavelength (Angstrom) dataset 3: 1:21.48 Wavelength (Angstrom) dataset 4: 1:41.96 Wavelength (Angstrom) dataset 1: 1:21.48 Wavelength (Angstrom) (unchanged) dataset 2: 1:41.96 Wavelength (Angstrom) (unchanged) dataset 3: 1:21.48 Wavelength (Angstrom) (unchanged) dataset 4: 1:41.96 Wavelength (Angstrom) (unchanged)
arm_label_comb_arf = ['three ObsIDs +1/-1 combined heg response',
'three ObsIDs +1/-1 combined meg response']
plt.figure(figsize=(9, 5))
#original ARFs
plot("arf", 1, "arf", 2, label=['confused','confused'], color='red')
#cleaned ARFs
plot("arf", 3, "arf", 4, label=['cleaned','cleaned'],color='blue', overplot=True)
axes = plt.gcf().axes
for ax, arm in zip(axes,arm_label_comb_arf):
ax.set_xlim(0, 20)
ax.set_title(arm)
plt.subplots_adjust(left=0, hspace=0.5, wspace=0.3)
As exposure time is increased with multiple ObsIDs, more emission lines are visible for COUP 394. Also note how the combined response does not go to zero because confusion does not occur at the same spot in all ObsIDs due to roll angle variations and source variability. Multiple observations can be used to mitigate confusion in crowded regions.This comparison of the original (confused) data with the cleaned data demonstrate the necessity to account for confusion from other sources in the field of view.
This also demonstrates how cleaning spectra takes away signal, which includes real data, too. In the MEG there is significant emission in the (real) Ne X emission line at 12.14 Å. However, in one of the observations a large region of the arm is potentially confused as thus filtered out (see the lower panel in the ARF plots, where the "cleaned" ARF is significantly below the "confused" ARF below 15.7 Å). This confusion is relevant for large ranges of that spectrum, in particular in regions where the source is weak. However, at 12.14 Å the source is so strong that is outshines any confusion, yet the real counts are also removed when the entire region is masked as "potentially confused" by crisscross. In that sense, crisscross is desined to be conservative and to mask all regions with potential confusion, but it is possible that this also removes counts in areas where the source is so bright that the relative contribution of the confusion is not significant.
For questions, comments or suggestions about crisscross and its functionality, please contact the CXC helpdesk or through email at cxchelp@cfa.harvard.edu.
Download the notebook.