Exemple #1
0
 def show_example(self):
     data, label = self.random_model_label(1)
     label = label.flatten()
     _data = data[0].reshape(21, 21)
     pos = np.array(label[0]).flatten()
     plt.imshow(_data, cmap="Greys_r")
     t0_c2dg = time.time()
     pred_data = [data[0].reshape(1, 21, 21, 1)]
     t0_nn = time.time()
     pred = self.model(pred_data, training=False).numpy()
     tf_nn = time.time() - t0_nn
     plt.plot(*label[::-1], "x", c="k", label="true")
     plt.plot(*pred.T[::-1], "x", label="NNCentroid")
     t0_c2dg = time.time()
     c2dg = centroid_2dg(_data)
     tf_c2dg = time.time() - t0_c2dg
     plt.plot(*c2dg, "x", label="centroid_2dg")
     plt.legend()
     ax = plt.gca()
     plt.text(
         0.05,
         0.05,
         "distance:\nNNCentroid: {:.1e} ({:.0f}x faster)\n2DGaussian: {:.1e}"
         .format(
             np.sqrt(np.sum((label - pred)**2)),
             tf_c2dg / tf_nn,
             np.sqrt(np.sum((label - c2dg[::-1])**2)),
         ),
         fontsize=11,
         horizontalalignment='left',
         verticalalignment='bottom',
         transform=ax.transAxes,
         c="w")
Exemple #2
0
def get_pix(cube, cen=None, geom='3x3', normalize=True):
    """
    assumes the cube has shape (n,k,k), where n is the number of frames and k
    is the width of each (square) frame, and that k > 5.
    """
    def fix_nan(im):
        im[np.isnan(im)] = np.median(im[~np.isnan(im)])

    if not cen:
        med_im = np.median(cube, axis=0)
        fix_nan(med_im)
        cx, cy = centroid_2dg(med_im)
        cx, cy = list(map(int, list(map(round, [cx, cy]))))
        print("centroid: {}, {}".format(cx, cy))
    else:
        cx, cy = list(map(int, cen))
    if geom == '3x3':
        i = 1
    elif geom == '5x5':
        i = 2
    else:
        raise ValueError("geometry not supported")

    x0 = cx - i
    x1 = cx + i + 1
    y0 = cy - i
    y1 = cy + i + 1
    sub_cube = cube[:, x0:x1, y0:y1]
    pixels = sub_cube.reshape(cube.shape[0], sub_cube.shape[1]**2)

    if normalize:
        for i in pixels:
            i /= i.sum()

    return pixels
Exemple #3
0
    def centroid_2dg(self):
        """
        This function ...
        :return:
        """

        #from ..tools import plotting
        #plotting.plot_box(self._data)
        return centroid_2dg(self._data)
Exemple #4
0
 def _baricenterCalculator(self, reference_image):
     ''' Calculate the peak position of the image
     args:
         reference_image = camera frame
     returns:
         cy, cx = y and x coord
     '''
     counts, bin_edges = np.histogram(reference_image)
     thr = 5 * bin_edges[np.where(counts == max(counts))]
     idx = np.where(reference_image < thr)
     img = reference_image.copy()
     img[idx] = 0
     cx, cy = centroids.centroid_2dg(img)
     return np.int(cy), np.int(cx)
Exemple #5
0
    def centroid_2dg(self, cutoff=None):

        """
        This function ...
        :param cutoff: sigma level for cutting off the data
        :return:
        """

        # Create cutoff mask
        if cutoff: mask = self.create_sigma_mask(cutoff, invert=True)
        else: mask = None

        #from ..tools import plotting
        #plotting.plot_box(self._data)
        return centroid_2dg(self._data, mask=mask)
Exemple #6
0
 def _newThr(self, img):
     ''' Calculate the peak position of the image '''
     counts, bin_edges = np.histogram(img)
     edges = (bin_edges[2:] + bin_edges[1:len(bin_edges) - 1]) / 2
     thr = 5 * edges[np.where(counts == max(counts))]
     idx = np.where(img < thr)
     img[idx] = 0
     # cy, cx = scin.measurements.center_of_mass(img)
     cx, cy = centroids.centroid_2dg(img)
     baricenterCoord = [np.int(round(cy)), np.int(round(cx))]
     pick = [
         baricenterCoord[0] - 25, baricenterCoord[0] + 25,
         baricenterCoord[1] - 75, baricenterCoord[1] + 75
     ]
     return pick
