Example #1
0
    def _get_maskpoly_from_row(self, table_row):
        """
        Get a mask polygon from a table row

        Parameters
        ----------
        table_row : `np.ndarray`
           Row of a mask table with RAs and Decs

        Returns
        -------
        maskpoly : `healsparse.Polygon`
        """
        mask_poly = healsparse.Polygon(ra=np.array([
            table_row['ra_1'], table_row['ra_2'], table_row['ra_3'],
            table_row['ra_4']
        ]),
                                       dec=np.array([
                                           table_row['dec_1'],
                                           table_row['dec_2'],
                                           table_row['dec_3'],
                                           table_row['dec_4']
                                       ]),
                                       value=1)
        return mask_poly
Example #2
0
def load_polygons(*, data, values, bands=None):
    """
    load a set of polygons (rectangles) from the input
    data.  EDGEBLEED is skipped for 'u' and 'Y' bands

    Parameters
    ----------
    data: array with fields
        Must have
        ra_1, dec_1
        ra_2, dec_2
        ra_3, dec_3
        ra_4, dec_4
        badpix
    """

    values = _extract_values(values, data.size)

    has_bands = 'band' in data.dtype.names
    has_badpix = 'badpix' in data.dtype.names
    if bands is not None and not has_bands:
        raise ValueError('bands= sent but no bands present in input data')

    EDGEBLEED = 128

    polygons = []
    for i in range(data.size):

        idata = data[i]

        if has_bands and bands is not None:
            band = idata['band'].strip()
            if band not in bands:
                continue

        if (has_bands and has_badpix and band in ['u', 'Y']
                and idata['badpix'] == EDGEBLEED):

            print('skipping %s EDGEBLEED' % band)
            continue

        ra, dec = _extract_vert(idata)

        polygon = hs.Polygon(
            ra=ra,
            dec=dec,
            value=values[i],
        )
        polygons.append(polygon)

    return polygons
Example #3
0
    def _render_catalog(self, maskmap, maskcat, indices):
        """
        Render part of a maskcat into a mask map.

        Parameters
        ----------
        maskmap : `healsparse.HealSparseMap`
           The map must be of `healsparse.WIDE_MASK` type
        maskcat : `np.ndarray`
           Catalog of region information
        indices : `np.ndarray`
           Array of indices from maskcat to render
        """
        box = np.isnan(maskcat['radius'][indices])
        circ = ~box

        ra = maskcat['ra'][indices[circ]]
        dec = maskcat['dec'][indices[circ]]
        radius = maskcat['radius'][indices[circ]]

        shapes = []
        for i in range(ra.size):
            shapes.append(
                healsparse.Circle(ra=ra[i],
                                  dec=dec[i],
                                  radius=radius[i],
                                  value=[self.config.default_bit]))

        ra = maskcat['ra'][indices[box]]
        dec = maskcat['dec'][indices[box]]
        width = maskcat['width'][indices[box]] / np.cos(
            np.deg2rad(maskcat['dec'][indices[box]]))
        height = maskcat['height'][indices[box]]

        for i in range(ra.size):
            shapes.append(
                healsparse.Polygon(ra=[
                    ra[i] - width[i] / 2., ra[i] - width[i] / 2.,
                    ra[i] + width[i] / 2., ra[i] + width[i] / 2.
                ],
                                   dec=[
                                       dec[i] - height[i] / 2.,
                                       dec[i] + height[i] / 2.,
                                       dec[i] + height[i] / 2.,
                                       dec[i] - height[i] / 2.
                                   ],
                                   value=[self.config.default_bit]))

        healsparse.realize_geom(shapes, maskmap)
Example #4
0
    def _make_nexp_map(self):
        """
        Make the number-of-exposures map

        Returns
        -------
        nexp_map : `healsparse.HealSparseMap`
        """
        # Figure out coverage
        ipnest = hp.ang2pix(self.config.nside_coverage,
                            self.centers['ra_center'], self.centers['dec_center'],
                            nest=True, lonlat=True)
        pixels = np.unique(ipnest)

        # Initialize the map and memory
        nexp_map = healsparse.HealSparseMap.make_empty(self.config.nside_coverage,
                                                       self.config.nside,
                                                       np.int32,
                                                       sentinel=0,
                                                       cov_pixels=pixels)

        bad = ((self.table['naxis1'] < self.config.border*2) |
               (self.table['naxis2'] < self.config.border*2))

        # Loop over boxes
        for i, wcs in enumerate(self.wcs_list):
            if bad[i]:
                continue

            x_coords = np.array([self.config.border,
                                 self.config.border,
                                 self.table['naxis1'][i] - self.config.border,
                                 self.table['naxis1'][i] - self.config.border])
            y_coords = np.array([self.config.border,
                                 self.table['naxis2'][i] - self.config.border,
                                 self.table['naxis2'][i] - self.config.border,
                                 self.config.border])
            ra, dec = wcs.image2sky(x_coords, y_coords)
            poly = healsparse.Polygon(ra=ra, dec=dec,
                                      value=1)
            try:
                nexp_map[poly.get_pixels(nside=nexp_map.nside_sparse)] += 1
            except ValueError:
                print('Bad WCS mapping for %d/%d' %
                      (self.table[self.config.exp_field][i],
                       self.table[self.config.ccd_field][i]))

        return nexp_map
