Instruction:
Write a Python 3 script to
calculate the expected bias on each of the parameters in the model.

Response:
def RB_bias(data, pars, ita=None, acf=None):
    """
    Calculate the expected bias on each of the parameters in the model pars.
    Only parameters that are allowed to vary will have a bias.
    Calculation follows the description of Refrieger & Brown 1998 (cite).

    Parameters
    ----------
    data : 2d-array
        data that was fit

    pars : lmfit.Parameters
        The model

    ita : 2d-array
        The ita matrix (optional).

    acf : 2d-array
        The acf for the data.

    Returns
    -------
    bias : array
        The bias on each of the parameters
    """
    log.info("data {0}".format(data.shape))
    nparams = np.sum([pars[k].vary for k in pars.keys() if k != 'components'])
    # masked pixels
    xm, ym = np.where(np.isfinite(data))
    # all pixels
    x, y = np.indices(data.shape)
    # Create the jacobian as an AxN array accounting for the masked pixels
    j = np.array(np.vsplit(lmfit_jacobian(pars, xm, ym).T, nparams)).reshape(nparams, -1)

    h = hessian(pars, x, y)
    # mask the hessian to be AxAxN array
    h = h[:, :, xm, ym]
    Hij = np.einsum('ik,jk', j, j)
    Dij = np.linalg.inv(Hij)
    Bijk = np.einsum('ip,jkp', j, h)
    Eilkm = np.einsum('il,km', Dij, Dij)

    Cimn_1 =    -1 * np.einsum('krj,ir,km,jn', Bijk, Dij, Dij, Dij)
    Cimn_2 = -1./2 * np.einsum('rkj,ir,km,jn', Bijk, Dij, Dij, Dij)
    Cimn = Cimn_1 + Cimn_2

    if ita is None:
        # N is the noise (data-model)
        N = data - ntwodgaussian_lmfit(pars)(x, y)
        if acf is None:
            acf = nan_acf(N)
        ita = make_ita(N, acf=acf)
        log.info('acf.shape {0}'.format(acf.shape))
        log.info('acf[0] {0}'.format(acf[0]))
        log.info('ita.shape {0}'.format(ita.shape))
        log.info('ita[0] {0}'.format(ita[0]))

    # Included for completeness but not required

    # now mask/ravel the noise
    # N = N[np.isfinite(N)].ravel()
    # Pi = np.einsum('ip,p', j, N)
    # Qij = np.einsum('ijp,p', h, N)

    Vij = np.einsum('ip,jq,pq', j, j, ita)
    Uijk = np.einsum('ip,jkq,pq', j, h, ita)

    bias_1 = np.einsum('imn, mn', Cimn, Vij)
    bias_2 = np.einsum('ilkm, mlk', Eilkm, Uijk)
    bias = bias_1 + bias_2
    log.info('bias {0}'.format(bias))
    return bias


Instruction:
Make a summary of the following Python 3 code
def bias_correct(params, data, acf=None):
    bias = RB_bias(data, params, acf=acf)
    i = 0
    for p in params:
        if 'theta' in p:
            continue
        if params[p].vary:
            params[p].value -= bias[i]
            i += 1
    return

Response: Calculate and apply a bias correction to the given fit parameters.


Instruction:
Can you create a Python 3 function that
calculates the parameter errors for a fitted source based on the conditions of the source.

Response:
def condon_errors(source, theta_n, psf=None):
    """
    Calculate the parameter errors for a fitted source
    using the description of Condon'97
    All parameters are assigned errors, assuming that all params were fit.
    If some params were held fixed then these errors are overestimated.

    Parameters
    ----------
    source : :class:`AegeanTools.models.SimpleSource`
        The source which was fit.

    theta_n : float or None
        A measure of the beam sampling. (See Condon'97).

    psf : :class:`AegeanTools.fits_image.Beam`
        The psf at the location of the source.

    Returns
    -------
    None

    """

    # indices for the calculation or rho
    alphas = {'amp': (3. / 2, 3. / 2),
              'major': (5. / 2, 1. / 2),
              'xo': (5. / 2, 1. / 2),
              'minor': (1. / 2, 5. / 2),
              'yo': (1. / 2, 5. / 2),
              'pa': (1. / 2, 5. / 2)}

    major = source.a / 3600.  # degrees
    minor = source.b / 3600.  # degrees
    phi = np.radians(source.pa)  # radians
    if psf is not None:
        beam = psf.get_beam(source.ra, source.dec)
        if beam is not None:
            theta_n = np.hypot(beam.a, beam.b)
            print(beam, theta_n)

    if theta_n is None:
        source.err_a = source.err_b = source.err_peak_flux = source.err_pa = source.err_int_flux = 0.0
        return

    smoothing = major * minor / (theta_n ** 2)
    factor1 = (1 + (major / theta_n))
    factor2 = (1 + (minor / theta_n))
    snr = source.peak_flux / source.local_rms
    # calculation of rho2 depends on the parameter being used so we lambda this into a function
    rho2 = lambda x: smoothing / 4 * factor1 ** alphas[x][0] * factor2 ** alphas[x][1] * snr ** 2

    source.err_peak_flux = source.peak_flux * np.sqrt(2 / rho2('amp'))
    source.err_a = major * np.sqrt(2 / rho2('major')) * 3600.  # arcsec
    source.err_b = minor * np.sqrt(2 / rho2('minor')) * 3600.  # arcsec

    err_xo2 = 2. / rho2('xo') * major ** 2 / (8 * np.log(2))  # Condon'97 eq 21
    err_yo2 = 2. / rho2('yo') * minor ** 2 / (8 * np.log(2))
    source.err_ra = np.sqrt(err_xo2 * np.sin(phi)**2 + err_yo2 * np.cos(phi)**2)
    source.err_dec = np.sqrt(err_xo2 * np.cos(phi)**2 + err_yo2 * np.sin(phi)**2)

    if (major == 0) or (minor == 0):
        source.err_pa = ERR_MASK
    # if major/minor are very similar then we should not be able to figure out what pa is.
    elif abs(2 * (major-minor) / (major+minor)) < 0.01:
        source.err_pa = ERR_MASK
    else:
        source.err_pa = np.degrees(np.sqrt(4 / rho2('pa')) * (major * minor / (major ** 2 - minor ** 2)))

    # integrated flux error
    err2 = (source.err_peak_flux / source.peak_flux) ** 2
    err2 += (theta_n ** 2 / (major * minor)) * ((source.err_a / source.a) ** 2 + (source.err_b / source.b) ** 2)
    source.err_int_flux = source.int_flux * np.sqrt(err2)
    return


Instruction:
Explain what the following Python 3 code does
def errors(source, model, wcshelper):

    # if the source wasn't fit then all errors are -1
    if source.flags & (flags.NOTFIT | flags.FITERR):
        source.err_peak_flux = source.err_a = source.err_b = source.err_pa = ERR_MASK
        source.err_ra = source.err_dec = source.err_int_flux = ERR_MASK
        return source
    # copy the errors from the model
    prefix = "c{0}_".format(source.source)
    err_amp = model[prefix + 'amp'].stderr
    xo, yo = model[prefix + 'xo'].value, model[prefix + 'yo'].value
    err_xo = model[prefix + 'xo'].stderr
    err_yo = model[prefix + 'yo'].stderr

    sx, sy = model[prefix + 'sx'].value, model[prefix + 'sy'].value
    err_sx = model[prefix + 'sx'].stderr
    err_sy = model[prefix + 'sy'].stderr

    theta = model[prefix + 'theta'].value
    err_theta = model[prefix + 'theta'].stderr

    source.err_peak_flux = err_amp
    pix_errs = [err_xo, err_yo, err_sx, err_sy, err_theta]

    log.debug("Pix errs: {0}".format(pix_errs))

    ref = wcshelper.pix2sky([xo, yo])
    # check to see if the reference position has a valid WCS coordinate
    # It is possible for this to fail, even if the ra/dec conversion works elsewhere
    if not all(np.isfinite(ref)):
        source.flags |= flags.WCSERR
        source.err_peak_flux = source.err_a = source.err_b = source.err_pa = ERR_MASK
        source.err_ra = source.err_dec = source.err_int_flux = ERR_MASK
        return source

    # position errors
    if model[prefix + 'xo'].vary and model[prefix + 'yo'].vary \
            and all(np.isfinite([err_xo, err_yo])):
        offset = wcshelper.pix2sky([xo + err_xo, yo + err_yo])
        source.err_ra = gcd(ref[0], ref[1], offset[0], ref[1])
        source.err_dec = gcd(ref[0], ref[1], ref[0], offset[1])
    else:
        source.err_ra = source.err_dec = -1

    if model[prefix + 'theta'].vary and np.isfinite(err_theta):
        # pa error
        off1 = wcshelper.pix2sky([xo + sx * np.cos(np.radians(theta)), yo + sy * np.sin(np.radians(theta))])
        off2 = wcshelper.pix2sky(
            [xo + sx * np.cos(np.radians(theta + err_theta)), yo + sy * np.sin(np.radians(theta + err_theta))])
        source.err_pa = abs(bear(ref[0], ref[1], off1[0], off1[1]) - bear(ref[0], ref[1], off2[0], off2[1]))
    else:
        source.err_pa = ERR_MASK

    if model[prefix + 'sx'].vary and model[prefix + 'sy'].vary \
            and all(np.isfinite([err_sx, err_sy])):
        # major axis error
        ref = wcshelper.pix2sky([xo + sx * np.cos(np.radians(theta)), yo + sy * np.sin(np.radians(theta))])
        offset = wcshelper.pix2sky(
            [xo + (sx + err_sx) * np.cos(np.radians(theta)), yo + sy * np.sin(np.radians(theta))])
        source.err_a = gcd(ref[0], ref[1], offset[0], offset[1]) * 3600

        # minor axis error
        ref = wcshelper.pix2sky([xo + sx * np.cos(np.radians(theta + 90)), yo + sy * np.sin(np.radians(theta + 90))])
        offset = wcshelper.pix2sky(
            [xo + sx * np.cos(np.radians(theta + 90)), yo + (sy + err_sy) * np.sin(np.radians(theta + 90))])
        source.err_b = gcd(ref[0], ref[1], offset[0], offset[1]) * 3600
    else:
        source.err_a = source.err_b = ERR_MASK

    sqerr = 0
    sqerr += (source.err_peak_flux / source.peak_flux) ** 2 if source.err_peak_flux > 0 else 0
    sqerr += (source.err_a / source.a) ** 2 if source.err_a > 0 else 0
    sqerr += (source.err_b / source.b) ** 2 if source.err_b > 0 else 0
    if sqerr == 0:
        source.err_int_flux = ERR_MASK
    else:
        source.err_int_flux = abs(source.int_flux * np.sqrt(sqerr))

    return source

Response: Convert pixel based errors into sky coord errors


Instruction:
Write a Python 3 function for
converting pixel based errors into sky coord errors Uses covariance matrix for ra dec errors Uses a linear approach to calculate the error in the source obejct.

