Exemple #1
0
def get_map_range(hpxmap, pixel=None, nside=None, wrap_angle=180):
    """ Calculate the longitude and latitude range for a map. """
    check_hpxmap(hpxmap, pixel, nside)
    if isinstance(hpxmap, np.ma.MaskedArray):
        hpxmap = hpxmap.data

    if pixel is None:
        nside = hp.get_nside(hpxmap)
        pixel = np.arange(len(hpxmap), dtype=int)

    ipring, = np.where(np.isfinite(hpxmap) & (hpxmap != hp.UNSEEN))
    theta, phi = hp.pix2ang(nside, pixel[ipring])
    lon = np.mod(np.degrees(phi), 360)
    lat = 90.0 - np.degrees(theta)

    # Small offset to add to make sure we get the whole pixel
    eps = np.degrees(hp.max_pixrad(nside))

    # CHECK ME
    hi, = np.where(lon > wrap_angle)
    lon[hi] -= 360.0

    lon_min = max(np.nanmin(lon) - eps, wrap_angle - 360)
    lon_max = min(np.nanmax(lon) + eps, wrap_angle)
    lat_min = max(np.nanmin(lat) - eps, -90)
    lat_max = min(np.nanmax(lat) + eps, 90)

    return (lon_min, lon_max), (lat_min, lat_max)
 def __init__(self,
              nside=256,
              opsimdf=None,
              healpixelizedOpSim=None,
              preComputedMap=None,
              leafsize=50,
              fovRadius=1.75,
              raCol='ditheredRA',
              decCol='ditheredDec'):
     """
     nside : int, power of 2, defaults to 256
         nside parameter of healpix. determines the size of the tiles
         so that there are 12 * nside **2 equally sized tiles covering
         the sphere.
     """
     self.nside = nside
     self.maxradius = hp.max_pixrad(nside=self.nside)
     self.npix = hp.nside2npix(nside)
     self._tileArea = hp.nside2pixarea(nside)
     self.hpOpSim = healpixelizedOpSim
     self._preComputedMap = preComputedMap
     self.opsimdf = opsimdf
     if self.hpOpSim is None and self.preComputedMap is None and self.opsimdf is None:
         raise ValueError('hpOpSim and preComputedMap and opsimdf cannot'
                          ' both be None')
     self._preComputedEngine = None
     self.fovRadius = np.radians(fovRadius)
     self.pointingTree = None
     if self.opsimdf is not None:
         self.pointingTree = PointingTree(self.opsimdf,
                                          raCol=raCol,
                                          decCol=decCol,
                                          leafSize=leafsize,
                                          indexCol='obsHistID')
Exemple #3
0
    def calc_signal_spatial(self):
        # At the pixel level over the ROI
        pix_lon, pix_lat = self.roi.pixels_interior.lon, self.roi.pixels_interior.lat
        nside = self.config['coords']['nside_pixel']
        #self.angsep_sparse = angsep(self.lon,self.lat,pix_lon,pix_lat)
        #self.surface_intensity_sparse = self.kernel.surfaceIntensity(self.angsep_sparse)
        if self.kernel.extension < 2 * np.degrees(healpy.max_pixrad(nside)):
            #msg =  "small kernel"
            #msg += ("\n"+str(self.params))
            #logger.warning(msg)
            idx = self.roi.indexInterior(self.kernel.lon, self.kernel.lat)
            self.surface_intensity_sparse = np.zeros(len(pix_lon))
            self.surface_intensity_sparse[idx] = 1.0 / self.roi.area_pixel
        else:
            self.surface_intensity_sparse = self.kernel.pdf(pix_lon, pix_lat)

        # On the object-by-object level
        #self.angsep_object = angsep(self.lon,self.lat,self.catalog.lon,self.catalog.lat)
        #self.surface_intensity_object = self.kernel.surfaceIntensity(self.angsep_object)
        self.surface_intensity_object = self.kernel.pdf(
            self.catalog.lon, self.catalog.lat)

        # Spatial component of signal probability
        #u_spatial = self.roi.area_pixel * self.surface_intensity_object
        u_spatial = self.surface_intensity_object
        return u_spatial
Exemple #4
0
 def test_attributes(self):
     """Test basic, default attributes are set on creation
     """
     skymap = self.TEST_SKY_MAP
     assert skymap.info is None
     assert (not skymap.nest)
     assert skymap.nside == self.NSIDE
     assert skymap.npix == self.NPIX
     assert_array_equal(skymap.pindex, numpy.arange(self.NPIX))
     assert (not skymap.partial)
     assert isinstance(skymap.explicit, dict)
     assert len(skymap.explicit) == self.NPIX
     assert skymap.resolution.value == healpy.nside2resol(
         self.NSIDE, arcmin=True)
     assert skymap.resolution.unit == units.arcmin
     assert skymap.pixrad.value == healpy.max_pixrad(
         self.NSIDE) * units.rad.to("arcmin")
     assert skymap.pixrad.unit == units.arcmin
     assert skymap.pixarea.value == healpy.nside2pixarea(
         self.NSIDE, degrees=True)
     assert skymap.pixarea.unit == units.deg ** 2
     assert skymap.area.value == skymap.size * skymap.pixarea.value
     assert skymap.area.unit == units.deg ** 2
     assert isinstance(skymap.directions, numpy.ndarray)
     assert_array_equal(skymap.directions.shape, (skymap.npix, 3))
    def __call__(self):
        #load module
        mod = importlib.import_module(self.ctx.params.beam_profile_provider)

        #delegate loading of profile

        params = self.ctx.params
        #setting up angular grid
        beam_area = np.radians(params.beam_elevation) * np.radians(
            params.beam_azimut)  # [rad]
        pixel_area = hp.nside2pixarea(params.beam_nside, degrees=False)
        pixels = np.floor(np.sqrt(beam_area / pixel_area))
        pixels = pixels if pixels % 2 == 1 else pixels + 1

        pixel_size = hp.max_pixrad(params.beam_nside)
        theta = (np.linspace(0, pixels, pixels * 2 + 1) -
                 pixels / 2) * (pixel_size)
        phi = theta

        beam_spec = BeamSpec(phi, theta, pixels**2)
        frequencies = np.arange(params.beam_frequency_min,
                                params.beam_frequency_max,
                                params.beam_frequency_pixscale)

        beam_profiles, beam_norms = mod.load_beam_profile(
            beam_spec, frequencies, self.ctx.params)

        self.ctx.beam_spec = beam_spec
        self.ctx.frequencies = frequencies
        self.ctx.beam_profiles = beam_profiles
        self.ctx.beam_norms = beam_norms
Exemple #6
0
 def __call__(self):
     #load module
     mod = importlib.import_module(self.ctx.params.beam_profile_provider)
     
     #delegate loading of profile
     
     params = self.ctx.params
     #setting up angular grid
     beam_area = np.radians(params.beam_elevation) * np.radians(params.beam_azimut) # [rad]
     pixel_area = hp.nside2pixarea(params.beam_nside, degrees=False)
     pixels = np.floor(np.sqrt(beam_area / pixel_area))
     pixels = pixels if pixels%2==1 else pixels+1
     
     pixel_size = hp.max_pixrad(params.beam_nside)
     theta = (np.linspace(0, pixels, pixels*2+1)-pixels/2)*(pixel_size)
     phi = theta
     
     beam_spec = BeamSpec(phi, theta, pixels**2)
     frequencies = np.arange(params.beam_frequency_min, params.beam_frequency_max, params.beam_frequency_pixscale)
     
     beam_profiles, beam_norms = mod.load_beam_profile(beam_spec, frequencies, self.ctx.params)
     
     self.ctx.beam_spec = beam_spec
     self.ctx.frequencies = frequencies
     self.ctx.beam_profiles = beam_profiles
     self.ctx.beam_norms = beam_norms
Exemple #7
0
def get_pix_range(nside, pixel, nest=False, wrap_angle=180):
    """
    Calculate the longitude and latitude range for a map.

    Parameters
    ----------
    nside : map nside
    pixel : pixel index
    wrap_angle : map wrapping angle (deg)

    Returns
    -------
    [[lon_min,lon_max],[lat_min,lat_max]] : map range (deg)
    """
    lon, lat = hp.pix2ang(nside, pixel, nest=nest, lonlat=True)

    # Small offset to add to make sure we get the whole pixel
    eps = np.degrees(hp.max_pixrad(nside))

    # CHECK ME
    # hi,=np.where(lon > wrap_angle)
    # lon[hi] -= 360.0

    lon_min = max(np.nanmin(lon) - eps, 0)  # ,wrap_angle-360)
    lon_max = min(np.nanmax(lon) + eps, 360)  # ,wrap_angle)
    lat_min = max(np.nanmin(lat) - eps, -90)
    lat_max = min(np.nanmax(lat) + eps, 90)

    return (lon_min, lon_max), (lat_min, lat_max)
