示例#1
0
def test_flat_correct_min_value(ccd_data):
    size = ccd_data.shape[0]

    # create the flat
    data = 2 * np.random.normal(loc=1.0, scale=0.05, size=(size, size))
    flat = CCDData(data, meta=fits.header.Header(), unit=ccd_data.unit)
    flat_orig_data = flat.data.copy()
    min_value = 2.1  # should replace some, but not all, values
    flat_corrected_data = flat_correct(ccd_data, flat, min_value=min_value)
    flat_with_min = flat.copy()
    flat_with_min.data[flat_with_min.data < min_value] = min_value

    # Check that the flat was normalized. The asserts below, which look a
    # little odd, are correctly testing that
    #    flat_corrected_data = ccd_data / (flat_with_min / mean(flat_with_min))
    np.testing.assert_almost_equal(
        (flat_corrected_data.data * flat_with_min.data).mean(),
        (ccd_data.data * flat_with_min.data.mean()).mean()
    )
    np.testing.assert_allclose(ccd_data.data / flat_corrected_data.data,
                               flat_with_min.data / flat_with_min.data.mean())

    # Test that flat is not modified.
    assert (flat_orig_data == flat.data).all()
    assert flat_orig_data is not flat.data
示例#2
0
def test_flat_correct_min_value(ccd_data):
    size = ccd_data.shape[0]

    # create the flat
    data = 2 * np.random.normal(loc=1.0, scale=0.05, size=(size, size))
    flat = CCDData(data, meta=fits.header.Header(), unit=ccd_data.unit)
    flat_orig_data = flat.data.copy()
    min_value = 2.1  # should replace some, but not all, values
    flat_corrected_data = flat_correct(ccd_data, flat, min_value=min_value)
    flat_with_min = flat.copy()
    flat_with_min.data[flat_with_min.data < min_value] = min_value

    # Check that the flat was normalized. The asserts below, which look a
    # little odd, are correctly testing that
    #    flat_corrected_data = ccd_data / (flat_with_min / mean(flat_with_min))
    np.testing.assert_almost_equal(
        (flat_corrected_data.data * flat_with_min.data).mean(),
        (ccd_data.data * flat_with_min.data.mean()).mean()
    )
    np.testing.assert_allclose(ccd_data.data / flat_corrected_data.data,
                               flat_with_min.data / flat_with_min.data.mean())

    # Test that flat is not modified.
    assert (flat_orig_data == flat.data).all()
    assert flat_orig_data is not flat.data
示例#3
0
def make_master_flat(table,
                     mbias,
                     sigma=3,
                     iters=5,
                     min_value=0,
                     colname_file='file',
                     colname_exptime='EXPTIME',
                     colname_nx='NAXIS1',
                     colname_ny='NAXIS2',
                     dtype=np.float32,
                     output=''):
    ''' Make master flat from the given flat table and master bias.
    
    Dark subtraction is not implemented yet.
    '''

    if not isinstance(mbias, CCDData):
        mbias = CCDData(data=mbias, unit='adu')
    else:
        mbias = mbias.copy()

    Nccd = len(table)

    exptimes = check_exptime(table=table,
                             colname_file=colname_file,
                             colname_nx=colname_nx,
                             colname_ny=colname_ny,
                             colname_exptime=colname_exptime)

    flat_orig = initialize_2Dcombiner(table=table,
                                      colname_nx=colname_nx,
                                      colname_ny=colname_ny,
                                      dtype=dtype)

    for i in range(Nccd):
        flat_orig[:, :,
                  i] = (fits.getdata(table[colname_file][i]) / exptimes[i])

    print_info(Nccd=Nccd, min_value=min_value, sigma=sigma, iters=iters)
    mflat = clip_median_combine(flat_orig,
                                sigma=sigma,
                                iters=iters,
                                min_value=min_value,
                                axis=-1)

    print('and bias subtraction ...')
    mflat -= mbias
    mflat = mflat.astype(dtype)

    if output != '':
        print('Saving to {:s}'.format(output))
        hdu = fits.PrimaryHDU(data=mflat.data)
        hdu.writeto(output, overwrite=True)

    return mflat.data
