;+
;PROCEDURE: THM_SST_REMOVE_SUNPULSE
;Purpose:
; Routine to perform a variety of calibrations on full distribution sst data.
; These can remove sun contamination and on-board masking. They can also scale
; the data to account for the loss of solid angle from the inability of the sst
; to measure directly along the probe geometric Z axis and the inability to measure
; directly along the probe geometric xy plane.(ie X=0,Y=0,Z = n or X=n,Y=m,Z=0, are SST 'blind spots')
; THM_REMOVE_SUNPULSE routine should not generally be called directly.
; Keywords to it will be passed down from higher level routines such as, thm_part_moments,
; thm_part_moments2, thm_part_dist,thm_part_getspec, thm_sst_psif, and thm_sst_psef
;
; Arguments:
; dat: the dat structure used in thm_part_dist, etc...
;
; Keywords:
;
; method_clean: Simplified sun cleaning methods:
; Allowed options are:
; 'manual': To use this option you need to specify the contaminated bins using the sun_bins keyword. Specified bins are removed and interpolated over phi.
;
; 'automatic': This method attempts to find contaminated bins using a statistical outlier test(modified z-score)
; then fills the bins by interpolation over phi.
;
; sun_bins: method_clean manual removal bin selection. Must be either a 64 element or a 16x64 element 0-1 array. Bins set to 1 will be used, bins set to 0
; will be removed and filled.
; The 64 element input is the same format as the output from edit3dbins, it removes and fills all energies at a particular angle of measure.
; The 16x64 element input allows the user to remove some bins at some of the 16 energies but to keep others.
;
;
; all_angle_median: set this option to replace the angular distribution with the median
; of the data calculated over the all angles(thetas & phis) for each energy.
; This will generally eliminate contamination in some of the moments, but will make
; analysis of angular plots impossible. It will also eliminate the velocity
; moment.
; scale_sphere: set this option to increase the value of all counts by 16%. This accounts
; for the loss of phase space mentioned above.
;
; method_sunpulse_clean: set this to a string: Either 'median' or 'spin_fit' or 'z_score_mod'
; 'median': This will remove all points that are greater
; than 2.0 standard deviations from the median.By default they will be filled by a
; linear interpolation across the phi angle by default.
; 'spin_fit': This will remove all points that are greater
; than 2.0 standard deviations from a spin fit across phi angle. The equation used to
; fit is A+B*sin(phi)+C*cos(phi). By default these points will be filled by a linear
; interpolation across the phi angle. The fitting is done using the svdfit routine
; from the idl distribution.
; 'z_score_mod': This will remove all points that have a modified z-score(calculated across phi) greater than 3.5
; The modified z-score is a normalized outlier detection test defined as follows:
; #1 X_Bar = median(X+1)
; #2 Sigma = MAD = Median Absolute Deviation = median(abs(X-X_Bar))
; #3 Z_Score_Mod = .6745*(X - X_Bar)/Sigma
; This test can often get excellent results because it is insensitive to variation in standard deviation
; and skew in the distributions.
;
; limit_sunpulse_clean: set this equal to a floating point number that will override the default of 2.0 standard
; deviation tolerance or 3.5 z_score_tolerance, used by the sunpulse cleaning methods by default.
; This keyword will only have an effect if the method_sunpulse_clean keyword is set.
;
; fillin_method: Set this keyword to a string that specifies the method used to fill the points that are
; removed via the method_sunpulse_clean or the mask_remove keywords, and bins removed when enoise_remove_method='fill'.
; 'interpolation': this routine will interpolate across the phi angle. This is the
; default behavior. Interpolation is done using the interp_gap routine.
; 'spin_fit': this routine will perform a spin fit to the data after the points
; have been removed using the equation A+B*sin(phi)+C*cos(phi). It will then generate
; expected values for each removed phi using the equation of fit. The fitting is done using
; the svdfit routine from the idl distribution. Note that if 'spin_fit' is selected for
; the clean method and the fill method, this routine will perform two spin fits.
; 'blank' : Just removes the data but doesn't fill with anything
;
;
; mask_remove: Set this keyword to the proportion of values that must be 0 at all energies to determine that a mask is present.
; Generally .99 or 1.0 is a good value. The mask is a set of points that are set to 0 on-board the spacecraft.
; By default they will be filled by linear interpolation across phi. NOTE: This argument is not actually accepted by
; this routine, it is only documented here. If you provide this argument to thm_part_moments,
; thm_part_moments2, or thm_part_getspec, those routines will appropriately set the value for the
; mask_tot keyword to this routine.
;
; 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. NOTE: This argument is not actually accepted by
; this routine, it is only documented here. If you provide this argument to thm_part_moments,
; thm_part_moments2, or thm_part_getspec, those routines will appropriately set the value for the
; enoise_tot keyword to this routine.
;
; enoise_bgnd_time: 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.NOTE: This argument is not actually accepted by
; this routine, it is only documented here. If you provide this argument to thm_part_moments,
; thm_part_moments2, or thm_part_getspec, those routines will appropriately set the value for the
; enoise_tot keyword to this routine.
;
;
;
; 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 use
; the average of these values across the time interval for each bin/energy combination.
;
; 'fit_median' : Fill in selected bins with a value that is interpolated across phi, then use
; the mean of these values across the time interval for each bin/energy combination.
;
; 'fill': Fill in selected bins across phi, do not perform any subtraction. This will
; provide the cleanest looking plot, but the signal in that bin will be entirely removed.
;
;
; NOTE: This argument is not actually accepted by
; this routine, it is only documented here. If you provide this argument to thm_part_moments,
; thm_part_moments2, or thm_part_getspec, those routines will appropriately set the value for the
; enoise_tot keyword to this routine.
;
; 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).
;
; mask_tot: The user should never manually set this keyword. thm_part_moments,thm_part_moments2, & thm_part_getspec
; will properly set this keyword, if the mask_remove keyword is set when they are called.
;
; enoise_tot: The user should never manually set this keyword. thm_part_moments,thm_part_moments2, & thm_part_getspec
; will properly set this keyword, if enoise keywords are set when called.
;
;
;
;
;
; Examples:
;
; thm_part_moments,probe='a',inst='ps?f',method_clean='automatic'
;
; sun_bins=dblarr(64)+1
; sun_bins[[30,39,40,46,55,56,61,62]] = 0 ;these are the bin numbers that will be removed
; thm_part_moments,probe='a',inst='ps?f',sun_method='manual',sun_bins=sun_bins
;
; thm_part_moments,probe='a',instrum=['ps?f'],mag_suffix='_peif_magf',scpot_suffix='_peif_sc_pot',moments='*', $
; ,fillin_method='spin_fit',method_sunpulse_clean='spin_fit',limit_sunpulse_clean=1.8, $
; trange=['2008-05-19','2008-05-20'],tplotsuffix='_fit_mask_fit'
;
; thm_part_getspec, probe='a', trange=['2007-03-23','2007-03-23'],theta=[0,45], data_type='ps?f', angle='phi', $
; erange=[50000,100000], /mask_remove,method_sunpulse_clean='median',limit_sunpulse_clean=1.5, $
; suffix='_fit_mask_med_t2'
;
; edit3dbins,thm_sst_psef(probe='a', time_double('2008-03-01'),method_sunpulse_clean='spin_fit', $
; limit_sunpulse_clean=1.2),ebins=4,sum_ebins=1
;
;
;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_erange_bin_val.pro
;
; $LastChangedBy: pcruce $
; $LastChangedDate: 2012-08-10 09:42:49 -0700 (Fri, 10 Aug 2012) $
; $LastChangedRevision: 10800 $
; $URL: svn+ssh://thmsvn@ambrosia.ssl.berkeley.edu/repos/spdsoft/trunk/projects/themis/spacecraft/particles/SST/thm_sst_remove_sunpulse.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
;function to use in svd fitting for sunpulse correction
function svd_func,x,m
compile_opt idl2,hidden
return,[[1.0],[sin(x*!DTOR)],[cos(x*!DTOR)]]
end
;HELPER function
;performs filling using an svd fit
function do_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
;fills nan's using the nearest neighbor method
;function fill_nn,x,y
; idx = where(finite(y))
; y_nan = y[idx]
; x_nan = x[idx]
; int = interpol(findgen(n_elements(idx)),x_nan,x)
; return,y_nan[round(int)]
;end
function thm_sst_remove_sunpulse,dat, $
all_angle_median=all_angle_median, $
scale_sphere=scale_sphere, $
method_sunpulse_clean=method_sunpulse_clean, $
limit_sunpulse_clean=limit_sunpulse_clean, $
fillin_method=fillin_method, $
mask_tot=mask_tot, $
enoise_tot=enoise_tot, $
badbins2mask=badbins2mask,$
method_clean=method_clean,$ ;simplified logic for sun contamination removal
sun_bins=sun_bins,$
no_sun=no_sun, $
err_msg=err_msg, msg_suppress=msg_suppress
compile_opt idl2
;prevents double calculation during recursive calls
if keyword_set(no_sun) then return,dat
if keyword_set(method_clean) then begin
;if not set, automatic and manual contamination methods will use interpolation by default
if ~keyword_set(fillin_method) && ~is_string(fillin_method) then begin
fillin_method = 'interpolation'
endif
if strlowcase(method_clean) eq 'automatic' then begin
method_sunpulse_clean = 0 ;prevent interaction between multiple methods of contamination removal
endif else if (method_clean) eq 'manual' then begin
if undefined(sun_bins) then begin
err_msg = 'Warning: Sun contamination removal set to manual but sun bins is not set'
if ~keyword_set(msg_suppress) then dprint, dlevel=1, err_msg
return,dat
endif else begin
dim = dimen(sun_bins)
enoise_tot = dblarr(16,64)
if n_elements(dim) eq 1 then begin
idx = where(sun_bins eq 0,c)
if c gt 0 then begin
enoise_tot[*,idx] = replicate(!VALUES.D_NAN,16,c)
endif else begin
err_msg = 'Warning: No sun contamination bins selected, but sun contamination removal is turned on'
if ~keyword_set(msg_suppress) then dprint, dlevel=1, err_msg
endelse
endif else if n_elements(dim) eq 2 then begin ;select sun contaminated bins by angle and energy
idx = where(sun_bins eq 0,c)
if c gt 0 then begin
enoise_tot[idx] = !VALUES.D_NAN
endif else begin
err_msg = 'Warning: No sun contamination bins selected, but sun contamination removal is turned on
if ~keyword_set(msg_suppress) then dprint, dlevel=1, err_msg
endelse
endif
endelse
endif
endif
;************************************
;This code sets some defaults & validates inputs
;***********************************
if keyword_set(limit_sunpulse_clean) && is_num(limit_sunpulse_clean,/real) then begin
deviation = limit_sunpulse_clean
z_score_val = limit_sunpulse_clean
endif else begin
deviation = 2.0
z_score_val = 3.5
endelse
if keyword_set(method_sunpulse_clean) && is_string(method_sunpulse_clean) then begin
if strmatch(strlowcase(method_sunpulse_clean),'median') then begin
median = deviation
endif else if strmatch(strlowcase(method_sunpulse_clean),'spin_fit') then begin
spin_fit = deviation
endif else if strmatch(strlowcase(method_sunpulse_clean),'z_score_mod') then begin
zvar_mod = z_score_val
endif else begin
err_msg = 'method_sunpulse_clean unrecognized, probable typo in input: ' + method_sunpulse_clean
if ~keyword_set(msg_suppress) then dprint, dlevel=1, err_msg
return, dat
endelse
endif
if keyword_set(fillin_method) && is_string(fillin_method) then begin
if strmatch(strlowcase(fillin_method),'spin_fit') then begin
spin_fill = 1
endif else if strmatch(strlowcase(fillin_method),'blank') then begin
blank_fill = 1
endif else if ~strmatch(strlowcase(fillin_method),'interpolation') then begin
msg = 'fillin_method unrecognized, probable typo in input: ' + fillin_method
err_msg = keyword_set(err_msg) ? [err_msg,msg]:msg
if ~keyword_set(msg_suppress) then dprint, dlevel=1, msg
endif
endif
dat_out = dat ;don't mutate the original
;**********************************************
;This code substracts the electronic noise
;*********************************************
if keyword_set(enoise_tot) && enoise_tot[0] ne -1 then begin
dat_out.data -= enoise_tot
;Start removal here, to verify averages of enoise subtraction
; idx = where(dat_out.data lt 0,c)
;
; if c gt 0 then begin
; dat_out.data[idx] = 0
; endif
;Stop removal here
endif
;****************************************************
;median keyword: kluge to remove sunpulse corruption
;This is an old option that sets all angles to the median over angle
;Better methods are now available
;****************************************************
if keyword_set(all_angle_median) && ndimen(dat_out.data) eq 2 then begin
dim_med = dimen(dat_out.data)
val_med = rebin(median(dat_out.data,dimen=2),dim_med)
dat_out.data = val_med
endif
;*****************************************************
;This code removes the masking, using the value passed
;via mask_tot
;*****************************************************
if keyword_set(mask_tot) && mask_tot[0] ne -1 && ndimen(dat_out.data) eq 2 then begin
mask_ang_idx = thm_sst_dat2angsplit(dat_out) ; get indices to convert 16*64->16*4*16
mask_dat = dat_out.data[mask_ang_idx] ; get data from main stucture
mask_phi = dat_out.phi[mask_ang_idx]
mask_tots = mask_tot[mask_ang_idx]
mask_phi[*,[0,2],0:7] -= 360 ; make phi's monotonic
idx = where(mask_tots eq 0)
if idx[0] ne -1 then begin
for i=0, n_elements(idx)-1 do begin
en_idx = idx[i] mod 16 ; calculate dimensional indices from linear index
th_idx = (idx[i] / 16) mod 4
ph_idx = (idx[i] / 64)
mask_dat[en_idx,th_idx,ph_idx] = !VALUES.F_NAN
endfor
endif
mask_inv_ang_idx = bsort(mask_ang_idx) ;calculate indices to reverse transformation
dat_out.data = mask_dat[mask_inv_ang_idx] ;store result
endif
;****************************************************************
;outliers keyword: remove sunpulse contamination by replacing the top n
;angles in the array with an svd fit
;This code removes the sun contamination using a certain distance from a central value
;Several different methods are available
;****************************************************************
if (keyword_set(median) || keyword_set(spin_fit) || keyword_set(zvar_mod)) && ndimen(dat_out.data) eq 2 then begin
rem_ang_idx = thm_sst_dat2angsplit(dat_out) ; get indices to convert 16*64->16*4*16
rem_dat = dat_out.data[rem_ang_idx]
rem_phi = dat_out.phi[rem_ang_idx]
rem_phi[*,[0,2],0:7] -= 360 ; make phi's monotonic (sometimes they range from 180-360 degrees then 0-180, this makes them go from -180,180
for j=0L,16-1 do begin ; loop over energy
for k=0L,4-1 do begin ; loop over theta
rem_var = reform(rem_dat[j,k,*]) ;store this loop's row of values
rem_var_phi = reform(rem_phi[j,k,*]) ;store this loop's row of phis
if keyword_set(median) then begin
rem_med = median(rem_var) ;calculate statistics
rem_dev = sqrt(total((rem_var-rem_med)^2,/nan)/16)
;rem_min = rem_med-remove_std*rem_dev
rem_min = 0
rem_max = rem_med+median*rem_dev
rem_good = where(rem_var ge rem_min and rem_var le rem_max) ;identify good values
rem_bad = ssl_set_complement(rem_good,indgen(16))
if rem_bad[0] ne -1 then begin
rem_dat[j,k,rem_bad] = !VALUES.F_NAN ;store result
endif
endif else if keyword_set(spin_fit) then begin
idx = where(finite(rem_var))
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(rem_var_phi[idx],rem_var[idx],a=[1,1,1],function_name='svd_func',status=rem_s) ;fit the data
!EXCEPT = except_val
if rem_s eq 0 then begin
yfit = result[0] + result[1]*sin(rem_var_phi*!DTOR)+result[2]*cos(rem_var_phi*!DTOR)
ydev = stddev(yfit,/nan)
ymin = 0
ymax = yfit+spin_fit*ydev
ygood = where(rem_var ge ymin and rem_var le ymax) ;identify good values
ybad = ssl_set_complement(ygood,indgen(16))
if ybad[0] ne -1 && ygood[0] ne -1 then begin
rem_dat[j,k,ybad] = !VALUES.F_NAN ;store result
endif
endif
endif else if keyword_set(zvar_mod) then begin
zvar_med = median(rem_var+1) ;calculate statistics
zvar_mad = median(abs(rem_var - zvar_med))
zvar_var = .6745*(rem_var - zvar_med)/zvar_mad
zvar_good = where(zvar_var le zvar_mod)
zvar_bad = ssl_set_complement(zvar_good,indgen(16))
if zvar_bad[0] ne -1 then begin
rem_dat[j,k,zvar_bad] = !VALUES.F_NAN ;store result
endif
endif
endfor
endfor
;replace data
rem_inv_ang_idx = bsort(rem_ang_idx)
dat_out.data = rem_dat[rem_inv_ang_idx]
endif
;**************************************************
;perform fill, this is done in a separate step so that it can be done simulataneously for all modifications
;This code fills via interpolation of svd_fit
;**************************************************
idx = where(~finite(dat_out.data))
if idx[0] ne -1L then begin
if ~keyword_set(blank_fill) || keyword_set(spin_fill) then begin
fill_ang_idx = thm_sst_dat2angsplit(dat_out) ; get indices to convert 16*64->16*4*16
fill_dat = dat_out.data[fill_ang_idx]
fill_dat_ref = dat.data[fill_ang_idx] ; used as a reference to leave data unchanged if filling does not work
fill_phi = dat_out.phi[fill_ang_idx]
fill_bins = dat_out.bins[fill_ang_idx]
fill_phi[*,[0,2],0:7] -= 360 ; make phi's monotonic (sometimes they range from 180-360 degrees then 0-180, this makes them go from -180,180
for j=0L,16-1 do begin ; loop over energy
for k=0L,4-1 do begin ; loop over theta
fill_var = reform(fill_dat[j,k,*]) ;store this loop's row of values
fill_var_ref = reform(fill_dat_ref[j,k,*])
fill_var_phi = reform(fill_phi[j,k,*]) ;store this loop's row of phis
triple_fill_var = [fill_var,fill_var,fill_var] ;triple the data points to ensure that the fit wraps around the phi rotation
triple_fill_ref = [fill_var_ref,fill_var_ref,fill_var_ref]
triple_fill_phi = [fill_var_phi-360,fill_var_phi,fill_var_phi+360]
if keyword_set(spin_fill) then begin
triple_fill_var = do_spin_fill(triple_fill_var,triple_fill_ref,triple_fill_phi)
; dprint,'Spin Fill'
endif else begin
interp_gap,triple_fill_phi,triple_fill_var
; dprint,'Interp Fill'
endelse
idx = where(~finite(triple_fill_var[16:31]),cnt)
if cnt ne 0 then begin
fill_dat[j,k,*] = 0
fill_bins[j,k,*] = 0 ;if all bins got flagged as contaminated, disable those bins when constructing moments and spectra so that they don't contaminate the data
endif else begin
fill_dat[j,k,*] = triple_fill_var[16:31] ;store result (only the center element of the triple)
endelse
endfor
endfor
;idx = where(~finite(fill_dat),cnt)
; if cnt ne 0 then stop
;replace data
fill_inv_ang_idx = bsort(fill_ang_idx)
dat_out.data = fill_dat[fill_inv_ang_idx]
dat_out.bins = fill_bins[fill_ang_idx]
endif else begin
; dprint,'Blank Fill'
dat_out.bins[idx] = 0 ;if data blanking is set, disable NAN bins
dat_out.data[idx]= 0 ;since data doesn't tolerate nans
endelse
endif
if keyword_set(badbins2mask) then begin
;Zero out the bins
bad_ang = where(badbins2mask eq 0)
dat_out.bins[*,bad_ang] = 0
dat_out.data[*,bad_ang] = !VALUES.D_NAN
endif
;*****************************************************************************
;scale the input to account for the region of the sphere that is not covered
;This code scales the value for the loss of solid angle, if requested
;*****************************************************************************
if keyword_set(scale_sphere) && ndimen(dat_out.data) eq 2 then begin
dat_out.data *= 1.16
endif
return,dat_out
end