Example #1
0
    def _lnprior():
        """
        Internal function for the log prior function.

        :return: Log prior.
        :rtype: float
        """

        if prior == "flat":

            if bounds[0][0] <= param[0] <= bounds[0][1] and \
               bounds[1][0] <= param[1] <= bounds[1][1] and \
               bounds[2][0] <= param[2] <= bounds[2][1]:

                ln_prior = 0.

            else:

                ln_prior = -np.inf

        elif prior == "aperture":

            x_pos, y_pos = polar_to_cartesian(images, param[0]/pixscale, param[1])

            delta_x = x_pos - aperture['pos_x']
            delta_y = y_pos - aperture['pos_y']

            if aperture['type'] == "circular":

                if math.sqrt(delta_x**2+delta_y**2) < aperture['radius'] and \
                   bounds[2][0] <= param[2] <= bounds[2][1]:

                    ln_prior = 0.

                else:

                    ln_prior = -np.inf

            elif aperture['type'] == "elliptical":

                cos_ang = math.cos(math.radians(180.-aperture['angle']))
                sin_ang = math.sin(math.radians(180.-aperture['angle']))

                x_rot = delta_x*cos_ang - delta_y*sin_ang
                y_rot = delta_x*sin_ang + delta_y*cos_ang

                r_check = (x_rot/aperture['semimajor'])**2 + (y_rot/aperture['semiminor'])**2

                if r_check <= 1. and bounds[2][0] <= param[2] <= bounds[2][1]:
                    ln_prior = 0.

                else:
                    ln_prior = -np.inf


        else:
            raise ValueError("Prior type not recognized.")

        return ln_prior
Example #2
0
    def aperture_dict(self, images):
        """
        Function to create or update the dictionary with aperture properties.

        Parameters
        ----------
        images : numpy.ndarray
            Input images.

        Returns
        -------
        NoneType
            None
        """

        pixscale = self.m_image_in_port.get_attribute('PIXSCALE')

        if isinstance(self.m_aperture, float):
            xy_pos = polar_to_cartesian(images, self.m_param[0] / pixscale,
                                        self.m_param[1])

            self.m_aperture = {
                'type': 'circular',
                'pos_x': xy_pos[0],
                'pos_y': xy_pos[1],
                'radius': self.m_aperture / pixscale
            }

        elif isinstance(self.m_aperture, dict):
            if self.m_aperture['type'] == 'circular':
                self.m_aperture['radius'] /= pixscale

            elif self.m_aperture['type'] == 'elliptical':
                self.m_aperture['semimajor'] /= pixscale
                self.m_aperture['semiminor'] /= pixscale

        if self.m_variance == 'gaussian' and self.m_aperture[
                'type'] != 'circular':
            raise ValueError(
                'Gaussian variance can only be used in combination with a'
                'circular aperture.')