Example #5
0
    def _extract(self):

        ra = []
        dec = []

        npair = len(self.data) // 2

        ra = np.zeros(npair)
        dec = np.zeros(npair)

        for i in range(npair):
            ira = i * 2
            idec = i * 2 + 1

            ra[i] = self.data[ira]
            dec[i] = self.data[idec]

        self._geom = hs.Polygon(
            ra=ra,
            dec=dec,
            value=self.value,
        )
Example #6
0
def worker(image, return_dict, procnum):
    #Read image header and get coordinates of the image

    print(i, 'working with image', image)
    hdr = pf.open(image)
    w = astrowcs.WCS(hdr[1].header)
    corners_image = w.calc_footprint(center=False)
    corners_pixels = w.calc_footprint(center=True)
    nx = hdr[1].header['naxis1']
    ny = hdr[1].header['naxis2']

    coords1 = corners_image[0]
    coords2 = corners_image[1]
    coords3 = corners_image[2]
    coords4 = corners_image[3]
    #Read image data
    image_data = pf.getdata(image)

    #Define healsparse polygon within the image
    ra = [coords1[0], coords4[0], coords3[0], coords2[0]]
    dec = [coords1[1], coords4[1], coords3[1], coords2[1]]
    raC = (np.max(ra) + np.min(ra)) / 2.
    decC = (np.max(dec) + np.min(dec)) / 2.

    nside = 2**17
    poly = hs.Polygon(ra=ra, dec=dec, value=1)
    smap = poly.get_map(nside=nside, dtype=np.int16)
    a = smap.validPixels

    b = hp.nest2ring(nside, a)
    #a are pixels in NEST, b in RING
    #Get center coordinates of each pixel
    raF, decF = hp.pix2ang(nside, a, lonlat=True, nest=True)

    #Get nx, ny in image from healsparse pixels
    myhdr = hdr[1].header
    wcs = wc.WCS(myhdr)
    xn, yn = wcs.sky2image(raF, decF)
    #Get associated weight values
    values = []
    for x, y in zip(xn, yn):

        values.append(image_data[int(y - 1), int(x - 1)])

    values = np.array(values)
    #Define healsparse map
    hsp_map_2 = hs.HealSparseMap.makeEmpty(512, nside, dtype=np.int16)

    hsp_map_2.updateValues(a, values)

    #Degrade to nside=4096
    low_res_hsp = hsp_map_2.degrade(4096)

    j = low_res_hsp.validPixels

    test_values_2 = low_res_hsp.getValuePixel(j)

    #Uncomment if you want in RING format
    #k=hp.nest2ring(4096,j)

    #hp_aux = np.zeros(hp.nside2npix(4096))+hp.UNSEEN
    #hp_aux[j] = test_values_2

    hdr.close()

    return_dict[procnum] = np.array([j, test_values_2]).transpose()
