Example #1
0
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