def _set_aperture_elements(self): """ Set internal aperture elements. ``self._ap_rect``, ``self.ap_el_1``, ``self.ap_el_2`` and their ``_in`` counterparts are always made by ``np.atleast_2d(self.position)``, so their results are always in the ``N x 2`` shape. """ if hasattr(self, 'a'): w = self.w a = self.a b = self.b h = self.h theta = self.theta elif hasattr(self, 'a_in'): # annulus w = self.w a = self.a_out b = self.b_out h = self.h_out theta = self.theta else: raise ValueError('Cannot determine the aperture shape.') # positions only accepted in the shape of (N, 2), so shape[0] # gives the number of positions: pos = np.atleast_2d(self.positions) self.offset = np.array([w * np.cos(theta) / 2, w * np.sin(theta) / 2]) offsets = np.repeat(np.array([ self.offset, ]), pos.shape[0], 0) # aperture elements for aperture, # OUTER aperture elements for annulus: self._ap_rect = RectangularAperture(positions=pos, w=w, h=h, theta=theta) self._ap_el_1 = EllipticalAperture(positions=pos - offsets, a=a, b=b, theta=theta) self._ap_el_2 = EllipticalAperture(positions=pos + offsets, a=a, b=b, theta=theta) if hasattr(self, 'a_in'): # inner components of annulus self._ap_rect_in = RectangularAperture(positions=pos, w=self.w, h=self.h_in, theta=self.theta) self._ap_el_1_in = EllipticalAperture(positions=pos - offsets, a=self.a_in, b=self.b_in, theta=self.theta) self._ap_el_2_in = EllipticalAperture(positions=pos + offsets, a=self.a_in, b=self.b_in, theta=self.theta)
def aperture_photometry_scan(data, x_pos, y_pos, ap_width, ap_length, theta=0.0, show=False, plt_title=None): """Aperture photometry on source located on x_pos, y_pos with rectangular aperture of dimensions specified by ap_length, ap_width is used. Aperture sums are NOT sky subtracted. Parameters ---------- data : `np.array` 2D array of floats x_pos : float X position of source. y_pos : float Y position of source ap_width : int Width (along x axis) of photometric aperture. ap_length : int Length (along y axis) of photometric aperture. theta : float Angle of orientation (from x-axis) for aperture, in radians. Increases counter-clockwise. show : bool, optional If true, plot showing aperture(s) on source will pop up. Defaults to F. plt_title : str or None, optional Only used if `show` is True. Title for plot. Defaults to None. Returns ------- phot_tab : `astropy.table` Table containing """ copy_data = copy.copy(data) rect_ap = RectangularAperture((x_pos, y_pos), w=ap_width, h=ap_length, theta=theta) phot_table = aperture_photometry(copy_data, rect_ap, method='exact') if show: mask = rect_ap.to_mask(method='center') data_cutout = mask.cutout(data) plt.title(plt_title) z1, z2 = (-12.10630989074707, 32.53888328838081) plt.imshow(data, origin='lower', vmin=z1, vmax=z2) rect_ap.plot(color='white', lw=2) plt.show() plt.close() return phot_table
def buildApertures(self): """Builds the apertures from the centers""" if np.size(self.aperCens) < 1: return self.apersObj = RectangularAperture(positions=self.aperCens, \ w=self.rectWidth, \ h=self.rectHeight, \ theta=self.rectTheta) self.apersSky = RectangularAperture(positions=self.skyCens, \ w=self.rectWidth, \ h=self.rectHeight, \ theta=self.rectTheta)
def custom_aperture(self, shape=None, r=0.0, l=0.0, w=0.0, theta=0.0, pos=None, method='exact'): """ Creates a custom circular or rectangular aperture of arbitrary size. Parameters ---------- shape: str, optional The shape of the aperture to be used. Must be either `circle` or `rectangle.` r: float, optional If shape is `circle` the radius of the circular aperture to be used. l: float, optional If shape is `rectangle` the length of the rectangular aperture to be used. w: float, optional If shape is `rectangle` the width of the rectangular aperture to be used. theta: float, optional If shape is `rectangle` the rotation of the rectangle relative to detector coordinate. Uses units of radians. pos: tuple, optional The center of the aperture, in TPF coordinates. If not set, defaults to the center of the TPF. method: str, optional The method of producing a light curve to be used, either `exact`, `center`, or `subpixel`. Passed through to photutils and used as intended by that package. """ if shape is None: print("Please select a shape: circle or rectangle") shape = shape.lower() if pos is None: pos = (self.tpf.shape[1] / 2, self.tpf.shape[2] / 2) else: pos = pos if shape == 'circle': if r == 0.0: print("Please set a radius (in pixels) for your aperture") else: aperture = CircularAperture(pos, r=r) self.aperture = aperture.to_mask(method=method)[0].to_image( shape=((np.shape(self.tpf[0])))) elif shape == 'rectangle': if l == 0.0 or w == 0.0: print( "For a rectangular aperture, please set both length and width: custom_aperture(shape='rectangle', l=#, w=#)" ) else: aperture = RectangularAperture(pos, l=l, w=w, t=theta) self.aperture = aperture.to_mask(method=method)[0].to_image( shape=((np.shape(self.tpf[0])))) else: print( "Aperture shape not recognized. Please set shape == 'circle' or 'rectangle'" )
def photometry(image2d, cen_x, cen_y, index=0, shape='Circ', rad=None, r_in=None, r_out=None, ht=None, wid=None, w_in=None, w_out=None, h_out=None, ang=0.0): """ PARAMETERS ---------- image2d = 2 dimensional image array. Type = ndarray cen_x, cen_y = x & y center position. Type = ndarray/list index = if cen_x and cen_Y is a list of more than 1 element, specify the desired index. Type = Integer shape = 'Circ':CircularAperture, 'Rect':RectangularAperture, 'CircAnn':CircularAnnulus, 'RectAnn':RectangularAnnulus rad, r_in, r_out, ht, wid, w_in, w_out, h_out, ang = Astropy's aperture parameters RETURNS ------- flux = flux of the image extracted by the aperture described in the "shape" parameter. Type = Float aperture = aperture object created by astropy """ mask = np.isnan(image2d) == True if shape == 'Circ': aperture = CircularAperture((cen_x[index], cen_y[index]), r=rad) elif shape == 'Rect': aperture = RectangularAperture((cen_x[index], cen_y[index]), w=wid, h=ht, theta=ang) elif shape == 'CircAnn': aperture = CircularAnnulus((cen_x[index], cen_y[index]), r_in=r_in, r_out=r_out) elif shape == 'RectAnn': aperture = RectangularAnnulus((cen_x[index], cen_y[index]), w_in=w_in, w_out=w_out, h_out=h_out, theta=ang) phot_table = aperture_photometry(image2d, aperture, mask=mask) flux = phot_table['aperture_sum'] return flux, aperture
def place_overlay(x_pixel, y_pixel, mousebutton, wcs, ax, FOV=1.5, Size='750'): """Places either the main CCD, or the guide CCD apeture overlay centered on the pixel coordinates depending on the boolean case "guide" specified in the input arguments. The overlay for the guide camera is placed with respect to the CCD center. Function also replots image to reset apertures (Easiest way I could find to do it for now) """ fov = FOV * 60 size = int(Size) scale = fov / size #Set plate scale of image 60 is number of arcmin across position_Target = (size / 2, size / 2) aperture_Target = CircularAperture(position_Target, r=(PlateScale / scale)) aperture_Target.plot(color='blue', lw=1.5, alpha=0.5) if x_pixel == None or y_pixel == None: position_CCD = (size / 2, size / 2) position_Guide = (size / 2 - GuideOffRA / scale, ((size / 2) + (GuideOffDEC / scale))) else: if mousebutton: position_Guide = (x_pixel, y_pixel) position_CCD = ((x_pixel) + GuideOffRA / scale, (y_pixel - (GuideOffDEC / scale))) else: position_Guide = (x_pixel - GuideOffRA / scale, y_pixel + (GuideOffDEC / scale)) position_CCD = ((x_pixel), (y_pixel)) aperture_GuideStar = CircularAperture(position_Guide, r=(PlateScale / scale)) aperture_Guide = RectangularAperture(position_Guide, (GuideX / scale), (GuideY / scale)) aperture_GuideStar.plot(color='blue', lw=1.5, alpha=0.5) aperture_Guide.plot(color='red', lw=1.5, alpha=0.5) aperture_CCD = RectangularAperture(position_CCD, (SciX / scale), (SciY / scale)) aperture_CCD.plot(color='green', lw=1.5, alpha=0.5) ax.figure.canvas.draw() coords = SkyCoord.from_pixel(position_CCD[0], position_CCD[1], wcs) RA_str = coords.ra.to_string(units.hour) DEC_str = coords.dec.to_string(units.degree) #print coords of main CCD center print(RA_str, DEC_str)
def get_slit_loss(fwhm, sw): ''' ''' sig = fwhm * gaussian_fwhm_to_sigma ny = nx = np.int(10 * sig) Y, X = np.mgrid[-ny:ny + 1, -nx - 10:nx + 1 + 10] g = Gaussian2D(amplitude=1 / (2 * np.pi * sig**2), x_mean=0, y_mean=0, x_stddev=sig, y_stddev=sig, theta=0) data = g(X, Y) (y, x) = np.unravel_index(np.argmax(data, axis=None), data.shape) aper = RectangularAperture((x, y), w=sw, h=data.shape[0]) # HM = data[ind] / 2 # FW = np.where( HM <= data[ind[0]] )[0][-1] - np.where( HM <= data[ind[0]] )[0][ 0] # print( FW * gaussian_fwhm_to_sigma / sig ) # Aperture photometry phot_table = aperture_photometry(data, aper, method='subpixel', subpixels=100) slit_loss = data.sum() - phot_table['aperture_sum'][0] # fig, ax = plt.subplots( 1, 1, figsize = ( 10, 10 ) ) # ax.imshow( data ) # aper.plot( axes = ax, color = 'red', lw = 2 ) # plt.show() return slit_loss
def transform_xy_rectangle(centerX, centerY,width, height, cubeObj): """ Update rectangle data widgets and image object attributes :param float centerX: x value of the center coordinate :param float centerY: y value of the center coordinate :param float width: width value of the rectangle :param float height: height value of the rectangle :param object cubeObj: current data from the cube :return list fValues: flux values list for each wavelength :return list wValues: wavelenght list for each slice :return Aperture_Photometry aperture: aperture of the rectangle """ fValues = [] #Because it gets all the flux on a pixel, it needs to get the area of it rather than one value. pixelArea = (cubeObj.cubeRAValue * 3600.) * (cubeObj.cubeDValue * 3600.) aperture = RectangularAperture([centerX, centerY], width, height) for i in range(cubeObj.maxSlice): phot_table = aperture_photometry(cubeObj.data_cube[i], aperture, method='subpixel') fValues.append(phot_table['aperture_sum'][0]*pixelArea) wValues = [((w+1) - cubeObj.cubeZCPix)*cubeObj.cubeWValue + cubeObj.cubeZCRVal for w in range(len(fValues))] return fValues, wValues, aperture
def A_photometry(image_data, bg_err, factor=1, ape_sum=[], ape_sum_err=[], cx=15, cy=15, r=2.5, a=5, b=5, w_r=5, h_r=5, theta=0, shape='Circular', method='center'): ''' Performs aperture photometry, first by creating the aperture (Circular, Rectangular or Elliptical), then it sums up the flux that falls into the aperture. Parameters ---------- image_data: 3D array Data cube of images (2D arrays of pixel values). bg_err : 1D array Array of uncertainties on pixel value. factor : float (optional) Electron count to photon count factor. Default is 1 if none given. ape_sum : 1D array (optional) Array of flux to append new flux values to. If 'None', the new values will be appended to an empty array ape_sum_err: 1D array (optional) Array of flux uncertainty to append new flux uncertainty values to. If 'None', the new values will be appended to an empty array. cx : int (optional) x-coordinate of the center of the aperture. Default is 15. cy : int (optional) y-coordinate of the center of the aperture. Default is 15. r : int (optional) If phot_meth is 'Aperture' and ap_shape is 'Circular', c_radius is the radius for the circular aperture. Default is 2.5. a : int (optional) If phot_meth is 'Aperture' and ap_shape is 'Elliptical', e_semix is the semi-major axis for elliptical aperture (x-axis). Default is 5. b : int (optional) If phot_meth is 'Aperture' and ap_shape is 'Elliptical', e_semiy is the semi-major axis for elliptical aperture (y-axis). Default is 5. w_r : int (optional) If phot_meth is 'Aperture' and ap_shape is 'Rectangular', r_widthx is the full width for rectangular aperture (x-axis). Default is 5. h_r : int (optional) If phot_meth is 'Aperture' and ap_shape is 'Rectangular', r_widthy is the full height for rectangular aperture (y-axis). Default is 5. theta : int (optional) If phot_meth is 'Aperture' and ap_shape is 'Elliptical' or 'Rectangular', theta is the angle of the rotation angle in radians of the semimajor axis from the positive x axis. The rotation angle increases counterclockwise. Default is 0. shape : string object (optional) If phot_meth is 'Aperture', ap_shape is the shape of the aperture. Possible aperture shapes are 'Circular', 'Elliptical', 'Rectangular'. Default is 'Circular'. method : string object (optional) If phot_meth is 'Aperture', apemethod is the method used to determine the overlap of the aperture on the pixel grid. Possible methods are 'exact', 'subpixel', 'center'. Default is 'center'. Returns ------- ape_sum : 1D array Array of flux with new flux appended. ape_sum_err: 1D array Array of flux uncertainties with new flux uncertainties appended. ''' l, h, w = image_data.shape position = [cx, cy] if (shape == 'Circular'): aperture = CircularAperture(position, r=r) elif (shape == 'Elliptical'): aperture = EllipticalAperture(position, a=a, b=b, theta=theta) elif (shape == 'Rectangular'): aperture = RectangularAperture(position, w=w_r, h=h_r, theta=theta) for i in range(l): data_error = calc_total_error(image_data[i, :, :], bg_err[i], effective_gain=1) phot_table = aperture_photometry(image_data[i, :, :], aperture, error=data_error, pixelwise_error=False) if (phot_table['aperture_sum_err'] > 0.000001): ape_sum.extend(phot_table['aperture_sum'] * factor) ape_sum_err.extend(phot_table['aperture_sum_err'] * factor) else: ape_sum.extend([np.nan]) ape_sum_err.extend([np.nan]) return ape_sum, ape_sum_err
class ApertureSet(object): """Set of apertures for the photometry""" # (I'm not sure if I want to stick with photutils, since that # currently enforces the same aperture size for all # apertures. Either way, we separate the apertures list out from # the individual image. def __init__(self, perpendic=False): # control variable self.perpendic = perpendic # Some parameters for aperture construction self.rectWidth = 220. self.rectHeight = 90. self.rectTheta = np.radians(325.) # sky rectangles self.skyWidth = np.copy(self.rectWidth) self.skyHeight = np.copy(self.rectHeight) # sky nudge self.skyNudge = np.array([0., 30.]) if self.perpendic: self.rectTheta = np.radians(55.) self.skyNudge = np.array([120., 130.]) # some default positions for apertures self.aperCens = np.array([]) self.skyCens = np.array([]) # INCLUDE ENTRIES FOR THE SKY REGIONS!!! def setDefaultCenters(self): """Sets up default aperture centers for testing on the 2017-04-14 Jupiter data""" # the third one is a dummy to help me get the array dimensions # the right way round... vX = np.array([944., 892., 1105., 693., 1297.]) vY = np.array([520., 446., 365., 592., 250.5]) if self.perpendic: vX = np.array([960., 885., 1100., 772., 1285.]) vY = np.array([456., 509., 381., 588., 264.0]) self.aperCens = np.vstack((vX, vY)) # set up the sky apertures self.skyCens = np.copy(self.aperCens) # DO THE OFFSET HERE. self.skyCens[1] -= (self.skyNudge[1] + self.skyHeight) self.skyCens[0] -= self.skyNudge[0] if not self.perpendic: self.skyCens[1, 1] += 2.0 * (self.skyNudge[1] + self.skyHeight) # self.skyCens[1,0] += 2.0 * (self.skyNudge[1] + self.skyHeight) def buildApertures(self): """Builds the apertures from the centers""" if np.size(self.aperCens) < 1: return self.apersObj = RectangularAperture(positions=self.aperCens, \ w=self.rectWidth, \ h=self.rectHeight, \ theta=self.rectTheta) self.apersSky = RectangularAperture(positions=self.skyCens, \ w=self.rectWidth, \ h=self.rectHeight, \ theta=self.rectTheta) def showApertures(self, figNum=1, inAx=None): """Uses photutils apertures built-in to show the apertures""" # This is a really stupid cargo-cult way to proceed... Come # back to this later!! if not inAx: fig = plt.figure(figNum) fig.clf() ax = fig.add_subplot(111) ax.plot([0., 1024], [0., 1024], 'w.', alpha=0.) else: ax = inAx self.apersObj.plot(ax=ax, color='g', alpha=0.7) self.apersSky.plot(ax=ax, color='b', ls='--', alpha=0.7)
coeff = chebfit(x_sky[~clip_mask], sky_val[~clip_mask], deg=ORDER_APSKY) data_skysub.append(cut_i - chebval(np.arange(cut_i.shape[0]), coeff)) data_skysub = np.array(data_skysub).T hdr = objhdu[0].header hdr.add_history(f"Sky subtracted using sky offset = {ap_sky_offset}, " + f"{SIGMA_APSKY}-sigma {ITERS_APSKY}-iter clipping " + f"to fit order {ORDER_APSKY} Chebyshev") _ = fits.PrimaryHDU(data=data_skysub, header=hdr) _.data = _.data.astype('float32') _.writeto(SKYSBPATH / (OBJIMAGE.stem + ".skysub.fits"), overwrite=True) pos = np.array([x_ap, y_ap]).T aps = RectangularAperture(positions=pos, w=1, h=apheight, theta=0) phot = aperture_photometry(data_skysub, aps, method='subpixel', subpixels=30) ap_summed = phot['aperture_sum'] / EXPTIME fig, axs = plt.subplots(2, 1, figsize=(10, 6), sharex=False, sharey=False, gridspec_kw=None) axs[0].imshow(objimage, vmin=0, vmax=4900, origin='lower') axs[1].imshow(data_skysub, vmin=0, vmax=200, origin='lower') axs[1].plot(pos[:, 0], pos[:, 1], 'r-') axs[0].set(title="Before sky subtraction") axs[1].set(title="Sky subtracted")
def radial_profile(data, lim): ############################################################ # # Get radial profiles - model # ############################################################ ############################################################ # Fetching information imfile = open("Image_jband.out").readlines() for line in imfile: if line.split('=')[0] == 'MCobs:fov': fov = float(line.split('=')[1].split('!')[0]) elif line.split('=')[0] == 'MCobs:npix': npix = float(line.split('=')[1].split('!')[0]) elif line.split('=')[0] == 'MCobs:phi': phi_image = float(line.split('=')[1].split('!')[0]) elif line.split('=')[0] == 'MCobs:theta': theta = float(line.split('=')[1].split('!')[0]) else: continue infile = open("input.dat").readlines() for line in infile: if line.split('=')[0] == 'Distance': d = float(line.split('=')[1]) ############################################################ # Derived quantities pxsize = fov / npix # pixel scale (arcsec/px) phi = (phi_image * units.deg).to( units.rad).value # PA from north to east (rad) e = np.sin(theta) # eccentricity of the annulus angle_annulus = ((0.0) * units.deg).to(units.rad).value e = np.sin( (theta * units.deg).to(units.rad).value) # eccentricity of the annulus # Determining limit for radial profile linear_lim = 2 * lim # AU angular_lim = linear_lim / ((d * units.pc).to(units.au).value) # rad angular_lim = (angular_lim * units.rad).to(units.arcsec).value # arcsec pixel_lim = int(round(angular_lim / pxsize)) xc = 0.5 * data.shape[0] # Image center in data coordinates yc = 0.5 * data.shape[1] # Image center in data coordinates dr = 1.0 # Width of the annulus w = 1.0 h = 1.0 lim = 120 lim = toPX(lim, pxsize, d) InRad = np.pi / 180.0 xc_array = [] yc_array = [] x0 = xc y0 = yc xc_array.append(x0) yc_array.append(y0) for l in np.arange(-lim, lim, 1.0): xval = x0 + l * np.cos(angle_annulus) yval = y0 + l * np.sin(angle_annulus) xc_array.append(xval) yc_array.append(yval) positions = [(i, j) for (i, j) in zip(xc_array, yc_array)] InRad = np.pi / 180.0 apertures = RectangularAperture(positions, w, h, angle_annulus) """ ############################################################ # Do a check a=0.4 vmin=np.percentile(data,a) vmax=np.percentile(data,100-a) plt.imshow(data,clim=(vmin,vmax)) plt.title("Image observation") apertures.plot(color='red',lw=1) #plt.xlim(400,600) #plt.ylim(600,400) plt.show() """ phot_table = aperture_photometry(data, apertures) r_au = [ toAU(phot_table['xcenter'][i].value, phot_table['ycenter'][i].value, xc, yc, pxsize, d) for i in range(0, len(phot_table)) ] brightness = [ phot_table['aperture_sum'][i] for i in range(0, len(phot_table)) ] brightness = np.array(brightness) ############################################################ # Creating brightness profile normalized rcmin = 35.0 rcmax = 100.0 bmaxc = [] for i in range(0, len(r_au)): if rcmin <= r_au[i] <= rcmax: bmaxc.append(brightness[i]) bmaxc = np.array(bmaxc) fac = 1 / max(bmaxc) brightness = brightness * fac """ fig=plt.figure() ax=plt.axes() ax.plot(r_au,brightness,'.') ax.set_xlabel(r"Projected radial distance (AU)") ax.set_ylabel("Density flux (a.u.)") ax.set_title("Radial profile observation") #ax.set_ylim(-0.1,5) plt.show() sys.exit() """ ############################################################ # Creating file file = open('jband_radial_profile_modeled.dat', "w") for i in range(0, len(r_au)): file.write('%.15e %.15e \n' % (r_au[i], brightness[i])) #data_mod=get_profile(data_mod,pxsize,lim) return None
def extract_ifu(input_model, source_type, extract_params): """This function does the extraction. Parameters ---------- input_model: IFUCubeModel The input model. source_type: string "point" or "extended" extract_params: dict The extraction parameters for aperture photometry. Returns ------- (ra, dec, wavelength, net, background, dq) """ data = input_model.data shape = data.shape if len(shape) != 3: log.error("Expected a 3-D IFU cube; dimension is %d.", len(shape)) raise RuntimeError("The IFU cube should be 3-D.") # We need to allocate net, background, and dq arrays no matter what. net = np.zeros(shape[0], dtype=np.float64) background = np.zeros(shape[0], dtype=np.float64) dq = np.zeros(shape[0], dtype=np.int32) x_center = extract_params['x_center'] y_center = extract_params['y_center'] if x_center is None: x_center = float(shape[2]) / 2. else: x_center = float(x_center) if y_center is None: y_center = float(shape[1]) / 2. else: y_center = float(y_center) method = extract_params['method'] # subpixels is only needed if method = 'subpixel'. subpixels = extract_params['subpixels'] subtract_background = extract_params['subtract_background'] smaller_axis = float(min(shape[1], shape[2])) # for defaults if source_type == 'point': radius = extract_params['radius'] if radius is None: radius = smaller_axis / 4. if subtract_background: inner_bkg = extract_params['inner_bkg'] if inner_bkg is None: inner_bkg = radius outer_bkg = extract_params['outer_bkg'] if outer_bkg is None: outer_bkg = min(inner_bkg * math.sqrt(2.), smaller_axis / 2. - 1.) if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: log.debug("Turning background subtraction off, due to " "the values of inner_bkg and outer_bkg.") subtract_background = False width = None height = None theta = None else: width = extract_params['width'] if width is None: width = smaller_axis / 2. height = extract_params['height'] if height is None: height = smaller_axis / 2. theta = extract_params['theta'] * math.pi / 180. radius = None subtract_background = False inner_bkg = None outer_bkg = None log.debug("IFU 1-D extraction parameters:") log.debug(" x_center = %s", str(x_center)) log.debug(" y_center = %s", str(y_center)) if source_type == 'point': log.debug(" radius = %s", str(radius)) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" inner_bkg = %s", str(inner_bkg)) log.debug(" outer_bkg = %s", str(outer_bkg)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) else: log.debug(" width = %s", str(width)) log.debug(" height = %s", str(height)) log.debug(" theta = %s degrees", str(extract_params['theta'])) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) # Check for out of bounds. # The problem with having the background aperture extend beyond the # image is that the normalization would not account for the resulting # decrease in the area of the annulus, so the background subtraction # would be systematically low. outside = False f_nx = float(shape[2]) f_ny = float(shape[1]) if x_center < 0. or x_center >= f_nx - 1. or \ y_center < 0. or y_center >= f_ny - 1.: outside = True log.error("Target location is outside the image.") if subtract_background and \ (x_center - outer_bkg < -0.5 or x_center + outer_bkg > f_nx - 0.5 or y_center - outer_bkg < -0.5 or y_center + outer_bkg > f_ny - 0.5): outside = True log.error("Background region extends outside the image.") if outside: (ra, dec) = (0., 0.) wavelength = np.zeros(shape[0], dtype=np.float64) dq[:] = dqflags.pixel['DO_NOT_USE'] return (ra, dec, wavelength, net, background, dq) # all bad if hasattr(input_model.meta, 'wcs'): wcs = input_model.meta.wcs else: log.warning("WCS function not found in input.") wcs = None if wcs is not None: x_array = np.empty(shape[0], dtype=np.float64) x_array.fill(float(shape[2]) / 2.) y_array = np.empty(shape[0], dtype=np.float64) y_array.fill(float(shape[1]) / 2.) z_array = np.arange(shape[0], dtype=np.float64) # for wavelengths ra, dec, wavelength = wcs(x_array, y_array, z_array) nelem = len(wavelength) ra = ra[nelem // 2] dec = dec[nelem // 2] else: (ra, dec) = (0., 0.) wavelength = np.arange(1, shape[0] + 1, dtype=np.float64) position = (x_center, y_center) if source_type == 'point': aperture = CircularAperture(position, r=radius) if subtract_background: annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) normalization = aperture.area() / annulus.area() else: aperture = RectangularAperture(position, width, height, theta) # No background is computed for an extended source. for k in range(shape[0]): phot_table = aperture_photometry(data[k, :, :], aperture, method=method, subpixels=subpixels) net[k] = float(phot_table['aperture_sum'][0]) if subtract_background: bkg_table = aperture_photometry(data[k, :, :], annulus, method=method, subpixels=subpixels) background[k] = float(bkg_table['aperture_sum'][0]) net[k] = net[k] - background[k] * normalization # Check for NaNs in the wavelength array, flag them in the dq array, # and truncate the arrays if NaNs are found at endpoints (unless the # entire array is NaN). nan_mask = np.isnan(wavelength) n_nan = nan_mask.sum(dtype=np.intp) if n_nan > 0: log.warning("%d NaNs in wavelength array.", n_nan) dq[nan_mask] = np.bitwise_or(dq[nan_mask], dqflags.pixel['DO_NOT_USE']) not_nan = np.logical_not(nan_mask) flag = np.where(not_nan) if len(flag[0]) > 0: n_trimmed = flag[0][0] + nelem - (flag[0][-1] + 1) if n_trimmed > 0: log.info("Output arrays have been trimmed by %d elements", n_trimmed) slc = slice(flag[0][0], flag[0][-1] + 1) wavelength = wavelength[slc] net = net[slc] background = background[slc] dq = dq[slc] else: dq |= dqflags.pixel['DO_NOT_USE'] return (ra, dec, wavelength, net, background, dq)
def get_profile(file, pxsize, PA_disk, inc, d): ############################################################ # # Get flux along the semi-major axis with a rectangular # aperture. # # file: the fits file of the observation # pxsize: pixel scale (arcsec/px) # PA_disk: position angle of the disk measured east-north (deg) # inc: disk's inclination (deg) # d: distance to the source (pc) # # returns a file with the brightness profile along the # semi-major axis and a file with the position and flux # value of the pixel located along the semi-major axis # in the "South-East" quadrant containing the maximum flux # value. The brightness profile returned is not normalized. # ############################################################ ############################################################ # Load data hdulist = fits.open(file) data_obs = hdulist[0].data # adu's ############################################################ # Derived properties angle_annulus = ((PA_disk - 90.0) * units.deg).to(units.rad).value e = np.sin((inc * units.deg).to(units.rad).value) xc = 0.5 * data_obs.shape[0] # Image center in data coordinates yc = 0.5 * data_obs.shape[1] # Image center in data coordinates w = 1.0 h = 1.0 lim = 120.0 lim = toPX(lim, pxsize, d) xc_array = [] yc_array = [] ############################################################ # Setting up aperture photometry x0 = xc y0 = yc xc_array.append(x0) yc_array.append(y0) for l in np.arange(-lim, lim, 1.0): xval = x0 + l * np.cos(angle_annulus) yval = y0 + l * np.sin(angle_annulus) xc_array.append(xval) yc_array.append(yval) positions = [(i, j) for (i, j) in zip(xc_array, yc_array)] apertures = RectangularAperture(positions, w, h, angle_annulus) """ # Do a check? a=0.01 vmin=np.percentile(data_obs,a) vmax=np.percentile(data_obs,100-a) plt.imshow(data_obs,clim=(vmin,vmax)) apertures.plot(color='red',lw=1) plt.show() sys.exit() """ ############################################################ # Performing aperture photometry phot_table = aperture_photometry(data_obs, apertures) r_au = [ toAU(phot_table['xcenter'][i].value, phot_table['ycenter'][i].value, xc, yc, pxsize, d) for i in range(0, len(phot_table)) ] brightness = [ phot_table['aperture_sum'][i] for i in range(0, len(phot_table)) ] for i in range(0, len(brightness)): brightness[i] = brightness[i] / apertures[i].area """ # Do a check? fig=plt.figure() ax=plt.axes() ax.plot(r_au,brightness,'.') ax.set_xlabel(r"Projected radial distance (AU)") ax.set_ylabel("Density flux (a.u.)") plt.show() sys.exit() """ ############################################################ # Finding maximum value along semi-major axis rstart = 40.0 rend = 80.0 bmax = [] for i in range(0, len(r_au)): if rstart <= r_au[i] <= rend: bmax.append(brightness[i]) bmax = np.array(bmax) imax = np.where(brightness == max(bmax))[0][0] ############################################################ # Writing files f1 = open("info_max_Qphi.dat", "w") f2 = open("radial_cut_0FWHM.dat", "w") f1.write("r_max=%.2f (AU)\n" % r_au[imax]) f1.write("PA_max=%.2f (deg)\n" % PA_disk) f1.write("B_max=%.15f (a.u.)\n" % brightness[imax]) for i in range(0, len(r_au)): f2.write("%.5e %.5e \n" % (r_au[i], brightness[i])) f1.close() f2.close() return None
def extract_ifu(input_model, source_type, extract_params): """This function does the extraction. Parameters ---------- input_model : IFUCubeModel The input model. source_type : string "point" or "extended" extract_params : dict The extraction parameters for aperture photometry. Returns ------- ra, dec : float ra and dec are the right ascension and declination respectively at the nominal center of the image. wavelength : ndarray, 1-D The wavelength in micrometers at each pixel. net : ndarray, 1-D The count rate (or flux) minus the background at each pixel. background : ndarray, 1-D The background count rate that was subtracted from the total source count rate to get `net`. npixels : ndarray, 1-D, float64 For each slice, this is the number of pixels that were added together to get `net`. dq : ndarray, 1-D, uint32 The data quality array. """ data = input_model.data shape = data.shape if len(shape) != 3: log.error("Expected a 3-D IFU cube; dimension is %d.", len(shape)) raise RuntimeError("The IFU cube should be 3-D.") # We need to allocate net, background, npixels, and dq arrays # no matter what. We may need to divide by npixels, so the default # is 1 rather than 0. net = np.zeros(shape[0], dtype=np.float64) background = np.zeros(shape[0], dtype=np.float64) npixels = np.ones(shape[0], dtype=np.float64) dq = np.zeros(shape[0], dtype=np.uint32) x_center = extract_params['x_center'] y_center = extract_params['y_center'] if x_center is None: x_center = float(shape[2]) / 2. else: x_center = float(x_center) if y_center is None: y_center = float(shape[1]) / 2. else: y_center = float(y_center) method = extract_params['method'] # subpixels is only needed if method = 'subpixel'. subpixels = extract_params['subpixels'] subtract_background = extract_params['subtract_background'] smaller_axis = float(min(shape[1], shape[2])) # for defaults radius = None inner_bkg = None outer_bkg = None if source_type == 'point': radius = extract_params['radius'] if radius is None: radius = smaller_axis / 4. if subtract_background: inner_bkg = extract_params['inner_bkg'] if inner_bkg is None: inner_bkg = radius outer_bkg = extract_params['outer_bkg'] if outer_bkg is None: outer_bkg = min(inner_bkg * math.sqrt(2.), smaller_axis / 2. - 1.) if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: log.debug("Turning background subtraction off, due to " "the values of inner_bkg and outer_bkg.") subtract_background = False width = None height = None theta = None else: width = extract_params['width'] if width is None: width = smaller_axis / 2. height = extract_params['height'] if height is None: height = smaller_axis / 2. theta = extract_params['theta'] * math.pi / 180. subtract_background = False log.debug("IFU 1-D extraction parameters:") log.debug(" x_center = %s", str(x_center)) log.debug(" y_center = %s", str(y_center)) if source_type == 'point': log.debug(" radius = %s", str(radius)) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" inner_bkg = %s", str(inner_bkg)) log.debug(" outer_bkg = %s", str(outer_bkg)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) else: log.debug(" width = %s", str(width)) log.debug(" height = %s", str(height)) log.debug(" theta = %s degrees", str(extract_params['theta'])) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) # Check for out of bounds. # The problem with having the background aperture extend beyond the # image is that the normalization would not account for the resulting # decrease in the area of the annulus, so the background subtraction # would be systematically low. outside = False f_nx = float(shape[2]) f_ny = float(shape[1]) if x_center < 0. or x_center >= f_nx - 1. or \ y_center < 0. or y_center >= f_ny - 1.: outside = True log.error("Target location is outside the image.") if subtract_background and \ (x_center - outer_bkg < -0.5 or x_center + outer_bkg > f_nx - 0.5 or y_center - outer_bkg < -0.5 or y_center + outer_bkg > f_ny - 0.5): outside = True log.error("Background region extends outside the image.") if outside: (ra, dec) = (0., 0.) wavelength = np.zeros(shape[0], dtype=np.float64) dq[:] = dqflags.pixel['DO_NOT_USE'] return (ra, dec, wavelength, net, background, npixels, dq) # all bad x0 = float(shape[2]) / 2. y0 = float(shape[1]) / 2. (ra, dec, wavelength) = get_coordinates(input_model, x0, y0) position = (x_center, y_center) if source_type == 'point': aperture = CircularAperture(position, r=radius) if subtract_background: annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) normalization = aperture.area() / annulus.area() else: aperture = RectangularAperture(position, width, height, theta) # No background is computed for an extended source. npixels[:] = aperture.area() for k in range(shape[0]): phot_table = aperture_photometry(data[k, :, :], aperture, method=method, subpixels=subpixels) net[k] = float(phot_table['aperture_sum'][0]) if subtract_background: bkg_table = aperture_photometry(data[k, :, :], annulus, method=method, subpixels=subpixels) background[k] = float(bkg_table['aperture_sum'][0]) net[k] = net[k] - background[k] * normalization # Check for NaNs in the wavelength array, flag them in the dq array, # and truncate the arrays if NaNs are found at endpoints (unless the # entire array is NaN). (wavelength, net, background, npixels, dq) = \ nans_in_wavelength(wavelength, net, background, npixels, dq) return (ra, dec, wavelength, net, background, npixels, dq)
def OutflowIntensity(model, dstar, group, wave): import numpy as np import matplotlib.pyplot as plt from hyperion.model import ModelOutput from photutils import aperture_photometry as ap from photutils import CircularAperture, RectangularAperture import astropy.constants as const pc = const.pc.cgs.value # m = ModelOutput('/Volumes/SD-Mac/model131.rtout') image = m.get_image(group=group, inclination=0, distance=dstar * pc, units='MJy/sr') # The radius of the aperture in arcsec # radius = 10 x = 100 y = 100 # area = np.pi*radius**2 / 4.25e10 # in sr area = x*y / 4.25e10 # The offset to the center offset = 50 if wave != list: wave = [wave] iwav = np.argmin(np.abs(wave - image.wav)) # Image in the unit of MJy/sr, change it into erg/s/cm2/Hz/sr factor = 1e-23*1e6 val = image.val[::-1, :, iwav] * factor + 1e-30 # Calculate the image width in arcseconds given the distance used above # get the max radius rmax = max(m.get_quantities().r_wall) w = np.degrees(rmax / image.distance) * 3600. pos_n = (len(val[0,:])/2.-1,len(val[0,:])/2.-1 + offset*len(val[0,:])/2/w) pos_s = (len(val[0,:])/2.-1,len(val[0,:])/2.-1 - offset*len(val[0,:])/2/w) # aper_n = CircularAperture(pos_n, r=radius * len(val[0,:])/2/w ) # aper_s = CircularAperture(pos_s, r=radius * len(val[0,:])/2/w ) # # plot to make sure the selection is correct # from astropy.convolution import Gaussian1DKernel, convolve # g = Gaussian1DKernel(stddev=20) # # fig = plt.figure(figsize=(8,6)) # ax = fig.add_subplot(111) # # ax.plot(np.arange(-w, w, 2*w/len(val[0,:])), convolve(np.sum(val[len(val[0,:])/2.-11:len(val[0,:])/2.+9,:], axis=0), g, boundary='extend'), color='b') # ax.plot(np.arange(-w, w, 2*w/len(val[0,:])), convolve(np.sum(val[:,len(val[0,:])/2.-11:len(val[0,:])/2.+9], axis=1), g, boundary='extend'), color='r') # # ax.set_xscale('log') # # fig.savefig('/Users/yaolun/test/im_test.pdf', format='pdf', dpi=300, bbox_inches='tight') # fig.clf() aper_n = RectangularAperture(pos_n, w=x*len(val[0,:])/2/w, h=y*len(val[0,:])/2/w, theta=0) aper_s = RectangularAperture(pos_s, w=x*len(val[0,:])/2/w, h=y*len(val[0,:])/2/w, theta=0) # multiply the aperture size in sr and convert to Jy phot_n = ap(val, aper_n)['aperture_sum'].data * area * 1e23 phot_s = ap(val, aper_s)['aperture_sum'].data * area * 1e23 return phot_n, phot_s
def extract_ifu(input_model, source_type, extract_params): """This function does the extraction. Parameters ---------- input_model: IFUCubeModel The input model. source_type: string "point" or "extended" extract_params: dict The extraction parameters for aperture photometry. Returns ------- (wavelength, net, background, dq) """ data = input_model.data shape = data.shape if len(shape) != 3: log.error("Expected a 3-D IFU cube; dimension is %d.", len(shape)) raise RuntimeError("The IFU cube should be 3-D.") # We need to allocate net, background, and dq arrays no matter what. net = np.zeros(shape[0], dtype=np.float64) background = np.zeros(shape[0], dtype=np.float64) dq = np.zeros(shape[0], dtype=np.int32) x_center = extract_params['x_center'] y_center = extract_params['y_center'] if x_center is None: x_center = float(shape[2]) / 2. else: x_center = float(x_center) if y_center is None: y_center = float(shape[1]) / 2. else: y_center = float(y_center) method = extract_params['method'] # subpixels is only needed if method = 'subpixel'. subpixels = extract_params['subpixels'] subtract_background = extract_params['subtract_background'] smaller_axis = float(min(shape[1], shape[2])) # for defaults if source_type == 'point': radius = extract_params['radius'] if radius is None: radius = smaller_axis / 4. if subtract_background: inner_bkg = extract_params['inner_bkg'] if inner_bkg is None: inner_bkg = radius outer_bkg = extract_params['outer_bkg'] if outer_bkg is None: outer_bkg = min(inner_bkg * math.sqrt(2.), smaller_axis / 2. - 1.) width = None height = None theta = None else: width = extract_params['width'] if width is None: width = smaller_axis / 2. height = extract_params['height'] if height is None: height = smaller_axis / 2. theta = extract_params['theta'] * math.pi / 180. radius = None inner_bkg = None outer_bkg = None if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: subtract_background = False log.debug("IFU 1-D extraction parameters:") log.debug(" x_center = %s", str(x_center)) log.debug(" y_center = %s", str(y_center)) if source_type == 'point': log.debug(" radius = %s", str(radius)) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" inner_bkg = %s", str(inner_bkg)) log.debug(" outer_bkg = %s", str(outer_bkg)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) else: log.debug(" width = %s", str(width)) log.debug(" height = %s", str(height)) log.debug(" theta = %s degrees", str(extract_params['theta'])) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) # Check for out of bounds. # The problem with having the background aperture extend beyond the # image is that the normalization would not account for the resulting # decrease in the area of the annulus, so the background subtraction # would be systematically low. outside = False f_nx = float(shape[2]) f_ny = float(shape[1]) if x_center < 0. or x_center >= f_nx - 1. or \ y_center < 0. or y_center >= f_ny - 1.: outside = True log.error("Target location is outside the image.") if subtract_background and \ (x_center - outer_bkg < -0.5 or x_center + outer_bkg > f_nx - 0.5 or y_center - outer_bkg < -0.5 or y_center + outer_bkg > f_ny - 0.5): outside = True log.error("Background region extends outside the image.") if outside: wavelength = np.zeros(shape[0], dtype=np.float64) dq[:] = dqflags.pixel['DO_NOT_USE'] return (wavelength, net, background, dq) # all bad wcs = input_model.meta.wcs x_array = np.empty(shape[0], dtype=np.float64) x_array.fill(float(shape[2]) / 2.) y_array = np.empty(shape[0], dtype=np.float64) y_array.fill(float(shape[1]) / 2.) z_array = np.arange(shape[0], dtype=np.float64) # for wavelengths _, _, wavelength = wcs(x_array, y_array, z_array) position = (x_center, y_center) if source_type == 'point': aperture = CircularAperture(position, r=radius) if subtract_background: annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) normalization = aperture.area() / annulus.area() else: aperture = RectangularAperture(position, width, height, theta) # No background is computed for an extended source. for k in range(shape[0]): phot_table = aperture_photometry(data[k, :, :], aperture, method=method, subpixels=subpixels) net[k] = float(phot_table['aperture_sum'][0]) if subtract_background: bkg_table = aperture_photometry(data[k, :, :], annulus, method=method, subpixels=subpixels) background[k] = float(bkg_table['aperture_sum'][0]) net[k] = net[k] - background[k] * normalization return (wavelength, net, background, dq)
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
def _prepare_mask(bbox, ap_r, ap_1, ap_2, method, subpixels, min_mask=0): """ Make the pill box mask array. Note ---- To make an ndarray to represent the overlapping mask, the three (a rectangular and two elliptical) apertures are generated, but parallely shifted such that the bounding box has ``ixmin`` and ``iymin`` both zero. Then proper mask is generated as an ndarray. It is then used by ``PillBoxMaskMixin.to_mask`` to make an ``ApertureMask`` object by combining this mask with the original bounding box. Parameters ---------- bbox : `~photutils.BoundingBox` The bounding box of the original aperture. ap_r : `~photutils.RectangularAperture` The rectangular aperture of a pill box. ap_1, ap_2 : `~photutils.EllipticalAperture` The elliptical apertures of a pill box. The order of left/right ellipses is not important for this method. method : See `~photutils.PillBoxMaskMixin.to_mask` subpixels : See `~photutils.PillBoxMaskMixin.to_mask` min_mask : float, optional The mask values smaller than this value is ignored (set to 0). This is required because the subtraction of elliptical and rectangular masks give some negative values. One can set it to be ``1/subpixels**2`` because ``RectangularAperture`` does not support ``method='exact'`` yet. Returns ------- mask_pill : ndarray The mask of the pill box. """ aps = [] for ap in [ap_r, ap_1, ap_2]: pos_cent = ap.positions tmp_cent = pos_cent - np.array([bbox.ixmin, bbox.iymin]) if hasattr(ap, 'w'): tmp_ap = RectangularAperture(positions=tmp_cent, w=ap.w, h=ap.h, theta=ap.theta) else: tmp_ap = EllipticalAperture(positions=tmp_cent, a=ap.a, b=ap.b, theta=ap.theta) aps.append(tmp_ap) bbox_shape = bbox.shape mask_kw = dict(method=method, subpixels=subpixels) mask_r = (aps[0].to_mask(**mask_kw).to_image(bbox_shape)) mask_1 = (aps[1].to_mask(**mask_kw).to_image(bbox_shape)) mask_2 = (aps[2].to_mask(**mask_kw).to_image(bbox_shape)) # Remove both machine epsilon artifact & negative mask values: mask_pill_1 = mask_1 - mask_r mask_pill_1[mask_pill_1 < min_mask] = 0 mask_pill_2 = mask_2 - mask_r mask_pill_2[mask_pill_2 < min_mask] = 0 mask_pill = mask_r + mask_pill_1 + mask_pill_2 # Overlap of elliptical parts may make value > 1: mask_pill[mask_pill > 1] = 1 return mask_pill
def rectangle(pos, l, w, t): return RectangularAperture(pos, l, w, t)
def square(pos, l, w, theta): return RectangularAperture(pos, l, w, theta)
def extract_ifu(input_model, source_type, extract_params): """This function does the extraction. Parameters ---------- input_model : IFUCubeModel The input model. source_type : string "point" or "extended" extract_params : dict The extraction parameters for aperture photometry. Returns ------- ra, dec : float ra and dec are the right ascension and declination respectively at the nominal center of the image. wavelength : ndarray, 1-D The wavelength in micrometers at each plane of the IFU cube. temp_flux : ndarray, 1-D The sum of the data values in the extraction aperture minus the sum of the data values in the background region (scaled by the ratio of areas), for each plane. The data values are in units of surface brightness, so this value isn't really the flux, it's an intermediate value. Dividing by `npixels` (to compute the average) will give the value for the `surf_bright` (surface brightness) column, and multiplying by the solid angle of a pixel will give the flux for a point source. background : ndarray, 1-D The background count rate that was subtracted from the total source data values to get `temp_flux`. npixels : ndarray, 1-D, float64 For each slice, this is the number of pixels that were added together to get `temp_flux`. dq : ndarray, 1-D, uint32 The data quality array. """ data = input_model.data shape = data.shape if len(shape) != 3: log.error("Expected a 3-D IFU cube; dimension is %d.", len(shape)) raise RuntimeError("The IFU cube should be 3-D.") # We need to allocate temp_flux, background, npixels, and dq arrays # no matter what. We may need to divide by npixels, so the default # is 1 rather than 0. temp_flux = np.zeros(shape[0], dtype=np.float64) background = np.zeros(shape[0], dtype=np.float64) npixels = np.ones(shape[0], dtype=np.float64) dq = np.zeros(shape[0], dtype=np.uint32) # For an extended target, the entire aperture will be extracted, so # it makes no sense to shift the extraction location. if source_type != "extended": ra_targ = input_model.meta.target.ra dec_targ = input_model.meta.target.dec locn = locn_from_wcs(input_model, ra_targ, dec_targ) if locn is None or np.isnan(locn[0]): log.warning("Couldn't determine pixel location from WCS, so " "nod/dither correction will not be applied.") x_center = extract_params['x_center'] y_center = extract_params['y_center'] if x_center is None: x_center = float(shape[-1]) / 2. else: x_center = float(x_center) if y_center is None: y_center = float(shape[-2]) / 2. else: y_center = float(y_center) else: (x_center, y_center) = locn log.info("Using x_center = %g, y_center = %g, based on " "TARG_RA and TARG_DEC.", x_center, y_center) method = extract_params['method'] # subpixels is only needed if method = 'subpixel'. subpixels = extract_params['subpixels'] subtract_background = extract_params['subtract_background'] smaller_axis = float(min(shape[-2], shape[-1])) # for defaults radius = None inner_bkg = None outer_bkg = None if source_type == 'extended': # Ignore any input parameters, and extract the whole image. width = float(shape[-1]) height = float(shape[-2]) x_center = width / 2. - 0.5 y_center = height / 2. - 0.5 theta = 0. subtract_background = False else: radius = extract_params['radius'] if radius is None: radius = smaller_axis / 4. if subtract_background: inner_bkg = extract_params['inner_bkg'] if inner_bkg is None: inner_bkg = radius outer_bkg = extract_params['outer_bkg'] if outer_bkg is None: outer_bkg = min(inner_bkg * math.sqrt(2.), smaller_axis / 2. - 1.) if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: log.debug("Turning background subtraction off, due to " "the values of inner_bkg and outer_bkg.") subtract_background = False width = None height = None theta = None log.debug("IFU 1-D extraction parameters:") log.debug(" x_center = %s", str(x_center)) log.debug(" y_center = %s", str(y_center)) if source_type == 'point': log.debug(" radius = %s", str(radius)) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" inner_bkg = %s", str(inner_bkg)) log.debug(" outer_bkg = %s", str(outer_bkg)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) else: log.debug(" width = %s", str(width)) log.debug(" height = %s", str(height)) log.debug(" theta = %s degrees", str(theta)) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) x0 = float(shape[2]) / 2. y0 = float(shape[1]) / 2. (ra, dec, wavelength) = get_coordinates(input_model, x0, y0) position = (x_center, y_center) if source_type == 'point': aperture = CircularAperture(position, r=radius) else: aperture = RectangularAperture(position, width, height, theta) if subtract_background and inner_bkg is not None and outer_bkg is not None: annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) else: annulus = None # Compute the area of the aperture and possibly also of the annulus. normalization = 1. temp = np.ones(shape[-2:], dtype=np.float64) phot_table = aperture_photometry(temp, aperture, method=method, subpixels=subpixels) aperture_area = float(phot_table['aperture_sum'][0]) if LooseVersion(photutils.__version__) >= '0.7': log.debug("aperture.area = %g; aperture_area = %g", aperture.area, aperture_area) else: log.debug("aperture.area() = %g; aperture_area = %g", aperture.area(), aperture_area) if subtract_background and annulus is not None: # Compute the area of the annulus. phot_table = aperture_photometry(temp, annulus, method=method, subpixels=subpixels) annulus_area = float(phot_table['aperture_sum'][0]) if LooseVersion(photutils.__version__) >= '0.7': log.debug("annulus.area = %g; annulus_area = %g", annulus.area, annulus_area) else: log.debug("annulus.area() = %g; annulus_area = %g", annulus.area(), annulus_area) if annulus_area > 0.: normalization = aperture_area / annulus_area else: log.warning("Background annulus has no area, so background " "subtraction will be turned off.") subtract_background = False del temp npixels[:] = aperture_area for k in range(shape[0]): phot_table = aperture_photometry(data[k, :, :], aperture, method=method, subpixels=subpixels) temp_flux[k] = float(phot_table['aperture_sum'][0]) if subtract_background: bkg_table = aperture_photometry(data[k, :, :], annulus, method=method, subpixels=subpixels) background[k] = float(bkg_table['aperture_sum'][0]) temp_flux[k] = temp_flux[k] - background[k] * normalization # Check for NaNs in the wavelength array, flag them in the dq array, # and truncate the arrays if NaNs are found at endpoints (unless the # entire array is NaN). (wavelength, temp_flux, background, npixels, dq) = \ nans_in_wavelength(wavelength, temp_flux, background, npixels, dq) return (ra, dec, wavelength, temp_flux, background, npixels, dq)
def A_photometry(image_data, bg_err, factor=1, ape_sum=None, ape_sum_err=None, cx=15, cy=15, r=2.5, a=5, b=5, w_r=5, h_r=5, theta=0, shape='Circular', method='center'): """ Performs aperture photometry, first by creating the aperture (Circular, Rectangular or Elliptical), then it sums up the flux that falls into the aperture. Args: image_data (3D array): Data cube of images (2D arrays of pixel values). bg_err (1D array): Array of uncertainties on pixel value. factor (float, optional): Electron count to photon count factor. Default is 1 if none given. ape_sum (1D array, optional): Array of flux to append new flux values to. If None, the new values will be appended to an empty array ape_sum_err (1D array, optional): Array of flux uncertainty to append new flux uncertainty values to. If None, the new values will be appended to an empty array. cx (int, optional): x-coordinate of the center of the aperture. Default is 15. cy (int, optional): y-coordinate of the center of the aperture. Default is 15. r (int, optional): If shape is 'Circular', r is the radius for the circular aperture. Default is 2.5. a (int, optional): If shape is 'Elliptical', a is the semi-major axis for elliptical aperture (x-axis). Default is 5. b (int, optional): If shape is 'Elliptical', b is the semi-major axis for elliptical aperture (y-axis). Default is 5. w_r (int, optional): If shape is 'Rectangular', w_r is the full width for rectangular aperture (x-axis). Default is 5. h_r (int, optional): If shape is 'Rectangular', h_r is the full height for rectangular aperture (y-axis). Default is 5. theta (int, optional): If shape is 'Elliptical' or 'Rectangular', theta is the angle of the rotation angle in radians of the semimajor axis from the positive x axis. The rotation angle increases counterclockwise. Default is 0. shape (string, optional): shape is the shape of the aperture. Possible aperture shapes are 'Circular', 'Elliptical', 'Rectangular'. Default is 'Circular'. method (string, optional): The method used to determine the overlap of the aperture on the pixel grid. Possible methods are 'exact', 'subpixel', 'center'. Default is 'center'. Returns: tuple: ape_sum (1D array) Array of flux with new flux appended. ape_sum_err (1D array) Array of flux uncertainties with new flux uncertainties appended. """ if ape_sum is None: ape_sum = [] if ape_sum_err is None: ape_sum_err = [] l, h, w = image_data.shape tmp_sum = [] tmp_err = [] movingCentroid = (isinstance(cx, Iterable) or isinstance(cy, Iterable)) if not movingCentroid: position = [cx, cy] if (shape == 'Circular'): aperture = CircularAperture(position, r=r) elif (shape == 'Elliptical'): aperture = EllipticalAperture(position, a=a, b=b, theta=theta) elif (shape == 'Rectangular'): aperture = RectangularAperture(position, w=w_r, h=h_r, theta=theta) for i in range(l): if movingCentroid: position = [cx[i], cy[i]] if (shape == 'Circular'): aperture = CircularAperture(position, r=r) elif (shape == 'Elliptical'): aperture = EllipticalAperture(position, a=a, b=b, theta=theta) elif (shape == 'Rectangular'): aperture = RectangularAperture(position, w=w_r, h=h_r, theta=theta) data_error = calc_total_error(image_data[i, :, :], bg_err[i], effective_gain=1) phot_table = aperture_photometry( image_data[i, :, :], aperture, error=data_error, method=method) #, pixelwise_error=False) tmp_sum.extend(phot_table['aperture_sum'] * factor) tmp_err.extend(phot_table['aperture_sum_err'] * factor) # removing outliers tmp_sum = sigma_clip(tmp_sum, sigma=4, maxiters=2, cenfunc=np.ma.median) tmp_err = sigma_clip(tmp_err, sigma=4, maxiters=2, cenfunc=np.ma.median) ape_sum.extend(tmp_sum) ape_sum_err.extend(tmp_err) return ape_sum, ape_sum_err
def extract_ifu(input_model, source_type, extract_params): """This function does the extraction. Parameters ---------- input_model : IFUCubeModel The input model. source_type : string "POINT" or "EXTENDED" extract_params : dict The extraction parameters for aperture photometry. Returns ------- ra, dec : float ra and dec are the right ascension and declination respectively at the nominal center of the image. wavelength : ndarray, 1-D The wavelength in micrometers at each plane of the IFU cube. temp_flux : ndarray, 1-D The sum of the data values in the extraction aperture minus the sum of the data values in the background region (scaled by the ratio of areas), for each plane. The data values are in units of surface brightness, so this value isn't really the flux, it's an intermediate value. Dividing by `npixels` (to compute the average) will give the value for the `surf_bright` (surface brightness) column, and multiplying by the solid angle of a pixel will give the flux for a point source. background : ndarray, 1-D The background count rate that was subtracted from the total source data values to get `temp_flux`. npixels : ndarray, 1-D, float64 For each slice, this is the number of pixels that were added together to get `temp_flux`. dq : ndarray, 1-D, uint32 The data quality array. npixels_annulus : ndarray, 1-D, float64 For each slice, this is the number of pixels that were added together to get `temp_flux` for an annulus region. radius_match: ndarray,1-D, float64 The size of the extract radius in pixels used at each wavelength of the IFU cube x_center, y_center : float The x and y center of the extraction region """ data = input_model.data weightmap = input_model.weightmap shape = data.shape if len(shape) != 3: log.error("Expected a 3-D IFU cube; dimension is %d.", len(shape)) raise RuntimeError("The IFU cube should be 3-D.") # We need to allocate temp_flux, background, npixels, and dq arrays # no matter what. We may need to divide by npixels, so the default # is 1 rather than 0. temp_flux = np.zeros(shape[0], dtype=np.float64) background = np.zeros(shape[0], dtype=np.float64) npixels = np.ones(shape[0], dtype=np.float64) npixels_annulus = np.ones(shape[0], dtype=np.float64) dq = np.zeros(shape[0], dtype=np.uint32) # For an extended target, the entire aperture will be extracted, so # it makes no sense to shift the extraction location. if source_type != "EXTENDED": ra_targ = input_model.meta.target.ra dec_targ = input_model.meta.target.dec locn = locn_from_wcs(input_model, ra_targ, dec_targ) if locn is None or np.isnan(locn[0]): log.warning("Couldn't determine pixel location from WCS, so " "source offset correction will not be applied.") x_center = float(shape[-1]) / 2. y_center = float(shape[-2]) / 2. else: (x_center, y_center) = locn log.info("Using x_center = %g, y_center = %g, based on " "TARG_RA and TARG_DEC.", x_center, y_center) method = extract_params['method'] subpixels = extract_params['subpixels'] subtract_background = extract_params['subtract_background'] radius = None inner_bkg = None outer_bkg = None width = None height = None theta = None # pull wavelength plane out of input data. # using extract 1d wavelength, interpolate the radius, inner_bkg, outer_bkg to match input wavelength # find the wavelength array of the IFU cube x0 = float(shape[2]) / 2. y0 = float(shape[1]) / 2. (ra, dec, wavelength) = get_coordinates(input_model, x0, y0) # interpolate the extraction parameters to the wavelength of the IFU cube radius_match = None if source_type == 'POINT': wave_extract = extract_params['wavelength'].flatten() inner_bkg = extract_params['inner_bkg'].flatten() outer_bkg = extract_params['outer_bkg'].flatten() radius = extract_params['radius'].flatten() frad = interp1d(wave_extract, radius, bounds_error=False, fill_value="extrapolate") radius_match = frad(wavelength) # radius_match is in arc seconds - need to convert to pixels # the spatial scale is the same for all wavelengths do we only need to call compute_scale once. if locn is None: locn_use = (input_model.meta.wcsinfo.crval1, input_model.meta.wcsinfo.crval2, wavelength[0]) else: locn_use = (ra_targ, dec_targ, wavelength[0]) scale_degrees = compute_scale( input_model.meta.wcs, locn_use, disp_axis=input_model.meta.wcsinfo.dispersion_direction) scale_arcsec = scale_degrees*3600.00 radius_match /= scale_arcsec finner = interp1d(wave_extract, inner_bkg, bounds_error=False, fill_value="extrapolate") inner_bkg_match = finner(wavelength)/scale_arcsec fouter = interp1d(wave_extract, outer_bkg, bounds_error=False, fill_value="extrapolate") outer_bkg_match = fouter(wavelength)/scale_arcsec elif source_type == 'EXTENDED': # Ignore any input parameters, and extract the whole image. width = float(shape[-1]) height = float(shape[-2]) x_center = width / 2. - 0.5 y_center = height / 2. - 0.5 theta = 0. subtract_background = False log.debug("IFU 1-D extraction parameters:") log.debug(" x_center = %s", str(x_center)) log.debug(" y_center = %s", str(y_center)) if source_type == 'POINT': log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) else: log.debug(" width = %s", str(width)) log.debug(" height = %s", str(height)) log.debug(" theta = %s degrees", str(theta)) log.debug(" subtract_background = %s", str(subtract_background)) log.debug(" method = %s", method) if method == "subpixel": log.debug(" subpixels = %s", str(subpixels)) position = (x_center, y_center) # get aperture for extended it will not change with wavelength if source_type == 'EXTENDED': aperture = RectangularAperture(position, width, height, theta) annulus = None for k in range(shape[0]): inner_bkg = None outer_bkg = None if source_type == 'POINT': radius = radius_match[k] # this radius has been converted to pixels aperture = CircularAperture(position, r=radius) inner_bkg = inner_bkg_match[k] outer_bkg = outer_bkg_match[k] if inner_bkg <= 0. or outer_bkg <= 0. or inner_bkg >= outer_bkg: log.debug("Turning background subtraction off, due to " "the values of inner_bkg and outer_bkg.") subtract_background = False if subtract_background and inner_bkg is not None and outer_bkg is not None: annulus = CircularAnnulus(position, r_in=inner_bkg, r_out=outer_bkg) else: annulus = None subtract_background_plane = subtract_background # Compute the area of the aperture and possibly also of the annulus. # for each wavelength bin (taking into account empty spaxels) normalization = 1. temp = weightmap[k,:,:] temp[temp>1] = 1 aperture_area = 0 annulus_area = 0 # aperture_photometry - using weight map phot_table = aperture_photometry(temp, aperture, method=method, subpixels=subpixels) aperture_area = float(phot_table['aperture_sum'][0]) if LooseVersion(photutils.__version__) >= '0.7': log.debug("aperture.area = %g; aperture_area = %g", aperture.area, aperture_area) else: log.debug("aperture.area() = %g; aperture_area = %g", aperture.area(), aperture_area) if(aperture_area ==0 and aperture.area > 0): aperture_area = aperture.area if subtract_background and annulus is not None: # Compute the area of the annulus. phot_table = aperture_photometry(temp, annulus, method=method, subpixels=subpixels) annulus_area = float(phot_table['aperture_sum'][0]) if LooseVersion(photutils.__version__) >= '0.7': log.debug("annulus.area = %g; annulus_area = %g", annulus.area, annulus_area) else: log.debug("annulus.area() = %g; annulus_area = %g", annulus.area(), annulus_area) if(annulus_area ==0 and annulus.area > 0): annulus_area = annulus.area if annulus_area > 0.: normalization = aperture_area / annulus_area else: log.warning("Background annulus has no area, so background " "subtraction will be turned off. %g" ,k) subtract_background_plane = False del temp npixels[k] = aperture_area npixels_annulus[k] = 0.0 if annulus is not None: npixels_annulus[k] = annulus_area # aperture_photometry - using data phot_table = aperture_photometry(data[k, :, :], aperture, method=method, subpixels=subpixels) temp_flux[k] = float(phot_table['aperture_sum'][0]) if subtract_background_plane: bkg_table = aperture_photometry(data[k, :, :], annulus, method=method, subpixels=subpixels) background[k] = float(bkg_table['aperture_sum'][0]) temp_flux[k] = temp_flux[k] - background[k] * normalization # Check for NaNs in the wavelength array, flag them in the dq array, # and truncate the arrays if NaNs are found at endpoints (unless the # entire array is NaN). (wavelength, temp_flux, background, npixels, dq, npixels_annulus) = \ nans_in_wavelength(wavelength, temp_flux, background, npixels, dq, npixels_annulus) return (ra, dec, wavelength, temp_flux, background, npixels, dq, npixels_annulus, radius_match, x_center, y_center)
def A_photometry(bg_err, cx=15, cx_med=15, cy=15, cy_med=15, r=[2.5], a=[5], b=[5], w_r=[5], h_r=[5], theta=[0], scale=1, shape='Circular', methods=['center', 'exact'], moveCentroids=[True], i=0, img_data=None): """Performs aperture photometry, first by creating the aperture then summing the flux within the aperture. Note that this will implicitly use the global variable image_stack (3D array) to allow for parallel computing with large (many GB) datasets. Args: bg_err (1D array): Array of uncertainties on pixel value. cx (int/array, optional): x-coordinate(s) of the center of the aperture. Default is 15. cy (int/array, optional): y-coordinate(s) of the center of the aperture. Default is 15. r (int/array, optional): If shape is 'Circular', the radii to try for aperture photometry in units of pixels. Default is just 2.5 pixels. a (int/array, optional): If shape is 'Elliptical', the semi-major axes to try for elliptical aperture in units of pixels. Default is 5. b (int/array, optional): If shape is 'Elliptical', the semi-minor axes to try for elliptical aperture in units of pixels. Default is 5. w_r (int/array, optional): If shape is 'Rectangular', the full widths to try for rectangular aperture (x-axis). Default is 5. h_r (int/array, optional): If shape is 'Rectangular', the full heights to try for rectangular aperture (y-axis). Default is 5. theta (int/array, optional): If shape is 'Elliptical' or 'Rectangular', the rotation angles in radians of the semimajor axis from the positive x axis. The rotation angle increases counterclockwise. Default is 0. scale (int, optional): If the image is oversampled, scaling factor for centroid and bounds, i.e, give centroid in terms of the pixel value of the initial image. shape (string, optional): shape is the shape of the aperture. Possible aperture shapes are 'Circular', 'Elliptical', 'Rectangular'. Default is 'Circular'. methods (iterable, optional): The methods used to determine the overlap of the aperture on the pixel grid. Possible methods are 'exact', 'subpixel', 'center'. Default is ['center', 'exact']. i (int, optional): The current frame number being examined. img_data (3D array): The image stack being analyzed if not using the global variable to allow for parallel computing. Returns: tuple: results (2D array) Array of flux and flux errors, of shape (nMethods*nSizes, 2), where the nSizes loop is nested inside the nMethods loop which is itself nested inside the moveCentoids loop. """ # Access the global variable if img_data is None: global image_stack else: image_stack = img_data if not isinstance(r, Iterable): r = [r] if not isinstance(cx, Iterable): cx = [cx] if not isinstance(cy, Iterable): cy = [cy] if not isinstance(a, Iterable): a = [a] if not isinstance(b, Iterable): b = [b] if not isinstance(w_r, Iterable): w_r = [w_r] if not isinstance(h_r, Iterable): h_r = [h_r] if not isinstance(theta, Iterable): theta = [theta] if not isinstance(methods, Iterable): methods = [methods] data_error = calc_total_error(image_stack[i, :, :], bg_err[i], effective_gain=1) results = [] for moveCentroid in moveCentroids: # Set up aperture(s) apertures = [] if not moveCentroid: position = [cx_med * scale, cy_med * scale] else: position = [cx[i] * scale, cy[i] * scale] if (shape == 'Circular'): for j in range(len(r)): apertures.append(CircularAperture(position, r=r[j] * scale)) elif (shape == 'Elliptical'): for j in range(len(a)): apertures.append( EllipticalAperture(position, a=a[j] * scale, b=b[j] * scale, theta=theta[j])) elif (shape == 'Rectangular'): for j in range(len(w_r)): apertures.append( RectangularAperture(position, w=w_r[j] * scale, h=h_r[j] * scale, theta=theta[j])) for method in methods: phot_table = aperture_photometry(image_stack[i, :, :], apertures, error=data_error, method=method) results.extend([ float(phot_table[f'aperture_sum_{j}']) for j in range(len(apertures)) ]) return np.array(results)
def show_stamps(pscs, frame_idx=None, stamp_size=11, aperture_position=None, aperture_size=None, show_residual=False, stretch=None, save_name=None, show_max=False, show_pixel_grid=False, **kwargs): if aperture_position is None: midpoint = (stamp_size - 1) / 2 aperture_position = (midpoint, midpoint) if aperture_size: aperture = RectangularAperture(aperture_position, w=aperture_size, h=aperture_size, theta=0) ncols = len(pscs) if show_residual: ncols += 1 nrows = 1 fig = Figure() FigureCanvas(fig) fig.set_figheight(4) fig.set_figwidth(8) if frame_idx is not None: s0 = pscs[0][frame_idx] s1 = pscs[1][frame_idx] else: s0 = pscs[0] s1 = pscs[1] if stretch == 'log': stretch = LogStretch() else: stretch = LinearStretch() norm = ImageNormalize(s0, interval=MinMaxInterval(), stretch=stretch) ax1 = fig.add_subplot(nrows, ncols, 1) im = ax1.imshow(s0, cmap=get_palette(), norm=norm) if aperture_size: aperture.plot(color='r', lw=4, ax=ax1) # annulus.plot(color='c', lw=2, ls='--', ax=ax1) # create an axes on the right side of ax. The width of cax will be 5% # of ax and the padding between cax and ax will be fixed at 0.05 inch. # https://stackoverflow.com/questions/18195758/set-matplotlib-colorbar-size-to-match-graph divider = make_axes_locatable(ax1) cax = divider.append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax) ax1.set_title('Target') # Comparison ax2 = fig.add_subplot(nrows, ncols, 2) im = ax2.imshow(s1, cmap=get_palette(), norm=norm) if aperture_size: aperture.plot(color='r', lw=4, ax=ax1) # annulus.plot(color='c', lw=2, ls='--', ax=ax1) divider = make_axes_locatable(ax2) cax = divider.append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax) ax2.set_title('Comparison') if show_pixel_grid: add_pixel_grid(ax1, stamp_size, stamp_size, show_superpixel=False) add_pixel_grid(ax2, stamp_size, stamp_size, show_superpixel=False) if show_residual: ax3 = fig.add_subplot(nrows, ncols, 3) # Residual residual = s0 - s1 im = ax3.imshow(residual, cmap=get_palette(), norm=ImageNormalize(residual, interval=MinMaxInterval(), stretch=LinearStretch())) divider = make_axes_locatable(ax3) cax = divider.append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax) ax3.set_title('Noise Residual') ax3.set_title('Residual RMS: {:.01%}'.format(residual.std())) ax3.set_yticklabels([]) ax3.set_xticklabels([]) if show_pixel_grid: add_pixel_grid(ax1, stamp_size, stamp_size, show_superpixel=False) # Turn off tick labels ax1.set_yticklabels([]) ax1.set_xticklabels([]) ax2.set_yticklabels([]) ax2.set_xticklabels([]) if save_name: try: fig.savefig(save_name) except Exception as e: warn("Can't save figure: {}".format(e)) return fig
def asymmetric_elongated_aperture_photom(scidata, starts, inverted, jpeg='', plot=False, sat_adu = 16383, spectral_band='G'): ''' Compute photometry using rectangular apertures. This is a good method to use for "April 2014" de Bruijn encoding ''' logger = logging.getLogger() logger.debug(f'science image is dimensions: {np.shape(scidata)}') match, factor = table_matches_image(starts, scidata) if not match: logger.warning(f'table data does not match image data. Dividing table coordinates by a factor of {factor:.2f} to table data (spectral band is {spectral_band})') starts['x_image'] /= factor starts['y_image'] /= factor positions, radii, thetas = aperture_centers_weighted_start_differential(starts) # get median pixel value inside the fireball bounding box median_bckgnd = np.median(get_bounding_box(scidata, starts)) if plot: plt.close() fig = plt.gcf() ax = fig.gca() plt.imshow(jpeg, cmap=plt.cm.gray) starts['aperture_sum'] = np.nan starts['bckgng_sub_measurements'] = np.nan starts['SNR'] = np.nan starts['signal'] = np.nan starts['exp_time'] = np.nan starts['aperture_sum_err'] = np.nan starts['aperture_sum_err_plus'] = np.nan starts['aperture_sum_err_minus'] = np.nan starts['aperture_saturated'] = False for i in range(len(starts)-1): starts[i]['exp_time'] = LC_open_time(starts, i, i+1, inverted) for el, pos, radius, theta in zip(starts[:-1], positions, radii, thetas): #zero_order_light = scidata[int(pos[1])][int(pos[0])] aperture = RectangularAperture(pos, w=radius*2, h=7.*2, theta=theta) # 7 # determine if any of the pixels in the aperture have reached saturation level, # flag accordingly aperture_mask = aperture.to_mask('center') aperture_values = aperture_mask.multiply(scidata) if np.any(aperture_values == sat_adu): el['aperture_saturated'] = True # do photometry aperture_result = aperture_photometry(scidata, aperture, method='subpixel', subpixels=16) el['aperture_sum'] = aperture_result['aperture_sum'].data #el['aperture_sum_err'] = aperture_result['aperture_sum_err'].data TODO FIXME el['bckgng_sub_measurements'] = el['aperture_sum'] - aperture.area*median_bckgnd el['SNR'] = el['aperture_sum'] / np.sqrt(el['aperture_sum'] + aperture.area*median_bckgnd) el['aperture_sum_err_plus'] = 1/el['SNR'] el['aperture_sum_err_minus'] = 1/el['SNR'] if plot: aperture.plot(color='white') if plot: #min_x, min_y, max_x, max_y = get_bounds([p['x_image'] for p in points], pos2) ax.set_xlim([np.min(starts['x_image']), np.max(starts['x_image'])]) ax.set_ylim([np.min(starts['y_image']), np.max(starts['y_image'])]) full_path = starts.meta['self_file_name'] dirname = os.path.dirname(full_path) basename = os.path.basename(full_path).split('.')[0] fname = os.path.join(dirname, basename + "_aperture_photometry_"+spectral_band+".jpg") plt.savefig(fname, dpi=150) return starts
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 measure_flux(fitsimage, detections=None, pixel_coords=None, skycoords=None, method='single-aperture', a=None, b=None, theta=None, n_boostrap=100, minimal_aperture_size=1, aperture_size=None, aperture_scale=6.0, gaussian_segment_scale=4.0, plot=False, ax=None, color='white', debug=False): """Accurate flux measure Args: fitsimage: the FitsImage class detections: astropy.table, including all source position and shapes pixel_coords: the pixel coordinates of the detections skycoords: the sky coordinates of the detections. aperture_size: the fixed size of the aperture, in arcsec a,b,theta: the size of the source, in arcsec and deg minimal_aperture_size: if the aperture_size is None, this can control the minial aperture_size for the fain source, where the adaptive aperture could not be securely measured aperture_scale: the source shape determined aperture, lower priority than aperture_size Note: When several coordinates parameters are provided, detections has the higher priority """ pixel2arcsec = fitsimage.pixel2deg_ra * 3600 arcsec2pixel = 1 / pixel2arcsec if detections is not None: if len(detections) < 1: print('No source founded...') return None, None dets_colnames = detections.colnames # if ('x' in dets_colnames) and ('y' in dets_colnames): # pixel_coords = np.array(list(zip(detections['x'], detections['y']))) if ('ra' in dets_colnames) and ('dec' in dets_colnames): ra = np.array([detections['ra']]) dec = np.array([detections['dec']]) skycoords = SkyCoord(ra.flatten(), dec.flatten(), unit='deg') pixel_coords = np.array( list(zip(*skycoord_to_pixel(skycoords, fitsimage.wcs)))) if aperture_scale is not None: if a is None: # in arcsec if 'a' in detections.colnames: a = detections['a'] else: a = fitsimage.bmaj * 0.5 * 3600 if b is None: if 'b' in detections.colnames: b = detections['b'] else: b = fitsimage.bmin * 0.5 * 3600 if theta is None: # in deg if 'theta' in detections.colnames: theta = detections['theta'] else: theta = fitsimage.bpa elif skycoords is not None: pixel_coords = np.array( list(zip(*skycoord_to_pixel(skycoords, fitsimage.wcs)))) if a is None: a = fitsimage.bmaj * 0.5 * 3600 if b is None: b = fitsimage.bmin * 0.5 * 3600 if theta is None: theta = fitsimage.bpa # in deg elif pixel_coords is not None: if a is None: a = fitsimage.bmaj * 0.5 * 3600 if b is None: b = fitsimage.bmin * 0.5 * 3600 if theta is None: theta = fitsimage.bpa # in deg else: print("Nothing to do...") return None, None n_sources = len(pixel_coords) # define aperture for all the detections if aperture_scale is not None: if isinstance(a, (tuple, list, np.ndarray)): a_aper = aperture_scale * a * arcsec2pixel # a_aper = aperture_scale*a*u.arcsec else: a_aper = np.full(n_sources, aperture_scale * a * arcsec2pixel) if isinstance(b, (tuple, list, np.ndarray)): b_aper = aperture_scale * b * arcsec2pixel # b_aper = aperture_scale*b*u.arcsec else: b_aper = np.full(n_sources, aperture_scale * b * arcsec2pixel) if minimal_aperture_size is not None: minimal_aperture_size_in_pixel = minimal_aperture_size * arcsec2pixel a_aper[ a_aper < minimal_aperture_size_in_pixel] = minimal_aperture_size_in_pixel b_aper[ b_aper < minimal_aperture_size_in_pixel] = minimal_aperture_size_in_pixel if not isinstance(theta, (tuple, list, np.ndarray)): theta = np.full(n_sources, theta) if aperture_size is not None: aperture_size_pixel = aperture_size * arcsec2pixel a_aper = np.full(n_sources, aperture_size_pixel) b_aper = np.full(n_sources, aperture_size_pixel) theta = np.full(n_sources, 0) apertures = [] for i, coord in enumerate(pixel_coords): apertures.append( EllipticalAperture(coord, a_aper[i], b_aper[i], theta[i])) # apertures.append(SkyEllipticalAperture(skycoords, a_aper[i], b_aper[i], theta[i])) detections_mask = np.zeros(fitsimage.imagesize, dtype=bool) for mask in apertures: image_aper_mask = mask.to_mask().to_image(shape=fitsimage.imagesize) if image_aper_mask is not None: detections_mask = detections_mask + image_aper_mask else: continue detections_mask = (detections_mask > 0) | fitsimage.imagemask detections_apers = [] flux = np.zeros(n_sources) fluxerr = np.zeros(n_sources) if method == 'single-aperture': # measuring flux density for i, aper in enumerate(apertures): x, y = pixel_coords[i] pixel_fluxscale = 1. / (fitsimage.beamsize) detections_apers.append( EllipticalAperture([x, y], a_aper[i], b_aper[i], theta[i])) if fitsimage.has_pbcor: phot_table = aperture_photometry(fitsimage.image_pbcor, aper, mask=fitsimage.imagemask) else: phot_table = aperture_photometry(fitsimage.image, aper, mask=fitsimage.imagemask) flux[i] = phot_table['aperture_sum'].value * pixel_fluxscale # measuring the error of flux density # run the boostrap for random aperture with the image pixel_x = np.random.random(n_boostrap) * fitsimage.imagesize[ 1] # 1 for x axis pixel_y = np.random.random(n_boostrap) * fitsimage.imagesize[ 0] # 0 for y axis # points_select = (pixel_x**2 + pixel_y**2) < (np.min(fitsimage.imagesize)-np.max([a,b]))**2 # points_select = (pixel_x-**2 + pixel_y**2) > (np.max([a,b]))**2 # pixel_coords_boostrap = np.vstack([pixel_x[points_select], pixel_y[points_select]]).T pixel_coords_boostrap = np.vstack([pixel_x, pixel_y]).T apertures_boostrap = EllipticalAperture(pixel_coords_boostrap, a_aper[i], b_aper[i], theta[i]) noise_boostrap = aperture_photometry(fitsimage.image, apertures_boostrap, mask=detections_mask) fluxerr[i] = np.std( np.ma.masked_invalid( noise_boostrap['aperture_sum'])) * pixel_fluxscale # fluxerr[i] = np.std(noise_boostrap['aperture_sum']) * pixel_fluxscale if fitsimage.has_pbcor: pixel_pbcor = 1. / fitsimage.image_pb[int(np.round(x)), int(np.round(y))] fluxerr[i] = fluxerr[i] * pixel_pbcor if method == 'gaussian': seg_size = gaussian_segment_scale * np.int(fitsimage.bmaj_pixel) segments = RectangularAperture(pixel_coords, seg_size, seg_size, theta=0) segments_mask = segments.to_mask(method='center') for i, s in enumerate(segments_mask): x, y = pixel_coords[i] pixel_fluxscale = 1 / (fitsimage.beamsize) image_cutout = s.cutout(fitsimage.image) gaussian_fitting = gaussian_2Dfitting(image_cutout, debug=debug) flux[i] = gaussian_fitting['flux'] * pixel_fluxscale # boostrap for noise measurement a_fitted_aper = 1.0 * 2.355 * gaussian_fitting[ 'x_stddev'] # 2xFWHM of gaussian b_fitted_aper = 1.0 * 2.355 * gaussian_fitting['y_stddev'] theta_fitted = gaussian_fitting['theta'] detections_apers.append( EllipticalAperture([x, y], a_fitted_aper, b_fitted_aper, theta_fitted)) pixel_x = np.random.random(n_boostrap) * fitsimage.imagesize[ 1] # 1 for x axis pixel_y = np.random.random(n_boostrap) * fitsimage.imagesize[ 0] # 0 for y axis pixel_coords_boostrap = np.vstack([pixel_x, pixel_y]).T apertures_boostrap = EllipticalAperture(pixel_coords_boostrap, a_fitted_aper, b_fitted_aper, theta_fitted) noise_boostrap = aperture_photometry(fitsimage.image, apertures_boostrap, mask=detections_mask) fluxerr[i] = np.std( np.ma.masked_invalid( noise_boostrap['aperture_sum'])) * pixel_fluxscale if fitsimage.has_pbcor: pixel_pbcor = 1. / fitsimage.image_pb[int(np.round(x)), int(np.round(y))] flux[i] = flux[i] * pixel_pbcor fluxerr[i] = fluxerr[i] * pixel_pbcor if plot: if ax is None: fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(fitsimage.image, interpolation='nearest', vmin=-0.2 * fitsimage.std, vmax=10.0 * fitsimage.std, origin='lower') plt.colorbar(im, fraction=0.046, pad=0.04) for i in range(n_sources): obj = pixel_coords[i] im = detections_apers[i].plot(color=color, lw=1, alpha=0.8) ax.text( obj[0], (1.0 - 2.0 * detections_apers[i].a / fitsimage.imagesize[0]) * obj[1], "{:.2f}mJy".format(flux[i] * 1e3), color=color, horizontalalignment='center', verticalalignment='top', ) # # only for test # for ap in apertures_boostrap: # im = ap.plot(color='gray', lw=2, alpha=0.2) return flux, fluxerr