Example #1
0
def get_winpos(data, x, y, a, subsampling=5):
    """
    Get windowed position. These are more accurate positions calculated
    by SEP (SExtractor) created by iteratively recentering on the area
    contained by the half-flux radius.
    
    Parameters
    ----------
    data: `~numpy.array`
        Image data
    x: array-like
        X coordinates of the sources
    y: array-like
        Y coordinates of the sources
    subsampling: int, optional
        Number of subpixels for each image pixel
        (used to calculate the half flux radius).
        Default=5.
    """
    import sep
    r, flag = sep.flux_radius(data, x, y, 
        6.*a, 0.5, subpix=subsampling)
    sig = 2. / 2.35 * r
    xwin, ywin, flags = sep.winpos(data, x, y, sig)
    return xwin, ywin
Example #2
0
File: test.py Project: cmccully/sep
def test_flux_radius():
    data = np.ones(data_shape)
    fluxfrac = [0.2**2, 0.3**2, 0.7**2, 1.]
    true_r = [2., 3., 7., 10.]
    r, _ = sep.flux_radius(data, x, y, 10.*np.ones_like(x),
                           [0.2**2, 0.3**2, 0.7**2, 1.], subpix=5)
    for i in range(len(fluxfrac)):
        assert_allclose(r[:, i], true_r[i], rtol=0.01)
Example #3
0
    def _measure(self, img, sources, mask=None):

        logger.info('measuring source parameters')

        # HACK: issues with numerical precision
        # must have pi/2 <= theta <= npi/2
        sources[np.abs(np.abs(sources['theta']) - np.pi/2) < 1e-6] = np.pi/2

        for p in ['x', 'y', 'a', 'b', 'theta']:
            sources = sources[~np.isnan(sources[p])]

        # calculate "AUTO" parameters
        kronrad, krflag = sep.kron_radius(
            img, sources['x'], sources['y'], sources['a'], sources['b'],
            sources['theta'], 6.0, mask=mask)
    
        flux, fluxerr, flag = sep.sum_ellipse(
            img, sources['x'], sources['y'], sources['a'], sources['b'],
            sources['theta'], 2.5*kronrad, subpix=5, mask=mask)
        flag |= krflag  # combine flags into 'flag'

        sources = sources[~np.isnan(flux)]
        flux = flux[~np.isnan(flux)]
        sources = sources[flux > 0]
        flux = flux[flux > 0]

        mag_auto = utils.zpt - 2.5*np.log10(flux)
        r, flag = sep.flux_radius(
            img, sources['x'], sources['y'], 6.*sources['a'], 0.5,
            normflux=flux, subpix=5, mask=mask)

        sources['mag_auto'] = mag_auto
        sources['flux_auto'] = flux
        sources['flux_radius'] = r * utils.pixscale

        # approximate fwhm 
        r_squared = sources['a']**2 + sources['b']**2
        sources['fwhm'] = 2 * np.sqrt(np.log(2) * r_squared) *  utils.pixscale
        
        q = sources['b'] / sources['a']
        area = np.pi * q * sources['flux_radius']**2
        sources['mu_ave_auto'] = sources['mag_auto']  + 2.5 * np.log10(2*area)
        
        area_arcsec = np.pi * (self.psf_fwhm/2)**2 * utils.pixscale**2
        flux, fluxerr, flag = sep.sum_circle(
            img, sources['x'], sources['y'], self.psf_fwhm/2, 
            subpix=5, mask=mask)
        flux[flux<=0] = np.nan
        mu_0 = utils.zpt - 2.5*np.log10(flux / area_arcsec)

        sources['mu_0_aper'] = mu_0

        return sources
Example #4
0
def find_flux(tbdat_sub, objects, kronrad, kronflag):
	flux, fluxerr, flag = sep.sum_ellipse(tbdat_sub, objects['x'], objects['y'], objects['a'], objects['b'], objects['theta'], pho_auto_A = (2.5*kronrad), err = bkg.globalrms, subpix=1)
	flag |=kronflag #combines all flags
	r_min = 1.75 #minimum diameter = 3.5
	use_circle = kronrad * np.sqrt(a * b) < r_min
	cflux, cfluxerr, cflag = sep.sum_circle(tbdat_sub, objects['x'][use_circle], objects['y'][use_circle], r_min, subpix=1)
	flux[use_circle] = cflux
	fluxerr[use_circle] = cfluxerr
	flag[use_circle] = cflag
	r, rflag = sep.flux_radius(data, x, y, 6.0*objects['a'], rmax = 0.5, normflux = flux, subpix =5)
	sig = 2.0 / (2.35*r) # r from sep.flux_radius() above, with fluxfrac = 0.5
	xwin, ywin, wflag = sep.winpos(tbdat_sub, objects['x'], objects['y'], sig)
	return flux, fluxerr, flag, r, xwin, ywin
Example #5
0
def make_HSC_detect_mask(bin_msk,
                         img,
                         objects,
                         segmap,
                         r=10.0,
                         radius=1.5,
                         threshold=0.01):
    '''Make HSC detection and bright star mask, 
    based on HSC binary mask flags.
    
    Parameters:
    -----------
    bin_msk: 2-D np.array, can be loaded from HSC image cutouts
    objects: table, returned from sep.extract_obj
    segmap: 2-D np.array, returned from sep.extract_obj
    r: float, blow-up parameter
    radius: float, convolution radius
    threshold: float, threshold of making mask after convolution

    Returns:
    -----------
    HSC_detect_mask: 2-D boolean np.array
    
    See also:
    -----------------
    convert_HSC_binary_mask(bin_msk)
    '''
    import sep
    TDmask = convert_HSC_binary_mask(bin_msk)
    cen_mask = np.zeros(bin_msk.shape, dtype=np.bool)
    cen_obj = objects[segmap[int(bin_msk.shape[0] / 2.),
                             int(bin_msk.shape[1] / 2.)] - 1]

    fraction_radius = sep.flux_radius(img, cen_obj['x'], cen_obj['y'],
                                      10 * cen_obj['a'], 0.5)[0]
    ba = np.divide(cen_obj['b'], cen_obj['a'])
    sep.mask_ellipse(cen_mask,
                     cen_obj['x'],
                     cen_obj['y'],
                     fraction_radius,
                     fraction_radius * ba,
                     cen_obj['theta'],
                     r=r)
    from astropy.convolution import convolve, Gaussian2DKernel
    HSC_mask = (TDmask[:, :, 5]).astype(bool) * (
        ~cen_mask) + TDmask[:, :, 9].astype(bool)
    # Convolve the image with a Gaussian kernel with the width of 1.5 pixel
    cvl = convolve(HSC_mask.astype('float'), Gaussian2DKernel(radius))
    HSC_detect_mask = cvl >= threshold
    return HSC_detect_mask
Example #6
0
    def _get_flux_auto(self, objs):
        flux_auto = np.zeros(objs.size) - 9999.0
        fluxerr_auto = np.zeros(objs.size) - 9999.0
        flux_radius = np.zeros(objs.size) - 9999.0
        kron_radius = np.zeros(objs.size) - 9999.0

        w, = np.where((objs['a'] >= 0.0) & (objs['b'] >= 0.0)
                      & (objs['theta'] >= -np.pi / 2.)
                      & (objs['theta'] <= np.pi / 2.))

        if w.size > 0:
            kron_radius[w], krflag = sep.kron_radius(
                self.image,
                objs['x'][w],
                objs['y'][w],
                objs['a'][w],
                objs['b'][w],
                objs['theta'][w],
                6.0,
            )
            objs['flag'][w] |= krflag

            aper_rad = 2.5 * kron_radius
            flux_auto[w], fluxerr_auto[w], flag_auto = \
                sep.sum_ellipse(
                    self.image,
                    objs['x'][w],
                    objs['y'][w],
                    objs['a'][w],
                    objs['b'][w],
                    objs['theta'][w],
                    aper_rad[w],
                    subpix=1,
                )
            objs['flag'][w] |= flag_auto

            flux_radius[w], frflag = sep.flux_radius(
                self.image,
                objs['x'][w],
                objs['y'][w],
                6. * objs['a'][w],
                PHOT_FLUXFRAC,
                normflux=flux_auto[w],
                subpix=5,
            )
            objs['flag'][w] |= frflag  # combine flags into 'flag'

        return flux_auto, fluxerr_auto, flux_radius, kron_radius
Example #7
0
def measureHfd(data):
    bkg = sep.Background(data)
    thresh = 3 * bkg.globalrms
    objects = sep.extract(data - bkg, thresh)
    # taken from g-tecs autoFocus.py
    hfr, mask = sep.flux_radius(data,
                                objects['x'],
                                objects['y'],
                                30 * np.ones_like(objects['x']),
                                0.5,
                                normflux=objects['cflux'])
    mask = np.logical_and(mask == 0, objects['peak'] < 40000)
    hfd = 2 * hfr[mask]
    if hfd.size > 3:
        mean, median, std = sigma_clipped_stats(hfd, sigma=2.5, iters=10)
        return median, std
    return 0.0, 0.0
Example #8
0
def findSpot(data, sigma):
    image=data
    #m, s = np.mean(image), np.std(image)
    bkg = sep.Background(image, bw=32, bh=32, fw=3, fh=3)
    objs = sep.extract(image-bkg, sigma, err=bkg.globalrms)
    aper_radius=3
    
    # Calculate the Kron Radius
    kronrad, krflag = sep.kron_radius(image, objs['x'], objs['y'], \
        objs['a'], objs['b'], objs['theta'], aper_radius)

    r_min = 3
    use_circle = kronrad * np.sqrt(objs['a'] * objs['b'])
    cinx=np.where(use_circle <= r_min)
    einx=np.where(use_circle > r_min)

    # Calculate the equivalent of FLUX_AUTO
    flux, fluxerr, flag = sep.sum_ellipse(image, objs['x'][einx], objs['y'][einx], \
        objs['a'][einx], objs['b'][einx], objs['theta'][einx], 2.5*kronrad[einx],subpix=1)		

    cflux, cfluxerr, cflag = sep.sum_circle(image, objs['x'][cinx], objs['y'][cinx],
                                    objs['a'][cinx], subpix=1)

    # Adding half pixel to measured coordinate.  
    objs['x'] =  objs['x']+0.5
    objs['y'] =  objs['y']+0.5

    objs['flux'][einx]=flux
    objs['flux'][cinx]=cflux


    r, flag = sep.flux_radius(image, objs['x'], objs['y'], \
        6*objs['a'], 0.3,normflux=objs['flux'], subpix=5)

    flag |= krflag
 
    objs=rfn.append_fields(objs, 'r', data=r, usemask=False)

    objects=objs[:]
    
    return objects
Example #9
0
def measureHfd(img):
    data = fits.open(img)[0].data
    y, x = data.shape
    xcen = int(x / 2)
    ycen = int(y / 2)
    # grab the middle 2k x 2k
    stats_area = np.array(data[ycen-1024: ycen+1024, \
                               xcen-1024: xcen+1024]).astype(np.int32).copy(order='C')
    bkg = sep.Background(stats_area)
    thresh = 3 * bkg.globalrms
    objects = sep.extract(stats_area - bkg, thresh)
    # taken from g-tecs autoFocus.py
    hfr, mask = sep.flux_radius(stats_area,
                                objects['x'],
                                objects['y'],
                                30 * np.ones_like(objects['x']),
                                0.5,
                                normflux=objects['cflux'])
    mask = np.logical_and(mask == 0, objects['peak'] < 40000)
    hfd = 2 * hfr[mask]
    if hfd.size > 3:
        mean, median, std = sigma_clipped_stats(hfd, sigma=2.5, iters=10)
        return median, std
    return 0.0, 0.0
Example #10
0
def sourceExtract(data,
                  thresh=3,
                  bkg=False,
                  bkg_rms=None,
                  err=None,
                  mask=None,
                  min_area=5,
                  deblend_cont=0.05,
                  segment=False,
                  extras=False):
    """
    Extract all sources above a certain threshold in the given image
    
    Parameters
    ----------
    data : array-like
        CCD image frame from which to extract sources
    thresh : float, optional
        Number of sigma a detection must be above the background to 
        be flagged as a source - if err not given, bkg_rms is needed
        Default = 3
    bkg : bool, optional
        Toggle to model spatially varying background and subtract from
        data - by default assumes this has been done separately
        Default = False
    bkg_rms : float, optional
        Estimation of the global background noise - used to determine
        threshold if err is None - can calculate global background
        rms when subtracting background model
        Default = None
    err : array-like, optional
        Error array for the CCD frame - supersedes bkg_rms when
        determining the threshold
        Default = None
    min_area : int, optional
        Minimum number of pixels to be flagged as a source
        Default = 5
    deblend_cont : float, optional
        Minimum contrast ratio used by SEP for deblending
        Default = 0.05
    segment : bool, optional
        Toggle to generate a segmentation map for the given image
        Default = False
    extras : bool, optional
        Toggle to calculate ellipticity, FWHM, Kron radius and 
        flux radius
        Default = False
    
    Returns
    -------
    sources : astropy Table object
        Table containing quantities determined by sep for each source
        detected in the given image
    segmentation_map : array-like, optional
        Array of integers with same shape as data - pixels not 
        belonging to any object have value 0, whilst all pixels 
        belonging to ith object have value (e.g. sources[i]) have
        value i+1 - only returned if seg_map is True
    
    Raises
    ------
    None
    """

    # subtract spatially varying background model if requested
    if bkg:
        data, bkg_rms = subtractBackground(data)

    # determine threshold for extraction
    if err is None:
        thresh *= bkg_rms

    # extract sources
    if not segment:
        sources = sep.extract(data,
                              thresh,
                              err=err,
                              mask=mask,
                              deblend_cont=deblend_cont)
    else:
        sources, seg_map = sep.extract(data,
                                       thresh,
                                       err=err,
                                       mask=mask,
                                       deblend_cont=deblend_cont,
                                       segmentation_map=seg_map)

    sources = Table(sources)

    # remove nans from table
    sources = pruneNansFromTable(sources)

    if extras:
        # calculate ellipticity parameter
        sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

        # calculate full width half maxima
        sources['fwhm'] = calculateFWHM(sources['a'], sources['b'])

        # compute kron radii
        try:
            sources['kronr'], krflag = sep.kron_radius(data, sources['x'],
                                                       sources['y'],
                                                       sources['a'],
                                                       sources['b'],
                                                       sources['theta'], 6.0)
            sources['flag'] |= krflag
        except Exception as e:
            print(e)
            pass

        # compute flux radii
        try:
            sources['fluxr'], frflag = sep.flux_radius(data,
                                                       sources['x'],
                                                       sources['y'],
                                                       6.0 * sources['a'],
                                                       0.5,
                                                       subpix=5)
            sources['flag'] |= frflag
        except Exception as e:
            print(e)
            pass

    if segment:
        return sources, seg_map
    else:
        return sources
