Ejemplo n.º 1
0
def Isophote_Initialize(IMG, results, options):
    """
    Determine the global pa and ellipticity for a galaxy. First grow circular isophotes
    until reaching near the noise floor, then evaluate the phase of the second FFT
    coefficients and determine the average direction. Then fit an ellipticity for one
    of the outer isophotes.
    """

    ######################################################################
    # Initial attempt to find size of galaxy in image
    # based on when isophotes SB values start to get
    # close to the background noise level
    circ_ellipse_radii = [results['psf fwhm']]
    allphase = []
    dat = IMG - results['background']

    while circ_ellipse_radii[-1] < (len(IMG) / 2):
        circ_ellipse_radii.append(circ_ellipse_radii[-1] * (1 + 0.2))
        isovals = _iso_extract(dat,
                               circ_ellipse_radii[-1],
                               0.,
                               0.,
                               results['center'],
                               more=True,
                               sigmaclip=True,
                               sclip_nsigma=3,
                               interp_mask=True)
        coefs = fft(isovals[0])
        allphase.append(coefs[2])
        # Stop when at 3 time background noise
        if np.quantile(isovals[0], 0.8) < (3 * results['background noise']
                                           ) and len(circ_ellipse_radii) > 4:
            break
    logging.info('%s: init scale: %f pix' %
                 (options['ap_name'], circ_ellipse_radii[-1]))
    # Find global position angle.
    phase = (-Angle_Median(np.angle(allphase[-5:])) / 2) % np.pi

    # Find global ellipticity
    test_ellip = np.linspace(0.05, 0.95, 15)
    test_f2 = []
    for e in test_ellip:
        test_f2.append(
            sum(
                list(
                    _fitEllip_loss(e, dat, circ_ellipse_radii[-2] *
                                   m, phase, results['center'],
                                   results['background noise'])
                    for m in np.linspace(0.8, 1.2, 5))))
    ellip = test_ellip[np.argmin(test_f2)]
    res = minimize(lambda e, d, r, p, c, n: sum(
        list(
            _fitEllip_loss(_x_to_eps(e[0]), d, r * m, p, c, n)
            for m in np.linspace(0.8, 1.2, 5))),
                   x0=_inv_x_to_eps(ellip),
                   args=(dat, circ_ellipse_radii[-2], phase, results['center'],
                         results['background noise']),
                   method='Nelder-Mead',
                   options={
                       'initial_simplex': [[_inv_x_to_eps(ellip) - 1 / 15],
                                           [_inv_x_to_eps(ellip) + 1 / 15]]
                   })
    if res.success:
        logging.debug(
            '%s: using optimal ellipticity %.3f over grid ellipticity %.3f' %
            (options['ap_name'], _x_to_eps(res.x[0]), ellip))
        ellip = _x_to_eps(res.x[0])

    # Compute the error on the parameters
    ######################################################################
    RR = np.linspace(circ_ellipse_radii[-2] - results['psf fwhm'],
                     circ_ellipse_radii[-2] + results['psf fwhm'], 10)
    errallphase = []
    for rr in RR:
        isovals = _iso_extract(dat,
                               rr,
                               0.,
                               0.,
                               results['center'],
                               more=True,
                               sigmaclip=True,
                               sclip_nsigma=3,
                               interp_mask=True)
        coefs = fft(isovals[0])
        errallphase.append(coefs[2])
    sample_pas = (-np.angle(1j * np.array(errallphase) / np.mean(errallphase))
                  / 2) % np.pi
    pa_err = iqr(sample_pas, rng=[16, 84]) / 2
    res_multi = map(
        lambda rrp: minimize(lambda e, d, r, p, c, n: _fitEllip_loss(
            _x_to_eps(e[0]), d, r, p, c, n),
                             x0=_inv_x_to_eps(ellip),
                             args=(dat, rrp[0], rrp[1], results['center'],
                                   results['background noise']),
                             method='Nelder-Mead',
                             options={
                                 'initial_simplex': [[
                                     _inv_x_to_eps(ellip) - 1 / 15
                                 ], [_inv_x_to_eps(ellip) + 1 / 15]]
                             }), zip(RR, sample_pas))
    ellip_err = iqr(list(_x_to_eps(rm.x[0])
                         for rm in res_multi), rng=[16, 84]) / 2

    circ_ellipse_radii = np.array(circ_ellipse_radii)

    if 'ap_doplot' in options and options['ap_doplot']:

        ranges = [
            [
                max(0,
                    int(results['center']['x'] -
                        circ_ellipse_radii[-1] * 1.5)),
                min(dat.shape[1],
                    int(results['center']['x'] + circ_ellipse_radii[-1] * 1.5))
            ],
            [
                max(0,
                    int(results['center']['y'] -
                        circ_ellipse_radii[-1] * 1.5)),
                min(dat.shape[0],
                    int(results['center']['y'] + circ_ellipse_radii[-1] * 1.5))
            ]
        ]

        LSBImage(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]],
                 results['background noise'])
        # plt.imshow(np.clip(dat[ranges[1][0]: ranges[1][1], ranges[0][0]: ranges[0][1]],a_min = 0, a_max = None),
        #            origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch()))
        plt.gca().add_patch(
            Ellipse((results['center']['x'] - ranges[0][0],
                     results['center']['y'] - ranges[1][0]),
                    2 * circ_ellipse_radii[-1],
                    2 * circ_ellipse_radii[-1] * (1. - ellip),
                    phase * 180 / np.pi,
                    fill=False,
                    linewidth=1,
                    color='y'))
        plt.plot([results['center']['x'] - ranges[0][0]],
                 [results['center']['y'] - ranges[1][0]],
                 marker='x',
                 markersize=3,
                 color='r')
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%sinitialize_ellipse_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

        fig, ax = plt.subplots(2, 1, figsize=(6, 6))
        plt.subplots_adjust(hspace=0.01, wspace=0.01)
        ax[0].plot(circ_ellipse_radii[:-1],
                   ((-np.angle(allphase) / 2) % np.pi) * 180 / np.pi,
                   color='k')
        ax[0].axhline(phase * 180 / np.pi, color='r')
        ax[0].axhline((phase + pa_err) * 180 / np.pi,
                      color='r',
                      linestyle='--')
        ax[0].axhline((phase - pa_err) * 180 / np.pi,
                      color='r',
                      linestyle='--')
        #ax[0].axvline(circ_ellipse_radii[-2], color = 'orange', linestyle = '--')
        ax[0].set_xlabel('Radius [pix]', fontsize=16)
        ax[0].set_ylabel('FFT$_{1}$ phase [deg]', fontsize=16)
        ax[0].tick_params(labelsize=12)
        ax[1].plot(test_ellip, test_f2, color='k')
        ax[1].axvline(ellip, color='r')
        ax[1].axvline(ellip + ellip_err, color='r', linestyle='--')
        ax[1].axvline(ellip - ellip_err, color='r', linestyle='--')
        ax[1].set_xlabel('Ellipticity [1 - b/a]', fontsize=16)
        ax[1].set_ylabel('Loss [FFT$_{2}$/med(flux)]', fontsize=16)
        ax[1].tick_params(labelsize=14)
        plt.tight_layout()
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%sinitialize_ellipse_optimize_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

    auxmessage = 'global ellipticity: %.3f +- %.3f, pa: %.3f +- %.3f deg, size: %f pix' % (
        ellip, ellip_err, PA_shift_convention(phase) * 180 / np.pi,
        pa_err * 180 / np.pi, circ_ellipse_radii[-2])
    return IMG, {
        'init ellip': ellip,
        'init ellip_err': ellip_err,
        'init pa': phase,
        'init pa_err': pa_err,
        'init R': circ_ellipse_radii[-2],
        'auxfile initialize': auxmessage
    }