Exemple #8
0
    def calc_signal_spatial(self):
        # At the pixel level over the ROI
        pix_lon,pix_lat = self.roi.pixels_interior.lon,self.roi.pixels_interior.lat
        nside = self.config['coords']['nside_pixel']
        #self.angsep_sparse = angsep(self.lon,self.lat,pix_lon,pix_lat)
        #self.surface_intensity_sparse = self.kernel.surfaceIntensity(self.angsep_sparse)
        if self.kernel.extension < 2*np.degrees(healpy.max_pixrad(nside)):
            #msg =  "small kernel"
            #msg += ("\n"+str(self.params))
            #logger.warning(msg)
            idx = self.roi.indexInterior(self.kernel.lon,self.kernel.lat)
            self.surface_intensity_sparse = np.zeros(len(pix_lon))
            self.surface_intensity_sparse[idx] = 1.0/self.roi.area_pixel
        else:
            self.surface_intensity_sparse = self.kernel.pdf(pix_lon,pix_lat)

        # On the object-by-object level
        #self.angsep_object = angsep(self.lon,self.lat,self.catalog.lon,self.catalog.lat)
        #self.surface_intensity_object = self.kernel.surfaceIntensity(self.angsep_object)
        self.surface_intensity_object = self.kernel.pdf(self.catalog.lon,self.catalog.lat)
        
        # Spatial component of signal probability
        #u_spatial = self.roi.area_pixel * self.surface_intensity_object
        u_spatial = self.surface_intensity_object
        return u_spatial
Exemple #9
0
def draw_pixel(hpxmap, **kwargs):
    if isinstance(hpxmap, np.ma.MaskedArray):
        pix = np.where(~hpxmap.mask)
    else:
        pix = np.where((np.isfinite(hpxmap)) & (hpxmap != hp.UNSEEN))

    vmin, vmax = np.percentile(hpxmap[pix], [0.5, 99.5])
    kwargs.setdefault('vmin', vmin)
    kwargs.setdefault('vmax', vmax)
    kwargs.setdefault('rasterized', True)
    kwargs.setdefault('cmap', 'jet')

    nside = hp.npix2nside(len(hpxmap))
    pixrad = np.degrees(hp.max_pixrad(nside))
    ra, dec = hp.pix2ang(nside, pix, lonlat=True)

    xmin, xmax = ra.min() - pixrad, ra.max() + pixrad
    ymin, ymax = dec.min() - pixrad, dec.max() + pixrad

    delta = 0.01
    xx, yy = np.meshgrid(np.arange(xmin, xmax, delta),
                         np.arange(ymin, ymax, delta))
    pp = hp.ang2pix(nside, xx, yy, lonlat=True)

    ax = plt.gca()
    im = ax.pcolormesh(xx[::-1], yy, hpxmap[pp], **kwargs)
    ax.set_xlabel('RA (deg)')
    ax.set_ylabel('DEC (deg)')
    ax.set_xlim(xmax, xmin)
    ax.set_ylim(ymin, ymax)
    ax.grid(ls=':', color='black', lw=0.5)
    return im
    def find_cone_coords(self):
        """ """
        cone_coords = []
        cone_ids = []

        scan_radius = np.degrees(hp.max_pixrad(self.cone_nside))

        self.logger.info("Finding search pixels:")

        for i in tqdm(range(hp.nside2npix(self.cone_nside))):
            ra, dec = self.extract_ra_dec(self.cone_nside, i)
            ra_rad = np.radians(ra)
            dec_rad = np.radians(dec)
            if np.logical_and(ra > self.ra_min - scan_radius,
                              ra < self.ra_max + scan_radius):
                if np.logical_and(
                        dec > self.dec_min - scan_radius,
                        dec < self.dec_max + scan_radius,
                ):
                    cone_coords.append((ra_rad, dec_rad))
                    cone_ids.append(i)

        cone_coords = np.array(cone_coords,
                               dtype=np.dtype([("ra", float), ("dec", float)]))

        return cone_ids, cone_coords
Exemple #11
0
def max_pixrad(testcase):
    cs = []
    for norder in range(16):
        nside = 1 << norder
        args = (nside, )
        cs.append(dict(args=args, expected=healpy.max_pixrad(*args)))
    testcase['max_pixrad'] = cs
def calculate_nside_resolution():
    NSIDE = [2**i for i in range(11)]
    print('given nside | number of pixels | resolution (pixel size in degree) | Maximum angular distance (degree) | pixel area (in square degrees)')
    for nside in NSIDE:
        npix = hp.nside2npix(nside)
        resol = np.rad2deg(hp.nside2resol(nside))
        maxrad = np.rad2deg(hp.max_pixrad(nside))
        pixarea = hp.nside2pixarea(nside, degrees=True)
        print('{0:^11} | {1:^16} | {2:^33.4f} | {3:^33.4f} | {4:^30.6f}'.format(nside, npix, resol, maxrad, pixarea))
Exemple #13
0
def subpixel(superpix, nside_superpix, nside_subpix):
    """
    Return the indices of sub-pixels (resolution nside_subpix) within the super-pixel with (resolution nside_superpix).
    """
    if nside_superpix==nside_subpix: return superpix
    vec = hp.pix2vec(nside_superpix, superpix)
    radius = np.degrees(2. * hp.max_pixrad(nside_superpix))
    subpix = query_disc(nside_subpix, vec, radius)
    pix_for_subpix = superpixel(subpix,nside_subpix,nside_superpix)
    # Might be able to speed up array indexing...
    return subpix[pix_for_subpix == superpix]
Exemple #14
0
    def pixrad(self):
        """Angular size of a pixel in this `SkyMap`

        Returns the maximum angular distance (arcminutes) between any pixel
        center and its corners

        This returns an instance of `~astropy.units.Quantity` with explicit
        units, which can then be converted by the user as-needed.
        """
        radius = healpy.max_pixrad(self.nside) * units.Unit("rad")
        return radius.to("arcmin")
    def setUp(self):
        if not healpy:
            self.skipTest("Missing healpy dependency.")

        self.config = HealpixSkyMap.ConfigClass()
        nside = 2**self.config.log2NSide
        self._NumTracts = healpy.nside2npix(nside)  # Number of tracts to expect
        self._NeighborAngularSeparation = healpy.max_pixrad(
            nside) * afwGeom.radians  # Expected tract separation
        self._SkyMapClass = HealpixSkyMap  # Class of SkyMap to test
        self._SkyMapName = "healpix"  # Name of SkyMap class to test
        self._numNeighbors = 1  # Number of neighbours
Exemple #16
0
def subpixel(superpix, nside_superpix, nside_subpix):
    """
    Return the indices of sub-pixels (resolution nside_subpix) within the super-pixel with (resolution nside_superpix).
    """
    if nside_superpix == nside_subpix:
        return superpix
    vec = healpy.pix2vec(nside_superpix, superpix)
    radius = np.degrees(2.0 * healpy.max_pixrad(nside_superpix))
    subpix = query_disc(nside_subpix, vec, radius)
    pix_for_subpix = superpixel(subpix, nside_subpix, nside_superpix)
    # Might be able to speed up array indexing...
    return subpix[pix_for_subpix == superpix]
Exemple #17
0
def calculate_nside_resolution():
    NSIDE = [2**i for i in range(11)]
    print(
        'given nside | number of pixels | resolution (pixel size in degree) | Maximum angular distance (degree) | pixel area (in square degrees)'
    )
    for nside in NSIDE:
        npix = hp.nside2npix(nside)
        resol = np.rad2deg(hp.nside2resol(nside))
        maxrad = np.rad2deg(hp.max_pixrad(nside))
        pixarea = hp.nside2pixarea(nside, degrees=True)
        print(
            '{0:^11} | {1:^16} | {2:^33.4f} | {3:^33.4f} | {4:^30.6f}'.format(
                nside, npix, resol, maxrad, pixarea))
Exemple #18
0
def subpixel(superpix, nside_superpix, nside_subpix):
    """
    Return the indices of sub-pixels (resolution nside_subpix) within
    the super-pixel with (resolution nside_superpix).
    
    ADW: It would be better to convert to next and do this explicitly
    """
    if nside_superpix == nside_subpix: return superpix
    vec = hp.pix2vec(nside_superpix, superpix)
    radius = np.degrees(2. * hp.max_pixrad(nside_superpix))
    subpix = query_disc(nside_subpix, vec, radius)
    pix_for_subpix = superpixel(subpix, nside_subpix, nside_superpix)
    # Might be able to speed up array indexing...
    return subpix[pix_for_subpix == superpix]
Exemple #19
0
    def setUp(self):
        if not healpy:
            self.skipTest("Missing healpy dependency.")

        config = HealpixSkyMap.ConfigClass()
        nside = 2**config.log2NSide
        self.setAttributes(
            SkyMapClass=HealpixSkyMap,
            name="healpix",
            config=config,
            numTracts=healpy.nside2npix(nside),
            numNeighbors=1,
            neighborAngularSeparation=healpy.max_pixrad(nside) * geom.radians,
        )
Exemple #20
0
    def setUp(self):
        if not healpy:
            self.skipTest("Missing healpy dependency.")

        config = HealpixSkyMap.ConfigClass()
        nside = 2**config.log2NSide
        self.setAttributes(
            SkyMapClass=HealpixSkyMap,
            name="healpix",
            config=config,
            numTracts=healpy.nside2npix(nside),
            numNeighbors=1,
            neighborAngularSeparation=healpy.max_pixrad(nside) * geom.radians,
        )
Exemple #21
0
    def _setup_subpix(self,nside=2**16):
        """
        Subpixels for random position generation.
        """
        # Only setup once...
        if hasattr(self,'subpix'): return

        # Simulate over full ROI
        self.roi_radius  = self.config['coords']['roi_radius']

        # Setup background spatial stuff
        logger.info("Setup subpixels...")
        self.nside_pixel = self.config['coords']['nside_pixel']
        self.nside_subpixel = self.nside_pixel * 2**4 # Could be config parameter
        epsilon = np.degrees(healpy.max_pixrad(self.nside_pixel)) # Pad roi radius to cover edge healpix
        subpix = ugali.utils.healpix.query_disc(self.nside_subpixel,self.roi.vec,self.roi_radius+epsilon)
        superpix = ugali.utils.healpix.superpixel(subpix,self.nside_subpixel,self.nside_pixel)
        self.subpix = subpix[np.in1d(superpix,self.roi.pixels)]
