Example #1
0
def _mergeUserMaskAndDQ(dq, mask, dqbits):
    # Optional package dependency
    try:
        from stsci.tools.bitmask import (interpret_bit_flags,
                                         bitfield_to_boolean_mask)
    except ImportError:
        from stsci.tools.bitmask import (interpret_bits_value as
                                         interpret_bit_flags, bitmask2mask as
                                         bitfield_to_boolean_mask)

    dqbits = interpret_bit_flags(dqbits)
    if dqbits is None:
        if mask is None:
            return None
        else:
            return mask.copy().astype(dtype=np.uint8)

    if dq is None:
        raise ValueError("DQ array is None while 'dqbits' is not None.")

    dqmask = bitfield_to_boolean_mask(dq,
                                      dqbits,
                                      good_mask_value=1,
                                      dtype=np.uint8)

    if mask is None:
        return dqmask

    # merge user mask with DQ mask:
    dqmask[mask == 0] = 0

    return dqmask
Example #2
0
def _mergeUserMaskAndDQ(dq, mask, dqbits):
    # Optional package dependency
    try:
        from stsci.tools.bitmask import (interpret_bit_flags,
                                         bitfield_to_boolean_mask)
    except ImportError:
        from stsci.tools.bitmask import (
            interpret_bits_value as interpret_bit_flags,
            bitmask2mask as bitfield_to_boolean_mask
        )

    dqbits = interpret_bit_flags(dqbits)
    if dqbits is None:
        if mask is None:
            return None
        else:
            return mask.copy().astype(dtype=np.uint8)

    if dq is None:
        raise ValueError("DQ array is None while 'dqbits' is not None.")

    dqmask = bitfield_to_boolean_mask(dq, dqbits, good_mask_value=1,
                                      dtype=np.uint8)

    if mask is None:
        return dqmask

    # merge user mask with DQ mask:
    dqmask[mask == 0] = 0

    return dqmask
    def _imodel2skyim(self, image_model):

        # create
        if self._dqbits is None:
            dqmask = np.isfinite(image_model.data).astype(dtype=np.uint8)
        else:
            dqmask = bitfield_to_boolean_mask(
                image_model.dq,
                self._dqbits,
                good_mask_value=1,
                dtype=np.uint8
            ) * np.isfinite(image_model.data)

        # see if 'skymatch' was previously run and raise an exception
        # if 'subtract' mode has changed compared to the previous pass:
        if image_model.meta.background.subtracted is None:
            if image_model.meta.background.level is not None:
                # report inconsistency:
                raise ValueError("Background level was set but the "
                                 "'subtracted' property is undefined (None).")
            level = 0.0

        else:
            level = image_model.meta.background.level
            if level is None:
                # NOTE: In principle we could assume that level is 0 and
                # possibly add a log entry documenting this, however,
                # at this moment I think it is saver to quit and...
                #
                # report inconsistency:
                raise ValueError("Background level was subtracted but the "
                                 "'level' property is undefined (None).")

            if image_model.meta.background.subtracted != self.subtract:
                # cannot run 'skymatch' step on already "skymatched" images
                # when 'subtract' spec is inconsistent with
                # meta.background.subtracted:
                raise ValueError("'subtract' step's specification is "
                                 "inconsistent with background info already "
                                 "present in image '{:s}' meta."
                                 .format(image_model.meta.filename))

        sky_im = SkyImage(
            image=image_model.data,
            wcs_fwd=image_model.meta.wcs.__call__,
            wcs_inv=image_model.meta.wcs.invert,
            pix_area=1.0, #TODO: pixel area
            convf=1.0,    #TODO: conv. factor to brightness
            mask=dqmask,
            id=image_model.meta.filename, # file name?
            skystat=self._skystat,
            stepsize=self.stepsize,
            meta={'imagemodel': image_model}
        )

        if self.subtract:
            sky_im.sky = level

        return sky_im
