;+
;PROCEDURE: THM_SST_ERANGE_BIN_VAL
;
;Purpose:
; This routine generates the values that will be subtracted to remove electronic noise. The actual subtraction is done by thm_sst_remove_sunpulse.pro
; A separate value will be calculated for each combination of bin and energy, but a single value will be calculated for all times(at a particular bin/energy).
; This value will be generated using one of several different functions of the values across time. The functions can be specified by the user.
; The user should never call this routine directly, but should instead provide the appropriate arguments to thm_part_moments,thm_part_moments2, & thm_part_getspec
; These routines will guarantee that this routine is called correctly.
;
;Arguments(note these arguments are generated correctly in thm_part_moments & thm_part_moments2)
; thx: a string representing a probe prefix(ie 'tha')
;
; instrument: a string representing the instrument(ie 'psif') Note that this routine will only perform an operation if
; it is passed a string representing sst full distribution data. (ie 'psif', 'psef')
;
; times: a list of times for the sst measurements, this list is generated by thm_part_dist
;
;Keywords:
;
; method_clean: If set to 'manual', this keyword disables the enoise code, as another method will be used by thm_sst_remove_sunpulse.pro
;
; enoise_bins: A 0-1 array that indicates which bins should be used to calculate electronic noise. A 0 indicates that the
; bin should be used for electronic noise calculations. This is basically the output from the bins argument of edit3dbins.
; It should have dimensions 16x4.
;
; enoise_bgnd_times: This should be either a 2 element array or a 2xN element array(where n is the number of elements in enoise_bins).
; The arguments represents the start and end times over which the electronic background will be calculated for each
; bin. If you pass a 2 element array the same start and end times can be used for each bin. If you pass a 2xN element
; array, then the Ith bin in enoise_bins will use the time enoise_bgnd_time[0,I] as the start time and enoise_bgnd_time[1,I] as
; the end time for the calculation of the background for that bin. If this keyword is not set then electronic noise will not
; be subtracted.
;
;
;
; enoise_remove_method(default: 'fit_median') set the keyword to a string specifying the method you want to use to calculate the electronic noise that will be subtracted
; This function combines values across time. The allowable options are:
; 'min': Use the minimum value in the time interval for each bin/energy combination.
; 'average': Use the average value in the time interval for each bin/energy combination.
; 'median': Use the median value in the time interval for each bin/energy combination.
; 'fit_average': Fill in selected bins with a value that is interpolated across phi then subtracts the average of the difference
; between the interpolated value and the actual value from each selected bin/energy combination.
; 'fit_median' :Fill in selected bins with a value that is interpolated across phi then subtracts the median of the difference
; between the interpolated value and the actual value from each selected bin/energy combination.
; 'fill': Fill the selected bins using the technique specified by enoise_remove_method_fit, or interpolation by default. (note that if this method is used, enoise_bgnd_time is not required)
;
; enoise_remove_fit_method(default:'interpolation'): Set this keyword to control the method used in 'fit_average' & 'fit_median' to
; fit across phi. Options are 'interpolation' & 'spin_fit' By default, missing bins are interpolated across phi. Setting
; enoise_remove_fit_method='spin_fit' will instead try to fill by fitting to a curve of the form A+B*sin(phi)+C*cos(phi).
;
;
;SEE ALSO:
; thm_part_moments.pro, thm_part_moments2.pro, thm_part_getspec.pro
; thm_part_dist.pro, thm_sst_psif.pro, thm_sst_psef.pro,thm_crib_sst_contamination.pro
; thm_sst_find_masking.pro, thm_sst_remove_sunpulse.pro
;
; $LastChangedBy: pcruce $
; $LastChangedDate: 2014-01-03 12:10:47 -0800 (Fri, 03 Jan 2014) $
; $LastChangedRevision: 13737 $
; $URL: svn+ssh://thmsvn@ambrosia.ssl.berkeley.edu/repos/spdsoft/trunk/projects/themis/spacecraft/particles/SST/thm_sst_erange_bin_val.pro $
;
;
;-
; Moving to separate file
;;HELPER function
;;take the dat component of a structure and splits it into an array
;;ordered in terms of theta = energy*angle->energy*theta*phi
;;dimensions 16*64->16*4*16, phi is guaranteed to be contiguous but
;;not necessarily ascending(some phi may be out of phase by 180 degrees)
;;returns indices to perform this transformation
;function dat2angsplit,dat
;
; compile_opt idl2,hidden
;
; index = indgen(16,64)
; t_sort = bsort(dat.theta[0,*])
; index = index[*,t_sort]
; return,transpose(reform(index,16,16,4),[0,2,1]) ; this magic properly reforms and orders the dimensions
;
;end
;HELPER function
;performs filling using an svd fit
function do_enoise_spin_fill,dat,ref,phi
compile_opt idl2,hidden
dat2 = dat
good = where(finite(dat2))
bad = ssl_set_complement(good,indgen(n_elements(dat)))
if good[0] ne -1 && bad[0] ne -1 then begin
idx = where(dat2[good] ne 0)
if idx[0] eq -1 then begin ;if the system of equations is not-invertible because it is all zeros then add a constant offset
dat2[good] += 1
endif
except_val = !EXCEPT ;svdfit annoyingly reports a floating point error EVERY TIME IT OCCURS if you don't disable them entirely
!EXCEPT = 0
result = svdfit(phi[good],dat2[good],a=[1,1,1],function_name='svd_func',status=stat) ;fit the data
!EXCEPT = except_val
if stat eq 0 then begin
; replace values with fit points
dat2[bad] = result[0] + result[1]*sin(phi[bad]*!DTOR)+result[2]*cos(phi[bad]*!DTOR)
endif else begin
dat2[bad] = ref[bad] ;replace values with ref values to ensure there are no NaNs in the output
endelse
if idx[0] eq -1 then begin ;remove a constant offset if one was added
dat2 -= 1
endif
endif
idx = where(~finite(dat2))
return,dat2
end
function thm_sst_erange_bin_val,thx,instrument,times,enoise_bins=enoise_bins,enoise_bgnd_times=enoise_bgnd_time,enoise_remove_method=enoise_remove_method,enoise_remove_fit_method=enoise_remove_fit_method,method_clean=method_clean,limit_sunpulse_clean=limit_sunpulse_clean,_extra=ex
compile_opt idl2
;Validate inputs
if keyword_set(method_clean) && strlowcase(method_clean) eq 'manual' then begin
return,-1
endif else if (keyword_set(method_clean) && strlowcase(method_clean) eq 'automatic') then begin
if ~strmatch(instrument,'ps?f') && ~strmatch(instrument,'pseb') then begin
dprint,'Automatic sun contamination not enabled for instrument ' + instrument
return,-1
endif
trange = times ; note that this is not nec the user specified time range. Is at least a full day.
n_tm = n_elements(trange)
format = thx+'_'+instrument
if keyword_set(limit_sunpulse_clean) && is_num(limit_sunpulse_clean) then begin
zscore_max = limit_sunpulse_clean
endif else begin
zscore_max = 10 ;In standard statistical tests, this is an extremely large limit, making for a weak outlier test.
;But for sun contamination outliers generally have gigantic scores, so it shouldn't be a problem.
;What tricks the test isn't the threshold, but the heterogenous nature of the data across time.
;A better method in the future would test attenuator open and closed data separately
endelse
totalzscore = dblarr(64)
; The modified z score for each angular bin (summed over energy) is calculated for each time.
; The truncated mean of these z scores over time is then used to find outliers.
; The test is applied for all energies summed and for bin 9 separately.
; For some reason, there is sometimes a pattern of contamination the hits bin 9 only
data_arr = dblarr(n_tm,16,64)
for j = 0, n_tm-1 do begin ; loop over times
dat = thm_part_dist(format,index=j,_extra=ex,/no_sun) ;get data for this time
if is_struct(dat) then begin
data_arr[j,*,*]=dat.data/thm_sst_atten_scale(dat.atten,dimen(dat.data))
endif
endfor
totaloverenergy = total(data_arr,2)
;totaloverenergy = reform(data_arr,n_tm,16*64)
zscr_med = median(totaloverenergy+1,dimension=2); Adding 1 here prevents infinities in the case that all totaloverenergy=0
;zscr_sigma = median(abs(totaloverenergy-(zscr_med#(dblarr(64)+1))),dimension=2)
zscr_sigma = median(abs(totaloverenergy-(zscr_med#(dblarr(64)+1))),dimension=2)
zscr_arr = .6745*(totaloverenergy-zscr_med#(dblarr(64)+1))/(zscr_sigma#(dblarr(64)+1))
;totalzscore = total(zscr_arr,1)
lim1=0.1 ;all energies
lim2=0.1 ;bin 9
;sort so that we can do a truncated mean
;This removes any outliers in the z-score days due to glitches
;Glitches can create z-scores that are big enough that they throw off an entire day's average even though they represent only a few points out of 10-20k points
for j = 0,64-1 do begin
sort_idx=bsort(zscr_arr[*,j])
zscr_arr[*,j]=zscr_arr[sort_idx,j]
endfor
low_ten=floor(n_tm*lim1)
high_ten=floor(n_tm*(1-lim1))
;truncated mean removes outliers due to glitches.
averagezscore = total(zscr_arr[low_ten:high_ten,*],1,/nan)/(high_ten-low_ten)
;averagezscore=total(zscr_arr,1,/nan)/n_tm
;averagezscore = totalzscore/n_tm
zscore_bad = where(averagezscore gt zscore_max,c); not using abs value as we are really interested in values too high, not too low (masking is removed separately)
out = dblarr(16,64)
if c gt 0 then begin
dprint,'Sun Contaminated Bins Flagged: ' + strjoin(strtrim(zscore_bad,2),' '),dlevel=2
out[*,zscore_bad] = !VALUES.D_NAN ;store result
endif
;We do a separate test on energy bin 9. For a reason that is not understood, there is a pattern of sun contamination that only affects bin 9 on occassion.
;Possible explanations include idiosyncrasies of the electronics & glint off of the side of the collimator.
totaloverenergy=reform(data_arr[*,9,*])
;totaloverenergy = reform(data_arr,n_tm,16*64)
zscr_med = median(totaloverenergy+1,dimension=2); Adding 1 here prevents infinities in the case that all totaloverenergy=0
zscr_sigma = median(abs(totaloverenergy-(zscr_med#(dblarr(64)+1))),dimension=2)
zscr_arr = .6745*(totaloverenergy-zscr_med#(dblarr(64)+1))/(zscr_sigma#(dblarr(64)+1))
;totalzscore = total(zscr_arr,1)
;sort so that we can do a truncated mean
for j = 0,64-1 do begin
sort_idx=bsort(zscr_arr[*,j])
zscr_arr[*,j]=zscr_arr[sort_idx,j]
endfor
low_ten=floor(n_tm*lim2)
high_ten=floor(n_tm*(1-lim2))
;truncated mean removes outliers due to glitches.
averagezscore = total(zscr_arr[low_ten:high_ten,*],1,/nan)/(high_ten-low_ten)
;averagezscore=total(zscr_arr,1,/nan)/n_tm
;averagezscore = totalzscore/n_tm
zscore_bad = where(averagezscore gt zscore_max,c); not using abs value as we are really interested in values too high, not too low (masking is removed separately)
if c gt 0 then begin
dprint,'Earth Shine Contaminated Bins Flagged: ' + strjoin(strtrim(zscore_bad,2),' '),dlevel=2
out[*,zscore_bad] = !VALUES.D_NAN ;store result
endif
return,out
endif else if keyword_set(method_clean) then begin
dprint,'Unrecognized value for keyword method_clean: ' + method_clean
dprint,'Did you mean to use method_sunpulse_clean?"
endif
; enoise_bins rather than automatic clean
if ~keyword_set(enoise_bins) then begin
return,-1
endif
if ~strmatch(instrument,'ps?f') && ~strmatch(instrument,'pseb') then begin
dprint,'Electronic removal not enabled for instrument ' + instrument
return,-1
endif
;fix to use 0-1 array rather than indexes
enoise_bins2 = where(enoise_bins eq 0,bin_cnt)
if enoise_bins2[0] eq -1 then begin
dprint,'No bins selected for ' + thx + '/' + instrument + ' Enoise removal not applied'
return,-1
endif
if keyword_set(enoise_remove_method) && strlowcase(enoise_remove_method) eq 'fill' then begin
dprint,'Enoise Filling ' + strtrim(bin_cnt,2) + ' angle bins for ' + thx + '/' + instrument
out = dblarr(16,64) ;output array
out[*,enoise_bins2] = !VALUES.D_NAN
return,out
endif
if ~keyword_set(enoise_bgnd_time) then begin
dprint,'Cannot apply selected enoise remove method for '+ thx + '/' + instrument + '. Enoise_bgnd_time not set'
endif
d = dimen(enoise_bgnd_time)
if d[0] ne 2 then begin
dprint,'enoise_bgnd_time has illegal dimensions, electronic noise removal will not be performed'
return,-1
endif
n_bin = n_elements(enoise_bins2)
if n_elements(d) eq 1 then begin
e_times = make_array(2,n_bin,type=size(enoise_bgnd_time,/type))
e_times[0,*] = enoise_bgnd_time[0]
e_times[1,*] = enoise_bgnd_time[1]
endif else begin
e_times = enoise_bgnd_time
if d[1] ne n_bin then begin
dprint,'number of enoise_bins does not match number of enoise_times, electronic noise removal will not be performed'
return,-1
endif
endelse
;convert to numeric format if input is not numeric
if is_string(e_times) then begin
e_times = time_double(e_times)
endif
;Identify method
if ~keyword_set(enoise_remove_method) then begin
enoise_remove_method = 'fit_median'
endif
if strlowcase(enoise_remove_method) eq 'fit_median' then begin
fit_median = 1
endif else if strlowcase(enoise_remove_method) eq 'fit_average' then begin
fit_average = 1
endif else if strlowcase(enoise_remove_method) eq 'median' then begin
median = 1
endif else if strlowcase(enoise_remove_method) eq 'average' then begin
average = 1
endif else if strlowcase(enoise_remove_method) eq 'min' then begin
min = 1
endif else begin
dprint,'enoise remove method "' + enoise_remove_method + '" unrecognized, electronic noise removal will not be performed'
return,-1
endelse
if (keyword_set(min) || keyword_set(average) || keyword_set(min)) && $
keyword_set(enoise_remove_fit_method) then begin
dprint,'Enoise fit method does not have an effect if remove_method is not fit_average,fit_median, or fill'
endif
if ~keyword_set(enoise_remove_fit_method) then begin
enoise_remove_fit_method = 'interpolation'
endif
if strlowcase(enoise_remove_fit_method) eq 'interpolation' then begin
interpol_fit = 1
endif else if strlowcase(enoise_remove_fit_method) eq 'spin_fit' then begin
spin_fit = 1
endif else begin
dprint,'enoise fit method "' + enoise_remove_fit_method + '" unrecognized, electronic noise removal will not be performed'
return,-1
endelse
format = thx+'_'+instrument
out = dblarr(16,64) ;output array
for i = 0,n_bin-1 do begin ; loop over angle bins
trange = e_times[*,i] ; get times and bins
ind = where(times ge trange[0] and times le trange[1],cnt)
bin = enoise_bins2[i]
if cnt eq 0 then begin
dprint,'No valid times found for bin: ' + strcompress(string(bin))
continue
endif
n_tm = n_elements(ind) ;loop upper bound
totd = dblarr(16,n_tm) ;temporary storage of processed values
totm = dblarr(16,n_tm)
; print,'TIME!!! ',n_tm
; print,ind
; print,time_string(times[ind])
for j = 0, n_tm-1 do begin ; loop over times
dat = thm_part_dist(format,index=ind[j],_extra=ex) ;get data for this time
;copy of Jim M's quick fix, to guarantee time matchup
If(size(dat, /type) Eq 8) Then Begin
mid_time = (dat.time+dat.end_time)/2.0
trange2 = [dat.time, dat.end_time]
dat.time = mid_time
str_element, dat, 'end_time', /delete
str_element, dat, 'trange', trange2, /add_replace
Endif
if is_struct(dat) then begin
if keyword_set(fit_median) || keyword_set(fit_average) then begin
ang_split = thm_sst_dat2angsplit(dat) ;indexes to turn the data from 16x64 to 16x4x64
edat = dat
edat.data[*,enoise_bins2] = !VALUES.F_NAN ;these bins are going to be interpolated over
data = dat.data[ang_split]
edata = edat.data[ang_split]
phi = dat.phi[ang_split]
thetas = dat.theta[uniq(dat.theta,sort(dat.theta))]
theta = where(dat.theta[0,bin] eq thetas) ;which theta is the bin in question in
phi[*,[0,2],0:7] -= 360 ; make phi's monotonic
for k = 0, 16-1 do begin ;loop over energy
phis = reform(phi[k,theta,*])
edatas = reform(edata[k,theta,*])
ref_datas = reform(data[k,theta,*])
triple_phi = [phis-360,phis,phis+360]
triple_dat = [edatas,edatas,edatas]
triple_ref = [ref_datas,ref_datas,ref_datas]
if keyword_set(interpol_fit) then begin
interp_gap, triple_phi, triple_dat
endif else begin
triple_dat = do_enoise_spin_fill(triple_dat,triple_ref,triple_phi)
endelse
;triple_dat = do_spin_fill(triple_dat,triple_ref,triple_phi)
edata[k,theta,*] = triple_dat[16:31]
;data[k,theta,*] = edatas
endfor
;replace data
inv_ang_split = bsort(ang_split)
edat.data = edata[inv_ang_split]
totm[*,j] = edat.data[*,bin]
endif
totd[*,j] = dat.data[*,bin] ;store the data for this part of the time interval
endif
endfor
if keyword_set(fit_median) then begin ;perform requested processing operation
out[*,bin] = median(totd,dimension=2) - median(totm,dimension=2)
endif else if keyword_set(fit_average) then begin
out[*,bin] = average(totd,2,/nan) - average(totm,2,/nan)
endif else if keyword_set(median) then begin
out[*,bin] = median(totd,dimension=2)
endif else if keyword_set(average) then begin
out[*,bin] = average(totd,2,/nan)
endif else if keyword_set(min) then begin
out[*,bin] = min(totd,dimension=2,/nan)
endif
endfor
; idx = where(out lt 0,c)
;
; if c gt 0 then begin
; out[idx] = 0
; endif
return,out
end