Ejemplo n.º 2
0
def Center_HillClimb_mean(IMG, results, options):
    """
    Using 10 circular isophotes out to 10 times the PSF length, the first FFT coefficient
    phases are averaged to find the direction of increasing flux. Flux values are sampled
    along this direction and a quadratic fit gives the maximum. This is iteratively
    repeated until the step size becomes very small.
    """
    current_center = {'x': IMG.shape[0]/2, 'y': IMG.shape[1]/2}

    current_center = {'x': IMG.shape[1]/2, 'y': IMG.shape[0]/2}
    if 'ap_guess_center' in options:
        current_center = deepcopy(options['ap_guess_center'])
        logging.info('%s: Center initialized by user: %s' % (options['ap_name'], str(current_center)))
    if 'ap_set_center' in options:
        logging.info('%s: Center set by user: %s' % (options['ap_name'], str(options['ap_set_center'])))
        return IMG, {'center': deepcopy(options['ap_set_center'])}

    dat = IMG - results['background']

    sampleradii = np.linspace(1,10,10) * results['psf fwhm']

    track_centers = []
    small_update_count = 0
    total_count = 0
    while small_update_count <= 5 and total_count <= 100:
        total_count += 1
        phases = []
        isovals = []
        coefs = []
        for r in sampleradii:
            isovals.append(_iso_extract(dat,r,0.,0.,current_center, more = True))
            coefs.append(fft(isovals[-1][0]))
            phases.append((-np.angle(coefs[-1][1])) % (2*np.pi))
        direction = Angle_Median(phases) % (2*np.pi)
        levels = []
        level_locs = []
        for i, r in enumerate(sampleradii):
            floc = np.argmin(np.abs(isovals[i][1] - direction))
            rloc = np.argmin(np.abs(isovals[i][1] - ((direction+np.pi) % (2*np.pi))))
            smooth = np.abs(ifft(coefs[i][:min(10,len(coefs[i]))],n = len(coefs[i])))
            levels.append(smooth[floc])
            level_locs.append(r)
            levels.insert(0,smooth[rloc])
            level_locs.insert(0,-r)
        try:
            p = np.polyfit(level_locs, levels, deg = 2)
            if p[0] < 0 and len(levels) > 3:
                dist = np.clip(-p[1]/(2*p[0]), a_min = min(level_locs), a_max = max(level_locs))
            else:
                dist = level_locs[np.argmax(levels)]
        except:
            dist = results['psf fwhm']
        current_center['x'] += dist*np.cos(direction)
        current_center['y'] += dist*np.sin(direction)
        if abs(dist) < (0.25*results['psf fwhm']):
            small_update_count += 1
        else:
            small_update_count = 0
        track_centers.append([current_center['x'], current_center['y']])

    # refine center
    res = minimize(_hillclimb_loss, x0 =  [current_center['x'], current_center['y']], args = (dat, results['psf fwhm'], results['background noise']), method = 'Nelder-Mead')
    if res.success:
        current_center['x'] = res.x[0]
        current_center['y'] = res.x[1]
        
    return IMG, {'center': current_center, 'auxfile center': 'center x: %.2f pix, y: %.2f pix' % (current_center['x'], current_center['y'])}