Example #4
0
    def _imodel2skyim(self, image_model):

        # create
        if self._dqbits is None:
            dqmask = np.isfinite(image_model.data).astype(dtype=np.uint8)
        else:
            dqmask = bitfield_to_boolean_mask(
                image_model.dq,
                self._dqbits,
                good_mask_value=1,
                dtype=np.uint8
            ) * np.isfinite(image_model.data)

        # see if 'skymatch' was previously run and raise an exception
        # if 'subtract' mode has changed compared to the previous pass:
        if image_model.meta.background.subtracted is None:
            if image_model.meta.background.level is not None:
                # report inconsistency:
                raise ValueError("Background level was set but the "
                                 "'subtracted' property is undefined (None).")
            level = 0.0

        else:
            level = image_model.meta.background.level
            if level is None:
                # NOTE: In principle we could assume that level is 0 and
                # possibly add a log entry documenting this, however,
                # at this moment I think it is saver to quit and...
                #
                # report inconsistency:
                raise ValueError("Background level was subtracted but the "
                                 "'level' property is undefined (None).")

            if image_model.meta.background.subtracted != self.subtract:
                # cannot run 'skymatch' step on already "skymatched" images
                # when 'subtract' spec is inconsistent with
                # meta.background.subtracted:
                raise ValueError("'subtract' step's specification is "
                                 "inconsistent with background info already "
                                 "present in image '{:s}' meta."
                                 .format(image_model.meta.filename))

        sky_im = SkyImage(
            image=image_model.data,
            wcs_fwd=image_model.meta.wcs.__call__,
            wcs_inv=image_model.meta.wcs.invert,
            pix_area=1.0, #TODO: pixel area
            convf=1.0,    #TODO: conv. factor to brightness
            mask=dqmask,
            id=image_model.meta.filename, # file name?
            skystat=self._skystat,
            stepsize=self.stepsize,
            meta={'image_model': image_model}
        )

        if self.subtract:
            sky_im.sky = level

        return sky_im
Example #5
0
def test_bitfield_to_boolean_mask(data, flags, flip, goodval, dtype, ref):
    mask = bitmask.bitfield_to_boolean_mask(
        bitfield=data,
        ignore_flags=flags,
        flip_bits=flip,
        good_mask_value=goodval,
        dtype=dtype
    )

    assert(mask.dtype == dtype)
    assert np.all(mask == ref)
Example #6
0
    def process(self, input1, input2):
        cube_models = datamodels.ModelContainer(input1, persist=False)
        models2d = datamodels.ModelContainer(input2, persist=False)
        dqbits = interpret_bit_flags(self.dqbits)

        # set sky stattistics:
        self._skystat = SkyStats(
            skystat=self.skystat,
            lower=self.lower,
            upper=self.upper,
            nclip=self.nclip,
            lsig=self.lsigma,
            usig=self.usigma,
            binwidth=self.binwidth
        )

        # At this moment running 'cube_skymatch' on images whose
        # background has been previously subtracted is not supported.
        # Raise an exception if background was subtracted:
        self._check_background(cube_models)
        self._check_background(models2d)
        self._reset_background(cube_models)
        self._reset_background(models2d)

        # create a list of SkyCubes:
        skycubes = []

        for cm in cube_models:
            # process weights and combine with DQ:
            if not hasattr(cm, 'weightmap') or cm.weightmap is None:
                weights = np.ones_like(cm.data, dtype=np.float64)
            else:
                weights = cm.weightmap.copy()

            if dqbits is not None:
                dq = bitfield_to_boolean_mask(
                    cm.dq,
                    self._dqbits,
                    good_mask_value=0,
                    dtype=np.bool
                )
                weights[dq] = 0.0

            wcs = cm.meta.wcs if hasattr(cm.meta, 'wcs') else None
            wcsinfo = cm.meta.wcsinfo if hasattr(cm.meta, 'wcsinfo') else None

            exptime = cm.meta.exposure.exposure_time
            if exptime is None:
                exptime = 1.0

            sc = SkyCube(
                data=cm.data,
                wcs=wcs,
                wcsinfo=wcsinfo,
                weights=weights,
                cube_weight=exptime,
                bkg_deg=self.bkg_degree,
                bkg_center=None,
                id=None,
                meta={'original_cube_model': cm}
            )

            skycubes.append(sc)

        skymatch_cubes, nsubspace = match(skycubes, subtract=self.subtract)

        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

        for c in skymatch_cubes:
            self._set_cube_bkg_meta(c.meta['original_cube_model'], c)
            model2d, channel = _find_associated_2d_image(
                c.meta['original_cube_model'], models2d
            )

            if model2d is None:
                continue

            self._set_model2d_bkg_meta(
                c.meta['original_cube_model'],
                model2d,
                channel
            )

            if self.subtract2d:
                self._apply_sky_2d(model2d, channel)

        return cube_models, models2d
Example #7
0
def test_bitfield_must_be_integer_check():
    with pytest.raises(TypeError):
        bitmask.bitfield_to_boolean_mask(1.0, 1)