Exemple #22
0
    def _setup_subpix(self,nside=2**16):
        """
        Subpixels for random position generation.
        """
        # Only setup once...
        if hasattr(self,'subpix'): return

        # Simulate over full ROI
        self.roi_radius  = self.config['coords']['roi_radius']

        # Setup background spatial stuff
        logger.info("Setup subpixels...")
        self.nside_pixel = self.config['coords']['nside_pixel']
        self.nside_subpixel = self.nside_pixel * 2**4 # Could be config parameter
        epsilon = np.degrees(healpy.max_pixrad(self.nside_pixel)) # Pad roi radius to cover edge healpix
        subpix = ugali.utils.healpix.query_disc(self.nside_subpixel,self.roi.vec,self.roi_radius+epsilon)
        superpix = ugali.utils.healpix.superpixel(subpix,self.nside_subpixel,self.nside_pixel)
        self.subpix = subpix[np.in1d(superpix,self.roi.pixels)]
Exemple #23
0
    def calc_surface_intensity(self, factor=10):
        """Calculate the surface intensity for each pixel in the interior
        region of the ROI. Pixels are adaptively subsampled around the
        kernel centroid out to a radius of 'factor * max_pixrad'.

        Parameters:
        -----------
        factor : the radius of the oversample region in units of max_pixrad

        Returns:
        --------
        surface_intensity : the surface intensity at each pixel
        """
        # First we calculate the surface intensity at native resolution
        pixels = self.roi.pixels_interior
        nside_in = self.config['coords']['nside_pixel']
        surface_intensity = self.kernel.pdf(pixels.lon, pixels.lat)

        # Then we recalculate the surface intensity around the kernel
        # centroid at higher resolution
        for i in np.arange(1, 5):
            # Select pixels within the region of interest
            nside_out = 2**i * nside_in
            radius = factor * np.degrees(hp.max_pixrad(nside_out))
            pix = ang2disc(nside_in,
                           self.kernel.lon,
                           self.kernel.lat,
                           radius,
                           inclusive=True)

            # Select pix within the interior region of the ROI
            idx = ugali.utils.healpix.index_pix_in_pixels(pix, pixels)
            pix = pix[(idx >= 0)]
            idx = idx[(idx >= 0)]

            # Reset the surface intensity for the subsampled pixels
            subpix = ugali.utils.healpix.ud_grade_ipix(pix, nside_in,
                                                       nside_out)
            pix_lon, pix_lat = pix2ang(nside_out, subpix)
            surface_intensity[idx] = np.mean(self.kernel.pdf(pix_lon, pix_lat),
                                             axis=1)

        return surface_intensity
Exemple #24
0
    def plot(self, observatory=None):
        """Plots the observed pointings.

        Parameters
        ----------
        observatory : str
            Plot only the points for that observatory. Otherwise, plots all
            the pointings.

        """

        color_cycler = cycler.cycler(
            bgcolor=['b', 'r', 'g', 'y', 'm', 'c', 'k'])

        fig, ax = get_axes(projection='mollweide')

        data = self.schedule[self.schedule['ra'] > 0.]

        if observatory:
            data = data[data['observatory'] == observatory]

        for ii, sty in zip(range(len(self.targets)),
                           itertools.cycle(color_cycler)):

            target = self.targets[ii]
            name = target.name
            nside = target._get_nside(ifu=self.ifu)

            radius = healpy.max_pixrad(nside, degrees=True)

            target_data = data[data['target'] == name]

            plot_ellipse(ax,
                         target_data['ra'],
                         target_data['dec'],
                         width=radius,
                         origin=__MOLLWEIDE_ORIGIN__,
                         **sty)

            if observatory is not None:
                ax.set_title(f'Observatory: {observatory}')

        return fig
Exemple #25
0
    def calc_signal_spatial(self):
        """
        Calculate the spatial signal probability for each catalog object.

        Parameters:
        -----------
        None

        Returns:
        --------
        u_spatial : array of spatial probabilities per object
        """
        # At the pixel level over the ROI
        pix_lon, pix_lat = self.roi.pixels_interior.lon, self.roi.pixels_interior.lat
        nside = self.config['coords']['nside_pixel']
        #self.angsep_sparse = angsep(self.lon,self.lat,pix_lon,pix_lat)
        #self.surface_intensity_sparse = self.kernel.surfaceIntensity(self.angsep_sparse)

        # For small kernels, use the single pixel where they reside; otherwise, calculate over roi
        if self.kernel.extension < 2 * np.degrees(healpy.max_pixrad(nside)):
            #msg =  "small kernel"
            #msg += ("\n"+str(self.params))
            #logger.warning(msg)
            idx = self.roi.indexInterior(self.kernel.lon, self.kernel.lat)
            self.surface_intensity_sparse = np.zeros(len(pix_lon))
            self.surface_intensity_sparse[idx] = 1.0 / self.roi.area_pixel
        else:
            self.surface_intensity_sparse = self.kernel.pdf(pix_lon, pix_lat)

        # On the object-by-object level
        #self.angsep_object = angsep(self.lon,self.lat,self.catalog.lon,self.catalog.lat)
        #self.surface_intensity_object = self.kernel.surfaceIntensity(self.angsep_object)
        self.surface_intensity_object = self.kernel.pdf(
            self.catalog.lon, self.catalog.lat)

        # Spatial component of signal probability
        #u_spatial = self.roi.area_pixel * self.surface_intensity_object
        u_spatial = self.surface_intensity_object
        return u_spatial
Exemple #26
0
    def calc_surface_intensity(self, factor=10):
        """Calculate the surface intensity for each pixel in the interior
        region of the ROI. Pixels are adaptively subsampled around the
        kernel centroid out to a radius of 'factor * max_pixrad'.

        Parameters:
        -----------
        factor : the radius of the oversample region in units of max_pixrad

        Returns:
        --------
        surface_intensity : the surface intensity at each pixel
        """
        # First we calculate the surface intensity at native resolution
        pixels = self.roi.pixels_interior
        nside_in = self.config['coords']['nside_pixel']
        surface_intensity = self.kernel.pdf(pixels.lon,pixels.lat)

        # Then we recalculate the surface intensity around the kernel
        # centroid at higher resolution
        for i in np.arange(1,5):
            # Select pixels within the region of interest
            nside_out = 2**i * nside_in
            radius = factor*np.degrees(hp.max_pixrad(nside_out))
            pix = ang2disc(nside_in,self.kernel.lon,self.kernel.lat,
                           radius,inclusive=True)

            # Select pix within the interior region of the ROI
            idx = ugali.utils.healpix.index_pix_in_pixels(pix,pixels)
            pix = pix[(idx >= 0)]; idx = idx[(idx >= 0)]

            # Reset the surface intensity for the subsampled pixels
            subpix = ugali.utils.healpix.ud_grade_ipix(pix,nside_in,nside_out)
            pix_lon,pix_lat = pix2ang(nside_out,subpix)
            surface_intensity[idx]=np.mean(self.kernel.pdf(pix_lon,pix_lat),axis=1)

        return surface_intensity
