def as_mpl_artists(shape_list, properties_func=None, text_offset=5.0, origin=1): """ Converts a region list to a list of patches and a list of artists. Optional Keywords: [ text_offset ] - If there is text associated with the regions, add some vertical offset (in pixels) to the text so that it doesn't overlap with the regions. Often, the regions files implicitly assume the lower-left corner of the image as a coordinate (1,1). However, the python convetion is that the array index starts from 0. By default (origin = 1), coordinates of the returned mpl artists have coordinate shifted by (1, 1). If you do not want this shift, set origin=0. """ patch_list = [] artist_list = [] if properties_func is None: properties_func = properties_func_default # properties for continued(? multiline?) regions saved_attrs = None for shape in shape_list: patches = [] if saved_attrs is None: _attrs = [], {} else: _attrs = copy.copy(saved_attrs[0]), copy.copy(saved_attrs[1]) kwargs = properties_func(shape, _attrs) if shape.name == "composite": saved_attrs = shape.attr continue if saved_attrs is None and shape.continued: saved_attrs = shape.attr # elif (shape.name in shape.attr[1]): # if (shape.attr[1][shape.name] != "ignore"): # saved_attrs = shape.attr if not shape.continued: saved_attrs = None # text associated with the shape txt = shape.attr[1].get("text") if shape.name == "polygon": xy = np.array(shape.coord_list) xy.shape = -1, 2 # -1 for change origin to 0,0 patches = [mpatches.Polygon(xy - origin, closed=True, **kwargs)] elif shape.name == "rotbox" or shape.name == "box": xc, yc, w, h, rot = shape.coord_list # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin _box = np.array([[-w / 2., -h / 2.], [-w / 2., h / 2.], [w / 2., h / 2.], [w / 2., -h / 2.]]) box = _box + [xc, yc] rotbox = rotated_polygon(box, xc, yc, rot) patches = [mpatches.Polygon(rotbox, closed=True, **kwargs)] elif shape.name == "ellipse": xc, yc = shape.coord_list[:2] # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin angle = shape.coord_list[-1] maj_list, min_list = shape.coord_list[2:-1:2], shape.coord_list[3:-1:2] patches = [mpatches.Ellipse((xc, yc), 2 * maj, 2 * min, angle=angle, **kwargs) for maj, min in zip(maj_list, min_list)] elif shape.name == "annulus": xc, yc = shape.coord_list[:2] # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin r_list = shape.coord_list[2:] patches = [mpatches.Ellipse((xc, yc), 2 * r, 2 * r, **kwargs) for r in r_list] elif shape.name == "circle": xc, yc, major = shape.coord_list # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin patches = [mpatches.Ellipse((xc, yc), 2 * major, 2 * major, angle=0, **kwargs)] elif shape.name == "panda": xc, yc, a1, a2, an, r1, r2, rn = shape.coord_list # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin patches = [mpatches.Arc((xc, yc), rr * 2, rr * 2, angle=0, theta1=a1, theta2=a2, **kwargs) for rr in np.linspace(r1, r2, rn + 1)] for aa in np.linspace(a1, a2, an + 1): xx = np.array([r1, r2]) * np.cos(aa / 180. * np.pi) + xc yy = np.array([r1, r2]) * np.sin(aa / 180. * np.pi) + yc p = Path(np.transpose([xx, yy])) patches.append(mpatches.PathPatch(p, **kwargs)) elif shape.name == "pie": xc, yc, r1, r2, a1, a2 = shape.coord_list # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin patches = [mpatches.Arc((xc, yc), rr * 2, rr * 2, angle=0, theta1=a1, theta2=a2, **kwargs) for rr in [r1, r2]] for aa in [a1, a2]: xx = np.array([r1, r2]) * np.cos(aa / 180. * np.pi) + xc yy = np.array([r1, r2]) * np.sin(aa / 180. * np.pi) + yc p = Path(np.transpose([xx, yy])) patches.append(mpatches.PathPatch(p, **kwargs)) elif shape.name == "epanda": xc, yc, a1, a2, an, r11, r12, r21, r22, rn, angle = shape.coord_list # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin # mpl takes angle a1, a2 as angle as in circle before # transformation to ellipse. x1, y1 = cos(a1 / 180. * pi), sin(a1 / 180. * pi) * r11 / r12 x2, y2 = cos(a2 / 180. * pi), sin(a2 / 180. * pi) * r11 / r12 a1, a2 = atan2(y1, x1) / pi * 180., atan2(y2, x2) / pi * 180. patches = [mpatches.Arc((xc, yc), rr1 * 2, rr2 * 2, angle=angle, theta1=a1, theta2=a2, **kwargs) for rr1, rr2 in zip(np.linspace(r11, r21, rn + 1), np.linspace(r12, r22, rn + 1))] for aa in np.linspace(a1, a2, an + 1): xx = np.array([r11, r21]) * np.cos(aa / 180. * np.pi) yy = np.array([r11, r21]) * np.sin(aa / 180. * np.pi) p = Path(np.transpose([xx, yy])) tr = Affine2D().scale(1, r12 / r11).rotate_deg(angle).translate(xc, yc) p2 = tr.transform_path(p) patches.append(mpatches.PathPatch(p2, **kwargs)) elif shape.name == "text": xc, yc = shape.coord_list[:2] # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin if txt: _t = _get_text(txt, xc, yc, 0, 0, **kwargs) artist_list.append(_t) elif shape.name == "point": xc, yc = shape.coord_list[:2] # -1 for change origin to 0,0 xc, yc = xc - origin, yc - origin artist_list.append(Line2D([xc], [yc], **kwargs)) if txt: textshape = copy.copy(shape) textshape.name = "text" textkwargs = properties_func(textshape, _attrs) _t = _get_text(txt, xc, yc, 0, text_offset, va="bottom", **textkwargs) artist_list.append(_t) elif shape.name in ["line", "vector"]: if shape.name == "line": x1, y1, x2, y2 = shape.coord_list[:4] # -1 for change origin to 0,0 x1, y1, x2, y2 = x1 - origin, y1 - origin, x2 - origin, y2 - origin a1, a2 = shape.attr[1].get("line", "0 0").strip().split()[:2] arrowstyle = "-" if int(a1): arrowstyle = "<" + arrowstyle if int(a2): arrowstyle = arrowstyle + ">" else: # shape.name == "vector" x1, y1, l, a = shape.coord_list[:4] # -1 for change origin to 0,0 x1, y1 = x1 - origin, y1 - origin x2, y2 = x1 + l * np.cos(a / 180. * np.pi), y1 + l * np.sin(a / 180. * np.pi) v1 = int(shape.attr[1].get("vector", "0").strip()) if v1: arrowstyle = "->" else: arrowstyle = "-" patches = [mpatches.FancyArrowPatch(posA=(x1, y1), posB=(x2, y2), arrowstyle=arrowstyle, arrow_transmuter=None, connectionstyle="arc3", patchA=None, patchB=None, shrinkA=0, shrinkB=0, connector=None, **kwargs)] else: warnings.warn("'as_mpl_artists' does not know how to convert {0} " "to mpl artist".format(shape.name)) patch_list.extend(patches) if txt and patches: # the text associated with a shape uses different # matplotlib keywords than the shape itself for, e.g., # color textshape = copy.copy(shape) textshape.name = "text" textkwargs = properties_func(textshape, _attrs) # calculate the text position _bb = [p.get_window_extent() for p in patches] # this is to work around backward-incompatible change made # in matplotlib 1.2. This change is later reverted so only # some versions are affected. With affected version of # matplotlib, get_window_extent method calls get_transform # method which sets the _transformSet to True, which is # not desired. for p in patches: p._transformSet = False _bbox = Bbox.union(_bb) x0, y0, x1, y1 = _bbox.extents xc = .5 * (x0 + x1) _t = _get_text(txt, xc, y1, 0, text_offset, va="bottom", **textkwargs) artist_list.append(_t) return patch_list, artist_list