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)
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
def test_geos1(): """do second test posted to bug report""" n_fill = 10 theta1_high_fill = np.full(n_fill, 5.) theta1_low_fill = np.full(n_fill, -65.) phi1_high_fill = np.linspace(50. - 360., 50. - 1., n_fill) phi1_low_fill = phi1_high_fill[::-1] theta1s = np.hstack( [theta1_high_fill, theta1_low_fill, theta1_high_fill[0]]) phi1s = np.hstack([phi1_high_fill, phi1_low_fill, phi1_high_fill[0]]) phi_in1 = 0. theta_in1 = 70. bounding_xyz1 = np.array(sgv.radec_to_vector(phi1s, theta1s, degrees=True)).T inside_xyz1 = np.array( sgv.radec_to_vector(phi_in1, theta_in1, degrees=True)) sp_poly1 = SphericalPolygon(bounding_xyz1, inside=inside_xyz1) theta2_high_fill = np.full(n_fill, -30.) theta2_low_fill = np.full(n_fill, -85.) phi2_high_fill = np.linspace(65 - 360., 55. - 10., n_fill) phi2_low_fill = np.linspace(5 - 360., 5. - 10., n_fill)[::-1] theta2s = np.hstack( [theta2_high_fill, theta2_low_fill, theta2_high_fill[0]]) phi2s = np.hstack([phi2_high_fill, phi2_low_fill, phi2_high_fill[0]]) phi_in2 = 0. theta_in2 = 70. bounding_xyz2 = np.array(sgv.radec_to_vector(phi2s, theta2s, degrees=True)).T inside_xyz2 = np.array( sgv.radec_to_vector(phi_in2, theta_in2, degrees=True)) sp_poly2 = SphericalPolygon(bounding_xyz2, inside=inside_xyz2) int_poly = sp_poly2.intersection(sp_poly1) theta_out = -50. * np.pi / 180. phi_out = 0. outside_xyz = np.array( sgv.radec_to_vector(phi_out, theta_out, degrees=False)) theta_in3 = 70. * np.pi / 180. phi_in3 = 0. inside_xyz3 = np.array( sgv.radec_to_vector(phi_in3, theta_in3, degrees=False)) print("area int, area 1,area 2: ", int_poly.area(), sp_poly1.area(), sp_poly2.area()) print("Should be True , True : ", int_poly.area() <= sp_poly1.area(), int_poly.area() <= sp_poly2.area()) assert int_poly.area() <= sp_poly1.area() assert int_poly.area() <= sp_poly2.area() assert not int_poly.contains_point(outside_xyz) assert not sp_poly1.contains_point(outside_xyz) assert not sp_poly2.contains_point(outside_xyz) print("Should be True ,True ,True : ", int_poly.contains_point(inside_xyz1), sp_poly1.contains_point(inside_xyz1), sp_poly2.contains_point(inside_xyz1)) assert int_poly.contains_point(inside_xyz1) assert sp_poly1.contains_point(inside_xyz1) assert sp_poly2.contains_point(inside_xyz1) print("Should be True ,True ,True : ", int_poly.contains_point(inside_xyz2), sp_poly1.contains_point(inside_xyz2), sp_poly2.contains_point(inside_xyz2)) assert int_poly.contains_point(inside_xyz2) assert sp_poly1.contains_point(inside_xyz2) assert sp_poly2.contains_point(inside_xyz2) print("Should be True ,True ,True : ", int_poly.contains_point(inside_xyz3), sp_poly1.contains_point(inside_xyz3), sp_poly2.contains_point(inside_xyz3)) assert int_poly.contains_point(inside_xyz3) assert sp_poly1.contains_point(inside_xyz3) assert sp_poly2.contains_point(inside_xyz3) inside_res = list(int_poly.inside) for itr in range(0, len(inside_res)): print("Should be True ,True ,True : ", int_poly.contains_point(inside_res[itr]), sp_poly1.contains_point(inside_res[itr]), sp_poly2.contains_point(inside_res[itr])) assert int_poly.contains_point(inside_res[itr]) assert sp_poly1.contains_point(inside_res[itr]) assert sp_poly2.contains_point(inside_res[itr])
def analyze_protein(protein): prot = protein[0] chain = protein[1] print("Downloading {}{}.".format(prot, chain)) cmd = "wget https://files.rcsb.org/download/{}.pdb".format(prot) proc = Popen(shlex.split(cmd), stdout=DEVNULL, stderr=STDOUT) proc.communicate() print("Extracting coordinates.") cmd = "./pdb_to_xyz.R {}.pdb --chain-id={} -o {}.txt".format( prot, chain, prot) proc = Popen(shlex.split(cmd), stdout=DEVNULL, stderr=STDOUT) proc.communicate() print("Removing pdb file.") proc = Popen(["rm", "{}.pdb".format(prot)], stdout=DEVNULL, stderr=STDOUT) proc.communicate() print("Analyzing protein") for proj in projections: cmd = "./polynomial_invariant {}.txt --names-db=internal --arrow --nb-projections={} --output-diagram={}{}_gl_{}.txt".format( prot, proj, prot, chain, proj) proc = Popen(shlex.split(cmd), stdout=DEVNULL, stderr=STDOUT) proc.communicate() name = "{}{}_gl_{}.txt".format(prot, chain, proj) origins, origin_types = [], [] file = open(name, "r") next(file) me = 0 checked = [] vertex_to_index_list = dict() edges_to_add = [] for l in file: line = l.strip('\n').split('\t') if line[3] != 'UNKNOWN': if '*' not in line[3]: origin_types.append(line[3]) origins.append( [float(line[0]), float(line[1]), float(line[2])]) if line[3] not in checked: vertex_to_index_list[repr(line[3])] = me me += 1 origins = numpy.array(origins) voro = SphericalVoronoi(origins) voro.sort_vertices_of_regions() for i in range(len(voro.regions)): for j in range(i + 1, len(voro.regions)): intersect = list(set(voro.regions[i]) & set(voro.regions[j])) if intersect: permissable = set( [1, len(voro.regions[i]), len(voro.regions[j])]) index_A = [voro.regions[i].index(x) for x in intersect] diff_A = [ abs(j - i) for i, j in zip(index_A[:-1], index_A[1:]) ] index_B = [voro.regions[j].index(x) for x in intersect] diff_B = [ abs(j - i) for i, j in zip(index_B[:-1], index_B[1:]) ] # print ("intersect",intersect,"voro[i]",voro.regions[i],"voro[j]",voro.regions[j]) # print ("index_A",index_A,"diff_A",diff_A,"index_B",index_B,"diff_B",diff_B) # print ("perm_A",permissable & set(diff_A),"perm_B", permissable & set(diff_B)) if permissable & set(diff_A) and permissable & set(diff_B): for k in range(int(len(intersect) / 2)): edges_to_add.append( (vertex_to_index_list[repr(origin_types[i])], vertex_to_index_list[repr(origin_types[j])])) # Comment lines 155 - 165 and uncomment lines 169-170 to go to previous version. # for k in range(int(len(intersect)/2)): # edges_to_add.append((vertex_to_index_list[repr(origin_types[i])],vertex_to_index_list[repr(origin_types[j])])) # print ("len",len(voro.vertices)) print("Creating graph.") g = igraph.Graph(len(origin_types), directed=False) origin_types = [ x.split("|")[0].strip('m').strip('s') for x in origin_types ] g.vs["label"] = [x for x in origin_types] g.add_edges(edges_to_add) all_connections = [] for edge in g.es: src = g.vs[edge.source]['label'] tgt = g.vs[edge.target]['label'] if (src == '3_1' and tgt != '3_1'): all_connections.append((src, tgt)) elif (src != '3_1' and tgt == '3_1'): all_connections.append((tgt, src)) proj_errors = 0 error_pairs = [] for pair in all_connections: if theoretical_values[pair[0]][pair[1]] != 1: proj_errors += 1 error_pairs.append(pair) try: errors = round(proj_errors / len(all_connections), 4) except ZeroDivisionError: errors = 'NaN' edges_between = [] for edge in g.es: src = g.vs[edge.source]['label'] tgt = g.vs[edge.target]['label'] if (src == '3_1' and tgt == '2_1'): edges_between.append((src, tgt)) elif (src == '2_1' and tgt == '3_1'): edges_between.append((tgt, src)) if len(edges_between) > 0: try: interface = round(len(edges_between) / len(all_connections), 4) except ZeroDivisionError: interface = 'NaN' else: interface = 'NaN' area_31, area_21 = [], [] all_area_31, all_area_21 = [], [] # voro_areas = voro.calculate_areas() print("voro_areas: ", voro_areas) for i in range(len(voro.regions)): voronoi_cell = numpy.asarray( [list(voro.vertices[x]) for x in voro.regions[i]]) if origin_types[i] == '3_1': sph_pol = SphericalPolygon(voronoi_cell) area_31.append(sph_pol.area()) elif origin_types[i] == '2_1': sph_pol = SphericalPolygon(voronoi_cell) area_21.append(sph_pol.area()) all_area_31 = numpy.sum(area_31) all_area_21 = numpy.sum(area_21) all_graphs.append({ 'n': str(proj), 'Error': errors, 'Interface': interface, 'Protein': prot + chain, 'Spectrum': len(set(origin_types)), 'Error Pairs': set(error_pairs), 'Full Spectrum': set(origin_types), 'Area 3_1': all_area_31, 'Area 2_1': all_area_21, 'Area 2_1/3_1': all_area_21 / all_area_31 })
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