Exemple #27
0
def main(argv=None):
    """ Main Function """
    # Call initial parser from init_utils
    parser = ap.ArgumentParser(
        description="""Create HDF5 Astrometry file.""", add_help=True
    )

    parser.add_argument(
        "-sdir",
        "--shotdir",
        help="""Directory for shot H5 files to ingest""",
        type=str,
        default="/data/05350/ecooper/hdr2.1/reduction/data",
    )

    parser.add_argument(
        "-sl",
        "--shotlist",
        help="""Text file of DATE OBS list""",
        type=str,
        default="hdr2.1.shotlist",
    )

    parser.add_argument(
        "-of",
        "--outfilename",
        type=str,
        help="""Relative or absolute path for output HDF5
                        file.""",
        default=None,
    )

    parser.add_argument("-survey", "--survey", type=str, default="hdr2.1")

    args = parser.parse_args(argv)
    args.log = setup_logging()

    fileh = tb.open_file(
        args.outfilename, mode="w", title=args.survey.upper() + " Fiber Index file "
    )

    shotlist = Table.read(
        args.shotlist, format="ascii.no_header", names=["date", "obs"]
    )

    tableFibers = fileh.create_table(
        fileh.root,
        "FiberIndex",
        VIRUSFiberIndex,
        "Survey Fiber Coord Info",
        expectedrows=140369264,
    )

    # set up HEALPIX options
    Nside = 2 ** 15
    hp.max_pixrad(Nside, degrees=True) * 3600  # in unit of arcsec

    config = HDRconfig(survey=args.survey)

    badshot = np.loadtxt(config.badshot, dtype=int)

    for shotrow in shotlist:
        datevshot = str(shotrow["date"]) + "v" + str(shotrow["obs"]).zfill(3)
        shotid = int(str(shotrow["date"]) + str(shotrow["obs"]).zfill(3))
        
        date = shotrow["date"]
        
        try:
            args.log.info("Ingesting %s" % datevshot)
            file_obs = tb.open_file(op.join(args.shotdir, datevshot + ".h5"), "r")
            tableFibers_i = file_obs.root.Data.FiberIndex

            for row_i in tableFibers_i:

                row_main = tableFibers.row

                for col in tableFibers_i.colnames:
                    row_main[col] = row_i[col]

                fiberid = row_i["fiber_id"]

                try:
                    row_main["healpix"] = hp.ang2pix(
                        Nside, row_i["ra"], row_i["dec"], lonlat=True)
                except:
                    row_main["healpix"] = 0

                row_main["shotid"] = shotid
                row_main["date"] = date
                row_main["datevobs"] = datevshot
                
                row_main["specid"] = fiberid[20:23]
                row_main["ifuslot"] = fiberid[24:27]
                row_main["ifuid"] = fiberid[28:31]
                row_main["amp"] = fiberid[32:34]
                row_main.append()

            file_obs.close()

        except:            
            if shotid in badshot:
                pass
            else:
                args.log.error("could not ingest %s" % datevshot)

    tableFibers.cols.healpix.create_csindex()
    tableFibers.cols.ra.create_csindex()
    tableFibers.flush()
    fileh.close()
    def plotTilePointings(self,
                          tileID,
                          raCol='ditheredRA',
                          decCol='ditheredDec',
                          radius=1.75,
                          paddingFactors=1,
                          query=None,
                          ax=None,
                          projection='cyl',
                          tile_centers=None,
                          corners=None,
                          drawPointings=True,
                          **kwargs):
        """
        Plot the Healpix Tile and the maximal set of pointings overlapping with
        it.
        Parameters
        ----------
        tileID : int, mandatory
            Healpix tileID in the nested scheme
        raCol : string, defaults to 'ditheredRA'
            column name with ra that should be used 
        decCol : string, defaults to 'ditheredDec'
            column name with dec that should be used 
        radius : float, defaults to 1.75, degrees
            radius of the field of view
        paddingFactors: float, defaults to 1.0
            controls the size of the figure wrt angular dimensions of the tile.
            paddingFactors=1 is designed to get all the centers of pointings
            overlapping the tile inside the figure
        query : string
            query for the pandas dataframe to select only some of the observations
        ax : instance of matplotlib.figure.axes 
            axes for figure. New figure created insitue if not provided
        projections: string, defaults to `cyl`
            string to specify the `Basemap.projection` 
        tile_centers : tuples of 2 floats, degrees, defaults to None
            (ra of the center of the figure, dec of the center of the figure)
            in degrees
            if None, the center is set by using the center of the Healpixel of
            tileID and `self.nside`
        corners: tuple of floats, defaults to None
        kwargs: passed to the plotting of the the field of veiw
        drawPointings : Bool, defaults to True
            if False, draws only Tiles

        Returns
        -------
        fig, tile_centers, corners

        ..notes: Have not thought about wrapover
        """
        # if axes object not provided setup figure
        if ax is None:
            fig, ax = plt.subplots()

        # size of box
        padding = np.degrees(hp.max_pixrad(self.nside)) + radius

        # center of the figure
        if tile_centers is None:
            ra_tile = self.tileCenter(tileID)[0][0]
            dec_tile = self.tileCenter(tileID)[1][0]
        else:
            ra_tile, dec_tile = tile_centers

        # corner of the figure
        if corners is not None:
            llcrnrlat, llcrnrlon, urcrnrlat, urcrnrlon = corners
        else:
            llcrnrlat = dec_tile - padding * paddingFactors
            urcrnrlat = dec_tile + padding * paddingFactors
            llcrnrlon = ra_tile - padding * paddingFactors
            urcrnrlon = ra_tile + padding * paddingFactors

        # Instantiate basemap
        m = Basemap(llcrnrlat=llcrnrlat,
                    llcrnrlon=llcrnrlon,
                    urcrnrlat=urcrnrlat,
                    urcrnrlon=urcrnrlon,
                    projection=projection,
                    lon_0=ra_tile,
                    lat_0=dec_tile,
                    ax=ax)

        # Draw some parallels and meridians to get a spatial sense
        parallels = np.linspace(llcrnrlat, urcrnrlat, 3)
        meridians = np.linspace(llcrnrlon, urcrnrlon, 3)
        m.drawparallels(parallels, labels=(1, 0, 0, 0))
        m.drawmeridians(meridians, labels=(0, 1, 1, 1))

        # obtain the boundaries of the Healpixels and create the polygon patch
        lon, lat = healpix_boundaries(tileID,
                                      nside=self.nside,
                                      units='degrees',
                                      convention='celestial',
                                      step=10,
                                      nest=True)
        x, y = m(lon, lat)
        xy = zip(x, y)
        healpixels = Polygon(xy,
                             facecolor='w',
                             fill=False,
                             alpha=1.,
                             edgecolor='k',
                             lw=2)

        if drawPointings:
            # Obtain the centers of pointings
            ra, dec = self.pointingCenters(tileID,
                                           raCol=raCol,
                                           decCol=decCol,
                                           query=query)
            for ra, dec in zip(ra, dec):
                m.tissot(ra, dec, radius, 100, **kwargs)

        # Draw patch
        ax.add_patch(healpixels)

        # really important for the case where ax is None
        if ax is not None:
            fig = ax.figure

        return fig, (ra_tile, dec_tile), (llcrnrlat, llcrnrlon, urcrnrlat,
                                          urcrnrlon)
Exemple #29
0
def gaia_photometry(catfile,nside=64,band=None,plot=False,version='edr3'):
    if not os.path.exists(catfile): 
        msg = "Couldn't find %s"%catfile
        raise Exception(msg)

    #columns = [OBJECT_ID,'RA','DEC']
    columns = ['RA','DEC']
    spread,nepochs = ['WAVG_SPREAD_MODEL_R','NEPOCHS_R']
    mag_g,mag_r,mag_i,mag_z = bfields(['MAG_PSF'],['g','r','i','z'])
    #mag_g,mag_r,mag_i,mag_z = bfields(['WAVG_MAG_PSF'],['g','r','i','z'])
    columns += [spread, nepochs, mag_g, mag_r, mag_i, mag_z]

    # Hack to get pixel location
    hpx = int(catfile.split('_')[-1].split('.')[0])
    #hpx = ang2pix(NSIDE, cat['RA'], cat['DEC'])
    ra,dec = pix2ang(NSIDE, hpx)
    radius = np.degrees(hp.max_pixrad(NSIDE))

    msg = '%s (RA,DEC,RAD) = %.2f,%.2f,%.2f'%(os.path.basename(catfile),ra,dec,radius)
    print(msg)

    #print "Getting coadd catalog: DES"
    cat = load_infiles([catfile],columns)
    # Select stars with 16 < r < 20 and 0.0 < (g-i) < 1.5
    sel = (np.fabs(cat[spread])<0.002) & \
        (cat[mag_g]<90) & (cat[mag_r]<90) & (cat[mag_i]<90) & (cat[mag_z]<90) & \
        (cat[mag_r]>16) & (cat[mag_r]<20) & \
        ((cat[mag_g] - cat[mag_i]) > 0.0) & \
        ((cat[mag_g] - cat[mag_i]) < 1.5) & \
        (cat[nepochs] > 1)
    cat = cat[sel]

    if len(cat) == 0:
        msg = "WARNING: No objects passing selection in: %s"%catfile
        print(msg)
        return np.array([],dtype=int), np.array([])

    #msg = "Getting external catalog: %s"%catalog
    ext = get_gaia_catalog(hpx,version=version)

    m = match_query(cat['RA'],cat['DEC'],ext['RA'],ext['DEC'])

    # Use a fairly wide matching radius (2 arcsec)
    cut = 1.0
    sel = m[-1]*3600. < cut

    cat_match = cat[m[0][sel]]
    ext_match = ext[m[1][sel]]

    cat_G = gaia_transform(cat_match[mag_g],cat_match[mag_r],cat_match[mag_i],cat_match[mag_z],
                           version=version)

    # Need to put Gaia flux onto the AB system
    ext_G = -2.5 * np.log10(ext_match['PHOT_G_MEAN_FLUX']) + 25.7934
    diff  = cat_G - ext_G

    pix = ang2pix(nside,cat_match['RA'],cat_match['DEC'])
    upix = np.unique(pix)
    stat = nd.median(diff,labels=pix,index=upix)

    if False:
        plt.figure()
        plt.hist(cat_G - ext_G)
        import pdb; pdb.set_trace()
        
    return upix,stat
Exemple #30
0
def external_astrometry(catfile,catalog='GAIA',nside=64,band='r',plot=False):
    #nice = os.nice(0)
    #os.nice(10-nice)

    if band=='all': band = 'r'

    if not os.path.exists(catfile): 
        msg = "Couldn't find %s"%catfile
        raise Exception(msg)

    columns = [OBJECT_ID,'RA','DEC']
    spread,mag,nepochs = bfields(['WAVG_SPREAD_MODEL','MAG_PSF','NEPOCHS'],band)
    columns += [spread,mag,nepochs]

    # Hack to get pixel location
    hpx = int(catfile.split('_')[-1].split('.')[0])
    #hpx = ang2pix(NSIDE, cat['RA'], cat['DEC'])
    ra,dec = pix2ang(NSIDE, hpx)
    radius = np.degrees(healpy.max_pixrad(NSIDE))

    msg = '%s (RA,DEC,RAD) = %.2f,%.2f,%.2f'%(os.path.basename(catfile),ra,dec,radius)
    print(msg)

    #print "Getting coadd catalog: DES"
    cat = load_infiles([catfile],columns)
    # Select stars with 16 < mag < 21
    sel = (np.fabs(cat[spread])<0.002) & \
        (cat[mag]>16) & \
        (cat[mag]<21) & \
        (cat[nepochs] > 1)
    cat = cat[sel]

    if len(cat) == 0:
        msg = "WARNING: No objects passing selection in: %s"%catfile
        print(msg)
        return np.array([],dtype=int), np.array([])

    #print "Getting external catalog: %s"%catalog
    if catalog in list(CATALOGS.keys()):
        ext = get_vizier_catalog(ra,dec,radius,**CATALOGS[catalog])
    else:
        ext = get_local_catalog(ra,dec,radius,catalog)

    m = match_query(cat['RA'],cat['DEC'],ext['_RAJ2000'],ext['_DEJ2000'])

    # Use a fairly wide matching radius (2 arcsec)
    cut = 2.0
    sel = m[-1]*3600. < cut
    sepdeg = m[-1][sel]
    sepsec = m[-1][sel] * 3600.
    sepmas = sepsec * 1000.

    sep = sepmas

    pix = ang2pix(nside,cat['RA'][sel],cat['DEC'][sel])
    upix = np.unique(pix)
    try:
        peak = nd.median(sep,labels=pix,index=upix)
    except ValueError:
        import pdb; pdb.set_trace()
        
    if plot:
        plt.figure()
        draw_angsep(sep,bins=np.linspace(0,cut*1000.,101))
        if isinstance(plot,basestring):
            outfile = plot
            plt.savefig(outfile,bbox_inches='tight')

    #return cat,ext,m
    return upix,peak
