def get_fov(self, radius, fermi_frame=False):
        """
        Returns
        -------
        array of RA and DEC

        """

        steps = 500

        if fermi_frame:
            fermi = self._center

            poly = SphericalPolygon.from_cone(fermi.lon.value,
                                              fermi.lat.value,
                                              radius,
                                              steps=steps)

        else:

            j2000 = self._center.icrs

            poly = SphericalPolygon.from_cone(j2000.ra.value,
                                              j2000.dec.value,
                                              radius,
                                              steps=steps)

        # ra, dec
        return [p for p in poly.to_radec()][0]
Example #2
0
    def get_fov(self, radius, fermi_frame=False):
        """
        Returns
        -------
        array of RA and DEC

        """

        steps = 500

        if fermi_frame:
            fermi = self._center

            poly = SphericalPolygon.from_cone(fermi.lon.value,
                                              fermi.lat.value,
                                              radius,
                                              steps=steps)


        else:

            j2000 = self._center.icrs

            poly = SphericalPolygon.from_cone(j2000.ra.value,
                                              j2000.dec.value,
                                              radius,
                                              steps=steps)

        # ra, dec
        return [p for p in poly.to_radec()][0]
Example #3
0
 def _update_bounding_polygon(self):
     polygons = [im.polygon for im in self._images]
     if len(polygons) == 0:
         self._polygon = SphericalPolygon([])
         self._radec = []
     else:
         self._polygon = SphericalPolygon.multi_union(polygons)
         self._radec = list(self._polygon.to_radec())
Example #4
0
 def _update_bounding_polygon(self):
     polygons = [im.polygon for im in self._images]
     if len(polygons) == 0:
         self._polygon = SphericalPolygon([])
         self._radec = []
     else:
         self._polygon = SphericalPolygon.multi_union(polygons)
         self._radec = list(self._polygon.to_radec())
Example #5
0
def get_difference(poly1, poly2, pixels=None):
    """attempt to get the difference poly1-poly2 as poly1^~poly2
        use a pixelation to attempt to find an outside point"""
    import matplotlib.pyplot as plt
    from mpl_toolkits.basemap import Basemap
    m = Basemap(projection='moll', lon_0=0)
    if pixels is None:
        pixels = get_healpix_pixelation(4)
    bounding_xyz = list(poly2.points)
    #contained = np.zeros(pixels.shape[0],dtype==bool)
    #for itr in range(0,len(bounding_xyz)):
    #    ra,dec = sgv.vector_to_radec(
    #    contained = contained | contains_points(bounding_xyz[itr],pixels)
    contained = contains_points(pixels, poly2)
    poly2_complement = None
    #poly2_complement = poly2.invert_polygon()
    #contained_c = contains_points(pixels,poly2_complement)
    #assert not np.any(contained & contained_c)
    first_false = 100 + np.argmin(contained[100:])
    #print("orig 1",contains_points(pixels[first_false:first_false+1],poly1),poly1.area())
    #print("orig 2",contains_points(pixels[first_false:first_false+1],poly2),poly2.area())
    print(poly2)
    colors = ['red', 'green', 'blue']
    for itr in range(0, len(bounding_xyz)):
        first_false = 100 + itr + np.argmin(contained[100 + itr:])
        theta_in = pixels[first_false, 0]
        phi_in = pixels[first_false, 1]
        inside_xyz = np.asarray(
            sgv.radec_to_vector(phi_in, theta_in - np.pi / 2., degrees=False))
        loc_poly = SphericalPolygon(bounding_xyz[itr].copy(),
                                    inside_xyz.copy())
        loc_poly.draw(m, color=colors[itr])
        cont = contains_points(pixels[first_false:first_false + 1], loc_poly)
        print("loc contains: ", cont)
        if poly2_complement is None:
            poly2_complement = deepcopy(loc_poly)
        else:
            poly2_complement = deepcopy(
                poly2_complement.intersection(loc_poly))
        cont_comp = contains_points(pixels[first_false:first_false + 1],
                                    poly2_complement)
        print("comp contains: ", cont_comp)
        print("test: ",
              np.all(contains_points(pixels[contained], poly2_complement)))
        print("test: ",
              np.any(contains_points(pixels[~contained], poly2_complement)))
        print("insp: ", list(poly2_complement.inside))
        #print("comp ",itr,contains_points(pixels[first_false:first_false+1],poly2_complement),poly2_complement.area(),poly2_complement.is_clockwise())
        #print("inside c ",list(poly2_complement.inside))
        #print("vert c ",list(poly2_complement.points))
        #for itr2 in range(0,len(list(poly2_complement.inside))):
        #    print("loc cont inside c ",loc_poly.contains_point(list(poly2_complement.inside)[itr2]))
    #print(poly1.area(),poly2.area(),poly2_complement.area())
    plt.show()
    return poly1.intersection(poly2_complement)
Example #6
0
	def get_earth_point_with_time(self,t):
		
		earth_radius = 6371. * u.km
		if self.sc_pos_f is not None and self.time_band is not None:
			x_f, y_f, z_f = self.sc_pos_f
			try:
				n = len(t)
				t = np.array(t)
				tband = t.max() - t.min()
				tband_sl = self.time_band[1] - self.time_band[0]
				if tband <= tband_sl + 2:
					t[t <= self.time_band[0]] = self.time_band[0] + 0.00001
					t[t >= self.time_band[1]] = self.time_band[1] - 0.00001
				else:
					t[t <= self.time_band[0]] = np.nan
					t[t >= self.time_band[1]] = np.nan
					
				x = x_f(t) * self.pos_unit
				y = y_f(t) * self.pos_unit
				z = z_f(t) * self.pos_unit
				earth_point_list = []
				for i in range(n):
					position = cartesian_to_spherical(x[i],y[i],z[i])
					xyz_position = SkyCoord(position[2].deg,position[1].deg,frame='icrs',unit='deg')
					fermi_radius = np.sqrt(x[i]**2 + y[i]**2 + z[i]**2)
					radius_deg = np.rad2deg(np.arcsin((earth_radius / fermi_radius).to(u.dimensionless_unscaled)).value)
					poly = SphericalPolygon.from_cone(position[2].deg,position[1].deg,radius_deg,steps=180)
					x_,y_ = np.array(list(poly.to_radec())[0])
					earth_point_list.append([xyz_position,radius_deg,x_,y_])
				return earth_point_list
			except 	(TypeError):
				
				if (t<=self.time_band[0]):
					if (self.time_band[0]-t<=1):
						t = self.time_band[0]+0.00001
					else:
						t = np.nan
				if t>=self.time_band[1]:
					if (t-self.time_band[0]<=1):
						t = self.time_band[1]-0.00001
					else:
						t = np.nan
				x = x_f(t) * self.pos_unit
				y = y_f(t) * self.pos_unit
				z = z_f(t) * self.pos_unit
				position = cartesian_to_spherical(x,y,z)
				xyz_position = SkyCoord(position[2].deg,position[1].deg,frame='icrs',unit='deg')
				fermi_radius = np.sqrt(x**2 + y**2 + z**2)
				radius_deg = np.rad2deg(np.arcsin((earth_radius / fermi_radius).to(u.dimensionless_unscaled)).value)
				poly = SphericalPolygon.from_cone(position[2].deg,position[1].deg,radius_deg,steps=180)
				x_,y_ = np.array(list(poly.to_radec())[0])
				return xyz_position,radius_deg,x_,y_
		else:
			return None
Example #7
0
    def build_polygon(self, member='total'):
        if self.edges_ra is None:
            self.get_edges_sky(member=member)

        self.polygon = SphericalPolygon.from_radec(self.edges_ra,
                                                   self.edges_dec,
                                                   self.meta_wcs.wcs.crval)
Example #8
0
 def get_earth_point(self, index=None):
     if self.sc_pos is not None:
         earth_point_list = []
         if index is not None:
             sc_pos_list = self.sc_pos[index]
         else:
             sc_pos_list = self.sc_pos
         for sc_pos in sc_pos_list:
             position = cartesian_to_spherical(-sc_pos[0], -sc_pos[1],
                                               -sc_pos[2])
             xyz_position = SkyCoord(position[2].deg,
                                     position[1].deg,
                                     frame='icrs',
                                     unit='deg')
             earth_radius = 6371. * u.km
             fermi_radius = np.sqrt((sc_pos**2).sum())
             radius_deg = np.rad2deg(
                 np.arcsin((earth_radius / fermi_radius).to(
                     u.dimensionless_unscaled)).value)
             poly = SphericalPolygon.from_cone(position[2].deg,
                                               position[1].deg,
                                               radius_deg,
                                               steps=100)
             x, y = [p for p in poly.to_radec()][0]
             earth_point_list.append([xyz_position, radius_deg, x, y])
         return earth_point_list
     else:
         print('No satellite position!')
         return None
def sky_cone(ra_c, dec_c, theta, steps=50, include_center=True):
    """
    Get ra and dec coordinates of a cone on the sky.
    Parameters
    ----------
    ra_c, dec_c: float
        Center of cone in degrees.
    theta: astropy Quantity, float, or int
        Angular radius of cone. Must be in arcsec
        if not a Quantity object.
    steps: int, optional
        Number of steps in the cone.
    include_center: bool, optional
        If True, include center point in cone.
    Returns
    -------
    ra, dec: ndarry
        Coordinates of cone.
    """
    if isinstance(theta, float) or isinstance(theta, int):
        theta = theta * u.Unit('arcsec')

    cone = SphericalPolygon.from_cone(ra_c,
                                      dec_c,
                                      theta.to('deg').value,
                                      steps=steps)
    ra, dec = list(cone.to_lonlat())[0]
    ra = np.mod(ra - 360., 360.0)
    if include_center:
        ra = np.concatenate([ra, [ra_c]])
        dec = np.concatenate([dec, [dec_c]])
    return ra, dec
Example #10
0
    def build_polygon(self):
        if self.edges_ra is None:
            self.get_edges_sky()

        self.polygon = SphericalPolygon.from_radec(self.edges_ra,
                                                   self.edges_dec,
                                                   self.meta_wcs.wcs.crval)