Exemple #7
0
def compute_eff_radii(image, plot=False):
    max_pix_rad = np.min(image.shape) // 2
    radius = np.arange(3, max_pix_rad, 3)
    fake_image = np.zeros_like(image)
    fake_image[max_pix_rad // 2:(3 * max_pix_rad) // 2,
               max_pix_rad // 2:(3 * max_pix_rad) //
               2] = image[max_pix_rad // 2:(3 * max_pix_rad) // 2,
                          max_pix_rad // 2:(3 * max_pix_rad) // 2]
    com_x, com_y = centroid_2dg(fake_image)
    aperture_sum = []
    for rad_i in radius:
        aperture = CircularAperture((com_x, com_y), r=rad_i)
        phot_table = aperture_photometry(image, aperture)
        aperture_sum.append(phot_table['aperture_sum'].value)
    aperture_sum = np.array(aperture_sum).squeeze()
    norm_aperture_sum = aperture_sum / aperture_sum[-1]
    half_mass_rad = np.interp(0.5, norm_aperture_sum, radius)
    half_mass_aperture = CircularAperture((com_x, com_y), r=half_mass_rad)
    two_half_mass_aperture = CircularAperture((com_x, com_y),
                                              r=2 * half_mass_rad)
    half_mass_table = aperture_photometry(image, half_mass_aperture)
    two_half_mass_table = aperture_photometry(image, two_half_mass_aperture)
    if plot:
        fig = plt.figure()
        plt.imshow(np.log10(image))
        plt.colorbar(label='log(image)')
        plt.plot(com_x, com_y, '+', markersize=8, color='c')
        half_mass_aperture.plot(color='r', lw=2, label=r'Half mass rad')
        two_half_mass_aperture.plot(color='orange',
                                    lw=2,
                                    label=r'Two half mass rad')
        plt.annotate(
            'Tot mass={:.2}\nHalf mass={:.2}\nTwoHalf mass={:.2}'.format(
                float(aperture_sum[-1]),
                float(half_mass_table['aperture_sum'].value),
                float(two_half_mass_table['aperture_sum'].value)),
            xy=(.1, 1),
            xycoords='axes fraction',
            va='bottom')
        plt.legend()
        plt.close()
        return half_mass_aperture, two_half_mass_aperture, fig
    else:
        return half_mass_aperture, two_half_mass_aperture
Exemple #8
0
def optimizephot(obj, img):
    ix, iy = obj
    cenx, ceny = centroids.centroid_2dg(img[iy - 10:iy + 10, ix - 10:ix + 10])
    cenx += ix - 10
    ceny += iy - 10
    print("CENTROIDED LOCATION:", cenx, ceny)

    data = np.ma.asanyarray(img)
    weights = np.ones(data.shape)
    init_con = np.median(data)
    init_amp = np.max(data[int(ceny) - 5:int(ceny) + 5,
                           int(cenx) - 5:int(cenx) + 5])

    g_init = centroids.GaussianConst2D(constant=init_con,
                                       amplitude=init_amp,
                                       x_mean=cenx,
                                       y_mean=ceny,
                                       x_stddev=1,
                                       y_stddev=1,
                                       theta=0,
                                       fixed={
                                           'x_mean': True,
                                           'y_mean': True
                                       })
    fitter = LevMarLSQFitter()
    y, x = np.indices(data.shape)
    gfit = fitter(g_init, x, y, data, weights=weights)
    print(gfit)

    ap = np.round(1.6 * np.sqrt(gfit.x_stddev**2. + gfit.y_stddev**2),
                  1)  #Mighell 1999ASPC..189...50M
    print("OPTIMAL APERTURE (unscaled):", ap)
    if ap > 15. or ap <= 1.:
        print("~~~~~~~~ POSSIBLE BAD APERTURE FIT - CHECK IMAGE ~~~~~~~~")
    print()
    amp = gfit.amplitude
    return cenx, ceny, ap, amp
Exemple #9
0
def centroid(data, coord, box_size=30, wcs=None):
    if is_pix_coord(coord):
        coord = [np.float(coord[0]), np.float(coord[1])]
        print 'Centroiding at (%.2f,%.2f)' % (coord[0], coord[1]),
    elif is_sky_coord(coord):
        if wcs:
            try:
                x, y = wcs2pix(coord, wcs)
                print 'Centroiding at (%s,%s) -> (%.2f,%.2f)' % (
                    coord[0], coord[1], x, y)
                coord = (x, y)
            except:
                raise ValueError('Cannot parse coordinates with input wcs')
        else:
            raise InputError(
                "Must provide wcs object to parse sky coordinates")
    else:
        raise ValueError('Cannot parse coordinates')

    ## make cutout mask
    #print coord
    _, _, _, slices = cutout_footprint(data, coord, box_size=box_size)
    mask = np.ones_like(data, dtype=bool)
    mask[slices] = False
    xc, yc = centroid_2dg(data, mask=mask)
    print ' -> (%f, %f)' % (xc, yc),

    if wcs:
        ra, dec = pix2wcs([xc, yc], wcs)
        print ' [%s, %s]' % (ra, dec)
        tbl = Table([[xc], [yc], [ra], [dec]],
                    names=['xcen', 'ycen', 'ra', 'dec'])
        return tbl
    else:
        print
        tbl = Table([[xc], [yc]], names=['xcen', 'ycen'])
        return tbl
Exemple #10
0
def Center_2DGaussian(IMG, results, options):
    """
    Compute the pixel location of the galaxy center by fitting
    a 2d Gaussian as implimented by the photutils package.
    """
    
    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'])}
    
    # Create mask to focus centering algorithm on the center of the image
    ranges = [[max(0,int(current_center['x'] - 50*results['psf fwhm'])), min(IMG.shape[1],int(current_center['x'] + 50*results['psf fwhm']))],
              [max(0,int(current_center['y'] - 50*results['psf fwhm'])), min(IMG.shape[0],int(current_center['y'] + 50*results['psf fwhm']))]]
    centralize_mask = np.ones(IMG.shape, dtype = bool)
    centralize_mask[ranges[1][0]:ranges[1][1],
                    ranges[0][0]:ranges[0][1]] = False

    try:
        x, y = centroid_2dg(IMG - results['background'], mask = centralize_mask)
        current_center = {'x': x, 'y': y}
    except:
        logging.warning('%s: 2D Gaussian center finding failed! using image center (or guess).' % options['ap_name'])
        
    # Plot center value for diagnostic purposes
    if 'ap_doplot' in options and options['ap_doplot']:    
        plt.imshow(np.clip(IMG - results['background'],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 = 10, color = 'y')
        plt.savefig('%scenter_vis_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']))
        plt.close()
    logging.info('%s Center found: x %.1f, y %.1f' % (options['ap_name'], x, y))    
    return IMG, {'center': current_center, 'auxfile center': 'center x: %.2f pix, y: %.2f pix' % (current_center['x'], current_center['y'])}
 def calculate_local_center(self, cutout):
     center_box_slices = (slice(6,15), slice(6,15))
     cen = centroid_2dg(cutout[center_box_slices])
     return cen + 6
