def get_contour_mask(doselut, dosegridpoints, contour): """Get the mask for the contour with respect to the dose plane.""" if matplotlib.__version__ > '1.2': grid = mpl_Path(contour).contains_points(dosegridpoints) else: grid = nx.points_inside_poly(dosegridpoints, contour) grid = grid.reshape((len(doselut[1]), len(doselut[0]))) return grid
def polygonsOverlap(poly1, poly2): """Determine if two polygons intersect; can fail for pointy polygons. Accepts two polygons, as lists of vertices (x,y) pairs. If given `ShapeStim`-based instances, will use rendered (vertices + pos) as the polygon. Checks if any vertex of one polygon is inside the other polygon; will fail in some cases, especially for pointy polygons. "crossed-swords" configurations overlap but may not be detected by the algorithm. Used by :class:`~psychopy.visual.ShapeStim` `.overlaps()` """ try: #do this using try:...except rather than hasattr() for speed poly1 = poly1.verticesPix #we want to access this only once except: pass try: #do this using try:...except rather than hasattr() for speed poly2 = poly2.verticesPix #we want to access this only once except: pass # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > '1.2': if any(mpl_Path(poly1).contains_points(poly2)): return True return any(mpl_Path(poly2).contains_points(poly1)) else: try: # deprecated in matplotlib 1.2 if any(nxutils.points_inside_poly(poly1, poly2)): return True return any(nxutils.points_inside_poly(poly2, poly1)) except: pass # fall through to pure python: for p1 in poly1: if pointInPolygon(p1[0], p1[1], poly2): return True for p2 in poly2: if pointInPolygon(p2[0], p2[1], poly1): return True return False
def polygonsOverlap(poly1, poly2): """Determine if two polygons intersect; can fail for pointy polygons. Accepts two polygons, as lists of vertices (x,y) pairs. If given `ShapeStim`-based instances, will use rendered (vertices + pos) as the polygon. Checks if any vertex of one polygon is inside the other polygon; will fail in some cases, especially for pointy polygons. "crossed-swords" configurations overlap but may not be detected by the algorithm. Used by :class:`~psychopy.visual.ShapeStim` `.overlaps()` """ try: # do this using try:...except rather than hasattr() for speed poly1 = poly1.verticesPix # we want to access this only once except: pass try: # do this using try:...except rather than hasattr() for speed poly2 = poly2.verticesPix # we want to access this only once except: pass # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > "1.2": if any(mpl_Path(poly1).contains_points(poly2)): return True return any(mpl_Path(poly2).contains_points(poly1)) else: try: # deprecated in matplotlib 1.2 if any(nxutils.points_inside_poly(poly1, poly2)): return True return any(nxutils.points_inside_poly(poly2, poly1)) except: pass # fall through to pure python: for p1 in poly1: if pointInPolygon(p1[0], p1[1], poly2): return True for p2 in poly2: if pointInPolygon(p2[0], p2[1], poly1): return True return False
def polygonsOverlap(poly1, poly2): """Determine if two polygons intersect; can fail for very pointy polygons. Accepts two polygons, as lists of vertices (x,y) pairs. If given an object with with (vertices + pos), will try to use that as the polygon. Checks if any vertex of one polygon is inside the other polygon. Same as the `.overlaps()` method elsewhere. """ try: #do this using try:...except rather than hasattr() for speed poly1 = poly1.verticesPix #we want to access this only once except: pass try: #do this using try:...except rather than hasattr() for speed poly2 = poly2.verticesPix #we want to access this only once except: pass # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > '1.2': if any(mpl_Path(poly1).contains_points(poly2)): return True return any(mpl_Path(poly2).contains_points(poly1)) else: try: # deprecated in matplotlib 1.2 if any(nxutils.points_inside_poly(poly1, poly2)): return True return any(nxutils.points_inside_poly(poly2, poly1)) except: pass # fall through to pure python: for p1 in poly1: if pointInPolygon(p1[0], p1[1], poly2): return True for p2 in poly2: if pointInPolygon(p2[0], p2[1], poly1): return True return False
def polygonsOverlap(poly1, poly2): """Determine if two polygons intersect; can fail for very pointy polygons. Accepts two polygons, as lists of vertices (x,y) pairs. If given an object with with (vertices + pos), will try to use that as the polygon. Checks if any vertex of one polygon is inside the other polygon. Same as the `.overlaps()` method elsewhere. """ try: # do this using try:...except rather than hasattr() for speed poly1 = poly1.verticesPix # we want to access this only once except Exception: pass try: # do this using try:...except rather than hasattr() for speed poly2 = poly2.verticesPix # we want to access this only once except Exception: pass # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > '1.2': if any(mpl_Path(poly1).contains_points(poly2)): return True return any(mpl_Path(poly2).contains_points(poly1)) else: try: # deprecated in matplotlib 1.2 if any(nxutils.points_inside_poly(poly1, poly2)): return True return any(nxutils.points_inside_poly(poly2, poly1)) except Exception: pass # fall through to pure python: for p1 in poly1: if pointInPolygon(p1[0], p1[1], poly2): return True for p2 in poly2: if pointInPolygon(p2[0], p2[1], poly1): return True return False
def pointInPolygon(x, y, poly): """Determine if a point is inside a polygon; returns True if inside. (`x`, `y`) is the point to test. `poly` is a list of 3 or more vertices as (x,y) pairs. If given an object, such as a `ShapeStim`, will try to use its vertices and position as the polygon. Same as the `.contains()` method elsewhere. """ try: #do this using try:...except rather than hasattr() for speed poly = poly.verticesPix #we want to access this only once except: pass nVert = len(poly) if nVert < 3: msg = 'pointInPolygon expects a polygon with 3 or more vertices' logging.warning(msg) return False # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > '1.2': return mpl_Path(poly).contains_point([x,y]) else: try: return bool(nxutils.pnpoly(x, y, poly)) except: pass # fall through to pure python: # as adapted from http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ # via http://www.ariel.com.au/a/python-point-int-poly.html inside = False # trace (horizontal?) rays, flip inside status if cross an edge: p1x, p1y = poly[-1] for p2x, p2y in poly: if y > min(p1y, p2y) and y <= max(p1y, p2y) and x <= max(p1x, p2x): if p1y != p2y: xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xints: inside = not inside p1x, p1y = p2x, p2y return inside
def pointInPolygon(x, y, poly): """Determine if a point (`x`, `y`) is inside a polygon, using the ray casting method. `poly` is a list of 3+ vertices as (x,y) pairs. If given a `ShapeStim`-based object, will use the rendered vertices and position as the polygon. Returns True (inside) or False (outside). Used by :class:`~psychopy.visual.ShapeStim` `.contains()` """ try: #do this using try:...except rather than hasattr() for speed poly = poly.verticesPix #we want to access this only once except: pass nVert = len(poly) if nVert < 3: msg = 'pointInPolygon expects a polygon with 3 or more vertices' logging.warning(msg) return False # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > '1.2': return mpl_Path(poly).contains_point([x, y]) else: try: return bool(nxutils.pnpoly(x, y, poly)) except: pass # fall through to pure python: # as adapted from http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ # via http://www.ariel.com.au/a/python-point-int-poly.html inside = False # trace (horizontal?) rays, flip inside status if cross an edge: p1x, p1y = poly[-1] for p2x, p2y in poly: if y > min(p1y, p2y) and y <= max(p1y, p2y) and x <= max(p1x, p2x): if p1y != p2y: xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xints: inside = not inside p1x, p1y = p2x, p2y return inside
def pointInPolygon(x, y, poly): """Determine if a point (`x`, `y`) is inside a polygon, using the ray casting method. `poly` is a list of 3+ vertices as (x,y) pairs. If given a `ShapeStim`-based object, will use the rendered vertices and position as the polygon. Returns True (inside) or False (outside). Used by :class:`~psychopy.visual.ShapeStim` `.contains()` """ if hasattr(poly, '_verticesRendered') and hasattr(poly, '_posRendered'): poly = poly._verticesRendered + poly._posRendered nVert = len(poly) if nVert < 3: msg = 'pointInPolygon expects a polygon with 3 or more vertices' logging.warning(msg) return False # faster if have matplotlib tools: if haveMatplotlib: if matplotlib.__version__ > '1.2': return mpl_Path(poly).contains_point([x,y]) else: try: return bool(nxutils.pnpoly(x, y, poly)) except: pass # fall through to pure python: # as adapted from http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/ # via http://www.ariel.com.au/a/python-point-int-poly.html inside = False # trace (horizontal?) rays, flip inside status if cross an edge: p1x, p1y = poly[-1] for p2x, p2y in poly: if y > min(p1y, p2y) and y <= max(p1y, p2y) and x <= max(p1x, p2x): if p1y != p2y: xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xints: inside = not inside p1x, p1y = p2x, p2y return inside
def overlaps(left, right): poly_left = mpl_Path( left, closed=True) poly_right = mpl_Path(right, closed=True) return poly_left.intersects_path(poly_right, filled=True)
def MultiPolygon(self): # not used here but might be relevant: # https://stackoverflow.com/questions/22100453/gdal-python-creating-contourlines try: return self.__MultiPolygon except AttributeError: pass # fully external tile. if np.all(self.values > self.zmax) or np.all(self.values < self.zmin): self.__MultiPolygon = self.__get_empty_MultiPolygon() return self.__MultiPolygon # fully internal tile elif np.min(self.values) > self.zmin \ and np.max(self.values) < self.zmax: _LinearRing = self.__get_empty_LinearRing() bbox = self.bbox.get_points() x0, y0 = float(bbox[0][0]), float(bbox[0][1]) x1, y1 = float(bbox[1][0]), float(bbox[1][1]) _LinearRing.AddPoint(x0, y0, float(self.get_value(x0, y0))) _LinearRing.AddPoint(x1, y0, float(self.get_value(x1, y0))) _LinearRing.AddPoint(x1, y1, float(self.get_value(x1, y1))) _LinearRing.AddPoint(x0, y1, float(self.get_value(x0, y1))) _LinearRing.AddPoint(*_LinearRing.GetPoint(0)) _Polygon = self.__get_empty_Polygon() _Polygon.AddGeometry(_LinearRing) _MultiPolygon = self.__get_empty_MultiPolygon() _MultiPolygon.AddGeometry(_Polygon) self.__MultiPolygon = _MultiPolygon return self.__MultiPolygon # tile containing boundary _QuadContourSet = plt.contourf(self.x, self.y, self.values, levels=[self.zmin, self.zmax]) plt.close(plt.gcf()) for _PathCollection in _QuadContourSet.collections: _LinearRings = list() for _Path in _PathCollection.get_paths(): linear_rings = _Path.to_polygons(closed_only=True) for linear_ring in linear_rings: _LinearRing = ogr.Geometry(ogr.wkbLinearRing) _LinearRing.AssignSpatialReference(self.SpatialReference) for x, y in linear_ring: _LinearRing.AddPoint(float(x), float(y), float(self.get_value(x, y))) _LinearRing.CloseRings() _LinearRings.append(_LinearRing) # create output object _MultiPolygon = ogr.Geometry(ogr.wkbMultiPolygon) _MultiPolygon.AssignSpatialReference(self.SpatialReference) # sort list of linear rings into polygons areas = [_LinearRing.GetArea() for _LinearRing in _LinearRings] idx = np.where(areas == np.max(areas))[0][0] _Polygon = ogr.Geometry(ogr.wkbPolygon) _Polygon.AssignSpatialReference(self.SpatialReference) _Polygon.AddGeometry(_LinearRings.pop(idx)) while len(_LinearRings) > 0: _Path = mpl_Path(np.asarray( _Polygon.GetGeometryRef(0).GetPoints())[:, :2], closed=True) for i, _LinearRing in reversed(list(enumerate(_LinearRings))): x = _LinearRing.GetX(0) y = _LinearRing.GetY(0) if _Path.contains_point((x, y)): _Polygon.AddGeometry(_LinearRings.pop(i)) _Polygon.CloseRings() _MultiPolygon.AddGeometry(_Polygon) if len(_LinearRings) > 0: areas = [_LinearRing.GetArea() for _LinearRing in _LinearRings] idx = np.where(areas == np.max(areas))[0][0] _Polygon = ogr.Geometry(ogr.wkbPolygon) _Polygon.AssignSpatialReference(self.SpatialReference) _Polygon.AddGeometry(_LinearRings.pop(idx)) self.__MultiPolygon = _MultiPolygon return self.__MultiPolygon
def calculate_dvh(structure, dose, limit=None, callback=None): """Calculate the differential DVH for the given structure and dose grid.""" sPlanes = structure['planes'] logger.debug("Calculating DVH of %s %s", structure['id'], structure['name']) # Get the dose to pixel LUT doselut = dose.GetPatientToPixelLUT() # Generate a 2d mesh grid to create a polygon mask in dose coordinates # Code taken from Stack Overflow Answer from Joe Kington: # http://stackoverflow.com/questions/3654289/scipy-create-2d-polygon-mask/3655582 # Create vertex coordinates for each grid cell x, y = np.meshgrid(np.array(doselut[0]), np.array(doselut[1])) x, y = x.flatten(), y.flatten() dosegridpoints = np.vstack((x, y)).T # Create an empty array of bins to store the histogram in cGy # only if the structure has contour data or the dose grid exists if ((len(sPlanes)) and ("PixelData" in dose.ds)): # Get the dose and image data information dd = dose.GetDoseData() id = dose.GetImageData() maxdose = int(dd['dosemax'] * dd['dosegridscaling'] * 100) # Remove values above the limit (cGy) if specified if not (limit == None): if (limit < maxdose): maxdose = limit hist = np.zeros(maxdose) else: hist = np.array([0]) volume = 0 plane = 0 # Iterate over each plane in the structure for z, sPlane in sPlanes.iteritems(): # Get the contours with calculated areas and the largest contour index contours, largestIndex = calculate_contour_areas(sPlane) # Get the dose plane for the current structure plane doseplane = dose.GetDoseGrid(z) # If there is no dose for the current plane, go to the next plane if not len(doseplane): break # Calculate the histogram for each contour for i, contour in enumerate(contours): m = get_contour_mask(doselut, dosegridpoints, contour['data']) h, vol = calculate_contour_dvh(m, doseplane, maxdose, dd, id, structure) # If this is the largest contour, just add to the total histogram if (i == largestIndex): hist += h volume += vol # Otherwise, determine whether to add or subtract histogram # depending if the contour is within the largest contour or not else: contour['inside'] = False for point in contour['data']: if matplotlib.__version__ > '1.2': if mpl_Path(np.array(contours[largestIndex] ['data'])).contains_point(point): contour['inside'] = True # Assume if one point is inside, all will be inside break else: if nx.pnpoly(point[0], point[1], np.array(contours[largestIndex]['data'])): contour['inside'] = True # Assume if one point is inside, all will be inside break # If the contour is inside, subtract it from the total histogram if contour['inside']: hist -= h volume -= vol # Otherwise it is outside, so add it to the total histogram else: hist += h volume += vol plane += 1 if not (callback == None): callback(plane, len(sPlanes)) # Volume units are given in cm^3 volume = volume / 1000 # Rescale the histogram to reflect the total volume hist = hist * volume / sum(hist) # Remove the bins above the max dose for the structure hist = np.trim_zeros(hist, trim='b') return hist