示例#4
0
def make_master_dark(table,
                     mbias,
                     sigma=3,
                     iters=5,
                     min_value=0,
                     colname_file='file',
                     colname_exptime='EXPTIME',
                     colname_nx='NAXIS1',
                     colname_ny='NAXIS2',
                     dtype=np.float32,
                     output=''):
    '''Make dark frame from the given dark table
    '''

    if not isinstance(mbias, CCDData):
        mbias = CCDData(data=mbias, unit='adu')
    else:
        mbias = mbias.copy()

    Nccd = len(table)

    exptimes = check_exptime(table=table,
                             colname_file=colname_file,
                             colname_nx=colname_nx,
                             colname_ny=colname_ny,
                             colname_exptime=colname_exptime)

    dark_orig = initialize_2Dcombiner(table=table,
                                      colname_nx=colname_nx,
                                      colname_ny=colname_ny,
                                      dtype=dtype)

    for i in range(Nccd):
        dark_orig[:, :, i] = ((fits.getdata(table[colname_file][i]) - mbias) /
                              exptimes[i])
    print_info(Nccd=Nccd, min_value=min_value, sigma=sigma, iters=iters)
    mdark = clip_median_combine(dark_orig,
                                sigma=sigma,
                                iters=iters,
                                min_value=min_value,
                                axis=-1)
    mdark = mdark.astype(dtype)

    if output != '':
        print('Saving to {:s}'.format(output))
        hdu = fits.PrimaryHDU(data=mdark.data)
        hdu.writeto(output, overwrite=True)

    return mdark.data
