Пример #1
0
def _match_models(models, channel, degree, center=None, center_cs='image'):
    from ..cube_build import CubeBuildStep

    # create a list of cubes:
    cbs = CubeBuildStep()
    cbs.channel = str(channel)
    cbs.band = 'ALL'
    cbs.single = True
    cbs.weighting = 'EMSM'
    cube_models = cbs.process(models)
    if len(cube_models) != len(models):
        raise RuntimeError("The number of generated cube models does not "
                           "match the number of input 2D images.")

    # retrieve WCS (all cubes must have identical WCS so we use the first):
    meta = cube_models[0].meta

    if hasattr(meta, 'wcs'):
        wcs = meta.wcs
    else:
        raise ValueError("Cubes build from input 2D images do not contain WCS."
                         " Unable to proceed.")

    wcsinfo = meta.wcsinfo if hasattr(meta, 'wcsinfo') else None
    if wcsinfo is not None and (wcsinfo.crval1 is None
                                or wcsinfo.crval2 is None
                                or wcsinfo.crval3 is None):
        raise ValueError("'wcsinfo' cannot have its 'crvaln' set to None.")

    # set center of the coordinate system to CRVAL if available:
    if center is None and wcsinfo is not None:
        center = (wcsinfo.crval1, wcsinfo.crval2, wcsinfo.crval3)
        center_cs = 'world'

    # build lists of data, masks, and sigmas (weights)
    image_data = []
    mask_data = []
    sigma_data = []

    for cm in cube_models:
        #TODO: at this time it is not clear that data should be weighted
        #      by exptime the way it is done below and possibly should be
        #      revised later.
        exptime = cm.meta.exposure.exposure_time
        if exptime is None:
            exptime = 1.0

        # process weights and create masks:
        if not hasattr(cm, 'weightmap') or cm.weightmap is None:
            weights = np.ones_like(cm.data, dtype=np.float64)
            sigmas = weights / np.sqrt(exptime)
            mask = np.ones_like(weights, dtype=np.uint8)
            mask_data.append(mask)

        else:
            weights = cm.weightmap.copy()
            eps = np.finfo(weights.dtype).tiny
            bad_data = weights < eps
            weights[bad_data] = eps  # in order to avoid runtime warnings
            sigmas = 1.0 / np.sqrt(exptime * weights)
            mask = np.logical_not(bad_data).astype(np.uint8)
            mask_data.append(mask)

        image_data.append(cm.data)
        sigma_data.append(sigmas)

    # leaving in below commented out lines for
    # Mihia to de-bug step  when coefficients are NAN
    #mask_array = np.asarray(mask_data)
    #image_array = np.asarray(image_data)
    #sigma_array = np.asarray(sigma_data)
    #test_data = image_array[mask_array>0]
    #test_sigma = sigma_array[mask_array>0]
    #if np.isnan(test_data).any():
    #    print('a nan exists in test data')
    #if np.isnan(sigma_data).any():
    #    print('a nan exists in sigma data')

    # MRS fields of view are small compared to source sizes,
    # and undersampling produces significant differences
    # in overlap regions between exposures.
    # Therefore use sigma-clipping to detect and remove sources
    # (and unmasked outliers) prior to doing the background matching.
    # Loop over input exposures
    for image, mask in zip(image_data, mask_data):
        # Do statistics wavelength by wavelength
        for thisimg, thismask in zip(image, mask):
            # Avoid bug in sigma_clipped_stats (fixed in astropy 4.0.2) which
            # fails on all-zero arrays passed when mask_value=0
            if not np.any(thisimg):
                themed = 0.
                clipsig = 0.
            else:
                # Sigma clipped statistics, ignoring zeros where no data
                _, themed, clipsig = sigclip(thisimg, mask_value=0.)
            # Reject beyond 3 sigma
            reject = np.where(np.abs(thisimg - themed) > 3 * clipsig)
            thismask[reject] = 0

    bkg_poly_coef, mat, _, _, effc, cs = match_lsq(images=image_data,
                                                   masks=mask_data,
                                                   sigmas=sigma_data,
                                                   degree=degree,
                                                   center=center,
                                                   image2world=wcs.__call__,
                                                   center_cs=center_cs,
                                                   ext_return=True)

    if cs != 'world':
        raise RuntimeError("Unexpected coordinate system.")

    #TODO: try to identify if all images overlap
    #if nsubspace > 1:
    #self.log.warning("Not all cubes have been sky matched as "
    #"some of them do not overlap.")

    # save background info in 'meta' and subtract sky from 2D images
    # if requested:
    ##### model.meta.instrument.channel

    if np.isnan(bkg_poly_coef).any():
        bkg_poly_coef = None
        for im in models:
            im.meta.cal_step.mrs_imatch = 'SKIPPED'
            im.meta.background.subtracted = False
    else:
        # set 2D models' background meta info:
        for im, poly in zip(models, bkg_poly_coef):
            im.meta.background.subtracted = False
            im.meta.background.polynomial_info.append({
                'degree':
                degree,
                'refpoint':
                center,
                'coefficients':
                poly.ravel().tolist(),
                'channel':
                channel
            })

    return models
