def do_specfit(filename, lines=["NIIa + Halp + NIIb", "Hbet", "Hgam", "Hdel", "Ca H + Ca K", "Mg", "NaI", "OIIIa + OIIIb"], fix_lambda=True, smooth_size=3, verbose=False, verbose_print=True): ''' Given a FITS file, fit the specified lines to the spectra. ''' if verbose_print: print "Running on " + filename spec_file = fits.open(filename) flux = spec_file[1].data["flux"] # Correct for redshift lam_wav = 10**spec_file[1].data["loglam"] / (1 + spec_file[2].data["Z"]) # Correct for vacuum-to-air lam_wav = vac_to_air(lam_wav) # Optionally smooth the spectrum if smooth_size != 0: flux = median_filter(flux, smooth_size) spec = Spectrum(data=flux, xarr=lam_wav, header=spec_file[1].header, unit="", xarrkwargs={'unit': 'Angstroms'}) line_params = [] line_errs = [] for i, line in enumerate(lines): line_props = line_dict[line] num_lines = len(line_props) / 3 spec_line = spec.slice(start=line_props[1]-100, stop=line_props[-2]+100, units='Angstroms') if verbose: spec_line.plotter() spec_line.baseline(order=1, subract=False, annotate=False) # Estimate the amplitude of the line after background subtraction # using the peak of where the line is expected to be baseline_model = lambda x, p: p[1] + p[0]*x base_params = spec_line.baseline.baselinepars for i in range(num_lines): lam_line = find_nearest(lam_wav, line_props[3*i+1]) line_props[3*i] = float(flux[np.argwhere(lam_wav == lam_line)]) - \ baseline_model(lam_line, base_params) if fix_lambda: fix = [False, True, False] * num_lines else: fix = [False] * len(line_props) if num_lines > 1: multifit = True else: multifit = False try: spec_line.specfit(guesses=line_props, fixed=fix, fittype="gaussian", multifit=multifit) line_pars = spec_line.specfit.modelpars line_errors = spec_line.specfit.modelerrs except ValueError: line_pars = [0.0] * 3 * num_lines line_errors = [0.0] * 3 * num_lines except mpfitException: line_pars = [np.NaN] * 3 * num_lines line_errors = [np.NaN] * 3 * num_lines # For multifits, a bad fit for a line that isn't there increases the # error on a fit for a line that is. Remove # if num_lines > 1: # t_stats = [np.logical_and(np.abs(k)/j < 1, j != 0) # for k, j in zip(line_pars, line_errors)] # if np.any(t_stats): # posn = [f for f, j in enumerate(t_stats) if j] # bad_line = [] # for pos in posn: # for n in range(1, num_lines+1): # if pos < 3*i and pos > 3*(i-1): # bad_line.append(n) # break # # If they're both bad, just continue # if len(bad_line) == num_lines: # pass # else: # bad_line = np.sort(bad_line)[::-1] # for bad in bad_line: # bad = int(bad) - 1 # for p in range(3)[::-1]: # line_props.pop(3*bad + p) # fix.pop(3*bad + p) # if (num_lines - len(bad_line)) == 1: # multifit = False # spec_line.specfit(guesses=line_props, # fixed=fix, # fittype="gaussian", # multifit=multifit) # good_line = \ # np.asarray(list(set(range(1, num_lines+1)) & set(bad_line))) # good_line -= 1 # for good in good_line: # line_pars[3*(good-1):3*good] = spec_line.specfit.modelpars # line_errors[3*(good-1):3*good] = spec_line.specfit.modelerrs line_params.extend(line_pars) line_errs.extend(line_errors) if verbose: raw_input("Continue?") rows = len(line_params) / 3 line_params = np.array(line_params).reshape((rows, 3)) line_errs = np.array(line_errs).reshape((rows, 3)) # No point in saving lambda if it is fixed. if fix_lambda: line_params = np.hstack([line_params[:, 0], line_params[:, -1]]) line_errs = np.hstack([line_errs[:, 0], line_errs[:, -1]]) line_param_names = ['Amplitude', 'Width', 'Amplitude Error', 'Width Error'] line_names = [] for line in lines: line = line.split(" + ") if len(line) > 1: for l in line: line_names.append(l) else: line_names.append(line[0]) line_and_par_names = [] for par in line_param_names: for name in line_names: line_and_par_names.append(name+" "+par) # Return as a named series, which can be concatenated into a dataframe. ser = Series(np.hstack([line_params, line_errs]).ravel(), index=line_and_par_names) return ser