Пример #1
0
    def guide_star_seeing(subframe):
        # subframe = subframe - np.median(subframe)
        subframe = subframe - np.percentile(subframe,5)
        sub_frame_l = int(np.shape(subframe)[0])
        y, x = np.mgrid[:sub_frame_l, :sub_frame_l]

        # Fit with constant, bounds, tied x and y sigmas and outlier rejection:
        gaussian_init = models.Const2D(0.0) + models.Gaussian2D(subframe[int(sub_frame_l/2),int(sub_frame_l/2)],int(sub_frame_l/2),int(sub_frame_l/2),8/2.355,8/2.355,0)
        gaussian_init.x_stddev_1.min = 1.0/2.355
        gaussian_init.x_stddev_1.max = 20.0/2.355
        gaussian_init.y_stddev_1.min = 1.0/2.355
        gaussian_init.y_stddev_1.max = 20.0/2.355
        gaussian_init.y_stddev_1.tied = tie_sigma
        gaussian_init.theta_1.fixed = True
        fit_gauss = fitting.FittingWithOutlierRemoval(fitting.LevMarLSQFitter(),sigma_clip,niter=3,sigma=3.0)
        # gaussian, mask = fit_gauss(gaussian_init, x, y, subframe)
        gain = 8.21 #e per ADU
        read_noise = 2.43 #ADU
        weights = gain / np.sqrt(np.absolute(subframe)*gain + (read_noise*gain)**2) #1/sigma for each pixel
        gaussian, mask = fit_gauss(gaussian_init, x, y, subframe, weights)
        fwhm_x = 2.355*gaussian.x_stddev_1.value
        fwhm_y = 2.355*gaussian.y_stddev_1.value

        x_seeing = fwhm_x * 0.579
        y_seeing = fwhm_y * 0.579
        return(x_seeing,y_seeing)
Пример #2
0
def create_test_image(gap=3, N=25):
    '''
    Create one random stitched camera, with a Gaussian-ish background to it.
    '''

    # generate a random model
    p_generate = (models.Const2D(amplitude=10**np.random.uniform(0, 1)) +
               models.Gaussian2D(amplitude=10**np.random.uniform(.5, 2),
                                   x_mean=np.random.uniform(-N, N),
                                   y_mean=np.random.uniform(-N, N),
                                   x_stddev=np.random.uniform(2, N),
                                   y_stddev=np.random.uniform(2, N),
                                   theta=np.random.uniform(0, 2*np.pi)))

    # create fake x and y arrays
    x, y = np.meshgrid(np.arange(-N-gap, N+gap+1), np.arange(-N-gap, N+gap+1))

    # create the model
    perfect = p_generate(x, y)
    z = np.random.normal(perfect, 1)

    # find the gaps, replace them with zeros, and mark them as bad
    bad = (np.abs(x) < gap) | (np.abs(y) < gap)
    z[bad] = 0
    ok = bad == False

    return x, y, z, ok, p_generate
Пример #3
0
def test_get_bounding_box():
    model = models.Const2D(2)

    # No with_bbox
    assert model.get_bounding_box(False) is None

    # No bounding_box
    with pytest.raises(NotImplementedError):
        model.bounding_box
    assert model.get_bounding_box(True) is None

    # Normal bounding_box
    model.bounding_box = ((0, 1), (0, 1))
    assert not isinstance(model.bounding_box, CompoundBoundingBox)
    assert model.get_bounding_box(True) == ((0, 1), (0, 1))

    # CompoundBoundingBox with no removal
    bbox = CompoundBoundingBox.validate(model, {(1,): ((-1, 0), (-1, 0)), (2,): ((0, 1), (0, 1))},
                                        selector_args=[('y', False)])
    model.bounding_box = bbox
    assert isinstance(model.bounding_box, CompoundBoundingBox)
    # Get using argument not with_bbox
    assert model.get_bounding_box(True) == bbox
    # Get using with_bbox not argument
    assert model.get_bounding_box((1,)) == ((-1, 0), (-1, 0))
    assert model.get_bounding_box((2,)) == ((0, 1), (0, 1))
Пример #4
0
def test_legacy_const(tmpdir):
    model = astropy_models.Const1D(amplitude=5.)
    assert_model_roundtrip(model, tmpdir, version="1.3.0")

    model = astropy_models.Const2D(amplitude=5.)
    with pytest.raises(TypeError,
                       match="does not support models with > 1 dimension"):
        assert_model_roundtrip(model, tmpdir, version="1.3.0")
Пример #5
0
 def from_tree_transform(self, node):
     if self.version < AsdfVersion('1.4.0'):
         # The 'dimensions' property was added in 1.4.0,
         # previously all values were 1D.
         return models.Const1D(node['value'])
     elif node['dimensions'] == 1:
         return models.Const1D(node['value'])
     elif node['dimensions'] == 2:
         return models.Const2D(node['value'])
Пример #6
0
def FitGaussian2D(B, reconstruct_model=False):
    '''
	Fits a 2D gaussian model to a vignette using Levenberg-Marquardt algorithm and least squares statistic.
	 input:
	 	B:		Vignette with the object
	 	reconstruct_model:	Boolean flag. Indicates if the reconstructed model and its residuals should be computed
	
	 output:
	 	params:		Dictionary with the 6 fitted parameters. Keys = ['amp', 'ab', 'ellip', 'theta', 'x0', 'y0']
		*rec_model:	Vignette with the fitted model. Only if reconstruct_model=True
		*rec_resi:	Vignette with the residuals, ie: rec_resi=B-rec_model. Only if reconstruct_model=True

	Notes: Given the polar nature of the ellipticity definition, the fit can be confusing. For ex., given a
	gaussian with ellip = 0.1 and theta = 0 (where theta is measured from the y axis), it is posible that the
	fitter returns ellip = -0.1 and theta = pi/2. This time the minus sign in ellip indicates that it is meassuring
	it on the oposite axis, thus theta is being measured from the x axis.
	We fix the position x0,y0 of the model at the center of the stamp. There is no need to fit them
	Notes 2: Add the posibility of fitting a sum of gaussians instead of just one.
	'''

    p = model_Gaussian2D(amp=B.max()-B.min(), ab=5, e1=0., e2=0.2,x0=B.shape[0]/2,y0=B.shape[1]/2) + \
     models.Const2D(amplitude=B.min())

    # Constraints on some parameters
    p.x0_0.fixed = True
    p.y0_0.fixed = True

    x, y = np.meshgrid(range(B.shape[0]), range(B.shape[1]), indexing='ij')
    out_gauss, out_const = fitter(p, x, y, B, maxiter=10000,
                                  acc=1e-16)  # fitter() is defined globally

    # retrieves the 6 gaussian parameters
    params = {
        'amp': out_gauss.amp.value,
        'ab': out_gauss.ab.value,
        'x0': out_gauss.x0.value,
        'y0': out_gauss.y0.value,
        'e1': out_gauss.e1.value,
        'e2': out_gauss.e2.value
    }

    # generates a vignette with the model and computes residuals
    if reconstruct_model:
        rec_model = Gauss2D_stamp(amp=params['amp'],
                                  ab=params['ab'],
                                  e1=params['e1'],
                                  e2=params['e2'],
                                  x0=params['x0'],
                                  y0=params['y0'],
                                  noise=0,
                                  zerolevel=out_const.amplitude,
                                  shape=B.shape)
        resi = B - rec_model
        return params, rec_model, resi
    else:
        return params
Пример #7
0
def models_with_input_eq():
    # 1D model
    m1 = astmodels.Shift(1*u.kg)
    m1.input_units_equivalencies = {'x': u.mass_energy()}

    # 2D model
    m2 = astmodels.Const2D(10*u.Hz)
    m2.input_units_equivalencies = {'x': u.dimensionless_angles(),
                                    'y': u.dimensionless_angles()}

    # 2D model with only one input equivalencies
    m3 = astmodels.Const2D(10*u.Hz)
    m3.input_units_equivalencies = {'x': u.dimensionless_angles()}

    # model using equivalency that has args using units
    m4 = astmodels.PowerLaw1D(amplitude=1*u.m, x_0=10*u.pix, alpha=7)
    m4.input_units_equivalencies = {'x': u.equivalencies.pixel_scale(0.5*u.arcsec/u.pix)}

    return[m1, m2, m3, m4]
Пример #8
0
def FitSersic2D(B, reconstruct_model=False):
    '''
	Fits a 2D sersic model to a vignette using Levenberg-Marquardt algorithm and least squares statistic.
	 input:
	 	B:		Vignette with the object
	 	reconstruct_model:	Boolean flag. Indicates if the reconstructed model and its residuals should be computed
	
	 output:
	 	params:		Dictionary with the 7 fitted parameters. Keys = ['amp', 'radius', 'sersic_n', 'e1', 'e2', 'x0', 'y0']
		*rec_model:	Vignette with the fitted model. Only if reconstruct_model=True
		*rec_resi:	Vignette with the residuals, ie: rec_resi=B-rec_model. Only if reconstruct_model=True

	Notes: We fix the position x0,y0 of the model at the center of the stamp. There is no need to fit them
	'''

    p = model_Sersic2D(amp=B.max()-B.min(), radius=10, sersic_n=4, x0=B.shape[0]/2,y0=B.shape[1]/2,e1=0.1, e2=0.1) + \
     models.Const2D(amplitude=B.min())

    # Constraints on some parameters
    p.x0_0.fixed = True
    p.y0_0.fixed = True

    x, y = np.meshgrid(range(B.shape[0]), range(B.shape[1]), indexing='ij')
    out_sersic, out_const = fitter(p, x, y, B, maxiter=10000,
                                   acc=1e-16)  # fitter() is defined globally

    # retrieves sersic parameters
    params = {
        'amp': out_sersic.amp.value,
        'sersic_n': out_sersic.sersic_n.value,
        'radius': out_sersic.radius.value,
        'x0': out_sersic.x0.value,
        'y0': out_sersic.y0.value,
        'e1': out_sersic.e1.value,
        'e2': out_sersic.e2.value
    }

    # generates a vignette with the model and computes residuals
    if reconstruct_model:
        rec_model = Sersic2D_stamp(amp=out_sersic.amp,
                                   radius=out_sersic.radius,
                                   sersic_n=out_sersic.sersic_n,
                                   x0=out_sersic.x0,
                                   y0=out_sersic.y0,
                                   e1=out_sersic.e1,
                                   e2=out_sersic.e2,
                                   noise=0,
                                   zerolevel=out_const.amplitude,
                                   shape=B.shape)
        resi = B - rec_model
        return params, rec_model, resi
    else:
        return params
Пример #9
0
def FitMoffat2D(B, reconstruct_model=False):
    '''
	Fits a 2D moffat model to a vignette using Levenberg-Marquardt algorithm and least squares statistic.
	 input:
	 	B:		Vignette with the object
	 	reconstruct_model:	Boolean flag. Indicates if the reconstructed model and its residuals should be computed
	
	 output:
	 	params:		Dictionary with the 6 fitted parameters. Keys = ['beta', 'fwhm', 'e1', 'e2', 'x0', 'y0']
		*rec_model:	Vignette with the fitted model. Only if reconstruct_model=True
		*rec_resi:	Vignette with the residuals, ie: rec_resi=B-rec_model. Only if reconstruct_model=True

	Notes: We fix the position x0,y0 of the model at the center of the stamp. There is no need to fit them
	'''

    p = model_Moffat2D(fwhm=5, beta=1, x0=B.shape[0]/2,y0=B.shape[1]/2,e1=0.1, e2=0.1) + \
     models.Const2D(amplitude=B.min())

    # Constraints on some parameters
    p.x0_0.fixed = True
    p.y0_0.fixed = True

    x, y = np.meshgrid(range(B.shape[0]), range(B.shape[1]), indexing='ij')
    out_moffat, out_const = fitter(p, x, y, B, maxiter=10000,
                                   acc=1e-16)  # fitter() is defined globally

    # retrieves the 6 moffat parameters
    params = {
        'beta': out_moffat.beta.value,
        'fwhm': out_moffat.fwhm.value,
        'x0': out_moffat.x0.value,
        'y0': out_moffat.y0.value,
        'e1': out_moffat.e1.value,
        'e2': out_moffat.e2.value
    }

    # generates a vignette with the model and computes residuals
    if reconstruct_model:
        rec_model = Moffat2D_stamp(beta=out_moffat.beta,
                                   fwhm=out_moffat.fwhm,
                                   x0=out_moffat.x0,
                                   y0=out_moffat.y0,
                                   e1=out_moffat.e1,
                                   e2=out_moffat.e2,
                                   noise=0,
                                   zerolevel=out_const.amplitude,
                                   shape=B.shape)
        resi = B - rec_model
        return params, rec_model, resi
    else:
        return params
Пример #10
0
        def fit_gaussian2d(b, fitter=None):
            if fitter is None:
                fitter = fitting.LevMarLSQFitter()

            y2, x2 = np.mgrid[:b.shape[0], :b.shape[1]]
            ampl = b.max() - b.min()
            p = models.Gaussian2D(
                x_mean=b.shape[1] / 2.0,
                y_mean=b.shape[0] / 2.0,
                x_stddev=1.0,
                y_stddev=1.0,
                theta=np.pi / 4.0,
                amplitude=ampl,
            )

            p += models.Const2D(amplitude=b.min())
            out = fitter(p, x2, y2, b, maxiter=1000)
            return out
Пример #11
0
def fitpsf(psf, *center):

    fixed = {}
    bounds = {}

    if not center:

        amplitude = psf.max()
        y_center, x_center = numpy.unravel_index(psf.argmax(), psf.shape)
        fixed['x_mean'] = False
        fixed['y_mean'] = False
        bounds['x_stddev'] = [0., psf.shape[1]]
        bounds['y_stddev'] = [0., psf.shape[0]]

    else:

        amplitude = psf[center[1], center[0]]
        x_center = center[0]
        y_center = center[1]
        fixed['x_mean'] = True
        fixed['y_mean'] = True
        bounds['x_stddev'] = [0., psf.shape[1]]
        bounds['y_stddev'] = [0., psf.shape[0]]

    x_stddev = 3. / 2.35
    y_stddev = 3. / 2.35
    theta = 0

    p_init = models.Gaussian2D(amplitude = amplitude, x_mean = x_center, \
    y_mean = y_center, x_stddev = x_stddev, y_stddev = y_stddev, \
    theta = theta, fixed = fixed, bounds = bounds)

    p_const_init = models.Const2D(amplitude=0.)

    fit_p = fitting.LevMarLSQFitter()

    gridy, gridx = numpy.mgrid[0:psf.shape[0]:1, 0:psf.shape[1]:1]

    p = fit_p(p_init + p_const_init, gridx, gridy, psf, maxiter=200)

    if not numpy.any(fit_p.fit_info['param_cov']):
        p = None

    return p
Пример #12
0
def fit_plane(image, maxiter=5000, epsilon=1e-10):
    model = models.Planar2D(slope_x=0., slope_y=0,
                            intercept=0) + models.Const2D(0)

    # Make x and y grid to fit to
    y_arange, x_arange = np.where(~(np.isnan(image)))

    z = image[(y_arange, x_arange)]

    # Fit model to grid
    fit = fitting.LevMarLSQFitter()  # fitting.LinearLSQFitter()
    fitted_line = fit(model,
                      x_arange,
                      y_arange,
                      z,
                      maxiter=5000,
                      epsilon=1e-10)

    return fitted_line, fit