Ejemplo n.º 3
0
def Center_HillClimb(IMG, results, options):
    """
    Using 10 circular isophotes out to 10 times the PSF length, the first FFT coefficient
    phases are averaged to find the direction of increasing flux. Flux values are sampled
    along this direction and a quadratic fit gives the maximum. This is iteratively
    repeated until the step size becomes very small.
    """
    
    current_center = {'x': IMG.shape[1]/2, 'y': IMG.shape[0]/2}
    if 'ap_guess_center' in options:
        current_center = deepcopy(options['ap_guess_center'])
        logging.info('%s: Center initialized by user: %s' % (options['ap_name'], str(current_center)))
    if 'ap_set_center' in options:
        logging.info('%s: Center set by user: %s' % (options['ap_name'], str(options['ap_set_center'])))
        return IMG, {'center': deepcopy(options['ap_set_center'])}

    dat = IMG - results['background']

    sampleradii = np.linspace(1,10,10) * results['psf fwhm']

    track_centers = []
    small_update_count = 0
    total_count = 0
    while small_update_count <= 5 and total_count <= 100:
        total_count += 1
        phases = []
        isovals = []
        coefs = []
        for r in sampleradii:
            isovals.append(_iso_extract(dat,r,0.,0.,current_center, more = True))
            coefs.append(fft(np.clip(isovals[-1][0], a_max = np.quantile(isovals[-1][0],0.85), a_min = None)))
            phases.append((-np.angle(coefs[-1][1])) % (2*np.pi))
        direction = Angle_Median(phases) % (2*np.pi)
        levels = []
        level_locs = []
        for i, r in enumerate(sampleradii):
            floc = np.argmin(np.abs((isovals[i][1] % (2*np.pi)) - direction))
            rloc = np.argmin(np.abs((isovals[i][1] % (2*np.pi)) - ((direction+np.pi) % (2*np.pi))))
            smooth = np.abs(ifft(coefs[i][:min(10,len(coefs[i]))],n = len(coefs[i])))
            levels.append(smooth[floc])
            level_locs.append(r)
            levels.insert(0,smooth[rloc])
            level_locs.insert(0,-r)
        try:
            p = np.polyfit(level_locs, levels, deg = 2)
            if p[0] < 0 and len(levels) > 3:
                dist = np.clip(-p[1]/(2*p[0]), a_min = min(level_locs), a_max = max(level_locs))
            else:
                dist = level_locs[np.argmax(levels)]
        except:
            dist = results['psf fwhm']
        current_center['x'] += dist*np.cos(direction)
        current_center['y'] += dist*np.sin(direction)
        if abs(dist) < (0.25*results['psf fwhm']):
            small_update_count += 1
        else:
            small_update_count = 0
        track_centers.append([current_center['x'], current_center['y']])

    # refine center
    ranges = [[max(0,int(current_center['x']-results['psf fwhm']*5)), min(dat.shape[1],int(current_center['x']+results['psf fwhm']*5))],
              [max(0,int(current_center['y']-results['psf fwhm']*5)), min(dat.shape[0],int(current_center['y']+results['psf fwhm']*5))]]

    res = minimize(_hillclimb_loss, x0 =  [current_center['x'] - ranges[0][0], current_center['y'] - ranges[1][0]],
                   args = (dat[ranges[1][0]:ranges[1][1],ranges[0][0]:ranges[0][1]], results['psf fwhm'], results['background noise']), method = 'Nelder-Mead')
    if res.success:
        current_center['x'] = res.x[0] + ranges[0][0]
        current_center['y'] = res.x[1] + ranges[1][0]
    track_centers.append([current_center['x'], current_center['y']])
    # paper plot
    if 'ap_paperplots' in options and options['ap_paperplots']:    
        plt.imshow(np.clip(dat,a_min = 0, a_max = None), origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch()))
        plt.plot([current_center['x']],[current_center['y']], marker = 'x', markersize = 3, color = 'r')
        for i in range(3):
            plt.gca().add_patch(Ellipse((current_center['x'],current_center['y']), 2*((i+0.5)*results['psf fwhm']),
                                        2*((i+0.5)*results['psf fwhm']),
                                        0, fill = False, linewidth = 0.5, color = 'y'))
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig('%stest_center_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']), dpi = options['ap_plotdpi'] if 'ap_plotdpi'in options else 300)
        plt.close()
        track_centers = np.array(track_centers)
        xwidth = 2*max(np.abs(track_centers[:,0] - current_center['x']))
        ywidth = 2*max(np.abs(track_centers[:,1] - current_center['y']))
        width = max(xwidth, ywidth)
        ranges = [[int(current_center['x'] - width), int(current_center['x'] + width)],
                  [int(current_center['y'] - width), int(current_center['y'] + width)]]
        fig, axarr = plt.subplots(2,1, figsize = (3,6))
        plt.subplots_adjust(hspace = 0.01, wspace = 0.01, left = 0.05, right = 0.95, top = 0.95, bottom = 0.05)
        axarr[0].imshow(np.clip(dat[ranges[1][0]:ranges[1][1],ranges[0][0]:ranges[0][1]],a_min = 0, a_max = None),
                        origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch()),
                        extent = [ranges[0][0],ranges[0][1],ranges[1][0]-1,ranges[1][1]-1])
        axarr[0].plot(track_centers[:,0], track_centers[:,1], color = 'y')
        axarr[0].scatter(track_centers[:,0], track_centers[:,1], c = range(len(track_centers)), cmap = 'Reds')
        axarr[0].set_xticks([])
        axarr[0].set_yticks([])        
        width = 10.
        ranges = [[int(current_center['x'] - width), int(current_center['x'] + width)],
                  [int(current_center['y'] - width), int(current_center['y'] + width)]]
        axarr[1].imshow(np.clip(dat[ranges[1][0]:ranges[1][1],ranges[0][0]:ranges[0][1]],a_min = 0, a_max = None),
                        origin = 'lower', cmap = 'Greys_r',
                        extent = [ranges[0][0],ranges[0][1],ranges[1][0]-1,ranges[1][1]-1])
        axarr[1].plot(track_centers[:,0], track_centers[:,1], color = 'y')
        axarr[1].scatter(track_centers[:,0], track_centers[:,1], c = range(len(track_centers)), cmap = 'Reds')
        axarr[1].set_xlim(ranges[0])
        axarr[1].set_ylim(np.array(ranges[1])-1)        
        axarr[1].set_xticks([])
        axarr[1].set_yticks([])
        plt.savefig('%sCenter_path_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']), dpi = options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()
        
    return IMG, {'center': current_center, 'auxfile center': 'center x: %.2f pix, y: %.2f pix' % (current_center['x'], current_center['y'])}