Example #8
0
    def process(self, input1, input2):
        cube_models = datamodels.ModelContainer(input1, persist=False)
        models2d = datamodels.ModelContainer(input2, persist=False)
        dqbits = interpret_bit_flags(self.dqbits)

        # set sky stattistics:
        self._skystat = SkyStats(skystat=self.skystat,
                                 lower=self.lower,
                                 upper=self.upper,
                                 nclip=self.nclip,
                                 lsig=self.lsigma,
                                 usig=self.usigma,
                                 binwidth=self.binwidth)

        # At this moment running 'cube_skymatch' on images whose
        # background has been previously subtracted is not supported.
        # Raise an exception if background was subtracted:
        self._check_background(cube_models)
        self._check_background(models2d)
        self._reset_background(cube_models)
        self._reset_background(models2d)

        # create a list of SkyCubes:
        skycubes = []

        for cm in cube_models:
            # process weights and combine with DQ:
            if not hasattr(cm, 'weightmap') or cm.weightmap is None:
                weights = np.ones_like(cm.data, dtype=np.float64)
            else:
                weights = cm.weightmap.copy()

            if dqbits is not None:
                dq = bitfield_to_boolean_mask(cm.dq,
                                              self._dqbits,
                                              good_mask_value=0,
                                              dtype=np.bool)
                weights[dq] = 0.0

            wcs = cm.meta.wcs if hasattr(cm.meta, 'wcs') else None
            wcsinfo = cm.meta.wcsinfo if hasattr(cm.meta, 'wcsinfo') else None

            exptime = cm.meta.exposure.exposure_time
            if exptime is None:
                exptime = 1.0

            sc = SkyCube(data=cm.data,
                         wcs=wcs,
                         wcsinfo=wcsinfo,
                         weights=weights,
                         cube_weight=exptime,
                         bkg_deg=self.bkg_degree,
                         bkg_center=None,
                         id=None,
                         meta={'original_cube_model': cm})

            skycubes.append(sc)

        skymatch_cubes, nsubspace = match(skycubes, subtract=self.subtract)

        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

        for c in skymatch_cubes:
            self._set_cube_bkg_meta(c.meta['original_cube_model'], c)
            model2d, channel = _find_associated_2d_image(
                c.meta['original_cube_model'], models2d)

            if model2d is None:
                continue

            self._set_model2d_bkg_meta(c.meta['original_cube_model'], model2d,
                                       channel)

            if self.subtract2d:
                self._apply_sky_2d(model2d, channel)

        return cube_models, models2d
Example #9
0
    def _buildMask(self, image_fname, ext, dq_bits, dq, dqext, msk, mskext):
        if dq_bits is None or dq is None:
            # we will use only the user mask:
            if msk is not None:
                if (self._optimize == 'balanced' and msk.can_reload_data) or \
                    self._optimize == 'speed' or self._optimize == 'inmemory':
                    # nothing to do: simply re-use the user mask:
                    self._mask     = msk
                    self._maskext  = mskext
                    self._mask.hold()
                else:
                    # self._optimize == 'balanced' but the mask cannot be freed
                    # so we will create a temporary fits file to hold mask data:
                    maskdata = (msk.hdu[mskext].data != 0).astype(np.uint8)
                    (root,suffix,fext)  = file_name_components(image_fname)
                    mfname, self._mask = temp_mask_file(
                        maskdata, root, prefix='',
                        suffix='skymatch_mask',
                        ext=ext, randomize_prefix=False)
                    self._files2clean.append(mfname)
                    self._maskext = 0

                self._can_free_mask = self._mask.can_reload_data
                self._mask_is_imref = True

            else:
                # no mask will be used in sky computations:
                self._mask = None
                self._can_free_mask = False
                self._mask_is_imref = False

        else:
            # compute a new mask by:
            #    1. applying dq_bits to DQ array
            #    2. combining previous array with the user mask data
            #
            # If dq_bits show the "bad" bits then DQ mask should be computed
            # using:
            #
            #dqmskarr = np.logical_not(np.bitwise_and(dq.hdu[dqext].data,dq_bits))
            #
            # However, to keep the same convention with astrodrizzle, dq_bits
            # will show the "good" bits that should be removed from the DQ array:
            dqmskarr = bitfield_to_boolean_mask(dq.hdu[dqext].data, dq_bits,
                                                dtype=np.bool_)

            # 2. combine with user mask:
            if msk is not None:
                maskdata = (msk.hdu[mskext].data != 0)
                dqmskarr = np.logical_and(dqmskarr, maskdata)
            # create a temporary file with the combined mask:
            (root, suffix, fext) = file_name_components(image_fname)
            if self._optimize == 'inmemory':
                self._mask = in_memory_mask(dqmskarr.astype(np.uint8))
                strext = ext2str(ext, compact=True, default_extver=None)
                self._mask.original_fname = "{1:s}{0:s}{2:s}{0:s}{3:s}" \
                    .format('_', root, suffix, 'in-memory_skymatch_mask')
            else:
                mfname, self._mask = temp_mask_file(
                    dqmskarr.astype(np.uint8), root,
                    prefix='', suffix='skymatch_mask', ext=ext,
                    randomize_prefix=False
                )
                self._files2clean.append(mfname)

            self._maskext = 0
            self._can_free_mask = self._mask.can_reload_data
            self._mask_is_imref = True