Пример #13
0
def guess_2d_gaussian(x, y, z, ok=None):
    '''
    Make a guess to initialize the baseline + 2D Gaussian model,
    based on medians and weighted moments of the image.
    '''

    if ok == None:
        ok = np.ones_like(z).astype(np.bool)

    # estimate a baseline flux, from the median of the good pixels
    baseline_guess = np.median(z[ok])

    # do a *veyr* coarse subtraction, to focus on just the blob
    crudelysubtracted = np.maximum((z-baseline_guess), 0)
    amplitude_guess = np.sum(crudelysubtracted)


    def moment(q):
        '''
        Take the weighted moment of a quantity.
        '''
        return np.sum(q[ok]*crudelysubtracted[ok])/np.sum(crudelysubtracted[ok])

    # calculate flux-weighted centroids of the blob
    x_guess = moment(x)
    y_guess = moment(y)

    # calculate flux-weighted widths of the blob
    x_width_guess = np.sqrt(moment((x - x_guess)**2))
    y_width_guess = np.sqrt(moment((y - y_guess)**2))



    # create an initial model, with the initial guesses
    initial = (  models.Const2D(amplitude=baseline_guess) +
                models.Gaussian2D(amplitude=amplitude_guess,
                               x_mean=x_guess, y_mean=y_guess,
                               x_stddev=x_width_guess, y_stddev=y_width_guess,
                               theta=0.0))

    return initial
Пример #14
0
def fit_2D_gaussian(xmat, ymat, z):
    '''
    Return fitted model parameters
    '''

    g = astropy_models.Gaussian2D(x_mean=[0], y_mean=[0], x_stddev=[1],
                                  y_stddev=[1], amplitude=z.max(),
                                  theta=[0],
                                  fixed={'amplitude': True,
                                         'x_mean': True,
                                         'y_mean': True}) + \
        astropy_models.Const2D(amplitude=[np.percentile(z, 10)])

    fit_g = fitting.LevMarLSQFitter()
    output = fit_g(g, xmat, ymat, z)
    cov = fit_g.fit_info['param_cov']

    if cov is None:
        warn("Fitting failed.")
        cov = np.zeros((4, 4)) * np.NaN

    return output, cov
Пример #15
0
    def _initialize_model(self):
        """Initialize a model with first guesses for the parameters.
        The user can select between several astropy models, e.g., 'Gaussian2D', 'Moffat2D'. We will use the data to get
        the first estimates of the parameters of each model. Finally, a Constant2D model is added to account for the
        background or sky level around the star.
        """
        max_value = self.data.max()

        if self.model_type == self._GAUSSIAN2D:
            model = models.Gaussian2D(x_mean=self.x,
                                      y_mean=self.y,
                                      x_stddev=1,
                                      y_stddev=1)
            model.amplitude = max_value

            # Establish reasonable bounds for the fitted parameters
            model.x_stddev.bounds = (0, self._box / 4)
            model.y_stddev.bounds = (0, self._box / 4)
            model.x_mean.bounds = (self.x - 5, self.x + 5)
            model.y_mean.bounds = (self.y - 5, self.y + 5)

        elif self.model_type == self._MOFFAT2D:
            model = models.Moffat2D()
            model.x_0 = self.x
            model.y_0 = self.y
            model.gamma = 2
            model.alpha = 2
            model.amplitude = max_value

            #  Establish reasonable bounds for the fitted parameters
            model.alpha.bounds = (1, 6)
            model.gamma.bounds = (0, self._box / 4)
            model.x_0.bounds = (self.x - 5, self.x + 5)
            model.y_0.bounds = (self.y - 5, self.y + 5)

        model += models.Const2D(self.fit_sky())
        model.amplitude_1.fixed = True
        return model
Пример #16
0
def gfit(data,
         x0,
         y0,
         size=5,
         fwhm=3,
         sub=True,
         plot=None,
         fig=1,
         scale=1,
         pafixed=False):
    """ 
    Does gaussian fit to input data given initial xcen,ycen
    """
    fit = fitting.LevMarLSQFitter()
    #fit=fitting.SLSQPLSQFitter()
    z = data[int(y0) - size:int(y0) + size, int(x0) - size:int(x0) + size]
    xcen, ycen = np.unravel_index(np.argmax(z), z.shape)
    xcen += (int(x0) - size)
    ycen += (int(y0) - size)
    y, x = np.mgrid[ycen - size:ycen + size, xcen - size:xcen + size]
    z = data[ycen - size:ycen + size, xcen - size:xcen + size]
    g_init = models.Gaussian2D(x_mean=xcen,
                               y_mean=ycen,
                               x_stddev=fwhm / 2.354,
                               y_stddev=fwhm / 2.354,
                               amplitude=data[ycen, xcen],
                               theta=0.,
                               fixed={'theta': pafixed}) + models.Const2D(0.)
    g = fit(g_init, x, y, z)
    xfwhm = g[0].x_stddev * 2.354 * scale
    yfwhm = g[0].y_stddev * 2.354 * scale
    fwhm = np.sqrt(xfwhm * yfwhm)
    xcen = g[0].x_mean.value
    ycen = g[0].y_mean.value
    theta = (g[0].theta.value % (2 * np.pi)) * 180. / np.pi
    print(
        'xFWHM:{:8.2f}   yFWHM:{:8.2f}   FWHM:{:8.2f}  SCALE:{:8.2f}  PA:{:8.2f}'
        .format(xfwhm, yfwhm, fwhm, scale, theta))
    if plot is not None:
        r = np.sqrt((y - ycen)**2 + (x - xcen)**2)
        plots.plotp(plot, r, z, xt='R(pixels)', yt='Intensity')
        r = np.arange(0., 5 * fwhm / 2.354 / scale, 0.1)
        peak = g[0].amplitude
        plot.plot(
            r,
            peak * np.exp(-np.power(r, 2.) /
                          (2 * np.power(g[0].x_stddev, 2.))) + g[1].amplitude)
        plot.plot(
            r,
            peak * np.exp(-np.power(r, 2.) /
                          (2 * np.power(g[0].y_stddev, 2.))) + g[1].amplitude)
        plot.text(0.9,
                  0.9,
                  'x: {:7.1f} y: {:7.1f} fw: {:8.2f}'.format(xcen, ycen, fwhm),
                  transform=plot.transAxes,
                  ha='right')
        plt.draw()

    if sub:
        out = data
        out[ycen - size:ycen + size, xcen - size:xcen + size] -= g[0](x, y)
        return out
    return g
Пример #17
0
def model_plot(model,
               image,
               amp,
               x_wid=5,
               y_wid=5,
               angle=0,
               fignumber=1,
               title=None):

    cutout_size = np.shape(image)[0]
    # Open Fit
    y_o, x_o = np.mgrid[:cutout_size, :cutout_size]
    z_o = image

    if model == "Gaussian":
        g2d_model = models.Gaussian2D(np.amax(z_o),
                                      cutout_size / 2,
                                      cutout_size / 2,
                                      x_stddev=x_wid,
                                      y_stddev=y_wid,
                                      theta=angle)
        c2d_model = models.Const2D(0.0)
        p_init_o = g2d_model + c2d_model
    elif model == "Moffat":
        g2d_model = Elliptical_Moffat2D(amplitude=np.amax(z_o),
                                        x_0=cutout_size / 2,
                                        y_0=cutout_size / 2,
                                        width_x=x_wid,
                                        width_y=y_wid)
        c2d_model = models.Const2D(0.0)
        p_init_o = g2d_model + c2d_model

    fit_p_o = fitting.LevMarLSQFitter()
    # weights = 1.0 / z_o**0.5
    # weights = z_o**0.5
    # weights = np.ones(z_o.shape, dtype=float)
    p_o = fit_p_o(p_init_o,
                  x_o,
                  y_o,
                  z_o,
                  epsilon=1e-12,
                  acc=1e-12,
                  maxiter=300,
                  weights=None)

    # FWHM values?
    if model == "Gaussian":
        x_fwhm = p_o.x_stddev_0.value / gaussian_fwhm_to_sigma
        y_fwhm = p_o.y_stddev_0.value / gaussian_fwhm_to_sigma
    elif model == "Moffat":
        # BUG this isn't quite right for a moffat fit...
        x_fwhm = p_o.width_x_0.value
        y_fwhm = p_o.width_y_0.value

    #Images
    model_img = p_o(x_o, y_o)
    diff_img = z_o - model_img

    #Images cut:
    half_size = image.shape[0] / 2
    x_lo = int(round(half_size - x_fwhm))
    x_hi = x_lo + int(round(x_fwhm * 2))
    y_lo = int(round(half_size - y_fwhm))
    y_hi = y_lo + int(round(y_fwhm * 2))
    #cut size for metrics
    z_o_cut = z_o[y_lo:y_hi, x_lo:x_hi].astype(float)
    diff_img_cut = diff_img[y_lo:y_hi, x_lo:x_hi].astype(float)

    # Fit metrics
    residual_o = np.sum(diff_img_cut**2)
    PSF_mean = np.mean(z_o_cut)
    fresid = np.median(np.abs(diff_img_cut /
                              z_o_cut))  # mean fractional residual
    FVU = np.sum(diff_img**2) / np.sum(
        (z_o_cut - PSF_mean)**2)  # fraction of variance unexplained

    # Plotting
    plt.figure(fignumber, figsize=(12, 3))
    plt.suptitle(title)

    vmin = z_o.min()
    vmax = z_o.max()

    # left panel
    plt.subplot(1, 4, 1)
    plt.imshow(z_o, origin='lower', vmin=vmin, vmax=vmax)
    plt.gca().add_patch(
        Rectangle((x_lo, y_lo),
                  x_hi - x_lo,
                  y_hi - y_lo,
                  edgecolor='red',
                  facecolor='none',
                  lw=2))
    plt.colorbar(fraction=0.046, pad=0.05)
    plt.title("Data")

    # left middle panel
    plt.subplot(1, 4, 2)
    plt.imshow(model_img, origin='lower', vmin=vmin, vmax=vmax)
    plt.gca().add_patch(
        Rectangle((x_lo, y_lo),
                  x_hi - x_lo,
                  y_hi - y_lo,
                  edgecolor='red',
                  facecolor='none',
                  lw=2))
    plt.colorbar(fraction=0.046, pad=0.04)
    plt.title("Model")

    # right middle panel
    plt.subplot(1, 4, 3)
    plt.imshow(z_o - model_img, origin='lower', vmin=-vmax / 6, vmax=vmax / 6)
    plt.gca().add_patch(
        Rectangle((x_lo, y_lo),
                  x_hi - x_lo,
                  y_hi - y_lo,
                  edgecolor='red',
                  facecolor='none',
                  lw=1))
    plt.colorbar(fraction=0.046, pad=0.04)
    plt.title("Data-Model")

    # right panel
    plt.subplot(1, 4, 4)
    plt.imshow((z_o - model_img) / z_o, origin='lower', vmax=1, vmin=-1)
    plt.gca().add_patch(
        Rectangle((x_lo, y_lo),
                  x_hi - x_lo,
                  y_hi - y_lo,
                  edgecolor='red',
                  facecolor='none',
                  lw=1))
    plt.colorbar(fraction=0.046, pad=0.04)
    plt.title("Fract. Resid.")

    plt.tight_layout()

    print("Residuals Squared Summed               - ", title, ": ",
          '{:.2e}'.format(residual_o))
    print("Fraction of Variance Unexplained (FVU) - ", title, ": ",
          '{:.2e}'.format(FVU))
    print("Median Fractional Residual Per Pix     - ", title, ": ",
          '{:.2e}'.format(fresid))

    if model == "Moffat":
        print("beta: ", p_o.power_0.value)
    print('\nFit Info:')
    print('nfev = ', fit_p_o.fit_info['nfev'])
    print('ierr = ', fit_p_o.fit_info['ierr'])
    print('mesg = ', fit_p_o.fit_info['message'])
    print('\nParams:')

    for k, v in p_o._parameters_.items():
        val = v.value
        unit = 'pix'

        if 'phi' in k:
            val = val % 360.0
            unit = 'deg'
        if 'theta' in k:
            val = np.rad2deg(val) % 360.0
            unit = 'deg'
        if 'amp' in k:
            unit = 'flux'

        print(f'{k:15s} = {val:10.6f} {unit:5s}')

    print('\n')

    return p_o
