def make_varcube(self, maskfile=None, **kwargs): """ Takes the 1-dimensional variance spectrum and converts it into a 3-dimensional data cube, where each slice is created by multiplying an integer version of the mask (where 1 is good and 0 and bad) by the variance that has been computed for that slice. This method requires that the make_varspec method has been run first. """ """ Make the variance spectrum if it has not already been done """ if self.varspec is None: self.make_varspec(maskfile, **kwargs) """ Create the container for the variance cube """ self['var'] = WcsHDU(self.data, self.header) self['var'].data *= 0. """ Step through the slices, making each one a 2d variance slice """ for imslice, var in enumerate(self.varspec['flux']): """ Get the 2-dimensional mask that is appropriate for this slice """ if self.mask.ndim == 3: mask2d = self.mask[:, :, imslice] else: mask2d = np.transpose(self.mask) """ Set the good pixels to the variance value for the slice """ self['var'].data[:, :, imslice][mask2d] = var
def make_snrcube(self, maskfile=None): """ Use the information in the variance spectrum to make a SNR cube """ """ Make sure that the variance spectrum exists """ if self.varspec is None: print('') print('In order to run the make_snrcube algorithm, you first have ' 'to run make_varspec') print('') return """ Make a copy of the data cube and step through the slices, dividing each by the associated rms value """ cube = self.data.copy() rms = np.sqrt(self.varspec['flux']) mask = (np.transpose(self.mask)).astype(float) for i, r in enumerate(rms): cube[:, :, i] *= (mask / r) """ Save the result """ self['snr'] = WcsHDU(cube, self.header) del (cube)
def read_maskfile(self, maskfile, maskdir='../Clean', debug=False): """ Reads an external file that will be used as a mask for the spatial data. The data in the file should be set up so that values > 0 indicate good data, while 0 indicates bad data. The information gets saved as the 'mask' attribute of the class, which is a boolean array. """ """ Set up the mask filename if 'default' was chosen """ if maskfile == 'default': mfile = 'mask_%s_%s.fits' % (self.filt, self.lenslet) if maskdir is not None: maskfile = os.path.join(maskdir, mfile) else: maskfile = mfile if debug: print(maskfile) """ Load the information from the file and convert the data into a boolean format """ mhdu = WcsHDU(maskfile, wcsverb=False) self.mask = mhdu.data > 0.
def slice_cube(self, wlim=None, dmode='input', outroot='slice', debug=False): """ Splits the cube into all of its individual slices and saves them to disk """ """ Get the range of wavelength slices to extract from the cube. The default is to use the full wavelength range. """ if wlim is not None: wmin = wlim[0] wmax = wlim[1] else: wmin = 0 wmax = self.wsize """ Make a wcs header for the slice, i.e., a set of 2d wcs information without the spectral information. """ w2dhdr = self.make_wcs2dhdr() """ Flip the data cube if it hasn't already been done """ if 'xyz' not in self: data = self[dmode].data.swapaxes(0, 2) wcsinfo = self.wcsinfo.swapaxes(0, 2) hdr = wcsinfo.to_header() self['xyz'] = WcsHDU(data, hdr, wcsverb=False) else: data = self['xyz'].data """ Extract the slices """ for w in range(wmin, wmax): outname = '%s_%03d.fits' % (outroot, w) dat = data[w, :, :] pf.PrimaryHDU(dat, w2dhdr).writeto(outname, overwrite=True)
def smooth_xy(self, kwidth, smtype='median', outfile=None): """ Smooths the cube over the two spatial dimensions. The type of smoothing set by the smtype parameter. This could be one of the following: 'gauss': a circular gaussian with sigma=kwidth 'median': a median filter with side length=kwidth (more to come) kwidth is given in pixels """ """ Smooth the data """ data = self.data sm = smtype.lower() if sm == 'gauss' or sm == 'guass' or sm == 'gaussian': cube = filters.gaussian_filter(data, sigma=[kwidth, kwidth, 0]) smotype = 'Gaussian' elif sm == 'median' or sm == 'medfilt': cube = filters.median_filter(data, size=[kwidth, kwidth, 1]) smotype = 'Median filter' else: print('') print('Smoothing type %s has not been implemented' % smtype) print('') raise NameError """ Put the smoothed data into a new WcsHDU """ hdr = self.header.copy() hdr['history'] = 'Data have been spatially smoothed' hdr['smotype'] = smotype hdr['smoothw'] = ('%5.1f' % kwidth, 'Smoothing kernel width') self['smooth'] = WcsHDU(cube, hdr) """ Save the smoothed cube in an output file if desired """ if outfile: print('') print('Wrote smoothed data cube to %s' % outfile) self['smooth'].writeto(outfile, overwrite=True) print('')
def clean(self, nsig1=5., nsig2=5., smtype='median', smsize=3, skysub=True, verbose=False): """ Does a bad-pixel cleaning, slice by slice. The basic algorithm is the following: 1. Create a smoothed (on the slices but not in wavelength) version of the data using the smooth_xy method 2. Use the pixel-to-pixel variance for each plane (obtained previously by running the make_varspec method) to flag pixels that satisfy both of the following conditions: A. Deviate from the clipped mean by more than nsig1 sigma B. Deviate from the smoothed version of the data by more than nsig2 sigma This should flag bad pixels without incorrectly flagging pixels that are high due to the presence of a real astronomical object 3. Replace the flagged pixels with the corresponding value in the smooth data """ """ Make sure that the variance spectrum exists """ if self.varspec is None: print('') print('In order to run the clean algorithm, you first have to ' 'run make_varspec') print('') return """ Create a smoothed version of the data and subtract it from the input data """ data = self.data.copy() self.smooth_xy(smsize, smtype) diff = np.fabs(data - self['smooth'].data) """ Step through the slices, flagging the pixels that differ too much from both the clipped mean value and the smoothed data """ if verbose: print('') print('Slice N_flag') print('----- ------') rms = np.sqrt(self.varspec['flux']) for i, r in enumerate(rms): """ Subtract the sky if requested """ if skysub: slmean = self.meanspec[i] else: slmean = 0. smdat = self['smooth'].data[:, :, i] - slmean mdiff = np.fabs(data[:, :, i] - slmean) data[:, :, i] -= slmean mask = (mdiff > nsig1 * r) & (diff[:, :, i] > nsig2 * r) data[:, :, i][mask] = smdat[mask] if verbose: print(' %3d %5d' % (i, mask.sum())) """ Make sure that the regions outside the illuminated part of the chip are set to zero, since they may have been set to a non-zero value in the sky subtraction """ data[np.transpose(np.logical_not(self.mask))] = 0. """ Save the cleaned cube """ self['clean'] = WcsHDU(data, self.header)
def compress_spec(self, wlim=None, xlim=None, ylim=None, wmode='slice', dmode='input', combmode='sum', display=True, verbose=True, **kwargs): """ Compresses the data cube along the spectral dimension, but only for image slices between some minimum and maximum wavelengths. These wavelength limits (wlim) can be set either by the slice number or the actual wavelength in Angstrom [wavelength mode is NOT yet implemented]. Setting wlim=None (the default) will use the full wavelength range The compression can be done either as a sum or as a median. The result is a 2-dimensional spatial image, which is stored in the data container and, thus, can be easily displayed. """ """ First select the image slices to use """ if wmode == 'wavelength': print('NOTE: wavelength mode has not yet been implemented') return else: if wlim is None: wlim = (0, self.wsize - 1) minslice = wlim[0] maxslice = wlim[1] wavmin = self.wav[minslice] wavmax = self.wav[maxslice] if verbose: print('') print('Data cube will be compressed along the spectral direction') print(' Image slice range: %d - %d' % (minslice, maxslice)) print(' Corresponding wavelength range: %8.2f - %8.2f' % (wavmin, wavmax)) """ Create a temporary cube container """ cube, cubehdr = self.select_cube(wlim, xlim, ylim, dmode=dmode, verbose=verbose) """ Make a wcs header for the slice that will be the output of the compression, i.e., a set of 2d wcs information without the spectral information. """ w2dhdr = self.make_wcs2dhdr(hdr=cubehdr) """ Compress the temporary cube along the spectral axis """ if combmode == 'median': self['slice'] = WcsHDU(np.transpose(np.median(cube, axis=2)), w2dhdr) else: self['slice'] = WcsHDU(np.transpose(cube.sum(axis=2)), w2dhdr) """ Display the result if requested """ if display: self.display(dmode='slice', **kwargs) """ Clean up """ del (cube)
def set_imslice(self, imslice, dmode='input', display=True, mode='xy', **kwargs): """ Sets the 2-dimension slice to use for the display functions. Inputs: imslice - which image slice to use. """ """ Get the number of dimensions in the input image """ hdr = self.header if 'NAXIS' in hdr.keys(): ndim = hdr['naxis'] else: raise KeyError('No NAXIS keyword in fits header') """ The OSIRIS data-reduction pipeline produces a 3-dimensional data cube. Check this """ if ndim != 3: print('') print('ERROR: Expected a 3-dimensional data cube but found ' '%d dimensions' % ndim) print('') raise ValueError """ Select the image slice to use. Right now this assumes the standard axis order produced by the pipeline, namely that CTYPE1 is WAVE, CTYPE2 is RA, and CTYPE3 is DEC. This means that the data array structure is hard-wired to this assumption """ if imslice >= self.wsize: print('') print('ERROR: Requested an image slice outside the available ' 'range') print('Maximum available slice value is %d' % (self.wsize - 1)) print('') raise IndexError """ Make a wcs header for the slice, i.e., a set of 2d wcs information without the spectral information. """ w2dhdr = self.make_wcs2dhdr() """ Actually make the slice. The transpose is to get RA and Dec into the order that the WcsHDU container expects them to be The header information. """ # self['slice'] = WcsHDU(np.transpose(self.data[:, :, imslice])) self['slice'] = WcsHDU(np.transpose(self[dmode].data[:, :, imslice]), w2dhdr, wcsverb=False) """ Display the image slice if requested """ self.found_rms = False if display: self.display(dmode='slice', mode=mode, title='Image Slice %d (zero-indexed)' % imslice, **kwargs)