Exemple #12
0
    def psf_convolution(self, model):
        """Convolve the cropped image with the appropriate PSF for the
        JWST detector being simulated.

        Parameters
        ----------
        model : jwst.datamodels.ImageModel
            Data model instance containing the cropped image

        Returns
        -------
        model : jwst.datamodels.ImageModel
            Data model with image convolved by the PSF
        """
        # The jwst_psf and the mosaic_psf must have the same array size
        # and the same pixel scale. First deal with the pixel scale.

        # Rescale one of the PSFs if necessary, in order to get matching pixel scales.
        # Since the matching kernel is going to be convolved with the mosaic image,
        # then it seems like we should match the PSFs at the mosaic pixel scale.
        if not np.isclose(self.outscale1,
                          self.mosaic_metadata['pix_scale1'],
                          atol=0.,
                          rtol=0.01):
            orig_jwst = copy.deepcopy(self.jwst_psf)
            self.jwst_psf = resize_psf(self.jwst_psf,
                                       self.outscale1,
                                       self.mosaic_metadata['pix_scale1'],
                                       order=3)

            resized_y_dim, resized_x_dim = self.jwst_psf.shape
            if ((resized_y_dim % 2 == 0) or (resized_x_dim % 2 == 0)):
                if resized_y_dim % 2 == 0:
                    new_y_dim = resized_y_dim + 1
                else:
                    new_y_dim = resized_y_dim
                if resized_x_dim % 2 == 0:
                    new_x_dim = resized_x_dim + 1
                else:
                    new_x_dim = resized_x_dim

                # Add a column/row to the resized array,
                jwst_psf_padded = np.zeros((new_y_dim, new_x_dim))
                jwst_psf_padded[0:resized_y_dim,
                                0:resized_x_dim] = self.jwst_psf
                # Rather than zeros, make the top row/leftmost column a
                # copy of the row/column next to it
                if new_y_dim > resized_y_dim:
                    jwst_psf_padded[-1, 0:resized_x_dim] = self.jwst_psf[-1, :]
                if new_x_dim > resized_x_dim:
                    jwst_psf_padded[0:resized_y_dim, -1] = self.jwst_psf[:, -1]
                if ((new_y_dim > resized_y_dim)
                        and (new_x_dim > resized_x_dim)):
                    jwst_psf_padded[-1, -1] = self.jwst_psf[-1, 1]

                # Shift the source to be centered in the center pixel
                centerx, centery = centroid_2dg(jwst_psf_padded)
                jwst_shifted = shift(jwst_psf_padded, [0.5, 0.5], order=1)
                centerx, centery = centroid_2dg(jwst_shifted)
                self.jwst_psf = jwst_shifted
            else:
                jwst_psf_padded = self.jwst_psf

        jwst_shape = self.jwst_psf.shape
        mosaic_shape = self.mosaic_psf.shape
        if ((jwst_shape[0] % 2 != mosaic_shape[0] % 2)
                or (jwst_shape[1] % 2 != mosaic_shape[1] % 2)):
            raise ValueError((
                "ERROR: Mosaic PSF and JWST PSF have different shapes in terms "
                "of odd/even numbers of rows and/or columns. Try adding or subtracting "
                "rows/columns to the mosaic PSF. Mosaic PSF shape: {}, JWST PSF shape: {}"
                .format(mosaic_shape, jwst_shape)))

        # Now crop either the resized JWST PSF or the mosaic PSF in
        # order to get them both to the same array size
        self.logger.info("Crop PSFs to have the same array size")
        self.jwst_psf, self.mosaic_psf = tools.same_array_size(
            self.jwst_psf, self.mosaic_psf)

        # Now we make a matching kernel. The mosaic can then be
        # convolved with this kernel in order to adjust the PSFs to match
        # those from JWST.
        self.logger.info("Create matching kernel")
        kernel = self.matching_kernel(self.mosaic_psf,
                                      self.jwst_psf,
                                      window_type='TukeyWindow',
                                      alpha=1.5,
                                      beta=1.5)

        if self.save_intermediates:
            self.logger.info(
                'Save JWST psf and matching psf in outgoing_and_matching_kernel.fits'
            )
            ha = fits.PrimaryHDU(orig_jwst)
            h0 = fits.ImageHDU(self.jwst_psf)
            h1 = fits.ImageHDU(self.mosaic_psf)
            h2 = fits.ImageHDU(kernel)
            hlist = fits.HDUList([ha, h0, h1, h2])
            outfile = os.path.join(
                self.outdir, '{}_outgoing_and_matching_kernel.fits'.format(
                    self.output_base))
            hlist.writeto(outfile, overwrite=True)

        self.logger.info(
            'Convolve image cropped from mosaic with the matching PSF kernel')
        start_time = datetime.datetime.now()
        convolved_mosaic = fftconvolve(model.data, kernel, mode='same')
        end_time = datetime.datetime.now()
        delta_time = end_time - start_time
        self.logger.info("Convolution took {} seconds".format(
            delta_time.seconds))
        model.data = convolved_mosaic

        if self.save_intermediates:
            self.logger.info(
                'Saving convolved mosaic as convolved_mosaic.fits')
            h0 = fits.PrimaryHDU(convolved_mosaic)
            hlist = fits.HDUList([h0])
            outfile = os.path.join(
                self.outdir,
                '{}_convolved_mosaic.fits'.format(self.output_base))
            hlist.writeto(outfile, overwrite=True)

        return model