Ejemplo n.º 4
0
def Isophote_Initialize_mean(IMG, results, options):
    """Fit global elliptical isophote to a galaxy image using FFT coefficients.

    Same as the default isophote initialization routine, except uses
    mean/std measures for low S/N applications.

    Parameters
    -----------------
    ap_fit_limit : float, default 2
      noise level out to which to extend the fit in units of pixel
      background noise level. Default is 2, smaller values will end
      fitting further out in the galaxy image.

    Notes
    ----------
    :References:
    - 'background'
    - 'background noise'
    - 'psf fwhm'
    - 'center'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'init ellip': , # Ellipticity of the global fit (float)
         'init pa': ,# Position angle of the global fit (float)
         'init R': ,# Semi-major axis length of global fit (float)
         'auxfile initialize': # optional, message for aux file to record the global ellipticity and postition angle (string)

        }

    """

    ######################################################################
    # Initial attempt to find size of galaxy in image
    # based on when isophotes SB values start to get
    # close to the background noise level
    circ_ellipse_radii = [results["psf fwhm"]]
    allphase = []
    dat = IMG - results["background"]

    while circ_ellipse_radii[-1] < (len(IMG) / 2):
        circ_ellipse_radii.append(circ_ellipse_radii[-1] * (1 + 0.2))
        isovals = _iso_extract(
            dat,
            circ_ellipse_radii[-1],
            {
                "ellip": 0.0,
                "pa": 0.0
            },
            results["center"],
            more=True,
        )
        coefs = fft(isovals[0])
        allphase.append(coefs[2])
        # Stop when at 3 times background noise
        if (np.mean(isovals[0]) < (3 * results["background noise"])
                and len(circ_ellipse_radii) > 4):
            break
    logging.info("%s: init scale: %f pix" %
                 (options["ap_name"], circ_ellipse_radii[-1]))
    # Find global position angle.
    phase = (-Angle_Median(np.angle(allphase[-5:])) /
             2) % np.pi  # (-np.angle(np.mean(allphase[-5:]))/2) % np.pi

    # Find global ellipticity
    test_ellip = np.linspace(0.05, 0.95, 15)
    test_f2 = []
    for e in test_ellip:
        test_f2.append(
            sum(
                list(
                    _fitEllip_mean_loss(
                        e,
                        dat,
                        circ_ellipse_radii[-2] * m,
                        phase,
                        results["center"],
                        results["background noise"],
                    ) for m in np.linspace(0.8, 1.2, 5))))
    ellip = test_ellip[np.argmin(test_f2)]
    res = minimize(
        lambda e, d, r, p, c, n: sum(
            list(
                _fitEllip_mean_loss(_x_to_eps(e[0]), d, r * m, p, c, n)
                for m in np.linspace(0.8, 1.2, 5))),
        x0=_inv_x_to_eps(ellip),
        args=(
            dat,
            circ_ellipse_radii[-2],
            phase,
            results["center"],
            results["background noise"],
        ),
        method="Nelder-Mead",
        options={
            "initial_simplex": [
                [_inv_x_to_eps(ellip) - 1 / 15],
                [_inv_x_to_eps(ellip) + 1 / 15],
            ]
        },
    )
    if res.success:
        logging.debug(
            "%s: using optimal ellipticity %.3f over grid ellipticity %.3f" %
            (options["ap_name"], _x_to_eps(res.x[0]), ellip))
        ellip = _x_to_eps(res.x[0])

    # Compute the error on the parameters
    ######################################################################
    RR = np.linspace(
        circ_ellipse_radii[-2] - results["psf fwhm"],
        circ_ellipse_radii[-2] + results["psf fwhm"],
        10,
    )
    errallphase = []
    for rr in RR:
        isovals = _iso_extract(dat,
                               rr, {
                                   "ellip": 0.0,
                                   "pa": 0.0
                               },
                               results["center"],
                               more=True)
        coefs = fft(isovals[0])
        errallphase.append(coefs[2])
    sample_pas = (-np.angle(1j * np.array(errallphase) / np.mean(errallphase))
                  / 2) % np.pi
    pa_err = np.std(sample_pas)
    res_multi = map(
        lambda rrp: minimize(
            lambda e, d, r, p, c, n: _fitEllip_mean_loss(
                _x_to_eps(e[0]), d, r, p, c, n),
            x0=_inv_x_to_eps(ellip),
            args=(dat, rrp[0], rrp[1], results["center"], results[
                "background noise"]),
            method="Nelder-Mead",
            options={
                "initial_simplex": [
                    [_inv_x_to_eps(ellip) - 1 / 15],
                    [_inv_x_to_eps(ellip) + 1 / 15],
                ]
            },
        ),
        zip(RR, sample_pas),
    )
    ellip_err = np.std(list(_x_to_eps(rm.x[0]) for rm in res_multi))

    circ_ellipse_radii = np.array(circ_ellipse_radii)

    if "ap_doplot" in options and options["ap_doplot"]:

        ranges = [
            [
                max(0,
                    int(results["center"]["x"] -
                        circ_ellipse_radii[-1] * 1.5)),
                min(
                    dat.shape[1],
                    int(results["center"]["x"] + circ_ellipse_radii[-1] * 1.5),
                ),
            ],
            [
                max(0,
                    int(results["center"]["y"] -
                        circ_ellipse_radii[-1] * 1.5)),
                min(
                    dat.shape[0],
                    int(results["center"]["y"] + circ_ellipse_radii[-1] * 1.5),
                ),
            ],
        ]

        LSBImage(
            dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]],
            results["background noise"],
        )
        # plt.imshow(np.clip(dat[ranges[1][0]: ranges[1][1], ranges[0][0]: ranges[0][1]],a_min = 0, a_max = None),
        #            origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch()))
        plt.gca().add_patch(
            Ellipse(
                (
                    results["center"]["x"] - ranges[0][0],
                    results["center"]["y"] - ranges[1][0],
                ),
                2 * circ_ellipse_radii[-1],
                2 * circ_ellipse_radii[-1] * (1.0 - ellip),
                phase * 180 / np.pi,
                fill=False,
                linewidth=1,
                color="y",
            ))
        plt.plot(
            [results["center"]["x"] - ranges[0][0]],
            [results["center"]["y"] - ranges[1][0]],
            marker="x",
            markersize=3,
            color="r",
        )
        plt.tight_layout()
        if not ("ap_nologo" in options and options["ap_nologo"]):
            AddLogo(plt.gcf())
        plt.savefig(
            "%sinitialize_ellipse_%s.jpg" % (
                options["ap_plotpath"] if "ap_plotpath" in options else "",
                options["ap_name"],
            ),
            dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300,
        )
        plt.close()

        fig, ax = plt.subplots(2, 1, figsize=(6, 6))
        ax[0].plot(
            circ_ellipse_radii[:-1],
            ((-np.angle(allphase) / 2) % np.pi) * 180 / np.pi,
            color="k",
        )
        ax[0].axhline(phase * 180 / np.pi, color="r")
        ax[0].axhline((phase + pa_err) * 180 / np.pi,
                      color="r",
                      linestyle="--")
        ax[0].axhline((phase - pa_err) * 180 / np.pi,
                      color="r",
                      linestyle="--")
        # ax[0].axvline(circ_ellipse_radii[-2], color = 'orange', linestyle = '--')
        ax[0].set_xlabel("Radius [pix]")
        ax[0].set_ylabel("FFT$_{1}$ phase [deg]")
        ax[1].plot(test_ellip, test_f2, color="k")
        ax[1].axvline(ellip, color="r")
        ax[1].axvline(ellip + ellip_err, color="r", linestyle="--")
        ax[1].axvline(ellip - ellip_err, color="r", linestyle="--")
        ax[1].set_xlabel("Ellipticity [1 - b/a]")
        ax[1].set_ylabel("Loss [FFT$_{2}$/med(flux)]")
        plt.tight_layout()
        if not ("ap_nologo" in options and options["ap_nologo"]):
            AddLogo(plt.gcf())
        plt.savefig(
            "%sinitialize_ellipse_optimize_%s.jpg" % (
                options["ap_plotpath"] if "ap_plotpath" in options else "",
                options["ap_name"],
            ),
            dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300,
        )
        plt.close()

    auxmessage = (
        "global ellipticity: %.3f +- %.3f, pa: %.3f +- %.3f deg, size: %f pix"
        % (
            ellip,
            ellip_err,
            PA_shift_convention(phase) * 180 / np.pi,
            pa_err * 180 / np.pi,
            circ_ellipse_radii[-2],
        ))
    return IMG, {
        "init ellip": ellip,
        "init ellip_err": ellip_err,
        "init pa": phase,
        "init pa_err": pa_err,
        "init R": circ_ellipse_radii[-2],
        "auxfile initialize": auxmessage,
    }