Example #7
0
    def build_region_input_map(self, indices, tilename=None, hpix=None):
        """
        Build input map for a given tile or hpix.
        Must specify tilename or hpix.

        Parameters
        ----------
        indices : `np.ndarray`
           Indices of WCS table
        tilename : `str`, optional
           Name of tile
        hpix : `int`, optional
           Healpix number

        Returns
        -------
        input_map : `healsparse.HealSparseMap`
           Wide mask map
        """
        if tilename is None and hpix is None:
            raise RuntimeError("Must specify one of tilename or hpix.")
        if tilename is not None and hpix is not None:
            raise RuntimeError("Must specify one of tilename or hpix.")

        if self.config.use_two_amps:
            maxbits = len(indices) * 2
        else:
            maxbits = len(indices)
        region_input_map = healsparse.HealSparseMap.make_empty(
            nside_coverage=self.nside_coverage_region,
            nside_sparse=self.config.nside,
            dtype=healsparse.WIDE_MASK,
            wide_mask_maxbits=maxbits)

        # Pre-match mask tables if available
        expnums = np.unique(dg.table[self.config.exp_field][indices])
        if dg.streak_table is not None:
            _, streak_b = esutil.numpy_util.match(
                expnums, dg.streak_table[self.config.exp_field])
        if dg.bleed_table is not None:
            _, bleed_b = esutil.numpy_util.match(
                expnums, dg.bleed_table[self.config.exp_field])
        if dg.satstar_table is not None:
            _, satstar_b = esutil.numpy_util.match(
                expnums, dg.satstar_table[self.config.exp_field])

        metadata = {}
        for i, ind in enumerate(indices):
            if self.config.use_two_amps:
                # Everything needs to be done with 2 amps
                bit_a = i * 2
                bit_b = i * 2 + 1

                metadata['B%04dCCD' %
                         (bit_a)] = dg.table[self.config.ccd_field][ind]
                metadata['B%04dCCD' %
                         (bit_b)] = dg.table[self.config.ccd_field][ind]
                metadata['B%04dEXP' %
                         (bit_a)] = dg.table[self.config.exp_field][ind]
                metadata['B%04dEXP' %
                         (bit_b)] = dg.table[self.config.exp_field][ind]
                metadata['B%04dAMP' % (bit_a)] = 'A'
                metadata['B%04dAMP' % (bit_b)] = 'B'
                metadata['B%04dWT' % (bit_a)] = self._compute_weight(
                    self.config.skyvar_field + 'a', ind)
                metadata['B%04dWT' % (bit_b)] = self._compute_weight(
                    self.config.skyvar_field + 'b', ind)
            else:
                bit = i

                metadata['B%04dCCD' %
                         (bit)] = dg.table[self.config.ccd_field][ind]
                metadata['B%04dEXP' %
                         (bit)] = dg.table[self.config.exp_field][ind]
                metadata['B%04dWT' % (bit)] = self._compute_weight(
                    self.config.skyvar_field, ind)

            wcs = dg.wcs_list[ind]

            if wcs is None:
                continue

            if self.config.use_wcs:
                # Use the WCS and render the ccd boxes
                if self.config.use_two_amps:
                    x_coords_a = np.array([
                        self.config.amp_boundary, self.config.amp_boundary,
                        dg.table['naxis1'][ind] - self.config.border,
                        dg.table['naxis1'][ind] - self.config.border
                    ])
                    x_coords_b = np.array([
                        self.config.border, self.config.border,
                        self.config.amp_boundary, self.config.amp_boundary
                    ])
                else:
                    x_coords = np.array([
                        self.config.border, self.config.border,
                        dg.table['naxis1'][ind] - self.config.border,
                        dg.table['naxis1'][ind] - self.config.border
                    ])

                y_coords = np.array([
                    self.config.border,
                    dg.table['naxis2'][ind] - self.config.border,
                    dg.table['naxis2'][ind] - self.config.border,
                    self.config.border
                ])

                if self.config.use_two_amps:
                    ra_a, dec_a = wcs.image2sky(x_coords_a, y_coords)
                    ra_b, dec_b = wcs.image2sky(x_coords_b, y_coords)

                    poly_a = healsparse.Polygon(ra=ra_a,
                                                dec=dec_a,
                                                value=[bit_a])
                    poly_b = healsparse.Polygon(ra=ra_b,
                                                dec=dec_b,
                                                value=[bit_b])
                else:
                    ra, dec = wcs.image2sky(x_coords, y_coords)

                    poly = healsparse.Polygon(ra=ra, dec=dec, value=[bit])
            else:
                # Don't use the WCS and use the bounding box specified in the table.
                # This is only possible with 1-amp mode.
                ra = np.array([
                    dg.table[field][ind]
                    for field in self.config.ra_corner_fields
                ])
                dec = np.array([
                    dg.table[field][ind]
                    for field in self.config.dec_corner_fields
                ])
                poly = healsparse.Polygon(ra=ra, dec=dec, value=[bit])

            # Check if we have additional masking
            if (dg.streak_table is not None or dg.bleed_table is not None
                    or dg.satstar_table is not None):
                if self.config.use_two_amps:
                    poly_map_a = poly_a.get_map_like(region_input_map)
                    poly_map_b = poly_b.get_map_like(region_input_map)
                else:
                    poly_map = poly.get_map_like(region_input_map)

                mask_reg_list = []

                if dg.streak_table is not None:
                    sinds, = np.where(
                        (dg.streak_table[self.config.exp_field][streak_b] ==
                         dg.table[self.config.exp_field][ind])
                        & (dg.streak_table[self.config.ccd_field][streak_b] ==
                           dg.table[self.config.ccd_field][ind]))
                    for sind in streak_b[sinds]:
                        mask_reg_list.append(
                            self._get_maskpoly_from_row(dg.streak_table[sind]))

                if dg.bleed_table is not None:
                    binds, = np.where(
                        (dg.bleed_table[self.config.exp_field][bleed_b] ==
                         dg.table[self.config.exp_field][ind])
                        & (dg.bleed_table[self.config.ccd_field][bleed_b] ==
                           dg.table[self.config.ccd_field][ind]))
                    for bind in bleed_b[binds]:
                        mask_reg_list.append(
                            self._get_maskpoly_from_row(dg.bleed_table[bind]))

                if dg.satstar_table is not None:
                    sinds, = np.where(
                        (dg.satstar_table[self.config.exp_field][satstar_b] ==
                         dg.table[self.config.exp_field][ind])
                        & (dg.satstar_table[self.config.ccd_field][satstar_b]
                           == dg.table[self.config.ccd_field][ind]))
                    for sind in satstar_b[sinds]:
                        mask_reg_list.append(
                            self._get_maskcircle_from_row(
                                dg.satstar_table[sind]))

                mask_map = healsparse.HealSparseMap.make_empty(
                    nside_coverage=self.nside_coverage_region,
                    nside_sparse=self.config.nside,
                    dtype=np.uint8)
                healsparse.realize_geom(mask_reg_list, mask_map)

                if self.config.use_two_amps:
                    poly_map_a.apply_mask(mask_map)
                    poly_map_b.apply_mask(mask_map)
                    pixels_a = poly_map_a.valid_pixels
                    pixels_b = poly_map_b.valid_pixels
                else:
                    poly_map.apply_mask(mask_map)
                    pixels = poly_map.valid_pixels

            else:
                # With no masking we can do a slightly faster version direct
                # with the pixels
                if self.config.use_two_amps:
                    pixels_a = poly_a.get_pixels(nside=self.config.nside)
                    pixels_b = poly_b.get_pixels(nside=self.config.nside)
                else:
                    pixels = poly.get_pixels(nside=self.config.nside)

            # Check for bad amps -- only in two amp mode
            if self.config.use_two_amps:
                if int(dg.table[self.config.ccd_field][ind]) in list(
                        self.config.bad_amps):
                    ba = self.config.bad_amps[int(
                        dg.table[self.config.ccd_field][ind])]
                    for b in ba:
                        if b.lower() == 'a':
                            pixels_a = np.array([], dtype=np.int64)
                        elif b.lower() == 'b':
                            pixels_b = np.array([], dtype=np.int64)

            if int(dg.table[self.config.ccd_field]
                   [ind]) in self.config.bad_ccds:
                if self.config.use_two_amps:
                    pixels_a = np.array([], dtype=np.int64)
                    pixels_b = np.array([], dtype=np.int64)
                else:
                    pixels = np.array([], dtype=np.int64)

            # tilename implies DES, and use two amps
            if tilename is not None and self.config.use_two_amps:
                tind, = np.where(dg.tile_info['tilename'] == tilename)
                for pixels, bit in zip([pixels_a, pixels_b], [bit_a, bit_b]):
                    pixra, pixdec = hp.pix2ang(self.config.nside,
                                               pixels,
                                               lonlat=True,
                                               nest=True)
                    if dg.tile_info['crossra0'][tind] == 'Y':
                        # Special for cross-ra0, where uramin will be very large
                        uramin = dg.tile_info['uramin'][tind] - 360.0
                        pixra_rot = pixra.copy()
                        hi, = np.where(pixra > 180.0)
                        pixra_rot[hi] -= 360.0
                        ok = ((pixra_rot > uramin) &
                              (pixra_rot < dg.tile_info['uramax'][tind]) &
                              (pixdec > dg.tile_info['udecmin'][tind]) &
                              (pixdec <= dg.tile_info['udecmax'][tind]))
                    else:
                        ok = ((pixra > dg.tile_info['uramin'][tind]) &
                              (pixra <= dg.tile_info['uramax'][tind]) &
                              (pixdec > dg.tile_info['udecmin'][tind]) &
                              (pixdec <= dg.tile_info['udecmax'][tind]))
                    region_input_map.set_bits_pix(pixels[ok], [bit])

            else:
                # healpix mode
                bit_shift = 2 * int(
                    np.round(np.log2(
                        self.config.nside / self.config.nside_run)))
                npixels = 2**bit_shift
                pixel_min = hpix * npixels
                pixel_max = (hpix + 1) * npixels - 1
                if self.config.use_two_amps:
                    for pixels, bit in zip([pixels_a, pixels_b],
                                           [bit_a, bit_b]):
                        ok = ((pixels >= pixel_min) & (pixels <= pixel_max))
                        region_input_map.set_bits_pix(pixels[ok], [bit])
                else:
                    ok = ((pixels >= pixel_min) & (pixels <= pixel_max))
                    region_input_map.set_bits_pix(pixels[ok], [bit])

        region_input_map.metadata = metadata

        return region_input_map
