Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
3 changes: 3 additions & 0 deletions stix/dbase/demo/component_imaging/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This folder will contain the data FITS files needed to run the Spectral Component Imaging demonstration script stx_spectral_component_imaging_demo.pro.
The folder will usually be empty on initial installation of the STIX software with the files downloaded when the demo is run.
The OSPEX output files for the demo are already in this file.
213 changes: 213 additions & 0 deletions stix/idl/demo/stx_spectral_component_imaging_demo.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
;---------------------------------------------------------------------------
;+
; :project:
; STIX
;
; :name:
; stx_spectral_component_imaging_demo
;
; :description:
; This demonstration script shows how to use the routine stx_spectral_component_imaging. Spectral component imaging is a method of imaging individual
; spectral components rather than imaging in energy ranges. The routine is based on the paper: "Spectral component imaging of solar X-ray flares", Stiefel et al. (2025)
;
; :categories:
; demo, imaging
;
; :history:
; March-2026, Stiefel, M; first release
;-

; *******************************************
; ************ PREPARE THE DATA *************
; *******************************************

;;******************************** LOAD DATA - Mai 16 2023, 17:20 UT ********************************

; Folder in which the files downloaded for this demonstration are stored
default, out_dir, concat_dir( getenv('STX_DEMO_DATA'),'component_imaging', /d)

; UID of the science fits file to be dowloaded from the website
uid_sci_file = '2305162947'
path_sci_file = stx_get_science_fits_file(uid_sci_file, out_dir=out_dir)

; UID of the background fits file to be dowloaded from the website
uid_bkg_file = '2305176702'
path_bkg_file = stx_get_science_fits_file(uid_bkg_file, out_dir=out_dir)

;;*********************************** SET TIME AND ENERGY RANGES ***********************************

; Time range to be selected for image reconstruction
time_range = ['16-May-2023 17:20:50','16-May-2023 17:21:10']
; Set energy ranges that should be used for component imaging
energy_ranges = [[7,8],$
[8,9],$
[9,10],$
[10,11],$
[11,12],$
[12,13],$
[13,14],$
[14,15],$
[15,16],$
[16,18],$
[18,20],$
[20,22],$
[22,25],$
[25,28],$
[28,32],$
[32,36],$
[36,40],$
[40,45],$
[45,50]]

;********************************* DOWNLOAD L2 EPHEMERIS FITS FILE ***********************************
; see imaging demo for more details

aux_fits_file = stx_get_ephemeris_file(time_range[0], time_range[1], out_dir=out_dir)
aux_data = stx_create_auxiliary_data(aux_fits_file, time_range)


;********************************* GET FLARE LOCATION ***********************************
stx_estimate_flare_location, path_sci_file, time_range, aux_data, flare_loc=flare_loc, $
path_bkg_file=path_bkg_file
mapcenter = stx_hpc2stx_coord(flare_loc, aux_data)
xy_flare = mapcenter

; Angle between Solar Orbiter - Flare to flare normal
angle_sfn = 36.

; Get the SO-Sun distance and the difference in the light travel time
stx_get_header_corrections, path_sci_file, distance = distance, time_shift = time_shift

stop


; *******************************************
; ************ SPECTRAL FITTING *************
; *******************************************

; remove ";" below for fitting
; for the DEMO, the OSPEX fitting outputs are already saved in "Ospex_Output.sav" in the folder

;; Call OSPEX for fitting
;stx_convert_pixel_data, $
; fits_path_data = path_sci_file,$
; fits_path_bk = path_bkg_file, $
; distance = distance, $
; time_shift = time_shift, $
; flare_location_stx = xy_flare, $
; background_data = background_data, $
; ospex_obj = ospex_obj
;
;ospex_obj->set, spex_source_angle = angle_sfn
;ospex_obj->set, spex_fit_time_interval=anytim(['16-May-2023 17:20:48','16-May-2023 17:20:56'])+time_shift
;ospex_obj->set, fit_function='vth+thick2+albedo' ; careful: order of models does matter for later! If different order, code needs to be adapted below