Exemple #13
0
def Center_2DGaussian(IMG, results, options):
    """Find galaxy center with a 2D gaussian fit to the image..

    Compute the pixel location of the galaxy center by fitting a 2d
    Gaussian as implimented by the photutils package.

    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 50
      Size of ring to use when finding galaxy center, in units of
      PSF. Larger rings will give the 2D fit more data to work with
      and allow for the starting position to be further from the true
      galaxy center.  Smaller rings will include fewer spurious
      objects, and can stop the 2D fit from being distracted by larger
      nearby objects/galaxies.

    Notes
    ----------
    :References:
    - 'background'
    - '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)

        }

    """

    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"])}

    # Create mask to focus centering algorithm on the center of the image
    ranges = [
        [
            max(
                0,
                int(current_center["x"] -
                    (options["ap_centeringring"] if "ap_centeringring" in
                     options else 50) * results["psf fwhm"]),
            ),
            min(
                IMG.shape[1],
                int(current_center["x"] +
                    (options["ap_centeringring"] if "ap_centeringring" in
                     options else 50) * results["psf fwhm"]),
            ),
        ],
        [
            max(
                0,
                int(current_center["y"] -
                    (options["ap_centeringring"] if "ap_centeringring" in
                     options else 50) * results["psf fwhm"]),
            ),
            min(
                IMG.shape[0],
                int(current_center["y"] +
                    (options["ap_centeringring"] if "ap_centeringring" in
                     options else 50) * results["psf fwhm"]),
            ),
        ],
    ]
    centralize_mask = np.ones(IMG.shape, dtype=bool)
    centralize_mask[ranges[1][0]:ranges[1][1],
                    ranges[0][0]:ranges[0][1]] = False

    try:
        x, y = centroid_2dg(IMG - results["background"], mask=centralize_mask)
        current_center = {"x": x, "y": y}
    except:
        logging.warning(
            "%s: 2D Gaussian center finding failed! using image center (or guess)."
            % options["ap_name"])

    # Plot center value for diagnostic purposes
    if "ap_doplot" in options and options["ap_doplot"]:
        plt.imshow(
            np.clip(IMG - results["background"], 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=10,
            color="y",
        )
        plt.savefig("%scenter_vis_%s.jpg" % (
            options["ap_plotpath"] if "ap_plotpath" in options else "",
            options["ap_name"],
        ))
        plt.close()
    logging.info("%s Center found: x %.1f, y %.1f" %
                 (options["ap_name"], x, y))
    return IMG, {
        "center":
        current_center,
        "auxfile center":
        "center x: %.2f pix, y: %.2f pix" %
        (current_center["x"], current_center["y"]),
    }
Exemple #14
0
def dataquality(cubeslist, maskslist):
    """
    Perform bunch of QA checks to asses the final data quality of reduction
    
    """

    import os
    import numpy as np
    from astropy.io import fits
    from mypython.fits import pyregmask as msk
    from mypython.ifu import muse_utils as mutil
    from mypython.ifu import muse_source as msrc
    from matplotlib.backends.backend_pdf import PdfPages
    import matplotlib.pyplot as plt
    import matplotlib.cm as cm
    from astropy.stats import sigma_clipped_stats

    try:
        from photutils import CircularAperture, aperture_photometry,\
            data_properties, properties_table, centroids
    except:
        print("To run checks need photutils package")
        return

    print("Perform QA checks...")

    #make QA folder
    if not os.path.exists('QA'):
        os.makedirs('QA')

    #cube names
    cname = "COMBINED_CUBE.fits"
    iname = "COMBINED_IMAGE.fits"

    #first identify bright sources in final white image
    catsrc = msrc.findsources(iname,
                              cname,
                              check=True,
                              output='QA',
                              nsig=5,
                              minarea=20)

    #make rsdss images
    if not (os.path.isfile('QA/sdssr.fits')):
        mutil.cube2img(cname,
                       write='QA/sdssr.fits',
                       wrange=None,
                       helio=0,
                       filt=129)
    rsdssall = fits.open('QA/sdssr.fits')
    segmask = fits.open('QA/segmap.fits')
    whiteref = fits.open(iname)

    #select round and bright objects
    shapesrc = catsrc['a'] / catsrc['b']
    roundsrc = catsrc[np.where((shapesrc < 1.1) & (catsrc['cflux'] > 50))]
    imgfield = fits.open(iname)
    rms = np.std(imgfield[0].data)

    #perform aperture photometry on rband image - data already skysub
    positions = [roundsrc['x'], roundsrc['y']]
    apertures = CircularAperture(positions, r=10.)
    phot_table = aperture_photometry(rsdssall[1].data, apertures)
    phot_table_white = aperture_photometry(whiteref[0].data, apertures)
    rmag = -2.5 * np.log10(
        phot_table['aperture_sum']) + rsdssall[0].header['ZPAB']
    wmag = -2.5 * np.log10(phot_table_white['aperture_sum'])

    #find FWHM on rband image
    fwhm = np.zeros(len(rmag))
    for ii in range(len(rmag)):
        subdata=rsdssall[1].data[roundsrc['y'][ii]-10:roundsrc['y'][ii]+10,\
                                     roundsrc['x'][ii]-10:roundsrc['x'][ii]+10]
        tdfit = centroids.fit_2dgaussian(subdata, error=None, mask=None)
        fwhm[ii] = 2.3548 * 0.5 * (tdfit.x_stddev + tdfit.y_stddev
                                   ) * rsdssall[0].header['PC2_2'] * 3600.

    #find rms of cube - mask sources and add edge buffer
    maskwbuffer = np.copy(segmask[1].data)
    maskwbuffer[0:30, :] = 9999
    maskwbuffer[-31:-1, :] = 9999
    maskwbuffer[:, 0:30] = 9999
    maskwbuffer[:, -31:-1] = 9999
    cwrms, crms = mutil.cubestat(cname, mask=maskwbuffer)

    #open diagnostic output
    with PdfPages('QA/QAfile.pdf') as pdf:

        ###########################
        #display field with r mag #
        ###########################

        plt.figure(figsize=(10, 10))
        plt.imshow(imgfield[0].data,
                   origin='low',
                   clim=[-0.5 * rms, 0.5 * rms],
                   cmap='gray_r')
        #mark round soruces
        plt.scatter(roundsrc['x'], roundsrc['y'], color='red')
        for ii in range(len(rmag)):
            plt.text(roundsrc['x'][ii],
                     roundsrc['y'][ii],
                     " " + str(rmag[ii]),
                     color='red')
        plt.title('Round sources with SDSS r mag')
        pdf.savefig()  # saves the current figure into a pdf page
        plt.close()

        ###########################
        #display FWHM             #
        ###########################
        plt.figure(figsize=(10, 10))
        plt.scatter(rmag, fwhm, color='red')
        plt.xlabel('Source rmag')
        plt.ylabel('FWHM (arcsec)')
        plt.title('Median FWHM {}'.format(np.median(fwhm)))
        pdf.savefig()  # saves the current figure into a pdf page
        plt.close()

        ###########################
        #check centroid           #
        ###########################
        plt.figure(figsize=(10, 10))

        #loop on exposures
        for tmpc in open(cubeslist, 'r'):
            thisob = tmpc.split('/')[1]
            thisexp = tmpc.split('_')[3]
            wname = '../{}/Proc/DATACUBE_FINAL_LINEWCS_{}_white2.fits'.format(
                thisob, thisexp)
            wfits = fits.open(wname)

            #now loop on sources
            delta_x = np.zeros(len(rmag))
            delta_y = np.zeros(len(rmag))
            for ii in range(len(rmag)):
                subdata=wfits[0].data[roundsrc['y'][ii]-10:roundsrc['y'][ii]+10,\
                                          roundsrc['x'][ii]-10:roundsrc['x'][ii]+10]
                x1, y1 = centroids.centroid_2dg(subdata)
                delta_x[ii] = 10.5 - x1
                delta_y[ii] = 10.5 - y1

            #plot for this subunit
            plt.scatter(delta_x * rsdssall[0].header['PC2_2'] * 3600.,
                        delta_y * rsdssall[0].header['PC2_2'] * 3600.)

        plt.xlabel('Delta x (arcsec)')
        plt.ylabel('Delta y (arcsec)')
        plt.title('Check exposure aligment')
        pdf.savefig()  # saves the current figure into a pdf page
        plt.close()

        ###########################
        #check fluxing            #
        ###########################

        #make a check on fluxing
        plt.figure(figsize=(10, 10))

        #loop on exposures
        for tmpc in open(cubeslist, 'r'):
            thisob = tmpc.split('/')[1]
            thisexp = tmpc.split('_')[3]
            wname = '../{}/Proc/DATACUBE_FINAL_LINEWCS_{}_white2.fits'.format(
                thisob, thisexp)
            wfits = fits.open(wname)

            phot_this_white = aperture_photometry(wfits[0].data, apertures)
            wmag_this = -2.5 * np.log10(phot_this_white['aperture_sum'])

            #plot for this subunit
            ii = np.argsort(rmag)
            dd = wmag - wmag_this
            plt.plot(rmag[ii], dd[ii], label=thisob + thisexp)

        plt.xlabel('SDSS R mag')
        plt.ylabel('Delta White Mag')
        plt.title('Check exposure photometry')
        plt.legend()
        pdf.savefig()  # saves the current figure into a pdf page
        plt.close()

        #display rms stats + compute stats over cubes
        plt.figure(figsize=(10, 10))
        plt.semilogy(cwrms, crms, label='Coadd')

        for tmpc in open(cubeslist, 'r'):
            cwrms_this, crms_this = mutil.cubestat(tmpc.strip(),
                                                   mask=maskwbuffer)
            plt.semilogy(cwrms_this,
                         crms_this,
                         label=tmpc.split('/')[1] + tmpc.split('_')[3])

        plt.xlabel('Wave (A)')
        plt.ylabel('RMS (SB units)')
        plt.legend()
        plt.title('RMS in cubex')
        pdf.savefig()  # saves the current figure into a pdf page
        plt.close()
Exemple #15
0
def deblend_stars(image,
                  ew_map=None,
                  sigma_thresh=3.,
                  fwhm_pix=3.,
                  npixels=15,
                  nlevels=32,
                  contrast=0.01,
                  size_thresh=2.,
                  dist_to_center_thresh=10.,
                  area_thresh=200.,
                  ew_thresh=20.,
                  output_dir='',
                  save_fits=False):
    """
    This method estimates foreground stars over a given image.
    Additional constrains can be applied by providing an EW(Ha) map, preventing
    giant HII regions to be classified as stars.
    ---------------------------------------------------------------------------
    input params:
        - image: (2D array) Flux image used to deblend the sources
        - ew_map (optional): (2D array) EW(Ha) map used to further
        constrain the sources
        - sigma_thresh (float, default=3.0): number of stddev for estimating
        the signal threshold
        - fwhm_pix (float, default=3.0): FWHM for Gaussian smoothing prior to
        source detection
        - npixels (int, default=15): The number of connected pixels, each
        greater than threshold, that an object must have to be detected.
        - nlevels (int, default=15): The number of multi-thresholding levels
        to use
        - contrast (float, default=0.01)
        - size_thres (float, defualt=2.)
        -
        -
        - output_dir (optional, defatult=''): (str) directory where the results
        will be saved
        - save_filts (optional, default=False): Set True for saving the stellar
        masks
    """
    if ew_map is None:
        ew = np.zeros_like(image)
    rows, columns = image.shape
    centerx, centery = rows // 2, columns // 2
    XX, YY = np.meshgrid(np.arange(columns), np.arange(rows))
    # Estimating the image signal threshold
    threshold = detect_threshold(image, nsigma=sigma_thresh)
    # Kernel smoothing
    # sigma_pixels = FWHM_pixels / conversion_factor
    kernel_sigma = fwhm_pix / (2.0 * np.sqrt(2.0 * np.log(2.0)))
    kernel = Gaussian2DKernel(kernel_sigma,
                              x_size=int(fwhm_pix),
                              y_size=int(fwhm_pix))
    kernel.normalize()
    # Source detection
    segm = detect_sources(image, threshold, npixels, filter_kernel=kernel)
    # Source deblending
    segm_deblend = deblend_sources(image,
                                   segm,
                                   npixels=npixels,
                                   filter_kernel=kernel,
                                   nlevels=nlevels,
                                   contrast=0.01)
    deblended_areas = segm_deblend.areas
    stellar_masks = []

    if deblended_areas.size > 1:
        mask = segm_deblend.data
        cmap = segm_deblend.make_cmap()

        plt.figure()
        plt.imshow(segm_deblend, origin='lower', cmap=cmap)
        plt.colorbar()
        plt.contour(image, levels=20, colors='k', origin='lower')
        for ii in range(deblended_areas.size):
            plt.annotate(r'Pixel area: {}'.format(deblended_areas[ii]),
                         xy=(.02, .95 - ii * 0.05),
                         xycoords='axes fraction',
                         ha='left',
                         va='top',
                         color=cmap(ii + 1),
                         fontsize=9)
        plt.savefig(output_dir + 'deblending_map.png')
        plt.close()

        for ii in range(deblended_areas.size):
            source = mask == ii + 1
            source_area = deblended_areas[ii]
            source_image = np.copy(image)
            source_image[~source] = 0
            pos_x, pos_y = centroid_2dg(source_image)
            dist_to_center = np.sqrt((pos_x - centerx)**2 +
                                     (pos_y - centery)**2)
            amplitude_guess = source_image.max()
            initial_guess = [amplitude_guess, pos_x, pos_y, 3, 3, 0]
            try:
                popt, pcov = curve_fit(
                    gaussian2d,
                    [XX, YY],
                    source_image.ravel(),
                    p0=initial_guess,
                    # bounds=(0, [amplitude_guess*2, 80, 80, 5, 5])
                )
            except:
                print('Error fitting gaussian to data')
                continue
            popt = np.abs(popt)
            sigmax, sigmay = popt[3], popt[4]
            gaussian = gaussian2d([XX, YY], *popt).reshape(image.shape)
            level = gaussian2d([
                popt[1] + 3 * max([popt[3], 2]),
                popt[2] + 3 * max([popt[4], 2])
            ], *popt)
            central_pixels = gaussian2d([popt[1] + sigmax, popt[2] + sigmay],
                                        *popt)
            star_mask = gaussian > level
            central_star_mask = gaussian > central_pixels
            ew_source = ew[central_star_mask]
            median_ew = np.nanmedian(ew_source)
            if np.isnan(median_ew):
                median_ew = 0.
            ellipticity = sigmax / sigmay
            # chi2 = np.sum(chi2[star_mask])
            if (sigmax < size_thresh) & (sigmay < size_thresh):
                # &(ellipticity<1.5)&(ellipticity>0.5)
                if (dist_to_center < dist_to_center_thresh) & (
                        source_area < area_thresh) & (median_ew <= ew_thresh):
                    is_star = True
                    stellar_masks.append(star_mask)
                elif (dist_to_center > dist_to_center_thresh) & (median_ew <=
                                                                 ew_thresh):
                    is_star = True
                    stellar_masks.append(star_mask)
                else:
                    is_star = False
            else:
                is_star = False

            plt.figure(figsize=(4, 4))
            plt.subplot(221)
            plt.plot(XX[0, :], np.sum(source_image, axis=0), 'k')
            plt.plot(XX[0, :], np.sum(gaussian, axis=0), 'r')
            plt.annotate(r'$\sigma_x$={:5.3}'.format(sigmax),
                         xy=(.1, .9),
                         xycoords='axes fraction',
                         ha='left',
                         va='top')
            plt.subplot(224)
            plt.plot(np.sum(source_image, axis=1), YY[:, 0], 'k')
            plt.plot(np.sum(gaussian, axis=1), YY[:, 0], 'r')
            plt.annotate(r'$\sigma_y$={:5.3}'.format(sigmay),
                         xy=(.9, .9),
                         xycoords='axes fraction',
                         ha='right',
                         va='top')
            plt.subplot(223)
            plt.imshow(image, cmap='gist_earth', aspect='auto', origin='lower')
            if is_star:
                plt.plot(pos_x, pos_y, '*', c='lime', markersize=10)
            else:
                plt.plot(pos_x, pos_y, '+', c='lime', markersize=10)
            plt.contour(gaussian, colors='r', levels=level, linewidths=2)
            plt.annotate(r'$\sigma_x/\sigma_y$={:5.3}'.format(ellipticity),
                         xy=(.05, .99),
                         xycoords='axes fraction',
                         ha='left',
                         va='top',
                         fontsize=8)
            plt.annotate(r'$EW(H\alpha)_{50}$' + '={:5.3}'.format(median_ew),
                         xy=(.05, .90),
                         xycoords='axes fraction',
                         ha='left',
                         va='top',
                         fontsize=8)
            plt.savefig(output_dir + 'detection_' + str(ii) + '.png')
            plt.close()
            # plt.xlim(pos_x-15,pos_x+15)
            # plt.ylim(pos_y-15,pos_y+15)

    stellar_masks = np.array(stellar_masks)

    if stellar_masks.size > 0:
        print('-->', stellar_masks.shape[0], ' stars detected')
        total_mask = np.zeros_like(image, dtype=bool)
        for i in range(stellar_masks.shape[0]):
            total_mask = total_mask | np.array(stellar_masks[i, :, :],
                                               dtype=bool)
        if save_fits:
            fits_path = output_dir + 'stellar_mask.fits'
            hdr = fits.Header()
            hdr['COMMENT'] = "Stellar masks"
            image_list = []
            image_list.append(fits.PrimaryHDU(header=hdr))
            for ii in range(stellar_masks.shape[0]):
                image_list.append(
                    fits.ImageHDU(np.array(stellar_masks[ii, :, :],
                                           dtype=int)))
            image_list.append(np.array(total_mask, dtype=int))
            hdu = fits.HDUList(image_list)
            hdu.writeto(fits_path, overwrite=True)
            hdu.close()
            print('File saved as: ' + fits_path)
        return total_mask, stellar_masks.shape[0]
    else:
        return np.zeros_like(image, dtype=bool), 0
Exemple #16
0
def centroider(target,
               sources,
               output_plots=False,
               gif=False,
               restore=False,
               box_w=8):
    matplotlib.use('TkAgg')
    plt.ioff()
    t1 = time.time()
    pines_path = pines_dir_check()
    short_name = short_name_creator(target)

    kernel = Gaussian2DKernel(x_stddev=1)  #For fixing nans in cutouts.

    #If restore == True, read in existing output and return.
    if restore:
        centroid_df = pd.read_csv(
            pines_path / ('Objects/' + short_name +
                          '/sources/target_and_references_centroids.csv'),
            converters={
                'X Centroids': eval,
                'Y Centroids': eval
            })
        print('Restoring centroider output from {}.'.format(
            pines_path / ('Objects/' + short_name +
                          '/sources/target_and_references_centroids.csv')))
        print('')
        return centroid_df

    #Create subdirectories in sources folder to contain output plots.
    if output_plots:
        subdirs = glob(
            str(pines_path / ('Objects/' + short_name + '/sources')) + '/*/')
        #Delete any source directories that are already there.
        for name in subdirs:
            shutil.rmtree(name)

        #Create new source directories.
        for name in sources['Name']:
            source_path = (
                pines_path /
                ('Objects/' + short_name + '/sources/' + name + '/'))
            os.mkdir(source_path)

    #Read in extra shifts, in case the master image wasn't used for source detection.
    extra_shift_path = pines_path / ('Objects/' + short_name +
                                     '/sources/extra_shifts.txt')
    extra_shifts = pd.read_csv(extra_shift_path,
                               delimiter=' ',
                               names=['Extra X shift', 'Extra Y shift'])
    extra_x_shift = extra_shifts['Extra X shift'][0]
    extra_y_shift = extra_shifts['Extra Y shift'][0]

    np.seterr(
        divide='ignore', invalid='ignore'
    )  #Suppress some warnings we don't care about in median combining.

    #Get list of reduced files for target.
    reduced_path = pines_path / ('Objects/' + short_name + '/reduced')
    reduced_filenames = natsort.natsorted(
        [x.name for x in reduced_path.glob('*red.fits')])
    reduced_files = np.array([reduced_path / i for i in reduced_filenames])

    #Declare a new dataframe to hold the centroid information for all sources we want to track.
    columns = []
    columns.append('Filename')
    columns.append('Seeing')
    columns.append('Time (JD UTC)')
    columns.append('Airmass')

    #Add x/y positions and cenroid flags for every tracked source
    for i in range(0, len(sources)):
        columns.append(sources['Name'][i] + ' Image X')
        columns.append(sources['Name'][i] + ' Image Y')
        columns.append(sources['Name'][i] + ' Cutout X')
        columns.append(sources['Name'][i] + ' Cutout Y')
        columns.append(sources['Name'][i] + ' Centroid Warning')

    centroid_df = pd.DataFrame(index=range(len(reduced_files)),
                               columns=columns)

    log_path = pines_path / ('Logs/')
    log_dates = np.array(
        natsort.natsorted(
            [x.name.split('_')[0] for x in log_path.glob('*.txt')]))

    #Make sure we have logs for all the nights of these data. Need them to account for image shifts.
    nights = list(set([i.name.split('.')[0] for i in reduced_files]))
    for i in nights:
        if i not in log_dates:
            print('ERROR: {} not in {}. Download it from the PINES server.'.
                  format(i + '_log.txt', log_path))
            pdb.set_trace()

    shift_tolerance = 2.0  #Number of pixels that the measured centroid can be away from the expected position in either x or y before trying other centroiding algorithms.
    for i in range(len(sources)):
        #Get the initial source position.
        x_pos = sources['Source Detect X'][i]
        y_pos = sources['Source Detect Y'][i]
        print('')
        print(
            'Getting centroids for {}, ({:3.1f}, {:3.1f}) in source detection image. Source {} of {}.'
            .format(sources['Name'][i], x_pos, y_pos, i + 1, len(sources)))
        if output_plots:
            print('Saving centroid plots to {}.'.format(
                pines_path / ('Objects/' + short_name + '/sources/' +
                              sources['Name'][i] + '/')))
        pbar = ProgressBar()
        for j in pbar(range(len(reduced_files))):
            centroid_df[sources['Name'][i] + ' Centroid Warning'][j] = 0
            file = reduced_files[j]
            image = fits.open(file)[0].data
            #Get the measured image shift for this image.
            log = pines_log_reader(log_path /
                                   (file.name.split('.')[0] + '_log.txt'))
            log_ind = np.where(log['Filename'] == file.name.split('_')[0] +
                               '.fits')[0][0]

            x_shift = float(log['X shift'][log_ind])
            y_shift = float(log['Y shift'][log_ind])

            #Save the filename for readability. Save the seeing for use in variable aperture photometry. Save the time for diagnostic plots.
            if i == 0:
                centroid_df['Filename'][j] = file.name.split('_')[0] + '.fits'
                centroid_df['Seeing'][j] = log['X seeing'][log_ind]
                time_str = fits.open(file)[0].header['DATE-OBS']

                #Correct some formatting issues that can occur in Mimir time stamps.
                if time_str.split(':')[-1] == '60.00':
                    time_str = time_str[0:14] + str(
                        int(time_str.split(':')[-2]) + 1) + ':00.00'
                elif time_str.split(':')[-1] == '010.00':
                    time_str = time_str[0:17] + time_str.split(':')[-1][1:]

                centroid_df['Time (JD UTC)'][j] = julian.to_jd(
                    datetime.datetime.strptime(time_str,
                                               '%Y-%m-%dT%H:%M:%S.%f'))
                centroid_df['Airmass'][j] = log['Airmass'][log_ind]

            nan_flag = False  #Flag indicating if you should not trust the log's shifts. Set to true if x_shift/y_shift are 'nan' or > 30 pixels.

            #If bad shifts were measured for this image, skip.
            if log['Shift quality flag'][log_ind] == 1:
                continue

            if np.isnan(x_shift) or np.isnan(y_shift):
                x_shift = 0
                y_shift = 0
                nan_flag = True

            #If there are clouds, shifts could have been erroneously high...just zero them?
            if abs(x_shift) > 200:
                #x_shift = 0
                nan_flag = True
            if abs(y_shift) > 200:
                #y_shift = 0
                nan_flag = True

            #Apply the shift. NOTE: This relies on having accurate x_shift and y_shift values from the log.
            #If they're incorrect, the cutout will not be in the right place.
            #x_pos = sources['Source Detect X'][i] - x_shift + extra_x_shift
            #y_pos = sources['Source Detect Y'][i] + y_shift - extra_y_shift

            x_pos = sources['Source Detect X'][i] - (x_shift - extra_x_shift)
            y_pos = sources['Source Detect Y'][i] + (y_shift - extra_y_shift)

            #TODO: Make all this its own function.

            #Cutout around the expected position and interpolate over any NaNs (which screw up source detection).
            cutout = interpolate_replace_nans(
                image[int(y_pos - box_w):int(y_pos + box_w) + 1,
                      int(x_pos - box_w):int(x_pos + box_w) + 1],
                kernel=Gaussian2DKernel(x_stddev=0.5))

            #interpolate_replace_nans struggles with edge pixels, so shave off edge_shave pixels in each direction of the cutout.
            edge_shave = 1
            cutout = cutout[edge_shave:len(cutout) - edge_shave,
                            edge_shave:len(cutout) - edge_shave]

            vals, lower, upper = sigmaclip(
                cutout, low=1.5,
                high=2.5)  #Get sigma clipped stats on the cutout
            med = np.nanmedian(vals)
            std = np.nanstd(vals)

            try:
                centroid_x_cutout, centroid_y_cutout = centroid_2dg(
                    cutout - med)  #Perform centroid detection on the cutout.
            except:
                pdb.set_trace()

            centroid_x = centroid_x_cutout + int(
                x_pos
            ) - box_w + edge_shave  #Translate the detected centroid from the cutout coordinates back to the full-frame coordinates.
            centroid_y = centroid_y_cutout + int(y_pos) - box_w + edge_shave

            # if i == 0:
            #     qp(cutout)
            #     plt.plot(centroid_x_cutout, centroid_y_cutout, 'rx')

            #     # qp(image)
            #     # plt.plot(centroid_x, centroid_y, 'rx')
            #     pdb.set_trace()

            #If the shifts in the log are not 'nan' or > 200 pixels, check if the measured shifts are within shift_tolerance pixels of the expected position.
            #   If they aren't, try alternate centroiding methods to try and find it.

            #Otherwise, use the shifts as measured with centroid_1dg. PINES_watchdog likely failed while observing, and we don't expect the centroids measured here to actually be at the expected position.
            if not nan_flag:
                #Try a 2D Gaussian detection.
                if (abs(centroid_x - x_pos) > shift_tolerance) or (
                        abs(centroid_y - y_pos) > shift_tolerance):
                    centroid_x_cutout, centroid_y_cutout = centroid_2dg(
                        cutout - med)
                    centroid_x = centroid_x_cutout + int(x_pos) - box_w
                    centroid_y = centroid_y_cutout + int(y_pos) - box_w

                    #If that fails, try a COM detection.
                    if (abs(centroid_x - x_pos) > shift_tolerance) or (
                            abs(centroid_y - y_pos) > shift_tolerance):
                        centroid_x_cutout, centroid_y_cutout = centroid_com(
                            cutout - med)
                        centroid_x = centroid_x_cutout + int(x_pos) - box_w
                        centroid_y = centroid_y_cutout + int(y_pos) - box_w

                        #If that fails, try masking source and interpolate over any bad pixels that aren't in the bad pixel mask, then redo 1D gaussian detection.
                        if (abs(centroid_x - x_pos) > shift_tolerance) or (
                                abs(centroid_y - y_pos) > shift_tolerance):
                            mask = make_source_mask(cutout,
                                                    nsigma=4,
                                                    npixels=5,
                                                    dilate_size=3)
                            vals, lo, hi = sigmaclip(cutout[~mask])
                            bad_locs = np.where((mask == False) & (
                                (cutout > hi) | (cutout < lo)))
                            cutout[bad_locs] = np.nan
                            cutout = interpolate_replace_nans(
                                cutout, kernel=Gaussian2DKernel(x_stddev=0.5))

                            centroid_x_cutout, centroid_y_cutout = centroid_1dg(
                                cutout - med)
                            centroid_x = centroid_x_cutout + int(x_pos) - box_w
                            centroid_y = centroid_y_cutout + int(y_pos) - box_w

                            #Try a 2D Gaussian detection on the interpolated cutout
                            if (abs(centroid_x - x_pos) > shift_tolerance) or (
                                    abs(centroid_y - y_pos) > shift_tolerance):
                                centroid_x_cutout, centroid_y_cutout = centroid_2dg(
                                    cutout - med)
                                centroid_x = centroid_x_cutout + int(
                                    x_pos) - box_w
                                centroid_y = centroid_y_cutout + int(
                                    y_pos) - box_w

                                #Try a COM on the interpolated cutout.
                                if (abs(centroid_x - x_pos) > shift_tolerance
                                    ) or (abs(centroid_y - y_pos) >
                                          shift_tolerance):
                                    centroid_x_cutout, centroid_y_cutout = centroid_com(
                                        cutout)
                                    centroid_x = centroid_x_cutout + int(
                                        x_pos) - box_w
                                    centroid_y = centroid_y_cutout + int(
                                        y_pos) - box_w

                                    #Last resort: try cutting off the edge of the cutout. Edge pixels can experience poor interpolation, and this sometimes helps.
                                    if (abs(centroid_x - x_pos) >
                                            shift_tolerance) or (
                                                abs(centroid_y - y_pos) >
                                                shift_tolerance):
                                        cutout = cutout[1:-1, 1:-1]
                                        centroid_x_cutout, centroid_y_cutout = centroid_1dg(
                                            cutout - med)
                                        centroid_x = centroid_x_cutout + int(
                                            x_pos) - box_w + 1
                                        centroid_y = centroid_y_cutout + int(
                                            y_pos) - box_w + 1

                                        #Try with a 2DG
                                        if (abs(centroid_x - x_pos) >
                                                shift_tolerance) or (
                                                    abs(centroid_y - y_pos) >
                                                    shift_tolerance):
                                            centroid_x_cutout, centroid_y_cutout = centroid_2dg(
                                                cutout - med)
                                            centroid_x = centroid_x_cutout + int(
                                                x_pos) - box_w + 1
                                            centroid_y = centroid_y_cutout + int(
                                                y_pos) - box_w + 1

                                            #If ALL that fails, report the expected position as the centroid.
                                            if (abs(centroid_x - x_pos) >
                                                    shift_tolerance) or (
                                                        abs(centroid_y - y_pos)
                                                        > shift_tolerance):
                                                print(
                                                    'WARNING: large centroid deviation measured, returning predicted position'
                                                )
                                                print('')
                                                centroid_df[
                                                    sources['Name'][i] +
                                                    ' Centroid Warning'][j] = 1
                                                centroid_x = x_pos
                                                centroid_y = y_pos
                                                #pdb.set_trace()

            #Check that your measured position is actually on the detector.
            if (centroid_x < 0) or (centroid_y < 0) or (centroid_x > 1023) or (
                    centroid_y > 1023):
                #Try a quick mask/interpolation of the cutout.
                mask = make_source_mask(cutout,
                                        nsigma=3,
                                        npixels=5,
                                        dilate_size=3)
                vals, lo, hi = sigmaclip(cutout[~mask])
                bad_locs = np.where((mask == False)
                                    & ((cutout > hi) | (cutout < lo)))
                cutout[bad_locs] = np.nan
                cutout = interpolate_replace_nans(
                    cutout, kernel=Gaussian2DKernel(x_stddev=0.5))
                centroid_x, centroid_y = centroid_2dg(cutout - med)
                centroid_x += int(x_pos) - box_w
                centroid_y += int(y_pos) - box_w
                if (centroid_x < 0) or (centroid_y < 0) or (
                        centroid_x > 1023) or (centroid_y > 1023):
                    print(
                        'WARNING: large centroid deviation measured, returning predicted position'
                    )
                    print('')
                    centroid_df[sources['Name'][i] +
                                ' Centroid Warning'][j] = 1
                    centroid_x = x_pos
                    centroid_y = y_pos
                    #pdb.set_trace()

            #Check to make sure you didn't measure nan's.
            if np.isnan(centroid_x):
                centroid_x = x_pos
                print(
                    'NaN returned from centroid algorithm, defaulting to target position in source_detct_image.'
                )
            if np.isnan(centroid_y):
                centroid_y = y_pos
                print(
                    'NaN returned from centroid algorithm, defaulting to target position in source_detct_image.'
                )

            #Record the image and relative cutout positions.
            centroid_df[sources['Name'][i] + ' Image X'][j] = centroid_x
            centroid_df[sources['Name'][i] + ' Image Y'][j] = centroid_y
            centroid_df[sources['Name'][i] +
                        ' Cutout X'][j] = centroid_x_cutout
            centroid_df[sources['Name'][i] +
                        ' Cutout Y'][j] = centroid_y_cutout

            if output_plots:
                #Plot
                lock_x = int(centroid_df[sources['Name'][i] + ' Image X'][0])
                lock_y = int(centroid_df[sources['Name'][i] + ' Image Y'][0])
                norm = ImageNormalize(data=cutout, interval=ZScaleInterval())
                plt.imshow(image, origin='lower', norm=norm)
                plt.plot(centroid_x, centroid_y, 'rx')
                ap = CircularAperture((centroid_x, centroid_y), r=5)
                ap.plot(lw=2, color='b')
                plt.ylim(lock_y - 30, lock_y + 30 - 1)
                plt.xlim(lock_x - 30, lock_x + 30 - 1)
                plt.title('CENTROID DIAGNOSTIC PLOT\n' + sources['Name'][i] +
                          ', ' + reduced_files[j].name + ' (image ' +
                          str(j + 1) + ' of ' + str(len(reduced_files)) + ')',
                          fontsize=10)
                plt.text(centroid_x,
                         centroid_y + 0.5,
                         '(' + str(np.round(centroid_x, 1)) + ', ' +
                         str(np.round(centroid_y, 1)) + ')',
                         color='r',
                         ha='center')
                plot_output_path = (
                    pines_path /
                    ('Objects/' + short_name + '/sources/' +
                     sources['Name'][i] + '/' + str(j).zfill(4) + '.jpg'))
                plt.gca().set_axis_off()
                plt.subplots_adjust(top=1,
                                    bottom=0,
                                    right=1,
                                    left=0,
                                    hspace=0,
                                    wspace=0)
                plt.margins(0, 0)
                plt.gca().xaxis.set_major_locator(plt.NullLocator())
                plt.gca().yaxis.set_major_locator(plt.NullLocator())
                plt.savefig(plot_output_path,
                            bbox_inches='tight',
                            pad_inches=0,
                            dpi=150)
                plt.close()

        if gif:
            gif_path = (pines_path / ('Objects/' + short_name + '/sources/' +
                                      sources['Name'][i] + '/'))
            gif_maker(path=gif_path, fps=10)

    output_filename = pines_path / (
        'Objects/' + short_name +
        '/sources/target_and_references_centroids.csv')
    #centroid_df.to_csv(pines_path/('Objects/'+short_name+'/sources/target_and_references_centroids.csv'))

    print('Saving centroiding output to {}.'.format(output_filename))
    with open(output_filename, 'w') as f:
        for j in range(len(centroid_df)):
            #Write the header line.
            if j == 0:
                f.write('{:<17s}, '.format('Filename'))
                f.write('{:<15s}, '.format('Time (JD UTC)'))
                f.write('{:<6s}, '.format('Seeing'))
                f.write('{:<7s}, '.format('Airmass'))
                for i in range(len(sources['Name'])):
                    n = sources['Name'][i]
                    if i != len(sources['Name']) - 1:
                        f.write(
                            '{:<23s}, {:<23s}, {:<24s}, {:<24s}, {:<34s}, '.
                            format(n + ' Image X', n + ' Image Y',
                                   n + ' Cutout X', n + ' Cutout Y',
                                   n + ' Centroid Warning'))
                    else:
                        f.write(
                            '{:<23s}, {:<23s}, {:<24s}, {:<24s}, {:<34s}\n'.
                            format(n + ' Image X', n + ' Image Y',
                                   n + ' Cutout X', n + ' Cutout Y',
                                   n + ' Centroid Warning'))

            #Write in the data lines.
            try:
                f.write('{:<17s}, '.format(centroid_df['Filename'][j]))
                f.write('{:<15.7f}, '.format(centroid_df['Time (JD UTC)'][j]))
                f.write('{:<6.1f}, '.format(float(centroid_df['Seeing'][j])))
                f.write('{:<7.2f}, '.format(centroid_df['Airmass'][j]))
            except:
                pdb.set_trace()

            for i in range(len(sources['Name'])):
                n = sources['Name'][i]
                if i != len(sources['Name']) - 1:
                    format_string = '{:<23.4f}, {:<23.4f}, {:<24.4f}, {:<24.4f}, {:<34d}, '
                else:
                    format_string = '{:<23.4f}, {:<23.4f}, {:<24.4f}, {:<24.4f}, {:<34d}\n'

                f.write(
                    format_string.format(
                        centroid_df[n + ' Image X'][j],
                        centroid_df[n + ' Image Y'][j],
                        centroid_df[n + ' Cutout X'][j],
                        centroid_df[n + ' Cutout Y'][j],
                        centroid_df[n + ' Centroid Warning'][j]))
    np.seterr(divide='warn', invalid='warn')
    print('')
    print('centroider runtime: {:.2f} minutes.'.format(
        (time.time() - t1) / 60))
    print('')
    return centroid_df