Example #8
0
    def run(self, sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict):
        """Run the healsparse property task.

        Parameters
        ----------
        sky_map : Sky map object
        tract : `int`
            Tract number.
        band : `str`
            Band name for logging.
        coadd_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
            Dictionary of coadd exposure references.  Keys are patch numbers.
        input_map_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
            Dictionary of input map references.  Keys are patch numbers.
        visit_summary_dict : `dict` [`int`: `lsst.afw.table.ExposureCatalog`]
            Dictionary of visit summary tables.  Keys are visit numbers.

        Raises
        ------
        RepeatableQuantumError
            If visit_summary_dict is missing any visits or detectors found in an
            input map.  This leads to an inconsistency between what is in the coadd
            (via the input map) and the visit summary tables which contain data
            to compute the maps.
        """
        tract_info = sky_map[tract]

        tract_maps_initialized = False

        for patch in input_map_dict.keys():
            self.log.info("Making maps for band %s, tract %d, patch %d.",
                          band, tract, patch)

            patch_info = tract_info[patch]

            input_map = input_map_dict[patch].get()
            coadd_photo_calib = coadd_dict[patch].get(component="photoCalib")
            coadd_inputs = coadd_dict[patch].get(component="coaddInputs")

            coadd_zeropoint = 2.5*np.log10(coadd_photo_calib.getInstFluxAtZeroMagnitude())

            # Crop input_map to the inner polygon of the patch
            poly_vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
            patch_radec = self._vertices_to_radec(poly_vertices)
            patch_poly = hsp.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1],
                                     value=np.arange(input_map.wide_mask_maxbits))
            patch_poly_map = patch_poly.get_map_like(input_map)
            input_map = hsp.and_intersection([input_map, patch_poly_map])

            if not tract_maps_initialized:
                # We use the first input map nside information to initialize
                # the tract maps
                nside_coverage = self._compute_nside_coverage_tract(tract_info)
                nside = input_map.nside_sparse

                do_compute_approx_psf = False
                # Initialize the tract maps
                for property_map in self.property_maps:
                    property_map.initialize_tract_maps(nside_coverage, nside)
                    if property_map.requires_psf:
                        do_compute_approx_psf = True

                tract_maps_initialized = True

            valid_pixels, vpix_ra, vpix_dec = input_map.valid_pixels_pos(return_pixels=True)

            # Check if there are no valid pixels for the inner (unique) patch region
            if valid_pixels.size == 0:
                continue

            # Initialize the value accumulators
            for property_map in self.property_maps:
                property_map.initialize_values(valid_pixels.size)
                property_map.zeropoint = coadd_zeropoint

            # Initialize the weight and counter accumulators
            total_weights = np.zeros(valid_pixels.size)
            total_inputs = np.zeros(valid_pixels.size, dtype=np.int32)

            for bit, ccd_row in enumerate(coadd_inputs.ccds):
                # Which pixels in the map are used by this visit/detector
                inmap, = np.where(input_map.check_bits_pix(valid_pixels, [bit]))

                # Check if there are any valid pixels in the map from this deteector.
                if inmap.size == 0:
                    continue

                # visit, detector_id, weight = input_dict[bit]
                visit = ccd_row["visit"]
                detector_id = ccd_row["ccd"]
                weight = ccd_row["weight"]

                x, y = ccd_row.getWcs().skyToPixelArray(vpix_ra[inmap], vpix_dec[inmap], degrees=True)
                scalings = self._compute_calib_scale(ccd_row, x, y)

                if do_compute_approx_psf:
                    psf_array = compute_approx_psf_size_and_shape(ccd_row, vpix_ra[inmap], vpix_dec[inmap])
                else:
                    psf_array = None

                total_weights[inmap] += weight
                total_inputs[inmap] += 1

                # Retrieve the correct visitSummary row
                if visit not in visit_summary_dict:
                    msg = f"Visit {visit} not found in visit_summaries."
                    raise pipeBase.RepeatableQuantumError(msg)
                row = visit_summary_dict[visit].find(detector_id)
                if row is None:
                    msg = f"Visit {visit} / detector_id {detector_id} not found in visit_summaries."
                    raise pipeBase.RepeatableQuantumError(msg)

                # Accumulate the values
                for property_map in self.property_maps:
                    property_map.accumulate_values(inmap,
                                                   vpix_ra[inmap],
                                                   vpix_dec[inmap],
                                                   weight,
                                                   scalings,
                                                   row,
                                                   psf_array=psf_array)

            # Finalize the mean values and set the tract maps
            for property_map in self.property_maps:
                property_map.finalize_mean_values(total_weights, total_inputs)
                property_map.set_map_values(valid_pixels)
