Ejemplo n.º 1
0
    def run(self) -> None:
        """
        Run method of the module. Computes the flux within a circular aperture for each
        frame and saves the values in the database.

        Returns
        -------
        NoneType
            None
        """
        def _photometry(image, aperture):
            return aperture_photometry(image, aperture,
                                       method='exact')['aperture_sum']

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

        if self.m_position is None:
            self.m_position = center_subpixel(self.m_image_in_port[0, ])

        # Position in CircularAperture is defined as (x, y)
        aperture = CircularAperture(self.m_position, self.m_radius)

        self.apply_function_to_images(_photometry,
                                      self.m_image_in_port,
                                      self.m_phot_out_port,
                                      'Running AperturePhotometryModule',
                                      func_args=(aperture, ))

        history = f'radius [arcsec] = {self.m_radius*pixscale:.3f}'
        self.m_phot_out_port.copy_attributes(self.m_image_in_port)
        self.m_phot_out_port.add_history('AperturePhotometryModule', history)
        self.m_phot_out_port.close_port()
Ejemplo n.º 2
0
    def run(self) -> None:
        """
        Run method of the module. Computes the flux within a circular aperture for each
        frame and saves the values in the database.

        Returns
        -------
        NoneType
            None
        """
        def _photometry(image, aperture):
            # https://photutils.readthedocs.io/en/stable/overview.html
            # In Photutils, pixel coordinates are zero-indexed, meaning that (x, y) = (0, 0)
            # corresponds to the center of the lowest, leftmost array element. This means that
            # the value of data[0, 0] is taken as the value over the range -0.5 < x <= 0.5,
            # -0.5 < y <= 0.5. Note that this is the same coordinate system as used by PynPoint.

            return aperture_photometry(image, aperture,
                                       method='exact')['aperture_sum']

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

        if self.m_position is None:
            self.m_position = center_subpixel(self.m_image_in_port[0, ])

        # Position in CircularAperture is defined as (x, y)
        aperture = CircularAperture((self.m_position[1], self.m_position[0]),
                                    self.m_radius)

        self.apply_function_to_images(_photometry,
                                      self.m_image_in_port,
                                      self.m_phot_out_port,
                                      'Running AperturePhotometryModule',
                                      func_args=(aperture, ))

        history = f'radius [arcsec] = {self.m_radius*pixscale:.3f}'
        self.m_phot_out_port.copy_attributes(self.m_image_in_port)
        self.m_phot_out_port.add_history('AperturePhotometryModule', history)
        self.m_phot_out_port.close_port()