Ejemplo n.º 5
0
def Isophote_Initialize(IMG, results, options):
    """Fit global elliptical isophote to a galaxy image using FFT coefficients.

    A global position angle and ellipticity are fit in a two step
    process.  First, a series of circular isophotes are geometrically
    sampled until they approach the background level of the image.  An
    FFT is taken for the flux values around each isophote and the
    phase of the second coefficient is used to determine a direction.
    The average direction for the outer isophotes is taken as the
    position angle of the galaxy.  Second, with fixed position angle
    the ellipticity is optimized to minimize the amplitude of the
    second FFT coefficient relative to the median flux in an isophote.

    To compute the error on position angle we use the standard
    deviation of the outer values from step one.  For ellipticity the
    error is computed by optimizing the ellipticity for multiple
    isophotes within 1 PSF length of each other.

    Parameters
    -----------------
    ap_fit_limit : float, default 2
      noise level out to which to extend the fit in units of pixel background noise level. Default is 2, smaller values will end fitting further out in the galaxy image.

    ap_isoinit_pa_set : float, default None
      User set initial position angle in degrees, will override the calculation.

    ap_isoinit_ellip_set : float, default None
      User set initial ellipticity (1 - b/a), will override the calculation.

    Notes
    ----------
    :References:
    - 'background'
    - 'background noise'
    - 'psf fwhm'
    - 'center'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'init ellip': , # Ellipticity of the global fit (float)
         'init pa': ,# Position angle of the global fit (float)
         'init R': ,# Semi-major axis length of global fit (float)
         'auxfile initialize': # optional, message for aux file to record the global ellipticity and postition angle (string)

        }

    """

    ######################################################################
    # Initial attempt to find size of galaxy in image
    # based on when isophotes SB values start to get
    # close to the background noise level
    circ_ellipse_radii = [1.0]
    allphase = []
    dat = IMG - results["background"]
    mask = results["mask"] if "mask" in results else None
    if not np.any(mask):
        mask = None

    while circ_ellipse_radii[-1] < (len(IMG) / 2):
        circ_ellipse_radii.append(circ_ellipse_radii[-1] * (1 + 0.2))
        isovals = _iso_extract(
            dat,
            circ_ellipse_radii[-1],
            {
                "ellip": 0.0,
                "pa": 0.0
            },
            results["center"],
            more=True,
            mask=mask,
            sigmaclip=True,
            sclip_nsigma=3,
            interp_mask=True,
        )
        coefs = fft(isovals[0])
        allphase.append(coefs[2])
        # Stop when at 3 time background noise
        if (np.quantile(isovals[0], 0.8) < (
            (options["ap_fit_limit"] + 1 if "ap_fit_limit" in options else 3) *
                results["background noise"]) and len(circ_ellipse_radii) > 4):
            break
    logging.info("%s: init scale: %f pix" %
                 (options["ap_name"], circ_ellipse_radii[-1]))
    # Find global position angle.
    phase = (-Angle_Median(np.angle(allphase[-5:])) / 2) % np.pi
    if "ap_isoinit_pa_set" in options:
        phase = PA_shift_convention(options["ap_isoinit_pa_set"] * np.pi / 180)

    # Find global ellipticity
    test_ellip = np.linspace(0.05, 0.95, 15)
    test_f2 = []
    for e in test_ellip:
        test_f2.append(
            sum(
                list(
                    _fitEllip_loss(
                        e,
                        dat,
                        circ_ellipse_radii[-2] * m,
                        phase,
                        results["center"],
                        results["background noise"],
                        mask,
                    ) for m in np.linspace(0.8, 1.2, 5))))
    ellip = test_ellip[np.argmin(test_f2)]
    res = minimize(
        lambda e, d, r, p, c, n, msk: sum(
            list(
                _fitEllip_loss(_x_to_eps(e[0]), d, r * m, p, c, n, msk)
                for m in np.linspace(0.8, 1.2, 5))),
        x0=_inv_x_to_eps(ellip),
        args=(
            dat,
            circ_ellipse_radii[-2],
            phase,
            results["center"],
            results["background noise"],
            mask,
        ),
        method="Nelder-Mead",
        options={
            "initial_simplex": [
                [_inv_x_to_eps(ellip) - 1 / 15],
                [_inv_x_to_eps(ellip) + 1 / 15],
            ]
        },
    )
    if res.success:
        logging.debug(
            "%s: using optimal ellipticity %.3f over grid ellipticity %.3f" %
            (options["ap_name"], _x_to_eps(res.x[0]), ellip))
        ellip = _x_to_eps(res.x[0])
    if "ap_isoinit_ellip_set" in options:
        ellip = options["ap_isoinit_ellip_set"]

    # Compute the error on the parameters
    ######################################################################
    RR = np.linspace(
        circ_ellipse_radii[-2] - results["psf fwhm"],
        circ_ellipse_radii[-2] + results["psf fwhm"],
        10,
    )
    errallphase = []
    for rr in RR:
        isovals = _iso_extract(
            dat,
            rr,
            {
                "ellip": 0.0,
                "pa": 0.0
            },
            results["center"],
            more=True,
            sigmaclip=True,
            sclip_nsigma=3,
            interp_mask=True,
        )
        coefs = fft(isovals[0])
        errallphase.append(coefs[2])
    sample_pas = (-np.angle(1j * np.array(errallphase) / np.mean(errallphase))
                  / 2) % np.pi
    pa_err = iqr(sample_pas, rng=[16, 84]) / 2
    res_multi = map(
        lambda rrp: minimize(
            lambda e, d, r, p, c, n, m: _fitEllip_loss(_x_to_eps(e[0]), d, r,
                                                       p, c, n, m),
            x0=_inv_x_to_eps(ellip),
            args=(
                dat,
                rrp[0],
                rrp[1],
                results["center"],
                results["background noise"],
                mask,
            ),
            method="Nelder-Mead",
            options={
                "initial_simplex": [
                    [_inv_x_to_eps(ellip) - 1 / 15],
                    [_inv_x_to_eps(ellip) + 1 / 15],
                ]
            },
        ),
        zip(RR, sample_pas),
    )
    ellip_err = iqr(list(_x_to_eps(rm.x[0])
                         for rm in res_multi), rng=[16, 84]) / 2

    circ_ellipse_radii = np.array(circ_ellipse_radii)

    if "ap_doplot" in options and options["ap_doplot"]:
        Plot_Isophote_Init_Ellipse(dat, circ_ellipse_radii, ellip, phase,
                                   results, options)
        Plot_Isophote_Init_Optimize(
            circ_ellipse_radii,
            allphase,
            phase,
            pa_err,
            test_ellip,
            test_f2,
            ellip,
            ellip_err,
            results,
            options,
        )

    auxmessage = (
        "global ellipticity: %.3f +- %.3f, pa: %.3f +- %.3f deg, size: %f pix"
        % (
            ellip,
            ellip_err,
            PA_shift_convention(phase) * 180 / np.pi,
            pa_err * 180 / np.pi,
            circ_ellipse_radii[-2],
        ))
    return IMG, {
        "init ellip": ellip,
        "init ellip_err": ellip_err,
        "init pa": phase,
        "init pa_err": pa_err,
        "init R": circ_ellipse_radii[-2],
        "auxfile initialize": auxmessage,
    }