Example #11
0
    def build_polygon(self):

        inner_pix = self.wcs.pixel_to_world_values(2, 2)
        # define polygon on the sky
        self.polygon = SphericalPolygon.from_radec(self.corners[:, 0],
                                                   self.corners[:,
                                                                1], inner_pix)
Example #12
0
def getWcsRegion(imageRow):
    region = None
    stcs = imageRow["regionSTCS"]
    if (stcs is not None):
        a = stcs.split(' ')
        if len(a) == 10:
            ras = [
                float(a[2]),
                float(a[4]),
                float(a[6]),
                float(a[8]),
                float(a[2])
            ]
            decs = [
                float(a[3]),
                float(a[5]),
                float(a[7]),
                float(a[9]),
                float(a[3])
            ]
            print ras
            print decs
            region = SphericalPolygon.from_radec(ras, decs)

    return region
Example #13
0
def get_fov(conter,radius = 10.):
		fov_point_list = []
		for conter_i in conter:
			poly = SphericalPolygon.from_cone(conter_i.ra.value,conter_i.dec.value,radius,steps=100)
			x,y = [p for p in poly.to_radec()][0]
			fov_point_list.append([radius,x,y])
		return fov_point_list
Example #14
0
	def get_fov(self,conter,radius = 10.):
		fov_point_list = []
		for conter_i in conter:
			poly = SphericalPolygon.from_cone(conter_i.ra.value,conter_i.dec.value,radius,steps=180)
			x,y = np.array(list(poly.to_radec())[0])
			fov_point_list.append([radius,x,y])
		return fov_point_list
Example #15
0
    def contains_point(self, point):

        steps = 300
        j2000 = self.center.icrs
        poly = SphericalPolygon.from_cone(j2000.ra.value,
                                          j2000.dec.value,
                                          self.radius,
                                          steps=steps)
        return poly.contains_point(point.cartesian.xyz.value)
Example #16
0
    def set_members(self, mlist, polygon):
        if mlist is None:
            member_list = []
        elif isinstance(mlist, list):
            member_list = mlist
            if [1 for m in mlist if not isinstance(m, SkyLineMember)]:
                raise ValueError("The 'mlist' argument must be either "
                                 "a single 'SkyLineMember' object or a "
                                 "Python list of 'SkyLineMember' objects.")
        elif isinstance(mlist, SkyLineMember):
            member_list = [mlist]
        else:
            raise ValueError("The 'mlist' argument must be either "
                             "a single 'SkyLineMember' object or a "
                             "Python list of 'SkyLineMember' objects.")

        self._members = []

        # Not using set to preserve order
        n = len(member_list)
        if n == 0:
            if polygon is None:
                self.polygon = SphericalPolygon([])
            else:
                self.polygon = deepcopy(polygon)
            self._id = ''
            self._is_mf_mosaic = False

        elif n == 1:
            assert isinstance(member_list[0], SkyLineMember)
            if polygon is None:
                self.polygon = deepcopy(member_list[0].polygon)
            else:
                self.polygon = deepcopy(polygon)
            self._id = member_list[0].id
            self._members.append(member_list[0])
            self._is_mf_mosaic = False

        else:
            assert isinstance(member_list[0], SkyLineMember)
            if polygon is None:
                mpol = deepcopy(member_list[0].polygon)
            else:
                mpol = deepcopy(polygon)
            self._members.append(member_list[0])

            for m in member_list[1:]:
                # Report corrupted members list instead of skipping
                assert isinstance(m, SkyLineMember)

                if m not in self._members:
                    self._members.append(m)
                    if polygon is None:
                        mpol = mpol.union(m.polygon)

            self.polygon = mpol
            self._update_mosaic_flag_id()
Example #17
0
def get_poly(theta_vertices, phi_vertices, theta_in, phi_in):
    """get the SphericalPolygon object for the geometry"""
    bounding_theta = theta_vertices - np.pi / 2.  #to radec
    bounding_phi = phi_vertices
    bounding_xyz = np.asarray(
        sgv.radec_to_vector(bounding_phi, bounding_theta, degrees=False)).T
    inside_xyz = np.asarray(
        sgv.radec_to_vector(phi_in, theta_in - np.pi / 2., degrees=False))

    sp_poly = SphericalPolygon(bounding_xyz, inside=inside_xyz)
    return sp_poly
Example #18
0
	def get_fov(self,radius):
		'''这里是计算出探头标注'''
		if radius >= 60:
			steps = 500
		elif radius >= 30:
			steps = 300
		else:
			steps = 250
		j2000 = self.center.icrs
		poly = SphericalPolygon.from_cone(j2000.ra.value,j2000.dec.value,radius,steps = steps)
		re =  [p for p in poly.to_radec()][0]
		return re
Example #19
0
def makeQueryRegion(ra, dec, size):
    region = None

    if size[1] is None:
        # Then we have a radius, not a box.
        radius = size[0]
        region = SphericalPolygon.from_cone(ra, dec, radius)
    else:
        # Make a box.
        radius = None
        # Figure out this stupid shape later...

    return region
Example #20
0
def getWcsRegion(imageRow):
    region = None
    stcs = imageRow["regionSTCS"]
    if stcs is not None:
        a = stcs.split(" ")
        if len(a) == 10:
            ras = [float(a[2]), float(a[4]), float(a[6]), float(a[8]), float(a[2])]
            decs = [float(a[3]), float(a[5]), float(a[7]), float(a[9]), float(a[3])]
            print ras
            print decs
            region = SphericalPolygon.from_radec(ras, decs)

    return region
Example #21
0
def makeQueryRegion(ra, dec, size):
    region = None

    if size[1] is None:
        # Then we have a radius, not a box.
        radius = size[0]
        region = SphericalPolygon.from_cone(ra, dec, radius)
    else:
        # Make a box.
        radius = None
        # Figure out this stupid shape later...

    return region
Example #22
0
 def get_fov(self, radius):
     if radius >= 60:
         steps = 5000  ## could be modified to speed up the plotting
     elif radius >= 30:
         steps = 400  ## could be modified to speed up the plotting
     else:
         steps = 100  ## could be modified to speed up the plotting
     j2000 = self.center.icrs
     poly = SphericalPolygon.from_cone(j2000.ra.value,
                                       j2000.dec.value,
                                       radius,
                                       steps=steps)
     re = [p for p in poly.to_radec()][0]
     return re
Example #23
0
    def run(self, dataSlice, slicePoint=None):
        # RA and Dec from dataSlice doesn't necessarily match healpix
        RA = dataSlice['fieldRA'][0]
        Dec = dataSlice['fieldDec'][0]
        # Get RA and Dec from slicer
        RA = slicePoint['ra']
        Dec = slicePoint['dec']
        nside = slicePoint['nside']
        # get the boundaries from HealPix
        bounding = hp.boundaries(nside, slicePoint['sid'], step=1).T
        inside_val = np.asarray(
            sgv.radec_to_vector(slicePoint['ra'],
                                slicePoint['dec'],
                                degrees=False))
        test_pointing = SphericalPolygon(bounding, inside_val)

        overlap_area = [
            test_pointing.intersection(survey_polygon).area()
            for survey_polygon in survey_list[self.region_name]
        ]
        total = sum(overlap_area)
        healpix_area = test_pointing.area()
        return min(total, 4 * np.pi - total)
Example #24
0
    def plot_earth(self, t, satellite, **kwargs):

        if 'facecolor' not in kwargs:
            kwargs['facecolor'] = '#90d7ec'
        if 'edgecolor' not in kwargs:
            kwargs['edgecolor'] = '#90d7ec'

        try:
            met, ra, dec, radius = satellite.get_earth_point(t)
            for i in range(len(met)):
                poly = SphericalPolygon.from_cone(ra[i],
                                                  dec[i],
                                                  radius[i],
                                                  steps=180)
                x_, y_ = np.array(list(poly.to_radec())[0])
                earth = self.Polygon(list(zip(x_, y_))[::-1], **kwargs)
                self.ax.add_patch(earth)
        except (TypeError):
            met, ra, dec, radius = satellite.get_earth_point(t)
            poly = SphericalPolygon.from_cone(ra, dec, radius, steps=180)
            x_, y_ = np.array(list(poly.to_radec())[0])
            earth = self.Polygon(list(zip(x_, y_))[::-1], **kwargs)
            self.ax.add_patch(earth)
Example #25
0
    def _draw_members(self, map, **kwargs):
        """
        Draw individual extensions in members.
        Useful for debugging.

        Parameters
        ----------
        map : Basemap axes object

        **kwargs : Any plot arguments to pass to basemap

        """
        wcs_list = self._indv_mem_wcslist()

        for wcs in wcs_list:
            poly = SphericalPolygon.from_wcs(wcs)
            poly.draw(map, **kwargs)
Example #26
0
    def _draw_members(self, map, **kwargs):
        """
        Draw individual extensions in members.
        Useful for debugging.

        Parameters
        ----------
        map : Basemap axes object

        **kwargs : Any plot arguments to pass to basemap

        """
        wcs_list = self._indv_mem_wcslist()

        for wcs in wcs_list:
            poly = SphericalPolygon.from_wcs(wcs)
            poly.draw(map, **kwargs)
 def get_good_detector_centers(self, source=None):
     good_detector_centers = []
     good_detector_index = []
     if source is not None:
         for index, center in enumerate(self.detectors.center_all):
             steps = 250
             j2000 = center.icrs
             poly = SphericalPolygon.from_cone(j2000.ra.value,
                                               j2000.dec.value,
                                               self.radius,
                                               steps=steps)
             if (poly.contains_point(source.cartesian.xyz.value)):
                 good_detector_centers.append(center)
                 good_detector_index.append(index)
     else:
         print('No source! return []')
     return good_detector_index, good_detector_centers
 def get_fov(self, radius):
     if radius >= 60:
         steps = 500
     elif (radius >= 30):
         steps = 300
     else:
         steps = 250
     j2000 = self.center_all.icrs
     c = []
     for index, center in enumerate(j2000):
         print(str(index))
         poly = SphericalPolygon.from_cone(center.ra.value,
                                           center.dec.value,
                                           radius,
                                           steps=steps)
         re = [p for p in poly.to_radec()][0]
         #print(re)
         c.append(re)
     return c