Ejemplo n.º 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
Ejemplo n.º 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
Ejemplo n.º 5
0
    def run(self) -> None:
        """
        Run method of the module. Calculates the SNR and FPF for a specified position in a post-
        processed image with the Student's t-test (Mawet et al. 2014). This approach assumes
        Gaussian noise but accounts for small sample statistics.

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

        def _snr_optimize(arg):
            pos_x, pos_y = arg

            _, _, snr, _ = false_alarm(image=image,
                                       x_pos=pos_x,
                                       y_pos=pos_y,
                                       size=self.m_aperture,
                                       ignore=self.m_ignore)

            return -snr

        self.m_snr_out_port.del_all_data()
        self.m_snr_out_port.del_all_attributes()

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

        nimages = self.m_image_in_port.get_shape()[0]

        bounds = ((self.m_position[0]+self.m_bounds[0][0], self.m_position[0]+self.m_bounds[0][1]),
                  (self.m_position[1]+self.m_bounds[1][0], self.m_position[1]+self.m_bounds[1][1]))

        start_time = time.time()
        for j in range(nimages):
            progress(j, nimages, 'Calculating S/N and FPF...', start_time)

            image = self.m_image_in_port[j, ]
            center = center_subpixel(image)

            if self.m_optimize:
                result = minimize(fun=_snr_optimize,
                                  x0=[self.m_position[0], self.m_position[1]],
                                  method='SLSQP',
                                  bounds=bounds,
                                  tol=None,
                                  options={'ftol': self.m_tolerance})

                _, _, snr, fpf = false_alarm(image=image,
                                             x_pos=result.x[0],
                                             y_pos=result.x[1],
                                             size=self.m_aperture,
                                             ignore=self.m_ignore)

                x_pos, y_pos = result.x[0], result.x[1]

            else:
                _, _, snr, fpf = false_alarm(image=image,
                                             x_pos=self.m_position[0],
                                             y_pos=self.m_position[1],
                                             size=self.m_aperture,
                                             ignore=self.m_ignore)

                x_pos, y_pos = self.m_position[0], self.m_position[1]

            sep_ang = cartesian_to_polar(center, y_pos, x_pos)
            result = np.column_stack((x_pos, y_pos, sep_ang[0]*pixscale, sep_ang[1], snr, fpf))

            self.m_snr_out_port.append(result, data_dim=2)

        history = f'aperture [arcsec] = {self.m_aperture*pixscale:.2f}'
        self.m_snr_out_port.copy_attributes(self.m_image_in_port)
        self.m_snr_out_port.add_history('FalsePositiveModule', history)
        self.m_snr_out_port.close_port()
Ejemplo n.º 6
0
    def run(self) -> None:
        """
        Run method of the module. The position and contrast of a planet is measured by injecting
        negative copies of the PSF template and applying a simplex method (Nelder-Mead) for
        minimization of a figure of merit at the planet location.

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

        for item in self.m_res_out_port:
            item.del_all_data()
            item.del_all_attributes()

        for item in self.m_flux_pos_port:
            item.del_all_data()
            item.del_all_attributes()

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

        aperture = (self.m_position[1], self.m_position[0], self.m_aperture/pixscale)

        self.m_sigma /= pixscale

        if self.m_cent_size is not None:
            self.m_cent_size /= pixscale

        if self.m_edge_size is not None:
            self.m_edge_size /= pixscale

        psf = self.m_psf_in_port.get_all()
        images = self.m_image_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 SimplexMinimizationModule.')

        center = center_subpixel(psf)

        if self.m_reference_in_port is not None and self.m_merit != 'poisson':
            raise NotImplementedError('The reference_in_tag can only be used in combination with '
                                      'the \'poisson\' figure of merit.')

        def _objective(arg, count, n_components, sklearn_pca):
            pos_y = arg[0]
            pos_x = arg[1]
            mag = arg[2]

            sep_ang = cartesian_to_polar(center, pos_y, pos_x)

            fake = fake_planet(images=images,
                               psf=psf,
                               parang=parang,
                               position=(sep_ang[0], sep_ang[1]),
                               magnitude=mag,
                               psf_scaling=self.m_psf_scaling)

            mask = create_mask(fake.shape[-2:], (self.m_cent_size, self.m_edge_size))

            if self.m_reference_in_port is None:
                im_res_rot, im_res_derot = pca_psf_subtraction(images=fake*mask,
                                                               angles=-1.*parang+self.m_extra_rot,
                                                               pca_number=n_components,
                                                               pca_sklearn=sklearn_pca,
                                                               im_shape=None,
                                                               indices=None)

            else:
                im_reshape = np.reshape(fake*mask, (im_shape[0], im_shape[1]*im_shape[2]))

                im_res_rot, im_res_derot = pca_psf_subtraction(images=im_reshape,
                                                               angles=-1.*parang+self.m_extra_rot,
                                                               pca_number=n_components,
                                                               pca_sklearn=sklearn_pca,
                                                               im_shape=im_shape,
                                                               indices=None)

            res_stack = combine_residuals(method=self.m_residuals,
                                          res_rot=im_res_derot,
                                          residuals=im_res_rot,
                                          angles=parang)

            self.m_res_out_port[count].append(res_stack, data_dim=3)

            chi_square = merit_function(residuals=res_stack[0, ],
                                        merit=self.m_merit,
                                        aperture=aperture,
                                        sigma=self.m_sigma)

            position = rotate_coordinates(center, (pos_y, pos_x), -self.m_extra_rot)

            res = np.asarray([position[1],
                              position[0],
                              sep_ang[0]*pixscale,
                              (sep_ang[1]-self.m_extra_rot) % 360.,
                              mag,
                              chi_square])

            self.m_flux_pos_port[count].append(res, data_dim=2)

            sys.stdout.write('\rSimplex minimization... ')
            sys.stdout.write(f'{n_components} PC - chi^2 = {chi_square:.8E}')
            sys.stdout.flush()

            return chi_square

        pos_init = rotate_coordinates(center,
                                      (self.m_position[1], self.m_position[0]),  # (y, x)
                                      self.m_extra_rot)

        for i, n_components in enumerate(self.m_pca_number):
            sys.stdout.write(f'\rSimplex minimization... {n_components} PC ')
            sys.stdout.flush()

            if self.m_reference_in_port is None:
                sklearn_pca = None

            else:
                ref_data = self.m_reference_in_port.get_all()

                im_shape = images.shape
                ref_shape = ref_data.shape

                if ref_shape[1:] != im_shape[1:]:
                    raise ValueError('The image size of the science data and the reference data '
                                     'should be identical.')

                # reshape reference data and select the unmasked pixels
                ref_reshape = ref_data.reshape(ref_shape[0], ref_shape[1]*ref_shape[2])

                mean_ref = np.mean(ref_reshape, axis=0)
                ref_reshape -= mean_ref

                # create the PCA basis
                sklearn_pca = PCA(n_components=n_components, svd_solver='arpack')
                sklearn_pca.fit(ref_reshape)

                # add mean of reference array as 1st PC and orthogonalize it to the PCA basis
                mean_ref_reshape = mean_ref.reshape((1, mean_ref.shape[0]))

                q_ortho, _ = np.linalg.qr(np.vstack((mean_ref_reshape,
                                                     sklearn_pca.components_[:-1, ])).T)

                sklearn_pca.components_ = q_ortho.T

            minimize(fun=_objective,
                     x0=[pos_init[0], pos_init[1], self.m_magnitude],
                     args=(i, n_components, sklearn_pca),
                     method='Nelder-Mead',
                     tol=None,
                     options={'xatol': self.m_tolerance, 'fatol': float('inf')})

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

        history = f'merit = {self.m_merit}'

        for item in self.m_flux_pos_port:
            item.copy_attributes(self.m_image_in_port)
            item.add_history('SimplexMinimizationModule', history)

        for item in self.m_res_out_port:
            item.copy_attributes(self.m_image_in_port)
            item.add_history('SimplexMinimizationModule', history)

        self.m_res_out_port[0].close_port()