Пример #2
0
def trace_slice(thisslice, data, snum, basex, basey, nmed, method, verbose):
    # Zero out everything outside the peak slice
    indx = np.where(snum == thisslice)
    data_slice = data * 0.
    data_slice[indx] = data[indx]
    ysize, xsize = data.shape
    xmin, xmax = np.min(basex[indx]), np.max(basex[indx])

    ###################

    # First pass for x locations in this slice;
    if verbose:
        print('First pass trace fitting')
    xcen_pass1 = np.zeros(ysize)
    for ii in range(0, ysize):
        ystart = max(0, int(ii - nmed / 2))
        ystop = min(ysize, ystart + nmed)
        cut = np.nanmedian(data_slice[ystart:ystop, :], axis=0)
        xcen_pass1[ii] = np.argmax(cut)
    # Clean up any bad values by looking for 3sigma outliers
    # and replacing them with the median value
    rms, med = np.nanstd(xcen_pass1), np.nanmedian(xcen_pass1)
    indx = np.where((xcen_pass1 < med - 3 * rms)
                    | (xcen_pass1 > med + 3 * rms))
    xcen_pass1[indx] = med
    xwid_pass1 = np.ones(ysize)  # First pass width is 1 pixel

    ###################

    # Second pass for x locations along the trace within this slice
    if verbose:
        print('Second pass trace fitting')
    xcen_pass2 = np.zeros(ysize)
    xwid_pass2 = np.zeros(ysize)
    for ii in range(0, ysize):
        xtemp = np.arange(xmin, xmax, 1)
        ftemp = data_slice[ii, xtemp]

        # Initial guess at fit parameters
        p0 = [ftemp.max(), xcen_pass1[ii], xwid_pass1[ii], 0.]
        # Bounds for fit parameters
        bound_low = [0., xcen_pass1[ii] - 3 * rms, 0, -ftemp.max()]
        bound_hi = [
            10 * np.max(ftemp), xcen_pass1[ii] + 3 * rms, 10,
            ftemp.max()
        ]
        # Do the fit
        popt, _ = curve_fit(gauss1d,
                            xtemp,
                            ftemp,
                            p0=p0,
                            bounds=(bound_low, bound_hi),
                            method='trf')
        xcen_pass2[ii] = popt[1]
        xwid_pass2[ii] = popt[2]

    ###################

    # Third pass for x location; use a fixed profile width
    twidth = np.nanmedian(xwid_pass2)
    if verbose:
        print('Third pass trace fitting, median trace width ', twidth,
              ' pixels')
    xcen_pass3 = np.zeros(ysize)
    for ii in range(0, ysize):
        xtemp = np.arange(xmin, xmax, 1)
        ftemp = data_slice[ii, xtemp]

        # Initial guess at fit parameters
        p0 = [ftemp.max(), xcen_pass2[ii], twidth, 0.]
        # Bounds for fit parameters
        bound_low = [
            0., xcen_pass2[ii] - 3 * rms, twidth * 0.999, -ftemp.max()
        ]
        bound_hi = [
            10 * np.max(ftemp), xcen_pass2[ii] + 3 * rms, twidth * 1.001,
            ftemp.max()
        ]
        # Do the fit
        popt, _ = curve_fit(gauss1d,
                            xtemp,
                            ftemp,
                            p0=p0,
                            bounds=(bound_low, bound_hi),
                            method='trf')
        xcen_pass3[ii] = popt[1]

    # Clean up the fit to remove outliers
    qual = np.ones(ysize)
    # Low order polynomial fit to find the worst outliers using plain RMS
    fit = np.polyfit(basey[:, 0], xcen_pass3, 2)
    temp = np.polyval(fit, basey[:, 0])
    indx = np.where(
        np.abs(xcen_pass3 - temp) > 3 * np.nanstd(xcen_pass3 - temp))
    qual[indx] = 0
    good = (np.where(qual == 1))[0]
    # Another fit to find lesser outliers using sigma-clipped RMS
    fit = np.polyfit(basey[good, 0], xcen_pass3[good], 2)
    temp = np.polyval(fit, basey[:, 0])
    indx = np.where(
        np.abs(xcen_pass3 - temp) > 3 * (sigclip(xcen_pass3 - temp)[2]))
    qual[indx] = 0

    # Look for bad failures (e.g., steps that occur if the source is at the edge
    # of the field)
    diff = xcen_pass3 - temp
    good = (np.where(qual == 1))[0]
    rms = np.nanstd(diff[good])

    # If rms of the GOOD point fit is over 0.3 pixels don't do spline fitting
    if (rms > 0.3):
        print('WARNING: Alpha trace is poor!  Source at edge of field?')
        # Replace bad values
        bad = np.where(qual == 0)
        xcen_pass3[bad] = temp[bad]
    else:
        # Spline fit
        spl = UnivariateSpline(basey[:, 0], xcen_pass3, w=qual, s=1)
        model = spl(basey[:, 0])
        # Replace bad values
        bad = np.where(qual == 0)
        xcen_pass3[bad] = model[bad]
        # If method='model' then return the model itself for everything
        if (method == 'model'):
            xcen_pass3 = model

    return xcen_pass2, xcen_pass3