Example #11
0
File: test.py Project: cmccully/sep
def test_vs_sextractor():
    """Test behavior of sep versus sextractor.

    Note: we turn deblending off for this test. This is because the
    deblending algorithm uses a random number generator. Since the sequence
    of random numbers is not the same between sextractor and sep or between
    different platforms, object member pixels (and even the number of objects)
    can differ when deblending is on.

    Deblending is turned off by setting DEBLEND_MINCONT=1.0 in the sextractor
    configuration file and by setting deblend_cont=1.0 in sep.extract().
    """

    data = np.copy(image_data)  # make an explicit copy so we can 'subfrom'
    bkg = sep.Background(data, bw=64, bh=64, fw=3, fh=3)

    # Test that SExtractor background is same as SEP:
    bkgarr = bkg.back(dtype=np.float32)
    assert_allclose(bkgarr, image_refback, rtol=1.e-5)

    # Extract objects (use deblend_cont=1.0 to disable deblending).
    bkg.subfrom(data)
    objs = sep.extract(data, 1.5*bkg.globalrms, deblend_cont=1.0)
    objs = np.sort(objs, order=['y'])

    # Read SExtractor result
    refobjs = np.loadtxt(IMAGECAT_FNAME, dtype=IMAGECAT_DTYPE)
    refobjs = np.sort(refobjs, order=['y'])

    # Found correct number of sources at the right locations?
    assert_allclose(objs['x'], refobjs['x'] - 1., atol=1.e-3)
    assert_allclose(objs['y'], refobjs['y'] - 1., atol=1.e-3)

    # Test aperture flux
    flux, fluxerr, flag = sep.sum_circle(data, objs['x'], objs['y'], 5.,
                                         err=bkg.globalrms)
    assert_allclose(flux, refobjs['flux_aper'], rtol=2.e-4)
    assert_allclose(fluxerr, refobjs['fluxerr_aper'], rtol=1.0e-5)

    # check if the flags work at all (comparison values 
    assert ((flag & sep.APER_TRUNC) != 0).sum() == 4
    assert ((flag & sep.APER_HASMASKED) != 0).sum() == 0

    # Test "flux_auto"
    kr, flag = sep.kron_radius(data, objs['x'], objs['y'], objs['a'],
                               objs['b'], objs['theta'], 6.0)

    flux, fluxerr, flag = sep.sum_ellipse(data, objs['x'], objs['y'],
                                          objs['a'], objs['b'],
                                          objs['theta'], r=2.5 * kr,
                                          err=bkg.globalrms, subpix=1)

    # For some reason, one object doesn't match. It's very small
    # and kron_radius is set to 0.0 in SExtractor, but 0.08 in sep.
    # Could be due to a change in SExtractor between v2.8.6 (used to
    # generate "truth" catalog) and v2.18.11 (from which sep was forked).
    i = 56  # index is 59 when deblending is on.
    kr[i] = 0.0
    flux[i] = 0.0
    fluxerr[i] = 0.0

    # We use atol for radius because it is reported to nearest 0.01 in
    # reference objects.
    assert_allclose(2.5*kr, refobjs['kron_radius'], atol=0.01, rtol=0.) 
    assert_allclose(flux, refobjs['flux_auto'], rtol=0.0005)
    assert_allclose(fluxerr, refobjs['fluxerr_auto'], rtol=0.0005)

    # Test ellipse representation conversion
    cxx, cyy, cxy = sep.ellipse_coeffs(objs['a'], objs['b'], objs['theta'])
    assert_allclose(cxx, objs['cxx'], rtol=1.e-4)
    assert_allclose(cyy, objs['cyy'], rtol=1.e-4)
    assert_allclose(cxy, objs['cxy'], rtol=1.e-4)

    a, b, theta = sep.ellipse_axes(objs['cxx'], objs['cyy'], objs['cxy'])
    assert_allclose(a, objs['a'], rtol=1.e-4)
    assert_allclose(b, objs['b'], rtol=1.e-4)
    assert_allclose(theta, objs['theta'], rtol=1.e-4)

    #test round trip
    cxx, cyy, cxy = sep.ellipse_coeffs(a, b, theta)
    assert_allclose(cxx, objs['cxx'], rtol=1.e-4)
    assert_allclose(cyy, objs['cyy'], rtol=1.e-4)
    assert_allclose(cxy, objs['cxy'], rtol=1.e-4)

    # test flux_radius
    fr, flags = sep.flux_radius(data, objs['x'], objs['y'], 6.*refobjs['a'],
                                [0.1, 0.5, 0.6], normflux=refobjs['flux_auto'],
                                subpix=5)
    assert_allclose(fr, refobjs["flux_radius"], rtol=0.04, atol=0.01)

    # test winpos
    sig = 2. / 2.35 * fr[:, 1]  # flux_radius = 0.5
    xwin, ywin, flag = sep.winpos(data, objs['x'], objs['y'], sig)
    assert_allclose(xwin, refobjs["xwin"] - 1., rtol=0., atol=0.0025)
    assert_allclose(ywin, refobjs["ywin"] - 1., rtol=0., atol=0.0025)
Example #12
0
    async def __call__(self, image: Image) -> Image:
        """Find stars in given image and append catalog.

        Args:
            image: Image to find stars in.

        Returns:
            Image with attached catalog.
        """
        import sep

        loop = asyncio.get_running_loop()

        # got data?
        if image.data is None:
            log.warning("No data found in image.")
            return image

        # no mask?
        mask = image.mask if image.mask is not None else np.zeros(
            image.data.shape, dtype=bool)

        # remove background
        data, bkg = SepSourceDetection.remove_background(image.data, mask)

        # extract sources
        sources = await loop.run_in_executor(
            None,
            partial(
                sep.extract,
                data,
                self.threshold,
                err=bkg.globalrms,
                minarea=self.minarea,
                deblend_nthresh=self.deblend_nthresh,
                deblend_cont=self.deblend_cont,
                clean=self.clean,
                clean_param=self.clean_param,
                mask=image.mask,
            ),
        )

        # convert to astropy table
        sources = pd.DataFrame(sources)

        # only keep sources with detection flag < 8
        sources = sources[sources["flag"] < 8]
        x, y = sources["x"], sources["y"]

        # Calculate the ellipticity
        sources["ellipticity"] = 1.0 - (sources["b"] / sources["a"])

        # calculate the FWHMs of the stars
        fwhm = 2.0 * (np.log(2) * (sources["a"]**2.0 + sources["b"]**2.0))**0.5
        sources["fwhm"] = fwhm

        # clip theta to [-pi/2,pi/2]
        sources["theta"] = sources["theta"].clip(lower=np.pi / 2,
                                                 upper=np.pi / 2)

        # Kron radius
        kronrad, krflag = sep.kron_radius(data, x, y, sources["a"],
                                          sources["b"], sources["theta"], 6.0)
        sources["flag"] |= krflag
        sources["kronrad"] = kronrad

        # equivalent of FLUX_AUTO
        gain = image.header["DET-GAIN"] if "DET-GAIN" in image.header else None
        flux, fluxerr, flag = await loop.run_in_executor(
            None,
            partial(
                sep.sum_ellipse,
                data,
                x,
                y,
                sources["a"],
                sources["b"],
                sources["theta"],
                2.5 * kronrad,
                subpix=5,
                mask=image.mask,
                gain=gain,
            ),
        )
        sources["flag"] |= flag
        sources["flux"] = flux

        # radii at 0.25, 0.5, and 0.75 flux
        flux_radii, flag = sep.flux_radius(data,
                                           x,
                                           y,
                                           6.0 * sources["a"],
                                           [0.25, 0.5, 0.75],
                                           normflux=sources["flux"],
                                           subpix=5)
        sources["flag"] |= flag
        sources["fluxrad25"] = flux_radii[:, 0]
        sources["fluxrad50"] = flux_radii[:, 1]
        sources["fluxrad75"] = flux_radii[:, 2]

        # xwin/ywin
        sig = 2.0 / 2.35 * sources["fluxrad50"]
        xwin, ywin, flag = sep.winpos(data, x, y, sig)
        sources["flag"] |= flag
        sources["xwin"] = xwin
        sources["ywin"] = ywin

        # theta in degrees
        sources["theta"] = np.degrees(sources["theta"])

        # only keep sources with detection flag < 8
        sources = sources[sources["flag"] < 8]

        # match fits conventions
        sources["x"] += 1
        sources["y"] += 1

        # pick columns for catalog
        cat = sources[[
            "x",
            "y",
            "peak",
            "flux",
            "fwhm",
            "a",
            "b",
            "theta",
            "ellipticity",
            "tnpix",
            "kronrad",
            "fluxrad25",
            "fluxrad50",
            "fluxrad75",
            "xwin",
            "ywin",
        ]]

        # copy image, set catalog and return it
        img = image.copy()
        img.catalog = Table.from_pandas(cat)
        return img
Example #13
0
def get_objects_sep(image, header=None, mask=None, thresh=4.0, aper=3.0, bkgann=None, r0=0.5, gain=1, edge=0, minnthresh=2, minarea=5, relfluxradius=2.0, wcs=None, use_fwhm=False, use_mask_bg=False, use_mask_large=False, subtract_bg=True, npix_large=300, sn=10.0, verbose=True, get_fwhm=False, **kwargs):
    if r0 > 0.0:
        kernel = make_kernel(r0)
    else:
        kernel = None

    if verbose:
        print("Preparing background mask")

    if mask is None:
        mask = np.zeros_like(image, dtype=np.bool)

    mask_bg = np.zeros_like(mask)
    mask_segm = np.zeros_like(mask)

    if use_mask_bg:
        # Simple heuristics to mask regions with rapidly varying background
        if verbose:
            print("Masking rapidly changing background")

        bg2 = sep.Background(image, mask=mask|mask_bg, bw=64, bh=64)

        for _ in xrange(3):
            bg1 = sep.Background(image, mask=mask|mask_bg, bw=256, bh=256)

            ibg = bg2.back() - bg1.back()

            tmp = np.abs(ibg - np.median(ibg)) > 5.0*mad_std(ibg)
            mask_bg |= dilate(tmp, np.ones([100, 100]))

        mask_bg = dilate(tmp, np.ones([100, 100]))

    if verbose:
        print("Building background map")

    bg = sep.Background(image, mask=mask|mask_bg, bw=64, bh=64)
    if subtract_bg:
        image1 = image - bg.back()
    else:
        image1 = image.copy()

    sep.set_extract_pixstack(image.shape[0]*image.shape[1])

    if use_mask_large:
        # Mask regions around huge objects as they are most probably corrupted by saturation and blooming
        if verbose:
            print("Extracting initial objects")

        obj0,segm = sep.extract(image1, err=bg.rms(), thresh=thresh, minarea=minarea, mask=mask|mask_bg, filter_kernel=kernel, segmentation_map=True, **kwargs)

        if verbose:
            print("Dilating large objects")

        mask_segm = np.isin(segm, [_+1 for _,npix in enumerate(obj0['npix']) if npix > npix_large])
        mask_segm = dilate(mask_segm, np.ones([50, 50]))

    if verbose:
        print("Extracting final objects")

    obj0 = sep.extract(image1, err=bg.rms(), thresh=thresh, minarea=minarea, mask=mask|mask_bg|mask_segm, filter_kernel=kernel, **kwargs)

    if use_fwhm:
        # Estimate FHWM and use it to get optimal aperture size
        idx = obj0['flag'] == 0
        fwhm = 2.0*np.sqrt(np.hypot(obj0['a'][idx], obj0['b'][idx])*np.log(2))
        fwhm = 2.0*sep.flux_radius(image1, obj0['x'][idx], obj0['y'][idx], relfluxradius*fwhm*np.ones_like(obj0['x'][idx]), 0.5, mask=mask)[0]
        fwhm = np.median(fwhm)

        aper = max(1.5*fwhm, aper)

        if verbose:
            print("FWHM = %.2g, aperture = %.2g" % (fwhm, aper))

    # Windowed positional parameters are often biased in crowded fields, let's avoid them for now
    # xwin,ywin,flag = sep.winpos(image1, obj0['x'], obj0['y'], 0.5, mask=mask)
    xwin,ywin = obj0['x'], obj0['y']

    # Filter out objects too close to frame edges
    idx = (np.round(xwin) > edge) & (np.round(ywin) > edge) & (np.round(xwin) < image.shape[1]-edge) & (np.round(ywin) < image.shape[0]-edge) # & (obj0['flag'] == 0)

    if minnthresh:
        idx &= (obj0['tnpix'] >= minnthresh)

    if verbose:
        print("Measuring final objects")

    flux,fluxerr,flag = sep.sum_circle(image1, xwin[idx], ywin[idx], aper, err=bg.rms(), gain=gain, mask=mask|mask_bg|mask_segm, bkgann=bkgann)
    # For debug purposes, let's make also the same aperture photometry on the background map
    bgflux,bgfluxerr,bgflag = sep.sum_circle(bg.back(), xwin[idx], ywin[idx], aper, err=bg.rms(), gain=gain, mask=mask|mask_bg|mask_segm)

    bg = bgflux/np.pi/aper**2

    # Fluxes to magnitudes
    mag,magerr = np.zeros_like(flux), np.zeros_like(flux)
    mag[flux>0] = -2.5*np.log10(flux[flux>0])
    # magerr[flux>0] = 2.5*np.log10(1.0 + fluxerr[flux>0]/flux[flux>0])
    magerr[flux>0] = 2.5/np.log(10)*fluxerr[flux>0]/flux[flux>0]

    # better FWHM estimation - FWHM=HFD for Gaussian
    if get_fwhm:
        fwhm = 2.0*sep.flux_radius(image1, xwin[idx], ywin[idx], relfluxradius*aper*np.ones_like(xwin[idx]), 0.5, mask=mask)[0]
    else:
        fwhm = np.zeros_like(xwin[idx])

    flag |= obj0['flag'][idx]

    # Quality cuts
    fidx = (flux > 0) & (magerr < 1.0/sn)

    if wcs is None and header is not None:
        # If header is provided, we may build WCS from it
        wcs = WCS(header)

    if wcs is not None:
        # If WCS is provided we may convert x,y to ra,dec
        ra,dec = wcs.all_pix2world(obj0['x'][idx], obj0['y'][idx], 0)
    else:
        ra,dec = np.zeros_like(obj0['x'][idx]),np.zeros_like(obj0['y'][idx])

    if verbose:
        print("All done")

    return {'x':xwin[idx][fidx], 'y':ywin[idx][fidx], 'flux':flux[fidx], 'fluxerr':fluxerr[fidx], 'mag':mag[fidx], 'magerr':magerr[fidx], 'flags':obj0['flag'][idx][fidx]|flag[fidx], 'ra':ra[fidx], 'dec':dec[fidx], 'bg':bg[fidx], 'fwhm':fwhm[fidx], 'aper':aper, 'bkgann':bkgann, 'a':obj0['a'][idx][fidx], 'b':obj0['b'][idx][fidx], 'theta':obj0['theta'][idx][fidx]}