Ejemplo n.º 7
0
def false_alarm(image, x_pos, y_pos, size, ignore):
    """
    Function for the formal t-test for high-contrast imaging at small working angles and the
    related false positive fraction (Mawet et al. 2014).

    Parameters
    ----------
    image : numpy.ndarray
        Input image (2D).
    x_pos : float
        Position (pix) along the horizontal axis. The pixel coordinates of the bottom-left
        corner of the image are (-0.5, -0.5).
    y_pos : float
        Position (pix) along the vertical axis. The pixel coordinates of the bottom-left corner
        of the image are (-0.5, -0.5).
    size : float
        Aperture radius (pix).
    ignore : bool
        Ignore the neighboring apertures for the noise estimate.

    Returns
    -------
    float
        Signal.
    float
        Noise level.
    float
        Signal-to-noise ratio.
    float
        False positive fraction (FPF).
    """

    center = center_subpixel(image)
    radius = math.sqrt((center[0] - y_pos)**2. + (center[1] - x_pos)**2.)

    num_ap = int(math.pi * radius / size)
    ap_theta = np.linspace(0, 2. * math.pi, num_ap, endpoint=False)

    if ignore:
        num_ap -= 2
        ap_theta = np.delete(ap_theta, [1, np.size(ap_theta) - 1])

    if num_ap < 3:
        raise ValueError(
            "Number of apertures (num_ap=%s) is too small to calculate the "
            "false positive fraction." % num_ap)

    ap_phot = np.zeros(num_ap)

    for i, theta in enumerate(ap_theta):
        x_tmp = center[1] + (x_pos-center[1])*math.cos(theta) - \
                            (y_pos-center[0])*math.sin(theta)

        y_tmp = center[0] + (x_pos-center[1])*math.sin(theta) + \
                            (y_pos-center[0])*math.cos(theta)

        aperture = CircularAperture((x_tmp, y_tmp), size)
        phot_table = aperture_photometry(image, aperture, method='exact')
        ap_phot[i] = phot_table['aperture_sum']

    noise = np.std(ap_phot[1:]) * math.sqrt(1. + 1. / float(num_ap - 1))
    t_test = (ap_phot[0] - np.mean(ap_phot[1:])) / noise

    # Note that the number of degrees of freedom is given by nu = n-1 with n the number of samples.
    # The number of samples is equal to the number of apertures minus 1 (i.e. the planet aperture).
    # See Section 3 of Mawet et al. (2014) for more details on the Student's t distribution.
    return ap_phot[0], noise, t_test, 1. - t.cdf(t_test, num_ap - 2)