Example #3
0
def contrast_limit(path_images, path_psf, noise, mask, parang, psf_scaling,
                   extra_rot, pca_number, threshold, aperture, residuals,
                   snr_inject, position):
    """
    Function for calculating the contrast limit at a specified position for a given sigma level or
    false positive fraction, both corrected for small sample statistics.

    Parameters
    ----------
    path_images : str
        System location of the stack of images (3D).
    path_psf : str
        System location of the PSF template for the fake planet (3D). Either a single image or a
        stack of images equal in size to science data.
    noise : numpy.ndarray
        Residuals of the PSF subtraction (3D) without injection of fake planets. Used to measure
        the noise level with a correction for small sample statistics.
    mask : numpy.ndarray
        Mask (2D).
    parang : numpy.ndarray
        Derotation angles (deg).
    psf_scaling : float
        Additional scaling factor of the planet flux (e.g., to correct for a neutral density
        filter). Should have a positive value.
    extra_rot : float
        Additional rotation angle of the images in clockwise direction (deg).
    pca_number : int
        Number of principal components used for the PSF subtraction.
    threshold : tuple(str, float)
        Detection threshold for the contrast curve, either in terms of "sigma" or the false
        positive fraction (FPF). The value is a tuple, for example provided as ("sigma", 5.) or
        ("fpf", 1e-6). Note that when sigma is fixed, the false positive fraction will change with
        separation. Also, sigma only corresponds to the standard deviation of a normal distribution
        at large separations (i.e., large number of samples).
    aperture : float
        Aperture radius (pix) for the calculation of the false positive fraction.
    residuals : str
        Method used for combining the residuals ("mean", "median", "weighted", or "clipped").
    position : tuple(float, float)
        The separation (pix) and position angle (deg) of the fake planet.
    snr_inject : float
        Signal-to-noise ratio of the injected planet signal that is used to measure the amount
        of self-subtraction.

    Returns
    -------
    NoneType
        None
    """

    images = np.load(path_images)
    psf = np.load(path_psf)

    if threshold[0] == "sigma":
        sigma = threshold[1]

        # Calculate the FPF for a given sigma level
        fpf = student_t(t_input=threshold,
                        radius=position[0],
                        size=aperture,
                        ignore=False)

    elif threshold[0] == "fpf":
        fpf = threshold[1]

        # Calculate the sigma level for a given FPF
        sigma = student_t(t_input=threshold,
                          radius=position[0],
                          size=aperture,
                          ignore=False)

    else:
        raise ValueError("Threshold type not recognized.")

    # Cartesian coordinates of the fake planet
    xy_fake = polar_to_cartesian(images, position[0], position[1] - extra_rot)

    # Determine the noise level
    _, t_noise, _, _ = false_alarm(image=noise[0, ],
                                   x_pos=xy_fake[0],
                                   y_pos=xy_fake[1],
                                   size=aperture,
                                   ignore=False)

    # Aperture properties
    im_center = center_subpixel(images)
    ap_dict = {
        'type': 'circular',
        'pos_x': im_center[1],
        'pos_y': im_center[0],
        'radius': aperture
    }

    # Measure the flux of the star
    phot_table = aperture_photometry(psf_scaling * psf[0, ],
                                     create_aperture(ap_dict),
                                     method='exact')
    star = phot_table['aperture_sum'][0]

    # Magnitude of the injected planet
    flux_in = snr_inject * t_noise
    mag = -2.5 * math.log10(flux_in / star)

    # Inject the fake planet
    fake = fake_planet(images=images,
                       psf=psf,
                       parang=parang,
                       position=(position[0], position[1]),
                       magnitude=mag,
                       psf_scaling=psf_scaling)

    # Run the PSF subtraction
    _, im_res = pca_psf_subtraction(images=fake * mask,
                                    angles=-1. * parang + extra_rot,
                                    pca_number=pca_number)

    # Stack the residuals
    im_res = combine_residuals(method=residuals, res_rot=im_res)

    # Measure the flux of the fake planet
    flux_out, _, _, _ = false_alarm(image=im_res[0, ],
                                    x_pos=xy_fake[0],
                                    y_pos=xy_fake[1],
                                    size=aperture,
                                    ignore=False)

    # Calculate the amount of self-subtraction
    attenuation = flux_out / flux_in

    # Calculate the detection limit
    contrast = sigma * t_noise / (attenuation * star)

    # The flux_out can be negative, for example if the aperture includes self-subtraction regions
    if contrast > 0.:
        contrast = -2.5 * math.log10(contrast)
    else:
        contrast = np.nan

    # Separation [pix], position antle [deg], contrast [mag], FPF
    return position[0], position[1], contrast, fpf