Example #10
0
def buildMask(dqarr, bitvalue):
    """ Builds a bit-mask from an input DQ array and a bitvalue flag """
    return bitfield_to_boolean_mask(dqarr,
                                    bitvalue,
                                    good_mask_value=1,
                                    dtype=np.uint8)
Example #11
0
def verify_guiding(filename, min_length=33):
    """ Verify whether or not the input image was affected by guiding problems.

    This algorithm evaluates the data from (SCI,1) to try to determine whether
    the image was affected by guiding problems which mimic SCAN mode or GRISM data
    with the trails in an arbitrary angle across the image.

    Parameters
    ==========
    filename : str
        Name of image to be evaluated

    min_length : int, optional
        Minimum length of trails (in pixels) to be detected in image.

    Returns
    ========
    bad_guiding : bool
        Boolean specifying whether or not the image was detected as
        being affected by guiding problems.  Value is True if image
        was affected.

    Note: This function will only be called from analyze_wrapper if the processing
          type is "MVM".  It is deliberately False for other processing (e.g., SVM and
          pipeline).  However, this routine can be invoked directly for pipeline
          processing from runastrodriz.py with the appropriate parameter setting.
    """
    log.info(
        f"Verifying that {filename} was not affected by guiding problems.")

    hdu = fits.open(filename)
    data = hdu[("SCI", 1)].data.copy()
    scale_data = hdu[("SCI", 1)].header["bunit"].endswith('/S')
    data = np.nan_to_num(data, nan=0.0)  # just to be careful
    if scale_data:
        # Photutils works best in units of DN
        scale_hdr = hdu[0].header if 'exptime' in hdu[0].header else hdu[
            1].header
        scale_val = scale_hdr['exptime']
        data *= scale_val
    bkg_stats = sigma_clipped_stats(data, maxiters=2)
    bkg_limit = bkg_stats[1] + bkg_stats[
        2]  # only need a 1-sigma detection limit here...
    log.debug(f"bkg_limit found to be: {bkg_limit:.2f}")

    data -= bkg_limit
    imgarr = np.clip(data, 0, data.max())

    # Build up a mask of all bad pixels and ignore them when looking for
    # sources and linear features
    dqarr = None
    for extn in hdu:
        if 'extname' in extn.header and extn.header['extname'] == 'DQ':
            dqarr = hdu[extn].data.copy()
            break
    if dqarr is not None:
        dqmask = bitfield_to_boolean_mask(dqarr, ignore_flags=BAD_DQ_FLAGS)
    else:
        dqmask = np.ones_like(data)
    # close FITS object (just to be nice to the OS...)
    hdu.close()
    del hdu

    # apply mask now...
    imgarr *= dqmask
    del dqmask  # just to clean up a little

    # Determine rough number of probable sources
    # Trying to ignore small sources (<= 4x4 pixels in size, or npixels < 17)
    #   which are either noise peaks or head-on CRs.
    segm = detect_sources(imgarr, 0, npixels=17)
    log.debug(f'Detected {segm.nlabels} raw sources in {filename}')
    if segm.nlabels < 2:
        return False

    src_cat = SourceCatalog(imgarr, segm)
    # Remove likely cosmic-rays based on central_moments classification
    bad_srcs = classify_sources(src_cat, 1.5)
    # Get the label IDs for sources flagged as CRs, IDs are 1-based not 0-based
    pt_srcs = np.where(bad_srcs == 0)[0] + 1
    segm.remove_labels(pt_srcs)
    src_cat = SourceCatalog(imgarr, segm)  # clean up source catalog now...
    num_sources = len(src_cat)

    # trim edges from image to avoid spurious sources
    trim_slice = (slice(2, -2), slice(2, -2))

    # Now determine whether this image was affected by guiding problems
    bad_guiding = lines_in_image(imgarr[trim_slice],
                                 num_sources,
                                 min_length=min_length,
                                 min_lines=MIN_LINES)
    if bad_guiding:
        log.warning(f"Image {filename}'s GUIDING detected as: BAD.")
    else:
        log.info(f"Image {filename}'s GUIDING detected as: GOOD.")

    return bad_guiding