stop

; save the OSPEX output files
;param_ospex = ospex_obj-> get(/spex_summ)
;spectrum_fits = ospex_obj-> calc_func_components(spex_unit='rate')
;save,param_ospex,spectrum_fits,filename=out_dir+'\Ospex_Output.sav'

stop
; *******************************************
; *******************************************
; ****** Export spectral parameters *********
; *******************************************
; *******************************************

; Here we used two models (thermal and nonthermal) and an albedo component
; In case more models or no albedo component were used, the code needs to be adapted accordingly
restore, out_dir + "\Ospex_Output.sav"

; Read out spectrum parameters from OSPEX
e_axis_ospex = average(param_ospex.spex_summ_energy,1)
e_bin_width = param_ospex.spex_summ_energy[1,*]-param_ospex.spex_summ_energy[0,*]
stix_spectrum = param_ospex.SPEX_SUMM_CT_RATE/e_bin_width

; Read out fitting models from OSPEX
; Total fit
total_fit = spectrum_fits.yvals[*,0]/e_bin_width

; Single models (s-1 keV-1) ****** ADAPT IF DIFFERENT MODELS USED ******
model_1 = spectrum_fits.yvals[*,1]/e_bin_width ; here: thermal
model_2 = spectrum_fits.yvals[*,2]/e_bin_width ; here: nonthermal
albedo_fit = spectrum_fits.yvals[*,3]/e_bin_width ; here: albedo

; prepare matrices for spectral component imaging
wanted_e_ranges = mean(energy_ranges,dimension=1)
F_tot = fltarr(n_elements(wanted_e_ranges))
frac = fltarr(2,n_elements(wanted_e_ranges)) ; 2: number of models, ****** ADAPT, IF MORE THAN 2 ******
for i=0,(n_elements(wanted_e_ranges)-1) do begin
ind = where(e_axis_ospex EQ wanted_e_ranges[i])
ind = ind[0]

F_tot[i] = stix_spectrum[ind]
frac[0,i] = model_1[ind]/(total_fit[ind]-albedo_fit[ind])
frac[1,i] = model_2[ind]/(total_fit[ind]-albedo_fit[ind])
endfor
cm_value = 0.096*8*24*0.25 ;(not very nice correction for area)
F_tot = F_tot/cm_value

stop

; *******************************************
; ***** RUN SPECTRAL COMPONENT IMAGING ******
; *******************************************

; define subcollimators
subc_index = stx_label2ind(['10a','10b','10c','9a','9b','9c','8a','8b','8c','7a','7b','7c',$
'6a','6b','6c','5a','5b','5c','4a','4b','4c','3a','3b','3c'])

; run the spectral component imaging routine
; Output: visibilities for the spectral components, here: thermal + nonthermal
vis_result = stx_spectral_component_imaging(path_sci_file, path_bkg_file, mapcenter, time_range, energy_ranges, F_tot, frac, $
sumcase='TOP+BOT', chi2_vis=chi2_vis, chi2_e_channel=chi2_e_channel, vis_array=vis_array, sigamp_array=sigamp_array)

vis_A = vis_result[*,0]
vis_B = vis_result[*,1]

stop

; *******************************************
; ********** IMAGING AND PLOTTING ***********
; *******************************************

;Image size
imsize = [251,251] ;[251,251] [129,129]
pixel = [2.,2.]*0.5

; Reconstruct images
mem_ge_map_A=stx_mem_ge(vis_A,imsize,pixel,aux_data)
mem_ge_map_B=stx_mem_ge(vis_B,imsize,pixel,aux_data)

stx_plot_fit_map, mem_ge_map_A, this_window=10
stx_plot_fit_map, mem_ge_map_B, this_window=11

