00001 import _asap 00002 00003 class linefinder: 00004 """ 00005 The class for automated spectral line search in ASAP. 00006 00007 Example: 00008 fl=linefinder() 00009 fl.set_scan(sc) 00010 fl.set_options(threshold=3) 00011 nlines=fl.find_lines(edge=(50,0)) 00012 if nlines!=0: 00013 print "Found ",nlines," spectral lines" 00014 print fl.get_ranges(False) 00015 else: 00016 print "No lines found!" 00017 sc2=sc.poly_baseline(fl.get_mask(),7) 00018 00019 The algorithm involves a simple threshold criterion. The line is 00020 considered to be detected if a specified number of consequtive 00021 channels (default is 3) is brighter (with respect to the current baseline 00022 estimate) than the threshold times the noise level. This criterion is 00023 applied in the iterative procedure updating baseline estimate and trying 00024 reduced spectral resolutions to detect broad lines as well. The off-line 00025 noise level is determined at each iteration as an average of 80% of the 00026 lowest variances across the spectrum (i.e. histogram equalization is 00027 used to avoid missing weak lines if strong ones are present). For 00028 bad baseline shapes it is recommended to increase the threshold and 00029 possibly switch the averaging option off (see set_options) to 00030 detect strong lines only, fit a high order baseline and repeat the line 00031 search. 00032 00033 """ 00034 00035 def __init__(self): 00036 """ 00037 Create a line finder object. 00038 """ 00039 self.finder = _asap.linefinder() 00040 return 00041 00042 def set_options(self,threshold=1.7320508075688772,min_nchan=3, 00043 avg_limit=8,box_size=0.2,noise_box='all',noise_stat='mean80'): 00044 """ 00045 Set the parameters of the algorithm 00046 Parameters: 00047 threshold a single channel S/N ratio above which the 00048 channel is considered to be a detection 00049 Default is sqrt(3), which together with 00050 min_nchan=3 gives a 3-sigma criterion 00051 min_nchan a minimal number of consequtive channels, 00052 which should satisfy a threshold criterion to 00053 be a detection. Default is 3. 00054 avg_limit A number of consequtive channels not greater than 00055 this parameter can be averaged to search for 00056 broad lines. Default is 8. 00057 box_size A running mean/median box size specified as a fraction 00058 of the total spectrum length. Default is 1/5 00059 noise_box Area of the spectrum used to estimate noise stats 00060 Both string values and numbers are allowed 00061 Allowed string values: 00062 'all' use all the spectrum (default) 00063 'box' noise box is the same as running mean/median 00064 box 00065 Numeric values are defined as a fraction from the 00066 spectrum size. Values should be positive. 00067 (noise_box == box_size has the same effect as 00068 noise_box = 'box') 00069 noise_stat Statistics used to estimate noise, allowed values: 00070 'mean80' use the 80% of the lowest deviations 00071 in the noise box (default) 00072 'median' median of deviations in the noise box 00073 00074 Note: For bad baselines threshold should be increased, 00075 and avg_limit decreased (or even switched off completely by 00076 setting this parameter to 1) to avoid detecting baseline 00077 undulations instead of real lines. 00078 """ 00079 if noise_stat.lower() not in ["mean80",'median']: 00080 raise RuntimeError, "noise_stat argument in linefinder.set_options can only be mean80 or median" 00081 nStat = (noise_stat.lower() == "median") 00082 nBox = -1. 00083 if isinstance(noise_box,str): 00084 if noise_box.lower() not in ['all','box']: 00085 raise RuntimeError, "string-valued noise_box in linefinder.set_options can only be all or box" 00086 if noise_box.lower() == 'box': 00087 nBox = box_size 00088 else: 00089 nBox = float(noise_box) 00090 self.finder.setoptions(threshold,min_nchan,avg_limit,box_size,nBox,nStat) 00091 return 00092 00093 def set_scan(self, scan): 00094 """ 00095 Set the 'data' (scantable) to work with. 00096 Parameters: 00097 scan: a scantable 00098 """ 00099 if not scan: 00100 raise RuntimeError, 'Please give a correct scan' 00101 self.finder.setscan(scan) 00102 00103 def set_data(self, spectrum): 00104 """ 00105 Set the 'data' (spectrum) to work with 00106 Parameters: a method to allow linefinder work without setting scantable 00107 for the purpose of using linefinder inside some method in scantable 00108 class. (Dec 22, 2010 by W.Kawasaki) 00109 """ 00110 if isinstance(spectrum, list) or isinstance(spectrum, tuple): 00111 if not isinstance(spectrum[0], float): 00112 raise RuntimeError, "Parameter 'spectrum' has to be a list or a tuple of float" 00113 else: 00114 raise RuntimeError, "Parameter 'spectrum' has to be a list or a tuple of float" 00115 self.finder.setdata(spectrum) 00116 00117 def find_lines(self,nRow=0,mask=[],edge=(0,0)): 00118 """ 00119 Search for spectral lines in the scan assigned in set_scan. 00120 Parameters: 00121 nRow: a row in the scantable to work with 00122 mask: an optional mask (e.g. retreived from scantable) 00123 edge: an optional number of channels to drop at 00124 the edge of the spectrum. If only one value is 00125 specified, the same number will be dropped from 00126 both sides of the spectrum. Default is to keep 00127 all channels 00128 A number of lines found will be returned 00129 """ 00130 if isinstance(edge,int): 00131 edge=(edge,) 00132 00133 from asap import _is_sequence_or_number as _is_valid 00134 00135 if not _is_valid(edge, int): 00136 raise RuntimeError, "Parameter 'edge' has to be an integer or \ 00137 a pair of integers specified as a tuple" 00138 00139 if len(edge)>2: 00140 raise RuntimeError, "The edge parameter should have two \ 00141 or less elements" 00142 return self.finder.findlines(mask,list(edge),nRow) 00143 def get_mask(self,invert=False): 00144 """ 00145 Get the mask to mask out all lines that have been found (default) 00146 00147 Parameters: 00148 invert if True, only channels belong to lines will be unmasked 00149 00150 Note: all channels originally masked by the input mask or 00151 dropped out by the edge parameter will still be excluded 00152 regardless on the invert option 00153 """ 00154 return self.finder.getmask(invert) 00155 def get_ranges(self,defunits=True): 00156 """ 00157 Get ranges (start and end channels or velocities) for all spectral 00158 lines found. 00159 00160 Parameters: 00161 defunits if True (default), the range will use the same units 00162 as set for the scan (e.g. LSR velocity) 00163 if False, the range will be expressed in channels 00164 """ 00165 if (defunits): 00166 return self.finder.getlineranges() 00167 else: 00168 return self.finder.getlinerangesinchannels()