Example #4
0
def contrast_limit(
        path_images: str, path_psf: str, noise: np.ndarray, mask: np.ndarray,
        parang: np.ndarray, psf_scaling: float, extra_rot: float,
        pca_number: int, threshold: Tuple[str, float], aperture: float,
        residuals: str, snr_inject: float,
        position: Tuple[float, float]) -> Tuple[float, float, float, float]:
    """
    Function for calculating the contrast limit at a specified position for a given sigma level or
    false positive fraction, both corrected for small sample statistics.

    Parameters
    ----------
    path_images : str
        System location of the stack of images (3D).
    path_psf : str
        System location of the PSF template for the fake planet (3D). Either a single image or a
        stack of images equal in size to science data.
    noise : numpy.ndarray
        Residuals of the PSF subtraction (3D) without injection of fake planets. Used to measure
        the noise level with a correction for small sample statistics.
    mask : numpy.ndarray
        Mask (2D).
    parang : numpy.ndarray
        Derotation angles (deg).
    psf_scaling : float
        Additional scaling factor of the planet flux (e.g., to correct for a neutral density
        filter). Should have a positive value.
    extra_rot : float
        Additional rotation angle of the images in clockwise direction (deg).
    pca_number : int
        Number of principal components used for the PSF subtraction.
    threshold : tuple(str, float)
        Detection threshold for the contrast curve, either in terms of 'sigma' or the false
        positive fraction (FPF). The value is a tuple, for example provided as ('sigma', 5.) or
        ('fpf', 1e-6). Note that when sigma is fixed, the false positive fraction will change with
        separation. Also, sigma only corresponds to the standard deviation of a normal distribution
        at large separations (i.e., large number of samples).
    aperture : float
        Aperture radius (pix) for the calculation of the false positive fraction.
    residuals : str
        Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped').
    snr_inject : float
        Signal-to-noise ratio of the injected planet signal that is used to measure the amount
        of self-subtraction.
    position : tuple(float, float)
        The separation (pix) and position angle (deg) of the fake planet.

    Returns
    -------
    float
        Separation (pix).
    float
        Position angle (deg).
    float
        Contrast (mag).
    float
        False positive fraction.
    """

    images = np.load(path_images)
    psf = np.load(path_psf)

    # Cartesian coordinates of the fake planet
    yx_fake = polar_to_cartesian(images, position[0], position[1] - extra_rot)

    # Determine the noise level
    noise_apertures = compute_aperture_flux_elements(image=noise[0, ],
                                                     x_pos=yx_fake[1],
                                                     y_pos=yx_fake[0],
                                                     size=aperture,
                                                     ignore=False)

    t_noise = np.std(noise_apertures, ddof=1) * \
              math.sqrt(1 + 1 / (noise_apertures.shape[0]))

    # get sigma from fpf or fpf from sigma
    # Note that the number of degrees of freedom is given by nu = n-1 with n the number of samples.
    # See Section 3 of Mawet et al. (2014) for more details on the Student's t distribution.

    if threshold[0] == 'sigma':
        sigma = threshold[1]

        # Calculate the FPF for a given sigma level

        fpf = t.sf(sigma, noise_apertures.shape[0] - 1, loc=0., scale=1.)

    elif threshold[0] == 'fpf':
        fpf = threshold[1]

        # Calculate the sigma level for a given FPF
        sigma = t.isf(fpf, noise_apertures.shape[0] - 1, loc=0., scale=1.)

    else:
        raise ValueError('Threshold type not recognized.')

    # Aperture properties
    im_center = center_subpixel(images)

    # Measure the flux of the star
    ap_phot = CircularAperture((im_center[1], im_center[0]), aperture)
    phot_table = aperture_photometry(psf_scaling * psf[0, ],
                                     ap_phot,
                                     method='exact')
    star = phot_table['aperture_sum'][0]

    # Magnitude of the injected planet
    flux_in = snr_inject * t_noise
    mag = -2.5 * math.log10(flux_in / star)

    # Inject the fake planet
    fake = fake_planet(images=images,
                       psf=psf,
                       parang=parang,
                       position=(position[0], position[1]),
                       magnitude=mag,
                       psf_scaling=psf_scaling)

    # Run the PSF subtraction
    _, im_res = pca_psf_subtraction(images=fake * mask,
                                    angles=-1. * parang + extra_rot,
                                    pca_number=pca_number)

    # Stack the residuals
    im_res = combine_residuals(method=residuals, res_rot=im_res)
    flux_out_frame = im_res[0, ] - noise[0, ]

    # Measure the flux of the fake planet after PCA
    # the first element is the planet
    flux_out = compute_aperture_flux_elements(image=flux_out_frame,
                                              x_pos=yx_fake[1],
                                              y_pos=yx_fake[0],
                                              size=aperture,
                                              ignore=False)[0]

    # Calculate the amount of self-subtraction
    attenuation = flux_out / flux_in
    # the throughput can not be negative. However, this can happen due to numerical inaccuracies
    if attenuation < 0:
        attenuation = 0

    # Calculate the detection limit
    contrast = (sigma * t_noise + np.mean(noise_apertures)) / (attenuation *
                                                               star)

    # The flux_out can be negative, for example if the aperture includes self-subtraction regions
    if contrast > 0.:
        contrast = -2.5 * math.log10(contrast)
    else:
        contrast = np.nan

    # Separation [pix], position angle [deg], contrast [mag], FPF
    return position[0], position[1], contrast, fpf