Example #29
0
def quad_to_poly(ra, dec, **kwargs):
    p = SphericalPolygon.from_radec(ra, dec, degrees=True)

    points = p.polygons[0]._points
    _ra, _dec = [], []
    for A, B in zip(points[0:-1], points[1:]):
        length = great_circle_arc.length(A, B, degrees=True)
        if not np.isfinite(length):
            length = 2
        interpolated = great_circle_arc.interpolate(A, B, length * 4)
        lon, lat = vector.vector_to_lonlat(interpolated[:, 0],
                                           interpolated[:, 1],
                                           interpolated[:, 2],
                                           degrees=True)
        for lon0, lat0, lon1, lat1 in zip(lon[0:-1], lat[0:-1], lon[1:],
                                          lat[1:]):
            _ra.append(lon0)
            _dec.append(lat0)

    _ra.append(lon1)
    _dec.append(lat1)
    return PolyCollection([np.c_[_ra, _dec]], **kwargs)
def true_axial_analysis(df_row, traj_def, euler_def, path_fnc, add_axial_rot_fnc):
    traj = df_row[traj_def]
    start_idx = df_row['up_down_analysis'].max_run_up_start_idx
    end_idx = df_row['up_down_analysis'].max_run_up_end_idx
    orient = np.rad2deg(rgetattr(traj, euler_def))
    true_axial = np.rad2deg(getattr(traj, 'true_axial_rot'))
    apparent_orient_diff = orient[end_idx, 2] - orient[start_idx, 2]
    true_axial_diff = true_axial[end_idx] - true_axial[start_idx]
    add_axial_rot = add_axial_rot_fnc(orient, start_idx, end_idx)

    long, lat = path_fnc(orient, start_idx, end_idx)

    # compute the area
    mid_ix = int((start_idx + end_idx) / 2)
    sp = SphericalPolygon.from_lonlat(long, lat, center=(orient[mid_ix, 0], orient[mid_ix, 1]/2))
    area = np.rad2deg(sp.area())

    # if the actual path and the "euler" path cross each other the spherical_geometry polygon incorrectly estimates the
    # area
    while area > 180:
        area -= 180

    return apparent_orient_diff, true_axial_diff, area, add_axial_rot, sp.is_clockwise()