Ejemplo n.º 8
0
def pixel_variance(var_type: str, images: np.ndarray, parang: np.ndarray,
                   cent_size: Optional[float], edge_size: Optional[float],
                   pca_number: int, residuals: str,
                   aperture: Tuple[int, int, float], sigma: float) -> float:
    """
    Function to calculate the variance of the noise. After the PSF subtraction, images are rotated
    in opposite direction of the regular derotation, therefore dispersing any companion or disk
    signal. The noise is measured within an annulus.

    Parameters
    ----------
    var_type : str
        Variance type ('gaussian' or 'hessian').
    images : numpy.ndarray
        Input images (3D).
    parang : numpy.ndarray
        Parallactic angles.
    cent_size : float, None
        Radius of the central mask (pix). No mask is used when set to None.
    edge_size : float, None
        Outer radius (pix) beyond which pixels are masked. No outer mask is used when set to
        None.
    pca_number : int
        Number of principal components (PCs) used for the PSF subtraction.
    residuals : str
        Method for combining the residuals ('mean', 'median', 'weighted', or 'clipped').
    aperture : tuple(int, int, float)
        Aperture position (y, x) and radius (pix).
    sigma : float, None
        Standard deviation (pix) of the Gaussian kernel which is used to smooth the images.

    Returns
    -------
    float
        Variance of the pixel values. Either the variance of the pixel values ('gaussian') or
        the variance of the determinant of the Hessian ('hessian').
    """

    mask = create_mask(images.shape[-2:], (cent_size, edge_size))

    _, im_res_derot = pca_psf_subtraction(images * mask, parang, pca_number)

    res_noise = combine_residuals(residuals, im_res_derot)

    sep_ang = cartesian_to_polar(center_subpixel(res_noise), aperture[0],
                                 aperture[1])

    if var_type == 'gaussian':
        selected = select_annulus(res_noise[0, ], sep_ang[0] - aperture[2],
                                  sep_ang[0] + aperture[2])

    elif var_type == 'hessian':
        hessian_rr, hessian_rc, hessian_cc = hessian_matrix(
            image=res_noise[0, ],
            sigma=sigma,
            mode='constant',
            cval=0.,
            order='rc')

        hes_det = (hessian_rr * hessian_cc) - (hessian_rc * hessian_rc)

        selected = select_annulus(hes_det, sep_ang[0] - aperture[2],
                                  sep_ang[0] + aperture[2])

    return float(np.var(selected))