Example #5
0
    def run(self) -> None:
        """
        Run method of the module. The posterior distributions of the separation, position angle,
        and flux contrast are sampled with the affine invariant Markov chain Monte Carlo (MCMC)
        ensemble sampler emcee. At each step, a negative copy of the PSF template is injected
        and the likelihood function is evaluated at the approximate position of the planet.

        Returns
        -------
        NoneType
            None
        """

        ndim = 3

        cpu = self._m_config_port.get_attribute('CPU')
        pixscale = self.m_image_in_port.get_attribute('PIXSCALE')
        parang = self.m_image_in_port.get_attribute('PARANG')

        images = self.m_image_in_port.get_all()
        psf = self.m_psf_in_port.get_all()

        im_shape = self.m_image_in_port.get_shape()[-2:]

        self.m_image_in_port.close_port()
        self.m_psf_in_port.close_port()

        if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]:
            raise ValueError('The number of frames in psf_in_tag does not match with the number of '
                             'frames in image_in_tag. The DerotateAndStackModule can be used to '
                             'average the PSF frames (without derotating) before applying the '
                             'MCMCsamplingModule.')

        if self.m_mask[0] is not None:
            self.m_mask = (self.m_mask[0]/pixscale, self.m_mask[1])

        if self.m_mask[1] is not None:
            self.m_mask = (self.m_mask[0], self.m_mask[1]/pixscale)

        # create the mask and get the unmasked image indices
        mask = create_mask(im_shape[-2:], self.m_mask)
        indices = np.where(mask.reshape(-1) != 0.)[0]

        if isinstance(self.m_aperture, float):
            yx_pos = polar_to_cartesian(images, self.m_param[0]/pixscale, self.m_param[1])
            aperture = (int(round(yx_pos[0])), int(round(yx_pos[1])), self.m_aperture/pixscale)

        elif isinstance(self.m_aperture, tuple):
            aperture = (self.m_aperture[1], self.m_aperture[0], self.m_aperture[2]/pixscale)

        initial = np.zeros((self.m_nwalkers, ndim))

        initial[:, 0] = self.m_param[0] + np.random.normal(0, self.m_sigma[0], self.m_nwalkers)
        initial[:, 1] = self.m_param[1] + np.random.normal(0, self.m_sigma[1], self.m_nwalkers)
        initial[:, 2] = self.m_param[2] + np.random.normal(0, self.m_sigma[2], self.m_nwalkers)

        print('Sampling the posterior distributions with MCMC...')

        with Pool(processes=cpu) as pool:
            sampler = emcee.EnsembleSampler(self.m_nwalkers,
                                            ndim,
                                            lnprob,
                                            pool=pool,
                                            args=([self.m_bounds,
                                                   images,
                                                   psf,
                                                   mask,
                                                   parang,
                                                   self.m_psf_scaling,
                                                   pixscale,
                                                   self.m_pca_number,
                                                   self.m_extra_rot,
                                                   aperture,
                                                   indices,
                                                   self.m_merit,
                                                   self.m_residuals]))

            sampler.run_mcmc(initial, self.m_nsteps, progress=True)

        self.m_image_in_port._check_status_and_activate()
        self.m_chain_out_port._check_status_and_activate()

        self.m_chain_out_port.set_all(sampler.get_chain())

        history = f'walkers = {self.m_nwalkers}, steps = {self.m_nsteps}'
        self.m_chain_out_port.copy_attributes(self.m_image_in_port)
        self.m_chain_out_port.add_history('MCMCsamplingModule', history)

        mean_accept = np.mean(sampler.acceptance_fraction)
        print(f'Mean acceptance fraction: {mean_accept:.3f}')
        self.m_chain_out_port.add_attribute('ACCEPTANCE', mean_accept, static=True)

        try:
            autocorr = emcee.autocorr.integrated_time(sampler.get_chain())
            print(f'Integrated autocorrelation time ={autocorr}')

        except emcee.autocorr.AutocorrError:
            autocorr = [np.nan, np.nan, np.nan]
            print('The chain is too short to reliably estimate the autocorrelation time. [WARNING]')

        self.m_chain_out_port.add_attribute('AUTOCORR_0', autocorr[0], static=True)
        self.m_chain_out_port.add_attribute('AUTOCORR_1', autocorr[1], static=True)
        self.m_chain_out_port.add_attribute('AUTOCORR_2', autocorr[2], static=True)

        self.m_chain_out_port.close_port()