Example #14
0
b =1
apertures = [2.5,3.4,5,7.5,10,15,20,30,50,70]
catalog = []
f = []
a =1
flux, fluxerr, flag = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[0],err=bkg.globalrms, gain=1.4)
flux1, fluxerr1, flag1 = sep.sum_ellipse(data_sub, objects['x'], objects['y'],  a, b, theta,apertures[1],err=bkg.globalrms, gain=1.4)
flux2, fluxerr2, flag2 = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[2],err=bkg.globalrms, gain=1.4)
flux3, fluxerr3, flag3 = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[3],err=bkg.globalrms, gain=1.4)
flux4, fluxerr4, flag4 = sep.sum_ellipse(data_sub, objects['x'], objects['y'],  a, b, theta,apertures[4],err=bkg.globalrms, gain=1.4)
flux5, fluxer5r, flag5 = sep.sum_ellipse(data_sub, objects['x'], objects['y'],  a, b, theta,apertures[5],err=bkg.globalrms, gain=1.4)
flux6, fluxerr6, flag6 = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[6],err=bkg.globalrms, gain=1.4)
flux7, fluxerr7, flag7 = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[7],err=bkg.globalrms, gain=1.4)
flux8, fluxerr8, flag8 = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[8],err=bkg.globalrms, gain=1.4)
flux9, fluxerr9, flag9 = sep.sum_ellipse(data_sub, objects['x'], objects['y'], a, b, theta,apertures[9],err=bkg.globalrms, gain=1.4)
r, flag = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r1, flag1 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r2, flag2 = sep.flux_radius(data_sub, objects['x'], objects['y'], 6.*objects['a'],0.5, normflux=flux, subpix=5)
r3, flag3 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r4, flag4 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r5, flag5 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r6, flag6 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r7, flag7 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r8, flag8 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
r9, flag9 = sep.flux_radius(data_sub, objects['x'], objects['y'],6.*objects['a'], 0.5, normflux=flux, subpix=5)
    
#print(flux)
    #return flux
          

catalog = np.array(flux)
Example #15
0
def findStars(wind, thresh, kernel_fwhm, return_bkg=False):
    """
    Use sep to find objects in image. Not sure the outputs returned
    are correct for xbin != ybin

    Parameters
    ----------
    wind : `~hipercam.window.Window`
        window in which to detect objects.

    thresh : float
        threshold for object detection, in muliples of background RMS.


    kernel_fwhm : float
        Image is convolved with a Gaussian kernel of this FWHM prior to object
        detection. Should be set to something similar to the typical FWHM in image.

    return_bkg : bool
        True to return the background as calculated by sep.Background

    Returns
    -------
    objects : np.ndarray
        Extracted object parameters (structured array).
        For available fields, see sep documentation.
        http://sep.readthedocs.io/en/v1.0.x/api/sep.extract.html#sep.extract

        Most quantities are converted to unbinned coordinates and pixels,
        apart from npix, and tnpix, the number of pixels in the objects.

    bkg : `~sep.Background` [if return_bkg]
        The background estimated by 'sep'. Use sep.subfrom
        to subtract from data.

    """
    # ensure float type, c-contiguous and native byte order
    data = wind.data.astype("float")

    bkg = sep.Background(data)
    bkg.subfrom(data)  # in-place background subtraction

    sigma = 2.5 * gaussian_fwhm_to_sigma
    kernel = Gaussian2DKernel(sigma, x_size=3, y_size=3)
    kernel.normalize()

    objects = sep.extract(data,
                          thresh,
                          err=bkg.globalrms,
                          clean=True,
                          filter_kernel=kernel.array)

    # add crude FWHM estimate and HFD measurement
    fwhm = 2.0 * np.sqrt(np.log(2.0) * (objects["a"]**2 + objects["b"]**2))
    hfr, flags = sep.flux_radius(
        data,
        objects["x"],
        objects["y"],
        25 * np.ones_like(objects["x"]),
        0.5,
        normflux=objects["cflux"],
    )
    bad_hfd = np.logical_or(flags != 0, hfr >= 25)
    hfr[bad_hfd] = np.nan
    objects = recfunctions.append_fields(objects, ("fwhm", "hfd"),
                                         (fwhm, 2 * hfr))

    # convert to un-binned pixels
    objects["xmin"] = (wind.x(objects["xmin"] - (wind.xbin - 1) / 2)).astype(
        np.int32)
    objects["xmax"] = (wind.x(objects["xmax"] + (wind.xbin - 1) / 2)).astype(
        np.int32)
    objects["ymin"] = (wind.y(objects["ymin"] - (wind.ybin - 1) / 2)).astype(
        np.int32)
    objects["ymax"] = (wind.y(objects["ymax"] + (wind.ybin - 1) / 2)).astype(
        np.int32)
    for key in ("x", "xcpeak", "xpeak"):
        objects[key] = wind.x(objects[key])
    for key in ("y", "ycpeak", "ypeak"):
        objects[key] = wind.y(objects[key])
    for key in ("xy", "cxy"):
        objects[key] *= wind.xbin * wind.ybin
    for key in ("x2", "a", "errx2", "cxx"):
        objects[key] *= wind.xbin
    for key in ("y2", "b", "erry2", "cyy"):
        objects[key] *= wind.ybin
    for key in ("fwhm", "hfd"):
        objects[key] *= np.sqrt(wind.xbin * wind.ybin)

    # change the data type to save on space
    objects = objects.astype(SMALL_TYPE)

    if return_bkg:
        return (objects, bkg)
    else:
        return objects
Example #16
0
def test_vs_sextractor():
    data = np.copy(image_data)  # make an explicit copy so we can 'subfrom'
    bkg = sep.Background(data, bw=64, bh=64, fw=3, fh=3)

    # Test that SExtractor background is same as SEP:
    bkgarr = bkg.back(dtype=np.float32)
    assert_allclose(bkgarr, image_refback, rtol=1.e-5)

    # Extract objects
    bkg.subfrom(data)
    objs = sep.extract(data, 1.5*bkg.globalrms)
    objs = np.sort(objs, order=['y'])

    # Read SExtractor result
    refobjs = np.loadtxt(IMAGECAT_FNAME, dtype=IMAGECAT_DTYPE)
    refobjs = np.sort(refobjs, order=['y'])

    # Found correct number of sources at the right locations?
    assert_allclose(objs['x'], refobjs['x'] - 1., atol=1.e-3)
    assert_allclose(objs['y'], refobjs['y'] - 1., atol=1.e-3)

    # Test aperture flux
    flux, fluxerr, flag = sep.sum_circle(data, objs['x'], objs['y'], 5.,
                                         err=bkg.globalrms)
    assert_allclose(flux, refobjs['flux_aper'], rtol=2.e-4)
    assert_allclose(fluxerr, refobjs['fluxerr_aper'], rtol=1.0e-5)

    # check if the flags work at all (comparison values 
    assert ((flag & sep.APER_TRUNC) != 0).sum() == 4
    assert ((flag & sep.APER_HASMASKED) != 0).sum() == 0

    # Test "flux_auto"
    kr, flag = sep.kron_radius(data, objs['x'], objs['y'], objs['a'],
                               objs['b'], objs['theta'], 6.0)

    flux, fluxerr, flag = sep.sum_ellipse(data, objs['x'], objs['y'],
                                          objs['a'], objs['b'],
                                          objs['theta'], r=2.5 * kr,
                                          err=bkg.globalrms, subpix=1)

    # For some reason, object at index 59 doesn't match. It's very small
    # and kron_radius is set to 0.0 in SExtractor, but 0.08 in sep.
    # Most of the other values are within 1e-4 except one which is only
    # within 0.01. This might be due to a change in SExtractor between
    # v2.8.6 (used to generate "truth" catalog) and v2.18.11.
    kr[59] = 0.0
    flux[59] = 0.0
    fluxerr[59] = 0.0
    assert_allclose(2.5*kr, refobjs['kron_radius'], rtol=0.01)
    assert_allclose(flux, refobjs['flux_auto'], rtol=0.01)
    assert_allclose(fluxerr, refobjs['fluxerr_auto'], rtol=0.01)

    # Test ellipse representation conversion
    cxx, cyy, cxy = sep.ellipse_coeffs(objs['a'], objs['b'], objs['theta'])
    assert_allclose(cxx, objs['cxx'], rtol=1.e-4)
    assert_allclose(cyy, objs['cyy'], rtol=1.e-4)
    assert_allclose(cxy, objs['cxy'], rtol=1.e-4)

    a, b, theta = sep.ellipse_axes(objs['cxx'], objs['cyy'], objs['cxy'])
    assert_allclose(a, objs['a'], rtol=1.e-4)
    assert_allclose(b, objs['b'], rtol=1.e-4)
    assert_allclose(theta, objs['theta'], rtol=1.e-4)

    #test round trip
    cxx, cyy, cxy = sep.ellipse_coeffs(a, b, theta)
    assert_allclose(cxx, objs['cxx'], rtol=1.e-4)
    assert_allclose(cyy, objs['cyy'], rtol=1.e-4)
    assert_allclose(cxy, objs['cxy'], rtol=1.e-4)

    # test flux_radius
    fr, flags = sep.flux_radius(data, objs['x'], objs['y'], 6.*refobjs['a'],
                                [0.1, 0.5, 0.6], normflux=refobjs['flux_auto'],
                                subpix=5)
    assert_allclose(fr, refobjs["flux_radius"], rtol=0.04, atol=0.01)

    # test winpos
    sig = 2. / 2.35 * fr[:, 1]  # flux_radius = 0.5
    xwin, ywin, flag = sep.winpos(data, objs['x'], objs['y'], sig)
    assert_allclose(xwin, refobjs["xwin"] - 1., rtol=0., atol=0.0025)
    assert_allclose(ywin, refobjs["ywin"] - 1., rtol=0., atol=0.0025)