Example #12
0
def buildMask(dqarr, bitvalue):
    """ Builds a bit-mask from an input DQ array and a bitvalue flag """
    return bitfield_to_boolean_mask(dqarr, bitvalue, good_mask_value=1,
                                    dtype=np.uint8)
Example #13
0
def create_primary_cutouts(catalog,
                           segmentation_image,
                           imdata,
                           imwcs,
                           imdq=None,
                           dqbitmask=0,
                           imweight=None,
                           data_units='counts',
                           exptime=1,
                           pad=1):
    """
    A function for creating first-order cutouts from a (drizzle-)combined
    image given a source catalog and a segmentation image.

    Parameters
    ----------
    catalog : ImageCatalog, astropy.table.Table
        A table of sources which need to be extracted. ``catalog`` must contain
        a column named ``'id'`` which contains IDs of segments from the
        ``segmentation_image``. If ``catalog`` is an `astropy.table.Table`,
        then it's ``meta`` attribute may contain an optional
        ``'weight_colname'`` item indicating which column in the table shows
        source weight. If not provided, unweighted fitting will be performed.

    segmentation_image: numpy.ndarray
        A 2D segmentation image identifying sources from the catalog
        in ``imdata``.

    imdata: numpy.ndarray
        Image data array.

    imwcs: astropy.wcs.WCS
        World coordinate system of image ``imdata``.

    imdq: numpy.ndarray, None, optional
        Data quality (DQ) array corresponding to ``imdata``.

    dqbitmask : int, str, None, optional
        Integer sum of all the DQ bit values from the input ``imdq``
        DQ array that should be considered "good" when building masks for
        cutouts. For example, if pixels in the DQ array can be
        combinations of 1, 2, 4, and 8 flags and one wants to consider DQ
        "defects" having flags 2 and 4 as being acceptable, then ``dqbitmask``
        should be set to 2+4=6. Then a DQ pixel having values 2,4, or 6
        will be considered a good pixel, while a DQ pixel with a value,
        e.g., 1+2=3, 4+8=12, etc. will be flagged as a "bad" pixel.

        Alternatively, one can enter a comma- or '+'-separated list
        of integer bit flags that should be added to obtain the
        final "good" bits. For example, both ``4,8`` and ``4+8``
        are equivalent to setting ``dqbitmask`` to 12.

        | Default value (0) will make *all* non-zero
          pixels in the DQ mask to be considered "bad" pixels, and the
          corresponding image pixels will be flagged in the ``mask`` property
          of the returned cutouts.

        | Set ``dqbitmask`` to `None` to not consider DQ array when computing
          cutout's ``mask``.

        | In order to reverse the meaning of the ``dqbitmask``
          parameter from indicating values of the "good" DQ flags
          to indicating the "bad" DQ flags, prepend '~' to the string
          value. For example, in order to mask only pixels that have
          corresponding DQ flags 4 and 8 and to consider
          as "good" all other pixels set ``dqbitmask`` to ``~4+8``, or ``~4,8``.
          To obtain the same effect with an `int` input value (except for 0),
          enter ``-(4+8+1)=-9``. Following this convention,
          a ``dqbitmask`` string value of ``'~0'`` would be equivalent to
          setting ``dqbitmask=None``.

    imweight: numpy.ndarray, None, optional
        Pixel weight array corresponding to ``imdata``.

    data_units: {'counts', 'rate'}, optional
        Indicates the type of data units: count-like or rate-like (counts per
        unit of time). This provides the information necessary for unit
        conversion when needed.

    exptime: float, optional
        Exposure time of image ``imdata``.

    pad: int, optional
        Number of pixels to pad around the minimal rectangle enclosing
        a source segmentation.

    Returns
    -------
    segments : list of Cutout
        A list of extracted ``Cutout`` s.

    """
    if isinstance(catalog, ImageCatalog):
        catalog = catalog.catalog

    ny, nx = segmentation_image.shape
    pad = _ceil(pad) if pad >= 0 else _floor(pad)

    # find IDs present both in the catalog AND segmentation image
    ids, cat_indices, _ = np.intersect1d(np.asarray(catalog['id']),
                                         np.setdiff1d(
                                             np.unique(segmentation_image),
                                             [0]),
                                         return_indices=True)

    segments = []
    if 'weight' in catalog.colnames:
        src_weights = catalog['weight']
    else:
        src_weights = None

    for sid, sidx in zip(ids, cat_indices):
        # find indices of pixels having a 'sid' ID:
        mask = segmentation_image == sid
        idx = np.where(mask)

        # find the boundary of the segmentation region enlarged by 1 on each
        # side, to be on the safe side when re-projecting the bounding box
        # to input (distorted) images:
        x1 = np.min(idx[1])
        x2 = np.max(idx[1])
        y1 = np.min(idx[0])
        y2 = np.max(idx[0])

        if x1 <= 0 or y1 <= 0 or x2 >= (nx - 1) or y2 >= (ny - 1):
            # skip sources sitting at the edge of segmentation image.
            # we simply do not know if these are "complete" sources or
            # that these sources did not extend beyond the current
            # boundaries of the segmentation image.
            continue

        # apply extra padding:
        x1 -= pad
        x2 += pad
        y1 -= pad
        y2 += pad

        src_pos = (catalog['x'][sidx], catalog['y'][sidx])
        if src_weights is None:
            src_weight = None
        else:
            src_weight = src_weights[sidx]

        cutout = Cutout(imdata,
                        imwcs,
                        blc=(x1, y1),
                        trc=(x2, y2),
                        src_pos=src_pos,
                        src_weight=src_weight,
                        dq=imdq,
                        weight=imweight,
                        src_id=sid,
                        data_units=data_units,
                        exptime=exptime,
                        fillval=0)

        cutout.mask |= np.logical_not(mask[cutout.extraction_slice])

        if imdq is not None:
            cutout.mask |= bitfield_to_boolean_mask(cutout.dq,
                                                    ignore_flags=dqbitmask,
                                                    good_mask_value=False)

        if not np.all(cutout.mask):  # ignore cutouts without any good pixels
            segments.append(cutout)

    return segments