Ejemplo n.º 9
0
def compute_aperture_flux_elements(image: np.ndarray, x_pos: float,
                                   y_pos: float, size: float, ignore: bool):
    """
    Computes the average fluxes inside apertures with the same separation from the center.
    This function can be used to to estimate the residual flux of a planet at position
    (x_pos, y_pos) and the respective noise elements with same separation (see function false_alarm)
    It can also be used to compute the noise apertures is if no planet is present
     (needed for contrast curves).

    Parameters
    ----------
    image : numpy.ndarray
        The input image as a 2D numpy array. For example, this could be a residual frame returned by
        a :class:`.PcaPsfSubtractionModule`.
    x_pos : float
        The planet position (in pixels) along the horizontal axis. The pixel coordinates of the
        bottom-left corner of the image are (-0.5, -0.5). If no planet is present x_pos and y_pos
        determine the separation from the center.
    y_pos : float
        The planet position (pix) along the vertical axis. The pixel coordinates of the bottom-left
        corner of the image are (-0.5, -0.5). If no planet is present x_pos and y_pos
        determine the separation from the center.
    size : float
        The radius of the reference apertures (in pixels). Usually, this value is chosen close to
        one half of the typical FWHM of the PSF (0.514 lambda over D for a perfect Airy pattern; in
        practice, however, the FWHM is often larger than this).
    ignore : bool
        Whether or not to ignore the immediate neighboring apertures for the noise estimate. This is
        desirable in case there are "self-subtraction wings" left and right of the planet which
        would bias the estimation of the noise level at the separation of the planet if not ignored.

    Returns
    -------
    ap_phot :
        A list of aperture photometry values. If a planet was present ap_phot[0] contains the flux
        of the planet and ap_phot[1:] contains the noise. If not planet was present ap_phot[...]
        gives the aperture photometry of the noise elements.
    """

    # Compute the center of the current frame (with subpixel precision) and use it to compute the
    # radius of the given position in polar coordinates (with the origin at the center of the frame)
    center = center_subpixel(image)
    radius = math.sqrt((center[0] - y_pos)**2 + (center[1] - x_pos)**2)

    # Compute the number of apertures which we can place at the separation of  the given position
    num_ap = int(math.pi * radius / size)

    # Compute the angles at which to place the reference apertures
    ap_theta = np.linspace(0, 2 * math.pi, num_ap, endpoint=False)

    # If ignore is True, delete the apertures immediately right and left of the aperture placed on
    # the planet signal. These apertures often contain "self-subtraction wings", which means they
    # cannot be considered to originate from the same distribution. In accordance with section 3.2
    # of Mawet et al. (2014), such apertures are ignored to prevent bias.
    if ignore:
        num_ap -= 2
        ap_theta = np.delete(ap_theta, [1, np.size(ap_theta) - 1])

    # If the number of apertures is 2 or less, we cannot compute the false positive fraction
    if num_ap < 3:
        raise ValueError(
            f'Number of apertures (num_ap={num_ap}) is too small to calculate the '
            'false positive fraction.')

    # Initialize a numpy array in which we will store the integrated flux of all reference apertures
    ap_phot = np.zeros(num_ap)

    # Loop over all reference apertures and measure the integrated flux
    for i, theta in enumerate(ap_theta):
        # Compute the position of the current aperture in polar coordinates and convert to Cartesian
        x_tmp = center[1] + (x_pos - center[1]) * math.cos(theta) - \
                (y_pos - center[0]) * math.sin(theta)
        y_tmp = center[0] + (x_pos - center[1]) * math.sin(theta) + \
                (y_pos - center[0]) * math.cos(theta)

        # Place a circular aperture at a position and sum up the flux inside the aperture
        aperture = CircularAperture((x_tmp, y_tmp), size)
        phot_table = aperture_photometry(image, aperture, method='exact')

        ap_phot[i] = phot_table['aperture_sum']

    return ap_phot
Ejemplo n.º 10
0
def merit_function(residuals: np.ndarray, merit: str,
                   aperture: Tuple[int, int, float], sigma: float) -> float:
    """
    Function to calculate the figure of merit at a given position in the image residuals.

    Parameters
    ----------
    residuals : numpy.ndarray
        Residuals of the PSF subtraction (2D).
    merit : str
        Figure of merit for the chi-square function ('hessian', 'poisson', or 'gaussian').
    aperture : tuple(int, int, float)
        Position (y, x) of the aperture center (pix) and aperture radius (pix).
    sigma : float
        Standard deviation (pix) of the Gaussian kernel which is used to smooth the residuals
        before the chi-square is calculated.

    Returns
    -------
    float
        Chi-square ('poisson' and 'gaussian') or sum of the absolute values ('hessian').
    """

    rr_grid = pixel_distance(im_shape=residuals.shape,
                             position=(aperture[0], aperture[1]))

    indices = np.where(rr_grid < aperture[2])

    if merit == 'hessian':

        # This is not the chi-square but simply the sum of the absolute values

        hessian_rr, hessian_rc, hessian_cc = hessian_matrix(image=residuals,
                                                            sigma=sigma,
                                                            mode='constant',
                                                            cval=0.,
                                                            order='rc')

        hes_det = (hessian_rr * hessian_cc) - (hessian_rc * hessian_rc)

        chi_square = np.sum(np.abs(hes_det[indices]))

    elif merit == 'poisson':

        if sigma > 0.:
            residuals = gaussian_filter(input=residuals, sigma=sigma)

        chi_square = np.sum(np.abs(residuals[indices]))

    elif merit == 'gaussian':

        # separation (pix) and position angle (deg)
        sep_ang = cartesian_to_polar(center=center_subpixel(residuals),
                                     y_pos=aperture[0],
                                     x_pos=aperture[1])

        if sigma > 0.:
            residuals = gaussian_filter(input=residuals, sigma=sigma)

        selected = select_annulus(image_in=residuals,
                                  radius_in=sep_ang[0] - aperture[2],
                                  radius_out=sep_ang[0] + aperture[2],
                                  mask_position=aperture[0:2],
                                  mask_radius=aperture[2])

        chi_square = np.sum(residuals[indices]**2) / np.var(selected)

    else:

        raise ValueError(
            'Figure of merit not recognized. Please use \'hessian\', \'poisson\' '
            'or \'gaussian\'. Previous use of \'sum\' should now be set as '
            '\'poisson\'.')

    return chi_square