Example #17
0
    def do_stage(self, images):
        for i, image in enumerate(images):
            try:
                # Set the number of source pixels to be 5% of the total. This keeps us safe from
                # satellites and airplanes.
                sep.set_extract_pixstack(int(image.nx * image.ny * 0.05))

                data = image.data.copy()
                error = (np.abs(data) + image.readnoise ** 2.0) ** 0.5
                mask = image.bpm > 0

                # Fits can be backwards byte order, so fix that if need be and subtract
                # the background
                try:
                    bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
                except ValueError:
                    data = data.byteswap(True).newbyteorder()
                    bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
                bkg.subfrom(data)

                # Do an initial source detection
                # TODO: Add back in masking after we are sure SEP works
                sources = sep.extract(data, self.threshold, minarea=self.min_area,
                                      err=error, deblend_cont=0.005)

                # Convert the detections into a table
                sources = Table(sources)

                # Calculate the ellipticity
                sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

                # Fix any value of theta that are invalid due to floating point rounding
                # -pi / 2 < theta < pi / 2
                sources['theta'][sources['theta'] > (np.pi / 2.0)] -= np.pi
                sources['theta'][sources['theta'] < (-np.pi / 2.0)] += np.pi

                # Calculate the kron radius
                kronrad, krflag = sep.kron_radius(data, sources['x'], sources['y'],
                                                  sources['a'], sources['b'],
                                                  sources['theta'], 6.0)
                sources['flag'] |= krflag
                sources['kronrad'] = kronrad

                # Calcuate the equivilent of flux_auto
                flux, fluxerr, flag = sep.sum_ellipse(data, sources['x'], sources['y'],
                                                      sources['a'], sources['b'],
                                                      np.pi / 2.0, 2.5 * kronrad,
                                                      subpix=1, err=error)
                sources['flux'] = flux
                sources['fluxerr'] = fluxerr
                sources['flag'] |= flag

                # Calculate the FWHMs of the stars:
                fwhm = 2.0 * (np.log(2) * (sources['a'] ** 2.0 + sources['b'] ** 2.0)) ** 0.5
                sources['fwhm'] = fwhm

                # Cut individual bright pixels. Often cosmic rays
                sources = sources[fwhm > 1.0]

                # Measure the flux profile
                flux_radii, flag = sep.flux_radius(data, sources['x'], sources['y'],
                                                   6.0 * sources['a'], [0.25, 0.5, 0.75],
                                                   normflux=sources['flux'], subpix=5)
                sources['flag'] |= flag
                sources['fluxrad25'] = flux_radii[:, 0]
                sources['fluxrad50'] = flux_radii[:, 1]
                sources['fluxrad75'] = flux_radii[:, 2]

                # Calculate the windowed positions
                sig = 2.0 / 2.35 * sources['fluxrad50']
                xwin, ywin, flag = sep.winpos(data, sources['x'], sources['y'], sig)
                sources['flag'] |= flag
                sources['xwin'] = xwin
                sources['ywin'] = ywin

                # Calculate the average background at each source
                bkgflux, fluxerr, flag = sep.sum_ellipse(bkg.back(), sources['x'], sources['y'],
                                                         sources['a'], sources['b'], np.pi / 2.0,
                                                         2.5 * sources['kronrad'], subpix=1)
                #masksum, fluxerr, flag = sep.sum_ellipse(mask, sources['x'], sources['y'],
                #                                         sources['a'], sources['b'], np.pi / 2.0,
                #                                         2.5 * kronrad, subpix=1)

                background_area = (2.5 * sources['kronrad']) ** 2.0 * sources['a'] * sources['b'] * np.pi # - masksum
                sources['background'] = bkgflux
                sources['background'][background_area > 0] /= background_area[background_area > 0]
                # Update the catalog to match fits convention instead of python array convention
                sources['x'] += 1.0
                sources['y'] += 1.0

                sources['xpeak'] += 1
                sources['ypeak'] += 1

                sources['xwin'] += 1.0
                sources['ywin'] += 1.0

                sources['theta'] = np.degrees(sources['theta'])

                image.catalog = sources['x', 'y', 'xwin', 'ywin', 'xpeak', 'ypeak',
                                        'flux', 'fluxerr', 'background', 'fwhm',
                                        'a', 'b', 'theta', 'kronrad', 'ellipticity',
                                        'fluxrad25', 'fluxrad50', 'fluxrad75',
                                        'x2', 'y2', 'xy', 'flag']

                # Add the units and description to the catalogs
                image.catalog['x'].unit = 'pixel'
                image.catalog['x'].description = 'X coordinate of the object'
                image.catalog['y'].unit = 'pixel'
                image.catalog['y'].description = 'Y coordinate of the object'
                image.catalog['xwin'].unit = 'pixel'
                image.catalog['xwin'].description = 'Windowed X coordinate of the object'
                image.catalog['ywin'].unit = 'pixel'
                image.catalog['ywin'].description = 'Windowed Y coordinate of the object'
                image.catalog['xpeak'].unit = 'pixel'
                image.catalog['xpeak'].description = 'X coordinate of the peak'
                image.catalog['ypeak'].unit = 'pixel'
                image.catalog['ypeak'].description = 'Windowed Y coordinate of the peak'
                image.catalog['flux'].unit = 'counts'
                image.catalog['flux'].description = 'Flux within a Kron-like elliptical aperture'
                image.catalog['fluxerr'].unit = 'counts'
                image.catalog['fluxerr'].description = 'Erronr on the flux within a Kron-like elliptical aperture'
                image.catalog['background'].unit = 'counts'
                image.catalog['background'].description = 'Average background value in the aperture'
                image.catalog['fwhm'].unit = 'pixel'
                image.catalog['fwhm'].description = 'FWHM of the object'
                image.catalog['a'].unit = 'pixel'
                image.catalog['a'].description = 'Semi-major axis of the object'
                image.catalog['b'].unit = 'pixel'
                image.catalog['b'].description = 'Semi-minor axis of the object'
                image.catalog['theta'].unit = 'degrees'
                image.catalog['theta'].description = 'Position angle of the object'
                image.catalog['kronrad'].unit = 'pixel'
                image.catalog['kronrad'].description = 'Kron radius used for extraction'
                image.catalog['ellipticity'].description = 'Ellipticity'
                image.catalog['fluxrad25'].unit = 'pixel'
                image.catalog['fluxrad25'].description = 'Radius containing 25% of the flux'
                image.catalog['fluxrad50'].unit = 'pixel'
                image.catalog['fluxrad50'].description = 'Radius containing 50% of the flux'
                image.catalog['fluxrad75'].unit = 'pixel'
                image.catalog['fluxrad75'].description = 'Radius containing 75% of the flux'
                image.catalog['x2'].unit = 'pixel^2'
                image.catalog['x2'].description = 'Variance on X coordinate of the object'
                image.catalog['y2'].unit = 'pixel^2'
                image.catalog['y2'].description = 'Variance on Y coordinate of the object'
                image.catalog['xy'].unit = 'pixel^2'
                image.catalog['xy'].description = 'XY covariance of the object'
                image.catalog['flag'].description = 'Bit mask combination of extraction and photometry flags'

                image.catalog.sort('flux')
                image.catalog.reverse()

                logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))

                # Save some background statistics in the header
                mean_background = stats.sigma_clipped_mean(bkg.back(), 5.0)
                image.header['L1MEAN'] = (mean_background,
                                          '[counts] Sigma clipped mean of frame background')
                logs.add_tag(logging_tags, 'L1MEAN', float(mean_background))

                median_background = np.median(bkg.back())
                image.header['L1MEDIAN'] = (median_background,
                                            '[counts] Median of frame background')
                logs.add_tag(logging_tags, 'L1MEDIAN', float(median_background))

                std_background = stats.robust_standard_deviation(bkg.back())
                image.header['L1SIGMA'] = (std_background,
                                           '[counts] Robust std dev of frame background')
                logs.add_tag(logging_tags, 'L1SIGMA', float(std_background))

                # Save some image statistics to the header
                good_objects = image.catalog['flag'] == 0

                seeing = np.median(image.catalog['fwhm'][good_objects]) * image.pixel_scale
                image.header['L1FWHM'] = (seeing, '[arcsec] Frame FWHM in arcsec')
                logs.add_tag(logging_tags, 'L1FWHM', float(seeing))

                mean_ellipticity = stats.sigma_clipped_mean(sources['ellipticity'][good_objects],
                                                            3.0)
                image.header['L1ELLIP'] = (mean_ellipticity, 'Mean image ellipticity (1-B/A)')
                logs.add_tag(logging_tags, 'L1ELLIP', float(mean_ellipticity))

                mean_position_angle = stats.sigma_clipped_mean(sources['theta'][good_objects], 3.0)
                image.header['L1ELLIPA'] = (mean_position_angle,
                                            '[deg] PA of mean image ellipticity')
                logs.add_tag(logging_tags, 'L1ELLIPA', float(mean_position_angle))

                self.logger.info('Extracted sources', extra=logging_tags)

            except Exception as e:
                logging_tags = logs.image_config_to_tags(image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename', os.path.basename(image.filename))
                self.logger.error(e, extra=logging_tags)
        return images
Example #18
0
def findStars(wind, thresh, kernel_fwhm):
    """
    Use sep to find objects in image.

    Parameters
    ----------
    wind : `~hipercam.window.Window`
        window in which to detect objects.

    thresh : float
        threshold for object detection, in muliples of background RMS.


    kernel_fwhm: float
        Image is convolved with a Gaussian kernel of this FWHM prior to object
        detection. Should be set to something similar to the typical FWHM in image.

    Returns
    -------
    objects :  np.ndarray
        Extracted object parameters (structured array).
        For available fields, see sep documentation.
        http://sep.readthedocs.io/en/v1.0.x/api/sep.extract.html#sep.extract

        Quantities are in un-binned pixels
    """
    # ensure float type, c-contiguous and native byte order
    data = wind.data.astype('float')

    bkg = sep.Background(data)
    bkg.subfrom(data)  # in-place background subtraction

    sigma = 2.5 * gaussian_fwhm_to_sigma
    kernel = Gaussian2DKernel(sigma, x_size=3, y_size=3)
    kernel.normalize()

    objects = sep.extract(data, thresh, err=bkg.globalrms, clean=True,
                          filter_kernel=kernel.array)

    # add crude FWHM estimate and HFD measurement
    fwhm = 2. * np.sqrt(np.log(2.) * (objects['a']**2 + objects['b']**2))
    hfr, flags = sep.flux_radius(data, objects['x'], objects['y'], 25*np.ones_like(objects['x']),
                                 0.5, normflux=objects['cflux'])
    bad_hfd = np.logical_or(flags != 0, hfr >= 25)
    hfr[bad_hfd] = np.nan
    objects = recfunctions.append_fields(objects, ('fwhm', 'hfd'), (fwhm, 2*hfr))

    # convert to un-binned pixels
    for key in ('x', 'xmin', 'xmax', 'xcpeak', 'xpeak'):
        objects[key] = objects[key]*wind.xbin + wind.llx
    for key in ('y', 'ymin', 'ymax', 'ycpeak', 'ypeak'):
        objects[key] = objects[key]*wind.ybin + wind.lly
    for key in ('npix', 'tnpix', 'xy', 'cxy'):
        objects[key] *= wind.xbin * wind.ybin
    for key in ('x2', 'a', 'errx2', 'cxx'):
        objects[key] *= wind.xbin
    for key in ('y2', 'b', 'erry2', 'cyy'):
        objects[key] *= wind.ybin
    for key in ('fwhm', 'hfd'):
        objects[key] *= np.sqrt(wind.xbin * wind.ybin)

    return objects
Example #19
0
    def do_stage(self, images):
        for i, image in enumerate(images):
            try:
                # Set the number of source pixels to be 5% of the total. This keeps us safe from
                # satellites and airplanes.
                sep.set_extract_pixstack(int(image.nx * image.ny * 0.05))

                data = image.data.copy()
                error = (np.abs(data) + image.readnoise**2.0)**0.5
                mask = image.bpm > 0

                # Fits can be backwards byte order, so fix that if need be and subtract
                # the background
                try:
                    bkg = sep.Background(data,
                                         mask=mask,
                                         bw=32,
                                         bh=32,
                                         fw=3,
                                         fh=3)
                except ValueError:
                    data = data.byteswap(True).newbyteorder()
                    bkg = sep.Background(data,
                                         mask=mask,
                                         bw=32,
                                         bh=32,
                                         fw=3,
                                         fh=3)
                bkg.subfrom(data)

                # Do an initial source detection
                # TODO: Add back in masking after we are sure SEP works
                sources = sep.extract(data,
                                      self.threshold,
                                      minarea=self.min_area,
                                      err=error,
                                      deblend_cont=0.005)

                # Convert the detections into a table
                sources = Table(sources)

                # Calculate the ellipticity
                sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

                # Fix any value of theta that are invalid due to floating point rounding
                # -pi / 2 < theta < pi / 2
                sources['theta'][sources['theta'] > (np.pi / 2.0)] -= np.pi
                sources['theta'][sources['theta'] < (-np.pi / 2.0)] += np.pi

                # Calculate the kron radius
                kronrad, krflag = sep.kron_radius(data, sources['x'],
                                                  sources['y'], sources['a'],
                                                  sources['b'],
                                                  sources['theta'], 6.0)
                sources['flag'] |= krflag
                sources['kronrad'] = kronrad

                # Calcuate the equivilent of flux_auto
                flux, fluxerr, flag = sep.sum_ellipse(data,
                                                      sources['x'],
                                                      sources['y'],
                                                      sources['a'],
                                                      sources['b'],
                                                      np.pi / 2.0,
                                                      2.5 * kronrad,
                                                      subpix=1,
                                                      err=error)
                sources['flux'] = flux
                sources['fluxerr'] = fluxerr
                sources['flag'] |= flag

                # Calculate the FWHMs of the stars:
                fwhm = 2.0 * (np.log(2) *
                              (sources['a']**2.0 + sources['b']**2.0))**0.5
                sources['fwhm'] = fwhm

                # Cut individual bright pixels. Often cosmic rays
                sources = sources[fwhm > 1.0]

                # Measure the flux profile
                flux_radii, flag = sep.flux_radius(data,
                                                   sources['x'],
                                                   sources['y'],
                                                   6.0 * sources['a'],
                                                   [0.25, 0.5, 0.75],
                                                   normflux=sources['flux'],
                                                   subpix=5)
                sources['flag'] |= flag
                sources['fluxrad25'] = flux_radii[:, 0]
                sources['fluxrad50'] = flux_radii[:, 1]
                sources['fluxrad75'] = flux_radii[:, 2]

                # Calculate the windowed positions
                sig = 2.0 / 2.35 * sources['fluxrad50']
                xwin, ywin, flag = sep.winpos(data, sources['x'], sources['y'],
                                              sig)
                sources['flag'] |= flag
                sources['xwin'] = xwin
                sources['ywin'] = ywin

                # Calculate the average background at each source
                bkgflux, fluxerr, flag = sep.sum_ellipse(bkg.back(),
                                                         sources['x'],
                                                         sources['y'],
                                                         sources['a'],
                                                         sources['b'],
                                                         np.pi / 2.0,
                                                         2.5 *
                                                         sources['kronrad'],
                                                         subpix=1)
                #masksum, fluxerr, flag = sep.sum_ellipse(mask, sources['x'], sources['y'],
                #                                         sources['a'], sources['b'], np.pi / 2.0,
                #                                         2.5 * kronrad, subpix=1)

                background_area = (
                    2.5 * sources['kronrad']
                )**2.0 * sources['a'] * sources['b'] * np.pi  # - masksum
                sources['background'] = bkgflux
                sources['background'][background_area > 0] /= background_area[
                    background_area > 0]
                # Update the catalog to match fits convention instead of python array convention
                sources['x'] += 1.0
                sources['y'] += 1.0

                sources['xpeak'] += 1
                sources['ypeak'] += 1

                sources['xwin'] += 1.0
                sources['ywin'] += 1.0

                sources['theta'] = np.degrees(sources['theta'])

                image.catalog = sources['x', 'y', 'xwin', 'ywin', 'xpeak',
                                        'ypeak', 'flux', 'fluxerr',
                                        'background', 'fwhm', 'a', 'b',
                                        'theta', 'kronrad', 'ellipticity',
                                        'fluxrad25', 'fluxrad50', 'fluxrad75',
                                        'x2', 'y2', 'xy', 'flag']

                # Add the units and description to the catalogs
                image.catalog['x'].unit = 'pixel'
                image.catalog['x'].description = 'X coordinate of the object'
                image.catalog['y'].unit = 'pixel'
                image.catalog['y'].description = 'Y coordinate of the object'
                image.catalog['xwin'].unit = 'pixel'
                image.catalog[
                    'xwin'].description = 'Windowed X coordinate of the object'
                image.catalog['ywin'].unit = 'pixel'
                image.catalog[
                    'ywin'].description = 'Windowed Y coordinate of the object'
                image.catalog['xpeak'].unit = 'pixel'
                image.catalog['xpeak'].description = 'X coordinate of the peak'
                image.catalog['ypeak'].unit = 'pixel'
                image.catalog[
                    'ypeak'].description = 'Windowed Y coordinate of the peak'
                image.catalog['flux'].unit = 'counts'
                image.catalog[
                    'flux'].description = 'Flux within a Kron-like elliptical aperture'
                image.catalog['fluxerr'].unit = 'counts'
                image.catalog[
                    'fluxerr'].description = 'Erronr on the flux within a Kron-like elliptical aperture'
                image.catalog['background'].unit = 'counts'
                image.catalog[
                    'background'].description = 'Average background value in the aperture'
                image.catalog['fwhm'].unit = 'pixel'
                image.catalog['fwhm'].description = 'FWHM of the object'
                image.catalog['a'].unit = 'pixel'
                image.catalog[
                    'a'].description = 'Semi-major axis of the object'
                image.catalog['b'].unit = 'pixel'
                image.catalog[
                    'b'].description = 'Semi-minor axis of the object'
                image.catalog['theta'].unit = 'degrees'
                image.catalog[
                    'theta'].description = 'Position angle of the object'
                image.catalog['kronrad'].unit = 'pixel'
                image.catalog[
                    'kronrad'].description = 'Kron radius used for extraction'
                image.catalog['ellipticity'].description = 'Ellipticity'
                image.catalog['fluxrad25'].unit = 'pixel'
                image.catalog[
                    'fluxrad25'].description = 'Radius containing 25% of the flux'
                image.catalog['fluxrad50'].unit = 'pixel'
                image.catalog[
                    'fluxrad50'].description = 'Radius containing 50% of the flux'
                image.catalog['fluxrad75'].unit = 'pixel'
                image.catalog[
                    'fluxrad75'].description = 'Radius containing 75% of the flux'
                image.catalog['x2'].unit = 'pixel^2'
                image.catalog[
                    'x2'].description = 'Variance on X coordinate of the object'
                image.catalog['y2'].unit = 'pixel^2'
                image.catalog[
                    'y2'].description = 'Variance on Y coordinate of the object'
                image.catalog['xy'].unit = 'pixel^2'
                image.catalog['xy'].description = 'XY covariance of the object'
                image.catalog[
                    'flag'].description = 'Bit mask combination of extraction and photometry flags'

                image.catalog.sort('flux')
                image.catalog.reverse()

                logging_tags = logs.image_config_to_tags(
                    image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename',
                             os.path.basename(image.filename))

                # Save some background statistics in the header
                mean_background = stats.sigma_clipped_mean(bkg.back(), 5.0)
                image.header['L1MEAN'] = (
                    mean_background,
                    '[counts] Sigma clipped mean of frame background')
                logs.add_tag(logging_tags, 'L1MEAN', float(mean_background))

                median_background = np.median(bkg.back())
                image.header['L1MEDIAN'] = (
                    median_background, '[counts] Median of frame background')
                logs.add_tag(logging_tags, 'L1MEDIAN',
                             float(median_background))

                std_background = stats.robust_standard_deviation(bkg.back())
                image.header['L1SIGMA'] = (
                    std_background,
                    '[counts] Robust std dev of frame background')
                logs.add_tag(logging_tags, 'L1SIGMA', float(std_background))

                # Save some image statistics to the header
                good_objects = image.catalog['flag'] == 0

                seeing = np.median(
                    image.catalog['fwhm'][good_objects]) * image.pixel_scale
                image.header['L1FWHM'] = (seeing,
                                          '[arcsec] Frame FWHM in arcsec')
                logs.add_tag(logging_tags, 'L1FWHM', float(seeing))

                mean_ellipticity = stats.sigma_clipped_mean(
                    sources['ellipticity'][good_objects], 3.0)
                image.header['L1ELLIP'] = (mean_ellipticity,
                                           'Mean image ellipticity (1-B/A)')
                logs.add_tag(logging_tags, 'L1ELLIP', float(mean_ellipticity))

                mean_position_angle = stats.sigma_clipped_mean(
                    sources['theta'][good_objects], 3.0)
                image.header['L1ELLIPA'] = (
                    mean_position_angle, '[deg] PA of mean image ellipticity')
                logs.add_tag(logging_tags, 'L1ELLIPA',
                             float(mean_position_angle))

                self.logger.info('Extracted sources', extra=logging_tags)

            except Exception as e:
                logging_tags = logs.image_config_to_tags(
                    image, self.group_by_keywords)
                logs.add_tag(logging_tags, 'filename',
                             os.path.basename(image.filename))
                self.logger.error(e, extra=logging_tags)
        return images
Example #20
0
    def find_stars(self, image: Image) -> Table:
        """Find stars in given image and append catalog.

        Args:
            image: Image to find stars in.

        Returns:
            Full table with results.
        """
        import sep

        # get data and make it continuous
        data = image.data.copy()

        # mask?
        mask = image.mask.data if image.mask is not None else None

        # estimate background, probably we need to byte swap, and subtract it
        try:
            bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
        except ValueError as e:
            data = data.byteswap(True).newbyteorder()
            bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
        bkg.subfrom(data)

        # extract sources
        try:
            sources = sep.extract(data, self.threshold, err=bkg.globalrms, minarea=self.minarea,
                                  deblend_nthresh=self.deblend_nthresh, deblend_cont=self.deblend_cont,
                                  clean=self.clean, clean_param=self.clean_param, mask=mask)
        except:
            log.exception('An error has occured.')
            return Table()

        # convert to astropy table
        sources = Table(sources)

        # only keep sources with detection flag < 8
        sources = sources[sources['flag'] < 8]

        # Calculate the ellipticity
        sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

        # calculate the FWHMs of the stars
        fwhm = 2.0 * (np.log(2) * (sources['a'] ** 2.0 + sources['b'] ** 2.0)) ** 0.5
        sources['fwhm'] = fwhm

        # get gain
        gain = image.header['DET-GAIN'] if 'DET-GAIN' in image.header else None

        # Kron radius
        kronrad, krflag = sep.kron_radius(data, sources['x'], sources['y'], sources['a'], sources['b'],
                                          sources['theta'], 6.0)
        sources['flag'] |= krflag
        sources['kronrad'] = kronrad

        # equivalent of FLUX_AUTO
        flux, fluxerr, flag = sep.sum_ellipse(data, sources['x'], sources['y'], sources['a'], sources['b'],
                                              sources['theta'], 2.5 * kronrad, subpix=1, mask=mask,
                                              err=bkg.rms(), gain=gain)
        sources['flag'] |= flag
        sources['flux'] = flux
        sources['fluxerr'] = fluxerr

        # radii at 0.25, 0.5, and 0.75 flux
        flux_radii, flag = sep.flux_radius(data, sources['x'], sources['y'], 6.0 * sources['a'], [0.25, 0.5, 0.75],
                                           normflux=sources['flux'], subpix=5)
        sources['flag'] |= flag
        sources['fluxrad25'] = flux_radii[:, 0]
        sources['fluxrad50'] = flux_radii[:, 1]
        sources['fluxrad75'] = flux_radii[:, 2]

        # xwin/ywin
        sig = 2. / 2.35 * sources['fluxrad50']
        xwin, ywin, flag = sep.winpos(data, sources['x'], sources['y'], sig)
        sources['flag'] |= flag
        sources['xwin'] = xwin
        sources['ywin'] = ywin

        # perform aperture photometry for diameters of 1" to 8"
        for diameter in [1, 2, 3, 4, 5, 6, 7, 8]:
            flux, fluxerr, flag = sep.sum_circle(data, sources['x'], sources['y'],
                                                 diameter / 2. / image.pixel_scale,
                                                 mask=mask, err=bkg.rms(), gain=gain)
            sources['fluxaper{0}'.format(diameter)] = flux
            sources['fluxerr{0}'.format(diameter)] = fluxerr
            sources['flag'] |= flag

        # average background at each source
        # since SEP sums up whole pixels, we need to do the same on an image of ones for the background_area
        bkgflux, fluxerr, flag = sep.sum_ellipse(bkg.back(), sources['x'], sources['y'],
                                                 sources['a'], sources['b'], np.pi / 2.0,
                                                 2.5 * sources['kronrad'], subpix=1)
        background_area, _, _ = sep.sum_ellipse(np.ones(shape=bkg.back().shape), sources['x'], sources['y'],
                                                sources['a'], sources['b'], np.pi / 2.0,
                                                2.5 * sources['kronrad'], subpix=1)
        sources['background'] = bkgflux
        sources['background'][background_area > 0] /= background_area[background_area > 0]

        # match fits conventions
        sources['x'] += 1.0
        sources['xpeak'] += 1
        sources['xwin'] += 1.0
        sources['xmin'] += 1
        sources['xmax'] += 1
        sources['y'] += 1.0
        sources['ypeak'] += 1
        sources['ywin'] += 1.0
        sources['ymin'] += 1
        sources['ymax'] += 1
        sources['theta'] = np.degrees(sources['theta'])

        # pick columns for catalog
        cat = sources['x', 'y', 'xwin', 'ywin', 'xpeak', 'ypeak',
                      'flux', 'fluxerr', 'peak', 'fluxaper1', 'fluxerr1',
                      'fluxaper2', 'fluxerr2', 'fluxaper3', 'fluxerr3',
                      'fluxaper4', 'fluxerr4', 'fluxaper5', 'fluxerr5',
                      'fluxaper6', 'fluxerr6', 'fluxaper7', 'fluxerr7',
                      'fluxaper8', 'fluxerr8', 'background', 'fwhm',
                      'a', 'b', 'theta', 'kronrad', 'ellipticity',
                      'fluxrad25', 'fluxrad50', 'fluxrad75',
                      'x2', 'y2', 'xy', 'flag']

        # set it
        image.catalog = cat

        # return full catalog
        return sources
Example #21
0
    def _run_sep(self):
        import sep
        # THRESH=1.2 # in sky sigma
        # DETECT_THRESH=1.6 # in sky sigma
        # DEBLEND_MINCONT=0.005
        # DETECT_MINAREA  = 6 # minimum number of pixels above threshold
        objs, seg = sep.extract(
            self.detim,
            self.detect_thresh,
            err=self.detnoise,
            segmentation_map=True,
            **self.sx_config
        )

        logger.debug('found %d objects' % objs.size)
        if objs.size == 0:
            self.cat = objs
            return None

        flux_auto = np.zeros(objs.size)-9999.0
        fluxerr_auto = np.zeros(objs.size)-9999.0
        flux_radius = np.zeros(objs.size)-9999.0
        kron_radius = np.zeros(objs.size)-9999.0

        w, = np.where(
            (objs['a'] >= 0.0) &
            (objs['b'] >= 0.0) &
            between(objs['theta'], -pi/2., pi/2., type='[]')
        )

        if w.size > 0:
            kron_radius[w], krflag = sep.kron_radius(
                self.detim,
                objs['x'][w],
                objs['y'][w],
                objs['a'][w],
                objs['b'][w],
                objs['theta'][w],
                6.0,
            )
            objs['flag'][w] |= krflag

            aper_rad = 2.5*kron_radius
            flux_auto[w], fluxerr_auto[w], flag_auto = \
                sep.sum_ellipse(
                    self.detim,
                    objs['x'][w],
                    objs['y'][w],
                    objs['a'][w],
                    objs['b'][w],
                    objs['theta'][w],
                    aper_rad[w],
                    subpix=1,
                )
            objs['flag'][w] |= flag_auto

            # what we did in DES, but note threshold above
            # is 1 as opposed to wide survey. deep survey
            # was even lower, 0.8?

            # used half light radius
            PHOT_FLUXFRAC = 0.5

            flux_radius[w], frflag = sep.flux_radius(
                self.detim,
                objs['x'][w],
                objs['y'][w],
                6.*objs['a'][w],
                PHOT_FLUXFRAC,
                normflux=flux_auto[w],
                subpix=5,
            )
            objs['flag'][w] |= frflag  # combine flags into 'flag'

        ncut = 2  # need this to make sure array
        new_dt = [
            ('id', 'i8'),
            ('number', 'i4'),
            ('ncutout', 'i4'),
            ('kron_radius', 'f4'),
            ('flux_auto', 'f4'),
            ('fluxerr_auto', 'f4'),
            ('flux_radius', 'f4'),
            ('isoarea_image', 'f4'),
            ('iso_radius', 'f4'),
            ('box_size', 'i4'),
            ('file_id', 'i8', ncut),
            ('orig_row', 'f4', ncut),
            ('orig_col', 'f4', ncut),
            ('orig_start_row', 'i8', ncut),
            ('orig_start_col', 'i8', ncut),
            ('orig_end_row', 'i8', ncut),
            ('orig_end_col', 'i8', ncut),
            ('cutout_row', 'f4', ncut),
            ('cutout_col', 'f4', ncut),
            ('dudrow', 'f8', ncut),
            ('dudcol', 'f8', ncut),
            ('dvdrow', 'f8', ncut),
            ('dvdcol', 'f8', ncut),
        ]

        cat = eu.numpy_util.add_fields(objs, new_dt)
        cat['id'] = np.arange(cat.size)
        cat['number'] = np.arange(1, cat.size+1)
        cat['ncutout'] = 1
        cat['flux_auto'] = flux_auto
        cat['fluxerr_auto'] = fluxerr_auto
        cat['flux_radius'] = flux_radius
        wcs = self.datalist[0]['wcs']
        cat['dudrow'][:, 0] = wcs.dudy
        cat['dudcol'][:, 0] = wcs.dudx
        cat['dvdrow'][:, 0] = wcs.dvdy
        cat['dvdcol'][:, 0] = wcs.dvdx

        # use the number of pixels in the seg map as the iso area
        for i in range(objs.size):
            w = np.where(seg == (i+1))
            cat['isoarea_image'][i] = w[0].size

        cat['iso_radius'] = np.sqrt(cat['isoarea_image'].clip(min=1)/np.pi)

        box_size = self._get_box_sizes(cat)

        half_box_size = box_size//2

        maxrow, maxcol = self.detim.shape

        cat['box_size'] = box_size

        cat['orig_row'][:, 0] = cat['y']
        cat['orig_col'][:, 0] = cat['x']

        orow = cat['orig_row'][:, 0].astype('i4')
        ocol = cat['orig_col'][:, 0].astype('i4')

        ostart_row = orow - half_box_size + 1
        ostart_col = ocol - half_box_size + 1
        oend_row = orow + half_box_size + 1  # plus one for slices
        oend_col = ocol + half_box_size + 1

        ostart_row.clip(min=0, out=ostart_row)
        ostart_col.clip(min=0, out=ostart_col)
        oend_row.clip(max=maxrow, out=oend_row)
        oend_col.clip(max=maxcol, out=oend_col)

        # could result in smaller than box_size above
        cat['orig_start_row'][:, 0] = ostart_row
        cat['orig_start_col'][:, 0] = ostart_col
        cat['orig_end_row'][:, 0] = oend_row
        cat['orig_end_col'][:, 0] = oend_col
        cat['cutout_row'][:, 0] = \
            cat['orig_row'][:, 0] - cat['orig_start_row'][:, 0]
        cat['cutout_col'][:, 0] = \
            cat['orig_col'][:, 0] - cat['orig_start_col'][:, 0]

        self.seg = seg
        self.bmask = np.zeros(seg.shape, dtype='i4')
        self.cat = cat
Example #22
0
def process_file(filename, favor2=None, verbose=False, replace=False, dbname=None, dbhost=None, photodir='photometry'):
    #### Some parameters
    aper = 2.0
    bkgann = None
    order = 4
    bg_order = 4
    color_order = 2
    sn = 5

    if not posixpath.exists(filename):
        return None

    # Rough but fast checking of whether the file is already processed
    if not replace and posixpath.exists(photodir + '/' + filename.split('/')[-2] + '/' + posixpath.splitext(posixpath.split(filename)[-1])[0] + '.cat'):
        return

    #### Preparation
    header = fits.getheader(filename, -1)

    if header['TYPE'] not in ['survey', 'imaging', 'widefield', 'Swift', 'Fermi', 'test']:
        return

    channel = header.get('CHANNEL ID')
    fname = header.get('FILTER', 'unknown')
    time = parse_time(header['TIME'])
    shutter = header.get('SHUTTER', -1)

    if fname not in ['Clear']:
        return

    if fname == 'Clear':
        effective_fname = 'V'
    else:
        effective_fname = fname

    night = get_night(time)

    dirname = '%s/%s' % (photodir, night)
    basename = posixpath.splitext(posixpath.split(filename)[-1])[0]
    basename = dirname + '/' + basename
    catname = basename + '.cat'

    if not replace and posixpath.exists(catname):
        return

    if verbose:
        print(filename, channel, night, fname, effective_fname)

    image = fits.getdata(filename, -1).astype(np.double)

    if favor2 is None:
        favor2 = Favor2(dbname=options.db, dbhost=options.dbhost)

    #### Basic calibration
    darkname = favor2.find_image('masterdark', header=header, debug=False)
    flatname = favor2.find_image('masterflat', header=header, debug=False)

    if darkname:
        dark = fits.getdata(darkname)
    else:
        dark = None

    if flatname:
        flat = fits.getdata(flatname)
    else:
        flat = None

    if dark is None or flat is None:
        survey.save_objects(catname, None)
        return

    image,header = calibrate.calibrate(image, header, dark=dark)

    # Check whether the calibration failed
    if 'SATURATE' not in header:
        print('Calibration failed for', filename)
        survey.save_objects(catname, None)
        return

    #### Basic masking
    mask = image > 0.9*header['SATURATE']
    fmask = ~np.isfinite(flat) | (flat < 0.5)
    dmask = dark > 10.0*mad_std(dark) + np.nanmedian(dark)

    if header.get('BLEMISHCORRECTION', 1):
        # We have to mask blemished pixels
        blemish = fits.getdata('calibrations/blemish_shutter_%d_channel_%d.fits' % (header['SHUTTER'], header['CHANNEL ID']))
        if verbose:
            print(100*np.sum(blemish>0)/blemish.shape[0]/blemish.shape[1], '% pixels blemished')
        dmask |= blemish > 0

    image[~fmask] *= np.median(flat[~fmask])/flat[~fmask]

    #### WCS
    wcs = WCS(header)
    pixscale = np.hypot(wcs.pixel_scale_matrix[0,0], wcs.pixel_scale_matrix[0,1])
    gain = 0.67 if header.get('SHUTTER') == 0 else 1.9

    #### Background mask
    mask_bg = np.zeros_like(mask)
    mask_segm = np.zeros_like(mask)

    # bg2 = sep.Background(image, mask=mask|mask_bg, bw=64, bh=64)

    # for _ in xrange(3):
    #     bg1 = sep.Background(image, mask=mask|mask_bg, bw=256, bh=256)

    #     ibg = bg2.back() - bg1.back()

    #     tmp = np.abs(ibg - np.median(ibg)) > 5.0*mad_std(ibg)
    #     mask_bg |= survey.dilate(tmp, np.ones([50, 50]))

    # mask_bg = survey.dilate(tmp, np.ones([50, 50]))

    # Large objects?..
    bg = sep.Background(image, mask=mask|dmask|fmask|mask_bg|mask_segm, bw=128, bh=128)
    image1 = image - bg.back()
    obj0,segm = sep.extract(image1, err=bg.rms(), thresh=10, minarea=10, mask=mask|dmask|fmask|mask_bg, filter_kernel=None, clean=False, segmentation_map=True)

    mask_segm = np.isin(segm, [_+1 for _,npix in enumerate(obj0['npix']) if npix > 500])
    mask_segm = survey.dilate(mask_segm, np.ones([20, 20]))

    if np.sum(mask_bg|mask_segm|mask|fmask|dmask)/mask_bg.shape[0]/mask_bg.shape[1] > 0.4:
        print(100*np.sum(mask_bg|mask_segm|mask|fmask|dmask)/mask_bg.shape[0]/mask_bg.shape[1], '% of image masked, skipping', filename)
        survey.save_objects(catname, None)
        return
    elif verbose:
        print(100*np.sum(mask_bg|mask_segm|mask|fmask|dmask)/mask_bg.shape[0]/mask_bg.shape[1], '% of image masked')

    # Frame footprint at +10 pixels from the edge
    ra,dec = wcs.all_pix2world([10, 10, image.shape[1]-10, image.shape[1]-10], [10, image.shape[0]-10, image.shape[0]-10, 10], 0)
    footprint = "(" + ",".join(["(%g,%g)" % (_,__) for _,__ in zip(ra, dec)]) + ")"

    #### Catalogue
    ra0,dec0,sr0 = survey.get_frame_center(header=header)
    cat = favor2.get_stars(ra0, dec0, sr0, catalog='gaia', extra=['g<14', 'q3c_poly_query(ra, dec, \'%s\'::polygon)' % footprint], limit=1000000)

    if verbose:
        print(len(cat['ra']), 'star positions from Gaia down to g=%.1f mag' % np.max(cat['g']))

    ## Detection of blended and not really needed stars in the catalogue
    h = htm.HTM(10)
    m = h.match(cat['ra'], cat['dec'], cat['ra'], cat['dec'], 2.0*aper*pixscale, maxmatch=0)
    m = [_[m[2]>1e-5] for _ in m]

    blended = np.zeros_like(cat['ra'], dtype=np.bool)
    notneeded = np.zeros_like(cat['ra'], dtype=np.bool)

    for i1,i2,dist in zip(*m):
        if dist*3600 > 0.5*aper*pixscale:
            if cat['g'][i1] - cat['g'][i2] < 3:
                blended[i1] = True
                blended[i2] = True
            else:
                # i1 is fainter by more than 3 mag
                notneeded[i1] = True

        if dist*3600 < 0.5*aper*pixscale:
            if cat['g'][i1] > cat['g'][i2]:
                notneeded[i1] = True

    cat,blended = [_[~notneeded] for _ in cat,blended]

    #### Background subtraction
    bg = sep.Background(image, mask=mask|dmask|fmask|mask_bg|mask_segm, bw=128, bh=128)
    # bg = sep.Background(image, mask=mask|dmask|fmask|mask_bg|mask_segm, bw=32, bh=32)
    image1 = image - bg.back()

    #### Detection of all objects on the frame
    obj0,segm = sep.extract(image1, err=bg.rms(), thresh=2, minarea=3, mask=mask|dmask|fmask|mask_bg|mask_segm, filter_kernel=None, clean=False, segmentation_map=True)
    obj0 = obj0[(obj0['x'] > 10) & (obj0['y'] > 10) & (obj0['x'] < image.shape[1]-10) & (obj0['y'] < image.shape[0]-10)]
    obj0 = obj0[obj0['flag'] <= 1] # We keep only normal and blended oblects

    fields = ['ra', 'dec', 'fluxerr', 'mag', 'magerr', 'flags', 'cat']
    obj0 = np.lib.recfunctions.append_fields(obj0, fields, [np.zeros_like(obj0['x'], dtype=np.int if _ in ['flags', 'cat'] else np.double) for _ in fields], usemask=False)
    obj0['ra'],obj0['dec'] = wcs.all_pix2world(obj0['x'], obj0['y'], 0)
    obj0['flags'] = obj0['flag']

    if verbose:
        print(len(obj0['x']), 'objects detected on the frame')

    ## Filter out objects not coincident with catalogue positions
    h = htm.HTM(10)
    m = h.match(obj0['ra'], obj0['dec'], cat['ra'], cat['dec'], aper*pixscale)

    nidx = np.isin(np.arange(len(obj0['ra'])), m[0], invert=True)
    obj0 = obj0[nidx]

    if verbose:
        print(len(obj0['x']), 'are outside catalogue apertures')

    # Catalogue stars
    xc,yc = wcs.all_world2pix(cat['ra'], cat['dec'], 0)
    obj = {'x':xc, 'y':yc, 'ra':cat['ra'], 'dec':cat['dec']}

    obj['flags'] = np.zeros_like(xc, dtype=np.int)
    obj['flags'][blended] |= FLAG_BLENDED

    obj['cat'] = np.ones_like(xc, dtype=np.int)

    for _ in ['mag', 'magerr', 'flux', 'fluxerr']:
        obj[_] = np.zeros_like(xc)

    # Merge detected objects
    for _ in ['x', 'y', 'ra', 'dec', 'flags', 'mag', 'magerr', 'flux', 'fluxerr', 'cat']:
        obj[_] = np.concatenate((obj[_], obj0[_]))

    if verbose:
        print(len(obj['x']), 'objects for photometry')

    # Simple aperture photometry
    obj['flux'],obj['fluxerr'],flag = sep.sum_circle(image1, obj['x'], obj['y'], aper, err=bg.rms(), gain=gain, mask=mask|dmask|fmask|mask_bg|mask_segm, bkgann=bkgann)
    obj['flags'] |= flag
    # Normalize flags
    obj['flags'][obj['flags'] & sep.APER_TRUNC] |= FLAG_TRUNCATED
    obj['flags'][obj['flags'] & sep.APER_ALLMASKED] |= FLAG_MASKED
    obj['flags'] &= FLAG_NORMAL | FLAG_BLENDED | FLAG_TRUNCATED | FLAG_MASKED | FLAG_NO_BACKGROUND | FLAG_BAD_CALIBRATION

    area,_,_ = sep.sum_circle(np.ones_like(image1), obj['x'], obj['y'], aper, err=bg.rms(), gain=gain, mask=mask|dmask|fmask|mask_bg|mask_segm, bkgann=bkgann)

    # Simple local background estimation
    bgflux,bgfluxerr,bgflag = sep.sum_circann(image1, obj['x'], obj['y'], 10, 15, err=bg.rms(), gain=gain, mask=mask|dmask|fmask|mask_bg|mask_segm|(segm>0))
    bgarea,_,_ = sep.sum_circann(np.ones_like(image1), obj['x'], obj['y'], 10, 15, err=bg.rms(), gain=gain, mask=mask|dmask|fmask|mask_bg|mask_segm|(segm>0))

    bgidx = np.isfinite(bgarea) & np.isfinite(area)
    bgidx[bgidx] &= (bgarea[bgidx] > 10) & (area[bgidx] > 1)

    obj['flux'][bgidx] -= bgflux[bgidx]*area[bgidx]/bgarea[bgidx]
    obj['flags'][~bgidx] |= FLAG_NO_BACKGROUND # No local background

    obj['deltabgflux'] = np.zeros_like(obj['x'])
    obj['deltabgflux'][bgidx] = bgflux[bgidx]*area[bgidx]/bgarea[bgidx]

    fidx = np.isfinite(obj['flux']) & np.isfinite(obj['fluxerr'])
    fidx[fidx] &= (obj['flux'][fidx] > 0)

    obj['mag'][fidx] = -2.5*np.log10(obj['flux'][fidx])
    obj['magerr'][fidx] = 2.5/np.log(10)*obj['fluxerr'][fidx]/obj['flux'][fidx]

    fidx[fidx] &= (obj['magerr'][fidx] > 0)
    fidx[fidx] &= 1/obj['magerr'][fidx] > sn

    for _ in obj.keys():
        if hasattr(obj[_], '__len__'):
            obj[_] = obj[_][fidx]

    obj['aper'] = aper

    if verbose:
        print(len(obj['x']), 'objects with S/N >', sn)

    if len(obj['x']) < 1000:
        print('Only', len(obj['x']), 'objects on the frame, skipping', filename)
        survey.save_objects(catname, None)
        return

    obj['fwhm'] = 2.0*sep.flux_radius(image1, obj['x'], obj['y'], 2.0*aper*np.ones_like(obj['x']), 0.5, mask=mask|dmask|fmask|mask_bg|mask_segm)[0]

    #### Check FWHM of all objects and select only 'good' ones
    idx = obj['flags'] == 0
    idx &= obj['magerr'] < 1/20

    fwhm0 = survey.fit_2d(obj['x'][idx], obj['y'][idx], obj['fwhm'][idx], obj['x'], obj['y'], weights=1/obj['magerr'][idx])

    fwhm_idx = np.abs(obj['fwhm'] - fwhm0 - np.median((obj['fwhm'] - fwhm0)[idx])) < 3.0*mad_std((obj['fwhm'] - fwhm0)[idx])
    obj['flags'][~fwhm_idx] |= FLAG_BLENDED

    #### Catalogue matching
    idx = obj['flags']
    m = htm.HTM(10).match(obj['ra'], obj['dec'], cat['ra'], cat['dec'], 1e-5)
    fidx = np.in1d(np.arange(len(cat['ra'])), m[1]) # Stars that got successfully measured and not blended

    cidx = (cat['good'] == 1) & (cat['var'] == 0)
    cidx &= np.isfinite(cat['B']) & np.isfinite(cat['V']) # & np.isfinite(cat['lum'])
    cidx[cidx] &= ((cat['B'] - cat['V'])[cidx] > -0.5) & ((cat['B'] - cat['V'])[cidx] < 2.0)
    # cidx[cidx] &= (cat['lum'][cidx] > 0.3) & (cat['lum'][cidx] < 30)

    if np.sum(cidx & fidx & (cat['multi_70'] == 0)) > 2000:
        cidx &= (cat['multi_70'] == 0)
        obj['cat_multi'] = 70
    elif np.sum(cidx & fidx & (cat['multi_45'] == 0)) > 1000:
        cidx &= (cat['multi_45'] == 0)
        obj['cat_multi'] = 45
    else:
        cidx &= (cat['multi_30'] == 0)
        obj['cat_multi'] = 30

    if verbose:
            print(np.sum(obj['flags'] == 0), 'objects without flags')
            print('Amount of good stars:',
                  np.sum(cidx & fidx & (cat['multi_70'] == 0)),
                  np.sum(cidx & fidx & (cat['multi_45'] == 0)),
                  np.sum(cidx & fidx & (cat['multi_30'] == 0)))
            print('Using %d arcsec avoidance radius' % obj['cat_multi'])

    # We match with very small SR to only account for manually placed apertures
    if verbose:
        print('Trying full fit:', len(obj['x']), 'objects,', np.sum(cidx), 'stars')

    match = Match(width=image.shape[1], height=image.shape[0])

    prev_ngoodstars = len(obj['x'])

    for iter in range(10):
        if not match.match(obj=obj, cat=cat[cidx], sr=1e-5, filter_name='V', order=order, bg_order=bg_order, color_order=color_order, verbose=False) or match.ngoodstars < 500:
            if verbose:
                print(match.ngoodstars, 'good matches, matching failed for', filename)
            survey.save_objects(catname, None)
            return

        if verbose:
            print(match.ngoodstars, 'good matches, std =', match.std)

        if match.ngoodstars == prev_ngoodstars:
            if verbose:
                print('Converged on iteration', iter)
            break
        prev_ngoodstars = match.ngoodstars

        # Match good objects with stars
        oidx = obj['flags'] == 0
        oidx1,cidx1,dist1 = htm.HTM(10).match(obj['ra'][oidx], obj['dec'][oidx], cat['ra'][cidx], cat['dec'][cidx], 1e-5)

        x = obj['x'][oidx][oidx1]
        y = obj['y'][oidx][oidx1]
        cbv = match.color_term[oidx][oidx1]
        cbv2 = match.color_term2[oidx][oidx1]
        cbv3 = match.color_term3[oidx][oidx1]
        bv = (cat['B'] - cat['V'])[cidx][cidx1]
        cmag = cat[match.cat_filter_name][cidx][cidx1]
        mag = match.mag[oidx][oidx1] + bv*cbv + bv**2*cbv2 + bv**3*cbv3
        magerr = np.hypot(obj['magerr'][oidx][oidx1], 0.02)

        dmag = mag-cmag
        ndmag = ((mag-cmag)/magerr)

        idx = cmag < match.mag_limit[oidx][oidx1]

        x,y,cbv,cbv2,cbv3,bv,cmag,mag,magerr,dmag,ndmag = [_[idx] for _ in [x,y,cbv,cbv2,cbv3,bv,cmag,mag,magerr,dmag,ndmag]]

        # Match all objects with good objects
        xy = np.array([x,y]).T
        xy0 = np.array([obj['x'], obj['y']]).T

        kd = cKDTree(xy)

        dist,m = kd.query(xy0, 101)
        dist = dist[:,1:]
        m = m[:,1:]

        vchi2 = mad_std(ndmag[m]**2, axis=1)

        # Mark regions of too sparse or too noisy matches as bad
        obj['flags'][vchi2 > 5] |= FLAG_BAD_CALIBRATION
        # obj['flags'][vchi2 > np.median(vchi2) + 5.0*mad_std(vchi2)] |= FLAG_BAD_CALIBRATION
        obj['flags'][dist[:,10] > np.median(dist[:,10]) + 10.0*mad_std(dist[:,10])] |= FLAG_BAD_CALIBRATION

    match.good_idx = (obj['flags'] & FLAG_BAD_CALIBRATION) == 0
    if verbose:
        print(np.sum(match.good_idx), 'of', len(match.good_idx), 'stars are good')

    #### Store objects to file
    try:
        os.makedirs(dirname)
    except:
        pass

    obj['mag_limit'] = match.mag_limit
    obj['color_term'] = match.color_term
    obj['color_term2'] = match.color_term2
    obj['color_term3'] = match.color_term3

    obj['filename'] = filename
    obj['night'] = night
    obj['channel'] = channel
    obj['filter'] = fname
    obj['cat_filter'] = match.cat_filter_name
    obj['time'] = time

    obj['mag_id'] = match.mag_id

    obj['good_idx'] = match.good_idx
    obj['calib_mag'] = match.mag
    obj['calib_magerr'] = match.magerr

    obj['std'] = match.std
    obj['nstars'] = match.ngoodstars

    survey.save_objects(catname, obj, header=header)
Example #23
0
    def do_stage(self, image):
        try:
            # Set the number of source pixels to be 5% of the total. This keeps us safe from
            # satellites and airplanes.
            sep.set_extract_pixstack(int(image.nx * image.ny * 0.05))

            data = image.data.copy()
            error = (np.abs(data) + image.readnoise**2.0)**0.5
            mask = image.bpm > 0

            # Fits can be backwards byte order, so fix that if need be and subtract
            # the background
            try:
                bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
            except ValueError:
                data = data.byteswap(True).newbyteorder()
                bkg = sep.Background(data, mask=mask, bw=32, bh=32, fw=3, fh=3)
            bkg.subfrom(data)

            # Do an initial source detection
            # TODO: Add back in masking after we are sure SEP works
            sources = sep.extract(data,
                                  self.threshold,
                                  minarea=self.min_area,
                                  err=error,
                                  deblend_cont=0.005)

            # Convert the detections into a table
            sources = Table(sources)

            # We remove anything with a detection flag >= 8
            # This includes memory overflows and objects that are too close the edge
            sources = sources[sources['flag'] < 8]

            sources = array_utils.prune_nans_from_table(sources)

            # Calculate the ellipticity
            sources['ellipticity'] = 1.0 - (sources['b'] / sources['a'])

            # Fix any value of theta that are invalid due to floating point rounding
            # -pi / 2 < theta < pi / 2
            sources['theta'][sources['theta'] > (np.pi / 2.0)] -= np.pi
            sources['theta'][sources['theta'] < (-np.pi / 2.0)] += np.pi

            # Calculate the kron radius
            kronrad, krflag = sep.kron_radius(data, sources['x'], sources['y'],
                                              sources['a'], sources['b'],
                                              sources['theta'], 6.0)
            sources['flag'] |= krflag
            sources['kronrad'] = kronrad

            # Calcuate the equivilent of flux_auto
            flux, fluxerr, flag = sep.sum_ellipse(data,
                                                  sources['x'],
                                                  sources['y'],
                                                  sources['a'],
                                                  sources['b'],
                                                  np.pi / 2.0,
                                                  2.5 * kronrad,
                                                  subpix=1,
                                                  err=error)
            sources['flux'] = flux
            sources['fluxerr'] = fluxerr
            sources['flag'] |= flag

            # Do circular aperture photometry for diameters of 1" to 6"
            for diameter in [1, 2, 3, 4, 5, 6]:
                flux, fluxerr, flag = sep.sum_circle(data,
                                                     sources['x'],
                                                     sources['y'],
                                                     diameter / 2.0 /
                                                     image.pixel_scale,
                                                     gain=1.0,
                                                     err=error)
                sources['fluxaper{0}'.format(diameter)] = flux
                sources['fluxerr{0}'.format(diameter)] = fluxerr
                sources['flag'] |= flag

            # Calculate the FWHMs of the stars:
            fwhm = 2.0 * (np.log(2) *
                          (sources['a']**2.0 + sources['b']**2.0))**0.5
            sources['fwhm'] = fwhm

            # Cut individual bright pixels. Often cosmic rays
            sources = sources[fwhm > 1.0]

            # Measure the flux profile
            flux_radii, flag = sep.flux_radius(data,
                                               sources['x'],
                                               sources['y'],
                                               6.0 * sources['a'],
                                               [0.25, 0.5, 0.75],
                                               normflux=sources['flux'],
                                               subpix=5)
            sources['flag'] |= flag
            sources['fluxrad25'] = flux_radii[:, 0]
            sources['fluxrad50'] = flux_radii[:, 1]
            sources['fluxrad75'] = flux_radii[:, 2]

            # Calculate the windowed positions
            sig = 2.0 / 2.35 * sources['fluxrad50']
            xwin, ywin, flag = sep.winpos(data, sources['x'], sources['y'],
                                          sig)
            sources['flag'] |= flag
            sources['xwin'] = xwin
            sources['ywin'] = ywin

            # Calculate the average background at each source
            bkgflux, fluxerr, flag = sep.sum_ellipse(bkg.back(),
                                                     sources['x'],
                                                     sources['y'],
                                                     sources['a'],
                                                     sources['b'],
                                                     np.pi / 2.0,
                                                     2.5 * sources['kronrad'],
                                                     subpix=1)
            # masksum, fluxerr, flag = sep.sum_ellipse(mask, sources['x'], sources['y'],
            #                                         sources['a'], sources['b'], np.pi / 2.0,
            #                                         2.5 * kronrad, subpix=1)

            background_area = (
                2.5 * sources['kronrad']
            )**2.0 * sources['a'] * sources['b'] * np.pi  # - masksum
            sources['background'] = bkgflux
            sources['background'][background_area > 0] /= background_area[
                background_area > 0]
            # Update the catalog to match fits convention instead of python array convention
            sources['x'] += 1.0
            sources['y'] += 1.0

            sources['xpeak'] += 1
            sources['ypeak'] += 1

            sources['xwin'] += 1.0
            sources['ywin'] += 1.0

            sources['theta'] = np.degrees(sources['theta'])

            catalog = sources['x', 'y', 'xwin', 'ywin', 'xpeak', 'ypeak',
                              'flux', 'fluxerr', 'peak', 'fluxaper1',
                              'fluxerr1', 'fluxaper2', 'fluxerr2', 'fluxaper3',
                              'fluxerr3', 'fluxaper4', 'fluxerr4', 'fluxaper5',
                              'fluxerr5', 'fluxaper6', 'fluxerr6',
                              'background', 'fwhm', 'a', 'b', 'theta',
                              'kronrad', 'ellipticity', 'fluxrad25',
                              'fluxrad50', 'fluxrad75', 'x2', 'y2', 'xy',
                              'flag']

            # Add the units and description to the catalogs
            catalog['x'].unit = 'pixel'
            catalog['x'].description = 'X coordinate of the object'
            catalog['y'].unit = 'pixel'
            catalog['y'].description = 'Y coordinate of the object'
            catalog['xwin'].unit = 'pixel'
            catalog['xwin'].description = 'Windowed X coordinate of the object'
            catalog['ywin'].unit = 'pixel'
            catalog['ywin'].description = 'Windowed Y coordinate of the object'
            catalog['xpeak'].unit = 'pixel'
            catalog['xpeak'].description = 'X coordinate of the peak'
            catalog['ypeak'].unit = 'pixel'
            catalog['ypeak'].description = 'Windowed Y coordinate of the peak'
            catalog['flux'].unit = 'count'
            catalog[
                'flux'].description = 'Flux within a Kron-like elliptical aperture'
            catalog['fluxerr'].unit = 'count'
            catalog[
                'fluxerr'].description = 'Error on the flux within Kron aperture'
            catalog['peak'].unit = 'count'
            catalog['peak'].description = 'Peak flux (flux at xpeak, ypeak)'
            for diameter in [1, 2, 3, 4, 5, 6]:
                catalog['fluxaper{0}'.format(diameter)].unit = 'count'
                catalog['fluxaper{0}'.format(
                    diameter
                )].description = 'Flux from fixed circular aperture: {0}" diameter'.format(
                    diameter)
                catalog['fluxerr{0}'.format(diameter)].unit = 'count'
                catalog['fluxerr{0}'.format(
                    diameter
                )].description = 'Error on Flux from circular aperture: {0}"'.format(
                    diameter)

            catalog['background'].unit = 'count'
            catalog[
                'background'].description = 'Average background value in the aperture'
            catalog['fwhm'].unit = 'pixel'
            catalog['fwhm'].description = 'FWHM of the object'
            catalog['a'].unit = 'pixel'
            catalog['a'].description = 'Semi-major axis of the object'
            catalog['b'].unit = 'pixel'
            catalog['b'].description = 'Semi-minor axis of the object'
            catalog['theta'].unit = 'degree'
            catalog['theta'].description = 'Position angle of the object'
            catalog['kronrad'].unit = 'pixel'
            catalog['kronrad'].description = 'Kron radius used for extraction'
            catalog['ellipticity'].description = 'Ellipticity'
            catalog['fluxrad25'].unit = 'pixel'
            catalog[
                'fluxrad25'].description = 'Radius containing 25% of the flux'
            catalog['fluxrad50'].unit = 'pixel'
            catalog[
                'fluxrad50'].description = 'Radius containing 50% of the flux'
            catalog['fluxrad75'].unit = 'pixel'
            catalog[
                'fluxrad75'].description = 'Radius containing 75% of the flux'
            catalog['x2'].unit = 'pixel^2'
            catalog[
                'x2'].description = 'Variance on X coordinate of the object'
            catalog['y2'].unit = 'pixel^2'
            catalog[
                'y2'].description = 'Variance on Y coordinate of the object'
            catalog['xy'].unit = 'pixel^2'
            catalog['xy'].description = 'XY covariance of the object'
            catalog[
                'flag'].description = 'Bit mask of extraction/photometry flags'

            catalog.sort('flux')
            catalog.reverse()

            # Save some background statistics in the header
            mean_background = stats.sigma_clipped_mean(bkg.back(), 5.0)
            image.header['L1MEAN'] = (
                mean_background,
                '[counts] Sigma clipped mean of frame background')

            median_background = np.median(bkg.back())
            image.header['L1MEDIAN'] = (median_background,
                                        '[counts] Median of frame background')

            std_background = stats.robust_standard_deviation(bkg.back())
            image.header['L1SIGMA'] = (
                std_background, '[counts] Robust std dev of frame background')

            # Save some image statistics to the header
            good_objects = catalog['flag'] == 0
            for quantity in ['fwhm', 'ellipticity', 'theta']:
                good_objects = np.logical_and(
                    good_objects, np.logical_not(np.isnan(catalog[quantity])))
            if good_objects.sum() == 0:
                image.header['L1FWHM'] = ('NaN',
                                          '[arcsec] Frame FWHM in arcsec')
                image.header['L1ELLIP'] = ('NaN',
                                           'Mean image ellipticity (1-B/A)')
                image.header['L1ELLIPA'] = (
                    'NaN', '[deg] PA of mean image ellipticity')
            else:
                seeing = np.median(
                    catalog['fwhm'][good_objects]) * image.pixel_scale
                image.header['L1FWHM'] = (seeing,
                                          '[arcsec] Frame FWHM in arcsec')

                mean_ellipticity = stats.sigma_clipped_mean(
                    catalog['ellipticity'][good_objects], 3.0)
                image.header['L1ELLIP'] = (mean_ellipticity,
                                           'Mean image ellipticity (1-B/A)')

                mean_position_angle = stats.sigma_clipped_mean(
                    catalog['theta'][good_objects], 3.0)
                image.header['L1ELLIPA'] = (
                    mean_position_angle, '[deg] PA of mean image ellipticity')

            logging_tags = {
                key: float(image.header[key])
                for key in [
                    'L1MEAN', 'L1MEDIAN', 'L1SIGMA', 'L1FWHM', 'L1ELLIP',
                    'L1ELLIPA'
                ]
            }

            logger.info('Extracted sources',
                        image=image,
                        extra_tags=logging_tags)
            # adding catalog (a data table) to the appropriate images attribute.
            image.data_tables['catalog'] = DataTable(data_table=catalog,
                                                     name='CAT')
        except Exception:
            logger.error(logs.format_exception(), image=image)
        return image
Example #24
0
    def AutoPhot(self, Kron_fact=2.5, min_diameter=3.5, write=True):
        kronrad, krflag = sep.kron_radius(self.dat, self.src['x'],
                                          self.src['y'], self.src['a'],
                                          self.src['b'], self.src['theta'], 6.)
        kronrad[np.isnan(kronrad) == True] = 0.
        flux, fluxerr, flag = sep.sum_ellipse(self.dat,
                                              self.src['x'],
                                              self.src['y'],
                                              self.src['a'],
                                              self.src['b'],
                                              self.src['theta'],
                                              Kron_fact * kronrad,
                                              err=self.skysigma,
                                              gain=self.gain,
                                              subpix=0)
        flag |= krflag  # Combining flags
        r_min = 0.5 * min_diameter

        use_circle = kronrad * np.sqrt(self.src['a'] * self.src['b']) < r_min
        cflux, cfluxerr, cflag = sep.sum_circle(self.dat,
                                                self.src['x'][use_circle],
                                                self.src['y'][use_circle],
                                                r_min,
                                                err=self.skysigma,
                                                gain=self.gain,
                                                subpix=0)
        flux[use_circle] = cflux
        fluxerr[use_circle] = cfluxerr
        flag[use_circle] = cflag

        mag = self.zmag - 2.5 * np.log10(flux)
        magerr = (2.5 / np.log(10.0)) * (fluxerr / flux)

        r, flag = sep.flux_radius(self.dat,
                                  self.src['x'],
                                  self.src['y'],
                                  6.0 * self.src['a'],
                                  0.5,
                                  normflux=flux,
                                  subpix=5)

        ra, dec = self.wcs.all_pix2world(self.src['x'] + 1, self.src['y'] + 1,
                                         1)

        df = pd.DataFrame(
            data={
                'x': self.src['x'],
                'y': self.src['y'],
                'ra': ra,
                'dec': dec,
                'a': self.src['a'],
                'b': self.src['b'],
                'theta': self.src['theta'],
                'flux': flux,
                'e_flux': fluxerr,
                'mag': mag,
                'e_mag': magerr,
                'kronrad': kronrad,
                'flxrad': r,
                'flag': flag
            })

        if write:
            df.to_csv(ip.tmp_dir + 'auto_' + self.img.split('.fits')[0] +
                      '.csv')

            f = open(ip.tmp_dir + 'src_' + self.img.split('.fits')[0] + '.reg',
                     'w')
            for i in np.arange(self.nsrc):
                f.write('{0:.3f}  {1:.3f}\n'.format(self.src['x'][i] + 1,
                                                    self.src['y'][i] + 1))
            f.close()

        return df
Example #25
0
    def _measure(self, img, sources, mask=None):

        logger.info('measuring source parameters')

        # HACK: issues with numerical precision
        # must have pi/2 <= theta <= npi/2
        sources[np.abs(np.abs(sources['theta']) -
                       np.pi / 2) < 1e-6] = np.pi / 2

        for p in ['x', 'y', 'a', 'b', 'theta']:
            sources = sources[~np.isnan(sources[p])]

        # calculate "AUTO" parameters
        kronrad, krflag = sep.kron_radius(img,
                                          sources['x'],
                                          sources['y'],
                                          sources['a'],
                                          sources['b'],
                                          sources['theta'],
                                          6.0,
                                          mask=mask)

        flux, fluxerr, flag = sep.sum_ellipse(img,
                                              sources['x'],
                                              sources['y'],
                                              sources['a'],
                                              sources['b'],
                                              sources['theta'],
                                              2.5 * kronrad,
                                              subpix=5,
                                              mask=mask)
        flag |= krflag  # combine flags into 'flag'

        sources = sources[~np.isnan(flux)]
        flux = flux[~np.isnan(flux)]
        sources = sources[flux > 0]
        flux = flux[flux > 0]

        mag_auto = self.zpt - 2.5 * np.log10(flux)
        r, flag = sep.flux_radius(img,
                                  sources['x'],
                                  sources['y'],
                                  6. * sources['a'],
                                  0.5,
                                  normflux=flux,
                                  subpix=5,
                                  mask=mask)

        sources['mag_auto'] = mag_auto
        sources['flux_auto'] = flux
        sources['flux_radius'] = r * self.pixscale

        # approximate fwhm
        r_squared = sources['a']**2 + sources['b']**2
        sources['fwhm'] = 2 * np.sqrt(np.log(2) * r_squared) * self.pixscale

        q = sources['b'] / sources['a']
        area = np.pi * q * sources['flux_radius']**2
        sources['mu_ave_auto'] = sources['mag_auto'] + 2.5 * np.log10(2 * area)

        area_arcsec = np.pi * (self.psf_fwhm / 2)**2 * self.pixscale**2
        flux, fluxerr, flag = sep.sum_circle(img,
                                             sources['x'],
                                             sources['y'],
                                             self.psf_fwhm / 2,
                                             subpix=5,
                                             mask=mask)
        flux[flux <= 0] = np.nan
        mu_0 = self.zpt - 2.5 * np.log10(flux / area_arcsec)

        sources['mu_0_aper'] = mu_0

        return sources
Example #26
0
def test_masked_segmentation_measurements():
    """Test measurements with segmentation masking"""

    NX = 100
    data = np.zeros((NX * 2, NX * 2))
    yp, xp = np.indices(data.shape)

    ####
    # Make two 2D gaussians that slightly overlap

    # width of the 2D objects
    gsigma = 10.

    # offset between two gaussians in sigmas
    off = 4

    for xy in [[NX, NX], [NX + off * gsigma, NX + off * gsigma]]:
        R = np.sqrt((xp - xy[0])**2 + (yp - xy[1])**2)
        g_i = np.exp(-R**2 / 2 / gsigma**2)
        data += g_i

    # Absolute total
    total_exact = g_i.sum()

    # Add some noise
    rms = 0.02
    np.random.seed(1)
    data += np.random.normal(size=data.shape) * rms

    # Run source detection
    objs, segmap = sep.extract(data,
                               thresh=1.2,
                               err=rms,
                               mask=None,
                               segmentation_map=True)

    seg_id = np.arange(1, len(objs) + 1, dtype=np.int32)

    # Compute Kron/Auto parameters
    x, y, a, b = objs['x'], objs['y'], objs['a'], objs['b']
    theta = objs['theta']

    kronrad, krflag = sep.kron_radius(data, x, y, a, b, theta, 6.0)

    flux_auto, fluxerr, flag = sep.sum_ellipse(data,
                                               x,
                                               y,
                                               a,
                                               b,
                                               theta,
                                               2.5 * kronrad,
                                               segmap=segmap,
                                               seg_id=seg_id,
                                               subpix=1)

    # Test total flux
    assert_allclose(flux_auto, total_exact, rtol=5.e-2)

    # Flux_radius
    for flux_fraction in [0.2, 0.5]:

        # Exact solution
        rhalf_exact = np.sqrt(-np.log(1 - flux_fraction) * gsigma**2 * 2)

        # Masked measurement
        flux_radius, flag = sep.flux_radius(data,
                                            x,
                                            y,
                                            6. * a,
                                            flux_fraction,
                                            seg_id=seg_id,
                                            segmap=segmap,
                                            normflux=flux_auto,
                                            subpix=5)

        # Test flux fraction
        assert_allclose(flux_radius, rhalf_exact, rtol=5.e-2)

    if False:
        print('test_masked_flux_radius')
        print(total_exact, flux_auto)
        print(rhalf_exact, flux_radius)
Example #27
0
def evaluate_sky(img, sigma=1.5, radius=10, pixel_scale=0.168, central_mask_radius=7.0, 
                 threshold=0.005, deblend_cont=0.001, deblend_nthresh=20, 
                 clean_param=1.0, show_fig=True, show_hist=True, f_factor=None):
    '''Evaluate the mean sky value.
    Parameters:
    ----------
    img: 2-D numpy array, the input image
    show_fig: bool. If True, it will show you the masked sky image.
    show_hist: bool. If True, it will show you the histogram of the sky value.
    
    Returns:
    -------
    median: median of background pixels, in original unit
    std: standard deviation, in original unit
    '''
    import sep
    import copy 
    from slug.imutils import extract_obj, make_binary_mask
    from astropy.convolution import convolve, Gaussian2DKernel
    b = 35  # Box size
    f = 5   # Filter width

    bkg = sep.Background(img, maskthresh=0, bw=b, bh=b, fw=f, fh=f)
    # first time
    objects, segmap = extract_obj(img - bkg.globalback, b=35, f=5, sigma=sigma,
                                    minarea=20, pixel_scale=pixel_scale,
                                    deblend_nthresh=deblend_nthresh, deblend_cont=deblend_cont,
                                    clean_param=clean_param, show_fig=False)
    
    seg_sky = copy.deepcopy(segmap)
    seg_sky[segmap > 0] = 1
    seg_sky = seg_sky.astype(bool)
    # Blow up the mask
    for obj in objects:
        sep.mask_ellipse(seg_sky, obj['x'], obj['y'], obj['a'], obj['b'], obj['theta'], r=radius)
    bkg_mask_1 = seg_sky
    
    data = copy.deepcopy(img - bkg.globalback)
    data[bkg_mask_1 == 1] = 0

    # Second time
    obj_lthre, seg_lthre = extract_obj(data, b=35, f=5, sigma=sigma + 1,
                                       minarea=5, pixel_scale=pixel_scale,
                                       deblend_nthresh=deblend_nthresh, deblend_cont=deblend_cont,
                                       clean_param=clean_param, show_fig=False)
    seg_sky = copy.deepcopy(seg_lthre)
    seg_sky[seg_lthre > 0] = 1
    seg_sky = seg_sky.astype(bool)
    # Blow up the mask
    for obj in obj_lthre:
        sep.mask_ellipse(seg_sky, obj['x'], obj['y'], obj['a'], obj['b'], obj['theta'], r=radius/2)
    bkg_mask_2 = seg_sky
    
    bkg_mask = (bkg_mask_1 + bkg_mask_2).astype(bool)
    
    cen_obj = objects[segmap[int(bkg_mask.shape[0] / 2.), int(bkg_mask.shape[1] / 2.)] - 1]
    fraction_radius = sep.flux_radius(img, cen_obj['x'], cen_obj['y'], 10*cen_obj['a'], 0.5)[0]
    
    ba = np.divide(cen_obj['b'], cen_obj['a'])
    
    if fraction_radius < int(bkg_mask.shape[0] / 8.):
        sep.mask_ellipse(bkg_mask, cen_obj['x'], cen_obj['y'], fraction_radius, fraction_radius * ba,
                        cen_obj['theta'], r=central_mask_radius)
    elif fraction_radius < int(bkg_mask.shape[0] / 4.):
        sep.mask_ellipse(bkg_mask, cen_obj['x'], cen_obj['y'], fraction_radius, fraction_radius * ba,
                        cen_obj['theta'], r=1.2)
    
    # Estimate sky from histogram of binned image
    import copy
    from scipy import stats
    from astropy.stats import sigma_clip
    from astropy.nddata import block_reduce
    data = copy.deepcopy(img)
    data[bkg_mask] = np.nan
    if f_factor is None:
        f_factor = round(6 / pixel_scale)
    rebin = block_reduce(data, f_factor)
    sample = rebin.flatten()
    if show_fig:
        display_single(rebin)
        plt.savefig('./{}-bkg.png'.format(np.random.randint(1000)), dpi=100, bbox_inches='tight')
    
    temp = sigma_clip(sample)
    sample = temp.data[~temp.mask]

    kde = stats.gaussian_kde(sample)
    print(f_factor)
    mean = np.nanmean(sample) / f_factor**2
    median = np.nanmedian(sample) / f_factor**2
    std = np.nanstd(sample, ddof=1) / f_factor / np.sqrt(len(sample))

    xlim = np.std(sample, ddof=1) * 7
    x = np.linspace(-xlim + np.median(sample), xlim + np.median(sample), 100)
    offset = x[np.argmax(kde.evaluate(x))] / f_factor**2
    
    print('mean', mean)
    print('median', median)
    print('std', std)

    bkg_global = sep.Background(img, 
                                mask=bkg_mask, maskthresh=0,
                                bw=f_factor, bh=f_factor, 
                                fw=f_factor/2, fh=f_factor/2)
    print("#SEP sky: Mean Sky / RMS Sky = %10.5f / %10.5f" % (bkg_global.globalback, bkg_global.globalrms))

    if show_hist:
        fig, ax = plt.subplots(figsize=(8,6))

        ax.plot(x, kde.evaluate(x), linestyle='dashed', c='black', lw=2,
                label='KDE')
        ax.hist(sample, bins=x, normed=1);
        ax.legend(loc='best', frameon=False, fontsize=20)

        ax.set_xlabel('Pixel Value', fontsize=20)
        ax.set_ylabel('Normed Number', fontsize=20)
        ax.tick_params(labelsize=20)
        ylim = ax.get_ylim()
        ax.text(-0.1 * f_factor + np.median(sample), 0.9 * (ylim[1] - ylim[0]) + ylim[0], 
                r'$\mathrm{offset}='+str(round(offset, 6))+'$', fontsize=20)
        ax.text(-0.1 * f_factor + np.median(sample), 0.8 * (ylim[1] - ylim[0]) + ylim[0],
                r'$\mathrm{median}='+str(round(median, 6))+'$', fontsize=20)
        ax.text(-0.1 * f_factor + np.median(sample), 0.7 * (ylim[1] - ylim[0]) + ylim[0],
                r'$\mathrm{std}='+str(round(std, 6))+'$', fontsize=20)
        plt.vlines(np.median(sample), 0, ylim[1], linestyle='--')

    return median, std, sample
Example #28
0
File: test.py Project: kbarbary/sep
def test_masked_segmentation_measurements():
    """Test measurements with segmentation masking"""
    
    NX = 100
    data = np.zeros((NX*2,NX*2))
    yp, xp = np.indices(data.shape)
    
    ####
    # Make two 2D gaussians that slightly overlap
    
    # width of the 2D objects
    gsigma = 10.  
      
    # offset between two gaussians in sigmas
    off = 4 

    for xy in [[NX,NX], [NX+off*gsigma, NX+off*gsigma]]:
        R = np.sqrt((xp-xy[0])**2+(yp-xy[1])**2)
        g_i = np.exp(-R**2/2/gsigma**2)
        data += g_i
    
    # Absolute total
    total_exact = g_i.sum()
    
    # Add some noise
    rms = 0.02
    np.random.seed(1)
    data += np.random.normal(size=data.shape)*rms
    
    # Run source detection
    objs, segmap = sep.extract(data, thresh=1.2, err=rms, mask=None,
                               segmentation_map=True)
    
    seg_id = np.arange(1, len(objs)+1, dtype=np.int32)
    
    # Compute Kron/Auto parameters
    x, y, a, b = objs['x'], objs['y'], objs['a'], objs['b']
    theta = objs['theta']
    
    kronrad, krflag = sep.kron_radius(data, x, y, a, b, theta, 6.0)
    
    flux_auto, fluxerr, flag = sep.sum_ellipse(data, x, y, a, b, theta,
                                               2.5*kronrad, 
                                               segmap=segmap, seg_id=seg_id, 
                                               subpix=1)
    
    # Test total flux
    assert_allclose(flux_auto, total_exact, rtol=5.e-2)
        
    # Flux_radius
    for flux_fraction in [0.2, 0.5]:
    
        # Exact solution
        rhalf_exact = np.sqrt(-np.log(1-flux_fraction)*gsigma**2*2)
    
        # Masked measurement
        flux_radius, flag = sep.flux_radius(data, x, y, 6.*a, flux_fraction,
                                        seg_id=seg_id, segmap=segmap, 
                                        normflux=flux_auto, subpix=5)
        
        # Test flux fraction
        assert_allclose(flux_radius, rhalf_exact, rtol=5.e-2)
    
    if False:
        print('test_masked_flux_radius')
        print(total_exact, flux_auto)
        print(rhalf_exact, flux_radius)