Пример #18
0
def find_source(im, guesspos=None, searchbox=None, fitbox=None,
                    guessmeth='max', smooth=0, searchsmooth=3,
                    guessFWHM=None, guessamp=None, guessbg=None,
                    method='fast', sign=1, verbose=False, fixFWHM=False,
                    fixpos=False, minamp=0.01, maxamp=None, plot=False,
                    maxFWHM=None, minFWHM=None, posradius=None, silent=False):

    """
    find the point source in an image and provide its best fit parameters from
    a Gaussian fit
    Parameters:
        - im: image to be searched
        - guesspos: 2D array with the (y, x) guessed position for the point
                    source position. If None, use the middle of the whole image
        - searchbox: int or 2D array with (y, x) size, size of box to be
                     searched centered on the guesspos. If None, search the
                     whole image
        - fitbox: int or 2D array with (y, x) size, size of box used for the
                  fit. If None, use searchbox
        - guessmeth: method for guesstimating the point source position
                     (currently only 'max' and other). For 'max' use the
                     maximum brightness pixel in the searchbox. Other: use
                     centre of the searchbox
        - smooth: optional smoothing with a Gaussian. The value specifies the
                  width of the Gaussian used for the smoothing
        - searchsmooth: optional smoothing only for guesstimating source
                        position
        - guessFWHM: guess for the FWHM of the source. If None, use middle
                     between minFWHM and maxFWHM or 5 if the former are not
                     provided
        - guessamp: guess for the amplitude of the source. If None, use the
                    pixel brightness at the guess position
        - guessbg: guess for the background level in the image. If None, use
                   the median of the image.
        - method: method used for the fitting: 'fast': use LevMarLSQFitter
                  from astropy, 'mpfit': use the MPFIT package
        - sign:   sign of the point source to be searched
        - fixFWHM: fix the FWHM of the source to the guess value
        - fixpos: fix the position of the source to the guess value
        - minamp, maxamp: minimum and maximum allowed values for the amplitude
                          of the source
        - minFWHM, maxFWHM: minimum and maximum allowed values for the FWHM
                            of the source
        - posradius: maximum allowed radius in pix around the guess position
                     for the source location. If None, the whole searchbox is
                     allowed

    """

    funname = "FIND_SOURCE"

    s2f = (2.0 * np.sqrt(2.0*np.log(2.0)))
    f2s = 1.0/s2f

    s = np.shape(im)

    if sign < 0:
        im = -im

    if smooth > 0:
        im = gaussian_filter(im, sigma=smooth)

    if guesspos is None :
        guesspos = 0.5*np.array(s)

    if verbose:
        print(funname + ": method: ", method)
        print(funname + ": s: ", s)
        print(funname + ": sign: ", sign)
        print(funname + ": searchsmooth: ", searchsmooth)
        print(funname + ": guessmeth: ", guessmeth)
        print(funname + ": initial guessamp: ", guessamp)
        print(funname + ": initial guessbg: ", guessbg)
        print(funname + ": intial guesspos: ", guesspos)
        print(funname + ": intial searchbox: ", searchbox)

    # --- define the search box
    if searchbox is not None :

        # test if the box provided is an integer, in which case blow up to array
        if not hasattr(searchbox, "__len__"):
            searchbox = np.array([searchbox, searchbox])

        searchbox = np.array(searchbox, dtype=int)

        sx0 = np.max([0, int(np.round(guesspos[1] - 0.5 * searchbox[1]))])
        sx1 = np.min([s[1], int(np.round(guesspos[1] + 0.5 * searchbox[1]))])
        sy0 = np.max([0, int(np.round(guesspos[0] - 0.5 * searchbox[0]))])
        sy1 = np.min([s[0], int(np.round(guesspos[0] + 0.5 * searchbox[0]))])
        searchim = im[sy0:sy1, sx0:sx1]

        if verbose:
            print(funname + ": sy0, sy1, sx0, sx1 ", sy0, sy1, sx0, sx1)
        searchbox = np.array(np.shape(searchim))

        # print(funname + ": ss: ", np.shape(searchim))

    else:
        searchbox = np.array(s, dtype=int)
        sx0 = 0
        sy0 = 0
        searchim = im

    if verbose:
        print(funname + ": final searchbox: ", searchbox)

    # --- should the first guess be based on the max or on the position?
    if guessmeth == 'max':
        if searchsmooth > 0:
            # smoothing is quick and thus on by default
            ssim = gaussian_filter(searchim, sigma=searchsmooth,
                                   mode='nearest')

            guesspos = np.array(np.unravel_index(np.nanargmax(ssim),
                                                 searchbox))

            if plot is True:
                plt.figure(1, figsize=(3,3))
                plt.imshow(ssim, origin='bottom', interpolation='nearest')
                plt.title('Smoothed Search image')
                plt.show()

            # print(funname + ": guesspos: ", guesspos)

        else:
            guesspos = np.array(np.unravel_index(np.nanargmax(searchim),
                                                 searchbox))

    else:
        guesspos = 0.5*searchbox

    if verbose:
        print(funname + ": guesspos in searchbox: ", guesspos)
        print(funname + ": guesspos in total image: ", guesspos[0] + sy0, guesspos[1] + sx0)
    # print(funname + ": guesspos: ", guesspos)

    guesspos = np.array([guesspos[0] + sy0, guesspos[1] + sx0])

    if plot is True:
        plt.clf()
        plt.close(1)
        plt.figure(1, figsize=(3,3))
        plt.imshow(searchim, origin='bottom', interpolation='nearest')
        plt.title('Search image')
        plt.show()

    if verbose:
        print(funname + ": intial fitbox: ", fitbox)

    # --- define the fit box
    if fitbox is not None:

        # test if the box provided is an integer, in which case blow up to array
        if not hasattr(fitbox, "__len__"):
            fitbox = np.array([fitbox, fitbox])

        fitbox = np.array(fitbox, dtype=int)
        fx0 = int(np.round(guesspos[1] - 0.5 * fitbox[1]))
        fx1 = int(np.round(guesspos[1] + 0.5 * fitbox[1]))
        fy0 = int(np.round(guesspos[0] - 0.5 * fitbox[0]))
        fy1 = int(np.round(guesspos[0] + 0.5 * fitbox[0]))

        guesspos = 0.5*fitbox
        # print('guesspos, fx0, fx1, fy0, fy1 ', guesspos, fx0, fx1, fy0, fy1)

        # for the new guess position, we have to take into account if the
        # fitbox is smaller than expected because being close to the edge

        if fx0 < 0:
            guesspos[1] = guesspos[1] + fx0
            fx0 = 0

#        if fx1 > s[1]:
#            guesspos[1] = guesspos[1] - (fx1 - s[1])
#            fx1 = s[1]

        if fy0 < 0:
            guesspos[0] = guesspos[0] + fy0
            fy0 = 0

#        if fy1 > s[0]:
#            guesspos[0] = guesspos[0] - (fy1 - s[0])
#            fy1 = s[0]

        fitim = im[fy0:fy1, fx0:fx1]
        fs = np.array(np.shape(fitim))

    else:
        fitim = im
        fx0 = 0
        fy0 = 0

    fs = np.shape(fitim)
    fitbox = fs

    if verbose:
        print(funname + ": final fitbox: ", fitbox)
        print(funname + ": final guesspos in fitbox: ", guesspos)


    if plot is True:
        plt.figure(1, figsize=(3,3))
        plt.imshow(fitim, origin='bottom', interpolation='nearest')
        plt.title('(Sub)image to be fitted')
        plt.show()

    if guessFWHM is None:
        if maxFWHM is not None and minFWHM is not None:
            guessFWHM = 0.5 * (maxFWHM + minFWHM)
        elif maxFWHM is not None:
            guessFWHM = 0.5 * maxFWHM
        elif minFWHM is not None:
            guessFWHM = 2 * minFWHM
        else:
            guessFWHM = 5

    # --- estimate the BG with ignoring central source (use either 3*FWHM or
    #     80% of image whatever is smaller). First generate a background image
    #     of sufficient size
    bgbox = int(np.round(6*guessFWHM))

    if verbose:
        print('bgbox:', bgbox)
        print("bgcenpos: ", [fy0 + 0.5*fitbox[0], fx0 + 0.5*fitbox[1]])

    bgim = _crop_image(im, box=bgbox,
                      cenpos=[fy0 + 0.5*fitbox[0], fx0 + 0.5*fitbox[1]],
                      exact=False)

    ignore_aper = np.min([3*guessFWHM, 0.8*np.max(s)])
    bgval, bgstd = _measure_bkg(bgim, ignore_aper=ignore_aper)

    if guessbg is None:
        guessbg = bgval

    if guessamp is None:
        guessamp = fitim[int(guesspos[0]), int(guesspos[1])] - guessbg

    if maxFWHM is None:
        maxFWHM = np.max(s)

    if minFWHM is None:
        minFWHM = 1

    maxsigma = maxFWHM * f2s
    minsigma = minFWHM * f2s

    if posradius is not None:
        minx = guesspos[1] - posradius
        maxx = guesspos[1] + posradius
        miny = guesspos[0] - posradius
        maxy = guesspos[0] + posradius
    else:
        minx = 0
        maxx = fs[1]
        miny = 0
        maxy = fs[0]

    sigma = guessFWHM * f2s
    guess = [guessbg, guessamp , guesspos[1],
             guesspos[0], sigma, sigma, 0]

    if verbose:
        print(' - Guess: ', guess)
        print(' - minFWHM: ', minFWHM)
        print(' - maxFWHM: ', maxFWHM)
        print(' - minsigma: ', minsigma)
        print(' - maxsigma: ', maxsigma)
        print(' - minamp: ', minamp)
        print(' - maxamp: ', maxamp)
        print(' - minx,maxx, miny,maxy: ', minx, maxx, miny, maxy)

    y, x = np.mgrid[:fs[0], :fs[1]]

    g_init = models.Gaussian2D(amplitude=guessamp, x_mean=guesspos[1],
                               y_mean=guesspos[0], x_stddev=sigma,
                               y_stddev=sigma)

    c_init = models.Const2D(amplitude=guessbg)

    init = g_init + c_init
    gim = init(x, y)

    if plot is True:
        plt.figure(1, figsize=(3,3))
        plt.imshow(gim, origin='bottom', interpolation='nearest')
        plt.title('Guess')
        plt.show()

    if np.isnan(fitim).any():
        if not silent:
            print(funname + ": WARNING: image to be cropped contains NaNs!")
        fitim[np.isnan(fitim)] = guessbg  # set any NaNs to 0 for crop to work

    if ('mpfit' in method) :

        # params=[] - initial input parameters for Gaussian function.
        # (height, amplitude, x, y, width_x, width_y, rota)

        # parameter limits
        minpars = [0, minamp, minx, miny, minsigma, minsigma, 0]
        maxpars = [0, maxamp, maxx, maxy, maxsigma, maxsigma, 0]
        limitedmin = [False, True, True, True, True, True, False]
        limitedmax = [False, False, True, True, True, True, False]

        # ensure that the fit is positive if the sign is 1
        # (or negative if the sign is -1)

        if minamp is None:
            limitedmin[1] = False

        if maxamp:
            limitedmax[1] = True

        if fixFWHM:
            limitedmin[4] = True
            limitedmax[4] = True
            minpars[4] = sigma-0.001
            maxpars[4] = sigma+0.001
            limitedmin[5] = True
            limitedmax[5] = True
            minpars[5] = sigma-0.001
            maxpars[5] = sigma+0.001

        if fixpos:
            limitedmin[2] = True
            limitedmax[2] = True
            minpars[2] = guesspos[1]-0.001
            maxpars[2] = guesspos[1]+0.001
            limitedmin[3] = True
            limitedmax[3] = True
            minpars[3] = guesspos[0]-0.001
            maxpars[3] = guesspos[0]+0.001

        res = _gaussfit(fitim, err=None, params=guess, returnfitimage=True,
                       return_all=1, minpars=minpars, maxpars=maxpars,
                       limitedmin=limitedmin, limitedmax=limitedmax)

        params = res[0][0]

        perrs = res[0][1]
        if perrs is None:
            perrs = np.full(6,-1, dtype=float)
        fit = res[1]

    elif 'fast' in method:

        # ensure that the fit is positive
        init.amplitude_0.bounds = (minamp, maxamp)

        init.x_mean_0.bounds = (minx, maxx)
        init.y_mean_0.bounds = (miny, maxy)

        init.x_stddev_0.bounds = (minsigma, maxsigma)

        # --- ensure that angle stays in useful pounds
        #init.theta_0.bounds = (-2*np.pi, 2*np.pi)  # somehow fixing the angle does not work

        if fixFWHM:
            init.x_stddev_0.fixed = True
            init.y_stddev_0.fixed = True

        if fixpos:
            init.x_mean_0.fixed = True
            init.y_mean_0.fixed = True

        fit_meth = fitting.LevMarLSQFitter()
#        fit_meth = fitting.SimplexLSQFitter()  # very slow
#        fit_meth = fitting.SLSQPLSQFitter()  # not faster than LevMar

        g_fit = fit_meth(init, x, y, fitim, acc=1e-8)

        fit = g_fit(x, y)

        params = np.array([g_fit.amplitude_1.value, g_fit.amplitude_0.value,
                           g_fit.x_mean_0.value, g_fit.y_mean_0.value,
                           g_fit.x_stddev_0.value, g_fit.y_stddev_0.value,
                           g_fit.theta_0.value])
        perrs = params * 0  # this method does not provide uncertainty estimates

        # --- convert theta to deg:
        params[6] = params[6]/np.pi*180.0

        # --- theta measures the angle from the x-axis, so we need to add 90
        params[6] = params[6] + 90.0


        # print(init.x_stddev_0.fixed, init.x_stddev_0.bounds)
        # print(g_fit.x_stddev_0.fixed, g_fit.x_stddev_0.bounds)

    else:
        print(funname + ": ERROR: non-valid method requested: " + method
              +"\n returning None")
        return(None, None, None)


    # --- use the STD of the BG for the BG level uncertainty if larger than
    #     error estimate
    if bgstd > perrs[0]:
        perrs[0] = bgstd

    if verbose:
        print(funname + ": uncorrected fit params: ", params)
        print(funname + ": uncorrected fit errs: ", perrs)

    # --- compute the position in the total image and switch x and y to agree
    #     with the numpy convention
    temp = np.copy(params)
    params[2] = temp[3] + fy0
    params[3] = temp[2] + fx0

    temp = np.copy(perrs)
    perrs[2] = temp[3]
    perrs[3] = temp[2]


    # --- if the y FWHM is larger than the one in x direction, switch them so
    #     that the first FWHM is the major axis one.
    if params[5] > params[4]:
        temp = params[4]
        params[4] = params[5]
        params[5] = temp

        temp = perrs[4]
        perrs[4] = perrs[5]
        perrs[5] = temp

        params[6] = params[6] + 90.0

    # --- normalise the angle
    params[6] = params[6] % 180
    if params[6] < 0:
        params[6] = params[6] + 180

    #
    if sign < 0:
        params[0] = - params[0]
        params[1] = - params[1]
        fit = - fit
        fitim = - fitim


    if verbose:
        print(" - GET_POINTSOURCE: fitted params: ", params)
    # convert sigma to FWHM for the output:
    params[4:6] = params[4:6] * s2f


    if plot is True:
        plt.figure(1, figsize=(3,3))
        plt.imshow(fit, origin='bottom', interpolation='nearest')
        plt.title('Fit with sign')
        plt.show()
        plt.close(1)

        plt.figure(1, figsize=(3,3))
        plt.imshow(fitim-fit, origin='bottom', interpolation='nearest')
        plt.title('Residual')
        plt.show()
        plt.close(1)

    ims = [fitim, fit, fitim-fit]

    return(params, perrs, ims)
Пример #19
0
 astmodels.Multiply(3),
 astmodels.Multiply(10 * u.m),
 astmodels.RotateCelestial2Native(5.63, -72.5, 180),
 astmodels.EulerAngleRotation(23, 14, 2.3, axes_order='xzx'),
 astmodels.Mapping((0, 1), n_inputs=3),
 astmodels.Shift(2. * u.deg),
 astmodels.Scale(3.4 * u.deg),
 astmodels.RotateNative2Celestial(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg),
 astmodels.RotateCelestial2Native(5.63 * u.deg, -72.5 * u.deg, 180 * u.deg),
 astmodels.RotationSequence3D([1.2, 2.3, 3.4, .3], 'xyzx'),
 astmodels.SphericalRotationSequence([1.2, 2.3, 3.4, .3], 'xyzy'),
 astmodels.AiryDisk2D(amplitude=10., x_0=0.5, y_0=1.5),
 astmodels.Box1D(amplitude=10., x_0=0.5, width=5.),
 astmodels.Box2D(amplitude=10., x_0=0.5, x_width=5., y_0=1.5, y_width=7.),
 astmodels.Const1D(amplitude=5.),
 astmodels.Const2D(amplitude=5.),
 astmodels.Disk2D(amplitude=10., x_0=0.5, y_0=1.5, R_0=5.),
 astmodels.Ellipse2D(amplitude=10., x_0=0.5, y_0=1.5, a=2., b=4.,
                     theta=0.1),
 astmodels.Exponential1D(amplitude=10., tau=3.5),
 astmodels.Gaussian1D(amplitude=10., mean=5., stddev=3.),
 astmodels.Gaussian2D(amplitude=10.,
                      x_mean=5.,
                      y_mean=5.,
                      x_stddev=3.,
                      y_stddev=3.),
 astmodels.KingProjectedAnalytic1D(amplitude=10., r_core=5., r_tide=2.),
 astmodels.Logarithmic1D(amplitude=10., tau=3.5),
 astmodels.Lorentz1D(amplitude=10., x_0=0.5, fwhm=2.5),
 astmodels.Moffat1D(amplitude=10., x_0=0.5, gamma=1.2, alpha=2.5),
 astmodels.Moffat2D(amplitude=10., x_0=0.5, y_0=1.5, gamma=1.2, alpha=2.5),