Exemple #31
0
def main():
    # parse command-line arguments
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--verbose', action='store_true',
        help='more verbose output')
    parser.add_argument('--order', type=int, default=5,
        help='order of healpix hierachical tesselation')
    parser.add_argument('--input', type=str, default=None,
        help='input file name')
    args = parser.parse_args()

    nside = 2**args.order
    npix = hp.nside2npix(nside)
    m = np.arange(npix)

    print 'HEALPix map info:'
    print '\tn pixels:', npix
    print '\tresolution:', hp.nside2resol(nside)
    print '\tpixel area:', hp.nside2pixarea(nside)
    print '\tmax distance from center:', hp.max_pixrad(nside)

    print 'xi binning info:'
    transerve_comoving_scale = 3820.43 # Mpc, at z = 2.1
    maxang = 200/transerve_comoving_scale
    print '\tmax ang scale:', maxang

    print 'test stuff:'

    test_theta = 1.58853
    test_phi = 0.154
    testvec = hp.ang2vec(test_theta, test_phi)
    testmax = 0.03

    print '\ttheta, phi:', test_theta, test_phi
    print '\t3d vec:', testvec
    print '\tpixel index:', hp.ang2pix(nside, test_theta, test_phi)
    print '\tclosest neighbors:', hp.pixelfunc.get_all_neighbours(nside, test_theta, test_phi)
    print '\tneighbors within max ang scale:', hp.query_disc(nside, testvec, testmax, inclusive=True)

    # include_pix = hp.query_disc(nside, hp.ang2vec(5./6.*np.pi/2, 7./6.*np.pi), 2*maxang, inclusive=True)

    # print '\tn pixels for test:', len(include_pix)

    qso_pos = np.loadtxt(args.input)
    print 'Shape of input data: ', qso_pos.shape

    m = {}

    qso_pos[:,0] = np.deg2rad(qso_pos[:,0])
    qso_pos[:,1] = np.deg2rad(90-qso_pos[:,1])

    class Quasar():
        def __init__(self, theta, phi, z):
            self.theta = theta
            self.phi = phi
            self.z = z
            # fake this for now
            self.wave = np.log10((1+z)*(1100+np.arange(10)))

    quasars = []

    for i, qso in enumerate(qso_pos):
        ra, dec, z = qso
        phi = ra
        theta = dec
        q = Quasar(theta, phi, z)
        map_index = hp.ang2pix(nside, theta, phi)

        # if map_index not in include_pix:
        #     continue
        #q.map_index = map_index
        if map_index in m:
            m[map_index].append(i)
        else:
            m[map_index] = [i]
        q.i = i
        quasars.append(q)

    print 'Number of pixels with at least one quasar: %d' % (len(m.keys()))
    print 'Average number of quasars per pixel: %f' % (float(len(quasars))/len(m.keys()))

    nearby_pixels_sum = 0
    nearby_quasars_sum = 0
    for q_index, q in enumerate(quasars):
        nearby_pixels = hp.query_disc(nside, hp.ang2vec(q.theta, q.phi), maxang, inclusive=True)
        nearby_pixels_sum += len(nearby_pixels)
        for nearby_pixel in nearby_pixels:
            if nearby_pixel in m:
                nearby_quasars = m[nearby_pixel]
                nearby_quasars_sum += len(nearby_quasars)

                p_phi = qso_pos[nearby_quasars,0]
                p_theta = qso_pos[nearby_quasars,1]

                angdist = np.sin(q.theta)*np.sin(p_theta) + np.cos(q.theta)*np.cos(p_theta)*(np.cos(q.phi)*np.cos(p_phi) + np.sin(q.phi)*np.sin(p_phi))

                for p_i, p_index in enumerate(nearby_quasars):
                    theta, phi, z = qso_pos[p_index]
                    p = Quasar(theta, phi, z)
                    loglamdist = abs(np.subtract.outer(q.wave, p.wave))
                    p_angdist = angdist[p_i]
                #losdist = abs(np.log(q_wave/p.wave))
    print 'Average number of pixels within 200 Mpc of a quasar: %f' % (float(nearby_pixels_sum)/len(quasars))
    print 'Average number of quasars within nearby pixels of a quasar: %f' % (float(nearby_quasars_sum)/len(quasars))

    mask = np.ones(npix).astype(np.bool)
    mask[m.keys()] = False

    mm = hp.ma(np.zeros(npix))
    mm.mask = mask
    mm[~mm.mask] = [len(m[i]) for i in m.keys()]

    hp.mollview(mm.filled(), rot=(270,0,0), flip='geo')

    plt.show()
Exemple #32
0
    def __init__(self, config, lon, lat):

        self.config = Config(config)
        self.lon = lon
        self.lat = lat

        self.projector = ugali.utils.projector.Projector(self.lon, self.lat)

        self.vec = vec = ang2vec(self.lon, self.lat)
        self.pix = ang2pix(self.config['coords']['nside_likelihood'], self.lon,
                           self.lat)

        # Pixels from the entire ROI disk
        pix = query_disc(self.config['coords']['nside_pixel'], vec,
                         self.config['coords']['roi_radius'])
        self.pixels = PixelRegion(self.config['coords']['nside_pixel'], pix)

        # Pixels in the interior region
        pix = query_disc(self.config['coords']['nside_pixel'], vec,
                         self.config['coords']['roi_radius_interior'])
        self.pixels_interior = PixelRegion(
            self.config['coords']['nside_pixel'], pix)

        # Pixels in the outer annulus
        pix = query_disc(self.config['coords']['nside_pixel'], vec,
                         self.config['coords']['roi_radius_annulus'])
        pix = np.setdiff1d(self.pixels, pix)
        self.pixels_annulus = PixelRegion(self.config['coords']['nside_pixel'],
                                          pix)

        # Pixels within target healpix region
        pix = ugali.utils.skymap.subpixel(
            self.pix, self.config['coords']['nside_likelihood'],
            self.config['coords']['nside_pixel'])
        self.pixels_target = PixelRegion(self.config['coords']['nside_pixel'],
                                         pix)

        # Boolean arrays for selecting given pixels
        # (Careful, this works because pixels are pre-sorted by query_disc before in1d)
        self.pixel_interior_cut = np.in1d(self.pixels, self.pixels_interior)
        self.pixel_annulus_cut = np.in1d(self.pixels, self.pixels_annulus)

        # Some pixel properties
        self.area_pixel = hp.nside2pixarea(
            self.config.params['coords']['nside_pixel'], degrees=True)  # deg^2
        self.max_pixrad = np.degrees(
            hp.max_pixrad(self.config['coords']['nside_pixel']))  # deg

        # ADW: These are really bin edges, should be careful and consistent
        # It would be cleaner to separate the CMD from ROI
        self.bins_mag = np.linspace(self.config.params['mag']['min'],
                                    self.config.params['mag']['max'],
                                    self.config.params['mag']['n_bins'] + 1)

        self.bins_color = np.linspace(
            self.config.params['color']['min'],
            self.config.params['color']['max'],
            self.config.params['color']['n_bins'] + 1)

        self.centers_mag = ugali.utils.binning.centers(self.bins_mag)
        self.centers_color = ugali.utils.binning.centers(self.bins_color)

        self.delta_mag = self.bins_mag[1] - self.bins_mag[0]
        self.delta_color = self.bins_color[1] - self.bins_color[0]
