def analyse_source(source, cube, plot=False, return_fit_params=False): """ Convenience method to spatially analyse a source. A 30x30 pixels 'flux map' is build from the sum of a few frames around each detected lines for the source. Two analysis are then performed: * Aperture photometry from the center of the source, to estimate a flux growth function and fit it with a custom erf function. * A Gaussian 2D fit of the PSF on the flux map This method can be used in a parallel process. Parameters ---------- source : :class:`~pandas:pandas.Series` A row from a :class:`~pandas:pandas.DataFrame` containing detected sources. Should have columns ``xpos``, ``ypos`` (Not astropy convention), ``velocity``, ``*_detected`` whare * is a line name, containing True or False for each line. cube : :class:`~ORCS:orcs.process.SpectralCube` SpectralCube instance where we are looking at the source plot : bool, Default = False (Optional) If True, the two fits are plotted return_fit_params : bool, Default = False (Optional) If True, returns the full fits parameters Returns ------- res : dict A dictionnary containing all the relevant fitted quantities. +-----------------------+---------------------------------------------------------------------------------------------------+ |Parameter |Description | +=======================+===================================================================================================+ |flux_map_ks_pvalue |Estimates the 'randomness' of the flux map, i.e if it's just noise or if we actually have a signal | +-----------------------+---------------------------------------------------------------------------------------------------+ |flux_r |Flux at different radius *r* | +-----------------------+---------------------------------------------------------------------------------------------------+ |flux_err_r |Flux error varying with *r* | +-----------------------+---------------------------------------------------------------------------------------------------+ |erf_amplitude |Amplitude estimated from erf fit | +-----------------------+---------------------------------------------------------------------------------------------------+ |erf_amplitude_err |Amplitude error | +-----------------------+---------------------------------------------------------------------------------------------------+ |erf_xfwhm |x-axis fwhm from erf fit | +-----------------------+---------------------------------------------------------------------------------------------------+ |erf_yfwhm |y-axis fwhm from erf fit | +-----------------------+---------------------------------------------------------------------------------------------------+ |erf_fwhm |Fwhm defined as *r* at which half of the max flux is reached | +-----------------------+---------------------------------------------------------------------------------------------------+ |flux_fraction_3 |Ratio between flux measured at 3 pixels from the center and max flux | +-----------------------+---------------------------------------------------------------------------------------------------+ |model_flux_fraction_15 |Ratio between estimated flux at 15 pixels from the center and max flux | +-----------------------+---------------------------------------------------------------------------------------------------+ |modeled_flux_r |Modeled flux varying with *r* | +-----------------------+---------------------------------------------------------------------------------------------------+ |psf_snr |Ratio between amplitude of the 2D fit and noise in the flux map | +-----------------------+---------------------------------------------------------------------------------------------------+ |psf_amplitude |Amplitude of the 2D fit | +-----------------------+---------------------------------------------------------------------------------------------------+ |psf_xfwhm |x-axis fwhm from 2D fit | +-----------------------+---------------------------------------------------------------------------------------------------+ |psf_yfwhm |y-axis fwhm from 2D fit | +-----------------------+---------------------------------------------------------------------------------------------------+ |psf_ks_pvalue |Randomness of the residuals map | +-----------------------+---------------------------------------------------------------------------------------------------+ """ result = {} try: from astropy.stats import sigma_clipped_stats, gaussian_sigma_to_fwhm from sitelle.constants import SN2_LINES, SN3_LINES from sitelle.region import centered_square_region from orb.utils.spectrum import line_shift from orb.core import Lines filter_name = cube.params.filter_name if filter_name == 'SN2': LINES = SN2_LINES elif filter_name == 'SN3': LINES = SN3_LINES else: raise ValueError(filter_name) ## We build a flux map of the detected lines try: detected_lines = [ line_name for line_name in LINES if source['%s_detected' % line_name.lower().replace('[', '').replace(']', '')] ] except KeyError as e: raise ValueError('No columns *_detected in the source') if detected_lines == []: return pd.Series(result) x, y = source.as_matrix(['xpos', 'ypos']).astype(int) big_box = centered_square_region(x, y, 30) medium_box = centered_square_region(15, 15, 5) small_box = centered_square_region(15, 15, 3) data = cube._extract_spectra_from_region(big_box, silent=True) mask = np.ones((30, 30)) mask[medium_box] = 0 bkg_spec = np.nanmedian(data[np.nonzero(mask)], axis=0) data -= bkg_spec axis = cube.params.base_axis spec = np.nansum(data[small_box], axis=0) line_pos = np.atleast_1d( Lines().get_line_cm1(detected_lines) + line_shift(source['velocity'], Lines().get_line_cm1(detected_lines), wavenumber=True)) pos_min = line_pos - cube.params.line_fwhm pos_max = line_pos + cube.params.line_fwhm pos_index = np.array([[ np.argmin(np.abs(axis - pos_min[i])), np.argmin(np.abs(axis - pos_max[i])) ] for i in range(pos_min.shape[0])]) bandpass_size = 0 flux_map = np.zeros(data.shape[:-1]) for line_detection in pos_index: bandpass_size += line_detection[1] - line_detection[0] flux_map += np.nansum(data[:, :, line_detection[0]:line_detection[1]], axis=-1) _, _, std_map = sigma_clipped_stats(data, axis=-1) flux_noise_map = np.sqrt(bandpass_size) * std_map #Test for randomness of the flux_map from scipy import stats result['flux_map_ks_pvalue'] = stats.kstest( (flux_map / flux_noise_map).flatten(), 'norm').pvalue #Fit of the growth function from photutils import RectangularAperture from scipy.special import erf from scipy.optimize import curve_fit try: _x0 = source['xcentroid'] - x + 15. _y0 = source['ycentroid'] - y + 15. except: _x0 = source['xpos'] - x + 15. _y0 = source['ypos'] - y + 15. flux_r = [0.] flux_err_r = [np.nanmin(flux_noise_map)] r_max = 15 r_range = np.arange(1, r_max + 1) for r in r_range: # aper = CircularAperture((_x0,_y0), r) aper = RectangularAperture((_x0, _y0), r, r, 0) flux_r.append(aper.do_photometry(flux_map)[0][0]) flux_err_r.append( np.sqrt(aper.do_photometry(flux_noise_map**2)[0][0])) flux_r = np.atleast_1d(flux_r) flux_err_r = np.atleast_1d(flux_err_r) result['flux_r'] = flux_r result['flux_err_r'] = flux_err_r try: def model(r, x0, y0, sx, sy, A): return A * erf((r / 2. - x0) / (2 * sx * np.sqrt(2))) * erf( (r / 2. - y0) / (2 * sy * np.sqrt(2))) R = np.arange(r_max + 1) p, cov = curve_fit(model, R, flux_r, p0=[0, 0, 1.5, 1.5, flux_map.max()], bounds=([-2, -2, -np.inf, -np.inf, -np.inf], [2, 2, np.inf, np.inf, np.inf]), sigma=flux_err_r, absolute_sigma=True, maxfev=10000) if (p[2] < 0) != (p[3] < 0): if p[-1] < 0: p[-1] = -p[-1] if p[2] < 0: p[2] = -p[2] elif p[3] < 0: p[3] = -p[3] if plot: f, ax = plt.subplots() ax.plot(R, model(R, *p), label='Fit') ax.errorbar(R, flux_r, flux_err_r, label='Flux') ax.set_ylabel('Flux') ax.set_xlabel('Radius from source') ax.legend() from scipy.optimize import bisect fwhm = bisect(lambda x: model(x, *p) - p[-1] / 2, 0.1, 10) result['erf_amplitude'] = p[-1] result['erf_amplitude_err'] = np.sqrt(np.diag(cov))[-1] result['erf_xfwhm'] = gaussian_sigma_to_fwhm * p[2] result['erf_yfwhm'] = gaussian_sigma_to_fwhm * p[3] result['erf_ks_pvalue'] = stats.kstest( (flux_r - model(R, *p)) / flux_err_r, 'norm').pvalue result['erf_fwhm'] = fwhm result['flux_fraction_3'] = flux_r[3] / p[-1] result['model_flux_fraction_15'] = model(R, * p)[r_range[-1]] / p[-1] result['modeled_flux_r'] = model(R, *p) except Exception as e: print(e) pass ## 2D fit of the PSF from astropy.modeling import models, fitting fitter = fitting.LevMarLSQFitter() X, Y = np.mgrid[:30, :30] flux_std = np.nanmean(flux_noise_map) gauss_model = models.Gaussian2D(amplitude=np.nanmax(flux_map / flux_std), x_mean=_y0, y_mean=_x0) gauss_model.bounds['x_mean'] = (14, 16) gauss_model.bounds['y_mean'] = (14, 16) gauss_fit = fitter(gauss_model, X, Y, flux_map / flux_std) if plot is True: f, ax = plt.subplots(1, 3, figsize=(8, 3)) v_min = np.nanmin(flux_map) v_max = np.nanmax(flux_map) plot_map(flux_map, ax=ax[0], cmap='RdBu_r', vmin=v_min, vmax=v_max) ax[0].set_title("Data") plot_map(gauss_fit(X, Y) * flux_std, ax=ax[1], cmap='RdBu_r', vmin=v_min, vmax=v_max) ax[1].set_title("Model") plot_map(flux_map - gauss_fit(X, Y) * flux_std, ax=ax[2], cmap='RdBu_r', vmin=v_min, vmax=v_max) ax[2].set_title("Residual") result['psf_snr'] = gauss_fit.amplitude[0] result['psf_amplitude'] = flux_std * gauss_fit.amplitude[ 0] * 2 * np.pi * gauss_fit.x_stddev * gauss_fit.y_stddev result['psf_xfwhm'] = gauss_fit.x_fwhm result['psf_yfwhm'] = gauss_fit.y_fwhm normalized_res = (flux_map - gauss_fit(X, Y) * flux_std) / flux_noise_map result['psf_ks_pvalue'] = stats.kstest(normalized_res.flatten(), 'norm').pvalue if return_fit_params: return pd.Series(result), p, gauss_fit else: return pd.Series(result) except Exception as e: print e return pd.Series(result)
def do_Rect_phot(pos, FWHM, trail, ap_min=3., ap_factor=1.5, \ win=None, wout=None, hout=None,\ sky_nsigma=3., sky_iter=10 ): if win == None: win = 4 * FWHM + trail if wout == None: wout = 8 * FWHM + trail if hout == None: hout = 8 * FWHM N = len(pos) if pos.ndim == 1: N = 1 theta = give_theta(number_of_stars=5) an = RectAn(pos, w_in=win, w_out=wout, h_out=hout, theta=theta) ap_size = np.max([ap_min, ap_factor*FWHM]) aperture = RectAp(pos, w=(trail+ap_size), h=ap_size, theta=theta) flux = aperture.do_photometry(image_reduc, method='exact')[0] # do phot and get sum from aperture. [0] is sum and [1] is error. #For test: #FWHM = FWHM_moffat.copy() #trail=trail_len.copy() #win = 4 * FWHM + trail #wout = 8 * FWHM + trail #hout = 8 * FWHM #N=len(pos_star_fit) #an = RectAn(pos_star_fit, w_in=win, w_out=wout, h_out=hout, theta=(theta+np.pi/2)) #ap_size = 1.5*FWHM_moffat #aperture = RectAp(pos_star_fit, w=(trail+ap_size), h=ap_size, theta=(theta+np.pi/2)) #flux = aperture.do_photometry(image_reduc, method='exact')[0] #plt.figure(figsize=(12,12)) #plt.imshow(image_reduc, origin='lower', vmin=-10, vmax=1000) #an.plot(color='white') #aperture.plot(color='red') flux_ss = np.zeros(N) error = np.zeros(N) for i in range(0, N): mask_an = (an.to_mask(method='center'))[i] # cf: test = mask_an.cutout(image_reduc) <-- will make cutout image. sky_an = mask_an.apply(image_reduc) all_sky = sky_an[np.nonzero(sky_an)] # only annulus region will be saved as np.ndarray msky, stdev, nsky, nrej = sky_fit(all_sky, method='Mode', mode_option='sex') area = aperture.area() flux_ss[i] = flux[i] - msky*area # sky subtracted flux error[i] = np.sqrt( flux_ss[i]/gain \ + area * stdev**2 \ + area**2 * stdev**2 / nsky ) if inputs.star_img_save: from matplotlib import pyplot as plt mask_ap = (aperture.to_mask(method='exact'))[i] star_ap_ss = mask_ap.apply(image_reduc-msky) sky_an_ss = mask_an.apply(image_reduc-msky) plt.suptitle('{0}, Star ID={1} ({nsky:3d} {nrej:3d} {msky:7.2f} {stdev:7.2f})'.format( inputs.filename, i, nsky=nsky, nrej=nrej, msky=msky, stdev=stdev )) ax1 = plt.subplot(1,2,1) im1 = ax1.imshow(sky_an_ss, origin='lower') plt.colorbar(im1, orientation='horizontal') ax2 = plt.subplot(1,2,2) im2 = ax2.imshow(star_ap_ss, origin='lower') plt.colorbar(im2, orientation='horizontal') plt.savefig('{0}.star{1}.png'.format(inputs.filename, i)) plt.clf() if pos.ndim > 1: print('\t[{x:7.2f}, {y:7.2f}], {nsky:3d} {nrej:3d} {msky:7.2f} {stdev:7.2f} {flux:7.1f} {ferr:3.1f}'.format(\ x=pos[i][0], y=pos[i][1], \ nsky=nsky, nrej=nrej, msky=msky, stdev=stdev,\ flux=flux_ss[i], ferr=error[i])) return flux_ss, error