Ejemplo n.º 11
0
def false_alarm(image: np.ndarray, x_pos: float, y_pos: float, size: float,
                ignore: bool) -> Tuple[float, float, float, float]:
    """
    Compute the signal-to-noise ratio (SNR), which is formally defined as the test statistic of a
    two-sample t-test, and related quantities (such as the FPF) at a given position in an image.

    For more detailed information about the definition of the signal-to-noise ratio and the
    motivation behind it, please see the following paper:

        Mawet, D. et al. (2014): "Fundamental limitations of high contrast imaging set by small
        sample statistics". *The Astrophysical Journal*, 792(2), 97.
        DOI: `10.1088/0004-637X/792/2/97 <https://dx.doi.org/10.1088/0004-637X/792/2/97>`_.

    Parameters
    ----------
    image : numpy.ndarray
        The input image as a 2D numpy array. For example, this could be a residual frame returned by
        a :class:`.PcaPsfSubtractionModule`.
    x_pos : float
        The planet position (in pixels) along the horizontal axis. The pixel coordinates of the
        bottom-left corner of the image are (-0.5, -0.5).
    y_pos : float
        The planet position (pix) along the vertical axis. The pixel coordinates of the bottom-left
        corner of the image are (-0.5, -0.5).
    size : float
        The radius of the references apertures (in pixels). Usually, this values should be chosen
        close to lambda over D, that is, the typical FWHM of the PSF.
    ignore : bool
        Whether or not to ignore the immediate neighboring apertures for the noise estimate. This is
        desirable in case there are "self-subtraction wings" left and right of the planet which
        would bias the estimation of the noise level at the separation of the planet if not ignored.

    Returns
    -------
    signal_sum :
        The integrated (summed up) flux inside the signal aperture.

        Please note that this is **not** identical to the numerator of the fraction defining the SNR
        (which is given by the `signal_sum` minus the mean of the noise apertures).
    noise :
        The denominator of the SNR, i.e., the standard deviation of the integrated flux of the noise
        apertures, times a correction factor that accounts for small sample statistics.
    snr :
        The signal-to-noise ratio (SNR) as defined by Mawet et al. (2014) in eq. (8).
    fpf :
        The false positive fraction (FPF) as defined by Mawet et al. (2014) in eq. (10).
    """

    # Compute the center of the current frame (with subpixel precision) and use it to compute the
    # radius of the given position in polar coordinates (with the origin at the center of the frame)
    center = center_subpixel(image)
    radius = math.sqrt((center[0] - y_pos)**2 + (center[1] - x_pos)**2)

    # Compute the number of apertures which we can place at the separation of  the given position
    num_ap = int(math.pi * radius / size)

    # Compute the angles at which to place the reference apertures
    ap_theta = np.linspace(0, 2 * math.pi, num_ap, endpoint=False)

    # If ignore is True, delete the apertures immediately right and left of the aperture placed on
    # the planet signal. These apertures often contain "self-subtraction wings", which means they
    # cannot be considered to originate from the same distribution. In accordance with section 3.2
    # of Mawet et al. (2014), such apertures are ignored to prevent bias.
    if ignore:
        num_ap -= 2
        ap_theta = np.delete(ap_theta, [1, np.size(ap_theta) - 1])

    # If the number of apertures is 2 or less, we cannot compute the false positive fraction
    if num_ap < 3:
        raise ValueError(
            f'Number of apertures (num_ap={num_ap}) is too small to calculate the '
            'false positive fraction.')

    # Initialize a numpy array in which we will store the integrated flux of all reference apertures
    ap_phot = np.zeros(num_ap)

    # Loop over all reference apertures and measure the integrated flux
    for i, theta in enumerate(ap_theta):

        # Compute the position of the current aperture in polar coordinates and convert to Cartesian
        x_tmp = center[1] + (x_pos - center[1]) * math.cos(theta) - \
                            (y_pos - center[0]) * math.sin(theta)
        y_tmp = center[0] + (x_pos - center[1]) * math.sin(theta) + \
                            (y_pos - center[0]) * math.cos(theta)

        # Place a circular aperture at this position and sum up the flux inside the aperture
        aperture = CircularAperture((x_tmp, y_tmp), size)

        phot_table = aperture_photometry(image, aperture, method='exact')
        ap_phot[i] = phot_table['aperture_sum']

    # Define shortcuts to the signal and the noise aperture sums
    signal_aperture = ap_phot[0]
    noise_apertures = ap_phot[1:]

    # Compute the "signal", that is, the numerator of the signal-to-noise ratio: According to
    # eq. (8) in Mawet et al. (2014), this is given by the difference between the integrated flux
    # in the signal aperture and the mean of the integrated flux in the noise apertures
    signal = signal_aperture - np.mean(noise_apertures)

    # Compute the "noise", that is, the denominator of the signal-to-noise-ratio: According to
    # eq. (8) in Mawet et al. (2014), this is given by the standard deviation of the integrated flux
    # in the noise apertures times a correction factor to account for the small sample statistics.
    # NOTE: `ddof=1` is a necessary argument for np.std() in order to compute the *unbiased*
    #       estimate (i.e., including Bessel's corrections) of the standard deviation.
    noise = np.std(ap_phot[1:], ddof=1) * math.sqrt(1 + 1 / (num_ap - 1))

    # Compute the signal-to-noise ratio by dividing the "signal" through the "noise"
    snr = signal / noise

    # Compute the false positive fraction (FPF). According to eq. (10) in Mawet et al. (2014), the
    # FPF is given by 1 - F_nu(SNR), where F_nu is the cumulative distribution function (CDF) of a
    # t-distribution with `nu = n-1` degrees of freedom (see Section 3 of Mawet et al. (2014) for
    # more details on the Student's t distribution).
    # For numerical reasons, we use the survival function (SF), which is defined precisely as 1-CDF,
    # but may give more accurate results according to the scipy documentation.
    fpf = t.sf(snr, df=(num_ap - 2))

    return signal_aperture, noise, snr, fpf