def Hopkins_method(ts_map_array, ra, dec, fdr, psf_size, N, nside, hopkins_correction=True):

    # Compute the number of correlated pixels, i.e., the number of pixels within a PSF distance
    # NOTE: query_disc gets the *radius* of the disc as input, not the diameter, so we can directly use psf_size
    n_correlated_pixels = len(hp.query_disc(nside, (0, 0, 1), np.deg2rad(psf_size), inclusive=True, fact=4))

    print("Got a map with nside %i. Using a number of correlated pixels of %i" % (nside, n_correlated_pixels))

    # This number should be close to psf_size (within ~1 deg)
    approx_corr_rad = np.sqrt(n_correlated_pixels / np.pi) * np.rad2deg(hp.max_pixrad(nside))

    print("Approx. correlation radius (should be within 1 deg of PSF size): %.2f deg" % approx_corr_rad)

    # This notation follows Clements, Sarkar and Guo (2012):
    # https://web.njit.edu/~wguo/Clements_Sarkar_Guo%202012.pdf

    # Get the corresponding p-values
    # We use here the result from Mattox et al. 1996, that TS is distributed as 0.5 chi2(1 dof)
    p_values = 0.5 * scipy.stats.chi2(1).sf(ts_map_array)  # type: np.ndarray

    # Order the p-values from the smallest to the largest. We use argsort to keep track of the reordering
    sort_idx = p_values.argsort()

    # Find
    # kBH = max
    # {j: P(j) <= j alpha / N}

    alpha_over_N = fdr / N

    # Hopkins adjustement
    if hopkins_correction:

        print("Using Hopkins correction")

        C_N = np.sum(1.0 / np.arange(1, n_correlated_pixels+1))

    else:

        C_N = 1

    threshold = np.arange(1, N+1) * alpha_over_N / C_N

    print("Minimum threshold: %.2g, maximum threshold: %.2g" % (threshold.min(), threshold.max()))

    idx = (p_values[sort_idx] <= threshold)

    # Find the largest p-value satisfying the condition
    if np.sum(idx) == 0:

        # No points above the threshold

        return [], [], []

    else:

        # Detections

        # Find out the last point above the threshold, all points before in the sorted p_values array
        # are considered detection

        rightmost_p_value_below_threshold_idx = idx.nonzero()[0].max()

        points_to_keep_idx = sort_idx[:rightmost_p_value_below_threshold_idx+1]

        return ra[points_to_keep_idx], dec[points_to_keep_idx], ts_map_array[points_to_keep_idx]
Exemple #34
0
def match_secondary(primtargs, scxdir, scndout, sep=1., pix=None, nside=None):
    """Match secondary targets to primary targets and update bits.

    Parameters
    ----------
    primtargs : :class:`~numpy.ndarray`
        An array of primary targets.
    scndout : :class`~numpy.ndarray`
        Name of a sub-directory to which to write the information in
        `desitarget.secondary.outdatamodel` with `TARGETID` and (the
        highest) `PRIORITY_INIT` updated with matching primary info.
    scxdir : :class:`str`, optional, defaults to `None`
        Name of the directory that hosts secondary targets.
    sep : :class:`float`, defaults to 1 arcsecond
        The separation at which to match in ARCSECONDS.
    pix : :class:`list`, optional, defaults to `None`
        Limit secondary targets to (NESTED) HEALpixels that touch
        pix at the supplied `nside`, as a speed-up.
    nside : :class:`int`, optional, defaults to `None`
        The (NESTED) HEALPixel nside to be used with `pixlist`.

    Returns
    -------
    :class:`~numpy.ndarray`
        The array of primary targets, with the `SCND_TARGET` bit
        populated for matches to secondary targets
    """
    # ADM add a SCND_TARGET column to the primary targets.
    dt = primtargs.dtype.descr
    dt.append(('SCND_TARGET', '>i8'))
    targs = np.zeros(len(primtargs), dtype=dt)
    for col in primtargs.dtype.names:
        targs[col] = primtargs[col]

    # ADM check if this is an SV or main survey file.
    cols, mx, surv = main_cmx_or_sv(targs, scnd=True)
    log.info('running on the {} survey...'.format(surv))
    if surv != 'main':
        scxdir = os.path.join(scxdir, surv)

    # ADM read in non-OVERRIDE secondary targets.
    scxtargs = read_files(scxdir, mx[3])
    scxtargs = scxtargs[~scxtargs["OVERRIDE"]]

    # ADM match primary targets to non-OVERRIDE secondary targets.
    inhp = np.ones(len(scxtargs), dtype="?")
    # ADM as a speed-up, save memory by limiting the secondary targets
    # ADM to just HEALPixels that could touch the primary targets.
    if nside is not None and pix is not None:
        # ADM remember to grab adjacent pixels in case of edge effects.
        allpix = add_hp_neighbors(nside, pix)
        inhp = is_in_hp(scxtargs, nside, allpix)
        # ADM it's unlikely that the matching separation is comparable
        # ADM to the HEALPixel resolution, but guard against that anyway.
        halfpix = np.degrees(hp.max_pixrad(nside)) * 3600.
        if sep > halfpix:
            msg = 'sep ({}") exceeds (half) HEALPixel size ({}")'.format(
                sep, halfpix)
            log.critical(msg)
            raise ValueError(msg)

    # ADM warn the user if the secondary and primary samples are "large".
    big = 500000
    if np.sum(inhp) > big and len(primtargs) > big:
        log.warning('Large secondary (N={}) and primary (N={}) samples'.format(
            np.sum(inhp), len(primtargs)))
        log.warning('The code may run slowly')

    # ADM for each secondary target, determine if there is a match
    # ADM with a primary target. Note that sense is important, here
    # ADM (the primary targets must be passed first).
    log.info(
        'Matching primary and secondary targets for {} at {}"...t={:.1f}s'.
        format(scndout, sep,
               time() - start))
    mtargs, mscx = radec_match_to(targs, scxtargs[inhp], sep=sep)
    # ADM recast the indices to the full set of secondary targets,
    # ADM instead of just those that were in the relevant HEALPixels.
    mscx = np.where(inhp)[0][mscx]

    # ADM loop through the matches and update the SCND_TARGET
    # ADM column in the primary target list. The np.unique is a
    # ADM speed-up to assign singular matches first.
    umtargs, inv, cnt = np.unique(mtargs,
                                  return_inverse=True,
                                  return_counts=True)
    # ADM number of times each primary target was matched, ordered
    # ADM the same as mtargs, i.e. n(mtargs) for each entry in mtargs.
    nmtargs = cnt[inv]
    # ADM assign anything with nmtargs = 1 directly.
    singular = nmtargs == 1
    targs["SCND_TARGET"][mtargs[singular]] = scxtargs["SCND_TARGET"][
        mscx[singular]]
    # ADM loop through things with nmtargs > 1 and combine the bits.
    for i in range(len((mtargs[~singular]))):
        targs["SCND_TARGET"][mtargs[~singular][i]] |= scxtargs["SCND_TARGET"][
            mscx[~singular][i]]
    # ADM also assign the SCND_ANY bit to the primary targets.
    desicols, desimasks, _ = main_cmx_or_sv(targs, scnd=True)
    desi_mask = desimasks[0]

    targs[desicols[0]][umtargs] |= desi_mask.SCND_ANY

    # ADM rename the SCND_TARGET column, in case this is an SV file.
    targs = rfn.rename_fields(targs, {'SCND_TARGET': desicols[3]})

    # APC Secondary target bits only affect PRIORITY, NUMOBS and
    # APC obsconditions for specific DESI_TARGET bits
    # APC See https://github.com/desihub/desitarget/pull/530

    # APC Only consider primary targets with secondary bits set
    scnd_update = (targs[desicols[0]] & desi_mask['SCND_ANY']) != 0
    if np.any(scnd_update):
        # APC Allow changes to primaries if the DESI_TARGET bitmask has
        # APC only the following bits set, in any combination.
        log.info(
            'Testing if secondary targets can update {} matched primaries'.
            format(scnd_update.sum()))
        update_from_scnd_bits = desi_mask['SCND_ANY'] | desi_mask[
            'MWS_ANY'] | desi_mask['STD_BRIGHT'] | desi_mask[
                'STD_FAINT'] | desi_mask['STD_WD']
        scnd_update &= ((targs[desicols[0]] & ~update_from_scnd_bits) == 0)
        log.info(
            'Setting new priority, numobs and obsconditions from secondary for {} matched primaries'
            .format(scnd_update.sum()))

        # APC Primary and secondary obsconditions are or'd
        scnd_obscon = set_obsconditions(targs[scnd_update], scnd=True)
        targs['OBSCONDITIONS'][scnd_update] &= scnd_obscon

        # APC bit of a hack here
        # APC Check for _BRIGHT, _DARK split in column names
        darkbright = 'NUMOBS_INIT_DARK' in targs.dtype.names
        if darkbright:
            ender, obscon = ["_DARK", "_BRIGHT"], ["DARK|GRAY", "BRIGHT"]
        else:
            ender, obscon = [""], [
                "DARK|GRAY|BRIGHT|POOR|TWILIGHT12|TWILIGHT18"
            ]

        # APC secondaries can increase priority and numobs
        for edr, oc in zip(ender, obscon):
            pc, nc = "PRIORITY_INIT" + edr, "NUMOBS_INIT" + edr
            scnd_priority, scnd_numobs = initial_priority_numobs(
                targs[scnd_update], obscon=oc, scnd=True)
            targs[nc][scnd_update] = np.maximum(targs[nc][scnd_update],
                                                scnd_numobs)
            targs[pc][scnd_update] = np.maximum(targs[pc][scnd_update],
                                                scnd_priority)

    # ADM update the secondary targets with the primary information.
    scxtargs["TARGETID"][mscx] = targs["TARGETID"][mtargs]
    # ADM the maximum priority will be used to break ties in the
    # ADM unlikely event that a secondary matches two primaries.
    hipri = np.maximum(targs["PRIORITY_INIT_DARK"],
                       targs["PRIORITY_INIT_BRIGHT"])
    scxtargs["PRIORITY_INIT"][mscx] = hipri[mtargs]

    # ADM write the secondary targets that have updated TARGETIDs.
    ii = scxtargs["TARGETID"] != -1
    nmatches = np.sum(ii)
    log.info('Writing {} secondary target matches to {}...t={:.1f}s'.format(
        nmatches, scndout,
        time() - start))
    if nmatches > 0:
        hdr = fitsio.FITSHDR()
        hdr["SURVEY"] = surv
        fitsio.write(scndout,
                     scxtargs[ii],
                     extname='SCND_TARG',
                     header=hdr,
                     clobber=True)

    log.info('Done...t={:.1f}s'.format(time() - start))

    return targs
    def plot_overlap_with_observations(self,
                                       fields=None,
                                       pid=None,
                                       first_det_window_days=None,
                                       min_sep=0.01):
        """ """

        (
            double_in_plane_pixels,
            double_in_plane_probs,
            single_in_plane_pixels,
            single_in_plane_prob,
            veto_pixels,
            plane_pixels,
            plane_probs,
            times,
            double_no_plane_prob,
            double_no_plane_pixels,
            single_no_plane_prob,
            single_no_plane_pixels,
            overlapping_fields,
        ) = self.calculate_overlap_with_observations(
            fields=fields,
            pid=pid,
            first_det_window_days=first_det_window_days,
            min_sep=min_sep,
        )

        fig = plt.figure()
        plt.subplot(projection="aitoff")

        self.overlap_fields = list(set(overlapping_fields))

        self.overlap_prob = np.sum(double_in_plane_probs +
                                   double_no_plane_prob) * 100.0

        size = hp.max_pixrad(self.nside)**2 * 50.0

        veto_pos = np.array([
            hp.pixelfunc.pix2ang(self.nside, i, lonlat=True)
            for i in veto_pixels
        ]).T

        if len(veto_pos) > 0:

            plt.scatter(
                np.radians(self.wrap_around_180(veto_pos[0])),
                np.radians(veto_pos[1]),
                color="red",
                s=size,
            )

        plane_pos = np.array([
            hp.pixelfunc.pix2ang(self.nside, i, lonlat=True)
            for i in plane_pixels
        ]).T

        if len(plane_pos) > 0:

            plt.scatter(
                np.radians(self.wrap_around_180(plane_pos[0])),
                np.radians(plane_pos[1]),
                color="green",
                s=size,
            )

        single_pos = np.array([
            hp.pixelfunc.pix2ang(self.nside, i, lonlat=True)
            for i in single_no_plane_pixels
        ]).T

        if len(single_pos) > 0:
            plt.scatter(
                np.radians(self.wrap_around_180(single_pos[0])),
                np.radians(single_pos[1]),
                c=single_no_plane_prob,
                vmin=0.0,
                vmax=max(self.data[self.key]),
                s=size,
                cmap="gray",
            )

        plot_pos = np.array([
            hp.pixelfunc.pix2ang(self.nside, i, lonlat=True)
            for i in double_no_plane_pixels
        ]).T

        if len(plot_pos) > 0:
            plt.scatter(
                np.radians(self.wrap_around_180(plot_pos[0])),
                np.radians(plot_pos[1]),
                c=double_no_plane_prob,
                vmin=0.0,
                vmax=max(self.data[self.key]),
                s=size,
            )

        red_patch = mpatches.Patch(color="red", label="Not observed")
        gray_patch = mpatches.Patch(color="gray", label="Observed once")
        violet_patch = mpatches.Patch(color="green",
                                      label="Observed Galactic Plane (|b|<10)")
        plt.legend(handles=[red_patch, gray_patch, violet_patch])

        message = (
            "In total, {0:.2f} % of the contour was observed at least once.\n"
            "This estimate includes {1:.2f} % of the contour "
            "at a galactic latitude <10 deg.\n"
            "In total, {2:.2f} % of the contour was observed at least twice. \n"
            "In total, {3:.2f} % of the contour was observed at least twice, "
            "and excluding low galactic latitudes.\n"
            "These estimates account for chip gaps.".format(
                100 *
                (np.sum(double_in_plane_probs) + np.sum(single_in_plane_prob) +
                 np.sum(single_no_plane_prob) + np.sum(double_no_plane_prob)),
                100 * np.sum(plane_probs),
                100.0 *
                (np.sum(double_in_plane_probs) + np.sum(double_no_plane_prob)),
                100.0 * np.sum(double_no_plane_prob),
            ))

        all_pix = single_in_plane_pixels + double_in_plane_pixels

        n_pixels = len(single_in_plane_pixels + double_in_plane_pixels +
                       double_no_plane_pixels + single_no_plane_pixels)
        n_double = len(double_no_plane_pixels + double_in_plane_pixels)
        n_plane = len(plane_pixels)

        self.healpix_area = (
            hp.pixelfunc.nside2pixarea(self.nside, degrees=True) * n_pixels)
        self.double_extragalactic_area = (
            hp.pixelfunc.nside2pixarea(self.nside, degrees=True) * n_double)
        plane_area = hp.pixelfunc.nside2pixarea(self.nside,
                                                degrees=True) * n_plane

        self.overlap_fields = overlapping_fields

        #     area = (2. * base_ztf_rad)**2 * float(len(overlapping_fields))
        #     n_fields = len(overlapping_fields)

        self.logger.info(
            f"{n_pixels} pixels were covered, covering approximately {self.healpix_area:.2g} sq deg."
        )
        self.logger.info(
            f"{n_double} pixels were covered at least twice (b>10), covering approximately {self.double_extragalactic_area:.2g} sq deg."
        )
        self.logger.info(
            f"{n_plane} pixels were covered at low galactic latitude, covering approximately {plane_area:.2g} sq deg."
        )
        return fig, message