Ejemplo n.º 6
0
def Center_HillClimb_mean(IMG, results, options):
    """Follow locally increasing brightness (robust to PSF size objects) to find peak.

    Using 10 circular isophotes out to 10 times the PSF length, the
    first FFT coefficient phases are averaged to find the direction of
    increasing flux. Flux values are sampled along this direction and
    a quadratic fit gives the maximum. This is iteratively repeated
    until the step size becomes very small. This function is identical
    to :func:`~autoprofutils.Center.Center_HillClimb` except that all
    averages/scatters are mean/std based instead of median/iqr based.

    Parameters
    -----------------
    ap_guess_center : dict, default None
      user provided starting point for center fitting. Center should
      be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_set_center : dict, default None
      user provided fixed center for rest of calculations. Center
      should be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_centeringring : int, default 10
      Size of ring to use when finding galaxy center, in units of
      PSF. Larger rings will be robust to features (i.e., foreground
      stars), while smaller rings may be needed for small galaxies.

    Notes
    ----------
    :References:
    - 'background'
    - 'background noise'
    - 'psf fwhm'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'center': {'x': , # x coordinate of the center (pix)
                    'y': }, # y coordinate of the center (pix)

         'auxfile center': # optional, message for aux file to record galaxy center (string)
         'auxfile centeral sb': # optional, central surface brightness value (float)

        }

    """
    current_center = {"x": IMG.shape[0] / 2, "y": IMG.shape[1] / 2}

    current_center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2}
    if "ap_guess_center" in options:
        current_center = deepcopy(options["ap_guess_center"])
        logging.info("%s: Center initialized by user: %s" %
                     (options["ap_name"], str(current_center)))
    if "ap_set_center" in options:
        logging.info("%s: Center set by user: %s" %
                     (options["ap_name"], str(options["ap_set_center"])))
        return IMG, {"center": deepcopy(options["ap_set_center"])}

    dat = IMG - results["background"]

    searchring = (int(options["ap_centeringring"])
                  if "ap_centeringring" in options else 10)
    sampleradii = np.linspace(1, searchring, searchring) * results["psf fwhm"]

    track_centers = []
    small_update_count = 0
    total_count = 0
    while small_update_count <= 5 and total_count <= 100:
        total_count += 1
        phases = []
        isovals = []
        coefs = []
        for r in sampleradii:
            isovals.append(
                _iso_extract(dat,
                             r, {
                                 "ellip": 0.0,
                                 "pa": 0.0
                             },
                             current_center,
                             more=True))
            coefs.append(fft(isovals[-1][0]))
            phases.append((-np.angle(coefs[-1][1])) % (2 * np.pi))
        direction = Angle_Median(phases) % (2 * np.pi)
        levels = []
        level_locs = []
        for i, r in enumerate(sampleradii):
            floc = np.argmin(np.abs(isovals[i][1] - direction))
            rloc = np.argmin(
                np.abs(isovals[i][1] - ((direction + np.pi) % (2 * np.pi))))
            smooth = np.abs(
                ifft(coefs[i][:min(10, len(coefs[i]))], n=len(coefs[i])))
            levels.append(smooth[floc])
            level_locs.append(r)
            levels.insert(0, smooth[rloc])
            level_locs.insert(0, -r)
        try:
            p = np.polyfit(level_locs, levels, deg=2)
            if p[0] < 0 and len(levels) > 3:
                dist = np.clip(-p[1] / (2 * p[0]),
                               a_min=min(level_locs),
                               a_max=max(level_locs))
            else:
                dist = level_locs[np.argmax(levels)]
        except:
            dist = results["psf fwhm"]
        current_center["x"] += dist * np.cos(direction)
        current_center["y"] += dist * np.sin(direction)
        if abs(dist) < (0.25 * results["psf fwhm"]):
            small_update_count += 1
        else:
            small_update_count = 0
        track_centers.append([current_center["x"], current_center["y"]])

    # refine center
    res = minimize(
        _hillclimb_mean_loss,
        x0=[current_center["x"], current_center["y"]],
        args=(dat, results["psf fwhm"], results["background noise"]),
        method="Nelder-Mead",
    )
    if res.success:
        current_center["x"] = res.x[0]
        current_center["y"] = res.x[1]

    return IMG, {
        "center":
        current_center,
        "auxfile center":
        "center x: %.2f pix, y: %.2f pix" %
        (current_center["x"], current_center["y"]),
    }