Ejemplo n.º 12
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)
Ejemplo n.º 13
0
    def run(self):
        """
        Run method of the module. Calculates the SNR and FPF for a specified position in a post-
        processed image with the Student's t-test (Mawet et al. 2014). This approach assumes
        Gaussian noise but accounts for small sample statistics.

        Returns
        -------
        NoneType
            None
        """
        def _fpf_minimize(arg):
            pos_x, pos_y = arg

            try:
                _, _, _, fpf = false_alarm(image=image,
                                           x_pos=pos_x,
                                           y_pos=pos_y,
                                           size=self.m_aperture,
                                           ignore=self.m_ignore)

            except ValueError:
                fpf = float('inf')

            return fpf

        self.m_snr_out_port.del_all_data()
        self.m_snr_out_port.del_all_attributes()

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

        nimages = self.m_image_in_port.get_shape()[0]

        start_time = time.time()
        for j in range(nimages):
            progress(j, nimages, 'Running FalsePositiveModule...', start_time)

            image = self.m_image_in_port[j, ]
            center = center_subpixel(image)

            if self.m_optimize:
                result = minimize(fun=_fpf_minimize,
                                  x0=[self.m_position[0], self.m_position[1]],
                                  method='Nelder-Mead',
                                  tol=None,
                                  options={
                                      'xatol': self.m_tolerance,
                                      'fatol': float('inf')
                                  })

                _, _, snr, fpf = false_alarm(image=image,
                                             x_pos=result.x[0],
                                             y_pos=result.x[1],
                                             size=self.m_aperture,
                                             ignore=self.m_ignore)

                x_pos, y_pos = result.x[0], result.x[1]

            else:
                _, _, snr, fpf = false_alarm(image=image,
                                             x_pos=self.m_position[0],
                                             y_pos=self.m_position[1],
                                             size=self.m_aperture,
                                             ignore=self.m_ignore)

                x_pos, y_pos = self.m_position[0], self.m_position[1]

            sep_ang = cartesian_to_polar(center, x_pos, y_pos)
            result = np.column_stack(
                (x_pos, y_pos, sep_ang[0] * pixscale, sep_ang[1], snr, fpf))

            self.m_snr_out_port.append(result, data_dim=2)

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

        history = f'aperture [arcsec] = {self.m_aperture*pixscale:.2f}'
        self.m_snr_out_port.copy_attributes(self.m_image_in_port)
        self.m_snr_out_port.add_history('FalsePositiveModule', history)
        self.m_snr_out_port.close_port()