Exemple #36
0
def match_secondary(infile, scxtargs, sep=1., scxdir=None):
    """Match secondary targets to primary targets and update bits.

    Parameters
    ----------
    infile : :class:`str`
        The full path to a file containing primary targets.
    scxtargs : :class:`~numpy.ndarray`
        An array of secondary targets.
    sep : :class:`float`, defaults to 1 arcsecond
        The separation at which to match in ARCSECONDS.
    scxdir : :class:`str`, optional, defaults to `None`
        Name of the directory that hosts secondary targets. If passed,
        this is written to the output primary file header as `SCNDDIR`.

    Returns
    -------
    :class:`~numpy.ndarray`
        The array of secondary targets, with the `TARGETID` bit
        updated with any matching primary targets from `infile`.

    Notes
    -----
        - The primary target `infiles` are written back to their
          original path with `.fits` changed to `-wscnd.fits` and the
          `SCND_TARGET` bit populated for matching targets.
    """
    # ADM just the file name for logging.
    fn = os.path.basename(infile)
    # ADM read in the primary targets.
    log.info('Reading primary targets file {}...t={:.1f}s'.format(
        infile,
        time() - start))
    intargs, hdr = fitsio.read(infile, extension="TARGETS", header=True)

    # ADM fail if file's already been matched to secondary targets.
    if "SCNDDIR" in hdr:
        msg = "{} already matched to secondary targets".format(fn) \
              + " (did you mean to remove {}?)!!!".format(fn)
        log.critical(msg)
        raise ValueError(msg)
    # ADM add the SCNDDIR to the primary targets file header.
    hdr["SCNDDIR"] = scxdir
    # ADM add a SCND_TARGET column to the primary targets.
    dt = intargs.dtype.descr
    dt.append(('SCND_TARGET', '>i8'))
    targs = np.zeros(len(intargs), dtype=dt)
    for col in intargs.dtype.names:
        targs[col] = intargs[col]

    # ADM match to all secondary targets for non-custom primary files.
    inhp = np.ones(len(scxtargs), dtype="?")
    # ADM as a speed-up, save memory by limiting the secondary targets
    # ADM to just HEALPixels that could touch the primary targets.
    if 'FILEHPX' in hdr:
        nside, pix = hdr['FILENSID'], hdr['FILEHPX']
        # ADM remember to grab adjacent pixels in case of edge effects.
        allpix = add_hp_neighbors(nside, pix)
        inhp = is_in_hp(scxtargs, nside, allpix)
        # ADM it's unlikely that the matching separation is comparable
        # ADM to the HEALPixel resolution, but guard against that anyway.
        halfpix = np.degrees(hp.max_pixrad(nside)) * 3600.
        if sep > halfpix:
            msg = 'sep ({}") exceeds (half) HEALPixel size ({}")'.format(
                sep, halfpix)
            log.critical(msg)
            raise ValueError(msg)
    # ADM warn the user if the secondary and primary samples are "large".
    big = 500000
    if np.sum(inhp) > big and len(intargs) > big:
        log.warning('Large secondary (N={}) and primary (N={}) samples'.format(
            np.sum(inhp), len(intargs)))
        log.warning('The code may run slowly')

    # ADM for each secondary target, determine if there is a match
    # ADM with a primary target. Note that sense is important, here
    # ADM (the primary targets must be passed first).
    log.info(
        'Matching primary and secondary targets for {} at {}"...t={:.1f}s'.
        format(fn, sep,
               time() - start))
    mtargs, mscx = radec_match_to(targs, scxtargs[inhp], sep=sep)
    # ADM recast the indices to the full set of secondary targets,
    # ADM instead of just those that were in the relevant HEALPixels.
    mscx = np.where(inhp)[0][mscx]

    # ADM loop through the matches and update the SCND_TARGET
    # ADM column in the primary target list. The np.unique is a
    # ADM speed-up to assign singular matches first.
    umtargs, inv, cnt = np.unique(mtargs,
                                  return_inverse=True,
                                  return_counts=True)
    # ADM number of times each primary target was matched, ordered
    # ADM the same as mtargs, i.e. n(mtargs) for each entry in mtargs.
    nmtargs = cnt[inv]
    # ADM assign anything with nmtargs = 1 directly.
    singular = nmtargs == 1
    targs["SCND_TARGET"][mtargs[singular]] = scxtargs["SCND_TARGET"][
        mscx[singular]]
    # ADM loop through things with nmtargs > 1 and combine the bits.
    for i in range(len((mtargs[~singular]))):
        targs["SCND_TARGET"][mtargs[~singular][i]] |= scxtargs["SCND_TARGET"][
            mscx[~singular][i]]
    # ADM also assign the SCND_ANY bit to the primary targets.
    desicols, desimasks, _ = main_cmx_or_sv(targs, scnd=True)
    targs[desicols[0]][umtargs] |= desimasks[0].SCND_ANY

    # ADM rename the SCND_TARGET column, in case this is an SV file.
    targs = rfn.rename_fields(targs, {'SCND_TARGET': desicols[3]})

    # ADM update the secondary targets with the primary TARGETID.
    scxtargs["TARGETID"][mscx] = targs["TARGETID"][mtargs]

    # ADM form the output primary file name and write the file.
    base, ext = os.path.splitext(infile)
    outfile = "{}{}{}".format(base, '-wscnd', ext)
    log.info('Writing updated primary targets to {}...t={:.1f}s'.format(
        outfile,
        time() - start))
    fitsio.write(outfile, targs, extname='TARGETS', header=hdr, clobber=True)

    log.info('Done for {}...t={:.1f}s'.format(fn, time() - start))

    return scxtargs
