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 _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
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
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)
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
def test_bitfield_must_be_integer_check(): with pytest.raises(TypeError): bitmask.bitfield_to_boolean_mask(1.0, 1)
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
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
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)
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
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
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
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
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