Example #14
0
def drz_from_input_cutouts(input_cutouts,
                           segmentation_image,
                           imdata,
                           imwcs,
                           imdq=None,
                           dqbitmask=0,
                           imweight=None,
                           data_units='counts',
                           exptime=1,
                           pad=1,
                           combine_seg_mask=True):
    """
    A function for creating cutouts in one image from cutouts from another
    image. Specifically, this function maps input cutouts to quadrilaterals
    in some image and then finds minimal enclosing rectangle that encloses
    the quadrilateral. This minimal rectangle is then padded as requested
    and a new `Cutout` from ``imdata`` is created.

    This function is similar to ``create_input_image_cutouts`` the main
    differences being how partial overlaps are treated and "bad" pixels
    (pixels that are not within the segmentation map) are masked in the
    ``mask`` attribute.

    If an input ``Cutout`` from ``input_cutouts`` does not fit even partially
    within ``imdata`` (after padding) that ``Cutout`` is ignored.
    If an input ``Cutout`` from ``input_cutouts`` does fit partially
    within ``imdata`` (after padding) that ``Cutout`` is filled with zeros.

    Parameters
    ----------
    input_cutouts : list of Cutout
        A list of ``Cutout``s that need to be mapped to *another* image.

    segmentation_image: numpy.ndarray
        A 2D segmentation image identifying sources from the catalog
        in ``imdata``. This is used for creating boolean mask of ``bad``
        (not within a segmentation region) pixels.

    imdata: numpy.ndarray
        Image data array to which ``input_cutouts`` should be mapped.

    imwcs: astropy.wcs.WCS
        World coordinate system of image ``imdata``.

    imdq: numpy.ndarray, None, optional
        Data quality array corresponding to ``imdata``.

    dqbitmask : int, str, None, optional
        Integer sum of all the DQ bit values from the input ``imdq``
        DQ array that should be considered "good" when building masks for
        cutouts. For more details, see `create_primary_cutouts`.

    imweight: numpy.ndarray, None, optional
        Pixel weight array corresponding to ``imdata``.

    data_units: {'counts', 'rate'}, optional
        Indicates the type of data units: count-like or rate-like (counts per
        unit of time). This provides the information necessary for unit
        conversion when needed.

    exptime: float, optional
        Exposure time of image ``imdata``.

    pad: int, optional
        Number of pixels to pad around the minimal rectangle enclosing
        a mapped cutout (a cutout to be extracted).

    combine_seg_mask: bool, optional
        Indicates whether to combine segmanetation mask with cutout's
        mask. When `True`, segmentation image is used to create a mask that
        indicates "good" pixels in the image. This mask is combined with
        cutout's mask.

    Returns
    -------
    imcutouts : list of Cutout
        A list of extracted ``Cutout``s.

    valid_input_cutouts : list of Cutout
        A list of ``Cutout``s from ``primary_cutouts`` that at least partially
        fit within ``imdata``. There is a one-to-one correspondence
        between cutouts in ``valid_input_cutouts`` and cutouts in
        ``imcutouts``. That is, each ``Cutout`` from ``imcutouts`` has a
        corresponding (at the same position in the list) ``Cutout``
        in ``valid_input_cutouts``.

    """
    imcutouts = []
    valid_input_cutouts = []
    ny, nx = imdata.shape
    pad = _ceil(pad) if pad >= 0 else _floor(pad)

    for ct in input_cutouts:
        imfootprint = imwcs.all_world2pix(ct.get_bbox('world'),
                                          0,
                                          accuracy=1e-5,
                                          maxiter=50)

        # find a conservative bounding *rectangle*:
        x1 = _floor(imfootprint[:, 0].min() - pad)
        y1 = _floor(imfootprint[:, 1].min() - pad)
        x2 = _ceil(imfootprint[:, 0].max() + pad)
        y2 = _ceil(imfootprint[:, 1].max() + pad)

        # skip a cutout if its bounding rectangle is entirely inside
        # the image's data array:
        try:
            imct = Cutout(imdata,
                          imwcs,
                          blc=(x1, y1),
                          trc=(x2, y2),
                          src_weight=ct.src_weight,
                          dq=imdq,
                          weight=imweight,
                          src_id=ct.src_id,
                          data_units=data_units,
                          exptime=exptime,
                          mode='fill',
                          fillval=0)

        except NoOverlapError:
            continue

        # update cutout mask with segmentation image:
        if combine_seg_mask:
            seg = np.zeros_like(imct.data)
            seg[imct.insertion_slice] = segmentation_image[
                imct.extraction_slice]
            imct.mask |= ~(seg == ct.src_id)

        if imdq is not None:
            imct.mask |= bitfield_to_boolean_mask(imct.dq,
                                                  ignore_flags=dqbitmask,
                                                  good_mask_value=False)

        if np.all(imct.mask):
            continue

        # only when there is at least partial overlap,
        # compute source position from the primary_cutouts to
        # imcutouts using full WCS transformations:
        imct.cutout_src_pos = imct.world2pix(ct.pix2world([ct.cutout_src_pos
                                                           ]))[0].tolist()

        imcutouts.append(imct)
        valid_input_cutouts.append(ct)

    return imcutouts, valid_input_cutouts