Exemple #37
0
def uniform_in_pixel(nside, ipix, size, nest=False):
    '''Uniform distribution of points over healpix pixel.

    Draws randomly distributed points from the healpix pixel `ipix` for a map
    with a given `nside` parameter.

    Parameters
    ----------
    nside : int
        Healpix map `nside` parameter.
    ipix : int
        Healpix map pixel index.
    size : int
        Number of points to draw.
    nest : bool, optional
        If True assume ``NESTED`` pixel ordering, otherwise ``RING`` pixel
        ordering. Default is ``RING`` pixel ordering.

    Returns
    -------
    coords : `~astropy.coordinates.SkyCoord`
        Randomly distributed points over the healpix pixel.

    Warnings
    --------
    This function requires the ``healpy`` package.

    Examples
    --------
    See :ref:`User Documentation <skypy.position.uniform_in_pixel>`.

    '''

    from healpy import pix2ang, max_pixrad, nside2pixarea, ang2pix

    # get the centre of the healpix pixel as a SkyCoord
    centre_lon, centre_lat = pix2ang(nside, ipix, nest=nest, lonlat=True)
    centre = SkyCoord(centre_lon, centre_lat, unit=units.deg)

    # get the maximum radius of a healpix pixel in radian
    r = max_pixrad(nside)

    # use that radius as the aperture of a spherical area in steradian
    area = TWO_PI * (1 - np.cos(r)) * units.sr

    # oversampling factor = 1/(probability of the draw)
    over = area.value / nside2pixarea(nside)

    # the array of longitudes and latitudes of the sample
    lon, lat = np.empty(0), np.empty(0)

    # rejection sampling over irregularly shaped healpix pixels
    miss = size
    while miss > 0:
        # get the coordinates in a circular aperture around centre
        sample = uniform_around(centre, area, int(np.ceil(miss * over)))

        # get longitude and latitude of the sample
        sample_lon, sample_lat = sample.ra.deg, sample.dec.deg

        # accept those positions that are inside the correct pixel
        accept = ipix == ang2pix(nside,
                                 sample_lon,
                                 sample_lat,
                                 nest=nest,
                                 lonlat=True)

        # store the new positions
        lon = np.append(lon, np.extract(accept, sample_lon))
        lat = np.append(lat, np.extract(accept, sample_lat))
        miss = size - len(lon)

    # construct the coordinates
    return SkyCoord(lon[:size], lat[:size], unit=units.deg)
Exemple #38
0
def match_secondary(primtargs, scxdir, scndout, sep=1., pix=None, nside=None):
    """Match secondary targets to primary targets and update bits.

    Parameters
    ----------
    primtargs : :class:`~numpy.ndarray`
        An array of primary targets.
    scndout : :class`~numpy.ndarray`
        Name of a sub-directory to which to write the information in
        `desitarget.secondary.outdatamodel` with `TARGETID` and (the
        highest) `PRIORITY_INIT` updated with matching primary info.
    scxdir : :class:`str`, optional, defaults to `None`
        Name of the directory that hosts secondary targets.
    sep : :class:`float`, defaults to 1 arcsecond
        The separation at which to match in ARCSECONDS.
    pix : :class:`list`, optional, defaults to `None`
        Limit secondary targets to (NESTED) HEALpixels that touch
        pix at the supplied `nside`, as a speed-up.
    nside : :class:`int`, optional, defaults to `None`
        The (NESTED) HEALPixel nside to be used with `pixlist`.

    Returns
    -------
    :class:`~numpy.ndarray`
        The array of primary targets, with the `SCND_TARGET` bit
        populated for matches to secondary targets
    """
    # ADM add a SCND_TARGET column to the primary targets.
    dt = primtargs.dtype.descr
    dt.append(('SCND_TARGET', '>i8'))
    targs = np.zeros(len(primtargs), dtype=dt)
    for col in primtargs.dtype.names:
        targs[col] = primtargs[col]

    # ADM check if this is an SV or main survey file.
    cols, mx, surv = main_cmx_or_sv(targs, scnd=True)
    log.info('running on the {} survey...'.format(surv))
    if surv != 'main':
        scxdir = os.path.join(scxdir, surv)

    # ADM read in non-OVERRIDE secondary targets.
    scxtargs = read_files(scxdir, mx[3])
    scxtargs = scxtargs[~scxtargs["OVERRIDE"]]

    # ADM match primary targets to non-OVERRIDE secondary targets.
    inhp = np.ones(len(scxtargs), dtype="?")
    # ADM as a speed-up, save memory by limiting the secondary targets
    # ADM to just HEALPixels that could touch the primary targets.
    if nside is not None and pix is not None:
        # ADM remember to grab adjacent pixels in case of edge effects.
        allpix = add_hp_neighbors(nside, pix)
        inhp = is_in_hp(scxtargs, nside, allpix)
        # ADM it's unlikely that the matching separation is comparable
        # ADM to the HEALPixel resolution, but guard against that anyway.
        halfpix = np.degrees(hp.max_pixrad(nside)) * 3600.
        if sep > halfpix:
            msg = 'sep ({}") exceeds (half) HEALPixel size ({}")'.format(
                sep, halfpix)
            log.critical(msg)
            raise ValueError(msg)
    # ADM warn the user if the secondary and primary samples are "large".
    big = 500000
    if np.sum(inhp) > big and len(primtargs) > big:
        log.warning('Large secondary (N={}) and primary (N={}) samples'.format(
            np.sum(inhp), len(primtargs)))
        log.warning('The code may run slowly')

    # ADM for each secondary target, determine if there is a match
    # ADM with a primary target. Note that sense is important, here
    # ADM (the primary targets must be passed first).
    log.info(
        'Matching primary and secondary targets for {} at {}"...t={:.1f}s'.
        format(scndout, sep,
               time() - start))
    mtargs, mscx = radec_match_to(targs, scxtargs[inhp], sep=sep)
    # ADM recast the indices to the full set of secondary targets,
    # ADM instead of just those that were in the relevant HEALPixels.
    mscx = np.where(inhp)[0][mscx]

    # ADM loop through the matches and update the SCND_TARGET
    # ADM column in the primary target list. The np.unique is a
    # ADM speed-up to assign singular matches first.
    umtargs, inv, cnt = np.unique(mtargs,
                                  return_inverse=True,
                                  return_counts=True)
    # ADM number of times each primary target was matched, ordered
    # ADM the same as mtargs, i.e. n(mtargs) for each entry in mtargs.
    nmtargs = cnt[inv]
    # ADM assign anything with nmtargs = 1 directly.
    singular = nmtargs == 1
    targs["SCND_TARGET"][mtargs[singular]] = scxtargs["SCND_TARGET"][
        mscx[singular]]
    # ADM loop through things with nmtargs > 1 and combine the bits.
    for i in range(len((mtargs[~singular]))):
        targs["SCND_TARGET"][mtargs[~singular][i]] |= scxtargs["SCND_TARGET"][
            mscx[~singular][i]]
    # ADM also assign the SCND_ANY bit to the primary targets.
    desicols, desimasks, _ = main_cmx_or_sv(targs, scnd=True)
    targs[desicols[0]][umtargs] |= desimasks[0].SCND_ANY

    # ADM rename the SCND_TARGET column, in case this is an SV file.
    targs = rfn.rename_fields(targs, {'SCND_TARGET': desicols[3]})

    # ADM update the secondary targets with the primary information.
    scxtargs["TARGETID"][mscx] = targs["TARGETID"][mtargs]
    # ADM the maximum priority will be used to break ties in the
    # ADM unlikely event that a secondary matches two primaries.
    hipri = np.maximum(targs["PRIORITY_INIT_DARK"],
                       targs["PRIORITY_INIT_BRIGHT"])
    scxtargs["PRIORITY_INIT"][mscx] = hipri[mtargs]

    # ADM write the secondary targets that have updated TARGETIDs.
    ii = scxtargs["TARGETID"] != -1
    nmatches = np.sum(ii)
    log.info('Writing {} secondary target matches to {}...t={:.1f}s'.format(
        nmatches, scndout,
        time() - start))
    if nmatches > 0:
        hdr = fitsio.FITSHDR()
        hdr["SURVEY"] = surv
        fitsio.write(scndout,
                     scxtargs[ii],
                     extname='SCND_TARG',
                     header=hdr,
                     clobber=True)

    log.info('Done...t={:.1f}s'.format(time() - start))

    return targs