Example #9
0
    def build_ccd_input_map(self, bbox, wcs, ccds):
        """Build a map from ccd valid polygons or bounding boxes.

        Parameters
        ----------
        bbox : `lsst.geom.Box2I`
            Bounding box for region to build input map.
        wcs : `lsst.afw.geom.SkyWcs`
            WCS object for region to build input map.
        ccds : `lsst.afw.table.ExposureCatalog`
            Exposure catalog with ccd data from coadd inputs.
        """
        with warnings.catch_warnings():
            # Healsparse will emit a warning if nside coverage is greater than
            # 128.  In the case of generating patch input maps, and not global
            # maps, high nside coverage works fine, so we can suppress this
            # warning.
            warnings.simplefilter("ignore")
            self.ccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
                                                              nside_sparse=self.config.nside,
                                                              dtype=hsp.WIDE_MASK,
                                                              wide_mask_maxbits=len(ccds))
        self._wcs = wcs
        self._bbox = bbox
        self._ccds = ccds

        pixel_scale = wcs.getPixelScale().asArcseconds()
        hpix_area_arcsec2 = hp.nside2pixarea(self.config.nside, degrees=True)*(3600.**2.)
        self._min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)

        metadata = {}
        self._bits_per_visit_ccd = {}
        self._bits_per_visit = defaultdict(list)
        for bit, ccd_row in enumerate(ccds):
            metadata[f"B{bit:04d}CCD"] = ccd_row["ccd"]
            metadata[f"B{bit:04d}VIS"] = ccd_row["visit"]
            metadata[f"B{bit:04d}WT"] = ccd_row["weight"]

            self._bits_per_visit_ccd[(ccd_row["visit"], ccd_row["ccd"])] = bit
            self._bits_per_visit[ccd_row["visit"]].append(bit)

            ccd_poly = ccd_row.getValidPolygon()
            if ccd_poly is None:
                ccd_poly = afwGeom.Polygon(lsst.geom.Box2D(ccd_row.getBBox()))
            # Detectors need to be rendered with their own wcs.
            ccd_poly_radec = self._pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices())

            # Create a ccd healsparse polygon
            poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
                               dec=ccd_poly_radec[: -1, 1],
                               value=[bit])
            self.ccd_input_map.set_bits_pix(poly.get_pixels(nside=self.ccd_input_map.nside_sparse),
                                            [bit])

        # Cut down to the overall bounding box with associated wcs.
        bbox_afw_poly = afwGeom.Polygon(lsst.geom.Box2D(bbox))
        bbox_poly_radec = self._pixels_to_radec(self._wcs,
                                                bbox_afw_poly.convexHull().getVertices())
        bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
                                value=np.arange(self.ccd_input_map.wide_mask_maxbits))
        bbox_poly_map = bbox_poly.get_map_like(self.ccd_input_map)
        self.ccd_input_map = hsp.and_intersection([self.ccd_input_map, bbox_poly_map])
        self.ccd_input_map.metadata = metadata

        # Create a temporary map to hold the count of bad pixels in each healpix pixel
        self._ccd_input_pixels = self.ccd_input_map.valid_pixels

        dtype = [(f"v{visit}", np.int64) for visit in self._bits_per_visit.keys()]

        with warnings.catch_warnings():
            # Healsparse will emit a warning if nside coverage is greater than
            # 128.  In the case of generating patch input maps, and not global
            # maps, high nside coverage works fine, so we can suppress this
            # warning.
            warnings.simplefilter("ignore")
            self._ccd_input_bad_count_map = hsp.HealSparseMap.make_empty(
                nside_coverage=self.config.nside_coverage,
                nside_sparse=self.config.nside,
                dtype=dtype,
                primary=dtype[0][0])

        # Don't set input bad map if there are no ccds which overlap the bbox.
        if len(self._ccd_input_pixels) > 0:
            self._ccd_input_bad_count_map[self._ccd_input_pixels] = np.zeros(1, dtype=dtype)
    def setUp(self):
        tract = 0
        band = 'r'
        patch = 0
        visits = [100, 101]
        # Good to test crossing 0.
        ra_center = 0.0
        dec_center = -45.0
        pixel_scale = 0.2
        coadd_zp = 27.0

        # Generate a mock skymap with one patch
        config = DiscreteSkyMap.ConfigClass()
        config.raList = [ra_center]
        config.decList = [dec_center]
        config.radiusList = [150 * pixel_scale / 3600.]
        config.patchInnerDimensions = (350, 350)
        config.patchBorder = 50
        config.tractOverlap = 0.0
        config.pixelScale = pixel_scale
        sky_map = DiscreteSkyMap(config)

        visit_summaries = [
            makeMockVisitSummary(visit,
                                 ra_center=ra_center,
                                 dec_center=dec_center) for visit in visits
        ]
        visit_summary_refs = [
            MockVisitSummaryReference(visit_summary, visit)
            for visit_summary, visit in zip(visit_summaries, visits)
        ]
        self.visit_summary_dict = {
            visit: ref.get()
            for ref, visit in zip(visit_summary_refs, visits)
        }

        # Generate an input map.  Note that this does not need to be consistent
        # with the visit_summary projections, we're just tracking values.
        input_map = hsp.HealSparseMap.make_empty(
            nside_coverage=256,
            nside_sparse=32768,
            dtype=hsp.WIDE_MASK,
            wide_mask_maxbits=len(visits) * 2)
        patch_poly = afwGeom.Polygon(
            geom.Box2D(sky_map[tract][patch].getOuterBBox()))
        sph_pts = sky_map[tract].getWcs().pixelToSky(
            patch_poly.convexHull().getVertices())
        patch_poly_radec = np.array([(sph.getRa().asDegrees(),
                                      sph.getDec().asDegrees())
                                     for sph in sph_pts])
        poly = hsp.Polygon(ra=patch_poly_radec[:-1, 0],
                           dec=patch_poly_radec[:-1, 1],
                           value=[0])
        poly_pixels = poly.get_pixels(nside=input_map.nside_sparse)
        # The input map has full coverage for bits 0 and 1
        input_map.set_bits_pix(poly_pixels, [0])
        input_map.set_bits_pix(poly_pixels, [1])

        input_map_ref = MockInputMapReference(input_map,
                                              patch=patch,
                                              tract=tract)
        self.input_map_dict = {patch: input_map_ref}

        coadd = afwImage.ExposureF(sky_map[tract][patch].getOuterBBox(),
                                   sky_map[tract].getWcs())
        instFluxMag0 = 10.**(coadd_zp / 2.5)
        pc = afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0)
        coadd.setPhotoCalib(pc)

        # Mock the coadd input ccd table
        schema = afwTable.ExposureTable.makeMinimalSchema()
        schema.addField("ccd", type="I")
        schema.addField("visit", type="I")
        schema.addField("weight", type="F")
        ccds = afwTable.ExposureCatalog(schema)
        ccds.resize(2)
        ccds['id'] = np.arange(2)
        ccds['visit'][0] = visits[0]
        ccds['visit'][1] = visits[1]
        ccds['ccd'][0] = 0
        ccds['ccd'][1] = 1
        ccds['weight'] = 10.0
        for ccd_row in ccds:
            summary = self.visit_summary_dict[ccd_row['visit']].find(
                ccd_row['ccd'])
            ccd_row.setWcs(summary.getWcs())
            ccd_row.setPsf(summary.getPsf())
            ccd_row.setBBox(summary.getBBox())
            ccd_row.setPhotoCalib(summary.getPhotoCalib())

        inputs = afwImage.CoaddInputs()
        inputs.ccds = ccds
        coadd.getInfo().setCoaddInputs(inputs)

        coadd_ref = MockCoaddReference(coadd, patch=patch, tract=tract)
        self.coadd_dict = {patch: coadd_ref}

        self.tract = tract
        self.band = band
        self.sky_map = sky_map
        self.input_map = input_map