; comparison: reconstruct energy dependent images
vis_E1=stx_construct_calibrated_visibility(path_sci_file, time_range, [7,10], mapcenter, subc_index=subc_index, $
path_bkg_file=path_bkg_file, xy_flare=xy_flare, sumcase="TOP+BOT")
vis_E2=stx_construct_calibrated_visibility(path_sci_file, time_range, [22,28], mapcenter, subc_index=subc_index, $
path_bkg_file=path_bkg_file, xy_flare=xy_flare, sumcase="TOP+BOT")

mem_ge_map_E1=stx_mem_ge(vis_E1,imsize,pixel,aux_data)
mem_ge_map_E2=stx_mem_ge(vis_E2,imsize,pixel,aux_data)

; Plotting

levels=[20,40,60,80,90]
window,1,xsize=800,ysize=500
!P.MULTI=[0,2,1]
loadct,0
plot_map,mem_ge_map_A, title='Using Spectroscopic Imaging'
plot_map,mem_ge_map_A,/over,/per,levels=levels,c_thick=2.0
loadct,2
plot_map,mem_ge_map_B,/over,/per,levels=levels,color=100,c_thick=2.0

loadct,0
plot_map,mem_ge_map_E1, title='Using Energy Ranges'
plot_map,mem_ge_map_E1,/over,/per,levels=levels,c_thick=2.0
loadct,2
plot_map,mem_ge_map_E2,/over,/per,levels=levels,color=100,c_thick=2.0

end
152 changes: 152 additions & 0 deletions stix/idl/processing/imaging/stx_spectral_component_imaging.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
;+
;
; NAME:
;
; stx_spectral_component_imaging
;
; PURPOSE:
;
; Calculates the visibilites of the spectral components using energy dependent visibilites and spectral fitting
;
; CALLING SEQUENCE:
;
;
;
; vis = stx_spectral_component_imaging(path_sci_file, path_bkg_file, mapcenter, time_range, energy_ranges, F_tot, frac)
;
; INPUTS:
;
; path_sci_file: path to the STIX science file
;
; path_bkg_file: path to the STIX background file
;
; mapcenter: center around which visibilites are determined
;
; time_range: time range to calculate the visibilites
;
; energy_ranges: energy ranges to consider
;
; F_tot: total flux in each energy bin (counts/s/cm2/keV)
;
; frac: fractional contribution of each component to the total spectrum for each energy bin
;
; KEYWORDS:
;
; sumcase: which pixels to use, default TOP+BOT
;
; subc_index: which detectors to use, default TOP24
;
; chi2_vis: estimated chi2 of the linear system, output
;
; chi2_e_channel: estimated chi2 of linear system for each energy channel, output
;
; vis_array: array with the visibilities for each energy channel, output
;
; sigamp_array: array with the sigma on amplitudes of visibilites per energy channel, output
;
; OUTPUTS:
;
; vis_components: visibilities of the spectral components
;
;
; HISTORY: August 2025, Muriel Stiefel, created
; Ocotber 2025, Muriel Stiefel, updated
;
; CONTACT:
; muriel.stiefel@fhnw.ch
;-

function stx_spectral_component_imaging, path_sci_file, path_bkg_file, mapcenter, time_range, energy_ranges, F_tot, frac, $
sumcase=sumcase, subc_index=subc_index,$
chi2_vis=chi2_vis, chi2_e_channel=chi2_e_channel, vis_array=vis_array, sigamp_array=sigamp_array

; default values
default, sumcase, 'TOP + BOT'
default, subc_index, stx_label2ind(['10a','10b','10c','9a','9b','9c','8a','8b','8c','7a','7b','7c',$
'6a','6b','6c','5a','5b','5c','4a','4b','4c','3a','3b','3c'])

num_energy = n_elements(F_tot) ; number of energy ranges/energy dependent visibilities
n_free_vis = n_elements(subc_index) ; number of visibilities
xy_flare = mapcenter

; Loop through the energy ranges to get the energy dependent visibilites
vis_array = []
sigamp_array = []
for i=0,num_energy-1 do begin
vis = stx_construct_calibrated_visibility(path_sci_file, time_range, energy_ranges[*,i], mapcenter, subc_index=subc_index, $
path_bkg_file=path_bkg_file, xy_flare=xy_flare, sumcase=sumcase)