Example #6
0
    def run(self) -> None:
        """
        Run method of the module. The posterior distributions of the separation, position angle,
        and flux contrast are sampled with the affine invariant Markov chain Monte Carlo (MCMC)
        ensemble sampler emcee. At each step, a negative copy of the PSF template is injected
        and the likelihood function is evaluated at the approximate position of the planet.

        Returns
        -------
        NoneType
            None
        """

        ndim = 3

        cpu = self._m_config_port.get_attribute('CPU')
        pixscale = self.m_image_in_port.get_attribute('PIXSCALE')
        parang = self.m_image_in_port.get_attribute('PARANG')

        images = self.m_image_in_port.get_all()
        psf = self.m_psf_in_port.get_all()

        if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]:
            raise ValueError('The number of frames in psf_in_tag does not match with the number of '
                             'frames in image_in_tag. The DerotateAndStackModule can be used to '
                             'average the PSF frames (without derotating) before applying the '
                             'MCMCsamplingModule.')

        im_shape = self.m_image_in_port.get_shape()[-2:]

        if self.m_mask[0] is not None:
            self.m_mask = (self.m_mask[0]/pixscale, self.m_mask[1])

        if self.m_mask[1] is not None:
            self.m_mask = (self.m_mask[0], self.m_mask[1]/pixscale)

        # create the mask and get the unmasked image indices
        mask = create_mask(im_shape[-2:], self.m_mask)
        indices = np.where(mask.reshape(-1) != 0.)[0]

        if isinstance(self.m_aperture, float):
            yx_pos = polar_to_cartesian(images, self.m_param[0]/pixscale, self.m_param[1])
            aperture = (int(round(yx_pos[0])), int(round(yx_pos[1])), self.m_aperture/pixscale)

        elif isinstance(self.m_aperture, tuple):
            aperture = (self.m_aperture[1], self.m_aperture[0], self.m_aperture[2]/pixscale)

        initial = np.zeros((self.m_nwalkers, ndim))

        initial[:, 0] = self.m_param[0] + np.random.normal(0, self.m_sigma[0], self.m_nwalkers)
        initial[:, 1] = self.m_param[1] + np.random.normal(0, self.m_sigma[1], self.m_nwalkers)
        initial[:, 2] = self.m_param[2] + np.random.normal(0, self.m_sigma[2], self.m_nwalkers)

        # if self.m_merit == 'gaussian':
        #     variance = self.gaussian_variance(images*mask, psf, parang, aperture)
        # else:
        #     variance = None

        sampler = emcee.EnsembleSampler(nwalkers=self.m_nwalkers,
                                        dim=ndim,
                                        lnpostfn=lnprob,
                                        a=self.m_scale,
                                        args=([self.m_bounds,
                                               images,
                                               psf,
                                               mask,
                                               parang,
                                               self.m_psf_scaling,
                                               pixscale,
                                               self.m_pca_number,
                                               self.m_extra_rot,
                                               aperture,
                                               indices,
                                               self.m_merit,
                                               self.m_residuals]),
                                        threads=cpu)

        start_time = time.time()
        for i, _ in enumerate(sampler.sample(p0=initial, iterations=self.m_nsteps)):
            progress(i, self.m_nsteps, 'Running MCMCsamplingModule...', start_time)

        sys.stdout.write('Running MCMCsamplingModule... [DONE]\n')
        sys.stdout.flush()

        self.m_chain_out_port.set_all(sampler.chain)

        history = f'walkers = {self.m_nwalkers}, steps = {self.m_nsteps}'
        self.m_chain_out_port.copy_attributes(self.m_image_in_port)
        self.m_chain_out_port.add_history('MCMCsamplingModule', history)
        self.m_chain_out_port.close_port()

        print(f'Mean acceptance fraction: {np.mean(sampler.acceptance_fraction):.3f}')

        try:
            autocorr = emcee.autocorr.integrated_time(sampler.flatchain,
                                                      low=10,
                                                      high=None,
                                                      step=1,
                                                      c=10,
                                                      full_output=False,
                                                      axis=0,
                                                      fast=False)

            print('Integrated autocorrelation time =', autocorr)

        except emcee.autocorr.AutocorrError:
            print('The chain is too short to reliably estimate the autocorrelation time. [WARNING]')