Response:
def new_errors(source, model, wcshelper):  # pragma: no cover
    """
    Convert pixel based errors into sky coord errors
    Uses covariance matrix for ra/dec errors
    and calculus approach to a/b/pa errors

    Parameters
    ----------
    source : :class:`AegeanTools.models.SimpleSource`
        The source which was fit.

    model : lmfit.Parameters
        The model which was fit.

    wcshelper : :class:`AegeanTools.wcs_helpers.WCSHelper`
        WCS information.

    Returns
    -------
    source : :class:`AegeanTools.models.SimpleSource`
        The modified source obejct.

    """

    # if the source wasn't fit then all errors are -1
    if source.flags & (flags.NOTFIT | flags.FITERR):
        source.err_peak_flux = source.err_a = source.err_b = source.err_pa = ERR_MASK
        source.err_ra = source.err_dec = source.err_int_flux = ERR_MASK
        return source
    # copy the errors/values from the model
    prefix = "c{0}_".format(source.source)
    err_amp = model[prefix + 'amp'].stderr
    xo, yo = model[prefix + 'xo'].value, model[prefix + 'yo'].value
    err_xo = model[prefix + 'xo'].stderr
    err_yo = model[prefix + 'yo'].stderr

    sx, sy = model[prefix + 'sx'].value, model[prefix + 'sy'].value
    err_sx = model[prefix + 'sx'].stderr
    err_sy = model[prefix + 'sy'].stderr

    theta = model[prefix + 'theta'].value
    err_theta = model[prefix + 'theta'].stderr

    # the peak flux error doesn't need to be converted, just copied
    source.err_peak_flux = err_amp

    pix_errs = [err_xo, err_yo, err_sx, err_sy, err_theta]

    # check for inf/nan errors -> these sources have poor fits.
    if not all(a is not None and np.isfinite(a) for a in pix_errs):
        source.flags |= flags.FITERR
        source.err_peak_flux = source.err_a = source.err_b = source.err_pa = ERR_MASK
        source.err_ra = source.err_dec = source.err_int_flux = ERR_MASK
        return source

    # calculate the reference coordinate
    ref = wcshelper.pix2sky([xo, yo])
    # check to see if the reference position has a valid WCS coordinate
    # It is possible for this to fail, even if the ra/dec conversion works elsewhere
    if not all(np.isfinite(ref)):
        source.flags |= flags.WCSERR
        source.err_peak_flux = source.err_a = source.err_b = source.err_pa = ERR_MASK
        source.err_ra = source.err_dec = source.err_int_flux = ERR_MASK
        return source

    # calculate position errors by transforming the error ellipse
    if model[prefix + 'xo'].vary and model[prefix + 'yo'].vary:
        # determine the error ellipse from the Jacobian
        mat = model.covar[1:3, 1:3]
        if not(np.all(np.isfinite(mat))):
            source.err_ra = source.err_dec = ERR_MASK
        else:
            (a, b), e = np.linalg.eig(mat)
            pa = np.degrees(np.arctan2(*e[0]))
            # transform this ellipse into sky coordinates
            _, _, major, minor, pa = wcshelper.pix2sky_ellipse([xo, yo], a, b, pa)

            # now determine the radius of the ellipse along the ra/dec directions.
            source.err_ra = major*minor / np.hypot(major*np.sin(np.radians(pa)), minor*np.cos(np.radians(pa)))
            source.err_dec = major*minor / np.hypot(major*np.cos(np.radians(pa)), minor*np.sin(np.radians(pa)))
    else:
        source.err_ra = source.err_dec = -1

    if model[prefix + 'theta'].vary:
        # pa error
        off1 = wcshelper.pix2sky([xo + sx * np.cos(np.radians(theta)), yo + sy * np.sin(np.radians(theta))])
        # offset by 1 degree
        off2 = wcshelper.pix2sky(
            [xo + sx * np.cos(np.radians(theta + 1)), yo + sy * np.sin(np.radians(theta + 1))])
        # scale the initial theta error by this amount
        source.err_pa = abs(bear(ref[0], ref[1], off1[0], off1[1]) - bear(ref[0], ref[1], off2[0], off2[1])) * err_theta
    else:
        source.err_pa = ERR_MASK

    if model[prefix + 'sx'].vary and model[prefix + 'sy'].vary:
        # major axis error
        ref = wcshelper.pix2sky([xo + sx * np.cos(np.radians(theta)), yo + sy * np.sin(np.radians(theta))])
        # offset by 0.1 pixels
        offset = wcshelper.pix2sky(
            [xo + (sx + 0.1) * np.cos(np.radians(theta)), yo + sy * np.sin(np.radians(theta))])
        source.err_a = gcd(ref[0], ref[1], offset[0], offset[1])/0.1 * err_sx * 3600

        # minor axis error
        ref = wcshelper.pix2sky([xo + sx * np.cos(np.radians(theta + 90)), yo + sy * np.sin(np.radians(theta + 90))])
        # offset by 0.1 pixels
        offset = wcshelper.pix2sky(
            [xo + sx * np.cos(np.radians(theta + 90)), yo + (sy + 0.1) * np.sin(np.radians(theta + 90))])
        source.err_b = gcd(ref[0], ref[1], offset[0], offset[1])/0.1*err_sy * 3600
    else:
        source.err_a = source.err_b = ERR_MASK
    sqerr = 0
    sqerr += (source.err_peak_flux / source.peak_flux) ** 2 if source.err_peak_flux > 0 else 0
    sqerr += (source.err_a / source.a) ** 2 if source.err_a > 0 else 0
    sqerr += (source.err_b / source.b) ** 2 if source.err_b > 0 else 0
    source.err_int_flux = abs(source.int_flux * np.sqrt(sqerr))

    return source


Instruction:
Implement a Python 3 function for
returning a function that calculates the model given by params at pixel coordinates x y.

Response:
def ntwodgaussian_lmfit(params):
    """
    Convert an lmfit.Parameters object into a function which calculates the model.

    Parameters
    ----------
    params : lmfit.Parameters
        Model parameters, can have multiple components.

    Returns
    -------
    model : func
        A function f(x,y) that will compute the model.
    """

    def rfunc(x, y):
        """
        Compute the model given by params, at pixel coordinates x,y

        Parameters
        ----------
        x, y : numpy.ndarray
            The x/y pixel coordinates at which the model is being evaluated

        Returns
        -------
        result : numpy.ndarray
            Model
        """
        result = None
        for i in range(params['components'].value):
            prefix = "c{0}_".format(i)
            # I hope this doesn't kill our run time
            amp = np.nan_to_num(params[prefix + 'amp'].value)
            xo = params[prefix + 'xo'].value
            yo = params[prefix + 'yo'].value
            sx = params[prefix + 'sx'].value
            sy = params[prefix + 'sy'].value
            theta = params[prefix + 'theta'].value
            if result is not None:
                result += elliptical_gaussian(x, y, amp, xo, yo, sx, sy, theta)
            else:
                result = elliptical_gaussian(x, y, amp, xo, yo, sx, sy, theta)
        return result

    return rfunc


Instruction:
Given the following Python 3 function, write the documentation
def do_lmfit(data, params, B=None, errs=None, dojac=True):
    # copy the params so as not to change the initial conditions
    # in case we want to use them elsewhere
    params = copy.deepcopy(params)
    data = np.array(data)
    mask = np.where(np.isfinite(data))

    def residual(params, **kwargs):
        """
        The residual function required by lmfit

        Parameters
        ----------
        params: lmfit.Params
            The parameters of the model being fit

        Returns
        -------
        result : numpy.ndarray
            Model - Data
        """
        f = ntwodgaussian_lmfit(params)  # A function describing the model
        model = f(*mask)  # The actual model
        if B is None:
            return model - data[mask]
        else:
            return (model - data[mask]).dot(B)

    if dojac:
        result = lmfit.minimize(residual, params, kws={'x': mask[0], 'y': mask[1], 'B': B, 'errs': errs}, Dfun=lmfit_jacobian)
    else:
        result = lmfit.minimize(residual, params, kws={'x': mask[0], 'y': mask[1], 'B': B, 'errs': errs})

    # Remake the residual so that it is once again (model - data)
    if B is not None:
        result.residual = result.residual.dot(inv(B))
    return result, params

Response: Fit the model to the data and return the result.


Instruction:
Given the following Python 3 function, write the documentation
def covar_errors(params, data, errs, B, C=None):

    mask = np.where(np.isfinite(data))

    # calculate the proper parameter errors and copy them across.
    if C is not None:
        try:
            J = lmfit_jacobian(params, mask[0], mask[1], errs=errs)
            covar = np.transpose(J).dot(inv(C)).dot(J)
            onesigma = np.sqrt(np.diag(inv(covar)))
        except (np.linalg.linalg.LinAlgError, ValueError) as _:
            C = None

    if C is None:
        try:
            J = lmfit_jacobian(params, mask[0], mask[1], B=B, errs=errs)
            covar = np.transpose(J).dot(J)
            onesigma = np.sqrt(np.diag(inv(covar)))
        except (np.linalg.linalg.LinAlgError, ValueError) as _:
            onesigma = [-2] * len(mask[0])

    for i in range(params['components'].value):
        prefix = "c{0}_".format(i)
        j = 0
        for p in ['amp', 'xo', 'yo', 'sx', 'sy', 'theta']:
            if params[prefix + p].vary:
                params[prefix + p].stderr = onesigma[j]
                j += 1

    return params

Response: This function calculates the errors of the parameters that were fit with lmfit and replaces the errorsof the parameters with the 1 \ sigma errors calculated using the covariance matrix.


Instruction:
Here you have a function in Python 3, explain what it does
def barrier(events, sid, kind='neighbour'):
    events[sid].set()
    # only wait for the neighbours
    if kind=='neighbour':
        if sid > 0:
            logging.debug("{0} is waiting for {1}".format(sid, sid - 1))
            events[sid - 1].wait()
        if sid < len(bkg_events) - 1:
            logging.debug("{0} is waiting for {1}".format(sid, sid + 1))
            events[sid + 1].wait()
    # wait for all
    else:
        [e.wait() for e in events]
    return

Response: A multiprocessing barrier for a single event.


Instruction:
How would you explain what the following Python 3 function does
def sigmaclip(arr, lo, hi, reps=3):
    clipped = np.array(arr)[np.isfinite(arr)]

    if len(clipped) < 1:
        return np.nan, np.nan

    std = np.std(clipped)
    mean = np.mean(clipped)
    for _ in range(int(reps)):
        clipped = clipped[np.where(clipped > mean-std*lo)]
        clipped = clipped[np.where(clipped < mean+std*hi)]
        pstd = std
        if len(clipped) < 1:
            break
        std = np.std(clipped)
        mean = np.mean(clipped)
        if 2*abs(pstd-std)/(pstd+std) < 0.2:
            break
    return mean, std

Response: Perform sigma clipping on an array ignoring non finite values.