示例#5
0
class image(object):
    '''
    A single image object.

    Functions
    ---------
    * Read from fits file use CCDData.
    * get_size : Get the image size.
    * plot : Plot the image.
    * sigma_clipped_stats : Calculate the basic statistics of the image.
    * set_data : Load from numpy array.
    * set_mask : Set image mask.
    * set_pixel_scales : Set the pixel scales along two axes.
    * set_zero_point : Set magnitude zero point.
    '''
    def __init__(self,
                 filename=None,
                 hdu=0,
                 unit=None,
                 zero_point=None,
                 pixel_scales=None,
                 wcs_rotation=None,
                 mask=None,
                 verbose=True):
        '''
        Parameters
        ----------
        filename (optional) : string
            FITS file name of the image.
        hdu : int (default: 0)
            The number of extension to load from the FITS file.
        unit (optional) : string
            Unit of the image flux for CCDData.
        zero_point (optional) : float
            Magnitude zero point.
        pixel_scales (optional) : tuple
            Pixel scales along the first and second directions, units: arcsec.
        wcs_rotation (optional) : float
            WCS rotation, east of north, units: radian.
        mask (optional) : 2D bool array
            The image mask.
        verbose : bool (default: True)
            Print out auxiliary data.
        '''
        if filename is None:
            self.data = None
        else:
            self.data = CCDData.read(filename, hdu=hdu, unit=unit, mask=mask)
            if self.data.wcs and (pixel_scales is None):
                pixel_scales = proj_plane_pixel_scales(
                    self.data.wcs) * u.degree.to('arcsec')

        self.zero_point = zero_point
        if pixel_scales is None:
            self.pixel_scales = None
        else:
            self.pixel_scales = (pixel_scales[0] * u.arcsec,
                                 pixel_scales[1] * u.arcsec)

        if self.data.wcs and (wcs_rotation is None):
            self.wcs_rotation = get_wcs_rotation(self.data.wcs)
        elif wcs_rotation is not None:
            self.wcs_rotation = wcs_rotation * u.radian
        else:
            self.wcs_rotation = None
        self.sources_catalog = None
        self.sigma_image = None
        self.sources_skycord = None
        self.ss_data = None
        self.PSF = None

    def get_size(self, units='pixel'):
        '''
        Get the size of the image.

        Parameters
        ----------
        units : string
            Units of the size (pixel or angular units).

        Returns
        -------
        x, y : float
            Size along X and Y axes.
        '''
        nrow, ncol = self.data.shape
        if units == 'pixel':
            x = ncol
            y = nrow
        else:
            x = ncol * self.pixel_scales[0].to(units).value
            y = nrow * self.pixel_scales[1].to(units).value
        return (x, y)

    def get_size(self, units='pixel'):
        '''
        Get the size of the image.

        Parameters
        ----------
        units : string
            Units of the size (pixel or angular units).

        Returns
        -------
        x, y : float
            Size along X and Y axes.
        '''
        nrow, ncol = self.data.shape
        if units == 'pixel':
            x = ncol
            y = nrow
        else:
            x = ncol * self.pixel_scales[0].to(units).value
            y = nrow * self.pixel_scales[1].to(units).value
        return (x, y)

    def get_data_info(self):
        '''
        Data information to generate model image.

        Returns
        -------
        d : dict
            shape : (ny, nx)
                Image array shape.
            pixel_scale : (pixelscale_x, pixelscale_y), default units: arcsec
                Pixel scales.
            wcs_rotation : angle, default units: radian
                WCS rotation, east of north.
        '''
        d = dict(shape=self.data.shape,
                 pixel_scale=self.pixel_scale,
                 wcs_rotation=self.wcs_rotation)
        return d

    def sigma_clipped_stats(self, **kwargs):
        '''
        Run astropy.stats.sigma_clipped_stats to get the basic statistics of
        the image.

        Parameters
        ----------
        All of the parameters go to astropy.stats.sigma_clipped_stats().

        Returns
        -------
        mean, median, stddev : float
            The mean, median, and standard deviation of the sigma-clipped data.
        '''
        return sigma_clipped_stats(self.data.data,
                                   mask=self.data.mask,
                                   **kwargs)

    def plot(self,
             stretch='asinh',
             units='arcsec',
             vmin=None,
             vmax=None,
             a=None,
             ax=None,
             plain=False,
             **kwargs):
        '''
        Plot an image.

        Parameters
        ----------
        stretch : string (default: 'asinh')
            Choice of stretch: asinh, linear, sqrt, log.
        units : string (default: 'arcsec')
            Units of pixel scale.
        vmin (optional) : float
            Minimal value of imshow.
        vmax (optional) : float
            Maximal value of imshow.
        a (optional) : float
            Scale factor of some stretch function.
        ax (optional) : matplotlib.Axis
            Axis to plot the image.
        plain : bool (default: False)
            If False, tune the image.
        **kwargs : Additional parameters goes into plt.imshow()

        Returns
        -------
        ax : matplotlib.Axis
            Axis to plot the image.
        '''
        assert self.data is not None, 'Set data first!'
        ax = plot_image(self.data,
                        self.pixel_scales,
                        stretch=stretch,
                        units=units,
                        vmin=vmin,
                        vmax=vmax,
                        a=a,
                        ax=ax,
                        plain=plain,
                        **kwargs)
        if plain is False:
            ax.set_xlabel(r'$\Delta X$ ({0})'.format(units), fontsize=24)
            ax.set_ylabel(r'$\Delta Y$ ({0})'.format(units), fontsize=24)
        return ax

    def plot_direction(self,
                       ax,
                       xy=(0, 0),
                       len_E=None,
                       len_N=None,
                       color='k',
                       fontsize=20,
                       linewidth=2,
                       frac_len=0.1,
                       units='arcsec',
                       backextend=0.05):
        '''
        Plot the direction arrow. Only applied to plots using WCS.

        Parameters
        ----------
        ax : Axis
            Axis to plot the direction.
        xy : (x, y)
            Coordinate of the origin of the arrows.
        length : float
            Length of the arrows, units: pixel.
        units: string (default: arcsec)
            Units of xy.
        '''
        xlim = ax.get_xlim()
        len_total = np.abs(xlim[1] - xlim[0])
        pixelscale = self.pixel_scales[0].to('degree').value
        if len_E is None:
            len_E = len_total * frac_len / pixelscale
        if len_N is None:
            len_N = len_total * frac_len / pixelscale

        wcs = self.data.wcs
        header = wcs.to_header()
        d_ra = len_E * pixelscale
        d_dec = len_N * pixelscale
        ra = [header['CRVAL1'], header['CRVAL1'] + d_ra, header['CRVAL1']]
        dec = [header['CRVAL2'], header['CRVAL2'], header['CRVAL2'] + d_dec]
        ra_pix, dec_pix = wcs.all_world2pix(ra, dec, 1)
        d_arrow1 = [ra_pix[1] - ra_pix[0], dec_pix[1] - dec_pix[0]]
        d_arrow2 = [ra_pix[2] - ra_pix[0], dec_pix[2] - dec_pix[0]]
        l_arrow1 = np.sqrt(d_arrow1[0]**2 + d_arrow1[1]**2)
        l_arrow2 = np.sqrt(d_arrow2[0]**2 + d_arrow2[1]**2)
        d_arrow1 = np.array(d_arrow1) / l_arrow1 * len_E * pixelscale
        d_arrow2 = np.array(d_arrow2) / l_arrow2 * len_N * pixelscale

        def sign_2_align(sign):
            '''
            Determine the alignment of the text.
            '''
            if sign[0] < 0:
                ha = 'right'
            else:
                ha = 'left'
            if sign[1] < 0:
                va = 'top'
            else:
                va = 'bottom'
            return ha, va

        ha1, va1 = sign_2_align(np.sign(d_arrow1))
        ha2, va2 = sign_2_align(np.sign(d_arrow2))

        xy_e = (xy[0] - d_arrow1[0] * backextend,
                xy[1] - d_arrow1[1] * backextend)
        ax.annotate('E',
                    xy=xy_e,
                    xycoords='data',
                    fontsize=fontsize,
                    xytext=(d_arrow1[0] + xy[0], d_arrow1[1] + xy[1]),
                    color=color,
                    arrowprops=dict(color=color, arrowstyle="<-",
                                    lw=linewidth),
                    ha=ha1,
                    va=va1)
        xy_n = (xy[0] - d_arrow2[0] * backextend,
                xy[1] - d_arrow2[1] * backextend)
        ax.annotate('N',
                    xy=xy_n,
                    xycoords='data',
                    fontsize=fontsize,
                    xytext=(d_arrow2[0] + xy[0], d_arrow2[1] + xy[1]),
                    color=color,
                    arrowprops=dict(color=color, arrowstyle="<-",
                                    lw=linewidth),
                    ha=ha2,
                    va=va2)

    def set_data(self, data, unit):
        '''
        Parameters
        ----------
        data : 2D array
            Image data.
        unit : string
            Unit for CCDData.
        '''
        self.data = CCDData(data, unit=unit)

    def source_detection_individual(self, psfFWHM, nsigma=3.0, sc_key=''):
        '''
        Parameters
        ----------
        psfFWHM : float
            FWHM of the imaging point spread function
        nsigma : float
            source detection threshold
        '''
        data = np.array(self.data.copy())
        psfFWHMpix = psfFWHM / self.pixel_scales[0].value
        thresholder = detect_threshold(data, nsigma=nsigma)
        sigma = psfFWHMpix * gaussian_fwhm_to_sigma
        kernel = Gaussian2DKernel(sigma, x_size=5, y_size=5)
        kernel.normalize()
        segm = detect_sources(data,
                              thresholder,
                              npixels=5,
                              filter_kernel=kernel)
        props = source_properties(data, segm)
        tab = Table(props.to_table())
        self.sources_catalog = tab
        srcPstradec = self.data.wcs.all_pix2world(tab['xcentroid'],
                                                  tab['ycentroid'], 1)
        sc = SkyCoord(srcPstradec[0], srcPstradec[1], unit='deg')
        sctab = Table([sc, np.arange(len(sc))],
                      names=['sc', 'sloop_{0}'.format(sc_key)])
        self.sources_skycord = sctab

    def make_mask(self, sources=None, magnification=3.):
        '''
        make mask for the extension.

        Parameters
        ----------
        sources : a to-be masked source table (can generate from photutils source detection)
                  if None, will use its own source catalog
        magnification : expand factor to generate mask
        '''
        mask = np.zeros_like(self.data, dtype=bool)
        mask[np.isnan(self.data)] = True
        mask[np.isinf(self.data)] = True
        if sources is None:
            sources = self.sources_catalog
        for loop in range(len(sources)):
            position = (sources['xcentroid'][loop], sources['ycentroid'][loop])
            a = sources['semimajor_axis_sigma'][loop]
            b = sources['semiminor_axis_sigma'][loop]
            theta = sources['orientation'][loop] * 180. / np.pi
            mask = Maskellipse(mask, position, magnification * a, (1 - b / a),
                               theta)
        self.data.mask = mask
        if self.ss_data is not None:
            self.ss_data.mask = mask

    def set_mask(self, mask):
        '''
        Set mask for the extension.

        Parameters
        ----------
        mask : 2D array
            The mask.
        '''
        assert self.data.shape == mask.shape, 'Mask shape incorrect!'
        self.data.mask = mask
        if self.ss_data is not Nont:
            self.ss_data.mask = mask

    def set_pixel_scales(self, pixel_scales):
        '''
        Parameters
        ----------
        pixel_scales (optional) : tuple
            Pixel scales along the first and second directions, units: arcsec.
        '''
        self.pixel_scales = (pixel_scales[0] * u.arcsec,
                             pixel_scales[1] * u.arcsec)

    def set_zero_point(self, zp):
        '''
        Set magnitude zero point.
        '''
        self.zero_point = zp

    def sky_subtraction(self, order=3, filepath=None):
        '''
        Do polynomial-fitting sky subtraction
        Parameters
        ----------
        order (optional) : int
            order of the polynomial
        '''
        data = np.array(self.data.copy())
        maskplus = self.data.mask.copy()
        backR = polynomialfit(data, maskplus.astype(bool), order=order)
        background = backR['bkg']
        self.ss_data = CCDData(data - background, unit=self.data.unit)
        self.ss_data.mask = maskplus
        if filepath is not None:
            hdu_temp = fits.PrimaryHDU(data - background)
            hdu_temp.writeto(filepath, overwrite=True)

    def read_ss_image(self, filepath):
        '''
        read sky subtracted image from "filepath"
        '''
        hdu = fits.open(filepath)
        self.ss_data = CCDData(hdu[0].data, unit=self.data.unit)
        self.ss_data.mask = self.data.mask.copy()

    def cal_sigma_image(self, filepath=None):
        '''
    	Construct sigma map following the same procedure as Galfit (quadruture sum of sigma at each pixel from source and sky background).
    	Note
        ----------
    	'GAIN' keyword must be available in the image header and ADU x GAIN = electron

    	Parameters
        ----------
    		filepath:
    			Whether and where to save sigma map
        '''
        GAIN = self.data.header['CELL.GAIN']
        if self.ss_data is None:
            raise ValueError(" Please do sky subtration first !!!")
        data = np.array(self.ss_data.copy())
        mask = self.ss_data.mask.copy()
        bkgrms = np.nanstd(data[~mask.astype(bool)])
        data[~mask.astype(bool)] = 0.
        sigmap = np.sqrt(data / GAIN + bkgrms**2)
        self.sigma_image = sigmap
        if filepath is not None:
            hdu_temp = fits.PrimaryHDU(sigmap)
            hdu_temp.writeto(filepath, overwrite=True)

    def read_sigmap(self, filepath):
        '''
        read sigma image from "filepath"
        '''
        hdu = fits.open(filepath)
        self.sigma_image = hdu[0].data

    def read_PSF(self, filepath):
        '''
        read PSF image from "filepath"
        '''
        hdu = fits.open(filepath)
        self.PSF = hdu[0].data
        vmin, vmax = np.percentile(scidata.data, (15., 99.5))
        ax1.imshow(scidata.data, vmin=vmin, vmax=vmax)
        ax1.plot(x, y, marker='o', mec='r', mfc='none', ls='none')

        norm = ImageNormalize(refdata_reproj, PercentileInterval(99.))
        ax2.imshow(refdata_reproj, norm=norm)
        ax2.plot(x, y, marker='o', mec='r', mfc='none', ls='none')

    template_filename = os.path.join(workdir, 'template.fits')
    refdata_reproj[np.isnan(refdata_reproj)] = 0.
    refdata = CCDData(refdata_reproj, wcs=scidata.wcs, mask=1-refdata_foot, unit='adu')
    refdata.write(template_filename, overwrite=True)

    # # Make the PSF for the reference image
    refdata_unmasked = refdata.copy()
    refdata_unmasked.mask = np.zeros_like(refdata, bool)
    ref_psf, _ = make_psf(refdata_unmasked, catalog, show=show)

    # # Subtract the images and view the result

    output_filename = os.path.join(workdir, 'diff.fits')
    science = ImageClass(scidata.data, sci_psf.data, header=scidata.header, saturation=65565)
    reference = ImageClass(refdata.data, ref_psf.data, refdata.mask)
    difference = calculate_difference_image(science, reference, show=show)
    difference_zero_point = calculate_difference_image_zero_point(science, reference)
    normalized_difference = normalize_difference_image(difference, difference_zero_point, science, reference, 'i')
    save_difference_image_to_file(normalized_difference, science, 'i', output_filename)

    if show:
        vmin, vmax = np.percentile(difference, (15., 99.5))