Пример #20
0
def star_centers_from_waffle_cube(cube,
                                  wave,
                                  instrument,
                                  waffle_orientation,
                                  high_pass=False,
                                  center_offset=(0, 0),
                                  smooth=0,
                                  coro=True,
                                  display=False,
                                  save_path=None):
    '''
    Compute star center from waffle images

    Parameters
    ----------
    cube : array_like
        Waffle IRDIS cube

    wave : array_like
        Wavelength values, in nanometers

    instrument : str
        Instrument, IFS or IRDIS
    
    waffle_orientation : str
        String giving the waffle orientation '+' or 'x'

    high_pass : bool    
        Apply high-pass filter to the image before searching for the
        satelitte spots

    smooth : int    
        Apply a gaussian smoothing to the images to reduce noise. The
        value is the sigma of the gaussian in pixel.  Default is no
        smoothing
    
    center_offset : tuple
        Apply an (x,y) offset to the default center position. Default is no offset
    
    coro : bool
        Observation was performed with a coronagraph. Default is True

    display : bool
        Display the fit of the satelitte spots

    save_path : str
        Path where to save the fit images
    
    Returns
    -------
    spot_center : array_like
        Centers of each individual spot in each frame of the cube

    spot_dist : array_like
        The 6 possible distances between the different spots

    img_center : array_like
        The star center in each frame of the cube

    '''

    # instrument
    if instrument == 'IFS':
        pixel = 7.46
        offset = 102
    elif instrument == 'IRDIS':
        pixel = 12.25
        offset = 0
    else:
        raise ValueError('Unknown instrument {0}'.format(instrument))

    # standard parameters
    dim = cube.shape[-1]
    nwave = wave.size
    loD = wave * 1e-6 / 8 * 180 / np.pi * 3600 * 1000 / pixel

    # waffle parameters
    freq = 10 * np.sqrt(2) * 0.97
    box = 8
    if waffle_orientation == '+':
        orient = offset * np.pi / 180
    elif waffle_orientation == 'x':
        orient = offset * np.pi / 180 + np.pi / 4

    # spot fitting
    xx, yy = np.meshgrid(np.arange(2 * box), np.arange(2 * box))

    # multi-page PDF to save result
    if save_path is not None:
        pdf = PdfPages(save_path)

    # center guess
    if instrument == 'IFS':
        center_guess = np.full((nwave, 2), ((dim // 2) + 3, (dim // 2) - 1))
    elif instrument == 'IRDIS':
        center_guess = np.array(((485, 520), (486, 508)))

    # loop over images
    spot_center = np.zeros((nwave, 4, 2))
    spot_dist = np.zeros((nwave, 6))
    img_center = np.zeros((nwave, 2))
    for idx, (wave, img) in enumerate(zip(wave, cube)):
        print('  wave {0:2d}/{1:2d} ({2:.3f} micron)'.format(
            idx + 1, nwave, wave))

        # remove any NaN
        img = np.nan_to_num(img)

        # center guess (+offset)
        cx_int = int(center_guess[idx, 0]) + center_offset[0]
        cy_int = int(center_guess[idx, 1]) + center_offset[1]

        # optional high-pass filter
        if high_pass:
            img = img - ndimage.median_filter(img, 15, mode='mirror')

        # optional smoothing
        if smooth > 0:
            img = ndimage.gaussian_filter(img, smooth)

        # mask for non-coronagraphic observations
        if not coro:
            mask = aperture.disc(cube[0].shape[-1],
                                 5 * loD[idx],
                                 diameter=False,
                                 center=(cx_int, cy_int),
                                 invert=True)
            img *= mask

        # create plot if needed
        if save_path or display:
            fig = plt.figure(0, figsize=(8, 8))
            plt.clf()
            col = ['red', 'blue', 'magenta', 'purple']
            ax = fig.add_subplot(111)
            ax.imshow(img / img.max(),
                      aspect='equal',
                      vmin=1e-2,
                      vmax=1,
                      norm=colors.LogNorm(),
                      interpolation='nearest')
            ax.set_title(r'Image #{0} - {1:.3f} $\mu$m'.format(idx + 1, wave))

        # satelitte spots
        for s in range(4):
            cx = int(cx_int + freq * loD[idx] * np.cos(orient + np.pi / 2 * s))
            cy = int(cy_int + freq * loD[idx] * np.sin(orient + np.pi / 2 * s))

            sub = img[cy - box:cy + box, cx - box:cx + box]

            # fit: Gaussian + constant
            imax = np.unravel_index(np.argmax(sub), sub.shape)
            g_init = models.Gaussian2D(amplitude=sub.max(), x_mean=imax[1], y_mean=imax[0],
                                       x_stddev=loD[idx], y_stddev=loD[idx]) + \
                                       models.Const2D(amplitude=sub.min())
            fitter = fitting.LevMarLSQFitter()
            par = fitter(g_init, xx, yy, sub)
            fit = par(xx, yy)

            cx_final = cx - box + par[0].x_mean
            cy_final = cy - box + par[0].y_mean

            spot_center[idx, s, 0] = cx_final
            spot_center[idx, s, 1] = cy_final

            # plot sattelite spots and fit
            if save_path or display:
                ax.plot([cx_final], [cy_final], marker='D', color=col[s])
                ax.add_patch(
                    patches.Rectangle((cx - box, cy - box),
                                      2 * box,
                                      2 * box,
                                      ec='white',
                                      fc='none'))

                axs = fig.add_axes((0.17 + s * 0.2, 0.17, 0.1, 0.1))
                axs.imshow(sub,
                           aspect='equal',
                           vmin=0,
                           vmax=sub.max(),
                           interpolation='nearest')
                axs.plot([par[0].x_mean], [par[0].y_mean],
                         marker='D',
                         color=col[s])
                axs.set_xticks([])
                axs.set_yticks([])

                axs = fig.add_axes((0.17 + s * 0.2, 0.06, 0.1, 0.1))
                axs.imshow(fit,
                           aspect='equal',
                           vmin=0,
                           vmax=sub.max(),
                           interpolation='nearest')
                axs.set_xticks([])
                axs.set_yticks([])

        # lines intersection
        intersect = lines_intersect(spot_center[idx, 0, :], spot_center[idx,
                                                                        2, :],
                                    spot_center[idx, 1, :], spot_center[idx,
                                                                        3, :])
        img_center[idx] = intersect

        # scaling
        spot_dist[idx, 0] = np.sqrt(
            np.sum((spot_center[idx, 0, :] - spot_center[idx, 2, :])**2))
        spot_dist[idx, 1] = np.sqrt(
            np.sum((spot_center[idx, 1, :] - spot_center[idx, 3, :])**2))
        spot_dist[idx, 2] = np.sqrt(
            np.sum((spot_center[idx, 0, :] - spot_center[idx, 1, :])**2))
        spot_dist[idx, 3] = np.sqrt(
            np.sum((spot_center[idx, 0, :] - spot_center[idx, 3, :])**2))
        spot_dist[idx, 4] = np.sqrt(
            np.sum((spot_center[idx, 1, :] - spot_center[idx, 2, :])**2))
        spot_dist[idx, 5] = np.sqrt(
            np.sum((spot_center[idx, 2, :] - spot_center[idx, 3, :])**2))

        # finalize plot
        if save_path or display:
            ax.plot([spot_center[idx, 0, 0], spot_center[idx, 2, 0]],
                    [spot_center[idx, 0, 1], spot_center[idx, 2, 1]],
                    color='w',
                    linestyle='dashed')
            ax.plot([spot_center[idx, 1, 0], spot_center[idx, 3, 0]],
                    [spot_center[idx, 1, 1], spot_center[idx, 3, 1]],
                    color='w',
                    linestyle='dashed')

            ax.plot([intersect[0]], [intersect[1]],
                    marker='+',
                    color='w',
                    ms=15)

            ext = 1000 / pixel
            ax.set_xlim(intersect[0] - ext, intersect[0] + ext)
            ax.set_ylim(intersect[1] - ext, intersect[1] + ext)

            plt.tight_layout()

            if save_path:
                pdf.savefig()

            if display:
                plt.pause(1e-3)

    if save_path:
        pdf.close()

    return spot_center, spot_dist, img_center
Пример #21
0
def iraf_style_photometry(phot_apertures,
                          bg_apertures,
                          data,
                          dark_std_data,
                          header,
                          seeing,
                          bg_method='mean',
                          epadu=1.0,
                          gain=8.21,
                          non_linear_threshold=4000):
    """Computes photometry with PhotUtils apertures, with IRAF formulae
    Parameters
    ----------
    phot_apertures : photutils PixelAperture object (or subclass)
        The PhotUtils apertures object to compute the photometry.
        i.e. the object returned via CircularAperture.
    bg_apertures : photutils PixelAperture object (or subclass)
        The phoutils aperture object to measure the background in.
        i.e. the object returned via CircularAnnulus.
    data : array
        The data for the image to be measured.
    bg_method: {'mean', 'median', 'mode'}, optional
        The statistic used to calculate the background.
        All measurements are sigma clipped.
        NOTE: From DAOPHOT, mode = 3 * median - 2 * mean.
    epadu: float, optional
        Gain in electrons per adu (only use if image units aren't e-).
    Returns
    -------
    final_tbl : astropy.table.Table
        An astropy Table with the colums X, Y, flux, flux_error, mag,
        and mag_err measurements for each of the sources.
    """
    exptime = header['EXPTIME']

    if bg_method not in ['mean', 'median', 'mode']:
        raise ValueError(
            'Invalid background method, choose either mean, median, or mode')

    #Create a list to hold the flux for each source.
    aperture_sum = []
    interpolation_flags = np.zeros(len(phot_apertures.positions), dtype='bool')

    for i in range(len(phot_apertures.positions)):
        pos = phot_apertures.positions[i]
        #Cutout around the source position
        cutout_w = 15
        x_pos = pos[0]
        y_pos = pos[1]
        cutout = data[int((y_pos - cutout_w)):int(y_pos + cutout_w) + 1,
                      int(x_pos - cutout_w):int(x_pos + cutout_w) + 1]
        x_cutout = x_pos - np.floor(x_pos - cutout_w)
        y_cutout = y_pos - np.floor(y_pos - cutout_w)
        ap = CircularAperture((x_cutout, y_cutout), r=phot_apertures.r)

        #Cut out the pixels JUST inside the aperture, and check if there are NaNs there. If so, interpolate over NaNs in the cutout.
        ap_mask = ap.to_mask(method='exact')
        ap_cut = ap_mask.cutout(cutout)

        non_linear_sum = np.sum(ap_cut / gain > non_linear_threshold)

        # if non_linear_sum > 0:
        #     print('Pixels in the non-linear range!')
        #     breakpoint()

        bad_sum = np.sum(np.isnan(ap_cut))

        if bad_sum > 0:
            bads = np.where(np.isnan(cutout))
            bad_dists = np.sqrt((bads[0] - y_cutout)**2 +
                                (bads[1] - x_cutout)**2)

            #Check if any bad pixels fall within the aperture. If so, set the interpolation flag to True for this source.
            if np.sum(bad_dists < phot_apertures.r + 1):

                # if np.sum(bad_dists < 1) == 0:
                #     #ONLY interpolate if bad pixels lay away from centroid position by at least a pixel.
                #     #2D gaussian fitting approach
                #     #Set up a 2D Gaussian model to interpolate the bad pixel values in the cutout.
                #     model_init = models.Const2D(amplitude=np.nanmedian(cutout))+models.Gaussian2D(amplitude=np.nanmax(cutout), x_mean=x_cutout, y_mean=y_cutout, x_stddev=seeing, y_stddev=seeing)
                #     xx, yy = np.indices(cutout.shape) #2D grids of x and y coordinates
                #     mask = ~np.isnan(cutout) #Find locations where the cutout has *good* values (i.e. not NaNs).
                #     x = xx[mask] #Only use coordinates at these good values.
                #     y = yy[mask]
                #     cutout_1d = cutout[mask] #Make a 1D cutout using only the good values.
                #     fitter = fitting.LevMarLSQFitter()
                #     model_fit = fitter(model_init, x, y, cutout_1d) #Fit the model to the 1d cutout.
                #     cutout[~mask] = model_fit(xx,yy)[~mask] #Interpolate the pixels in the cutout using the 2D Gaussian fit.
                #     pdb.set_trace()
                #     #TODO: interpolate_replace_nans with 2DGaussianKernel probably gives better estimation of *background* pixels.
                # else:
                #     interpolation_flags[i] = True

                #2D gaussian fitting approach
                #Set up a 2D Gaussian model to interpolate the bad pixel values in the cutout.
                model_init = models.Const2D(
                    amplitude=np.nanmedian(cutout)) + models.Gaussian2D(
                        amplitude=np.nanmax(cutout),
                        x_mean=x_cutout,
                        y_mean=y_cutout,
                        x_stddev=seeing,
                        y_stddev=seeing)
                xx, yy = np.indices(
                    cutout.shape)  #2D grids of x and y coordinates
                mask = ~np.isnan(
                    cutout
                )  #Find locations where the cutout has *good* values (i.e. not NaNs).
                x = xx[mask]  #Only use coordinates at these good values.
                y = yy[mask]
                cutout_1d = cutout[
                    mask]  #Make a 1D cutout using only the good values.
                fitter = fitting.LevMarLSQFitter()
                model_fit = fitter(model_init, x, y,
                                   cutout_1d)  #Fit the model to the 1d cutout.

                # #Uncomment this block to show inerpolation plots.
                # norm = ImageNormalize(cutout, interval=ZScaleInterval())
                # plt.ion()
                # fig, ax = plt.subplots(1, 4, figsize=(10,4), sharex=True, sharey=True)
                # ax[0].imshow(cutout, origin='lower', norm=norm)
                # ax[0].set_title('Data')
                # ax[1].imshow(model_fit(xx,yy), origin='lower', norm=norm)
                # ax[1].set_title('2D Gaussian Model')

                cutout[~mask] = model_fit(
                    xx, yy
                )[~mask]  #Interpolate the pixels in the cutout using the 2D Gaussian fit.

                # ax[2].imshow(cutout, origin='lower', norm=norm)
                # ax[2].set_title('Data w/ Bad\nPixels Replaced')
                # ax[3].imshow(cutout-model_fit(xx,yy), origin='lower')
                # ax[3].set_title('Residuals')
                # pdb.set_trace()

                interpolation_flags[i] = True

                # #Gaussian convolution approach.
                # cutout = interpolate_replace_nans(cutout, kernel=Gaussian2DKernel(x_stddev=0.5))

        phot_source = aperture_photometry(cutout, ap)
        # if np.isnan(phot_source['aperture_sum'][0]):
        #     pdb.set_trace()

        aperture_sum.append(phot_source['aperture_sum'][0])

    #Add positions/fluxes to a table
    xcenter = phot_apertures.positions[:, 0] * u.pix
    ycenter = phot_apertures.positions[:, 1] * u.pix
    phot = QTable([xcenter, ycenter, aperture_sum],
                  names=('xcenter', 'ycenter', 'aperture_sum'))

    #Now measure the background around each source.
    mask = make_source_mask(
        data, nsigma=3, npixels=5, dilate_size=7
    )  #Make a mask to block out any sources that might show up in the annuli and bias them.
    bg_phot = aperture_stats_tbl(
        ~mask * data, bg_apertures, sigma_clip=True
    )  #Pass the data with sources masked out to the bg calculator.
    ap_area = phot_apertures.area

    bg_method_name = 'aperture_{}'.format(bg_method)
    background = bg_phot[bg_method_name]
    flux = phot['aperture_sum'] - background * ap_area

    # Need to use variance of the sources for Poisson noise term in error computation.
    flux_error = compute_phot_error(flux, bg_phot, bg_method, ap_area, exptime,
                                    dark_std_data, phot_apertures, epadu)

    mag = -2.5 * np.log10(flux)
    mag_err = 1.0857 * flux_error / flux

    # Make the final table
    X, Y = phot_apertures.positions.T
    stacked = np.stack([
        X, Y, flux, flux_error, mag, mag_err, background, interpolation_flags
    ],
                       axis=1)
    names = [
        'X', 'Y', 'flux', 'flux_error', 'mag', 'mag_error', 'background',
        'interpolation_flag'
    ]

    final_tbl = Table(data=stacked, names=names)

    #Check for nans
    if sum(np.isnan(final_tbl['flux'])) > 0:
        bad_locs = np.where(np.isnan(final_tbl['flux']))[0]
        #pdb.set_trace()

    return final_tbl
Пример #22
0
def simulate_image(psffits=None,
                   theofits=None,
                   obsfits=None,
                   distance=None,
                   extfits=None,
                   wlen=None,
                   pfov=None,
                   filt=None,
                   psfext=0,
                   obsext=0,
                   bgstd=0,
                   bgmed=0,
                   silent=False,
                   posang=0,
                   foi_as=4,
                   outname=None,
                   manscale=None,
                   fnucdiam_as=0.45,
                   suffix=None,
                   fitsize=None,
                   outfolder='.',
                   writesimfits=False,
                   writetheofits=False,
                   writeallfits=False,
                   writefitplot=False,
                   debug=False,
                   returncmod=True,
                   fitpsf=False,
                   saveplot=False,
                   meastime=False):
    """
    Simulate a (VISIR) imaging observaion given a real image, a PSF reference
    image and a model image for a provided object distance and position angle

    Current constraints:
        - The routine assumes that the images are squares (x = y)
    """

    if meastime:
        tstart = time.time()

    psfim = fits.getdata(psffits, ext=psfext)
    psfhead = fits.getheader(psffits)

    # print(obsfits, obsext)
    # ==== 1. Load Observational and PSF data ====
    if obsfits is not None:
        obsim = fits.getdata(obsfits, ext=obsext)
        obshead = fits.getheader(obsfits)

        if wlen is None:
            wlen = float(obshead['WAVELEN'])

        if pfov is None:
            pfov = obshead['PFOV']

        if filt is None:
            filt = obshead['Filter']

    else:
        if wlen is None:
            wlen = float(psfhead['WAVELEN'])

        if pfov is None:
            pfov = psfhead['PFOV']

        if filt is None:
            filt = psfhead['Filter']

    wlenstr = "{:.1f}".format(wlen)
    diststr = "{:.1f}".format(distance)
    pastr = "{:.0f}".format(posang)

    # --- optinal cropping of the PSF image
    if fitsize is not None:
        psfim = _crop_image(psfim,
                            box=fitsize,
                            cenpos=_get_pointsource(psfim)[0][2:4])

    psfsize = np.array(np.shape(psfim))
    psfsize_as = psfsize[0] * pfov

    if not silent:
        print("Obs. wavelength [um]: " + wlenstr)

    # ==== 2. Prepare theoretical image for convolution ====
    theohdu = fits.open(theofits)
    theohead = theohdu[0].header

    # --- check if provided file is the full cube
    if 'NAXIS3' in theohead:
        # find the right frame to be extracted
        mind, mwlen = find_wlen_match(theofits, wlen)
        theoim = theohdu[0].data[mind]

        if not silent:
            print(" - Model wavelength [um] | frame no: " + str(mwlen) +
                  " | " + str(mind))

    else:
        theoim = theohdu[0].data

    theohdu.close()

    if meastime:
        print(" - All files read. Elapsed time: ", time.time() - tstart)

    if debug is True:
        print("THEOIM: ")
        plt.imshow(theoim,
                   origin='bottom',
                   norm=LogNorm(),
                   interpolation='nearest')
        plt.show()

    # --- if a manual scaling (fudge) factor was provided, apply to the model
    if manscale:
        theoim = theoim * manscale

    # --- rotate the model image (this changes its extent and thus has to be
    #     done first)
    # unrot = np.copy(theoim)
    if np.abs(posang - 0) > 0.01:
        theoim = ndimage.interpolation.rotate(theoim, 180 - posang, order=0)

    if meastime:
        print(" - Model rotated. Elapsed time: ", time.time() - tstart)

    if debug is True:
        print("THEOIM_ROT: ")
        plt.imshow(theoim,
                   origin='bottom',
                   norm=LogNorm(),
                   interpolation='nearest')
        plt.show()

    # --- size units of the theoretical image
    theopixsize_pc = theohead['CDELT1']
    theosize = np.array(np.shape(theoim))
    theosize_pc = theopixsize_pc * theosize[0]  # pc
    theosize_as = 2 * np.arctan(
        theosize_pc / distance / 2.0e6) * 180 / np.pi * 3600
    theopixsize_as = (2 * np.arctan(theopixsize_pc / distance / 2.e6) * 180 /
                      np.pi * 3600)

    #    plt.imshow(unrot, origin='bottom', norm=LogNorm())
    #    plt.imshow(theoim, origin='bottom', norm=LogNorm())
    #    plt.imshow(theoim, origin='bottom')

    #    theopfov = angsize/theosize[0]

    # normalize image to flux = 1
    # theoim = theoim/np.sum(theoim)
    # theoim = theoim/np.max(theoim)

    # --- convert to the right flux units
    # surface brightness unit in cube is W/m^2/arcsec2
    # -> convert to flux density in mJy
    # print(np.sum(theoim))
    freq = 2.99793e8 / (1e-6 * wlen)
    # print(freq)

    theoim = theoim * 1.0e29 / freq * theopixsize_as**2

    theototflux = np.sum(theoim)

    # --- resample to the same pixel size as the observation
    # print(   "- Resampling the model image to instrument pixel size...")
    # --- the ndimage.zoom sometimes creates weird artifacts and thus might not
    #     be a good choice. Instead, use the self-made routine
    # theoim_resres = ndimage.zoom(theoim_res, theopixsize_as/pfov, order=0)
    # --- do the rasmpling before scaling it to the size of the observation to
    #     save computing time
    theoim_res, sizerat = _increase_pixelsize(theoim,
                                              oldpfov=theopixsize_as,
                                              newpfov=pfov,
                                              meastime=False)

    if meastime:
        print(" - Pixelsize increased. Elapsed time: ", time.time() - tstart)

    if debug is True:
        print("sizerat: ", sizerat)
        print("THEOIM_RESRES: ")
        plt.imshow(theoim_res,
                   origin='bottom',
                   norm=LogNorm(),
                   interpolation='nearest')
        plt.show()

    # --- if the PSF frame size is larger than the model extend the model frame
    framerat = psfsize_as / theosize_as

    if debug is True:
        print("psfsize_as: ", psfsize_as)
        print("theosize_as: ", theosize_as)
        print("theopixsize_as: ", theopixsize_as)
        print("theosize_pc: ", theosize_pc)
        print("theosize: ", theosize)
        print("framerat: ", framerat)
    #print("newsize: ", newsize)

    if framerat > 1.0:
        theoim_resres = _extend_image(theoim_res, fac=framerat)

        if meastime:
            print(" - Frame extended. Elapsed time: ", time.time() - tstart)

        if debug is True:
            print("thesize_ext: ", np.shape(theoim_resres))
            print("THEOIM_RESRES: ")
            plt.imshow(theoim_resres,
                       origin='bottom',
                       norm=LogNorm(),
                       interpolation='nearest')
            plt.show()
        # plt.imshow(theoim_res, origin='bottom')

    else:
        theoim_resres = np.copy(theoim_res)

    theosize_new = np.array(np.shape(theoim_resres))

    if debug is True:
        print("theosize_new: ", theosize_new)
        print("new theosize_as: ", theosize_new * pfov)

    # print(np.sum(theoim_resres))

    # --- after resampling we need to re-establish the right flux levels
    theoim_resres = theoim_resres / np.sum(theoim_resres) * theototflux

    # plt.imshow(theoim_resres, origin='bottom', norm=LogNorm())
    # plt.imshow(theoim_resres, origin='bottom')

    # ==== 4. Optionally, apply a foreground extinction map if provided ====
    if extfits:

        exthdu = fits.open(extfits)
        exthead = exthdu[0].header
        extpfov = exthead["PFOV"]

        # check if provided file is the full cube
        if 'NAXIS3' in exthead:

            # find the right frame to be extracted
            if not mind:
                mind, mwlen = find_wlen_match(theofits, wlen)

            extim = exthdu[0].data[mind]

        else:
            extim = exthdu[0].data

        # plt.imshow(extim,origin='bottom')
        # plt.show()

        exthdu.close()

        # --- Do we have to resample the extinction map
        if np.abs(extpfov - pfov) > 0.001:
            # print("Resampling extinction map... ")
            extim = ndimage.zoom(extim, 1.0 * pfov / extpfov, order=0)

        # --- match the size of the extinction map to the one of the model
        # image
        extsize = np.array(np.shape(extim))

        if theosize_new[0] > extsize[0]:
            print(" - ERROR: provided extinction map is too small to cover \
                   the imaged region. Abort...")
            return ()

        if extsize[0] > theosize_new[0]:
            # print("Cuttting exctinction map...")
            extim = _crop_image(extim, box=theosize_new)

        # --- finally apply the extinction map
        theoim_resres = theoim_resres * extim
        # print('Maximum extinction: ',np.min(extim))

    # ==== 5. Obtain the Airy function from the calibrator ====
    if debug is True:
        print("PSF_IM: ")
        plt.imshow(psfim,
                   origin='bottom',
                   norm=LogNorm(),
                   interpolation='nearest')
        plt.show()

    # --- set up the fitting of the STD star image
    # --- assume that the maximum of the (cropped) image is the STD star
    if fitpsf is True:
        psfmax = np.max(psfim)
        psfmaxpos = np.unravel_index(np.argmax(psfim), psfsize)
        # print(im_bg, im_max, im_size, im_maxpos)

        # --- Use an Airy plus a Moffat + constant as PSF model
        c_init = models.Const2D(amplitude=np.median(psfim))

        oa_init = obsc_AiryDisk2D(amplitude=psfmax,
                                  x_0=psfmaxpos[1],
                                  y_0=psfmaxpos[0],
                                  radius=5,
                                  obsrat=0.15)

        m_init = models.Moffat2D(amplitude=psfmax,
                                 x_0=psfmaxpos[1],
                                 y_0=psfmaxpos[0],
                                 gamma=5,
                                 alpha=1)

        a_plus_m = oa_init + m_init + c_init

        # print(psfmax, psfmaxpos)

        # --- Selection of fitting method
        fit_meth = fitting.LevMarLSQFitter()

        # --- Do the Fitting:
        y, x = np.mgrid[:psfsize[0], :psfsize[1]]
        am_fit = fit_meth(a_plus_m, x, y, psfim)
        # print(am_fit.radius_0.value, am_fit.obsrat_0.value)

        # then refine the fit by subtracting another Moffat
        # a_plus_m_minus_m = am_fit - m_init
        # amm_fit = fit_meth(a_plus_m_minus_m, x, y, psfim)
        # z_amm = amm_fit(x, y)
        #        res = psfim - z_amm
        #print(amm_fit.radius_0.value, amm_fit.obsrat_0.value)

        if meastime:
            print(" - PSf fit. Elapsed time: ", time.time() - tstart)

        if debug is True:
            print("PSF_FIT: ")
            plt.imshow(am_fit(x, y),
                       origin='bottom',
                       norm=LogNorm(),
                       interpolation='nearest')
            plt.show()

        # --- Genarate the PSF image with the fitted model
        # --- check whether the model image is on sky larger than the PSF image
        #     and if this is the case increase the grid and adjust the positions
        #     of the fits
        size_diff = theosize_new[0] - psfsize[0]
        if size_diff > 0:
            y, x = np.mgrid[:theosize_new[0], :theosize_new[1]]
            am_fit.x_0_0 = am_fit.x_0_0 + 0.5 * size_diff
            am_fit.y_0_0 = am_fit.y_0_0 + 0.5 * size_diff
            am_fit.x_0_1 = am_fit.x_0_1 + 0.5 * size_diff
            am_fit.y_0_1 = am_fit.y_0_1 + 0.5 * size_diff

        # --- create the final PSF image by subtracting the constant terms
        # and normalise by its area
        psf = am_fit(x, y) - am_fit.amplitude_2.value
        psf = psf / np.sum(psf)

        if debug is True:
            print("PSF_FINAL: ")
            plt.imshow(psf,
                       origin='bottom',
                       norm=LogNorm(),
                       interpolation='nearest')
            plt.show()

        # plt.imshow(psf, origin='bottom', norm=LogNorm())
        # plt.show()
        if writeallfits is True:

            fout = psffits.replace('.fits', '_fit.fits')
            if 'OBS NAME' in psfhead:
                psfhead.remove('OBS NAME')
            fits.writeto(fout, psf, psfhead, overwrite=True)

        # --- optinally, write the a plot documenting the quality of the PSF fit
        if writefitplot:

            fitplotfname = psffits.split('/')[-1]
            fitplotfname = fitplotfname.replace('.fits', '_fitplot.pdf')
            fitplotfname = outfolder + '/' + fitplotfname

            maxrad = int(0.5 * np.sqrt(0.5) * np.min(psfsize))

            _make_fit_plots(psfim,
                            am_fit(x, y),
                            fitplotfname,
                            am_fit.x_0_0,
                            am_fit.y_0_0,
                            maxrad,
                            inv=True,
                            cmap='gist_heat',
                            labelcolor='black')

    # --- alternatively the PSF can also be direclty provided so that no fit is
    #     necessary
    else:
        # normalise the provided psf
        psf = psfim - np.nanmin(psfim)
        psf = psf / np.nansum(psf)
        psf[np.argwhere(np.isnan(psf))] = 0

    # ==== 6. Convolution of the model image with the PSF ====
    simim = scipy.signal.convolve2d(theoim_resres, psf, mode='same')
    # print(np.sum(simim))

    if meastime:
        print(" - Model convolved. Elapsed time: ", time.time() - tstart)

    if debug is True:
        print("SIMIM:")
        plt.imshow(simim,
                   origin='bottom',
                   norm=LogNorm(),
                   interpolation='nearest')
        plt.show()

    # ==== 7. Flux measurements and application of noise ====
    # --- Flux measurement on the model image before applying noise
    ftotmod = int(np.nansum(simim))

    apos = 0.5 * np.array(theosize_new)

    nucrad_px = 0.5 * fnucdiam_as / pfov
    from aper import aper
    (mag, magerr, flux, fluxerr, sky, skyerr, badflag,
     outstr) = aper(simim,
                    apos[1],
                    apos[0],
                    apr=nucrad_px,
                    exact=True,
                    setskyval=0)

    fnucmod = int(flux)

    if not silent:
        print(' - Model total | nuclear flux [mJy]:    ' + str(ftotmod) +
              ' | ' + str(fnucmod))

    plotsize_px = int(1.0 * foi_as / pfov)

    # --- Measure the background noise from the real observation
    if obsfits is not None:
        bg = np.copy(obsim)
        #    bgsize = np.shape(bg)
        params, _, _ = _get_pointsource(obsim)
        xpos = params[2]
        ypos = params[3]

        bg[int(ypos - 0.5 * plotsize_px):int(ypos + 0.5 * plotsize_px),
           int(xpos - 0.5 * plotsize_px):int(xpos + 0.5 * plotsize_px)] = 0

        if bgstd == 0:
            bgstd = np.std(bg[bg != 0])

        if bgmed == 0:
            bgmed = np.median(bg[bg != 0])
        # plt.imshow(bg, origin='bottom', norm=LogNorm(), cmap='gist_heat')

        # --- crop the observational image to the requested plot size
        cim = _crop_image(obsim,
                          box=plotsize_px,
                          cenpos=_get_pointsource(obsim)[0][2:4])
        newsize = np.shape(cim)

        # --- make flux measurements on the nucleus and total
        ftotobs = int(np.sum(cim - bgmed))
        apos = 0.5 * np.array(newsize)

        (mag, magerr, flux, fluxerr, sky, skyerr, badflag,
         outstr) = aper(cim,
                        apos[1],
                        apos[0],
                        apr=nucrad_px,
                        exact=True,
                        setskyval=0)

        fnucobs = int(flux)

        if not silent:
            print(' - Observed total | nuclear flux [mJy]: ' + str(ftotobs) +
                  ' | ' + str(fnucobs))

    else:
        cim = None

    if meastime:
        print(" - Fluxes measured. Elapsed time: ", time.time() - tstart)

    # pdb.set_trace()
    # --- crop the simulated image to the requested plot size
    params, _, _ = _get_pointsource(simim)
    csimim = _crop_image(simim,
                         box=plotsize_px,
                         cenpos=_get_pointsource(simim)[0][2:4])
    if debug is True:
        plt.imshow(simim, origin='bottom', cmap='gist_heat', norm=LogNorm())
        plt.title('simim')
        plt.show()
        print(plotsize_px, np.shape(csimim))
        plt.imshow(csimim, origin='bottom', cmap='gist_heat', norm=LogNorm())
        plt.title('csimim')
        plt.show()

    if meastime:
        print(" - Obs cropped. Elapsed time: ", time.time() - tstart)

    # --- generate an artifical noise frame with same properties as in real
    #     image
    if bgstd > 0:
        artbg = np.random.normal(scale=bgstd,
                                 size=(plotsize_px, plotsize_px)) + bgmed
        # artbg = 0

        # --- apply the artificial background
        # csimim = csimim/np.max(csimim)*(np.max(cim)-bgmed)+bgmed+artbg
        csimim = csimim + artbg

    # --- crop the PSF image to the requested plot size
    cpsf = _crop_image(psf,
                       box=plotsize_px,
                       cenpos=_get_pointsource(psf)[0][2:4])

    # print(bgmed, bgstd, np.std(artbg), np.median(cim), np.median(csimim),
    # np.std(cim), np.std(csimim), np.max(cim), np.min(cim), np.max(csimim),
    # np.min(csimim))

    if meastime:
        print(" - PSF cropped. Elapsed time: ", time.time() - tstart)

    theopfov = theopixsize_as
    theobox = int(np.round(plotsize_px * pfov / theopfov))

    if returncmod:
        # --- crop the model imae to the requested plot size
        if framerat > 1.0:
            theoim_res_0 = _extend_image(theoim, fac=framerat)
        else:
            theoim_res_0 = theoim
        cmod = _crop_image(theoim_res_0, box=theobox, exact=False)
        # plt.imshow(cmod, origin='bottom', norm=LogNorm(), cmap='gist_heat')

        if meastime:
            print(" - Theo cropped. Elapsed time: ", time.time() - tstart)

    else:
        cmod = None

    # --- write out the simulated image as a fits file
    if not outname:

        theofitsfile = theofits.split("/")[-1]

        out_str = theofitsfile.replace("_total.fits", "")

        out_str = (out_str + "_pa" + pastr + "_dist" + diststr + "_wlen" +
                   wlenstr)

        if suffix:
            out_str = out_str + "_" + suffix

    else:
        out_str = outname

    out_str = outfolder + '/' + out_str

    if writeallfits or writetheofits:

        fout = out_str + '_mod.fits'

        theohead["Filter"] = filt
        theohead["WAVELEN"] = wlen
        theohead["PFOV"] = theopixsize_as

        fits.writeto(fout, cmod, theohead, overwrite=True)

        if saveplot:
            _simple_image_plot(cmod, fout.replace(".fits", ".png"), log=True)

    if writeallfits or writesimfits:

        fout = out_str + '_sim.fits'
        theohead["PFOV"] = pfov
        theohead["BUNIT"] = 'mJy'

        ts = np.shape(simim)
        theohead["CRPIX1"] = 0.5 * ts[1]
        theohead["CDELT1"] = pfov
        theohead["CTYPE1"] = 'arcsec'
        theohead["CRPIX2"] = 0.5 * ts[0]
        theohead["CDELT2"] = pfov
        theohead["CTYPE2"] = 'arcsec'

        fits.writeto(fout, csimim, theohead, overwrite=True)

        if saveplot:
            _simple_image_plot(csimim, fout.replace(".fits", ".png"), log=True)

    if meastime:
        print(" - All finished: ", time.time() - tstart)

    return (cpsf, cmod, cim, csimim, wlen, pfov, theopfov)
Пример #23
0
def star_centers_from_PSF_cube(cube,
                               wave,
                               pixel,
                               display=False,
                               save_path=None):
    '''
    Compute star center from PSF images

    Parameters
    ----------
    cube : array_like
        PSF IFS cube

    wave : array_like
        Wavelength values, in nanometers

    pixel : float
        Pixel scale, in mas/pixel
    
    display : bool
        Display the fit of the satelitte spots

    save_path : str
        Path where to save the fit images
    
    Returns
    -------
    img_center : array_like
        The star center in each frame of the cube
    '''

    # standard parameters
    nwave = wave.size
    loD = wave * 1e-6 / 8 * 180 / np.pi * 3600 * 1000 / pixel
    box = 30

    # spot fitting
    xx, yy = np.meshgrid(np.arange(2 * box), np.arange(2 * box))

    # multi-page PDF to save result
    if save_path is not None:
        pdf = PdfPages(save_path)

    # loop over images
    img_center = np.zeros((nwave, 2))
    for idx, (wave, img) in enumerate(zip(wave, cube)):
        print('  wave {0:2d}/{1:2d} ({2:.3f} micron)'.format(
            idx + 1, nwave, wave))

        # remove any NaN
        img = np.nan_to_num(img)

        # center guess
        cy, cx = np.unravel_index(np.argmax(img), img.shape)

        # sub-image
        sub = img[cy - box:cy + box, cx - box:cx + box]

        # fit peak with Gaussian + constant
        imax = np.unravel_index(np.argmax(sub), sub.shape)
        g_init = models.Gaussian2D(amplitude=sub.max(), x_mean=imax[1], y_mean=imax[0],
                                   x_stddev=loD[idx], y_stddev=loD[idx]) + \
                                   models.Const2D(amplitude=sub.min())
        fitter = fitting.LevMarLSQFitter()
        par = fitter(g_init, xx, yy, sub)

        cx_final = cx - box + par[0].x_mean
        cy_final = cy - box + par[0].y_mean

        img_center[idx, 0] = cx_final
        img_center[idx, 1] = cy_final

        if save_path or display:
            fig = plt.figure(0, figsize=(8, 8))
            plt.clf()
            ax = fig.add_subplot(111)

            ax.imshow(img / img.max(),
                      aspect='equal',
                      vmin=1e-6,
                      vmax=1,
                      norm=colors.LogNorm(),
                      interpolation='nearest')
            ax.plot([cx_final], [cy_final], marker='D', color='red')
            ax.add_patch(
                patches.Rectangle((cx - box, cy - box),
                                  2 * box,
                                  2 * box,
                                  ec='white',
                                  fc='none'))
            ax.set_title(r'Image #{0} - {1:.3f} $\mu$m'.format(idx + 1, wave))

            ext = 1000 / pixel
            ax.set_xlim(cx_final - ext, cx_final + ext)
            ax.set_ylim(cy_final - ext, cy_final + ext)

            plt.tight_layout()

            if save_path:
                pdf.savefig()

            if display:
                plt.pause(1e-3)

    if save_path:
        pdf.close()

    return img_center
Пример #24
0
def autocorrelation_fit(im2, rough_sep = 44.93, rough_pa=0., cutout_sz=5, background_subtracted=False,
             plot_cutout=False, plot_resids=False):
    """ Fits to the distance of the second peak in the autocorrelation (i.e. the binary separation).
        This will perform a Levenberg-Marquardt fit over a small, fixed region around the peak. 
        The model used for the fit will depend on the options set.
        
        rough_sep, rough_pa: Defaults 44.93 pixels and 0 degrees
            These define the position of the region used for the fit.
        cutout_sz: Default 5 pixels
            The radius of the box used for the fit.
        background_subtracted: Default False
            If True, the model used for the fit will be an Airy Disk + constant background.
            If False, the model will be a Gaussian + planar background.
            These seemed to work best.
    """
    
    rough_pos = np.round([rough_sep*np.sin(rough_pa),rough_sep*np.cos(rough_pa)]).astype(int)
    calc_pos = [rough_sep*np.sin(rough_pa),rough_sep*np.cos(rough_pa)]
    
    cutout = np.copy(im2[im2.shape[0]//2+rough_pos[0]-cutout_sz:im2.shape[0]//2+rough_pos[0]+cutout_sz,
                 im2.shape[1]//2+rough_pos[1]-cutout_sz:im2.shape[1]//2+rough_pos[1]+cutout_sz])

    cutout /= np.max(cutout)
    if plot_cutout:
        plt.imshow(cutout)

    x,y = np.indices(cutout.shape)
    x = x + rough_pos[0] - cutout_sz
    y = y + rough_pos[1] - cutout_sz
    
    # Fit a Gaussian
    fit = fitting.LevMarLSQFitter()
    
    if background_subtracted:
        gauss = models.AiryDisk2D(amplitude=cutout.max(),x_0=calc_pos[0],y_0=calc_pos[1],radius=3.54)        
        bckgd = models.Const2D(amplitude=0.6)
    else:
        gauss = models.Gaussian2D(amplitude = cutout.max(),x_stddev=1.36,y_stddev=1.36,
                                  x_mean=calc_pos[0],y_mean=calc_pos[1])

        bckgd = models.Planar2D(slope_x = -0.00564816,slope_y=-0.02378304,intercept=1.01)
        gauss.fixed['theta']=True
        
    cutout_model = gauss + bckgd

    # fit the data with the fitter
    fitted_model = fit(cutout_model,x,y,cutout,maxiter=100000,acc=1e-7);
    
    # Rename the parameters so the output looks the same
    if background_subtracted:
        fitted_model.x_mean_0 = fitted_model.x_0_0
        fitted_model.y_mean_0 = fitted_model.y_0_0
        
    if plot_resids:
        test = fitted_model(x,y)
        plt.figure(figsize=(12,4))
        plt.clf()
        plt.subplot(131)
        plt.imshow(cutout,origin='lowerleft',vmin=0.7,vmax=1);plt.colorbar()
        plt.subplot(132)
        plt.imshow(test,origin='lowerleft',vmin=0.7,vmax=1);plt.colorbar()
        plt.subplot(133)
        plt.imshow(cutout-test,origin='lowerleft',vmin=-0.05,vmax=0.05);plt.colorbar()
    return fitted_model
    def _do_fitting(self):
        """Performs 1-D and 2-G gaussian fits to the stars in the
           internal fit_table.
           
        A 2-dimensional Gaussian plus flat background model is fit to
        each cut-out image. The model is initialized with the global
        background level, and source centroid and peak value from the
        input photometry list. The Levenberg-Marquardt non-linear
        least squares fitting algorithm is used to fit the data.
        The data is assumed to have Gaussian errors that are the square
        root of the pixel counts.
           
        There are some limitations to this at present:
        - The reduced chi-squared values obtained are unrealistic. It is
          not clear whether the errors are underestimated and/or the
          fitting has trouble converging to the true solution.
        """

        # Setup
        num_stars = len(self._fit_table)
        sigma_to_fwhm = 2.35482
        fwhm_to_sigma = 1.0 / sigma_to_fwhm
        max_iterations = 500

        # Fit for x and y after initially just fitting for amplitude,
        # fwhm, and theta?
        is_pos_fitted_for = True

        # Extract data from main image, stores in _pixel_array.
        self._extract_cutouts()

        # Add extra columns to the output table for the fit results
        self._prepare_fit_columns()

        self._logger.info(
            f'Starting to fit Const2D+XX models to {num_stars} star cutouts.')
        perf_time_start = time.perf_counter(
        )  # highest res timer, counts sleeps

        # X and Y pixel index grids needed by the fitter
        x_grid, y_grid = np.mgrid[0:self._box_width_pix, 0:self._box_width_pix]

        # We initialize the 2-D Gaussians based on the initial FWHM
        # but with a slight asymmetry sig_x/sig_y = 1.1, theta approx 30 degrees.
        def_axrat = 1.05
        sig_y = self._init_fwhm * fwhm_to_sigma
        sig_x = def_axrat * sig_y
        rotang = 1.1  # radians, approx 60 degrees.
        xpos = self._box_width_pix / 2
        ypos = self._box_width_pix / 2
        ampl = 1
        num_fit_pars = 0

        # The background model starts off fixed.
        bg_mod = models.Const2D(amplitude=self._init_bglvl)
        bg_mod.amplitude.fixed = True

        # 2-D gaussian model for the star.
        star_mod = models.Gaussian2D(amplitude=ampl,
                                     x_mean=xpos,
                                     y_mean=ypos,
                                     x_stddev=sig_x,
                                     y_stddev=sig_y,
                                     theta=rotang)

        # Constraint theta to -pi/2 to pi/2
        # NOTE Disabled as it results in the fitter failing very often
        ##half_pi               = 0.5 * math.pi
        ##star_mod.theta.bounds = (-half_pi, half_pi)

        # Start fits at the initial centroid positions
        star_mod.x_mean.fixed = True
        star_mod.y_mean.fixed = True
        num_fit_pars += 4  # 4 free fit parameters
        comb_mod = star_mod + bg_mod

        fitter = fitting.LevMarLSQFitter()

        # Iterate over stars, first updating initial values, then fitting,
        # then storing the fit results.
        for idx in range(num_stars):
            # Get better estimate for initial parameters, in particular
            # the amplitude.
            # Note: you can set a Parameter object by value directly,
            # (e.g. "bob=3") but to access it you must access its value
            # attribute (e.g. "x = bob.value")
            starid = self._fit_table['id'][idx]
            xpos = self._fit_table['xcenter'][idx] - self._fit_table['xmin'][
                idx]
            ypos = self._fit_table['ycenter'][idx] - self._fit_table['ymin'][
                idx]
            ampl = self._fit_table['peak_adu'][idx]
            bglvl = self._fit_table['bgmed_per_pix'][idx]
            comb_mod[0].x_mean = xpos
            comb_mod[0].y_mean = ypos
            comb_mod[0].amplitude = ampl
            comb_mod[1].amplitude = bglvl
            current_num_fit_pars = num_fit_pars

            # Estimated standard devation of the data.
            var_arr = np.where(self._pixel_array[idx] > 0,
                               self._pixel_array[idx], 1)
            mean_variance = np.mean(var_arr[var_arr != 1])
            rms_stddev = math.sqrt(mean_variance)
            std_arr = np.where(var_arr != 1, np.sqrt(var_arr), rms_stddev)
            # If used, the weight array should have values of 1/sigma
            if self._use_weights:
                weights_arr = 1.0 / std_arr
            else:
                weights_arr = None

            # Fit
            self._logger.debug(
                f'Fitting star #{idx} (id={starid}) at fixed xpos={xpos:.2f}, ypos={ypos:.2f}'
            )

            fitted_mod, fit_status, fit_message, fit_ok, uncert_vals = self._do_single_fit(
                fitter, comb_mod, self._pixel_array[idx], weights_arr, x_grid,
                y_grid, max_iterations, current_num_fit_pars, idx)

            # If the ift is OK, and fit_for_bg is true, then fit for the BG level.
            if (fit_ok) and (self._fit_for_bg is True):
                fitted_mod[1].amplitude.fixed = False
                current_num_fit_pars += 1
                fitted_mod, fit_status, fit_message, fit_ok, uncert_vals = self._do_single_fit(
                    fitter, fitted_mod, self._pixel_array[idx], weights_arr,
                    x_grid, y_grid, max_iterations, current_num_fit_pars, idx)

            # If the fit did NOT fail we can relax the constraints on the
            # position of the gaussian.
            if fit_ok and is_pos_fitted_for:
                fitted_mod[0].x_mean.fixed = False
                fitted_mod[0].y_mean.fixed = False
                current_num_fit_pars += 2
                fitted_mod, fit_status, fit_message, fit_ok, uncert_vals = self._do_single_fit(
                    fitter, fitted_mod, self._pixel_array[idx], weights_arr,
                    x_grid, y_grid, max_iterations, current_num_fit_pars, idx)

            # Calculate reduced chi squared.
            rchisq = self._calculate_rchisq(self._pixel_array[idx], x_grid,
                                            y_grid, std_arr, fitted_mod,
                                            current_num_fit_pars)

            # Extract fit parameters
            # Unfortunately we have to hardcode the parameter order. :(
            # And which columns they should go into.
            fit_par_dict = {
                'xc_fit': 1,  # Not used.
                'yc_fit': 2,
                'ampl': 0,
                'fwhm_x': 3,
                'fwhm_y': 4,
                'theta': 5
            }

            if is_pos_fitted_for:
                # Error par dict if xc and yc are fitted for
                err_par_dict = {
                    'xc_err': 1,
                    'yc_err': 2,
                    'ampl_err': 0,
                    'fwhm_x_err': 3,
                    'fwhm_y_err': 4,
                    'theta_err': 5
                }
            else:
                # Error par dict if xc and yc are fixed
                err_par_dict = {
                    'ampl_err': 0,
                    'fwhm_x_err': 1,
                    'fwhm_y_err': 2,
                    'theta_err': 3
                }
            self._fit_table['ampl'][idx] = fitted_mod[0].amplitude.value
            self._fit_table['xc_fit'][idx] = fitted_mod[0].x_mean.value
            self._fit_table['yc_fit'][idx] = fitted_mod[0].y_mean.value
            self._fit_table['fwhm_x'][
                idx] = sigma_to_fwhm * fitted_mod[0].x_stddev.value
            self._fit_table['fwhm_y'][
                idx] = sigma_to_fwhm * fitted_mod[0].y_stddev.value
            self._fit_table['theta'][idx] = fitted_mod[0].theta.value
            self._fit_table['fit_ok'][idx] = fit_ok
            self._fit_table['rchisq'][idx] = rchisq

            # Get the errors.
            if fit_ok:
                for col in err_par_dict:
                    paridx = err_par_dict[col]
                    if 'fwhm' in col:
                        self._fit_table[col][
                            idx] = sigma_to_fwhm * uncert_vals[paridx]
                    else:
                        self._fit_table[col][idx] = uncert_vals[paridx]

            # Calculate axis ratio and circularity.
            # Note this calculation is not quite right because the two
            # parameters are NOT independent.
            fwhm_x = self._fit_table['fwhm_x'][idx]
            fwhm_y = self._fit_table['fwhm_y'][idx]
            axrat = max(fwhm_x, fwhm_y) / min(fwhm_x, fwhm_y)
            axrat_err = 0

            if fit_ok:
                fwhm_xerr = self._fit_table['fwhm_x_err'][idx]
                fwhm_yerr = self._fit_table['fwhm_y_err'][idx]
                axrat_err = axrat * math.sqrt((fwhm_xerr / fwhm_x)**2 +
                                              (fwhm_yerr / fwhm_y)**2)

                circular = self.is_circular(fwhm_x, fwhm_y, fwhm_xerr,
                                            fwhm_yerr)
                self._fit_table['circular'][idx] = circular

            self._fit_table['axrat'][idx] = axrat
            self._fit_table['axrat_err'][idx] = axrat_err

        perf_time_end = time.perf_counter()  # highest res timer, counts sleeps
        perf_time = perf_time_end - perf_time_start  # seconds
        avg_time_ms = 1000.0 * perf_time / num_stars  # milliseconds
        self._logger.debug(
            f'Fitting completed in {perf_time:.3f} s (average {avg_time_ms:.1f} ms/star)'
        )
        if not self._quiet:
            print('Fit results:')
            print(self._fit_table)
            print('')
        return
Пример #26
0
def peak_center(img, window=0, edges=0, mask=None):
    '''Determine the center position of a peak 
    
    Parameters
    ----------
    img : array
        Image where the peak must be found
     
    window : int
        Half-size of sub-window in which to do the fit. If 0, then the fit
        is performed over the full image. Default is 0

    edges : int
        Number of pixels to hide on the edges. Default is 0

    mask : array
        Mask to apply to the data. Default is None

    Returns
    -------
    cx, cy : tuple
        Center of the peak

    '''

    img = img.copy()

    # hide edges
    if edges > 0:
        img[:edges, :] = 0
        img[:, :edges] = 0
        img[edges:, :] = 0
        img[:, edges:] = 0

    # apply mask
    if mask is not None:
        img *= mask

    # estimate peak position
    imax = np.unravel_index(np.argmax(img), img.shape)
    cx_int = imax[1]
    cy_int = imax[0]

    # sub-window
    win = window // 2
    if window > 0:
        if (window % 2):
            raise ValueError('window parameter must be even')

        sub = img[cy_int - win:cy_int + win, cx_int - win:cx_int + win]

        cx_init = win
        cy_init = win
    else:
        sub = img

        cx_init = cx_int
        cy_init = cy_int

    # fit
    x, y = np.meshgrid(np.arange(sub.shape[1]), np.arange(sub.shape[0]))
    g_init = models.Gaussian2D(amplitude=sub.max(), x_mean=cx_init, y_mean=cy_init) + \
                                          models.Const2D(amplitude=0)

    fit_g = fitting.LevMarLSQFitter()
    g = fit_g(g_init, x, y, sub)

    cx = g[0].x_mean.value - cx_init + cx_int
    cy = g[0].y_mean.value - cy_init + cy_int

    return cx, cy
Пример #27
0
def star_centers_from_waffle_img_cube(cube_cen,
                                      wave,
                                      waffle_orientation,
                                      center_guess,
                                      pixel,
                                      orientation_offset,
                                      high_pass=False,
                                      center_offset=(0, 0),
                                      box_size=16,
                                      smooth=0,
                                      coro=True,
                                      save_path=None,
                                      logger=_log):
    '''
    Compute star center from waffle images (IRDIS CI, IRDIS DBI, IFS)

    Parameters
    ----------
    cube_cen : array_like
        IRDIFS waffle cube

    wave : array_like
        Wavelength values, in nanometers

    waffle_orientation : str
        String giving the waffle orientation '+' or 'x'

    center_guess : array
        Estimation of the image center as a function of wavelength. 
        This should be an array of shape nwave*2.

    pixel : float
        Pixel scale, in mas/pixel

    orientation_offset : float
        Field orientation offset, in degrees

    high_pass : bool
        Apply high-pass filter to the image before searching for the
        satelitte spots. Default is False

    smooth : int
        Apply a gaussian smoothing to the images to reduce noise. The
        value is the sigma of the gaussian in pixel.  Default is no
        smoothing

    center_offset : tuple
        Apply an (x,y) offset to the default center position. The offset
        will move the search box of the waffle spots by the amount of
        specified pixels in each direction. Default is no offset

    box_size : int
        Size of the box in which the fit is performed. Default is 16 pixels

    coro : bool
        Observation was performed with a coronagraph. Default is True

    save_path : str
        Path where to save the fit images. Default is None, which means
        that the plot is not produced

    logger : logHandler object
        Log handler for the reduction. Default is root logger

    Returns
    -------
    spot_centers : array_like
        Centers of each individual spot in each frame of the cube

    spot_dist : array_like
        The 6 possible distances between the different spots

    img_centers : array_like
        The star center in each frame of the cube

    '''

    # standard parameters
    nwave = wave.size
    loD = wave * 1e-9 / 8 * 180 / np.pi * 3600 * 1000 / pixel

    # waffle parameters
    freq = 10 * np.sqrt(2) * 0.97
    box = box_size // 2
    if waffle_orientation == '+':
        orient = orientation_offset * np.pi / 180
    elif waffle_orientation == 'x':
        orient = orientation_offset * np.pi / 180 + np.pi / 4

    # spot fitting
    xx, yy = np.meshgrid(np.arange(2 * box), np.arange(2 * box))

    # multi-page PDF to save result
    if save_path is not None:
        pdf = PdfPages(save_path)

    # loop over images
    spot_centers = np.zeros((nwave, 4, 2))
    spot_dist = np.zeros((nwave, 6))
    img_centers = np.zeros((nwave, 2))
    for idx, (wave, img) in enumerate(zip(wave, cube_cen)):
        logger.info('   ==> wave {0:2d}/{1:2d} ({2:4.0f} nm)'.format(
            idx + 1, nwave, wave))

        # remove any NaN
        img = np.nan_to_num(img)

        # center guess (+offset)
        cx_int = int(center_guess[idx, 0]) + center_offset[0]
        cy_int = int(center_guess[idx, 1]) + center_offset[1]

        # optional high-pass filter
        if high_pass:
            img = img - ndimage.median_filter(img, 15, mode='mirror')

        # optional smoothing
        if smooth > 0:
            img = ndimage.gaussian_filter(img, smooth)

        # mask for non-coronagraphic observations
        if not coro:
            mask = aperture.disc(cube_cen[0].shape[-1],
                                 5 * loD[idx],
                                 diameter=False,
                                 center=(cx_int, cy_int),
                                 invert=True)
            img *= mask

        # create plot if needed
        if save_path:
            fig = plt.figure('Waffle center - imaging', figsize=(8.3, 8))
            plt.clf()

            if high_pass:
                norm = colors.PowerNorm(gamma=1, vmin=-1e-1, vmax=1e-1)
            else:
                norm = colors.LogNorm(vmin=1e-2, vmax=1)

            col = ['green', 'blue', 'deepskyblue', 'purple']
            ax = fig.add_subplot(111)
            ax.imshow(img / img.max(),
                      aspect='equal',
                      norm=norm,
                      interpolation='nearest',
                      cmap=global_cmap)
            ax.set_title(r'Image #{0} - {1:.0f} nm'.format(idx + 1, wave))
            ax.set_xlabel('x position [pix]')
            ax.set_ylabel('y position [pix]')

        # satelitte spots
        for s in range(4):
            cx = int(cx_int + freq * loD[idx] * np.cos(orient + np.pi / 2 * s))
            cy = int(cy_int + freq * loD[idx] * np.sin(orient + np.pi / 2 * s))

            sub = img[cy - box:cy + box, cx - box:cx + box]

            # bounds for fitting: spots slightly outside of the box are allowed
            gbounds = {
                'amplitude': (0.0, None),
                'x_mean': (-2.0, box * 2 + 2),
                'y_mean': (-2.0, box * 2 + 2),
                'x_stddev': (1.0, 20.0),
                'y_stddev': (1.0, 20.0)
            }

            # fit: Gaussian + constant
            imax = np.unravel_index(np.argmax(sub), sub.shape)
            g_init = models.Gaussian2D(amplitude=sub.max(), x_mean=imax[1], y_mean=imax[0],
                                       x_stddev=loD[idx], y_stddev=loD[idx], bounds=gbounds) + \
                                       models.Const2D(amplitude=sub.min())
            fitter = fitting.LevMarLSQFitter()
            par = fitter(g_init, xx, yy, sub)
            fit = par(xx, yy)

            cx_final = cx - box + par[0].x_mean
            cy_final = cy - box + par[0].y_mean

            spot_centers[idx, s, 0] = cx_final
            spot_centers[idx, s, 1] = cy_final

            # plot sattelite spots and fit
            if save_path:
                ax.plot([cx_final], [cy_final],
                        marker='D',
                        color=col[s],
                        zorder=1000)
                ax.add_patch(
                    patches.Rectangle((cx - box, cy - box),
                                      2 * box,
                                      2 * box,
                                      ec='white',
                                      fc='none'))

                axs = fig.add_axes((0.17 + s * 0.2, 0.17, 0.1, 0.1))
                axs.imshow(sub,
                           aspect='equal',
                           vmin=0,
                           vmax=sub.max(),
                           interpolation='nearest',
                           cmap=global_cmap)
                axs.plot([par[0].x_mean.value], [par[0].y_mean.value],
                         marker='D',
                         color=col[s])
                axs.set_xticks([])
                axs.set_yticks([])

                axs = fig.add_axes((0.17 + s * 0.2, 0.06, 0.1, 0.1))
                axs.imshow(fit,
                           aspect='equal',
                           vmin=0,
                           vmax=sub.max(),
                           interpolation='nearest',
                           cmap=global_cmap)
                axs.set_xticks([])
                axs.set_yticks([])

        # lines intersection
        intersect = lines_intersect(spot_centers[idx, 0, :],
                                    spot_centers[idx, 2, :],
                                    spot_centers[idx,
                                                 1, :], spot_centers[idx,
                                                                     3, :])
        img_centers[idx] = intersect

        # scaling
        spot_dist[idx, 0] = np.sqrt(
            np.sum((spot_centers[idx, 0, :] - spot_centers[idx, 2, :])**2))
        spot_dist[idx, 1] = np.sqrt(
            np.sum((spot_centers[idx, 1, :] - spot_centers[idx, 3, :])**2))
        spot_dist[idx, 2] = np.sqrt(
            np.sum((spot_centers[idx, 0, :] - spot_centers[idx, 1, :])**2))
        spot_dist[idx, 3] = np.sqrt(
            np.sum((spot_centers[idx, 0, :] - spot_centers[idx, 3, :])**2))
        spot_dist[idx, 4] = np.sqrt(
            np.sum((spot_centers[idx, 1, :] - spot_centers[idx, 2, :])**2))
        spot_dist[idx, 5] = np.sqrt(
            np.sum((spot_centers[idx, 2, :] - spot_centers[idx, 3, :])**2))

        # finalize plot
        if save_path:
            ax.plot([spot_centers[idx, 0, 0], spot_centers[idx, 2, 0]],
                    [spot_centers[idx, 0, 1], spot_centers[idx, 2, 1]],
                    color='w',
                    linestyle='dashed',
                    zorder=900)
            ax.plot([spot_centers[idx, 1, 0], spot_centers[idx, 3, 0]],
                    [spot_centers[idx, 1, 1], spot_centers[idx, 3, 1]],
                    color='w',
                    linestyle='dashed',
                    zorder=900)

            ax.plot([intersect[0]], [intersect[1]],
                    marker='+',
                    color='w',
                    ms=15)

            ext = 1000 / pixel
            ax.set_xlim(intersect[0] - ext, intersect[0] + ext)
            ax.set_ylim(intersect[1] - ext, intersect[1] + ext)

            plt.subplots_adjust(left=0.1, right=0.98, bottom=0.1, top=0.95)

            if save_path:
                pdf.savefig()

    if save_path:
        pdf.close()

    return spot_centers, spot_dist, img_centers
Пример #28
0
def prepare_psf_model(psfmodel,
                      xname=None,
                      yname=None,
                      fluxname=None,
                      renormalize_psf=True):
    """
    Convert a 2D PSF model to one suitable for use with
    `BasicPSFPhotometry` or its subclasses.

    The resulting model may be a composite model, but should have only
    the x, y, and flux related parameters un-fixed.

    Parameters
    ----------
    psfmodel : a 2D model
        The model to assume as representative of the PSF.
    xname : str or None
        The name of the ``psfmodel`` parameter that corresponds to the
        x-axis center of the PSF.  If None, the model will be assumed to
        be centered at x=0, and a new parameter will be added for the
        offset.
    yname : str or None
        The name of the ``psfmodel`` parameter that corresponds to the
        y-axis center of the PSF.  If None, the model will be assumed to
        be centered at y=0, and a new parameter will be added for the
        offset.
    fluxname : str or None
        The name of the ``psfmodel`` parameter that corresponds to the
        total flux of the star.  If None, a scaling factor will be added
        to the model.
    renormalize_psf : bool
        If True, the model will be integrated from -inf to inf and
        re-scaled so that the total integrates to 1.  Note that this
        renormalization only occurs *once*, so if the total flux of
        ``psfmodel`` depends on position, this will *not* be correct.

    Returns
    -------
    outmod : a model
        A new model ready to be passed into `BasicPSFPhotometry` or its
        subclasses.
    """

    if xname is None:
        xinmod = models.Shift(0, name='x_offset')
        xname = 'offset_0'
    else:
        xinmod = models.Identity(1)
        xname = xname + '_2'
    xinmod.fittable = True

    if yname is None:
        yinmod = models.Shift(0, name='y_offset')
        yname = 'offset_1'
    else:
        yinmod = models.Identity(1)
        yname = yname + '_2'
    yinmod.fittable = True

    outmod = (xinmod & yinmod) | psfmodel

    if fluxname is None:
        outmod = outmod * models.Const2D(1, name='flux_scaling')
        fluxname = 'amplitude_3'
    else:
        fluxname = fluxname + '_2'

    if renormalize_psf:
        # we do the import here because other machinery works w/o scipy
        from scipy import integrate

        integrand = integrate.dblquad(psfmodel, -np.inf, np.inf,
                                      lambda x: -np.inf, lambda x: np.inf)[0]
        normmod = models.Const2D(1. / integrand, name='renormalize_scaling')
        outmod = outmod * normmod

    # final setup of the output model - fix all the non-offset/scale
    # parameters
    for pnm in outmod.param_names:
        outmod.fixed[pnm] = pnm not in (xname, yname, fluxname)

    # and set the names so that BasicPSFPhotometry knows what to do
    outmod.xname = xname
    outmod.yname = yname
    outmod.fluxname = fluxname

    # now some convenience aliases if reasonable
    outmod.psfmodel = outmod[2]
    if 'x_0' not in outmod.param_names and 'y_0' not in outmod.param_names:
        outmod.x_0 = getattr(outmod, xname)
        outmod.y_0 = getattr(outmod, yname)
    if 'flux' not in outmod.param_names:
        outmod.flux = getattr(outmod, fluxname)

    return outmod
Пример #29
0
def fit_alignment_box(region, box_size=30, verbose=False, seeing=None,
                      medfilt=False):
    pixelscale = u.pixel_scale(0.1798*u.arcsec/u.pixel)
    if medfilt is True:
        region = median_filter(region, size=(3,3))

    # Estimate center of alignment box
    threshold_pct = 80
    window = region.data > np.percentile(region.data, threshold_pct)
    alignment_box_position = ndimage.measurements.center_of_mass(window)

    offset_val = np.median(region.data[~window])
    offset = models.Const2D(offset_val)

    # Determine fluctuations in sky
    sky_amplitude = np.median(region.data[window])
    sky_fluctuations = np.std(region.data[window])

    # Detect box edges
    gradx = np.gradient(region.data, axis=1)
    horizontal_profile = np.sum(gradx, axis=0)
    h_edges = fit_CSU_edges(horizontal_profile)
    grady = np.gradient(region.data, axis=0)
    vertical_profile = np.sum(grady, axis=1)
    v_edges = fit_CSU_edges(vertical_profile)

    # Estimate stellar position
    maxr = np.max(region.data)
    starloc = (np.where(region == maxr)[0][0],
               np.where(region == maxr)[1][0])

    # Build model of sky, star, & box
    boxamplitude = 1

    box = mosfireAlignmentBox(boxamplitude, alignment_box_position[1], alignment_box_position[0],\
                       abs(h_edges[0]-h_edges[1]), abs(v_edges[0]-v_edges[1]))
    box.amplitude.fixed = True
    box.x_width.min = 10
    box.y_width.min = 10

    sky = models.Const2D(sky_amplitude)
    sky.amplitude.min = 0

    star_amplitude = maxr - sky_amplitude
    star_sigma = star_amplitude / sky_fluctuations
    if star_sigma < 5:
        if verbose: print(f'No star detected.  sigma={star_sigma:.1f}')
        return [None]*4
    else:
        if verbose: print(f'Detected peak pixel {star_sigma:.1f} sigma above sky.')
    star = models.Gaussian2D(amplitude=star_amplitude,
                             x_mean=starloc[1], y_mean=starloc[0],
                             x_stddev=2, y_stddev=2)
#     print(h_edges)
#     print(v_edges)
#     star.y_mean.min = v_edges[0]
#     star.y_mean.max = v_edges[1]
#     star.x_mean.min = h_edges[0]
#     star.x_mean.max = h_edges[1]
    star.amplitude.min = 5*sky_fluctuations
    star.x_stddev.min = 1 # FWHM = 2.355*stddev = 0.42 arcsec FWHM
    star.x_stddev.max = 4 # FWHM = 2.355*stddev = 1.47 arcsec FWHM
    star.y_stddev.min = 1
    star.y_stddev.max = 4

    if seeing is not None and seeing > 0:
        sigma = (seeing / 2.355 * u.arcsec).to(u.pixel, equivalencies=pixelscale)
        star.x_stddev.min = max(2, sigma.value-1)
        star.y_stddev.min = max(2, sigma.value-1)
        star.x_stddev.max = min(sigma.value+1, 4)
        star.y_stddev.max = min(sigma.value+1, 4)
#         print(f"Using seeing value {seeing} arcsec. sigma limits {star.x_stddev.min}, {star.x_stddev.max} pix")

    model = box*(sky + star) + offset

#     modelim = np.zeros((61,61))
#     fitim = np.zeros((61,61))
#     for i in range(0,60):
#         for j in range(0,60):
#             modelim[j,i] = model(i,j)
#             fitim[j,i] = model(i,j)
#     residuals = region.data-fitim
#     residualsum = np.sum(residuals)
#     import pdb ; pdb.set_trace()

    fitter = fitting.LevMarLSQFitter()
    y, x = np.mgrid[:2*box_size+1, :2*box_size+1]
    fit = fitter(model, x, y, region.data)

    FWHMx = 2*(2*np.log(2))**0.5*fit.x_stddev_2.value * u.pix
    FWHMy = 2*(2*np.log(2))**0.5*fit.y_stddev_2.value * u.pix
    FWHM = (FWHMx**2 + FWHMy**2)**0.5/2**0.5
    FWHMarcsec = FWHM.to(u.arcsec, equivalencies=pixelscale)
    sky_amplitude = fit.amplitude_1.value
    star_flux = 2*np.pi*fit.amplitude_2.value*fit.x_stddev_2.value*fit.y_stddev_2.value
    star_amplitude = fit.amplitude_2.value
    boxpos_x = fit.x_0_0.value
    boxpos_y = fit.y_0_0.value
    star_x = fit.x_mean_2.value
    star_y = fit.y_mean_2.value

    if verbose: print(f"  Box X Center = {boxpos_x:.0f}")
    if verbose: print(f"  Box Y Center = {boxpos_y:.0f}")
    if verbose: print(f"  Sky Brightness = {fit.amplitude_1.value:.0f} ADU")
    if verbose: print(f"  Stellar FWHM = {FWHMarcsec:.2f}")
    if verbose: print(f"  Stellar Xpos = {star_x:.0f}")
    if verbose: print(f"  Stellar Xpos = {star_y:.0f}")
    if verbose: print(f"  Stellar Amplitude = {star_amplitude:.0f} ADU")
    if verbose: print(f"  Stellar Flux (fit) = {star_flux:.0f} ADU")

    result = {'Star X': star_x,
              'Star Y': star_y,
              'Star Amplitude': star_amplitude,
              'Sky Amplitude': sky_amplitude,
              'FWHM pix': FWHM.value,
              'FWHM arcsec': FWHMarcsec,
              'Box X': boxpos_x,
              'Box Y': boxpos_y,
#               'Residuals': residuals,
             }
    return result
Пример #30
0
def star_centers_from_PSF_img_cube(cube,
                                   wave,
                                   pixel,
                                   exclude_fraction=0.1,
                                   high_pass=False,
                                   box_size=60,
                                   save_path=None,
                                   logger=_log):
    '''
    Compute star center from PSF images (IRDIS CI, IRDIS DBI, IFS)

    Parameters
    ----------
    cube : array_like
        IRDIFS PSF cube

    wave : array_like
        Wavelength values, in nanometers

    pixel : float
        Pixel scale, in mas/pixel

    exclude_fraction : float
        Exclude a fraction of the image borders to avoid getting
        biased by hot pixels close to the edges. Default is 10%

    high_pass : bool
        Apply high-pass filter to the PSF image before searching for the center.
        Default is False

    box_size : int
        Size of the box in which the fit is performed. Default is 60 pixels

    save_path : str
        Path where to save the fit images. Default is None, which means
        that the plot is not produced

    logger : logHandler object
        Log handler for the reduction. Default is root logger

    Returns
    -------
    img_centers : array_like
        The star center in each frame of the cube

    '''

    # standard parameters
    nwave = wave.size
    loD = wave * 1e-9 / 8 * 180 / np.pi * 3600 * 1000 / pixel
    box = box_size // 2

    # spot fitting
    xx, yy = np.meshgrid(np.arange(2 * box), np.arange(2 * box))

    # loop over images
    img_centers = np.zeros((nwave, 2))
    failed_centers = np.zeros(nwave, dtype=np.bool)
    for idx, (cwave, img) in enumerate(zip(wave, cube)):
        logger.info('   ==> wave {0:2d}/{1:2d} ({2:4.0f} nm)'.format(
            idx + 1, nwave, cwave))

        # remove any NaN
        img = np.nan_to_num(cube[idx])

        # optional high-pass filter
        if high_pass:
            img = img - ndimage.median_filter(img, 15, mode='mirror')
            cube[idx] = img

        # center guess
        cy, cx = np.unravel_index(np.argmax(img), img.shape)

        # check if we are really too close to the edge
        dim = img.shape
        lf = exclude_fraction
        hf = 1 - exclude_fraction
        if (cx <= lf*dim[-1]) or (cx >= hf*dim[-1]) or \
           (cy <= lf*dim[0]) or (cy >= hf*dim[0]):
            nimg = img.copy()
            nimg[:, :int(lf * dim[-1])] = 0
            nimg[:, int(hf * dim[-1]):] = 0
            nimg[:int(lf * dim[0]), :] = 0
            nimg[int(hf * dim[0]):, :] = 0

            cy, cx = np.unravel_index(np.argmax(nimg), img.shape)

        # sub-image
        sub = img[cy - box:cy + box, cx - box:cx + box]

        # fit peak with Gaussian + constant
        imax = np.unravel_index(np.argmax(sub), sub.shape)
        g_init = models.Gaussian2D(amplitude=sub.max(), x_mean=imax[1], y_mean=imax[0],
                                   x_stddev=loD[idx], y_stddev=loD[idx]) + \
                                   models.Const2D(amplitude=sub.min())
        fitter = fitting.LevMarLSQFitter()
        par = fitter(g_init, xx, yy, sub)

        cx_final = cx - box + par[0].x_mean
        cy_final = cy - box + par[0].y_mean

        img_centers[idx, 0] = cx_final
        img_centers[idx, 1] = cy_final

    # look for outliers and replace by a linear fit to all good ones
    # Ticket #81
    ibad = []
    if nwave > 2:
        c_med = np.median(img_centers, axis=0)
        c_std = np.std(img_centers, axis=0)
        bad = np.any(np.logical_or(img_centers < (c_med - 3 * c_std),
                                   img_centers > (c_med + 3 * c_std)),
                     axis=1)
        ibad = np.where(bad)[0]
        igood = np.where(np.logical_not(bad))[0]
        if len(ibad) != 0:
            logger.info(
                f'   ==> found {len(ibad)} outliers. Will replace them with a linear fit.'
            )

            idx = np.arange(nwave)

            # x
            lin = np.polyfit(idx[igood], img_centers[igood, 0], 1)
            pol = np.poly1d(lin)
            img_centers[ibad, 0] = pol(idx[ibad])

            # y
            lin = np.polyfit(idx[igood], img_centers[igood, 1], 1)
            pol = np.poly1d(lin)
            img_centers[ibad, 1] = pol(idx[ibad])

    #
    # Generate summary plot
    #

    # multi-page PDF to save result
    if save_path is not None:
        pdf = PdfPages(save_path)

        for idx, (cwave, img) in enumerate(zip(wave, cube)):
            cx_final = img_centers[idx, 0]
            cy_final = img_centers[idx, 1]

            failed = (idx in ibad)
            if failed:
                mcolor = 'r'
                bcolor = 'r'
            else:
                mcolor = 'b'
                bcolor = 'w'

            plt.figure('PSF center - imaging', figsize=(8.3, 8))
            plt.clf()

            plt.subplot(111)
            plt.imshow(img / np.nanmax(img),
                       aspect='equal',
                       norm=colors.LogNorm(vmin=1e-6, vmax=1),
                       interpolation='nearest',
                       cmap=global_cmap)
            plt.plot([cx_final], [cy_final], marker='D', color=mcolor)
            plt.gca().add_patch(
                patches.Rectangle((cx - box, cy - box),
                                  2 * box,
                                  2 * box,
                                  ec=bcolor,
                                  fc='none'))
            if failed:
                plt.text(cx,
                         cy + box,
                         'Fit failed',
                         color='r',
                         weight='bold',
                         fontsize='x-small',
                         ha='center',
                         va='bottom')
            plt.title(r'Image #{0} - {1:.0f} nm'.format(idx + 1, cwave))

            ext = 1000 / pixel
            plt.xlim(cx_final - ext, cx_final + ext)
            plt.xlabel('x position [pix]')
            plt.ylim(cy_final - ext, cy_final + ext)
            plt.ylabel('y position [pix]')

            plt.subplots_adjust(left=0.1, right=0.98, bottom=0.1, top=0.95)

            pdf.savefig()

        pdf.close()

    return img_centers