Example #15
0
def create_input_image_cutouts(primary_cutouts,
                               imdata,
                               imwcs,
                               imdq=None,
                               dqbitmask=0,
                               imweight=None,
                               data_units='counts',
                               exptime=1,
                               pad=1):
    """
    A function for creating cutouts in one image from cutouts from another
    image. Specifically, this function maps input cutouts to quadrilaterals
    in some image and then finds minimal enclosing rectangle that encloses
    the quadrilateral. This minimal rectangle is then padded as requested
    and a new `Cutout` from ``imdata`` is created. If an input ``Cutout``
    from ``primary_cutouts`` does not fit entirely within ``imdata``
    (after padding) that ``Cutout`` is ignored.

    Parameters
    ----------
    primary_cutouts : list of Cutout
        A list of ``Cutout``s that need to be mapped to *another* image.

    imdata: numpy.ndarray
        Image data array to which ``primary_cutouts`` should be mapped.

    imwcs: astropy.wcs.WCS
        World coordinate system of image ``imdata``.

    imdq: numpy.ndarray, None, optional
        Data quality array corresponding to ``imdata``.

    dqbitmask : int, str, None, optional
        Integer sum of all the DQ bit values from the input ``imdq``
        DQ array that should be considered "good" when building masks for
        cutouts. For more details, see `create_primary_cutouts`.

    imweight: numpy.ndarray, None, optional
        Pixel weight array corresponding to ``imdata``.

    data_units: {'counts', 'rate'}, optional
        Indicates the type of data units: count-like or rate-like (counts per
        unit of time). This provides the information necessary for unit
        conversion when needed.

    exptime: float, optional
        Exposure time of image ``imdata``.

    pad: int, optional
        Number of pixels to pad around the minimal rectangle enclosing
        a mapped cutout (a cutout to be extracted).

    Returns
    -------
    imcutouts : list of Cutout
        A list of extracted ``Cutout``s.

    valid_input_cutouts : list of Cutout
        A list of ``Cutout``s from ``primary_cutouts`` that completely fit
        within ``imdata``. There is a one-to-one correspondence
        between cutouts in ``valid_input_cutouts`` and cutouts in
        ``imcutouts``. That is, each ``Cutout`` from ``imcutouts`` has a
        corresponding (at the same position in the list) ``Cutout``
        in ``valid_input_cutouts``.

    """
    imcutouts = []
    valid_input_cutouts = []
    ny, nx = imdata.shape
    for ct in primary_cutouts:
        imfootprint = imwcs.all_world2pix(ct.get_bbox('world'),
                                          0,
                                          accuracy=1e-5,
                                          maxiter=50)

        # find a conservative bounding *rectangle*:
        x1 = _floor(imfootprint[:, 0].min() - pad)
        y1 = _floor(imfootprint[:, 1].min() - pad)
        x2 = _ceil(imfootprint[:, 0].max() + pad)
        y2 = _ceil(imfootprint[:, 1].max() + pad)

        # skip a cutout if its bounding rectangle is entirely inside
        # the image's data array:
        if (x1 < 0 or x1 >= nx or y1 < 0 or y1 > ny or x2 < 0 or x2 >= nx
                or y2 < 0 or y2 > ny):
            continue

        try:
            imct = Cutout(imdata,
                          imwcs,
                          blc=(x1, y1),
                          trc=(x2, y2),
                          src_weight=ct.src_weight,
                          dq=imdq,
                          weight=imweight,
                          src_id=ct.src_id,
                          data_units=data_units,
                          exptime=exptime,
                          fillval=0)

            if imdq is not None:
                imct.mask |= bitfield_to_boolean_mask(imct.dq,
                                                      ignore_flags=dqbitmask,
                                                      good_mask_value=False)

            if np.all(imct.mask):
                continue

        except (NoOverlapError, PartialOverlapError):
            continue

        # only when there is at least partial overlap,
        # compute source position from the primary_cutouts to
        # imcutouts using full WCS transformations:
        imct.cutout_src_pos = imct.world2pix(ct.pix2world([ct.cutout_src_pos
                                                           ]))[0].tolist()

        imcutouts.append(imct)
        valid_input_cutouts.append(ct)

    return imcutouts, valid_input_cutouts
