def __init__(self, data): """ Initialize the object by loading the portion of the image to be analyzed. """ """ Put the input data into the class """ self.data = data.copy() """ Set some default parameters """ self.rms_clip = None self.mean_clip = None """ It may be that the astropy model fitters don't work when the input data has NaN's in it. Therefore replace any NaN's or infinities with Gaussian noise """ goodmask = np.isfinite(data) if goodmask.sum() < data.size: self.mean_clip, self.rms_clip = df.sigclip(data) noise = np.random.normal(self.mean_clip, self.rms_clip, data.shape) badmask = np.logical_not(goodmask) self.data[badmask] = noise[badmask] del badmask """ Define the coordinate arrays """ self.y, self.x = np.indices(data.shape) """ Clean up """ del goodmask
def sigma_clip(self, nsig=3., statsec=None, mask=None, verbose=False): """ Runs a sigma-clipping on image data. After doing outlier rejection the code returns the mean and rms of the clipped data. This method is just a minimal wrapper for the sigclip method in the cdfutils.datafuncs library. NOTE: The region used for determining these image statistics is set by the following decision path: - if statsec is not None, use statsec - else, use the entire image for the second option, an optional mask can be used to exclude known bad pixels from the calculation. Optional inputs: nsig - Number of sigma from the mean beyond which points are rejected. Default=3. statsec - Region of the input image to be used to determine the image statistics, defined by the coordinates of its corners (x1, y1, x2, y2). If this variable is is None (the default value) then the image statistics will be determined from: - the subimage, if it has been set - else, the entire image. The format for statsec can be any of the following: 1. A 4-element numpy array 2. A 4-element list: [x1, y1, x2, y2] 3. A 4-element tuple: (x1, y1, x2, y2) 4. statsec=None. In this case, the region used for determining the pixel statistics defaults to either the subimage (if defined) or the full image (if no subimage has been defined) mask - If some of the input data are known to be bad, they can be flagged before the inputs are computed by including a mask. This mask must be set such that True indicates good data and False indicates bad data verbose - If False (the default) no information is printed """ """ Determine what the input data set is """ scdata = self.data.copy() if statsec is not None: x1, y1, x2, y2 = statsec scdata = scdata[y1:y2, x1:x2] """ Find the clipped mean and rms """ mu, sig = df.sigclip(scdata, nsig=nsig, mask=mask, verbose=verbose) """ Store the results and clean up """ del scdata self.found_rms = True self.mean_clip = mu self.rms_clip = sig return
def get_ap_oldham(self, slit, apcent, nsig, ordinfo, doplot=True): """ Defines a uniform aperture as in the example ESI extraction scripts from Lindsay """ B = ordinfo['pixmin'] R = ordinfo['pixmax'] xproj = np.median(slit[:, B:R], 1) m, s = df.sigclip(xproj) smooth = ndimage.gaussian_filter(xproj, 1) if ordinfo['name'] == 'Order 3': smooth = ndimage.gaussian_filter(xproj[:-30], 1) x = np.arange(xproj.size) * 1. """ The four parameters immediately below are the initial guesses bkgd, amplitude, mean location, and sigma for a Gaussian fit """ fit = np.array([0., smooth.max(), smooth.argmax(), 1.]) fit = sf.ngaussfit(xproj, fit)[0] cent = fit[2] + apcent / ordinfo['pixscale'] apymax = 0.1 * xproj.max() ap = np.where(abs(x - cent) < nsig / ordinfo['pixscale'], 1., 0.) # slit.apmin = cent - nsig # slit.apmax = cent + nsig if doplot: plt.subplot(2, 5, (ordinfo['order'])) plt.plot(x, apymax * ap) # Scale the aperture to easily see it plt.plot(x, xproj) plt.ylim(-apymax, 1.1 * xproj.max()) plt.axvline(cent, color='k', ls='dotted') ap = ap.repeat(slit.shape[1]).reshape(slit.shape) return ap, fit
def moments(self, x0, y0, rmax=10., detect_thresh=3., skytype='global', verbose=False): """ flux-weighted first and second moments within a square centered on the initial guess point and with side length of 2*rmax + 1. The moments will be estimates of the centroid and sigma of the light distribution within the square. Inputs: x0 - initial guess for x centroid y0 - initial guess for y centroid rmax - used to set size of image region probed, which will be a square with side = 2*rmax + 1. Default=10 skytype - set how the sky/background level is set. The options are: 'global' - Use the clipped mean as determined by the sigma_clip method. This is the default. None - Don't do any sky/background subtraction """ """ Select the data within the square of interest """ x1, x2 = x0-rmax-1, x0+rmax+1 y1, y2 = y0-rmax-1, y0+rmax+1 pixmask = (self.x > x1) & (self.x < x2) & (self.y > y1) & \ (self.y < y2) """ Subtract the sky level if requested """ if skytype is None: f = self.data[pixmask] else: if self.rms_clip is None: self.mean_clip, self.rms_clip = df.sigclip(self.data) if verbose: print(self.mean_clip, self.rms_clip) f = self.data[pixmask] - self.mean_clip """ Get the x and y coordinates associated with the region """ x = self.x[pixmask] y = self.y[pixmask] """ Select pixels that are significantly above background for the calculation """ objmask = f > self.mean_clip + detect_thresh * self.rms_clip fgood = f[objmask] """ Calculate the flux-weighted moments NOTE: Do the moment calculations relative to (x1, y1) -- and then add x1 and y1 back at the end -- in order to avoid rounding errors (see SExtractor user manual) """ xgood = x[objmask] - x1 ygood = y[objmask] - y1 fsum = fgood.sum() mux = (fgood * xgood).sum() / fsum muy = (fgood * ygood).sum() / fsum sigxx = (fgood * xgood**2).sum() / fsum - mux**2 sigyy = (fgood * ygood**2).sum() / fsum - muy**2 sigxy = (fgood * xgood * ygood).sum() / fsum - mux * muy mux += x1 muy += y1 if verbose: print(mux, muy) print(sqrt(sigxx), sqrt(sigyy), sigxy) """ Package the results in a dictionary, clean up, and return """ del x, y, xgood, ygood, fgood, pixmask, objmask outdict = {'mux': mux, 'muy': muy, 'sigxx': sigxx, 'sigxy': sigxy, 'sigyy': sigyy} return outdict
def slice_stats(self, imslice, dmode='input', nsig=3., verbose=False, debug=False): """ Calculates a mean and variance associated with the selected slice. The statistics are calculated within the good region of the slice, which is set by the mask if the mask has been loaded into the OsCube object. The returned values are the clipped mean and the square of the clipped rms, where "clipped" means that the statistics are calculated after a sigma-clipping routine that rejects obvious outliers has been run. Required inputs: imslice - the image slice for which the statistics are calculated Optional inputs: verbose - Report the image statistics? """ """ Get the 2-dimensional mask that is appropriate for this slice. NOTE: Ordinarily, if we were getting the mask as a 2d slice from a 3d mask, we would have to transpose the mask to match the image slice, since the slices are generally set up to have RA along the x axis. Here, however, since we just care about image statistics and not the WCS orientation, we don't transpose the image slices. This does, however, mean that a 2d mask does need to be transposed, since it is assuming the standard WCS orientation. """ if self.mask.ndim == 3: mask2d = self.mask[:, :, imslice] else: mask2d = np.transpose(self.mask) """ Select the requested slice from the science data cube and calculate its statistics """ # self.set_imslice(imslice, display=False) # self['slice'].sigma_clip(mask=mask2d, verbose=False) # mean = self['slice'].mean_clip # r = self['slice'].rms_clip data = self[dmode].data[:, :, imslice] mean, r = df.sigclip(data, nsig=nsig, mask=mask2d, verbose=False) var = r**2 if debug: print('Total pixels in slice: %d' % self['slice'].data.size) print('Number of good pixels: %d' % mask2d.sum()) self['slice'].sigma_clip(verbose=False) print('Unmasked rms: %f' % self['slice'].rms_clip) print('Masked rms: %f' % r) print('') """ Report statistics, if requested, and return the mean and variance """ if verbose: print(imslice, mean, r) return mean, var