Example #7
0
def paco_contrast_limit(path_images, path_psf, noise, parang, psf_rad,
                        psf_scaling, res_scaling, pixscale, extra_rot,
                        threshold, aperture, snr_inject, position, algorithm):
    """
    Function for calculating the contrast limit at a specified position for a given sigma level or
    false positive fraction, both corrected for small sample statistics.

    Parameters
    ----------
    path_images : str
        System location of the stack of images (3D).
    path_psf : str
        System location of the PSF template for the fake planet (3D). Either a single image or a
        stack of images equal in size to science data.
    noise : numpy.ndarray
        Residuals of the PSF subtraction (3D) without injection of fake planets. Used to measure
        the noise level with a correction for small sample statistics.
    parang : numpy.ndarray
        Derotation angles (deg).
    psf_scaling : float
        Additional scaling factor of the planet flux (e.g., to correct for a neutral density
        filter). Should have a positive value.
    extra_rot : float
        Additional rotation angle of the images in clockwise direction (deg).
    threshold : tuple(str, float)
        Detection threshold for the contrast curve, either in terms of "sigma" or the false
        positive fraction (FPF). The value is a tuple, for example provided as ("sigma", 5.) or
        ("fpf", 1e-6). Note that when sigma is fixed, the false positive fraction will change with
        separation. Also, sigma only corresponds to the standard deviation of a normal distribution
        at large separations (i.e., large number of samples).
    aperture : float
        Aperture radius (pix) for the calculation of the false positive fraction.
    position : tuple(float, float)
        The separation (pix) and position angle (deg) of the fake planet.
    snr_inject : float
        Signal-to-noise ratio of the injected planet signal that is used to measure the amount
        of self-subtraction.

    Returns
    -------
    NoneType
        None
    """
    images = np.load(path_images)
    psf = np.load(path_psf)

    if threshold[0] == "sigma":
        sigma = threshold[1]

        # Calculate the FPF for a given sigma level
        fpf = student_t(t_input=threshold,
                        radius=position[0],
                        size=aperture,
                        ignore=False)

    elif threshold[0] == "fpf":
        fpf = threshold[1]

        # Calculate the sigma level for a given FPF
        sigma = student_t(t_input=threshold,
                          radius=position[0],
                          size=aperture,
                          ignore=False)

    else:
        raise ValueError("Threshold type not recognized.")

    # Cartesian coordinates of the fake planet
    xy_fake = polar_to_cartesian(images, position[0], position[1] - extra_rot)

    # Determine the noise level
    _, t_noise, _, _ = false_alarm(image=noise,
                                   x_pos=xy_fake[0],
                                   y_pos=xy_fake[1],
                                   size=aperture,
                                   ignore=False)

    # Aperture properties
    im_center = center_subpixel(images)

    # Measure the flux of the star
    ap_phot = CircularAperture((im_center[1], im_center[0]), aperture)
    phot_table = aperture_photometry(psf_scaling * psf[0, ],
                                     ap_phot,
                                     method='exact')
    star = phot_table['aperture_sum'][0]

    # Magnitude of the injected planet
    flux_in = snr_inject * t_noise
    mag = -2.5 * math.log10(flux_in / star)

    # Inject the fake planet
    fake = fake_planet(images=images,
                       psf=psf,
                       parang=parang,
                       position=(position[0], position[1]),
                       magnitude=mag,
                       psf_scaling=psf_scaling)
    path_fake_planet = os.path.split(path_images)[0] = "/"
    np.save(path_fake_planet + "injected.npy", fake)
    # Run the PSF subtraction
    #_, im_res = pca_psf_subtraction(images=fake*mask,
    #                                angles=-1.*parang+extra_rot,
    #                                pca_number=pca_number)

    # Stack the residuals
    #im_res = combine_residuals(method=residuals, res_rot=im_res)

    # Measure the flux of the fake planet
    #flux_out, _, _, _ = false_alarm(image=im_res[0, ],
    #                                x_pos=xy_fake[0],
    #                                y_pos=xy_fake[1],
    #                                size=aperture,
    #                                ignore=False)
    # Setup PACO
    if algorithm == "fastpaco":
        fp = FastPACO(image_file=path_fake_planet + "injected.npy",
                      angles=parang,
                      psf=psf,
                      psf_rad=psf_rad,
                      px_scale=pixscale,
                      res_scale=res_scaling,
                      verbose=False)
    elif algorithm == "fullpaco":
        fp = FullPACO(image_file=path_fake_planet + "injected.npy",
                      angles=parang,
                      psf=psf,
                      psf_rad=psf_rad,
                      px_scale=pixscale,
                      res_scale=res_scaling,
                      verbose=False)

    # Run PACO
    # Restrict to 1 processor since this module is called from a child process
    a, b = fp.PACO(cpu=1)

    # Should do something with these?
    snr = b / np.sqrt(a)
    flux_residual = b / a
    flux_out, _, _, _ = false_alarm(image=flux_residual,
                                    x_pos=xy_fake[0],
                                    y_pos=xy_fake[1],
                                    size=aperture,
                                    ignore=False)

    # Iterative, unbiased flux estimation
    # This doesn't seem to give the correct results yet?
    #if self.m_flux_calc:
    #    phi0s = fp.thresholdDetection(snr,self.m_threshold)
    #    init = np.array([flux[int(phi0[0]),int(phi0[1])] for phi0 in phi0s])
    #    ests =  np.array(fp.fluxEstimate(phi0s,self.m_eps,init))

    # Calculate the amount of self-subtraction
    attenuation = flux_out / flux_in

    # Calculate the detection limit
    contrast = sigma * t_noise / (attenuation * star)

    # The flux_out can be negative, for example if the aperture includes self-subtraction regions
    if contrast > 0.:
        contrast = -2.5 * math.log10(contrast)
    else:
        contrast = np.nan

    # Separation [pix], position antle [deg], contrast [mag], FPF
    return (position[0], position[1], contrast, fpf)