Example #11
0
    def build_patch_input_map(self, butler, tract_wcs, patch_info, ccds,
                              nside_coverage_patch):
        """
        Build the patch input map.

        Parameters
        ----------
        butler : `lsst.daf.persistence.Butler`
           gen2 butler
        tract_wcs : `lsst.afw.geom.SkyWcs`
           WCS object for the tract
        patch_info : `lsst.skymap.PatchInfo`
           Patch info object
        ccds : `lsst.afw.table.ExposureCatalog`
           Catalog of ccd information
        nside_coverage_patch : `int`
           Healpix nside for the coverage map

        Returns
        -------
        patch_input_map : `healsparse.HealSparseMap`
           Healsparse map encoding input ccd information.
        """
        patch_input_map = healsparse.HealSparseMap.make_empty(
            nside_coverage=nside_coverage_patch,
            nside_sparse=self.config.nside,
            dtype=healsparse.WIDE_MASK,
            wide_mask_maxbits=len(ccds))

        if self.config.use_calexp_mask:
            # pixel_scale = tract_wcs.getPixelScale().asArcseconds()
            hpix_area_arcsec2 = hp.nside2pixarea(patch_input_map.nside_sparse,
                                                 degrees=True) * (3600.**2.)
            bad_mask = afwImage.Mask.getPlaneBitMask(
                self.config.bad_mask_planes)

        metadata = {}
        for bit, ccd in enumerate(ccds):
            metadata['B%04dCCD' % (bit)] = ccd['ccd']
            metadata['B%04dVIS' % (bit)] = ccd['visit']
            metadata['B%04dWT' % (bit)] = ccd['weight']

            wcs = ccd.getWcs()
            ccd_poly = ccd.getValidPolygon()
            if ccd_poly is None:
                ccd_poly = afwGeom.Polygon(lsst.geom.Box2D(ccd.getBBox()))
            ccd_poly_radec = pixels_to_radec(
                wcs,
                ccd_poly.convexHull().getVertices())

            # Use polygons for all of these
            poly = healsparse.Polygon(ra=ccd_poly_radec[:-1, 0],
                                      dec=ccd_poly_radec[:-1, 1],
                                      value=[bit])
            poly_map = poly.get_map_like(patch_input_map)

            dataId = {
                self.config.detector_id_name: int(ccd['ccd']),
                self.config.visit_id_name: int(ccd['visit'])
            }

            if self.config.use_calexp_mask:
                calexp = butler.get('calexp', dataId=dataId)
                calexp_metadata = calexp.getMetadata()

                mask = calexp.getMask()

                pixel_scale = tract_wcs.getPixelScale().asArcseconds()
                min_bad = self.config.bad_mask_coverage * hpix_area_arcsec2 / (
                    pixel_scale**2.)
                bad_pixels = np.where(mask.getArray() & bad_mask)

                # Convert bad pixels to position
                bad_radec = xy_to_radec(wcs, bad_pixels[1].astype(np.float64),
                                        bad_pixels[0].astype(np.float64))

                # Convert position to healpixel
                bad_hpix = hp.ang2pix(patch_input_map.nside_sparse,
                                      bad_radec[:, 0],
                                      bad_radec[:, 1],
                                      lonlat=True,
                                      nest=True)

                min_bad_hpix = bad_hpix.min()
                bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1,
                                          dtype=np.int32)
                np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1)
                hpix_to_mask = min_bad_hpix + np.where(
                    bad_hpix_count > min_bad)[0]

                poly_map.clear_bits_pix(hpix_to_mask, [bit])

                if 'SKYLEVEL' not in calexp_metadata:
                    # We must recompute skylevel, skysigma
                    skylevel, skysigma = self._compute_skylevel(
                        butler, dataId, calexp)
                else:
                    skylevel = calexp_metadata['SKYLEVEL']
                    skysigma = calexp_metadata['SKYSIGMA']
            else:
                calexp_metadata = butler.get('calexp_md', dataId=dataId)
                if 'SKYLEVEL' not in calexp_metadata:
                    # We want to log this
                    skylevel = 0.0
                    skysigma = 0.0
                else:
                    skylevel = calexp_metadata['SKYLEVEL']
                    skysigma = calexp_metadata['SKYSIGMA']

            metadata['B%04dSLV' % (bit)] = skylevel
            metadata['B%04dSSG' % (bit)] = skysigma
            metadata['B%04dBGM' % (bit)] = calexp_metadata['BGMEAN']
            metadata['B%04dBGV' % (bit)] = calexp_metadata['BGVAR']

            # Now we have the full masked ccd map, set the appropriate bit
            patch_input_map.set_bits_pix(poly_map.valid_pixels, [bit])

        # Now cut down to the inner tract polygon
        poly_vertices = patch_info.getInnerSkyPolygon(tract_wcs).getVertices()
        patch_radec = vertices_to_radec(poly_vertices)
        patch_poly = healsparse.Polygon(ra=patch_radec[:, 0],
                                        dec=patch_radec[:, 1],
                                        value=np.arange(
                                            patch_input_map.wide_mask_maxbits))

        # Realize the patch polygon
        patch_poly_map = patch_poly.get_map_like(patch_input_map)
        patch_input_map = healsparse.and_intersection(
            [patch_input_map, patch_poly_map])

        # And set the metadata
        patch_input_map.metadata = metadata

        return patch_input_map