vis_array = [[vis_array], [vis.obsvis/(F_tot[i])]]
sigamp_array = [[sigamp_array], [vis.sigamp/(F_tot[i])]]

endfor


; prepare for solving linear system
num_elem = n_elements(frac)/num_energy
dvis_array = vis_array[*,0:(num_elem-1)]
dsigamp_array = sigamp_array[*,0:(num_elem-1)]
sig_dvis_array = COMPLEXARR(24,num_elem,num_elem)

; Solve the linear least square problem to get the spectral component visibilites
trans_frac = transpose(frac)

for i=0,23 do begin
; Construct the covariant matrix
V_inv = COMPLEXARR(num_energy,num_energy)
V = COMPLEXARR(num_energy,num_energy)
for j=0,num_energy-1 do V_inv[j,j] = complex((1./sigamp_array[i,j]^2),(1./sigamp_array[i,j]^2))
for j=0,num_energy-1 do V[j,j] = complex(sigamp_array[i,j]^2,sigamp_array[i,j]^2)

; Linear least square method on the visibilites
A = complex(invert(trans_frac ## real_part(V_inv) ## frac) ## trans_frac ## real_part(V_inv), $
invert(trans_frac ## IMAGINARY(V_inv) ## frac) ## trans_frac ## IMAGINARY(V_inv))
dvis_array[i,*] = complex(real_part(A) ## real_part(vis_array[i,*]), $
IMAGINARY(A) ## IMAGINARY(vis_array[i,*]))

; Error propagation of the visibilities
sig_dvis_array[i,*,*] = complex(real_part(A) ## real_part(V) ## transpose(real_part(A)), $
IMAGINARY(A) ## IMAGINARY(V) ## transpose(IMAGINARY(A)))

; Calculate amplitude sigma for the visibilities
for j = 0,num_elem-1 do begin
vis_real = real_part(dvis_array[i,j])
vis_im = IMAGINARY(dvis_array[i,j])
vis_real_sig = real_part(sig_dvis_array[i,j,j]) ; is squared!
vis_im_sig = IMAGINARY(sig_dvis_array[i,j,j])
dsigamp_array[i,j] = sqrt((vis_real^2/(vis_real^2+vis_im^2)) * vis_real_sig + (vis_im^2/(vis_real^2+vis_im^2))*vis_im_sig)
endfor

endfor

; Calculate the chi2 on the linear system solution
pred_vis = frac ## dvis_array ; predicted energy dependent visibilities
chi2_vis = (total(abs(real_part(vis_array) - real_part(pred_vis))^2./(sigamp_array^2.)) + $
total(abs(imaginary(vis_array) - imaginary(pred_vis))^2./(sigamp_array^2.))) /(n_free_vis*num_energy-1)

chi2_e_channel = fltarr(num_energy)
for i=0,num_energy-1 do begin
chi2_e_channel[i] = (total(abs(real_part(vis_array[*,i]) - real_part(pred_vis[*,i]))^2./(sigamp_array[*,i]^2.)) + $
total(abs(real_part(vis_array[*,i]) - real_part(pred_vis[*,i]))^2./(sigamp_array[*,i]^2.))) /(n_free_vis-1)
endfor

; Prepare visibility structure for the spectral component visibiliites
vis_structure = stx_construct_calibrated_visibility(path_sci_file, time_range, energy_ranges[*,0], mapcenter, subc_index=subc_index, $
path_bkg_file=path_bkg_file, xy_flare=xy_flare, sumcase=sumcase)
vis_components = []

for i = 0,num_elem-1 do begin
vis_comp = vis_structure

vis_comp.type = 'stx_visibility comp ' + string(i+1)
vis_comp.obsvis = dvis_array[*,i]
vis_comp.sigamp = dsigamp_array[*,i]
vis_comp.TOTFLUX = max(abs(vis_comp.obsvis))

vis_components = [[vis_components], [vis_comp]]

endfor

return, vis_components

end