Instruction:
Can you tell what is the following Python 3 function doing
def sigma_filter(filename, region, step_size, box_size, shape, domask, sid):

    ymin, ymax = region
    logging.debug('rows {0}-{1} starting at {2}'.format(ymin, ymax, strftime("%Y-%m-%d %H:%M:%S", gmtime())))

    # cut out the region of interest plus 1/2 the box size, but clip to the image size
    data_row_min = max(0, ymin - box_size[0]//2)
    data_row_max = min(shape[0], ymax + box_size[0]//2)

    # Figure out how many axes are in the datafile
    NAXIS = fits.getheader(filename)["NAXIS"]

    with fits.open(filename, memmap=True) as a:
        if NAXIS == 2:
            data = a[0].section[data_row_min:data_row_max, 0:shape[1]]
        elif NAXIS == 3:
            data = a[0].section[0, data_row_min:data_row_max, 0:shape[1]]
        elif NAXIS == 4:
            data = a[0].section[0, 0, data_row_min:data_row_max, 0:shape[1]]
        else:
            logging.error("Too many NAXIS for me {0}".format(NAXIS))
            logging.error("fix your file to be more sane")
            raise Exception("Too many NAXIS")

    row_len = shape[1]

    logging.debug('data size is {0}'.format(data.shape))

    def box(r, c):
        """
        calculate the boundaries of the box centered at r,c
        with size = box_size
        """
        r_min = max(0, r - box_size[0] // 2)
        r_max = min(data.shape[0] - 1, r + box_size[0] // 2)
        c_min = max(0, c - box_size[1] // 2)
        c_max = min(data.shape[1] - 1, c + box_size[1] // 2)
        return r_min, r_max, c_min, c_max

    # set up a grid of rows/cols at which we will compute the bkg/rms
    rows = list(range(ymin-data_row_min, ymax-data_row_min, step_size[0]))
    rows.append(ymax-data_row_min)
    cols = list(range(0, shape[1], step_size[1]))
    cols.append(shape[1])

    # store the computed bkg/rms in this smaller array
    vals = np.zeros(shape=(len(rows),len(cols)))

    for i, row in enumerate(rows):
        for j, col in enumerate(cols):
            r_min, r_max, c_min, c_max = box(row, col)
            new = data[r_min:r_max, c_min:c_max]
            new = np.ravel(new)
            bkg, _ = sigmaclip(new, 3, 3)
            vals[i,j] = bkg

    # indices of all the pixels within our region
    gr, gc = np.mgrid[ymin-data_row_min:ymax-data_row_min, 0:shape[1]]

    logging.debug("Interpolating bkg to sharemem")
    ifunc = RegularGridInterpolator((rows, cols), vals)
    for i in range(gr.shape[0]):
        row = np.array(ifunc((gr[i], gc[i])), dtype=np.float32)
        start_idx = np.ravel_multi_index((ymin+i, 0), shape)
        end_idx = start_idx + row_len
        ibkg[start_idx:end_idx] = row  # np.ctypeslib.as_ctypes(row)
    del ifunc
    logging.debug(" ... done writing bkg")

    # signal that the bkg is done for this region, and wait for neighbours
    barrier(bkg_events, sid)

    logging.debug("{0} background subtraction".format(sid))
    for i in range(data_row_max - data_row_min):
        start_idx = np.ravel_multi_index((data_row_min + i, 0), shape)
        end_idx = start_idx + row_len
        data[i, :] = data[i, :] - ibkg[start_idx:end_idx]
    # reset/recycle the vals array
    vals[:] = 0

    for i, row in enumerate(rows):
        for j, col in enumerate(cols):
            r_min, r_max, c_min, c_max = box(row, col)
            new = data[r_min:r_max, c_min:c_max]
            new = np.ravel(new)
            _ , rms = sigmaclip(new, 3, 3)
            vals[i,j] = rms

    logging.debug("Interpolating rm to sharemem rms")
    ifunc = RegularGridInterpolator((rows, cols), vals)
    for i in range(gr.shape[0]):
        row = np.array(ifunc((gr[i], gc[i])), dtype=np.float32)
        start_idx = np.ravel_multi_index((ymin+i, 0), shape)
        end_idx = start_idx + row_len
        irms[start_idx:end_idx] = row  # np.ctypeslib.as_ctypes(row)
    del ifunc
    logging.debug(" .. done writing rms")

    if domask:
        barrier(mask_events, sid)
        logging.debug("applying mask")
        for i in range(gr.shape[0]):
            mask = np.where(np.bitwise_not(np.isfinite(data[i + ymin-data_row_min,:])))[0]
            for j in mask:
                idx = np.ravel_multi_index((i + ymin,j),shape)
                ibkg[idx] = np.nan
                irms[idx] = np.nan
        logging.debug(" ... done applying mask")
    logging.debug('rows {0}-{1} finished at {2}'.format(ymin, ymax, strftime("%Y-%m-%d %H:%M:%S", gmtime())))
    return

Response: Calculate the background and rms for a sub region of an image.


Instruction:
How would you code a function in Python 3 to
calculate the background and noise images corresponding to the input file. The calculation is done via a box-car approach and uses multiple cores and shared memory. Parameters ---------- filename : str Filename to be filtered. step_size : (int, int) Step size for the filter. box_size : (int, int) Box size for the filter. cores : int Number of cores to use. If None then use all available. nslice : int The image will be divided into this many horizontal stripes for processing. Default = None = equal to cores shape : (int, int) The shape of the image in the given file. domask : bool True(Default) = copy data mask to output. Returns ------- bkg, rms : numpy.ndarray The interpolated background and noise images.

Response:
def filter_mc_sharemem(filename, step_size, box_size, cores, shape, nslice=None, domask=True):
    """
    Calculate the background and noise images corresponding to the input file.
    The calculation is done via a box-car approach and uses multiple cores and shared memory.

    Parameters
    ----------
    filename : str
        Filename to be filtered.

    step_size : (int, int)
        Step size for the filter.

    box_size : (int, int)
        Box size for the filter.

    cores : int
        Number of cores to use. If None then use all available.

    nslice : int
        The image will be divided into this many horizontal stripes for processing.
        Default = None = equal to cores

    shape : (int, int)
        The shape of the image in the given file.

    domask : bool
        True(Default) = copy data mask to output.

    Returns
    -------
    bkg, rms : numpy.ndarray
        The interpolated background and noise images.
    """

    if cores is None:
        cores = multiprocessing.cpu_count()
    if (nslice is None) or (cores==1):
        nslice = cores

    img_y, img_x = shape
    # initialise some shared memory
    global ibkg
    # bkg = np.ctypeslib.as_ctypes(np.empty(shape, dtype=np.float32))
    # ibkg = multiprocessing.sharedctypes.Array(bkg._type_, bkg, lock=True)
    ibkg = multiprocessing.Array('f', img_y*img_x)

    global irms
    #rms = np.ctypeslib.as_ctypes(np.empty(shape, dtype=np.float32))
    #irms = multiprocessing.sharedctypes.Array(rms._type_, rms, lock=True)
    irms = multiprocessing.Array('f', img_y * img_x)

    logging.info("using {0} cores".format(cores))
    logging.info("using {0} stripes".format(nslice))

    if nslice > 1:
        # box widths should be multiples of the step_size, and not zero
        width_y = int(max(img_y/nslice/step_size[1], 1) * step_size[1])

        # locations of the box edges
        ymins = list(range(0, img_y, width_y))
        ymaxs = list(range(width_y, img_y, width_y))
        ymaxs.append(img_y)
    else:
        ymins = [0]
        ymaxs = [img_y]

    logging.debug("ymins {0}".format(ymins))
    logging.debug("ymaxs {0}".format(ymaxs))

    # create an event per stripe
    global bkg_events, mask_events
    bkg_events = [multiprocessing.Event() for _ in range(len(ymaxs))]
    mask_events = [multiprocessing.Event() for _ in range(len(ymaxs))]

    args = []
    for i, region in enumerate(zip(ymins, ymaxs)):
        args.append((filename, region, step_size, box_size, shape, domask, i))

    # start a new process for each task, hopefully to reduce residual memory use
    pool = multiprocessing.Pool(processes=cores, maxtasksperchild=1)
    try:
        # chunksize=1 ensures that we only send a single task to each process
        pool.map_async(_sf2, args, chunksize=1).get(timeout=10000000)
    except KeyboardInterrupt:
        logging.error("Caught keyboard interrupt")
        pool.close()
        sys.exit(1)
    pool.close()
    pool.join()
    bkg = np.reshape(np.array(ibkg[:], dtype=np.float32), shape)
    rms = np.reshape(np.array(irms[:], dtype=np.float32), shape)
    del ibkg, irms
    return bkg, rms


Instruction:
Implement a function in Python 3 to
filter an image into a single image.

Response:
def filter_image(im_name, out_base, step_size=None, box_size=None, twopass=False, cores=None, mask=True, compressed=False, nslice=None):
    """
    Create a background and noise image from an input image.
    Resulting images are written to `outbase_bkg.fits` and `outbase_rms.fits`

    Parameters
    ----------
    im_name : str or HDUList
        Image to filter. Either a string filename or an astropy.io.fits.HDUList.
    out_base : str
        The output filename base. Will be modified to make _bkg and _rms files.
    step_size : (int,int)
        Tuple of the x,y step size in pixels
    box_size : (int,int)
        The size of the box in piexls
    twopass : bool
        Perform a second pass calculation to ensure that the noise is not contaminated by the background.
        Default = False
    cores : int
        Number of CPU corse to use.
        Default = all available
    nslice : int
        The image will be divided into this many horizontal stripes for processing.
        Default = None = equal to cores
    mask : bool
        Mask the output array to contain np.nna wherever the input array is nan or not finite.
        Default = true
    compressed : bool
        Return a compressed version of the background/noise images.
        Default = False

    Returns
    -------
    None

    """

    header = fits.getheader(im_name)
    shape = (header['NAXIS2'],header['NAXIS1'])

    if step_size is None:
        if 'BMAJ' in header and 'BMIN' in header:
            beam_size = np.sqrt(abs(header['BMAJ']*header['BMIN']))
            if 'CDELT1' in header:
                pix_scale = np.sqrt(abs(header['CDELT1']*header['CDELT2']))
            elif 'CD1_1' in header:
                pix_scale = np.sqrt(abs(header['CD1_1']*header['CD2_2']))
                if 'CD1_2' in header and 'CD2_1' in header:
                    if header['CD1_2'] != 0 or header['CD2_1']!=0:
                        logging.warning("CD1_2 and/or CD2_1 are non-zero and I don't know what to do with them")
                        logging.warning("Ingoring them")
            else:
                logging.warning("Cannot determine pixel scale, assuming 4 pixels per beam")
                pix_scale = beam_size/4.
            # default to 4x the synthesized beam width
            step_size = int(np.ceil(4*beam_size/pix_scale))
        else:
            logging.info("BMAJ and/or BMIN not in fits header.")
            logging.info("Assuming 4 pix/beam, so we have step_size = 16 pixels")
            step_size = 16
        step_size = (step_size, step_size)

    if box_size is None:
        # default to 6x the step size so we have ~ 30beams
        box_size = (step_size[0]*6, step_size[1]*6)

    if compressed:
        if not step_size[0] == step_size[1]:
            step_size = (min(step_size), min(step_size))
            logging.info("Changing grid to be {0} so we can compress the output".format(step_size))

    logging.info("using grid_size {0}, box_size {1}".format(step_size,box_size))
    logging.info("on data shape {0}".format(shape))
    bkg, rms = filter_mc_sharemem(im_name, step_size=step_size, box_size=box_size, cores=cores, shape=shape, nslice=nslice, domask=mask)
    logging.info("done")

    bkg_out = '_'.join([os.path.expanduser(out_base), 'bkg.fits'])
    rms_out = '_'.join([os.path.expanduser(out_base), 'rms.fits'])

    # add a comment to the fits header
    header['HISTORY'] = 'BANE {0}-({1})'.format(__version__, __date__)

    # compress
    if compressed:
        hdu = fits.PrimaryHDU(bkg)
        hdu.header = copy.deepcopy(header)
        hdulist = fits.HDUList([hdu])
        compress(hdulist, step_size[0], bkg_out)
        hdulist[0].header = copy.deepcopy(header)
        hdulist[0].data = rms
        compress(hdulist, step_size[0], rms_out)
        return

    write_fits(bkg, header, bkg_out)
    write_fits(rms, header, rms_out)


Instruction:
Create a Python 3 function for
combining data and a fits header to write a fits file.

Response:
def write_fits(data, header, file_name):
    """
    Combine data and a fits header to write a fits file.

    Parameters
    ----------
    data : numpy.ndarray
        The data to be written.

    header : astropy.io.fits.hduheader
        The header for the fits file.

    file_name : string
        The file to write

    Returns
    -------
    None
    """
    hdu = fits.PrimaryHDU(data)
    hdu.header = header
    hdulist = fits.HDUList([hdu])
    hdulist.writeto(file_name, overwrite=True)
    logging.info("Wrote {0}".format(file_name))
    return


Instruction:
Write a Python 3 script for
converting sexegessimal RA string into a float in degrees.

Response:
def dec2dec(dec):
    """
    Convert sexegessimal RA string into a float in degrees.

    Parameters
    ----------
    dec : string
        A string separated representing the Dec.
        Expected format is `[+- ]hh:mm[:ss.s]`
        Colons can be replaced with any whit space character.

    Returns
    -------
    dec : float
        The Dec in degrees.
    """
    d = dec.replace(':', ' ').split()
    if len(d) == 2:
        d.append(0.0)
    if d[0].startswith('-') or float(d[0]) < 0:
        return float(d[0]) - float(d[1]) / 60.0 - float(d[2]) / 3600.0
    return float(d[0]) + float(d[1]) / 60.0 + float(d[2]) / 3600.0


Instruction:
How would you explain what the following Python 3 function does
def dec2dms(x):
    if not np.isfinite(x):
        return 'XX:XX:XX.XX'
    if x < 0:
        sign = '-'
    else:
        sign = '+'
    x = abs(x)
    d = int(math.floor(x))
    m = int(math.floor((x - d) * 60))
    s = float(( (x - d) * 60 - m) * 60)
    return '{0}{1:02d}:{2:02d}:{3:05.2f}'.format(sign, d, m, s)

Response: Convert decimal degrees into sexagessimal string in degrees.


Instruction:
Make a summary of the following Python 3 code
def dec2hms(x):
    if not np.isfinite(x):
        return 'XX:XX:XX.XX'
    # wrap negative RA's
    if x < 0:
        x += 360
    x /= 15.0
    h = int(x)
    x = (x - h) * 60
    m = int(x)
    s = (x - m) * 60
    return '{0:02d}:{1:02d}:{2:05.2f}'.format(h, m, s)

Response: Convert decimal degrees into sexagessimal string in hours.


Instruction:
Create a Python 3 function to
calculate the great circle distance between two points using the haversine formula.

Response:
def gcd(ra1, dec1, ra2, dec2):
    """
    Calculate the great circle distance between to points using the haversine formula [1]_.

    Parameters
    ----------
    ra1, dec1, ra2, dec2 : float
        The coordinates of the two points of interest.
        Units are in degrees.

    Returns
    -------
    dist : float
        The distance between the two points in degrees.

    Notes
    -----
    This duplicates the functionality of astropy but is faster as there is no creation of SkyCoords objects.

    .. [1] `Haversine formula <https://en.wikipedia.org/wiki/Haversine_formula>`_
    """
    # TODO:  Vincenty formula see - https://en.wikipedia.org/wiki/Great-circle_distance
    dlon = ra2 - ra1
    dlat = dec2 - dec1
    a = np.sin(np.radians(dlat) / 2) ** 2
    a += np.cos(np.radians(dec1)) * np.cos(np.radians(dec2)) * np.sin(np.radians(dlon) / 2) ** 2
    sep = np.degrees(2 * np.arcsin(np.minimum(1, np.sqrt(a))))
    return sep


Instruction:
How would you code a function in Python 3 to
calculate the bearing of point 2 from point 1 along a great circle.

Response:
def bear(ra1, dec1, ra2, dec2):
    """
    Calculate the bearing of point 2 from point 1 along a great circle.
    The bearing is East of North and is in [0, 360), whereas position angle is also East of North but (-180,180]

    Parameters
    ----------
    ra1, dec1, ra2, dec2 : float
        The sky coordinates (degrees) of the two points.

    Returns
    -------
    bear : float
        The bearing of point 2 from point 1 (degrees).
    """
    rdec1 = np.radians(dec1)
    rdec2 = np.radians(dec2)
    rdlon = np.radians(ra2-ra1)
    y = np.sin(rdlon) * np.cos(rdec2)
    x = np.cos(rdec1) * np.sin(rdec2)
    x -= np.sin(rdec1) * np.cos(rdec2) * np.cos(rdlon)
    return np.degrees(np.arctan2(y, x))


Instruction:
Can you generate the documentation for the following Python 3 function
def translate(ra, dec, r, theta):
    factor = np.sin(np.radians(dec)) * np.cos(np.radians(r))
    factor += np.cos(np.radians(dec)) * np.sin(np.radians(r)) * np.cos(np.radians(theta))
    dec_out = np.degrees(np.arcsin(factor))

    y = np.sin(np.radians(theta)) * np.sin(np.radians(r)) * np.cos(np.radians(dec))
    x = np.cos(np.radians(r)) - np.sin(np.radians(dec)) * np.sin(np.radians(dec_out))
    ra_out = ra + np.degrees(np.arctan2(y, x))
    return ra_out, dec_out

Response: Translate a given point a distance r in the initial direction theta along a great circle.


Instruction:
How would you code a function in Python 3 to
calculate the distance between two points on the Rhumb line.

Response:
def dist_rhumb(ra1, dec1, ra2, dec2):
    """
    Calculate the Rhumb line distance between two points [1]_.
    A Rhumb line between two points is one which follows a constant bearing.

    Parameters
    ----------
    ra1, dec1, ra2, dec2 : float
        The position of the two points (degrees).

    Returns
    -------
    dist : float
        The distance between the two points along a line of constant bearing.

    Notes
    -----
    .. [1] `Rhumb line <https://en.wikipedia.org/wiki/Rhumb_line>`_
    """
    # verified against website to give correct results
    phi1 = np.radians(dec1)
    phi2 = np.radians(dec2)
    dphi = phi2 - phi1
    lambda1 = np.radians(ra1)
    lambda2 = np.radians(ra2)
    dpsi = np.log(np.tan(np.pi / 4 + phi2 / 2) / np.tan(np.pi / 4 + phi1 / 2))
    if dpsi < 1e-12:
        q = np.cos(phi1)
    else:
        q = dpsi / dphi
    dlambda = lambda2 - lambda1
    if dlambda > np.pi:
        dlambda -= 2 * np.pi
    dist = np.hypot(dphi, q * dlambda)
    return np.degrees(dist)


Instruction:
Given the following Python 3 function, write the documentation
def bear_rhumb(ra1, dec1, ra2, dec2):
    # verified against website to give correct results
    phi1 = np.radians(dec1)
    phi2 = np.radians(dec2)
    lambda1 = np.radians(ra1)
    lambda2 = np.radians(ra2)
    dlambda = lambda2 - lambda1

    dpsi = np.log(np.tan(np.pi / 4 + phi2 / 2) / np.tan(np.pi / 4 + phi1 / 2))

    theta = np.arctan2(dlambda, dpsi)
    return np.degrees(theta)

Response: Calculate the bearing of point 2 from point 1 along a Rhumb line.


Instruction:
Given the following Python 3 function, write the documentation
def translate_rhumb(ra, dec, r, theta):
    # verified against website to give correct results
    # with the help of http://williams.best.vwh.net/avform.htm#Rhumb
    delta = np.radians(r)
    phi1 = np.radians(dec)
    phi2 = phi1 + delta * np.cos(np.radians(theta))
    dphi = phi2 - phi1

    if abs(dphi) < 1e-9:
        q = np.cos(phi1)
    else:
        dpsi = np.log(np.tan(np.pi / 4 + phi2 / 2) / np.tan(np.pi / 4 + phi1 / 2))
        q = dphi / dpsi

    lambda1 = np.radians(ra)
    dlambda = delta * np.sin(np.radians(theta)) / q
    lambda2 = lambda1 + dlambda

    ra_out = np.degrees(lambda2)
    dec_out = np.degrees(phi2)
    return ra_out, dec_out

Response: Translate a given point a distance r in the initial direction theta along a Rhumb line.


Instruction:
Create a Python 3 function to
convert a Galactic l and b to fk5 ra and dec

Response:
def galactic2fk5(l, b):
    """
    Convert galactic l/b to fk5 ra/dec

    Parameters
    ----------
    l, b : float
        Galactic coordinates in radians.

    Returns
    -------
    ra, dec : float
        FK5 ecliptic coordinates in radians.
    """
    a = SkyCoord(l, b, unit=(u.radian, u.radian), frame='galactic')
    return a.fk5.ra.radian, a.fk5.dec.radian


Instruction:
Write a Python 3 function for
masking a 2d image array such that pixels within region are set to nan.

Response:
def mask_plane(data, wcs, region, negate=False):
    """
    Mask a 2d image (data) such that pixels within 'region' are set to nan.

    Parameters
    ----------
    data : 2d-array
        Image array.

    wcs : astropy.wcs.WCS
        WCS for the image in question.

    region : :class:`AegeanTools.regions.Region`
        A region within which the image pixels will be masked.

    negate : bool
        If True then pixels *outside* the region are masked.
        Default = False.

    Returns
    -------
    masked : 2d-array
        The original array, but masked as required.
    """
    # create an array but don't set the values (they are random)
    indexes = np.empty((data.shape[0]*data.shape[1], 2), dtype=int)
    # since I know exactly what the index array needs to look like i can construct
    # it faster than list comprehension would allow
    # we do this only once and then recycle it
    idx = np.array([(j, 0) for j in range(data.shape[1])])
    j = data.shape[1]
    for i in range(data.shape[0]):
        idx[:, 1] = i
        indexes[i*j:(i+1)*j] = idx

    # put ALL the pixles into our vectorized functions and minimise our overheads
    ra, dec = wcs.wcs_pix2world(indexes, 1).transpose()
    bigmask = region.sky_within(ra, dec, degin=True)
    if not negate:
        bigmask = np.bitwise_not(bigmask)
    # rework our 1d list into a 2d array
    bigmask = bigmask.reshape(data.shape)
    # and apply the mask
    data[bigmask] = np.nan
    return data


Instruction:
Make a summary of the following Python 3 code
def mask_file(regionfile, infile, outfile, negate=False):
    # Check that the input file is accessible and then open it
    if not os.path.exists(infile): raise AssertionError("Cannot locate fits file {0}".format(infile))
    im = pyfits.open(infile)
    if not os.path.exists(regionfile): raise AssertionError("Cannot locate region file {0}".format(regionfile))
    region = Region.load(regionfile)
    try:
        wcs = pywcs.WCS(im[0].header, naxis=2)
    except:  # TODO: figure out what error is being thrown
        wcs = pywcs.WCS(str(im[0].header), naxis=2)

    if len(im[0].data.shape) > 2:
        data = np.squeeze(im[0].data)
    else:
        data = im[0].data

    print(data.shape)
    if len(data.shape) == 3:
        for plane in range(data.shape[0]):
            mask_plane(data[plane], wcs, region, negate)
    else:
        mask_plane(data, wcs, region, negate)
    im[0].data = data
    im.writeto(outfile, overwrite=True)
    logging.info("Wrote {0}".format(outfile))
    return

Response: Masks a single image in a single region.


Instruction:
Given the following Python 3 function, write the documentation
def mask_table(region, table, negate=False, racol='ra', deccol='dec'):
    inside = region.sky_within(table[racol], table[deccol], degin=True)
    if not negate:
        mask = np.bitwise_not(inside)
    else:
        mask = inside
    return table[mask]

Response: Masks a given table by removing all the rows with ra or dec inside the given region.


Instruction:
Explain what the following Python 3 code does
def mask_catalog(regionfile, infile, outfile, negate=False, racol='ra', deccol='dec'):
    logging.info("Loading region from {0}".format(regionfile))
    region = Region.load(regionfile)
    logging.info("Loading catalog from {0}".format(infile))
    table = load_table(infile)
    masked_table = mask_table(region, table, negate=negate, racol=racol, deccol=deccol)
    write_table(masked_table, outfile)
    return

Response: Apply a region file as a mask to a catalog.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def mim2reg(mimfile, regfile):
    region = Region.load(mimfile)
    region.write_reg(regfile)
    logging.info("Converted {0} -> {1}".format(mimfile, regfile))
    return

Response: Convert a MIMAS region into a DS9 region.


Instruction:
Implement a Python 3 function for
converting a MIMAS region into a FITS region.

Response:
def mim2fits(mimfile, fitsfile):
    """
    Convert a MIMAS region (.mim) file into a MOC region (.fits) file.

    Parameters
    ----------
    mimfile : str
        Input file in MIMAS format.

    fitsfile : str
        Output file.
    """
    region = Region.load(mimfile)
    region.write_fits(fitsfile, moctool='MIMAS {0}-{1}'.format(__version__, __date__))
    logging.info("Converted {0} -> {1}".format(mimfile, fitsfile))
    return


Instruction:
Write a Python 3 script to
convert a string that describes a box in ds9 format into a polygon

Response:
def box2poly(line):
    """
    Convert a string that describes a box in ds9 format, into a polygon that is given by the corners of the box

    Parameters
    ----------
    line : str
        A string containing a DS9 region command for a box.

    Returns
    -------
    poly : [ra, dec, ...]
        The corners of the box in clockwise order from top left.
    """
    words = re.split('[(\s,)]', line)
    ra = words[1]
    dec = words[2]
    width = words[3]
    height = words[4]
    if ":" in ra:
        ra = Angle(ra, unit=u.hour)
    else:
        ra = Angle(ra, unit=u.degree)
    dec = Angle(dec, unit=u.degree)
    width = Angle(float(width[:-1])/2, unit=u.arcsecond)  # strip the "
    height = Angle(float(height[:-1])/2, unit=u.arcsecond)  # strip the "
    center = SkyCoord(ra, dec)
    tl = center.ra.degree+width.degree, center.dec.degree+height.degree
    tr = center.ra.degree-width.degree, center.dec.degree+height.degree
    bl = center.ra.degree+width.degree, center.dec.degree-height.degree
    br = center.ra.degree-width.degree, center.dec.degree-height.degree
    return np.ravel([tl, tr, br, bl]).tolist()


Instruction:
Can you create a Python 3 function that
parses a string that describes a circle in ds9 format.

Response:
def circle2circle(line):
    """
    Parse a string that describes a circle in ds9 format.

    Parameters
    ----------
    line : str
        A string containing a DS9 region command for a circle.

    Returns
    -------
    circle : [ra, dec, radius]
        The center and radius of the circle.
    """
    words = re.split('[(,\s)]', line)
    ra = words[1]
    dec = words[2]
    radius = words[3][:-1]  # strip the "
    if ":" in ra:
        ra = Angle(ra, unit=u.hour)
    else:
        ra = Angle(ra, unit=u.degree)
    dec = Angle(dec, unit=u.degree)
    radius = Angle(radius, unit=u.arcsecond)
    return [ra.degree, dec.degree, radius.degree]


Instruction:
Can you implement a function in Python 3 that
parses a string containing a DS9 region command for a polygon and return a list of coordinates.

Response:
def poly2poly(line):
    """
    Parse a string of text containing a DS9 description of a polygon.

    This function works but is not very robust due to the constraints of healpy.

    Parameters
    ----------
    line : str
        A string containing a DS9 region command for a polygon.

    Returns
    -------
    poly : [ra, dec, ...]
        The coordinates of the polygon.
    """
    words = re.split('[(\s,)]', line)
    ras = np.array(words[1::2])
    decs = np.array(words[2::2])
    coords = []
    for ra, dec in zip(ras, decs):
        if ra.strip() == '' or dec.strip() == '':
            continue
        if ":" in ra:
            pos = SkyCoord(Angle(ra, unit=u.hour), Angle(dec, unit=u.degree))
        else:
            pos = SkyCoord(Angle(ra, unit=u.degree), Angle(dec, unit=u.degree))
        # only add this point if it is some distance from the previous one
        coords.extend([pos.ra.degree, pos.dec.degree])
    return coords


Instruction:
Can you generate a brief explanation for the following Python 3 code
def reg2mim(regfile, mimfile, maxdepth):
    logging.info("Reading regions from {0}".format(regfile))
    lines = (l for l in open(regfile, 'r') if not l.startswith('#'))
    poly = []
    circles = []
    for line in lines:
        if line.startswith('box'):
            poly.append(box2poly(line))
        elif line.startswith('circle'):
            circles.append(circle2circle(line))
        elif line.startswith('polygon'):
            logging.warning("Polygons break a lot, but I'll try this one anyway.")
            poly.append(poly2poly(line))
        else:
            logging.warning("Not sure what to do with {0}".format(line[:-1]))
    container = Dummy(maxdepth=maxdepth)
    container.include_circles = circles
    container.include_polygons = poly

    region = combine_regions(container)
    save_region(region, mimfile)
    return

Response: Parse a DS9 region file and write a MIMAS region file.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def combine_regions(container):
    # create empty region
    region = Region(container.maxdepth)

    # add/rem all the regions from files
    for r in container.add_region:
        logging.info("adding region from {0}".format(r))
        r2 = Region.load(r[0])
        region.union(r2)

    for r in container.rem_region:
        logging.info("removing region from {0}".format(r))
        r2 = Region.load(r[0])
        region.without(r2)

    # add circles
    if len(container.include_circles) > 0:
        for c in container.include_circles:
            circles = np.radians(np.array(c))
            if container.galactic:
                l, b, radii = circles.reshape(3, circles.shape[0]//3)
                ras, decs = galactic2fk5(l, b)
            else:
                ras, decs, radii = circles.reshape(3, circles.shape[0]//3)
            region.add_circles(ras, decs, radii)

    # remove circles
    if len(container.exclude_circles) > 0:
        for c in container.exclude_circles:
            r2 = Region(container.maxdepth)
            circles = np.radians(np.array(c))
            if container.galactic:
                l, b, radii = circles.reshape(3, circles.shape[0]//3)
                ras, decs = galactic2fk5(l, b)
            else:
                ras, decs, radii = circles.reshape(3, circles.shape[0]//3)
            r2.add_circles(ras, decs, radii)
            region.without(r2)

    # add polygons
    if len(container.include_polygons) > 0:
        for p in container.include_polygons:
            poly = np.radians(np.array(p))
            poly = poly.reshape((poly.shape[0]//2, 2))
            region.add_poly(poly)

    # remove polygons
    if len(container.exclude_polygons) > 0:
        for p in container.include_polygons:
            poly = np.array(np.radians(p))
            r2 = Region(container.maxdepth)
            r2.add_poly(poly)
            region.without(r2)

    return region

Response: Returns a new region that is the combination of those specified in the container.


Instruction:
How would you explain what the following Python 3 function does
def intersect_regions(flist):
    if len(flist) < 2:
        raise Exception("Require at least two regions to perform intersection")
    a = Region.load(flist[0])
    for b in [Region.load(f) for f in flist[1:]]:
        a.intersect(b)
    return a

Response: Returns a region which is the intersection of all regions described in the given list of file names.


Instruction:
Can you implement a function in Python 3 that
saves the given region to a file.

Response:
def save_region(region, filename):
    """
    Save the given region to a file

    Parameters
    ----------
    region : :class:`AegeanTools.regions.Region`
        A region.

    filename : str
        Output file name.
    """
    region.save(filename)
    logging.info("Wrote {0}".format(filename))
    return


Instruction:
Write a Python 3 function for
converting a MIMAS region into an image.

Response:
def save_as_image(region, filename):
    """
    Convert a MIMAS region (.mim) file into a image (eg .png)

    Parameters
    ----------
    region : :class:`AegeanTools.regions.Region`
        Region of interest.

    filename : str
        Output filename.
    """
    import healpy as hp
    pixels = list(region.get_demoted())
    order = region.maxdepth
    m = np.arange(hp.nside2npix(2**order))
    m[:] = 0
    m[pixels] = 1
    hp.write_map(filename, m, nest=True, coord='C')
    return


Instruction:
Write a Python 3 function for
returning some pixel information based on the given header

Response:
def get_pixinfo(header):
    """
    Return some pixel information based on the given hdu header
    pixarea - the area of a single pixel in deg2
    pixscale - the side lengths of a pixel (assuming they are square)

    Parameters
    ----------
    header : HDUHeader or dict
        FITS header information

    Returns
    -------
    pixarea : float
        The are of a single pixel at the reference location, in square degrees.

    pixscale : (float, float)
        The pixel scale in degrees, at the reference location.

    Notes
    -----
    The reference location is not always at the image center, and the pixel scale/area may
    change over the image, depending on the projection.
    """
    if all(a in header for a in ["CDELT1", "CDELT2"]):
        pixarea = abs(header["CDELT1"]*header["CDELT2"])
        pixscale = (header["CDELT1"], header["CDELT2"])
    elif all(a in header for a in ["CD1_1", "CD1_2", "CD2_1", "CD2_2"]):
        pixarea = abs(header["CD1_1"]*header["CD2_2"]
                    - header["CD1_2"]*header["CD2_1"])
        pixscale = (header["CD1_1"], header["CD2_2"])
        if not (header["CD1_2"] == 0 and header["CD2_1"] == 0):
            log.warning("Pixels don't appear to be square -> pixscale is wrong")
    elif all(a in header for a in ["CD1_1", "CD2_2"]):
        pixarea = abs(header["CD1_1"]*header["CD2_2"])
        pixscale = (header["CD1_1"], header["CD2_2"])
    else:
        log.critical("cannot determine pixel area, using zero EVEN THOUGH THIS IS WRONG!")
        pixarea = 0
        pixscale = (0, 0)
    return pixarea, pixscale


Instruction:
Can you implement a function in Python 3 that
returns a beam object from a header.

Response:
def get_beam(header):
    """
    Create a :class:`AegeanTools.fits_image.Beam` object from a fits header.

    BPA may be missing but will be assumed to be zero.

    if BMAJ or BMIN are missing then return None instead of a beam object.

    Parameters
    ----------
    header : HDUHeader
        The fits header.

    Returns
    -------
    beam : :class:`AegeanTools.fits_image.Beam`
        Beam object, with a, b, and pa in degrees.
    """

    if "BPA" not in header:
        log.warning("BPA not present in fits header, using 0")
        bpa = 0
    else:
        bpa = header["BPA"]

    if "BMAJ" not in header:
        log.warning("BMAJ not present in fits header.")
        bmaj = None
    else:
        bmaj = header["BMAJ"]

    if "BMIN" not in header:
        log.warning("BMIN not present in fits header.")
        bmin = None
    else:
        bmin = header["BMIN"]
    if None in [bmaj, bmin, bpa]:
        return None
    beam = Beam(bmaj, bmin, bpa)
    return beam


Instruction:
Create a Python 3 function to
fix the AIPS header.

Response:
def fix_aips_header(header):
    """
    Search through an image header. If the keywords BMAJ/BMIN/BPA are not set,
    but there are AIPS history cards, then we can populate the BMAJ/BMIN/BPA.
    Fix the header if possible, otherwise don't. Either way, don't complain.

    Parameters
    ----------
    header : HDUHeader
        Fits header which may or may not have AIPS history cards.

    Returns
    -------
    header : HDUHeader
        A header which has BMAJ, BMIN, and BPA keys, as well as a new HISTORY card.
    """
    if 'BMAJ' in header and 'BMIN' in header and 'BPA' in header:
        # The header already has the required keys so there is nothing to do
        return header
    aips_hist = [a for a in header['HISTORY'] if a.startswith("AIPS")]
    if len(aips_hist) == 0:
        # There are no AIPS history items to process
        return header
    for a in aips_hist:
        if "BMAJ" in a:
            # this line looks like
            # 'AIPS   CLEAN BMAJ=  1.2500E-02 BMIN=  1.2500E-02 BPA=   0.00'
            words = a.split()
            bmaj = float(words[3])
            bmin = float(words[5])
            bpa = float(words[7])
            break
    else:
        # there are AIPS cards but there is no BMAJ/BMIN/BPA
        return header
    header['BMAJ'] = bmaj
    header['BMIN'] = bmin
    header['BPA'] = bpa
    header['HISTORY'] = 'Beam information AIPS->fits by AegeanTools'
    return header


Instruction:
Write a Python 3 script to
set the image data.

Response:
def set_pixels(self, pixels):
        """
        Set the image data.
        Will not work if the new image has a different shape than the current image.

        Parameters
        ----------
        pixels : numpy.ndarray
            New image data

        Returns
        -------
        None
        """
        if not (pixels.shape == self._pixels.shape):
            raise AssertionError("Shape mismatch between pixels supplied {0} and existing image pixels {1}".format(pixels.shape,self._pixels.shape))
        self._pixels = pixels
        # reset this so that it is calculated next time the function is called
        self._rms = None
        return


Instruction:
Given the following Python 3 function, write the documentation
def get_background_rms(self):
        # TODO: return a proper background RMS ignoring the sources
        # This is an approximate method suggested by PaulH.
        # I have no idea where this magic 1.34896 number comes from...
        if self._rms is None:
            # Get the pixels values without the NaNs
            data = numpy.extract(self.hdu.data > -9999999, self.hdu.data)
            p25 = scipy.stats.scoreatpercentile(data, 25)
            p75 = scipy.stats.scoreatpercentile(data, 75)
            iqr = p75 - p25
            self._rms = iqr / 1.34896
        return self._rms

Response: Calculate the rms of the background image.


Instruction:
Given the following Python 3 function, write the documentation
def pix2sky(self, pixel):
        pixbox = numpy.array([pixel, pixel])
        skybox = self.wcs.all_pix2world(pixbox, 1)
        return [float(skybox[0][0]), float(skybox[0][1])]

Response: Get the sky coordinates for a given image pixel.


Instruction:
Make a summary of the following Python 3 code
def sky2pix(self, skypos):
        skybox = [skypos, skypos]
        pixbox = self.wcs.all_world2pix(skybox, 1)
        return [float(pixbox[0][0]), float(pixbox[0][1])]

Response: Get the pixel coordinates for a given sky position.


Instruction:
How would you explain what the following Python 3 function does
def load_sources(filename):
    catalog = catalogs.table_to_source_list(catalogs.load_table(filename))
    logging.info("read {0} sources from {1}".format(len(catalog), filename))
    return catalog

Response: Load all the sources in a file.


Instruction:
Can you generate the documentation for the following Python 3 function
def search_beam(hdulist):
    header = hdulist[0].header
    history = header['HISTORY']
    history_str = str(history)
    #AIPS   CLEAN BMAJ=  1.2500E-02 BMIN=  1.2500E-02 BPA=   0.00
    if 'BMAJ' in history_str:
        return True
    else:
        return False

Response: Search the beam info from the HISTORY header


Instruction:
Can you generate the documentation for the following Python 3 function
def fix_shape(source):
    if source.a < source.b:
        source.a, source.b = source.b, source.a
        source.err_a, source.err_b = source.err_b, source.err_a
        source.pa += 90
    return

Response: Ensure that a > = b for a given source object.


Instruction:
How would you code a function in Python 3 to
return the limit of theta in the base sequence.

Response:
def theta_limit(theta):
    """
    Angle theta is periodic with period pi.
    Constrain theta such that -pi/2<theta<=pi/2.

    Parameters
    ----------
    theta : float
        Input angle.

    Returns
    -------
    theta : float
        Rotate angle.
    """
    while theta <= -1 * np.pi / 2:
        theta += np.pi
    while theta > np.pi / 2:
        theta -= np.pi
    return theta


Instruction:
How would you implement a function in Python 3 that
converts a telescope name into a latitude.

Response:
def scope2lat(telescope):
    """
    Convert a telescope name into a latitude
    returns None when the telescope is unknown.

    Parameters
    ----------
    telescope : str
        Acronym (name) of telescope, eg MWA.

    Returns
    -------
    lat : float
        The latitude of the telescope.

    Notes
    -----
    These values were taken from wikipedia so have varying precision/accuracy
    """
    scopes = {'MWA': -26.703319,
              "ATCA": -30.3128,
              "VLA": 34.0790,
              "LOFAR": 52.9088,
              "KAT7": -30.721,
              "MEERKAT": -30.721,
              "PAPER": -30.7224,
              "GMRT": 19.096516666667,
              "OOTY": 11.383404,
              "ASKAP": -26.7,
              "MOST": -35.3707,
              "PARKES": -32.999944,
              "WSRT": 52.914722,
              "AMILA": 52.16977,
              "AMISA": 52.164303,
              "ATA": 40.817,
              "CHIME": 49.321,
              "CARMA": 37.28044,
              "DRAO": 49.321,
              "GBT": 38.433056,
              "LWA": 34.07,
              "ALMA": -23.019283,
              "FAST": 25.6525
              }
    if telescope.upper() in scopes:
        return scopes[telescope.upper()]
    else:
        log = logging.getLogger("Aegean")
        log.warn("Telescope {0} is unknown".format(telescope))
        log.warn("integrated fluxes may be incorrect")
        return None


Instruction:
Here you have a function in Python 3, explain what it does
def check_cores(cores):
    cores = min(multiprocessing.cpu_count(), cores)
    if six.PY3:
        log = logging.getLogger("Aegean")
        log.info("Multi-cores not supported in python 3+, using one core")
        return 1
    try:
        queue = pprocess.Queue(limit=cores, reuse=1)
    except:  # TODO: figure out what error is being thrown
        cores = 1
    else:
        try:
            _ = queue.manage(pprocess.MakeReusable(fix_shape))
        except:
            cores = 1
    return cores

Response: Determine how many cores we can use.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def get_aux_files(basename):
    base = os.path.splitext(basename)[0]
    files = {"bkg": base + "_bkg.fits",
             "rms": base + "_rms.fits",
             "mask": base + ".mim",
             "cat": base + "_comp.fits",
             "psf": base + "_psf.fits"}

    for k in files.keys():
        if not os.path.exists(files[k]):
            files[k] = None
    return files

Response: Returns a dict of all the aux files that are associated with this filename.


Instruction:
Implement a Python 3 function for
generating an island of a single image and return a list of islands.

Response:
def _gen_flood_wrap(self, data, rmsimg, innerclip, outerclip=None, domask=False):
        """
        Generator function.
        Segment an image into islands and return one island at a time.

        Needs to work for entire image, and also for components within an island.

        Parameters
        ----------
        data : 2d-array
            Image array.

        rmsimg : 2d-array
            Noise image.

        innerclip, outerclip :float
            Seed (inner) and flood (outer) clipping values.

        domask : bool
            If True then look for a region mask in globals, only return islands that are within the region.
            Default = False.

        Yields
        ------
        data_box : 2d-array
            A island of sources with subthreshold values masked.

        xmin, xmax, ymin, ymax : int
            The corners of the data_box within the initial data array.
        """

        if outerclip is None:
            outerclip = innerclip

        # compute SNR image (data has already been background subtracted)
        snr = abs(data) / rmsimg
        # mask of pixles that are above the outerclip
        a = snr >= outerclip
        # segmentation a la scipy
        l, n = label(a)
        f = find_objects(l)

        if n == 0:
            self.log.debug("There are no pixels above the clipping limit")
            return
        self.log.debug("{1} Found {0} islands total above flood limit".format(n, data.shape))
        # Yield values as before, though they are not sorted by flux
        for i in range(n):
            xmin, xmax = f[i][0].start, f[i][0].stop
            ymin, ymax = f[i][1].start, f[i][1].stop
            if np.any(snr[xmin:xmax, ymin:ymax] > innerclip):  # obey inner clip constraint
                # self.log.info("{1} Island {0} is above the inner clip limit".format(i, data.shape))
                data_box = copy.copy(data[xmin:xmax, ymin:ymax])  # copy so that we don't blank the master data
                data_box[np.where(
                    snr[xmin:xmax, ymin:ymax] < outerclip)] = np.nan  # blank pixels that are outside the outerclip
                data_box[np.where(l[xmin:xmax, ymin:ymax] != i + 1)] = np.nan  # blank out other summits
                # check if there are any pixels left unmasked
                if not np.any(np.isfinite(data_box)):
                    # self.log.info("{1} Island {0} has no non-masked pixels".format(i,data.shape))
                    continue
                if domask and (self.global_data.region is not None):
                    y, x = np.where(snr[xmin:xmax, ymin:ymax] >= outerclip)
                    # convert indices of this sub region to indices in the greater image
                    yx = list(zip(y + ymin, x + xmin))
                    ra, dec = self.global_data.wcshelper.wcs.wcs_pix2world(yx, 1).transpose()
                    mask = self.global_data.region.sky_within(ra, dec, degin=True)
                    # if there are no un-masked pixels within the region then we skip this island.
                    if not np.any(mask):
                        continue
                    self.log.debug("Mask {0}".format(mask))
                # self.log.info("{1} Island {0} will be fit".format(i, data.shape))
                yield data_box, xmin, xmax, ymin, ymax


Instruction:
Here you have a function in Python 3, explain what it does
def estimate_lmfit_parinfo(self, data, rmsimg, curve, beam, innerclip, outerclip=None, offsets=(0, 0),
                               max_summits=None):
        """
        Estimates the number of sources in an island and returns initial parameters for the fit as well as
        limits on those parameters.

        Parameters
        ----------
        data : 2d-array
            (sub) image of flux values. Background should be subtracted.

        rmsimg : 2d-array
            Image of 1sigma values

        curve : 2d-array
            Image of curvature values [-1,0,+1]

        beam : :class:`AegeanTools.fits_image.Beam`
            The beam information for the image.

        innerclip, outerclip : float
            Inerr and outer level for clipping (sigmas).

        offsets : (int, int)
            The (x,y) offset of data within it's parent image

        max_summits : int
            If not None, only this many summits/components will be fit. More components may be
            present in the island, but subsequent components will not have free parameters.

        Returns
        -------
        model : lmfit.Parameters
            The initial estimate of parameters for the components within this island.
        """

        debug_on = self.log.isEnabledFor(logging.DEBUG)
        is_flag = 0
        global_data = self.global_data

        # check to see if this island is a negative peak since we need to treat such cases slightly differently
        isnegative = max(data[np.where(np.isfinite(data))]) < 0
        if isnegative:
            self.log.debug("[is a negative island]")

        if outerclip is None:
            outerclip = innerclip

        self.log.debug(" - shape {0}".format(data.shape))

        if not data.shape == curve.shape:
            self.log.error("data and curvature are mismatched")
            self.log.error("data:{0} curve:{1}".format(data.shape, curve.shape))
            raise AssertionError()

        # For small islands we can't do a 6 param fit
        # Don't count the NaN values as part of the island
        non_nan_pix = len(data[np.where(np.isfinite(data))].ravel())
        if 4 <= non_nan_pix <= 6:
            self.log.debug("FIXED2PSF")
            is_flag |= flags.FIXED2PSF
        elif non_nan_pix < 4:
            self.log.debug("FITERRSMALL!")
            is_flag |= flags.FITERRSMALL
        else:
            is_flag = 0
        if debug_on:
            self.log.debug(" - size {0}".format(len(data.ravel())))

        if min(data.shape) <= 2 or (is_flag & flags.FITERRSMALL) or (is_flag & flags.FIXED2PSF):
            # 1d islands or small islands only get one source
            if debug_on:
                self.log.debug("Tiny summit detected")
                self.log.debug("{0}".format(data))
            summits = [[data, 0, data.shape[0], 0, data.shape[1]]]
            # and are constrained to be point sources
            is_flag |= flags.FIXED2PSF
        else:
            if isnegative:
                # the summit should be able to include all pixels within the island not just those above innerclip
                kappa_sigma = np.where(curve > 0.5, np.where(data + outerclip * rmsimg < 0, data, np.nan), np.nan)
            else:
                kappa_sigma = np.where(-1 * curve > 0.5, np.where(data - outerclip * rmsimg > 0, data, np.nan), np.nan)
            summits = list(self._gen_flood_wrap(kappa_sigma, np.ones(kappa_sigma.shape), 0, domask=False))

        params = lmfit.Parameters()
        i = 0
        summits_considered = 0
        # This can happen when the image contains regions of nans
        # the data/noise indicate an island, but the curvature doesn't back it up.
        if len(summits) < 1:
            self.log.debug("Island has {0} summits".format(len(summits)))
            return None

        # add summits in reverse order of peak SNR - ie brightest first
        for summit, xmin, xmax, ymin, ymax in sorted(summits, key=lambda x: np.nanmax(-1. * abs(x[0]))):
            summits_considered += 1
            summit_flag = is_flag
            if debug_on:
                self.log.debug(
                    "Summit({5}) - shape:{0} x:[{1}-{2}] y:[{3}-{4}]".format(summit.shape, ymin, ymax, xmin, xmax, i))
            try:
                if isnegative:
                    amp = np.nanmin(summit)
                    xpeak, ypeak = np.unravel_index(np.nanargmin(summit), summit.shape)
                else:
                    amp = np.nanmax(summit)
                    xpeak, ypeak = np.unravel_index(np.nanargmax(summit), summit.shape)
            except ValueError as e:
                if "All-NaN" in e.message:
                    self.log.warn("Summit of nan's detected - this shouldn't happen")
                    continue
                else:
                    raise e

            if debug_on:
                self.log.debug(" - max is {0:f}".format(amp))
                self.log.debug(" - peak at {0},{1}".format(xpeak, ypeak))
            yo = ypeak + ymin
            xo = xpeak + xmin

            # Summits are allowed to include pixels that are between the outer and inner clip
            # This means that sometimes we get a summit that has all it's pixels below the inner clip
            # So we test for that here.
            snr = np.nanmax(abs(data[xmin:xmax + 1, ymin:ymax + 1] / rmsimg[xmin:xmax + 1, ymin:ymax + 1]))
            if snr < innerclip:
                self.log.debug("Summit has SNR {0} < innerclip {1}: skipping".format(snr, innerclip))
                continue

            # allow amp to be 5% or (innerclip) sigma higher
            # TODO: the 5% should depend on the beam sampling
            # note: when innerclip is 400 this becomes rather stupid
            if amp > 0:
                amp_min, amp_max = 0.95 * min(outerclip * rmsimg[xo, yo], amp), amp * 1.05 + innerclip * rmsimg[xo, yo]
            else:
                amp_max, amp_min = 0.95 * max(-outerclip * rmsimg[xo, yo], amp), amp * 1.05 - innerclip * rmsimg[xo, yo]

            if debug_on:
                self.log.debug("a_min {0}, a_max {1}".format(amp_min, amp_max))

            pixbeam = global_data.psfhelper.get_pixbeam_pixel(yo + offsets[0], xo + offsets[1])
            if pixbeam is None:
                self.log.debug(" Summit has invalid WCS/Beam - Skipping.")
                continue

            # set a square limit based on the size of the pixbeam
            xo_lim = 0.5 * np.hypot(pixbeam.a, pixbeam.b)
            yo_lim = xo_lim

            yo_min, yo_max = yo - yo_lim, yo + yo_lim
            # if yo_min == yo_max:  # if we have a 1d summit then allow the position to vary by +/-0.5pix
            #    yo_min, yo_max = yo_min - 0.5, yo_max + 0.5

            xo_min, xo_max = xo - xo_lim, xo + xo_lim
            # if xo_min == xo_max:  # if we have a 1d summit then allow the position to vary by +/-0.5pix
            #    xo_min, xo_max = xo_min - 0.5, xo_max + 0.5

            # the size of the island
            xsize = data.shape[0]
            ysize = data.shape[1]

            # initial shape is the psf
            sx = pixbeam.a * FWHM2CC
            sy = pixbeam.b * FWHM2CC

            # lmfit does silly things if we start with these two parameters being equal
            sx = max(sx, sy * 1.01)

            # constraints are based on the shape of the island
            # sx,sy can become flipped so we set the min/max account for this
            sx_min, sx_max = sy * 0.8, max((max(xsize, ysize) + 1) * math.sqrt(2) * FWHM2CC, sx * 1.1)
            sy_min, sy_max = sy * 0.8, max((max(xsize, ysize) + 1) * math.sqrt(2) * FWHM2CC, sx * 1.1)

            theta = pixbeam.pa  # Degrees
            flag = summit_flag

            # check to see if we are going to fit this component
            if max_summits is not None:
                maxxed = i >= max_summits
            else:
                maxxed = False

            # components that are not fit need appropriate flags
            if maxxed:
                summit_flag |= flags.NOTFIT
                summit_flag |= flags.FIXED2PSF

            if debug_on:
                self.log.debug(" - var val min max | min max")
                self.log.debug(" - amp {0} {1} {2} ".format(amp, amp_min, amp_max))
                self.log.debug(" - xo {0} {1} {2} ".format(xo, xo_min, xo_max))
                self.log.debug(" - yo {0} {1} {2} ".format(yo, yo_min, yo_max))
                self.log.debug(" - sx {0} {1} {2} | {3} {4}".format(sx, sx_min, sx_max, sx_min * CC2FHWM,
                                                                    sx_max * CC2FHWM))
                self.log.debug(" - sy {0} {1} {2} | {3} {4}".format(sy, sy_min, sy_max, sy_min * CC2FHWM,
                                                                    sy_max * CC2FHWM))
                self.log.debug(" - theta {0} {1} {2}".format(theta, -180, 180))
                self.log.debug(" - flags {0}".format(flag))
                self.log.debug(" - fit?  {0}".format(not maxxed))

            # TODO: figure out how incorporate the circular constraint on sx/sy
            prefix = "c{0}_".format(i)
            params.add(prefix + 'amp', value=amp, min=amp_min, max=amp_max, vary=not maxxed)
            params.add(prefix + 'xo', value=xo, min=float(xo_min), max=float(xo_max), vary=not maxxed)
            params.add(prefix + 'yo', value=yo, min=float(yo_min), max=float(yo_max), vary=not maxxed)

            if summit_flag & flags.FIXED2PSF > 0:
                psf_vary = False
            else:
                psf_vary = not maxxed
            params.add(prefix + 'sx', value=sx, min=sx_min, max=sx_max, vary=psf_vary)
            params.add(prefix + 'sy', value=sy, min=sy_min, max=sy_max, vary=psf_vary)
            params.add(prefix + 'theta', value=theta, vary=psf_vary)
            params.add(prefix + 'flags', value=summit_flag, vary=False)

            # starting at zero allows the maj/min axes to be fit better.
            # if params[prefix + 'theta'].vary:
            #     params[prefix + 'theta'].value = 0

            i += 1
        if debug_on:
            self.log.debug("Estimated sources: {0}".format(i))
        # remember how many components are fit.
        params.add('components', value=i, vary=False)
        # params.components=i
        if params['components'].value < 1:
            self.log.debug("Considered {0} summits, accepted {1}".format(summits_considered, i))
        return params

Response: Estimate the number of sources in an island and returns initial parameters for the fit.


Instruction:
How would you explain what the following Python 3 function does
def result_to_components(self, result, model, island_data, isflags):
        global_data = self.global_data

        # island data
        isle_num = island_data.isle_num
        idata = island_data.i
        xmin, xmax, ymin, ymax = island_data.offsets

        box = slice(int(xmin), int(xmax)), slice(int(ymin), int(ymax))
        rms = global_data.rmsimg[box]
        bkg = global_data.bkgimg[box]
        residual = np.median(result.residual), np.std(result.residual)
        is_flag = isflags

        sources = []
        j = 0
        for j in range(model['components'].value):
            src_flags = is_flag
            source = OutputSource()
            source.island = isle_num
            source.source = j
            self.log.debug(" component {0}".format(j))
            prefix = "c{0}_".format(j)
            xo = model[prefix + 'xo'].value
            yo = model[prefix + 'yo'].value
            sx = model[prefix + 'sx'].value
            sy = model[prefix + 'sy'].value
            theta = model[prefix + 'theta'].value
            amp = model[prefix + 'amp'].value
            src_flags |= model[prefix + 'flags'].value

            # these are goodness of fit statistics for the entire island.
            source.residual_mean = residual[0]
            source.residual_std = residual[1]
            # set the flags
            source.flags = src_flags

            # #pixel pos within island +
            # island offset within region +
            # region offset within image +
            # 1 for luck
            # (pyfits->fits conversion = luck)
            x_pix = xo + xmin + 1
            y_pix = yo + ymin + 1
            # update the source xo/yo so the error calculations can be done correctly
            # Note that you have to update the max or the value you set will be clipped at the max allowed value
            model[prefix + 'xo'].set(value=x_pix, max=np.inf)
            model[prefix + 'yo'].set(value=y_pix, max=np.inf)
            # ------ extract source parameters ------

            # fluxes
            # the background is taken from background map
            # Clamp the pixel location to the edge of the background map
            y = max(min(int(round(y_pix - ymin)), bkg.shape[1] - 1), 0)
            x = max(min(int(round(x_pix - xmin)), bkg.shape[0] - 1), 0)
            source.background = bkg[x, y]
            source.local_rms = rms[x, y]
            source.peak_flux = amp

            # all params are in degrees
            source.ra, source.dec, source.a, source.b, source.pa = global_data.wcshelper.pix2sky_ellipse((x_pix, y_pix),
                                                                                sx * CC2FHWM,
                                                                                sy * CC2FHWM,
                                                                                theta)
            source.a *= 3600  # arcseconds
            source.b *= 3600
            # force a>=b
            fix_shape(source)
            # limit the pa to be in (-90,90]
            source.pa = pa_limit(source.pa)

            # if one of these values are nan then there has been some problem with the WCS handling
            if not all(np.isfinite((source.ra, source.dec, source.a, source.b, source.pa))):
                src_flags |= flags.WCSERR
            # negative degrees is valid for RA, but I don't want them.
            if source.ra < 0:
                source.ra += 360
            source.ra_str = dec2hms(source.ra)
            source.dec_str = dec2dms(source.dec)

            # calculate integrated flux
            source.int_flux = source.peak_flux * sx * sy * CC2FHWM ** 2 * np.pi
            # scale Jy/beam -> Jy using the area of the beam
            source.int_flux /= global_data.psfhelper.get_beamarea_pix(source.ra, source.dec)

            # Calculate errors for params that were fit (as well as int_flux)
            errors(source, model, global_data.wcshelper)

            source.flags = src_flags
            # add psf info
            local_beam = global_data.psfhelper.get_beam(source.ra, source.dec)
            if local_beam is not None:
                source.psf_a = local_beam.a * 3600
                source.psf_b = local_beam.b * 3600
                source.psf_pa = local_beam.pa
            else:
                source.psf_a = 0
                source.psf_b = 0
                source.psf_pa = 0
            sources.append(source)
            self.log.debug(source)

        if global_data.blank:
            outerclip = island_data.scalars[1]
            idx, idy = np.where(abs(idata) - outerclip * rms > 0)
            idx += xmin
            idy += ymin
            self.global_data.img._pixels[[idx, idy]] = np.nan

        # calculate the integrated island flux if required
        if island_data.doislandflux:
            _, outerclip, _ = island_data.scalars
            self.log.debug("Integrated flux for island {0}".format(isle_num))
            kappa_sigma = np.where(abs(idata) - outerclip * rms > 0, idata, np.NaN)
            self.log.debug("- island shape is {0}".format(kappa_sigma.shape))

            source = IslandSource()
            source.flags = 0
            source.island = isle_num
            source.components = j + 1
            source.peak_flux = np.nanmax(kappa_sigma)
            # check for negative islands
            if source.peak_flux < 0:
                source.peak_flux = np.nanmin(kappa_sigma)
            self.log.debug("- peak flux {0}".format(source.peak_flux))

            # positions and background
            if np.isfinite(source.peak_flux):
                positions = np.where(kappa_sigma == source.peak_flux)
            else:  # if a component has been refit then it might have flux = np.nan
                positions = [[kappa_sigma.shape[0] / 2], [kappa_sigma.shape[1] / 2]]
            xy = positions[0][0] + xmin, positions[1][0] + ymin
            radec = global_data.wcshelper.pix2sky(xy)
            source.ra = radec[0]

            # convert negative ra's to positive ones
            if source.ra < 0:
                source.ra += 360

            source.dec = radec[1]
            source.ra_str = dec2hms(source.ra)
            source.dec_str = dec2dms(source.dec)
            source.background = bkg[positions[0][0], positions[1][0]]
            source.local_rms = rms[positions[0][0], positions[1][0]]
            source.x_width, source.y_width = idata.shape
            source.pixels = int(sum(np.isfinite(kappa_sigma).ravel() * 1.0))
            source.extent = [xmin, xmax, ymin, ymax]

            # TODO: investigate what happens when the sky coords are skewed w.r.t the pixel coords
            # calculate the area of the island as a fraction of the area of the bounding box
            bl = global_data.wcshelper.pix2sky([xmax, ymin])
            tl = global_data.wcshelper.pix2sky([xmax, ymax])
            tr = global_data.wcshelper.pix2sky([xmin, ymax])
            height = gcd(tl[0], tl[1], bl[0], bl[1])
            width = gcd(tl[0], tl[1], tr[0], tr[1])
            area = height * width
            source.area = area * source.pixels / source.x_width / source.y_width  # area is in deg^2

            # create contours
            msq = MarchingSquares(idata)
            source.contour = [(a[0] + xmin, a[1] + ymin) for a in msq.perimeter]
            # calculate the maximum angular size of this island, brute force method
            source.max_angular_size = 0
            for i, pos1 in enumerate(source.contour):
                radec1 = global_data.wcshelper.pix2sky(pos1)
                for j, pos2 in enumerate(source.contour[i:]):
                    radec2 = global_data.wcshelper.pix2sky(pos2)
                    dist = gcd(radec1[0], radec1[1], radec2[0], radec2[1])
                    if dist > source.max_angular_size:
                        source.max_angular_size = dist
                        source.pa = bear(radec1[0], radec1[1], radec2[0], radec2[1])
                        source.max_angular_size_anchors = [pos1[0], pos1[1], pos2[0], pos2[1]]

            self.log.debug("- peak position {0}, {1} [{2},{3}]".format(source.ra_str, source.dec_str, positions[0][0],
                                                                       positions[1][0]))

            # integrated flux
            beam_area = global_data.psfhelper.get_beamarea_deg2(source.ra, source.dec)  # beam in deg^2
            # get_beamarea_pix(source.ra, source.dec)  # beam is in pix^2
            isize = source.pixels  # number of non zero pixels
            self.log.debug("- pixels used {0}".format(isize))
            source.int_flux = np.nansum(kappa_sigma)  # total flux Jy/beam
            self.log.debug("- sum of pixles {0}".format(source.int_flux))
            source.int_flux *= beam_area  # total flux in Jy
            self.log.debug("- integrated flux {0}".format(source.int_flux))
            eta = erf(np.sqrt(-1 * np.log(abs(source.local_rms * outerclip / source.peak_flux)))) ** 2
            self.log.debug("- eta {0}".format(eta))
            source.eta = eta
            source.beam_area = beam_area

            # I don't know how to calculate this error so we'll set it to nan
            source.err_int_flux = np.nan
            sources.append(source)
        return sources

Response: Convert fitting results into a set of components and islands.


Instruction:
Given the following Python 3 function, write the documentation
def load_globals(self, filename, hdu_index=0, bkgin=None, rmsin=None, beam=None, verb=False, rms=None, bkg=None, cores=1,
                     do_curve=True, mask=None, lat=None, psf=None, blank=False, docov=True, cube_index=None):
        """
        Populate the global_data object by loading or calculating the various components

        Parameters
        ----------
        filename : str or HDUList
            Main image which source finding is run on

        hdu_index : int
            HDU index of the image within the fits file, default is 0 (first)

        bkgin, rmsin : str or HDUList
            background and noise image filename or HDUList

        beam : :class:`AegeanTools.fits_image.Beam`
            Beam object representing the synthsized beam. Will replace what is in the FITS header.

        verb : bool
            Verbose. Write extra lines to INFO level log.

        rms, bkg : float
            A float that represents a constant rms/bkg levels for the entire image.
            Default = None, which causes the rms/bkg to be loaded or calculated.

        cores : int
            Number of cores to use if different from what is autodetected.

        do_curve : bool
            If True a curvature map will be created, default=True.

        mask : str or :class:`AegeanTools.regions.Region`
            filename or Region object

        lat : float
            Latitude of the observing telescope (declination of zenith)

        psf : str or HDUList
            Filename or HDUList of a psf image

        blank : bool
            True = blank output image where islands are found.
            Default = False.

        docov : bool
            True = use covariance matrix in fitting.
            Default = True.

        cube_index : int
            For an image cube, which slice to use.

        """
        # don't reload already loaded data
        if self.global_data.img is not None:
            return
        img = FitsImage(filename, hdu_index=hdu_index, beam=beam, cube_index=cube_index)
        beam = img.beam

        debug = logging.getLogger('Aegean').isEnabledFor(logging.DEBUG)

        if mask is None:
            self.global_data.region = None
        else:
            # allow users to supply and object instead of a filename
            if isinstance(mask, Region):
                self.global_data.region = mask
            elif os.path.exists(mask):
                self.log.info("Loading mask from {0}".format(mask))
                self.global_data.region = Region.load(mask)
            else:
                self.log.error("File {0} not found for loading".format(mask))
                self.global_data.region = None

        self.global_data.wcshelper = WCSHelper.from_header(img.get_hdu_header(), beam, lat)
        self.global_data.psfhelper = PSFHelper(psf, self.global_data.wcshelper)

        self.global_data.beam = self.global_data.wcshelper.beam
        self.global_data.img = img
        self.global_data.data_pix = img.get_pixels()
        self.global_data.dtype = type(self.global_data.data_pix[0][0])
        self.global_data.bkgimg = np.zeros(self.global_data.data_pix.shape, dtype=self.global_data.dtype)
        self.global_data.rmsimg = np.zeros(self.global_data.data_pix.shape, dtype=self.global_data.dtype)
        self.global_data.pixarea = img.pixarea
        self.global_data.dcurve = None

        if do_curve:
            self.log.info("Calculating curvature")
            # calculate curvature but store it as -1,0,+1
            dcurve = np.zeros(self.global_data.data_pix.shape, dtype=np.int8)
            peaks = scipy.ndimage.filters.maximum_filter(self.global_data.data_pix, size=3)
            troughs = scipy.ndimage.filters.minimum_filter(self.global_data.data_pix, size=3)
            pmask = np.where(self.global_data.data_pix == peaks)
            tmask = np.where(self.global_data.data_pix == troughs)
            dcurve[pmask] = -1
            dcurve[tmask] = 1
            self.global_data.dcurve = dcurve

        # if either of rms or bkg images are not supplied then calculate them both
        if not (rmsin and bkgin):
            if verb:
                self.log.info("Calculating background and rms data")
            self._make_bkg_rms(mesh_size=20, forced_rms=rms, forced_bkg=bkg, cores=cores)

        # replace the calculated images with input versions, if the user has supplied them.
        if bkgin:
            if verb:
                self.log.info("Loading background data from file {0}".format(bkgin))
            self.global_data.bkgimg = self._load_aux_image(img, bkgin)
        if rmsin:
            if verb:
                self.log.info("Loading rms data from file {0}".format(rmsin))
            self.global_data.rmsimg = self._load_aux_image(img, rmsin)

        # subtract the background image from the data image and save
        if verb and debug:
            self.log.debug("Data max is {0}".format(img.get_pixels()[np.isfinite(img.get_pixels())].max()))
            self.log.debug("Doing background subtraction")
        img.set_pixels(img.get_pixels() - self.global_data.bkgimg)
        self.global_data.data_pix = img.get_pixels()
        if verb and debug:
            self.log.debug("Data max is {0}".format(img.get_pixels()[np.isfinite(img.get_pixels())].max()))

        self.global_data.blank = blank
        self.global_data.docov = docov

        # Default to false until I can verify that this is working
        self.global_data.dobias = False

        # check if the WCS is galactic
        if 'lon' in self.global_data.img._header['CTYPE1'].lower():
            self.log.info("Galactic coordinates detected and noted")
            SimpleSource.galactic = True
        return

Response: Loads the global_data object from a FITS file.


Instruction:
Write a Python 3 function for
generating and save the background and RMS maps as FITS files.

Response:
def save_background_files(self, image_filename, hdu_index=0, bkgin=None, rmsin=None, beam=None, rms=None, bkg=None, cores=1,
                              outbase=None):
        """
        Generate and save the background and RMS maps as FITS files.
        They are saved in the current directly as aegean-background.fits and aegean-rms.fits.

        Parameters
        ----------
        image_filename : str or HDUList
            Input image.

        hdu_index : int
            If fits file has more than one hdu, it can be specified here.
            Default = 0.

        bkgin, rmsin : str or HDUList
            Background and noise image filename or HDUList

        beam : :class:`AegeanTools.fits_image.Beam`
            Beam object representing the synthsized beam. Will replace what is in the FITS header.

        rms, bkg : float
            A float that represents a constant rms/bkg level for the entire image.
            Default = None, which causes the rms/bkg to be loaded or calculated.

        cores : int
            Number of cores to use if different from what is autodetected.

        outbase : str
            Basename for output files.

        """

        self.log.info("Saving background / RMS maps")
        # load image, and load/create background/rms images
        self.load_globals(image_filename, hdu_index=hdu_index, bkgin=bkgin, rmsin=rmsin, beam=beam, verb=True, rms=rms, bkg=bkg,
                          cores=cores, do_curve=True)
        img = self.global_data.img
        bkgimg, rmsimg = self.global_data.bkgimg, self.global_data.rmsimg
        curve = np.array(self.global_data.dcurve, dtype=bkgimg.dtype)
        # mask these arrays have the same mask the same as the data
        mask = np.where(np.isnan(self.global_data.data_pix))
        bkgimg[mask] = np.NaN
        rmsimg[mask] = np.NaN
        curve[mask] = np.NaN

        # Generate the new FITS files by copying the existing HDU and assigning new data.
        # This gives the new files the same WCS projection and other header fields.
        new_hdu = img.hdu
        # Set the ORIGIN to indicate Aegean made this file
        new_hdu.header["ORIGIN"] = "Aegean {0}-({1})".format(__version__, __date__)
        for c in ['CRPIX3', 'CRPIX4', 'CDELT3', 'CDELT4', 'CRVAL3', 'CRVAL4', 'CTYPE3', 'CTYPE4']:
            if c in new_hdu.header:
                del new_hdu.header[c]

        if outbase is None:
            outbase, _ = os.path.splitext(os.path.basename(image_filename))
        noise_out = outbase + '_rms.fits'
        background_out = outbase + '_bkg.fits'
        curve_out = outbase + '_crv.fits'
        snr_out = outbase + '_snr.fits'

        new_hdu.data = bkgimg
        new_hdu.writeto(background_out, overwrite=True)
        self.log.info("Wrote {0}".format(background_out))

        new_hdu.data = rmsimg
        new_hdu.writeto(noise_out, overwrite=True)
        self.log.info("Wrote {0}".format(noise_out))

        new_hdu.data = curve
        new_hdu.writeto(curve_out, overwrite=True)
        self.log.info("Wrote {0}".format(curve_out))

        new_hdu.data = self.global_data.data_pix / rmsimg
        new_hdu.writeto(snr_out, overwrite=True)
        self.log.info("Wrote {0}".format(snr_out))
        return


Instruction:
Given the following Python 3 function, write the documentation
def save_image(self, outname):
        hdu = self.global_data.img.hdu
        hdu.data = self.global_data.img._pixels
        hdu.header["ORIGIN"] = "Aegean {0}-({1})".format(__version__, __date__)
        # delete some axes that we aren't going to need
        for c in ['CRPIX3', 'CRPIX4', 'CDELT3', 'CDELT4', 'CRVAL3', 'CRVAL4', 'CTYPE3', 'CTYPE4']:
            if c in hdu.header:
                del hdu.header[c]
        hdu.writeto(outname, overwrite=True)
        self.log.info("Wrote {0}".format(outname))
        return

Response: Save the image data to a file.