Example #16
0
    def _buildMask(self, image_fname, ext, dq_bits, dq, dqext, msk, mskext):
        if dq_bits is None or dq is None:
            # we will use only the user mask:
            if msk is not None:
                if (self._optimize == 'balanced' and msk.can_reload_data) or \
                    self._optimize == 'speed' or self._optimize == 'inmemory':
                    # nothing to do: simply re-use the user mask:
                    self._mask = msk
                    self._maskext = mskext
                    self._mask.hold()
                else:
                    # self._optimize == 'balanced' but the mask cannot be freed
                    # so we will create a temporary fits file to hold mask data:
                    maskdata = (msk.hdu[mskext].data != 0).astype(np.uint8)
                    (root, suffix, fext) = file_name_components(image_fname)
                    mfname, self._mask = temp_mask_file(maskdata,
                                                        root,
                                                        prefix='',
                                                        suffix='skymatch_mask',
                                                        ext=ext,
                                                        randomize_prefix=False)
                    self._files2clean.append(mfname)
                    self._maskext = 0

                self._can_free_mask = self._mask.can_reload_data
                self._mask_is_imref = True

            else:
                # no mask will be used in sky computations:
                self._mask = None
                self._can_free_mask = False
                self._mask_is_imref = False

        else:
            # compute a new mask by:
            #    1. applying dq_bits to DQ array
            #    2. combining previous array with the user mask data
            #
            # If dq_bits show the "bad" bits then DQ mask should be computed
            # using:
            #
            #dqmskarr = np.logical_not(np.bitwise_and(dq.hdu[dqext].data,dq_bits))
            #
            # However, to keep the same convention with astrodrizzle, dq_bits
            # will show the "good" bits that should be removed from the DQ array:
            dqmskarr = bitfield_to_boolean_mask(dq.hdu[dqext].data,
                                                dq_bits,
                                                dtype=np.bool_)

            # 2. combine with user mask:
            if msk is not None:
                maskdata = (msk.hdu[mskext].data != 0)
                dqmskarr = np.logical_and(dqmskarr, maskdata)
            # create a temporary file with the combined mask:
            (root, suffix, fext) = file_name_components(image_fname)
            if self._optimize == 'inmemory':
                self._mask = in_memory_mask(dqmskarr.astype(np.uint8))
                strext = ext2str(ext, compact=True, default_extver=None)
                self._mask.original_fname = "{1:s}{0:s}{2:s}{0:s}{3:s}" \
                    .format('_', root, suffix, 'in-memory_skymatch_mask')
            else:
                mfname, self._mask = temp_mask_file(dqmskarr.astype(np.uint8),
                                                    root,
                                                    prefix='',
                                                    suffix='skymatch_mask',
                                                    ext=ext,
                                                    randomize_prefix=False)
                self._files2clean.append(mfname)

            self._maskext = 0
            self._can_free_mask = self._mask.can_reload_data
            self._mask_is_imref = True