Ejemplo n.º 14
0
    def run(self):
        """
        Run method of the module. The position and flux of a planet are measured by injecting
        negative fake companions and applying a simplex method (Nelder-Mead) for minimization
        of a function of merit at the planet location. The default function of merit is the
        image curvature which is calculated as the sum of the absolute values of the
        determinant of the Hessian matrix.

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

        self.m_res_out_port.del_all_data()
        self.m_res_out_port.del_all_attributes()

        self.m_flux_position_port.del_all_data()
        self.m_flux_position_port.del_all_attributes()

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

        if isinstance(self.m_aperture, float):
            self.m_aperture = {
                'type': 'circular',
                'pos_x': self.m_position[0],
                'pos_y': self.m_position[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

        self.m_sigma /= pixscale

        if self.m_cent_size is not None:
            self.m_cent_size /= pixscale

        if self.m_edge_size is not None:
            self.m_edge_size /= pixscale

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

        center = center_subpixel(psf)

        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 SimplexMinimizationModule.')

        def _objective(arg):
            sys.stdout.write('.')
            sys.stdout.flush()

            pos_y = arg[0]
            pos_x = arg[1]
            mag = arg[2]

            sep_ang = cartesian_to_polar(center, pos_x, pos_y)

            fake = fake_planet(images=images,
                               psf=psf,
                               parang=parang,
                               position=(sep_ang[0], sep_ang[1]),
                               magnitude=mag,
                               psf_scaling=self.m_psf_scaling)

            im_shape = (fake.shape[-2], fake.shape[-1])

            mask = create_mask(im_shape, [self.m_cent_size, self.m_edge_size])

            _, im_res = pca_psf_subtraction(images=fake * mask,
                                            angles=-1. * parang +
                                            self.m_extra_rot,
                                            pca_number=self.m_pca_number)

            stack = combine_residuals(method=self.m_residuals, res_rot=im_res)

            self.m_res_out_port.append(stack, data_dim=3)

            merit = merit_function(residuals=stack[0, ],
                                   function=self.m_merit,
                                   variance='poisson',
                                   aperture=self.m_aperture,
                                   sigma=self.m_sigma)

            position = rotate_coordinates(center, (pos_y, pos_x),
                                          -self.m_extra_rot)

            res = np.asarray(
                (position[1], position[0], sep_ang[0] * pixscale,
                 (sep_ang[1] - self.m_extra_rot) % 360., mag, merit))

            self.m_flux_position_port.append(res, data_dim=2)

            return merit

        sys.stdout.write('Running SimplexMinimizationModule')
        sys.stdout.flush()

        pos_init = rotate_coordinates(center,
                                      (self.m_position[1], self.m_position[0]),
                                      self.m_extra_rot)

        # Change integer to float?
        pos_init = (int(pos_init[0]), int(pos_init[1]))  # (y, x)

        minimize(fun=_objective,
                 x0=[pos_init[0], pos_init[1], self.m_magnitude],
                 method='Nelder-Mead',
                 tol=None,
                 options={
                     'xatol': self.m_tolerance,
                     'fatol': float('inf')
                 })

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

        history = 'merit = ' + str(self.m_merit)
        self.m_flux_position_port.copy_attributes(self.m_image_in_port)
        self.m_flux_position_port.add_history('SimplexMinimizationModule',
                                              history)

        self.m_res_out_port.copy_attributes(self.m_image_in_port)
        self.m_res_out_port.add_history('SimplexMinimizationModule', history)
        self.m_res_out_port.close_port()