Example #31
0
    def _calc_sky_orig(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if overlap is None:

            if self.mask is None:
                data = self.image
            else:
                data = self.image[self.mask]

            polyarea = self.poly_area

        else:
            fill_mask = np.zeros(self.image.shape, dtype=bool)

            if isinstance(overlap, (SkyImage, SkyGroup, SphericalPolygon)):
                intersection = self.intersection(overlap)
                polyarea = np.fabs(intersection.area())
                radec = intersection.to_radec()

            else:  # assume a list of (ra, dec) tuples:
                radec = []
                polyarea = 0.0
                for r, d in overlap:
                    poly = SphericalPolygon.from_radec(r, d)
                    polyarea1 = np.fabs(poly.area())
                    if polyarea1 == 0.0 or len(r) < 4:
                        continue
                    polyarea += polyarea1
                    radec.append(self.intersection(poly).to_radec())

            if polyarea == 0.0:
                return (None, 0, 0.0)

            for ra, dec in radec:
                if len(ra) < 4:
                    continue

                # set pixels in 'fill_mask' that are inside a polygon to True:
                x, y = self.wcs_inv(ra, dec)
                poly_vert = list(zip(*[x, y]))

                polygon = region.Polygon(True, poly_vert)
                fill_mask = polygon.scan(fill_mask)

            if self.mask is not None:
                fill_mask &= self.mask

            data = self.image[fill_mask]

            if data.size < 1:
                return (None, 0, 0.0)

        # Calculate sky
        try:

            skyval, npix = self._skystat(data)

        except ValueError:

            return (None, 0, 0.0)

        if delta:
            skyval -= self._sky

        return skyval, npix, polyarea
Example #32
0
    def __init__(self,
                 image,
                 wcs_fwd,
                 wcs_inv,
                 pix_area=1.0,
                 convf=1.0,
                 mask=None,
                 id=None,
                 skystat=None,
                 stepsize=None,
                 meta=None):
        """ Initializes the SkyImage object.

        Parameters
        ----------
        image : numpy.ndarray
            A 2D array of image data.

        wcs_fwd : function
            "forward" pixel-to-world transformation function.

        wcs_inv : function
            "inverse" world-to-pixel transformation function.

        pix_area : float, optional
            Average pixel's sky area.

        convf : float, optional
            Conversion factor that when multiplied to `image` data converts
            the data to "uniform" (across multiple images) surface
            brightness units.

            .. note::

              The functionality to support this conversion is not yet
              implemented and at this moment `convf` is ignored.

        mask : numpy.ndarray
            A 2D array that indicates
            what pixels in the input `image` should be used for sky
            computations (``1``) and which pixels should **not** be used
            for sky computations (``0``).

        id : anything
            The value of this parameter is simple stored within the `SkyImage`
            object. While it can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        skystat : callable, None, optional
            A callable object that takes a either a 2D image (2D
            `numpy.ndarray`) or a list of pixel values (a Nx1 array) and
            returns a tuple of two values: some statistics (e.g., mean,
            median, etc.) and number of pixels/values from the input image
            used in computing that statistics.

            When `skystat` is not set, `SkyImage` will use
            :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` object
            to perform sky statistics on image data.

        stepsize : int, None, optional
            Spacing between vertices of the image's bounding polygon. Default
            value of `None` creates bounding polygons with four vertices
            corresponding to the corners of the image.

        meta : dict, None, optional
            A dictionary of various items to be stored within the `SkyImage`
            object.

        """
        self.image = image
        self.convf = convf
        self.meta = meta
        self._id = id
        self._pix_area = pix_area

        # WCS
        self.wcs_fwd = wcs_fwd
        self.wcs_inv = wcs_inv

        # initial sky value:
        self._sky = 0.0

        # check that mask has the same shape as image:
        if mask is None:
            self.mask = None

        else:
            if image is None:
                raise ValueError("'mask' must be None when 'image' is None")

            self.mask = np.asanyarray(mask, dtype=np.bool)

            if self.mask.shape != image.shape:
                raise ValueError("'mask' must have the same shape as 'image'.")

        # create spherical polygon bounding the image
        if image is None or wcs_fwd is None or wcs_inv is None:
            self._radec = [(np.array([]), np.array([]))]
            self._polygon = SphericalPolygon([])
            self._poly_area = 0.0

        else:
            self.calc_bounding_polygon(stepsize)

        # set sky statistics function (NOTE: it must return statistics and
        # the number of pixels used after clipping)
        if skystat is None:
            self.set_builtin_skystat()
        else:
            self.skystat = skystat
Example #33
0
class SkyImage:
    """
    Container that holds information about properties of a *single*
    image such as:

    * image data;
    * WCS of the chip image;
    * bounding spherical polygon;
    * id;
    * pixel area;
    * sky background value;
    * sky statistics parameters;
    * mask associated image data indicating "good" (1) data.

    """
    def __init__(self,
                 image,
                 wcs_fwd,
                 wcs_inv,
                 pix_area=1.0,
                 convf=1.0,
                 mask=None,
                 id=None,
                 skystat=None,
                 stepsize=None,
                 meta=None):
        """ Initializes the SkyImage object.

        Parameters
        ----------
        image : numpy.ndarray
            A 2D array of image data.

        wcs_fwd : function
            "forward" pixel-to-world transformation function.

        wcs_inv : function
            "inverse" world-to-pixel transformation function.

        pix_area : float, optional
            Average pixel's sky area.

        convf : float, optional
            Conversion factor that when multiplied to `image` data converts
            the data to "uniform" (across multiple images) surface
            brightness units.

            .. note::

              The functionality to support this conversion is not yet
              implemented and at this moment `convf` is ignored.

        mask : numpy.ndarray
            A 2D array that indicates
            what pixels in the input `image` should be used for sky
            computations (``1``) and which pixels should **not** be used
            for sky computations (``0``).

        id : anything
            The value of this parameter is simple stored within the `SkyImage`
            object. While it can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        skystat : callable, None, optional
            A callable object that takes a either a 2D image (2D
            `numpy.ndarray`) or a list of pixel values (a Nx1 array) and
            returns a tuple of two values: some statistics (e.g., mean,
            median, etc.) and number of pixels/values from the input image
            used in computing that statistics.

            When `skystat` is not set, `SkyImage` will use
            :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` object
            to perform sky statistics on image data.

        stepsize : int, None, optional
            Spacing between vertices of the image's bounding polygon. Default
            value of `None` creates bounding polygons with four vertices
            corresponding to the corners of the image.

        meta : dict, None, optional
            A dictionary of various items to be stored within the `SkyImage`
            object.

        """
        self.image = image
        self.convf = convf
        self.meta = meta
        self._id = id
        self._pix_area = pix_area

        # WCS
        self.wcs_fwd = wcs_fwd
        self.wcs_inv = wcs_inv

        # initial sky value:
        self._sky = 0.0

        # check that mask has the same shape as image:
        if mask is None:
            self.mask = None

        else:
            if image is None:
                raise ValueError("'mask' must be None when 'image' is None")

            self.mask = np.asanyarray(mask, dtype=np.bool)

            if self.mask.shape != image.shape:
                raise ValueError("'mask' must have the same shape as 'image'.")

        # create spherical polygon bounding the image
        if image is None or wcs_fwd is None or wcs_inv is None:
            self._radec = [(np.array([]), np.array([]))]
            self._polygon = SphericalPolygon([])
            self._poly_area = 0.0

        else:
            self.calc_bounding_polygon(stepsize)

        # set sky statistics function (NOTE: it must return statistics and
        # the number of pixels used after clipping)
        if skystat is None:
            self.set_builtin_skystat()
        else:
            self.skystat = skystat

    @property
    def id(self):
        """ Set or get `SkyImage`'s `id`.

        While `id` can be of any type, it is prefereble that `id` be
        of a type with nice string representation.

        """
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def pix_area(self):
        """ Set or get mean pixel area.
        """
        return self._pix_area

    @pix_area.setter
    def pix_area(self, pix_area):
        self._pix_area = pix_area

    @property
    def poly_area(self):
        """ Get bounding polygon area in srad units.
        """
        return self._poly_area

    @property
    def sky(self):
        """ Sky background value. See `calc_sky` for more details.
        """
        return self._sky

    @sky.setter
    def sky(self, sky):
        self._sky = sky

    @property
    def radec(self):
        """
        Get RA and DEC of the verteces of the bounding polygon as a
        `~numpy.ndarray` of shape (N, 2) where N is the number of verteces + 1.
        """
        return self._radec

    @property
    def polygon(self):
        """ Get image's bounding polygon.
        """
        return self._polygon

    def intersection(self, skyimage):
        """
        Compute intersection of this `SkyImage` object and another
        `SkyImage`, `SkyGroup`, or
        :py:class:`~spherical_geometry.polygon.SphericalPolygon`
        object.

        Parameters
        ----------
        skyimage : SkyImage, SkyGroup, SphericalPolygon
            Another object that should be intersected with this `SkyImage`.

        Returns
        -------
        polygon : SphericalPolygon
            A :py:class:`~spherical_geometry.polygon.SphericalPolygon` that is
            the intersection of this `SkyImage` and `skyimage`.

        """
        if isinstance(skyimage, (SkyImage, SkyGroup)):
            return self._polygon.intersection(skyimage.polygon)
        else:
            return self._polygon.intersection(skyimage)

    def calc_bounding_polygon(self, stepsize=None):
        """ Compute image's bounding polygon.

        Parameters
        ----------
        stepsize : int, None, optional
            Indicates the maximum separation between two adjacent vertices
            of the bounding polygon along each side of the image. Corners
            of the image are included automatically. If `stepsize` is `None`,
            bounding polygon will contain only vertices of the image.

        """
        ny, nx = self.image.shape

        if stepsize is None:
            nintx = 2
            ninty = 2
        else:
            nintx = max(2, int(np.ceil((nx + 1.0) / stepsize)))
            ninty = max(2, int(np.ceil((ny + 1.0) / stepsize)))

        xs = np.linspace(-0.5, nx - 0.5, nintx, dtype=np.float)
        ys = np.linspace(-0.5, ny - 0.5, ninty, dtype=np.float)[1:-1]
        nptx = xs.size
        npty = ys.size

        npts = 2 * (nptx + npty)

        borderx = np.empty((npts + 1, ), dtype=np.float)
        bordery = np.empty((npts + 1, ), dtype=np.float)

        # "bottom" points:
        borderx[:nptx] = xs
        bordery[:nptx] = -0.5
        # "right"
        sl = np.s_[nptx:nptx + npty]
        borderx[sl] = nx - 0.5
        bordery[sl] = ys
        # "top"
        sl = np.s_[nptx + npty:2 * nptx + npty]
        borderx[sl] = xs[::-1]
        bordery[sl] = ny - 0.5
        # "left"
        sl = np.s_[2 * nptx + npty:-1]
        borderx[sl] = -0.5
        bordery[sl] = ys[::-1]

        # close polygon:
        borderx[-1] = borderx[0]
        bordery[-1] = bordery[0]

        ra, dec = self.wcs_fwd(borderx, bordery, with_bounding_box=False)
        # TODO: for strange reasons, occasionally ra[0] != ra[-1] and/or
        #       dec[0] != dec[-1] (even though we close the polygon in the
        #       previous two lines). Then SphericalPolygon fails because
        #       points are not closed. Threfore we force it to be closed:
        ra[-1] = ra[0]
        dec[-1] = dec[0]

        self._radec = [(ra, dec)]
        self._polygon = SphericalPolygon.from_radec(ra, dec)
        self._poly_area = np.fabs(self._polygon.area())

    @property
    def skystat(self):
        """ Stores/retrieves a callable object that takes a either a 2D image
        (2D `numpy.ndarray`) or a list of pixel values (a Nx1 array) and
        returns a tuple of two values: some statistics
        (e.g., mean, median, etc.) and number of pixels/values from the input
        image used in computing that statistics.

        When `skystat` is not set, `SkyImage` will use
        :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` object
        to perform sky statistics on image data.

        """
        return self._skystat

    @skystat.setter
    def skystat(self, skystat):
        self._skystat = skystat

    def set_builtin_skystat(self,
                            skystat='median',
                            lower=None,
                            upper=None,
                            nclip=5,
                            lsigma=4.0,
                            usigma=4.0,
                            binwidth=0.1):
        """
        Replace already set `skystat` with a "built-in" version of a
        statistics callable object used to measure sky background.

        See :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` for the
        parameter description.

        """
        self._skystat = SkyStats(skystat=skystat,
                                 lower=lower,
                                 upper=upper,
                                 nclip=nclip,
                                 lsig=lsigma,
                                 usig=usigma,
                                 binwidth=binwidth)

    # TODO: due to a bug in the sphere package, see
    #       https://github.com/spacetelescope/sphere/issues/74
    #       intersections with polygons formed as union does not work.
    #       For this reason I re-implement 'calc_sky' below with
    #       a workaround for the bug.
    #       The original implementation (now called ``_calc_sky_orig``
    #       should replace current 'calc_sky' once the bug is fixed.
    #
    def calc_sky(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """
        if overlap is None:

            if self.mask is None:
                data = self.image
            else:
                data = self.image[self.mask]

            polyarea = self.poly_area

        else:
            fill_mask = np.zeros(self.image.shape, dtype=bool)

            if isinstance(overlap, SkyImage):
                intersection = self.intersection(overlap)
                polyarea = np.fabs(intersection.area())
                radec = list(intersection.to_radec())

            elif isinstance(overlap, SkyGroup):
                radec = []
                polyarea = 0.0
                for im in overlap:
                    intersection = self.intersection(im)
                    polyarea1 = np.fabs(intersection.area())
                    if polyarea1 == 0.0:
                        continue
                    polyarea += polyarea1
                    radec += list(intersection.to_radec())

            elif isinstance(overlap, SphericalPolygon):
                radec = []
                polyarea = 0.0
                for p in overlap._polygons:
                    intersection = self.intersection(SphericalPolygon([p]))
                    polyarea1 = np.fabs(intersection.area())
                    if polyarea1 == 0.0:
                        continue
                    polyarea += polyarea1
                    radec += list(intersection.to_radec())

            else:  # assume a list of (ra, dec) tuples:
                radec = []
                polyarea = 0.0
                for r, d in overlap:
                    poly = SphericalPolygon.from_radec(r, d)
                    polyarea1 = np.fabs(poly.area())
                    if polyarea1 == 0.0 or len(r) < 4:
                        continue
                    polyarea += polyarea1
                    radec.append(self.intersection(poly).to_radec())

            if polyarea == 0.0:
                return (None, 0, 0.0)

            for ra, dec in radec:
                if len(ra) < 4:
                    continue

                # set pixels in 'fill_mask' that are inside a polygon to True:
                x, y = self.wcs_inv(ra, dec)
                poly_vert = list(zip(*[x, y]))

                polygon = region.Polygon(True, poly_vert)
                fill_mask = polygon.scan(fill_mask)

            if self.mask is not None:
                fill_mask &= self.mask

            data = self.image[fill_mask]

            if data.size < 1:
                return (None, 0, 0.0)

        # Calculate sky
        try:

            skyval, npix = self._skystat(data)

        except ValueError:

            return (None, 0, 0.0)

        if delta:
            skyval -= self._sky

        return skyval, npix, polyarea

    def _calc_sky_orig(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if overlap is None:

            if self.mask is None:
                data = self.image
            else:
                data = self.image[self.mask]

            polyarea = self.poly_area

        else:
            fill_mask = np.zeros(self.image.shape, dtype=bool)

            if isinstance(overlap, (SkyImage, SkyGroup, SphericalPolygon)):
                intersection = self.intersection(overlap)
                polyarea = np.fabs(intersection.area())
                radec = intersection.to_radec()

            else:  # assume a list of (ra, dec) tuples:
                radec = []
                polyarea = 0.0
                for r, d in overlap:
                    poly = SphericalPolygon.from_radec(r, d)
                    polyarea1 = np.fabs(poly.area())
                    if polyarea1 == 0.0 or len(r) < 4:
                        continue
                    polyarea += polyarea1
                    radec.append(self.intersection(poly).to_radec())

            if polyarea == 0.0:
                return (None, 0, 0.0)

            for ra, dec in radec:
                if len(ra) < 4:
                    continue

                # set pixels in 'fill_mask' that are inside a polygon to True:
                x, y = self.wcs_inv(ra, dec)
                poly_vert = list(zip(*[x, y]))

                polygon = region.Polygon(True, poly_vert)
                fill_mask = polygon.scan(fill_mask)

            if self.mask is not None:
                fill_mask &= self.mask

            data = self.image[fill_mask]

            if data.size < 1:
                return (None, 0, 0.0)

        # Calculate sky
        try:

            skyval, npix = self._skystat(data)

        except ValueError:

            return (None, 0, 0.0)

        if delta:
            skyval -= self._sky

        return skyval, npix, polyarea

    def copy(self):
        """
        Return a shallow copy of the `SkyImage` object.
        """
        si = SkyImage(image=None,
                      wcs_fwd=self.wcs_fwd,
                      wcs_inv=self.wcs_inv,
                      pix_area=self.pix_area,
                      convf=self.convf,
                      mask=None,
                      id=self.id,
                      stepsize=None,
                      meta=self.meta)
        si.image = self.image
        si.mask = self.mask
        si._radec = self._radec
        si._polygon = self._polygon
        si._poly_area = self._poly_area
        si.sky = self.sky
        return si
Example #34
0
    def calc_bounding_polygon(self, stepsize=None):
        """ Compute image's bounding polygon.

        Parameters
        ----------
        stepsize : int, None, optional
            Indicates the maximum separation between two adjacent vertices
            of the bounding polygon along each side of the image. Corners
            of the image are included automatically. If `stepsize` is `None`,
            bounding polygon will contain only vertices of the image.

        """
        ny, nx = self.image.shape

        if stepsize is None:
            nintx = 2
            ninty = 2
        else:
            nintx = max(2, int(np.ceil((nx + 1.0) / stepsize)))
            ninty = max(2, int(np.ceil((ny + 1.0) / stepsize)))

        xs = np.linspace(-0.5, nx - 0.5, nintx, dtype=np.float)
        ys = np.linspace(-0.5, ny - 0.5, ninty, dtype=np.float)[1:-1]
        nptx = xs.size
        npty = ys.size

        npts = 2 * (nptx + npty)

        borderx = np.empty((npts + 1, ), dtype=np.float)
        bordery = np.empty((npts + 1, ), dtype=np.float)

        # "bottom" points:
        borderx[:nptx] = xs
        bordery[:nptx] = -0.5
        # "right"
        sl = np.s_[nptx:nptx + npty]
        borderx[sl] = nx - 0.5
        bordery[sl] = ys
        # "top"
        sl = np.s_[nptx + npty:2 * nptx + npty]
        borderx[sl] = xs[::-1]
        bordery[sl] = ny - 0.5
        # "left"
        sl = np.s_[2 * nptx + npty:-1]
        borderx[sl] = -0.5
        bordery[sl] = ys[::-1]

        # close polygon:
        borderx[-1] = borderx[0]
        bordery[-1] = bordery[0]

        ra, dec = self.wcs_fwd(borderx, bordery, with_bounding_box=False)
        # TODO: for strange reasons, occasionally ra[0] != ra[-1] and/or
        #       dec[0] != dec[-1] (even though we close the polygon in the
        #       previous two lines). Then SphericalPolygon fails because
        #       points are not closed. Threfore we force it to be closed:
        ra[-1] = ra[0]
        dec[-1] = dec[0]

        self._radec = [(ra, dec)]
        self._polygon = SphericalPolygon.from_radec(ra, dec)
        self._poly_area = np.fabs(self._polygon.area())
Example #35
0
class SkyImage:
    """
    Container that holds information about properties of a *single*
    image such as:

    * image data;
    * WCS of the chip image;
    * bounding spherical polygon;
    * id;
    * pixel area;
    * sky background value;
    * sky statistics parameters;
    * mask associated image data indicating "good" (1) data.

    """


    def __init__(self, image, wcs_fwd, wcs_inv, pix_area=1.0, convf=1.0,
                 mask=None, id=None, skystat=None, stepsize=None, meta=None):
        """ Initializes the SkyImage object.

        Parameters
        ----------
        image : numpy.ndarray
            A 2D array of image data.

        wcs_fwd : function
            "forward" pixel-to-world transformation function.

        wcs_inv : function
            "inverse" world-to-pixel transformation function.

        pix_area : float, optional
            Average pixel's sky area.

        convf : float, optional
            Conversion factor that when multiplied to `image` data converts
            the data to "uniform" (across multiple images) surface
            brightness units.

            .. note::

              The functionality to support this conversion is not yet
              implemented and at this moment `convf` is ignored.

        mask : numpy.ndarray
            A 2D array that indicates
            what pixels in the input `image` should be used for sky
            computations (``1``) and which pixels should **not** be used
            for sky computations (``0``).

        id : anything
            The value of this parameter is simple stored within the `SkyImage`
            object. While it can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        skystat : callable, None, optional
            A callable object that takes a either a 2D image (2D
            `numpy.ndarray`) or a list of pixel values (a Nx1 array) and
            returns a tuple of two values: some statistics (e.g., mean,
            median, etc.) and number of pixels/values from the input image
            used in computing that statistics.

            When `skystat` is not set, `SkyImage` will use
            :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` object
            to perform sky statistics on image data.

        stepsize : int, None, optional
            Spacing between vertices of the image's bounding polygon. Default
            value of `None` creates bounding polygons with four vertices
            corresponding to the corners of the image.

        meta : dict, None, optional
            A dictionary of various items to be stored within the `SkyImage`
            object.

        """
        self.image = image
        self.convf = convf
        self.meta = meta
        self._id = id
        self._pix_area = pix_area

        # WCS
        self.wcs_fwd = wcs_fwd
        self.wcs_inv = wcs_inv

        # initial sky value:
        self._sky = 0.0
        self._sky_is_valid = False

        # check that mask has the same shape as image:
        if mask is None:
            self.mask = None

        else:
            if image is None:
                raise ValueError("'mask' must be None when 'image' is None")

            self.mask = np.asanyarray(mask, dtype=np.bool)

            if self.mask.shape != image.shape:
                raise ValueError("'mask' must have the same shape as 'image'.")

        # create spherical polygon bounding the image
        if image is None or wcs_fwd is None or wcs_inv is None:
            self._radec = [(np.array([]), np.array([]))]
            self._polygon = SphericalPolygon([])
            self._poly_area = 0.0

        else:
            self.calc_bounding_polygon(stepsize)

        # set sky statistics function (NOTE: it must return statistics and
        # the number of pixels used after clipping)
        if skystat is None:
            self.set_builtin_skystat()
        else:
            self.skystat = skystat

    @property
    def id(self):
        """ Set or get `SkyImage`'s `id`.

        While `id` can be of any type, it is prefereble that `id` be
        of a type with nice string representation.

        """
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def pix_area(self):
        """ Set or get mean pixel area.
        """
        return self._pix_area

    @pix_area.setter
    def pix_area(self, pix_area):
        self._pix_area = pix_area

    @property
    def poly_area(self):
        """ Get bounding polygon area in srad units.
        """
        return self._poly_area

    @property
    def sky(self):
        """ Sky background value. See `calc_sky` for more details.
        """
        return self._sky

    @sky.setter
    def sky(self, sky):
        self._sky = sky


    @property
    def is_sky_valid(self):
        """
        Indicates whether sky value was successfully computed.
        Must be set externally.
        """
        return self._sky_is_valid

    @is_sky_valid.setter
    def is_sky_valid(self, valid):
        self._sky_is_valid = valid

    @property
    def radec(self):
        """
        Get RA and DEC of the verteces of the bounding polygon as a
        `~numpy.ndarray` of shape (N, 2) where N is the number of verteces + 1.
        """
        return self._radec

    @property
    def polygon(self):
        """ Get image's bounding polygon.
        """
        return self._polygon

    def intersection(self, skyimage):
        """
        Compute intersection of this `SkyImage` object and another
        `SkyImage`, `SkyGroup`, or
        :py:class:`~spherical_geometry.polygon.SphericalPolygon`
        object.

        Parameters
        ----------
        skyimage : SkyImage, SkyGroup, SphericalPolygon
            Another object that should be intersected with this `SkyImage`.

        Returns
        -------
        polygon : SphericalPolygon
            A :py:class:`~spherical_geometry.polygon.SphericalPolygon` that is
            the intersection of this `SkyImage` and `skyimage`.

        """
        if isinstance(skyimage, (SkyImage, SkyGroup)):
            return self._polygon.intersection(skyimage.polygon)
        else:
            return self._polygon.intersection(skyimage)

    def calc_bounding_polygon(self, stepsize=None):
        """ Compute image's bounding polygon.

        Parameters
        ----------
        stepsize : int, None, optional
            Indicates the maximum separation between two adjacent vertices
            of the bounding polygon along each side of the image. Corners
            of the image are included automatically. If `stepsize` is `None`,
            bounding polygon will contain only vertices of the image.

        """
        ny, nx = self.image.shape

        if stepsize is None:
            nintx = 2
            ninty = 2
        else:
            nintx = max(2, int(np.ceil((nx + 1.0) / stepsize)))
            ninty = max(2, int(np.ceil((ny + 1.0) / stepsize)))

        xs = np.linspace(-0.5, nx - 0.5, nintx, dtype=np.float)
        ys = np.linspace(-0.5, ny - 0.5, ninty, dtype=np.float)[1:-1]
        nptx = xs.size
        npty = ys.size

        npts = 2 * (nptx + npty)

        borderx = np.empty((npts + 1,), dtype=np.float)
        bordery = np.empty((npts + 1,), dtype=np.float)

        # "bottom" points:
        borderx[:nptx] = xs
        bordery[:nptx] = -0.5
        # "right"
        sl = np.s_[nptx:nptx + npty]
        borderx[sl] = nx - 0.5
        bordery[sl] = ys
        # "top"
        sl = np.s_[nptx + npty:2 * nptx + npty]
        borderx[sl] = xs[::-1]
        bordery[sl] = ny - 0.5
        # "left"
        sl = np.s_[2 * nptx + npty:-1]
        borderx[sl] = -0.5
        bordery[sl] = ys[::-1]

        # close polygon:
        borderx[-1] = borderx[0]
        bordery[-1] = bordery[0]

        ra, dec = self.wcs_fwd(borderx, bordery, with_bounding_box=False)
        # TODO: for strange reasons, occasionally ra[0] != ra[-1] and/or
        #       dec[0] != dec[-1] (even though we close the polygon in the
        #       previous two lines). Then SphericalPolygon fails because
        #       points are not closed. Threfore we force it to be closed:
        ra[-1] = ra[0]
        dec[-1] = dec[0]

        self._radec = [(ra, dec)]
        self._polygon = SphericalPolygon.from_radec(ra, dec)
        self._poly_area = np.fabs(self._polygon.area())

    @property
    def skystat(self):
        """ Stores/retrieves a callable object that takes a either a 2D image
        (2D `numpy.ndarray`) or a list of pixel values (a Nx1 array) and
        returns a tuple of two values: some statistics
        (e.g., mean, median, etc.) and number of pixels/values from the input
        image used in computing that statistics.

        When `skystat` is not set, `SkyImage` will use
        :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` object
        to perform sky statistics on image data.

        """
        return self._skystat

    @skystat.setter
    def skystat(self, skystat):
        self._skystat = skystat

    def set_builtin_skystat(self, skystat='median', lower=None, upper=None,
                            nclip=5, lsigma=4.0, usigma=4.0, binwidth=0.1):
        """
        Replace already set `skystat` with a "built-in" version of a
        statistics callable object used to measure sky background.

        See :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` for the
        parameter description.

        """
        self._skystat = SkyStats(
            skystat=skystat,
            lower=lower,
            upper=upper,
            nclip=nclip,
            lsig=lsigma,
            usig=usigma,
            binwidth=binwidth
        )

    # TODO: due to a bug in the sphere package, see
    #       https://github.com/spacetelescope/sphere/issues/74
    #       intersections with polygons formed as union does not work.
    #       For this reason I re-implement 'calc_sky' below with
    #       a workaround for the bug.
    #       The original implementation (now called ``_calc_sky_orig``
    #       should replace current 'calc_sky' once the bug is fixed.
    #
    def calc_sky(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """
        if overlap is None:

            if self.mask is None:
                data = self.image
            else:
                data = self.image[self.mask]

            polyarea = self.poly_area

        else:
            fill_mask = np.zeros(self.image.shape, dtype=bool)

            if isinstance(overlap, SkyImage):
                intersection = self.intersection(overlap)
                polyarea = np.fabs(intersection.area())
                radec = list(intersection.to_radec())

            elif isinstance(overlap, SkyGroup):
                radec = []
                polyarea = 0.0
                for im in overlap:
                    intersection = self.intersection(im)
                    polyarea1 = np.fabs(intersection.area())
                    if polyarea1 == 0.0:
                        continue
                    polyarea += polyarea1
                    radec += list(intersection.to_radec())

            elif isinstance(overlap, SphericalPolygon):
                radec = []
                polyarea = 0.0
                for p in overlap._polygons:
                    intersection = self.intersection(SphericalPolygon([p]))
                    polyarea1 = np.fabs(intersection.area())
                    if polyarea1 == 0.0:
                        continue
                    polyarea += polyarea1
                    radec += list(intersection.to_radec())

            else: # assume a list of (ra, dec) tuples:
                radec = []
                polyarea = 0.0
                for r, d in overlap:
                    poly = SphericalPolygon.from_radec(r, d)
                    polyarea1 = np.fabs(poly.area())
                    if polyarea1 == 0.0 or len(r) < 4:
                        continue
                    polyarea += polyarea1
                    radec.append(self.intersection(poly).to_radec())

            if polyarea == 0.0:
                return (None, 0, 0.0)

            for ra, dec in radec:
                if len(ra) < 4:
                    continue

                # set pixels in 'fill_mask' that are inside a polygon to True:
                x, y = self.wcs_inv(ra, dec)
                poly_vert = list(zip(*[x, y]))

                polygon = region.Polygon(True, poly_vert)
                fill_mask = polygon.scan(fill_mask)

            if self.mask is not None:
                fill_mask &= self.mask

            data = self.image[fill_mask]

            if data.size < 1:
                return (None, 0, 0.0)

        # Calculate sky
        try:

            skyval, npix = self._skystat(data)

        except ValueError:

            return (None, 0, 0.0)

        if delta:
            skyval -= self._sky

        return skyval, npix, polyarea

    def _calc_sky_orig(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if overlap is None:

            if self.mask is None:
                data = self.image
            else:
                data = self.image[self.mask]

            polyarea = self.poly_area

        else:
            fill_mask = np.zeros(self.image.shape, dtype=bool)

            if isinstance(overlap, (SkyImage, SkyGroup, SphericalPolygon)):
                intersection = self.intersection(overlap)
                polyarea = np.fabs(intersection.area())
                radec = intersection.to_radec()

            else: # assume a list of (ra, dec) tuples:
                radec = []
                polyarea = 0.0
                for r, d in overlap:
                    poly = SphericalPolygon.from_radec(r, d)
                    polyarea1 = np.fabs(poly.area())
                    if polyarea1 == 0.0 or len(r) < 4:
                        continue
                    polyarea += polyarea1
                    radec.append(self.intersection(poly).to_radec())

            if polyarea == 0.0:
                return (None, 0, 0.0)

            for ra, dec in radec:
                if len(ra) < 4:
                    continue

                # set pixels in 'fill_mask' that are inside a polygon to True:
                x, y = self.wcs_inv(ra, dec)
                poly_vert = list(zip(*[x, y]))

                polygon = region.Polygon(True, poly_vert)
                fill_mask = polygon.scan(fill_mask)

            if self.mask is not None:
                fill_mask &= self.mask

            data = self.image[fill_mask]

            if data.size < 1:
                return (None, 0, 0.0)

        # Calculate sky
        try:

            skyval, npix = self._skystat(data)

        except ValueError:

            return (None, 0, 0.0)

        if delta:
            skyval -= self._sky

        return skyval, npix, polyarea

    def copy(self):
        """
        Return a shallow copy of the `SkyImage` object.
        """
        si = SkyImage(
            image=None,
            wcs_fwd=self.wcs_fwd,
            wcs_inv=self.wcs_inv,
            pix_area=self.pix_area,
            convf=self.convf,
            mask=None,
            id=self.id,
            stepsize=None,
            meta=self.meta
        )
        si.image = self.image
        si.mask = self.mask
        si._radec = self._radec
        si._polygon = self._polygon
        si._poly_area = self._poly_area
        si.sky = self.sky
        return si
Example #36
0
def _skycoord_to_spherical_polygon(skycoord):
    return SphericalPolygon.from_radec(skycoord.spherical.lon.degree,
                                       skycoord.spherical.lat.degree,
                                       degrees=True)
Example #37
0
    def _calc_sky_orig(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if overlap is None:

            if self.mask is None:
                data = self.image
            else:
                data = self.image[self.mask]

            polyarea = self.poly_area

        else:
            fill_mask = np.zeros(self.image.shape, dtype=bool)

            if isinstance(overlap, (SkyImage, SkyGroup, SphericalPolygon)):
                intersection = self.intersection(overlap)
                polyarea = np.fabs(intersection.area())
                radec = intersection.to_radec()

            else: # assume a list of (ra, dec) tuples:
                radec = []
                polyarea = 0.0
                for r, d in overlap:
                    poly = SphericalPolygon.from_radec(r, d)
                    polyarea1 = np.fabs(poly.area())
                    if polyarea1 == 0.0 or len(r) < 4:
                        continue
                    polyarea += polyarea1
                    radec.append(self.intersection(poly).to_radec())

            if polyarea == 0.0:
                return (None, 0, 0.0)

            for ra, dec in radec:
                if len(ra) < 4:
                    continue

                # set pixels in 'fill_mask' that are inside a polygon to True:
                x, y = self.wcs_inv(ra, dec)
                poly_vert = list(zip(*[x, y]))

                polygon = region.Polygon(True, poly_vert)
                fill_mask = polygon.scan(fill_mask)

            if self.mask is not None:
                fill_mask &= self.mask

            data = self.image[fill_mask]

            if data.size < 1:
                return (None, 0, 0.0)

        # Calculate sky
        try:

            skyval, npix = self._skystat(data)

        except ValueError:

            return (None, 0, 0.0)

        if delta:
            skyval -= self._sky

        return skyval, npix, polyarea
Example #38
0
    def __init__(self, ra, dec, inside=None, max_depth=10):
        ra = ra.to(u.rad).value
        dec = dec.to(u.rad).value
        # Check if the vertices form a closed polygon
        if ra[0] != ra[-1] or dec[0] != dec[-1]:
            # If not, append the first vertex to ``vertices``
            ra = np.append(ra, ra[0])
            dec = np.append(dec, dec[0])
            vertices = SkyCoord(ra=ra, dec=dec, unit="rad", frame="icrs")

        if inside:
            # Convert it to (x, y, z) cartesian coordinates on the sphere
            inside = (inside.icrs.ra.rad, inside.icrs.dec.rad)

        self.polygon = SphericalPolygon.from_lonlat(lon=ra, lat=dec, center=inside, degrees=False)

        start_depth, ipixels = self._get_starting_depth()
        end_depth = max_depth

        # When the start depth returned is > to the depth requested
        # For that specific case, we only do one iteration at start_depth
        # Thus the MOC will contain the partially intersecting cells with the
        # contained ones at start_depth

        # And we degrade the MOC to the max_depth
        self.degrade_to_max_depth = False
        if start_depth > end_depth:
            end_depth = start_depth
            self.degrade_to_max_depth = True

        self.ipix_d = {str(order): [] for order in range(start_depth, end_depth + 1)}

        ## Iterative version of the algorithm: seems a bit faster than the recursive one
        for depth in range(start_depth, end_depth + 1):
            # Define a HEALPix at the current depth
            hp = HEALPix(nside=(1 << depth), order='nested', frame=ICRS())

            # Get the lon and lat of the corners of the pixels
            # intersecting the polygon
            lon, lat = hp.boundaries_lonlat(ipixels, step=1)
            lon = lon.to(u.rad).value
            lat = lat.to(u.rad).value

            # closes the lon and lat array so that their first and last value matches
            lon = self._closes_numpy_2d_array(lon)
            lat = self._closes_numpy_2d_array(lat)

            num_ipix_inter_poly = ipixels.shape[0]

            # Define a 3d numpy array containing the corners coordinates of the intersecting pixels
            # The first dim is the num of ipixels
            # The second is the number of coordinates (5 as it defines the closed polygon of a HEALPix cell)
            # The last is of size 2 (lon and lat)
            shapes = np.vstack((lon.ravel(), lat.ravel())).T.reshape(num_ipix_inter_poly, 5, -1)

            ipix_in_polygon_l = []
            ipix_inter_polygon_l = []

            for i in range(num_ipix_inter_poly):
                shape = shapes[i]
                # Definition of a SphericalPolygon from the border coordinates of a HEALPix cell
                ipix_shape = SphericalPolygon.from_radec(lon=shape[:, 0], lat=shape[:, 1], degrees=False)
                ipix = ipixels[i]

                if self.polygon.intersects_poly(ipix_shape):
                    # If we are at the max depth then we direcly add to the MOC the intersecting ipixels
                    if depth == end_depth:
                        ipix_in_polygon_l.append(ipix)
                    else:
                        # Check whether polygon contains ipix or not
                        if self.polygon_contains_ipix(ipix_shape):
                            ipix_in_polygon_l.append(ipix)
                        else:
                            # The ipix is just intersecting without being contained in the polygon
                            # We split it in its 4 children
                            child_ipix = ipix << 2
                            ipix_inter_polygon_l.extend([child_ipix,
                                                        child_ipix + 1,
                                                        child_ipix + 2,
                                                        child_ipix + 3])

            self.ipix_d.update({str(depth): ipix_in_polygon_l})
            ipixels = np.asarray(ipix_inter_polygon_l)
Example #39
0
    def calc_bounding_polygon(self, stepsize=None):
        """ Compute image's bounding polygon.

        Parameters
        ----------
        stepsize : int, None, optional
            Indicates the maximum separation between two adjacent vertices
            of the bounding polygon along each side of the image. Corners
            of the image are included automatically. If `stepsize` is `None`,
            bounding polygon will contain only vertices of the image.

        """
        ny, nx = self.image.shape

        if stepsize is None:
            nintx = 2
            ninty = 2
        else:
            nintx = max(2, int(np.ceil((nx + 1.0) / stepsize)))
            ninty = max(2, int(np.ceil((ny + 1.0) / stepsize)))

        xs = np.linspace(-0.5, nx - 0.5, nintx, dtype=np.float)
        ys = np.linspace(-0.5, ny - 0.5, ninty, dtype=np.float)[1:-1]
        nptx = xs.size
        npty = ys.size

        npts = 2 * (nptx + npty)

        borderx = np.empty((npts + 1,), dtype=np.float)
        bordery = np.empty((npts + 1,), dtype=np.float)

        # "bottom" points:
        borderx[:nptx] = xs
        bordery[:nptx] = -0.5
        # "right"
        sl = np.s_[nptx:nptx + npty]
        borderx[sl] = nx - 0.5
        bordery[sl] = ys
        # "top"
        sl = np.s_[nptx + npty:2 * nptx + npty]
        borderx[sl] = xs[::-1]
        bordery[sl] = ny - 0.5
        # "left"
        sl = np.s_[2 * nptx + npty:-1]
        borderx[sl] = -0.5
        bordery[sl] = ys[::-1]

        # close polygon:
        borderx[-1] = borderx[0]
        bordery[-1] = bordery[0]

        ra, dec = self.wcs_fwd(borderx, bordery, with_bounding_box=False)
        # TODO: for strange reasons, occasionally ra[0] != ra[-1] and/or
        #       dec[0] != dec[-1] (even though we close the polygon in the
        #       previous two lines). Then SphericalPolygon fails because
        #       points are not closed. Threfore we force it to be closed:
        ra[-1] = ra[0]
        dec[-1] = dec[0]

        self._radec = [(ra, dec)]
        self._polygon = SphericalPolygon.from_radec(ra, dec)
        self._poly_area = np.fabs(self._polygon.area())
Example #40
0
    def __init__(self, image, ext, dq_bits=0, dqimage=None, dqext=None,
                 usermask=None, usermask_ext=None):
        """
        Parameters
        ----------
        image : ImageRef
            An :py:class:`~stsci.skypac.utils.ImageRef` object that refers
            to an open FITS file

        ext : tuple, int, str
            Extension specification in the `image` the `SkyLineMember`
            object will be associated with.

            An int `ext` specifies extension number. A tuple in the form
            (str, int) specifies extension name and number. A string `ext`
            specifies extension name and the extension version is assumed
            to be 1. See documentation for `astropy.io.fits.getData`
            for examples.

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

            | Default value (0) will make *all* non-zero
              pixels in the DQ mask to be considered "bad" pixels,
              and the corresponding image pixels will not be used
              for sky computations.

            | Set `dq_bits` to `None` to turn off the use of
              image's DQ array for sky computations.

            .. note::
                DQ masks (if used), *will be* combined with user masks
                specified by the `usermask` parameter.

        dqimage : ImageRef
            An :py:class:`~stsci.skypac.utils.ImageRef` object that refers
            to an open FITS file that has DQ data of the input `image`.

            .. note::
               When DQ data are located in the same FITS file as the
               science image data (e.g., HST/ACS, HST/WFC3, etc.),
               `dqimage` may point to the
               same :py:class:`~stsci.skypac.utils.ImageRef` object.
               In this case the reference count of the
               \ :py:class:`~stsci.skypac.utils.ImageRef` object must be
               increased adequately.

        dqext : tuple, int, str
            Extension specification of the `dqimage` that contains
            `image`'s DQ information. See help for `ext` for more
            details on acceptable formats for this parameter.

        usermask : ImageRef
            An :py:class:`~stsci.skypac.utils.ImageRef` object that refers
            to an open FITS file that has user mask data that indicate
            what pixels in the input `image` should be used for sky
            computations (``1``) and which pixels should **not** be used
            for sky computations (``0``).

        usermask_ext : tuple, int, str
            Extension specification of the `usermask` mask file that
            contains user's mask data that should be associated with
            the input `image` and `ext`. See help for `ext` for more
            details on acceptable formats for this parameter.

        """
        assert(hasattr(self.__class__, '_initialized') and \
               self.__class__._initialized)
        self._reset()

        # check that input images and extensions are valid --
        # either integers or tuples of strings and integers, e.g., ('sci',1):
        _check_valid_imgext(image, 'image', ext, 'ext', can_img_be_None=False)
        if dq_bits is not None:
            if dqimage is None:
                dq_bits = 0
            else:
                _check_valid_imgext(dqimage, 'dqimage', dqext, 'dqext')
        _check_valid_imgext(usermask, 'usermask', usermask_ext,'usermask_ext')

        # get telescope, instrument, and detector info:
        self.telescope, self.instrument, self.detector = get_instrument_info(
            image, ext)

        # check dq_bits:
        if dq_bits is not None and not isinstance(dq_bits,int):
            if image:  dqimage.release()
            if usermask: usermask.release()
            if dqimage:  dqimage.release()
            raise TypeError("Argument 'dq_bits' must be either an integer or None.")

        # buld mask:
        self._buildMask(image.original_fname, ext, dq_bits,
                        dqimage, dqext, usermask, usermask_ext)
        if dqimage:  dqimage.release()
        if usermask: usermask.release()

        # save file, user mask, and DQ extension info:
        self._fname          = image.original_fname
        self._basefname      = basename(self._fname)
        self._image          = image
        self._ext            = ext
        self._can_free_image = image.can_reload_data and self.optimize != 'speed'

        # check extension and create a string representation:
        try:
            extstr = ext2str(ext)
        except ValueError:
            raise ValueError("Unexpected extension type \'{}\' for file {}.".\
                             format(ext,self._basefname))

        self._id = "{:s}[{:s}]".format(self._basefname, extstr)

        # extract WCS for bounding-box computation
        try:
            if hasattr(image.hdu[ext], 'wcs'):
                self._wcs = image.hdu[ext].wcs
            else:
                if self.telescope in supported_telescopes:
                    self._wcs = wcsutil.HSTWCS(image.hdu, ext)
                else:
                    self._wcs = pywcs.WCS(image.hdu[ext].header, image.hdu)
            if self._wcs is None:
                raise Exception("Invalid WCS.")
        except:
            msg = "Unable to obtain WCS information for the file {:s}." \
                .format(self._id)
            self._ml.error(msg)
            self._ml.flush()
            self._release_all()
            raise

        # determine pixel scale:
        self._get_pixel_scale()

        # see if image data are in counts or count-rate
        # and compute count(-rate) to flux (per arcsec^2) conversion factor:
        self._brightness_conv_from_hdu(image.hdu, self._idcscale)

        # process Sky user's keyword and its value:
        self._init_skyuser(image.hdu[ext].header)

        # Set polygon to be the bounding box of the chip:
        self._polygon = SphericalPolygon.from_wcs(self.wcs, steps=1)
Example #41
0
class SkyGroup:
    """
    Holds multiple :py:class:`SkyImage` objects whose sky background values
    must be adjusted together.

    `SkyGroup` provides methods for obtaining bounding polygon of the group
    of :py:class:`SkyImage` objects and to compute sky value of the group.

    """
    def __init__(self, images, id=None, sky=0.0):

        if isinstance(images, SkyImage):
            self._images = [images]

        elif hasattr(images, '__iter__'):
            self._images = []
            for im in images:
                if not isinstance(im, SkyImage):
                    raise TypeError("Each element of the 'images' parameter "
                                    "must be an 'SkyImage' object.")
                self._images.append(im)

        else:
            raise TypeError(
                "Parameter 'images' must be either a single "
                "'SkyImage' object or a list of 'SkyImage' objects")

        self._id = id
        self._update_bounding_polygon()
        self._sky = sky
        for im in self._images:
            im.sky += sky

    @property
    def id(self):
        """ Set or get `SkyImage`'s `id`.

            While `id` can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        """
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def sky(self):
        """ Sky background value. See `calc_sky` for more details.
        """
        return self._sky

    @sky.setter
    def sky(self, sky):
        delta_sky = sky - self._sky
        self._sky = sky
        for im in self._images:
            im.sky += delta_sky

    @property
    def radec(self):
        """
        Get RA and DEC of the verteces of the bounding polygon as a
        `~numpy.ndarray` of shape (N, 2) where N is the number of verteces + 1.

        """
        return self._radec

    @property
    def polygon(self):
        """ Get image's bounding polygon.
        """
        return self._polygon

    def intersection(self, skyimage):
        """
        Compute intersection of this `SkyImage` object and another
        `SkyImage`, `SkyGroup`, or
        :py:class:`~spherical_geometry.polygon.SphericalPolygon`
        object.

        Parameters
        ----------
        skyimage : SkyImage, SkyGroup, SphericalPolygon
            Another object that should be intersected with this `SkyImage`.

        Returns
        -------
        polygon : SphericalPolygon
            A :py:class:`~spherical_geometry.polygon.SphericalPolygon` that is
            the intersection of this `SkyImage` and `skyimage`.

        """
        if isinstance(skyimage, (SkyImage, SkyGroup)):
            return self._polygon.intersection(skyimage.polygon)
        else:
            return self._polygon.intersection(skyimage)

    def _update_bounding_polygon(self):
        polygons = [im.polygon for im in self._images]
        if len(polygons) == 0:
            self._polygon = SphericalPolygon([])
            self._radec = []
        else:
            self._polygon = SphericalPolygon.multi_union(polygons)
            self._radec = list(self._polygon.to_radec())

    def __len__(self):
        return len(self._images)

    def __getitem__(self, idx):
        return self._images[idx]

    def __setitem__(self, idx, value):
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images[idx] = value
        self._update_bounding_polygon()

    def __delitem__(self, idx):
        del self._images[idx]
        if len(self._images) == 0:
            self._sky = 0.0
            self._id = None
        self._update_bounding_polygon()

    def __iter__(self):
        for image in self._images:
            yield image

    def insert(self, idx, value):
        """Inserts a `SkyImage` into the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.insert(idx, value)
        self._update_bounding_polygon()

    def append(self, value):
        """Appends a `SkyImage` to the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.append(value)
        self._update_bounding_polygon()

    def calc_sky(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if len(self._images) == 0:
            return (None, 0, 0.0)

        wght = 0
        area = 0.0

        if overlap is None:
            # compute minimum sky across all images in the group:
            wsky = None

            for image in self._images:
                # make sure all images have the same background:
                image.background = self._sky

                sky, npix, imarea = image.calc_sky(overlap=None, delta=delta)

                if sky is None:
                    continue

                if wsky is None or wsky > sky:
                    wsky = sky
                    wght = npix
                    area = imarea

            return (wsky, wght, area)

        ################################################
        ##  compute weighted sky in various overlaps: ##
        ################################################
        wsky = 0.0

        for image in self._images:
            # make sure all images have the same background:
            image.background = self._sky

            sky, npix, area1 = image.calc_sky(overlap=overlap, delta=delta)

            area += area1

            if sky is not None and npix > 0:
                pix_area = npix * image.pix_area
                wsky += sky * pix_area
                wght += pix_area

        if wght == 0.0 or area == 0.0:
            return (None, wght, area)
        else:
            return (wsky / wght, wght, area)
Example #42
0
class SkyGroup:
    """
    Holds multiple :py:class:`SkyImage` objects whose sky background values
    must be adjusted together.

    `SkyGroup` provides methods for obtaining bounding polygon of the group
    of :py:class:`SkyImage` objects and to compute sky value of the group.

    """
    def __init__(self, images, id=None, sky=0.0):

        if isinstance(images, SkyImage):
            self._images = [images]

        elif hasattr(images, '__iter__'):
            self._images = []
            for im in images:
                if not isinstance(im, SkyImage):
                    raise TypeError("Each element of the 'images' parameter "
                                    "must be an 'SkyImage' object.")
                self._images.append(im)

        else:
            raise TypeError("Parameter 'images' must be either a single "
                            "'SkyImage' object or a list of 'SkyImage' objects")

        self._id = id
        self._update_bounding_polygon()
        self._sky = sky
        for im in self._images:
            im.sky += sky

    @property
    def id(self):
        """ Set or get `SkyImage`'s `id`.

            While `id` can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        """
        return self._id

    @id.setter
    def id(self, id):
        self._id = id

    @property
    def sky(self):
        """ Sky background value. See `calc_sky` for more details.
        """
        return self._sky

    @sky.setter
    def sky(self, sky):
        delta_sky = sky - self._sky
        self._sky = sky
        for im in self._images:
            im.sky += delta_sky

    @property
    def radec(self):
        """
        Get RA and DEC of the verteces of the bounding polygon as a
        `~numpy.ndarray` of shape (N, 2) where N is the number of verteces + 1.

        """
        return self._radec

    @property
    def polygon(self):
        """ Get image's bounding polygon.
        """
        return self._polygon

    def intersection(self, skyimage):
        """
        Compute intersection of this `SkyImage` object and another
        `SkyImage`, `SkyGroup`, or
        :py:class:`~spherical_geometry.polygon.SphericalPolygon`
        object.

        Parameters
        ----------
        skyimage : SkyImage, SkyGroup, SphericalPolygon
            Another object that should be intersected with this `SkyImage`.

        Returns
        -------
        polygon : SphericalPolygon
            A :py:class:`~spherical_geometry.polygon.SphericalPolygon` that is
            the intersection of this `SkyImage` and `skyimage`.

        """
        if isinstance(skyimage, (SkyImage, SkyGroup)):
            return self._polygon.intersection(skyimage.polygon)
        else:
            return self._polygon.intersection(skyimage)

    def _update_bounding_polygon(self):
        polygons = [im.polygon for im in self._images]
        if len(polygons) == 0:
            self._polygon = SphericalPolygon([])
            self._radec = []
        else:
            self._polygon = SphericalPolygon.multi_union(polygons)
            self._radec = list(self._polygon.to_radec())

    def __len__(self):
        return len(self._images)

    def __getitem__(self, idx):
        return self._images[idx]

    def __setitem__(self, idx, value):
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images[idx] = value
        self._update_bounding_polygon()

    def __delitem__(self, idx):
        del self._images[idx]
        if len(self._images) == 0:
            self._sky = 0.0
            self._id = None
        self._update_bounding_polygon()

    def __iter__(self):
        for image in self._images:
            yield image

    def insert(self, idx, value):
        """Inserts a `SkyImage` into the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.insert(idx, value)
        self._update_bounding_polygon()

    def append(self, value):
        """Appends a `SkyImage` to the group.
        """
        if not isinstance(value, SkyImage):
            raise TypeError("Item must be of 'SkyImage' type")
        value.sky += self._sky
        self._images.append(value)
        self._update_bounding_polygon()

    def calc_sky(self, overlap=None, delta=True):
        """
        Compute sky background value.

        Parameters
        ----------
        overlap : SkyImage, SkyGroup, SphericalPolygon, list of tuples, \
None, optional
            Another `SkyImage`, `SkyGroup`,
            :py:class:`spherical_geometry.polygons.SphericalPolygon`, or
            a list of tuples of (RA, DEC) of vertices of a spherical
            polygon. This parameter is used to indicate that sky statistics
            should computed only in the region of intersection of *this*
            image with the polygon indicated by `overlap`. When `overlap` is
            `None`, sky statistics will be computed over the entire image.

        delta : bool, optional
            Should this function return absolute sky value or the difference
            between the computed value and the value of the sky stored in the
            `sky` property.

        Returns
        -------
        skyval : float, None
            Computed sky value (absolute or relative to the `sky` attribute).
            If there are no valid data to perform this computations (e.g.,
            because this image does not overlap with the image indicated by
            `overlap`), `skyval` will be set to `None`.

        npix : int
            Number of pixels used to compute sky statistics.

        polyarea : float
            Area (in srad) of the polygon that bounds data used to compute
            sky statistics.

        """

        if len(self._images) == 0:
            return (None, 0, 0.0)

        wght = 0
        area = 0.0

        if overlap is None:
            # compute minimum sky across all images in the group:
            wsky = None

            for image in self._images:
                # make sure all images have the same background:
                image.background = self._sky

                sky, npix, imarea = image.calc_sky(overlap=None, delta=delta)

                if sky is None:
                    continue

                if wsky is None or wsky > sky:
                    wsky = sky
                    wght = npix
                    area = imarea

            return (wsky, wght, area)

        ################################################
        ##  compute weighted sky in various overlaps: ##
        ################################################
        wsky = 0.0

        for image in self._images:
            # make sure all images have the same background:
            image.background = self._sky

            sky, npix, area1 = image.calc_sky(overlap=overlap, delta=delta)

            area += area1

            if sky is not None and npix > 0:
                pix_area = npix * image.pix_area
                wsky += sky * pix_area
                wght += pix_area

        if wght == 0.0 or area == 0.0:
            return (None, wght, area)
        else:
            return (wsky / wght, wght, area)
Example #43
0
    def __init__(self, image, wcs_fwd, wcs_inv, pix_area=1.0, convf=1.0,
                 mask=None, id=None, skystat=None, stepsize=None, meta=None):
        """ Initializes the SkyImage object.

        Parameters
        ----------
        image : numpy.ndarray
            A 2D array of image data.

        wcs_fwd : function
            "forward" pixel-to-world transformation function.

        wcs_inv : function
            "inverse" world-to-pixel transformation function.

        pix_area : float, optional
            Average pixel's sky area.

        convf : float, optional
            Conversion factor that when multiplied to `image` data converts
            the data to "uniform" (across multiple images) surface
            brightness units.

            .. note::

              The functionality to support this conversion is not yet
              implemented and at this moment `convf` is ignored.

        mask : numpy.ndarray
            A 2D array that indicates
            what pixels in the input `image` should be used for sky
            computations (``1``) and which pixels should **not** be used
            for sky computations (``0``).

        id : anything
            The value of this parameter is simple stored within the `SkyImage`
            object. While it can be of any type, it is prefereble that `id` be
            of a type with nice string representation.

        skystat : callable, None, optional
            A callable object that takes a either a 2D image (2D
            `numpy.ndarray`) or a list of pixel values (a Nx1 array) and
            returns a tuple of two values: some statistics (e.g., mean,
            median, etc.) and number of pixels/values from the input image
            used in computing that statistics.

            When `skystat` is not set, `SkyImage` will use
            :py:class:`~jwst_pipeline.skymatch.skystatistics.SkyStats` object
            to perform sky statistics on image data.

        stepsize : int, None, optional
            Spacing between vertices of the image's bounding polygon. Default
            value of `None` creates bounding polygons with four vertices
            corresponding to the corners of the image.

        meta : dict, None, optional
            A dictionary of various items to be stored within the `SkyImage`
            object.

        """
        self.image = image
        self.convf = convf
        self.meta = meta
        self._id = id
        self._pix_area = pix_area

        # WCS
        self.wcs_fwd = wcs_fwd
        self.wcs_inv = wcs_inv

        # initial sky value:
        self._sky = 0.0
        self._sky_is_valid = False

        # check that mask has the same shape as image:
        if mask is None:
            self.mask = None

        else:
            if image is None:
                raise ValueError("'mask' must be None when 'image' is None")

            self.mask = np.asanyarray(mask, dtype=np.bool)

            if self.mask.shape != image.shape:
                raise ValueError("'mask' must have the same shape as 'image'.")

        # create spherical polygon bounding the image
        if image is None or wcs_fwd is None or wcs_inv is None:
            self._radec = [(np.array([]), np.array([]))]
            self._polygon = SphericalPolygon([])
            self._poly_area = 0.0

        else:
            self.calc_bounding_polygon(stepsize)

        # set sky statistics function (NOTE: it must return statistics and
        # the number of pixels used after clipping)
        if skystat is None:
            self.set_builtin_skystat()
        else:
            self.skystat = skystat