from shapely.geometry import Polygon, GeometryCollection
import shapely.wkt
import triangle as triangle_package
import math

#%% POINT

def point_2d_to_3d(coordinate_index, V0, N, a1, a2):
        """Projection of a 2D point on a 3D plane.

        :param plane: The plane for the projection
        :type plane: Plane
        :param coordinate_index: The index of the coordinate which was ignored
            to create the 2D projection. For example, coordinate_index=0
            means that the x-coordinate was ignored and this point
            was originally projected onto the yz plane.
        :type coordinate_index: int

        :raises ValueError: If coordinate_index is not between 0 and 2.

        :return: The 3D point as projected onto the plane.
        :rtype: Point

        """
        if coordinate_index==0:
            y=a1
            z=a2
            try:
                x=V0[0]-(N[1]*(y-V0[1])+N[2]*(z-V0[2]))/N[0]
            except ZeroDivisionError:
                raise ValueError('yz points (%s,%s) must exist on the plane.' % (y,z))

        elif coordinate_index==1:
            z=a1
            x=a2
            try:
                y=V0[1]-(N[2]*(z-V0[2])+N[0]*(x-V0[0]))/N[1]
            except ZeroDivisionError:
                raise ValueError('zx points (%s,%s) must exist on the plane.' % (z,x))

        elif coordinate_index==2:
            x=a1
            y=a2
            try:
                z=V0[2]-(N[0]*(x-V0[0])+N[1]*(y-V0[1]))/N[2]
            except ZeroDivisionError:
                raise ValueError('xy points (%s,%s) must exist on the plane.' % (x,y))

        else:
            raise ValueError('coordinate_index must be between 0 and 2')

        return x, y, z

def point_3d_to_2d(coordinate_index, a1, a2, a3):
    """Projection of a 3D point as a 2D point.

    :param coordinate_index: The index of the coordinate to ignore.
        Use coordinate_index=0 to ignore the x-coordinate, coordinate_index=1
        for the y-coordinate and coordinate_index=2 for the z-coordinate.
    :type coordinate_index: int

    :raises ValueError: If coordinate_index is not between 0 and 2.

    :return: A 2D point based on the projection of the 3D point.
    :rtype: Point

    """
    if coordinate_index==0:
        return a2, a3
    elif coordinate_index==1:
        return a3, a1
    elif coordinate_index==2:
        return a1, a2
    else:
        raise ValueError('coordinate_index must be between 0 and 2')

def point_difference_3d(a1,a2,a3,b1,b2,b3):
    """Returns the difference between two points, which is a vector

    :return vector: the difference of P1 - P0
    :rtype tuple:
    """
    return b1-a1,b2-a2,b3-a3

def point_project_on_new_coordinate_system_3d(x,y,z,P0_new,vx_new,vy_new,vz_new):
    """Projects a point onto a new coordinate system.

    New coordinate system described by origin and three axis vectors.

    Returns new x,y,z coordinates for the point.

    """
    point=(x,y,z)
    x_new=plane_dist_to_point_3d(point,P0_new,vx_new)[0]
    y_new=plane_dist_to_point_3d(point,P0_new,vy_new)[0]
    z_new=plane_dist_to_point_3d(point,P0_new,vz_new)[0]

    return x_new, y_new, z_new

def point_unproject_from_new_coordinate_system_3d(x,y,z,P0_new,vx_new,vy_new,vz_new):
    """Projects a point onto a new coordinate system.

    New coordinate system described by origin and three axis vectors.

    Returns new x,y,z coordinates for the point.

    """

    a=vector_multiplication_3d(*vx_new,x)
    b=vector_multiplication_3d(*vy_new,y)
    c=vector_multiplication_3d(*vz_new,z)

    return tuple(P0_new[i]+a[i]+b[i]+c[i] for i in [0,1,2])

#%% VECTOR

def vector_angle_3d(v1,v2,v3,w1,w2,w3):
        """Returns the angle between this vector and the supplied vector.

        :param vector: A 2D or 3D vector.
        :type vector: Vector

        :return: The angle in radians.
        :rtype: float

        """
        x=vector_dot_product_3d(v1,v2,v3,w1,w2,w3)
        y=vector_length_3d(v1,v2,v3)
        z=vector_length_3d(w1,w2,w3)
        return math.acos(x/y/z)

def vector_collinear_3d(v1,v2,v3,w1,w2,w3, abs_tol = 1e-7):
    """Tests if two vectors are collinear.

    :returns: True if both vectors are parallel, i.e. they lie on the same line.
        They do not need to be pointing in the same direction.
        Otherwise returns False.
    :rtype: bool

    """
    cp=vector_cross_product_3d(v1,v2,v3,w1,w2,w3)
    vl=vector_length_3d(*cp)
    return math.isclose(vl, 0, abs_tol=abs_tol)

def vector_cross_product_3d(v1,v2,v3,w1,w2,w3):
    """Returns the 3D cross product of two vectors

    :return vector: the cross product of V0 x V1
    :rtype tuple:
    """
    return v2*w3-v3*w2,v3*w1-v1*w3,v1*w2-v2*w1

def vector_dot_product_2d(v1,v2,w1,w2):
    """Returns the dot product of two vectors

    :return vector: the dot product of v.w
    :rtype tuple:
    """
    return v1*w1+v2*w2

def vector_dot_product_3d(v1,v2,v3,w1,w2,w3):
    """Returns the dot product of two vectors

    :returns: The dot product of the two vectors:
        returns 0 if self and vector are perpendicular;
        returns >0 if the angle between self and vector is an acute angle (i.e. <90deg);
        returns <0 if the angle between self and vector is an obtuse angle (i.e. >90deg).
    :rtype: float

    :return vector: the dot product of v.w
    :rtype tuple:
    """
    return v1*w1+v2*w2+v3*w3

def vector_index_largest_absolute_coordinate_3d(v1, v2, v3):
    """Returns the index of the largest absolute coordinate of the vector.

    :return: 0 if the x-coordinate has the largest absolute value,
        1 if the y-coordinate has the largest absolute value, or
        2 if the z-coordinate has the largest absolute value.
    :rtype: int

    """
    absolute_coords=abs(v1), abs(v2), abs(v3)
    return absolute_coords.index(max(absolute_coords))

def vector_perp_product_2d(v1,v2,w1,w2):
    """Returns the perp product of two vectors

    :return scalar: the perp product of v and w
    :rtype float:
    """
    return v1*w2-v2*w1

def vector_perpendicular_2d(v1,v2,w1,w2, abs_tol = 1e-7):
    """Tests if the vectors are perpendicular

    """
    dot=vector_dot_product_2d(v1,v2,w1,w2)
    return math.isclose(dot, 0, abs_tol=abs_tol)

def vector_perpendicular_3d(v1,v2,v3,w1,w2,w3, abs_tol = 1e-7):
    """Tests if the vectors are perpendicular

    """
    dot=vector_dot_product_3d(v1,v2,v3,w1,w2,w3)
    return math.isclose(dot, 0, abs_tol=abs_tol)

def vector_addition_3d(v1,v2,v3,w1,w2,w3):
    """Returns the sum of two vectors, or a point and a vector

    :return vector or point: the sum v + w, or p + v
    :rtype tuple:
    """
    return v1+w1,v2+w2,v3+w3

def vector_length_2d(v1,v2):
    """Returns the length of a vector

    :return length: |v|
    :rtype float:
    """
    return (v1**2+v2**2)**0.5

def vector_length_3d(v1,v2,v3):
    """Returns the length of a vector

    :return length: |v|
    :rtype float:
    """
    return (v1**2+v2**2+v3**2)**0.5

def vector_multiplication_3d(v1,v2,v3,a):
    """Returns the multiplication of a vector by a real number

    :param a: a real number

    :return vector: the multiplication a * v
    :rtype tuple:
    """
    return a*v1,a*v2,a*v3

def vector_normalize_3d(v1,v2,v3):
    """Returns a normalised vector

    :return vector: the normalised vector u for v
    :rtype tuple:
    """
    length=vector_length_3d(v1,v2,v3)
    return v1/length,v2/length,v3/length

#%% PLANE

def plane_almost_equal_3d(V0, N, W0, O, abs_tol1=1e-7, abs_tol2=1e-7):
    """Tests if two planes occupy the same space.

    plane1 - V0, N
    plane2 - W0, O

    :rtype: bool

    """
    return (vector_collinear_3d(*N, *O, abs_tol=abs_tol1)
            and math.isclose(plane_dist_to_point_3d(W0, V0, N)[0],
                             0,
                             abs_tol=abs_tol2))

def plane_azimuth_3d(V0,N):
    """The azimuth angle of the polygon from the y axis.

    :type polygon:

    :returns: The azimuth angle in degrees where 0 degrees is the direction
        of the y axis and a positive angle is clockwise.
        If the surface is horizontal, then raises Error.
    :rtype: float

    """
    if vector_collinear_3d(0,0,1,*N):
        raise Exception
    else:
        # working in 2d
        v=N[0],N[1]
        y_axis=(0,1)

        # angle between vectors
        angle=math.acos(vector_dot_product_2d(*v,*y_axis)
                        / vector_length_2d(*v)
                        / vector_length_2d(*y_axis))
        angle=math.degrees(angle)

        if vector_perp_product_2d(*v,*y_axis)>=0: # if y_axis is on the left of v
            return angle
        else: # y_axis is on the right of v
            return angle * -1

def plane_new_projection_axes_3d(N, abs_tol = 1e-7):
    """Returns three vectors which represent a new set of x,y,z axes.

    The plane lies on the new axes where new_z=0.

    The vx_new vector is the crossproduct of the plane normal vector and
    the z-axis vector (0,0,1). This vector will therefore have a zero z-component.
    If the plane normal vector is also (0,0,1),
    then the y-axis vector (0,1,0)  is used instead.

    The vy_new is crossproduct of both the vx_new and the plane normal vector.

    The vz_new is crossprodut of vx_new and vy_new.

    """

    # vx_new
    a=vector_cross_product_3d(0, 0, 1, *N)
    if not math.isclose(vector_length_3d(*a),0,abs_tol=abs_tol):
        vx_new=a
    else:
        vx_new=vector_cross_product_3d(0, 1, 0, *N)

    # vy_new
    vy_new=vector_multiplication_3d(*vector_cross_product_3d(*vx_new,*N),
                                -1)

    # normalise vectors
    vx_new=vector_normalize_3d(*vx_new)
    vy_new=vector_normalize_3d(*vy_new)
    vz_new=vector_normalize_3d(*N)

    return vx_new, vy_new, vz_new

def plane_dist_to_point_3d(test_point, V0, N):
    """Get distance (and perp base) from a point to a plane

    :param test_point: a 3D point
        - i.e. (0,0,0)
    :param V0: a 3D point on the plane
    :param N: a 3D vector which is the normal to the plane

    :return result: (the distance from the point to the plane,
                     the base point on the plane of perpendicular from the test_point)
    :rtype tuple:

    """
    sn=-vector_dot_product_3d(*N,*point_difference_3d(*V0,*test_point))
    sd=vector_dot_product_3d(*N,*N)
    sb=sn/sd
    PB = vector_addition_3d(*test_point,*vector_multiplication_3d(*N,sb))
    l=vector_length_3d(*point_difference_3d(*PB,*test_point))
    return l, PB

def plane_of_polygon_3d(shell, abs_tol = 1e-7):
    """Returns the plane of the 3D polygon

    :param shell: The exterior coords of the polygon. Start and end coords
        are the same.
    :type shell: list

    :return plane: a 3D plane which contains all the polygon points
    :rtype: Plane3D

    """
    for i in range(0,len(shell)+1-3):
        c0,c1,c2=shell[i:i+3]
        v1=point_difference_3d(*c0,*c1) #vector_from_points(c0,c1)
        v2=point_difference_3d(*c1,*c2) #vector_from_points(c1,c2)
        N=vector_cross_product_3d(*v1,*v2)
        l=vector_length_3d(*N)
        if not math.isclose(l, 0, abs_tol=abs_tol):
            return (shell[0],N)
    raise ValueError

def plane_tilt_3d(V0,N):
    """The tilt angle of the polygon from the horizontal.

    :returns: The tilt angle in degrees where vertically up is 0 degrees and
        face down is 180 degrees.
    :rtype: float

    """
    z_axis=(0,0,1)
    # angle between vectors
    angle=math.acos(vector_dot_product_3d(*N,*z_axis)
                    / vector_length_3d(*N)
                    / vector_length_3d(*z_axis))
    angle=math.degrees(angle)

    return angle

# def plane_point_on_plane_axes_3d(V0,N,
#                                  point,
#                                  v1,v2,
#                                  abs_tol = 1e-7):
#     """Returns x and y coordinates of a 3D point on a 3D plane.

#     Relative to the plane start point (V0) and the plane v1 and v2 axes.

#     :param plane: A plane.
#     :param point: A 3D point on the plane

#     v1, v2 from 'plane_axes_3D' method

#     :rtype: x, y

#     """
#     #print('point',point)
#     #print('v1',v1)
#     #print('v2',v2)

#     if plane_dist_to_point_3d(point,V0,N)[0] > abs_tol:
#         raise ValueError('Point does not lie on the plane')

#     x=plane_dist_to_point_3d(point,V0,v1)[0]
#     y=plane_dist_to_point_3d(point,V0,v2)[0]

#     #print('x',x)
#     #print('y',y)

#     return x, y

#%% POLYGON

# def polygon_on_plane_axes_3d(shell, holes):
#     """
#     """
#     V0,N=plane_of_polygon_3d(shell)
#     v1,v2=plane_axes_3d(N)

#     shell_axes_2d=[]
#     for point in shell:
#         point_axes_2d=plane_point_on_plane_axes_3d(V0, N, point, v1, v2)
#         shell_axes_2d.append(point_axes_2d)
#     shell_axes_2d=tuple(shell_axes_2d)

#     holes_axes_2d=[]
#     for hole in holes:
#         x=[]
#         for point in hole:
#             point_axes_2d=plane_point_on_plane_axes_3d(V0, N, point, v1, v2)
#             x.append(point_axes_2d)
#         holes_axes_2d.append(tuple(x))

#     return V0, N, v1, v2, shell_axes_2d, holes_axes_2d

def polygon_2d_to_3d(coordinate_index, V0, N, shell, holes):
    """
    """
    shell_3d=tuple([point_2d_to_3d(coordinate_index, V0, N, *pt) for pt in shell])

    holes_3d=[]
    for hole in holes:
        holes_3d.append([point_2d_to_3d(coordinate_index, V0, N, *pt)
                         for pt in hole])

    return shell_3d, holes_3d

def polygon_3d_to_2d(coordinate_index, shell, holes):
    """
    """
    shell_2d=[point_3d_to_2d(coordinate_index,*pt) for pt in shell]

    holes_2d=[]
    for hole in holes:
        holes_2d.append([point_3d_to_2d(coordinate_index,*pt)
                         for pt in hole])

    return shell_2d, holes_2d

def polygon_area_3d(shell,holes):
    """
    """
    V0,N=plane_of_polygon_3d(shell)
    coordinate_index=vector_index_largest_absolute_coordinate_3d(*N)
    shell_2d,holes_2d=polygon_3d_to_2d(coordinate_index, shell, holes)
    area_2d=Polygon(shell_2d,holes_2d).area
    return abs(area_2d * (vector_length_3d(*N) / N[coordinate_index]))

def polygon_bounds_3d(shell,holes):
    """
    """
    xs=[pt[0] for pt in shell]
    ys=[pt[1] for pt in shell]
    zs=[pt[2] for pt in shell]

    return min(xs),max(xs),min(ys),max(ys),min(zs),max(zs)

def polygon_centroid_2d(shell,holes):
    """
    """
    return Polygon(shell,holes).centroid.coords[0]

def polygon_centroid_3d(shell,holes):
    """
    """
    V0,N=plane_of_polygon_3d(shell)
    coordinate_index=vector_index_largest_absolute_coordinate_3d(*N)
    shell_2d,holes_2d=polygon_3d_to_2d(coordinate_index, shell, holes)
    pt=polygon_centroid_2d(shell_2d,holes_2d)
    return point_2d_to_3d(coordinate_index, V0, N, *pt)

def polygon_contains_2d(shell1, holes1,
                        shell2, holes2,
                        ):
    """Tests if one 2D polygon contains the other.

    :rtype: bool

    """
    return Polygon(shell1, holes1).contains(Polygon(shell2, holes2))

def polygon_contains_3d(shell1, holes1, shell2, holes2,
                       abs_tol = 1e-7, abs_tol1=1e-7, abs_tol2=1e-7):
    """Tests if one 2D polygon contains the other.

    :rtype: bool

    """
    V0,N=plane_of_polygon_3d(shell1, abs_tol=abs_tol)
    W0,O=plane_of_polygon_3d(shell2, abs_tol=abs_tol)

    if plane_almost_equal_3d(V0, N, W0, O, abs_tol1=1e-7, abs_tol2=1e-7):

        coordinate_index=vector_index_largest_absolute_coordinate_3d(*N)
        shell1_2d,holes1_2d=polygon_3d_to_2d(coordinate_index, shell1, holes1)
        shell2_2d,holes2_2d=polygon_3d_to_2d(coordinate_index, shell2, holes2)
        return polygon_contains_2d(shell1_2d, holes1_2d,
                                   shell2_2d, holes2_2d)

    else:

        return False

def polygon_difference_2d(shell1, holes1, shell2, holes2):
    """The difference of two 2D polygons

    :returns: The coords for the intersection polygons.
        [(shell1, holes1), (shell2, holes2), ...]

    """
    result=Polygon(shell1, holes1).difference(Polygon(shell2, holes2))

    if isinstance(result,Polygon):
        result=GeometryCollection([result])

    return [(tuple(pg.exterior.coords),
             [tuple(lr.coords) for lr in pg.interiors])
            for pg in result.geoms]

def polygon_equals_2d(shell1, holes1,
                      shell2, holes2):
    """Tests if two 2D polygons are equal/equivalent.

    Shapely - Returns True if the set-theoretic boundary, interior, and exterior of the object coincide with those of the other.

    :rtype: bool

    """
    return Polygon(shell1, holes1).equals(Polygon(shell2, holes2))

def polygon_equals_3d(shell1, holes1,
                      shell2, holes2,
                      abs_tol = 1e-7,
                      abs_tol1=1e-7, abs_tol2=1e-7):
    """Tests if two 2D polygons are equal/equivalent.

    :rtype: bool

    """
    V0,N=plane_of_polygon_3d(shell1, abs_tol=abs_tol)
    W0,O=plane_of_polygon_3d(shell2, abs_tol=abs_tol)

    if plane_almost_equal_3d(V0, N, W0, O, abs_tol1=1e-7, abs_tol2=1e-7):

        coordinate_index=vector_index_largest_absolute_coordinate_3d(*N)
        shell1_2d,holes1_2d=polygon_3d_to_2d(coordinate_index, shell1, holes1)
        shell2_2d,holes2_2d=polygon_3d_to_2d(coordinate_index, shell2, holes2)
        return polygon_equals_2d(shell1_2d, holes1_2d,
                                 shell2_2d, holes2_2d)

    else:

        return False

def polygon_intersection_overlapping_2d(shell1, holes1, shell2, holes2):
    """The intersection of two overlapping 2D polygons

    :returns: The coords for the intersection polygons.
        [(shell1, holes1), (shell2, holes2), ...].
        Intersection will always be at least one polygon as the initial
        polygons are overlapping.

    """
    result=Polygon(shell1, holes1).intersection(Polygon(shell2, holes2))

    if isinstance(result,Polygon):
        result=GeometryCollection([result])

    return [(tuple(pg.exterior.coords),
             [tuple(lr.coords) for lr in pg.interiors])
            for pg in result.geoms]

def polygon_intersects_2d(shell1, holes1, shell2, holes2):
    """Tests if two 2D polygons intersect.

    From shapely - Returns True if the boundary or interior of the object
        intersect in any way with those of the other. In other words,
        geometric objects intersect if they have any boundary or
        interior point in common.

    returns False if the polygons are equal

    :rtype: bool

    """
    return Polygon(shell1, holes1).intersects(Polygon(shell2, holes2))

def polygon_leftmost_lowest_vertex_index_2D(shell):
    """Returns the index of the leftmost lowest point of the polygon.

    :rtype: int

    """

    min_i=0
    for i in range(1,len(shell)):
        if shell[i][1]>shell[min_i][1]:
            continue
        if (shell[i][1]==shell[min_i][1]) and (shell[i][0] > shell[min_i][0]):
            continue
        min_i=i
    return min_i

def polygon_overlaps_2d(shell1, holes1, shell2, holes2):
    """Tests if two 2D polygons overlap.

    :rtype: bool

    """
    return Polygon(shell1, holes1).overlaps(Polygon(shell2, holes2))

def polygon_triangulate_2d(shell,holes):
    """Triangulates a 2D polygon

    :returns: A sequence of triangle shells
    :rtype: tuple

    """

    vertices=list(shell[:-1])
    for hole in holes:
        vertices.extend(hole[:-1])
    vertices=list(set(vertices))

    segments=[]
    for pts in [shell]+holes:
        for i in range(len(pts)-1):
            start_pt_index=vertices.index(pts[i])
            end_pt_index=vertices.index(pts[i+1])
            segments.append((start_pt_index,end_pt_index))

    holes=[list(Polygon(x).representative_point().coords[0]) for x in holes]
    #print(holes)

    if holes:
        A=dict(vertices=vertices,
               segments=segments,
               holes=holes)
    else:
        A=dict(vertices=vertices,
               segments=segments)

    #print(A)
    B=triangle_package.triangulate(A,'p')
    #print(B)
    tris=[]
    for tri in B.get('triangles',[]):
        tris.append((vertices[tri[0]],
                     vertices[tri[1]],
                     vertices[tri[2]],
                     vertices[tri[0]]))

    return tuple(tris)

    # vertices=shell[:-1]
    # segments=[[x,x+1] for x in range(len(shell)-1)]  # segments index the vertices
    # segments[-1][1]=0
    # A=dict(vertices=vertices,
    #        segments=segments)
    # B=triangle_package.triangulate(A,'p')
    # tris=[]
    # for tri in B.get('triangles',[]):
    #     tris.append((vertices[tri[0]],
    #                  vertices[tri[1]],
    #                  vertices[tri[2]],
    #                  vertices[tri[0]]))

    # return tuple(tris)

def polygon_triangulate_3d(shell,holes):
    """Triangulates a 3D polygon

    :returns: A sequence of triangle shells
    :rtype: tuple

    """
    V0,N=plane_of_polygon_3d(shell)
    coordinate_index=vector_index_largest_absolute_coordinate_3d(*N)
    shell_2d,holes_2d=polygon_3d_to_2d(coordinate_index, shell, holes)
    tris_2d=polygon_triangulate_2d(shell_2d,holes_2d)
    tris_3d=[]
    for tri_2d in tris_2d:
        tris_3d.append(polygon_2d_to_3d(coordinate_index, V0, N, tri_2d, [])[0])
    return tuple(tris_3d)

def polygon_touches_2d(shell1, holes1, shell2, holes2):
    """Tests if two 2D polygons touch.

    :rtype: bool

    """
    return Polygon(shell1, holes1).touches(Polygon(shell2, holes2))

def polygon_union_2d(shell1, holes1, shell2, holes2):
    """The union of two 2D polygons

    :returns: The coords for the union polygons.
        [(shell1, holes1), (shell2, holes2), ...]

    """

    result=Polygon(shell1, holes1).union(Polygon(shell2, holes2))

    if isinstance(result,Polygon):
        result=GeometryCollection([result])

    return [(tuple(pg.exterior.coords),
             [tuple(lr.coords) for lr in pg.interiors])
            for pg in result.geoms]

#%% POLYHEDRON

def polyhedron_from_base_shell_and_extrud_point_3d(base_shell, extrud_point):
    """Returns the PolyhedralSurface for the polyhedron

    """
    V0,N=plane_of_polygon_3d(base_shell)
    v=point_difference_3d(*V0,*extrud_point)
    x=vector_dot_product_3d(*N,*v)  # greater than zero if both vectors are pointing on same side of plane
    if x>0:
        base_shell=tuple(list(base_shell)[::-1])  # reverse tr1

    #top_shell=tuple([vector_addition_3d(*pt,*extrud_vector) for pt in base_shell[::-1]])

    side_shells=[]
    for i in range(len(base_shell)-1):
        sh=tuple((base_shell[i+1],
                  base_shell[i],
                  extrud_point,
                  base_shell[i+1]))
        side_shells.append(sh)

    return (base_shell,*side_shells)

def polyhedron_from_base_shell_and_extrud_vector_3d(base_shell, extrud_vector):
    """Returns the PolyhedralSurface for the polyhedron

    """
    V0,N=plane_of_polygon_3d(base_shell)  # gets the normal of tr1
    x=vector_dot_product_3d(*N,*extrud_vector)  # greater than zero if both vectors are on same side of triangle
    if x>0:
        base_shell=tuple(list(base_shell)[::-1])  # reverse tr1

    top_shell=tuple([vector_addition_3d(*pt,*extrud_vector) for pt in base_shell[::-1]])

    side_shells=[]
    for i in range(len(base_shell)-1):
        sh=tuple((base_shell[i+1],
                  base_shell[i],
                  vector_addition_3d(*base_shell[i],*extrud_vector),
                  vector_addition_3d(*base_shell[i+1],*extrud_vector),
                  base_shell[i+1]))
        side_shells.append(sh)

    return (base_shell,top_shell,*side_shells)

def polyhedron_to_wkt_3d(polyhedron):
    """

    :rtype: str

    """
    x=' '.join(Polygon(pg).wkt for pg in polyhedron)
    x=x[10:]
    x=x.replace(' POLYGON Z',',')
    return 'PolyhedralSurface Z (%s)' % x

def tetrahedron_from_points_3d(P0,P1,P2,P3):
    """Forms a tetrahedron from the specified points.

    :returns: A tetrahedron.
        All faces of the tetrahedron are 'outward-facing', i.e. their
        normals point to the exterior.
    :rtype: Tetrahedron

    """
    # form first 3D triangle
    tr1=((P0,P1,P2,P0))

    # is the first triangle 'outward facing'? If not then reverse.
    V0,N=plane_of_polygon_3d(tr1)  # gets the normal of tr1
    v=point_difference_3d(*P0,*P3)  # vector from P0 to P3
    x=vector_dot_product_3d(*N,*v)  # greater than zero if both vectors are on same side of triangle
    if x>0:
        tr1=((P0,P2,P1,P0))  # reverse tr1

    V0,N=plane_of_polygon_3d(tr1)  # gets the normal of tr1

    tr2=((P3,tr1[1],tr1[0],P3))
    tr3=((P3,tr1[2],tr1[1],P3))
    tr4=((P3,tr1[3],tr1[2],P3))

    return (tr1,tr2,tr3,tr4)

def tetrahedron_to_wkt_3d(tetrahedron):
    """

    :rtype: str

    """
    x=' '.join(Polygon(tr).wkt for tr in tetrahedron)
    x=x[10:]
    x=x.replace(' POLYGON Z',',')
    return 'Tin Z (%s)' % x

def tetrahedron_volume_3d(tetrahedron):
    """
    tetrahedron: sequence of the face polygons
    """
    # NEEDS testing
    a,b,c,d=list(set(pt for tr in tetrahedron for pt in tr))
    return (abs(vector_dot_product_3d(*point_difference_3d(*d,*a),
                                      *vector_cross_product_3d(*point_difference_3d(*d,*b),
                                                               *point_difference_3d(*d,*c))))
            / 6)

def tetrahedrons_from_base_shell_and_extrud_point_3d(base_shell,
                                                     extrud_point):
    """
    """
    tris=polygon_triangulate_3d(base_shell,[])

    ths=[]
    for tri in tris:
        ths.append(tetrahedron_from_points_3d(tri[0],
                                              tri[1],
                                              tri[2],
                                              extrud_point))
    return tuple(ths)

def tetrahedrons_from_base_shell_and_extrud_vector_3d(base_shell,
                                                      extrud_vector):
    """
    """
    tris=polygon_triangulate_3d(base_shell,[])

    ths=[]
    for tri in tris:
        ths.extend(tetrahedrons_from_extruded_triangle_3d(tri,
                                                          extrud_vector))
    return tuple(ths)

def tetrahedrons_from_extruded_triangle_3d(triangle, extrud_vector):
    """
    Returns the internal tetrahedrons for an extruded triangle.

    """

    th0=tetrahedron_from_points_3d(triangle[0],
                                   triangle[1],
                                   triangle[2],
                                   vector_addition_3d(*triangle[0],*extrud_vector))
    th1=tetrahedron_from_points_3d(triangle[1],
                                   triangle[2],
                                   vector_addition_3d(*triangle[0],*extrud_vector),
                                   vector_addition_3d(*triangle[1],*extrud_vector))
    th2=tetrahedron_from_points_3d(vector_addition_3d(*triangle[0],*extrud_vector),
                                   vector_addition_3d(*triangle[1],*extrud_vector),
                                   vector_addition_3d(*triangle[2],*extrud_vector),
                                   triangle[2])
    return (th0,th1,th2)

def tetrahedrons_intersect(th1, th2, abs_tol=1e-7):
    """Tests to see if two tetrahedrons intersect

    NOTE: Seems to work but not fully tested...

    :rtype: bool

    """
    th1_points=list(set([pt for tr in th1 for pt in tr]))
    th2_points=list(set([pt for tr in th2 for pt in tr]))

    # face planes
    for tr in th1:
        V0,N=plane_of_polygon_3d(tr)
        vs=[point_difference_3d(*V0,*pt) for pt in th2_points]
        x=[vector_dot_product_3d(*v,*N) for v in vs]
        if all((y>0 or math.isclose(y,0,abs_tol=abs_tol)) for y in x):
            return False

    for tr in th2:
        V0,N=plane_of_polygon_3d(tr)
        vs=[point_difference_3d(*V0,*pt) for pt in th1_points]
        x=[vector_dot_product_3d(*v,*N) for v in vs]
        if all((y>0 or math.isclose(y,0,abs_tol=abs_tol)) for y in x):
            return False

    th1_edges=(point_difference_3d(*th1_points[0],*th1_points[1]),
               point_difference_3d(*th1_points[0],*th1_points[2]),
               point_difference_3d(*th1_points[0],*th1_points[3]),
               point_difference_3d(*th1_points[1],*th1_points[2]),
               point_difference_3d(*th1_points[1],*th1_points[3]),
               point_difference_3d(*th1_points[2],*th1_points[3]))
    th2_edges=(point_difference_3d(*th2_points[0],*th2_points[1]),
               point_difference_3d(*th2_points[0],*th2_points[2]),
               point_difference_3d(*th2_points[0],*th2_points[3]),
               point_difference_3d(*th2_points[1],*th2_points[2]),
               point_difference_3d(*th2_points[1],*th2_points[3]),
               point_difference_3d(*th2_points[2],*th2_points[3]))

    # edge planes
    for th1_edge in th1_edges:
        for th2_edge in th2_edges:
            N=vector_cross_product_3d(*th1_edge,*th2_edge)

            if not math.isclose(vector_length_3d(*N),0,abs_tol=abs_tol):

                # project onto axis
                V0=th1_points[0]

                # th1 values
                vs=[point_difference_3d(*V0,*pt) for pt in th1_points]
                x=[vector_dot_product_3d(*v,*N)*vector_length_3d(*v) for v in vs]

                # th2 values
                vs=[point_difference_3d(*V0,*pt) for pt in th2_points]
                y=[vector_dot_product_3d(*v,*N)*vector_length_3d(*v) for v in vs]

                if ((max(x)<min(y)
                     or math.isclose(max(x),min(y),abs_tol=abs_tol))
                    or (max(y)<min(x)
                        or math.isclose(max(y),min(x),abs_tol=abs_tol))):
                    return False

                # # find V0 in th1 such that th1 is behind the plane
                # for th1_point in th1_points:
                #     vs=[point_difference_3d(*th1_point,*pt) for pt in th1_points]
                #     x=[dot_product_3d(*v,*N) for v in vs]
                #     if all((y<0 or math.isclose(y,0,abs_tol=abs_tol)) for y in x):
                #         V0=th1_point
                #         # is th2 above the plane?
                #         vs=[point_difference_3d(*V0,*pt) for pt in th2_points]
                #         x=[dot_product_3d(*v,*N) for v in vs]
                #         if all((y>0 or math.isclose(y,0,abs_tol=abs_tol)) for y in x):
                #             print(V0,N)
                #             return False

                # # find V0 in th2 such that th1 is behind the plane
                # for th2_point in th2_points:
                #     vs=[point_difference_3d(*th2_point,*pt) for pt in th2_points]
                #     x=[dot_product_3d(*v,*N) for v in vs]
                #     if all((y<0 or math.isclose(y,0,abs_tol=abs_tol)) for y in x):
                #         V0=th2_point
                #         # is th1 above the plane?
                #         vs=[point_difference_3d(*V0,*pt) for pt in th1_points]
                #         x=[dot_product_3d(*v,*N) for v in vs]
                #         if all((y>0 or math.isclose(y,0,abs_tol=abs_tol)) for y in x):
                #             return False

    return True

def wkt_to_polyhedron_3d(wkt):
    """

    Note: This works, but only just. It'll work if the wkt is created using
        polyhedron_to_wkt_3d.

    :rtype: tuple

    """
    x=wkt.strip()
    x=x.replace('(((','((')
    x=x.replace('((','Polygon Z ((')
    x=x.replace('PolyhedralSurface Z','GeometryCollection (')
    gc=shapely.wkt.loads(x)
    return tuple(tuple(pg.exterior.coords) for pg in gc.geoms)

def wkt_to_tetrahedron_3d(wkt):
    """

    Note: This works, but only just. It'll work if the wkt is created using
        tetrahedron_to_wkt_3d.

    :rtype: tuple

    """
    x=wkt.strip()
    x=x.replace('(((','((')
    x=x.replace('((','Polygon Z ((')
    x=x.replace('Tin Z','GeometryCollection (')
    gc=shapely.wkt.loads(x)
    return tuple(tuple(pg.exterior.coords) for pg in gc.geoms)

# class Point(shapely.geometry.Point):
#     ""

#     def add(self,vector):
#         """The addition of this point and a vector.

#         :param vector: The vector to be added to the point.
#         :type vector: Vector

#         :rtype: Point

#         """
#         zipped=itertools.zip_longest(self.coords[0],
#                                      vector.coords[0]) # missing values filled with None
#         try:
#             coordinates=[a+b for a,b in zipped]
#         except TypeError: # occurs if, say, a or b is None
#             raise ValueError('Point and vector to add must be of the same length.')
#         return Point(*coordinates)

#     def project_2D(self,coordinate_index):
#         """Projection of a 3D point as a 2D point.

#         :param coordinate_index: The index of the coordinate to ignore.
#             Use coordinate_index=0 to ignore the x-coordinate, coordinate_index=1
#             for the y-coordinate and coordinate_index=2 for the z-coordinate.
#         :type coordinate_index: int

#         :raises ValueError: If coordinate_index is not between 0 and 2.

#         :return: A 2D point based on the projection of the 3D point.
#         :rtype: Point

#         """
#         if coordinate_index==0:
#             return Point(self.y,self.z)
#         elif coordinate_index==1:
#             return Point(self.z,self.x)
#         elif coordinate_index==2:
#             return Point(self.x,self.y)
#         else:
#             raise ValueError('coordinate_index must be between 0 and 2')

#     def project_3D(self,plane,coordinate_index):
#         """Projection of a 2D point on a 3D plane.

#         :param plane: The plane for the projection
#         :type plane: Plane
#         :param coordinate_index: The index of the coordinate which was ignored
#             to create the 2D projection. For example, coordinate_index=0
#             means that the x-coordinate was ignored and this point
#             was originally projected onto the yz plane.
#         :type coordinate_index: int

#         :raises ValueError: If coordinate_index is not between 0 and 2.

#         :return: The 3D point as projected onto the plane.
#         :rtype: Point

#         """
#         if coordinate_index==0:
#             point=plane.point_yz(self.x,self.y)
#         elif coordinate_index==1:
#             point=plane.point_zx(self.x,self.y)
#         elif coordinate_index==2:
#             point=plane.point_xy(self.x,self.y)
#         else:
#             raise ValueError('coordinate_index must be between 0 and 2')

#         return point

#     def subtract(self,point_or_vector):
#         """Subtraction of supplied object from this point.

#         :param point_or_vector: Either a point or a vector.
#         :type point_or_vector: Point or Vector

#         :return: If a point is supplied, then a vector is returned (i.e. v=P1-P0).
#             If a vector is supplied, then a point is returned (i.e. P1=P0-v).
#         :rtype: Point or Vector

#         """
#         zipped=itertools.zip_longest(self.coords[0],
#                                      point_or_vector.coords[0]) # missing values filled with None
#         try:
#             coordinates=[a-b for a,b in zipped]
#         except TypeError: # occurs if, say, a or b is None
#             raise ValueError(r'Point and point/vector to subtract must be of the same length.')
#         if isinstance(point_or_vector,Point):
#             return Vector(*coordinates)
#         else:
#             return Point(*coordinates)

# class Vector(Point):
#     ""

#     def __str__(self):
#         ""
#         return 'VECTOR%s' % str(Point(self))[5:]

#     def cross_product(self,vector):
#         """Returns the 3D cross product of this vector and the supplied vector.

#         :param vector: A 3D vector.
#         :type vector: Vector

#         :raises ValueError: If the vector is not a 3D vector.

#         :return: The 3D cross product of the two vectors.
#             This returns a new vector which is perpendicular to
#             this vector (self) and the supplied vector.
#             The returned vector has direction according to the right hand rule.
#             If this vector (self) and the supplied vector are collinear,
#             then the returned vector is (0,0,0)

#         :rtype: Vector

#         """
#         if self.has_z:
#             v1, v2, v3=self.x, self.y, self.z
#             w1, w2, w3=vector.x, vector.y, vector.z
#             return Vector(v2*w3-v3*w2,
#                           v3*w1-v1*w3,
#                           v1*w2-v2*w1)
#         else:
#             raise ValueError('"cross_product" method can only be used for a 3D vector.')

#     def dot(self,vector):
#         """Return the dot product of this vector and the supplied vector.

#         :param vector: A vector.
#         :type vector: Vector

#         :returns: The dot product of the two vectors:
#             returns 0 if self and vector are perpendicular;
#             returns >0 if the angle between self and vector is an acute angle (i.e. <90deg);
#             returns <0 if the angle between self and vector is an obtuse angle (i.e. >90deg).
#         :rtype: float

#         """
#         zipped=itertools.zip_longest(self.coords[0],
#                                      vector.coords[0]) # missing values filled with None
#         try:
#             return sum(a*b for a,b in zipped)
#         except TypeError: # occurs if, say, a or b is None
#             raise ValueError(r'Vectors to subtract must be of the same length.')

#     @property
#     def index_largest_absolute_coordinate(self):
#         """Returns the index of the largest absolute coordinate of the vector.

#         :return: 1 if the x-coordinate has the largest absolute value,
#             2 if the y-coordinate has the largest absolute value, or
#             (for 3D vectors) 3 if the z-coordinate has the largest
#             absolute value.
#         :rtype: int

#         """
#         absolute_coords=abs(self.x), abs(self.y), abs(self.z)
#         return absolute_coords.index(max(absolute_coords))

#     def is_collinear(self,vector):
#         """Tests if this vector and the supplied vector are collinear.

#         :param vector: A vector.
#         :type vector: Vector

#         :raise ValueError: If the vector is not 2D or 3D.

#         :return: True if the vectors lie on the same line;
#             otherwise False.
#         :rtype: bool

#         """
#         if not self.has_z:
#             return math.isclose(self.perp_product(vector), 0, abs_tol=ABS_TOL)
#         else:
#             return math.isclose(self.cross_product(vector).length, 0, abs_tol=ABS_TOL)

#     def is_perpendicular(self,vector):
#         """Test if this vector and the supplied vector are perpendicular.

#         :param vector: A vector.
#         :type vector: Vector

#         :return: True if the vectors are perpendicular;
#             otherwise False.
#         :rtype: bool

#         """
#         return math.isclose(self.dot(vector), 0, abs_tol=ABS_TOL)

#     @property
#     def length(self):
#         """Returns the length of the 2D vector.

#         :rtype: float

#         """
#         return (self.x**2 + self.y**2)**0.5

#     @property
#     def length3D(self):
#         ""
#         return (self.x**2 + self.y**2 + self.z**2)**0.5

#     def perp_product(self,vector):
#         """Returns the perp product of this vector and the supplied vector.

#         :param vector: A 2D vector.
#         :type vector: Vector

#         :raises ValueError: If this vector is not a 2D vector.

#         :return: The perp product of the two vectors.
#             The perp product is the dot product of
#             the perp_vector of this vector and the supplied vector.
#             If supplied vector is collinear with self, returns 0.
#             If supplied vector is on the left of self, returns >0 (i.e. counterclockwise).
#             If supplied vector is on the right of self, returns <0 (i.e. clockwise).
#         :rtype: float

#         """
#         if not self.has_z:
#             return self.perp_vector.dot(vector)
#         else:
#             raise ValueError('"perp_product" method only applicable for a 2D vector.')

#     @property
#     def perp_vector(self):
#         """Returns the perp vector of this 2D vector.

#         :raises ValueError: If this vector is not a 2D vector.

#         :return: The perp vector, i.e. the normal vector on the left
#             (counterclockwise) side of self.
#         :rtype: Vector

#         """
#         if not self.has_z:
#             return Vector(-self.y,self.x)
#         else:
#             raise ValueError('"perp_vector" method only applicable for a 2D vector.')

#     def scale(self,scalar):
#         """Multiplication of this vector and a supplied scalar value.

#         :param scalar: A numerical scalar value.
#         :type scalar: float

#         :rtype: Vector

#         """
#         if not self.has_z:
#             return Vector(self.x*scalar, self.y*scalar)
#         else:
#             return Vector(self.x*scalar, self.y*scalar, self.z*scalar)

#     def subtract(self,vector):
#         """Subtraction of this vector and a supplied vector.

#         :param vector: A vector.
#         :type vector: Vector

#         :rtype: Vector

#         """

#         zipped=itertools.zip_longest(self.coords[0],
#                                      vector.coords[0]) # missing values filled with None
#         try:
#             coordinates=[a-b for a,b in zipped]
#         except TypeError: # occurs if, say, a or b is None
#             raise ValueError(r'Vectors to subtract must be of the same length.')
#         return Vector(*coordinates)

# class Line():
#     """

#     """

#     def __init__(self,P0=None,vL=None):
#         ""
#         self._P0=P0

#         if not vL is None:
#             if math.isclose(vL.length, 0, abs_tol=ABS_TOL):
#                 raise ValueError('length of vL must be greater than zero')
#             self._vL=vL
#         else:
#             self._vL=None

#     def equals(self,line):
#         """Tests if this line and the supplied line are equal.

#         :param line: A line.
#         :type line: Line

#         :raises TypeError: If a Line instance is not supplied.

#         :return: True if the start point of supplied line lies on line (self),
#             and the vL of supplied line is collinear to the vL of line (self);
#             otherwise False.
#         :rtype: bool

#         """
#         if isinstance(line,Line):
#             return self.intersects(line.P0) and self.vL.is_collinear(line.vL)
#         else:
#             raise TypeError('Line.__eq__ should be used with a Line instance')

#     @property
#     def P0(self):
#         """The starting point of the line.

#         :rtype: Point

#         """
#         return self._P0

#     @property
#     def vL(self):
#         """The vector of the line.

#         :rtype: Vector

#         """
#         return self._vL

# class Plane():
#     ""

#     def __init__(self,P0=None,N=None):
#         ""
#         if not P0 is None:
#             self._P0=Point(P0)  # should work for either coords or Point
#         else:
#             self._P0=None
#         if not N is None:
#             self._N=Vector(N)  # should work for either coords or Vector
#         else:
#             self._N=None

#     def __str__(self):
#         ""
#         return 'PLANE (%s, %s)' % (str(self.P0),
#                                    str(self.N))

#     # def contains(self,obj):
#     #     """Tests if the plane contains the object.

#     #     :param obj: A 3D geometric object.
#     #     :type obj: Point, Line

#     #     :raises TypeError: If obj is of a type that cannot be contained in a plane.

#     #     :rtype: bool

#     #     """
#     #     if isinstance(obj,Point):
#     #         return self.N.is_perpendicular(obj-self.P0)
#     #     # elif isinstance(obj,Line):
#     #     #    return self.contains(obj.P0) and self.N.is_perpendicular(obj.vL)
#     #     else:
#     #         raise TypeError

#     def equals(self,plane):
#         """Tests if this plane and the supplied plane occupy the same geometric space.

#         :param plane: A 3D plane.
#         :type plane: Plane

#         :return: True if the normal vectors are collinear and
#             a point can exist on both planes;
#             otherwise False.
#         :rtype: bool

#         """
#         if isinstance(plane,Plane):
#             return (self.N.is_collinear(plane.N)
#                     and math.isclose(abs(self.signed_distance_to_point(plane.P0)),
#                                      0,
#                                      abs_tol=ABS_TOL))
#         else:
#             return False

#     def intersection(self,obj):
#         """
#         """
#         if isinstance(obj,Plane):

#             plane=obj

#             if plane.equals(self):

#                 return self

#             elif plane.N.is_collinear(self.N):

#                 return GeometryCollection()  # test with is_empty

#             else:

#                 n1=self.N
#                 d1=-n1.dot(self.P0.subtract(Point(0,0,0)))
#                 n2=plane.N
#                 d2=-n2.dot(plane.P0.subtract(Point(0,0,0)))
#                 n3=n1.cross_product(n2)
#                 v=n1.scale(d2).subtract(n2.scale(d1))
#                 v1=v.cross_product(n3)
#                 v2=v1.scale((1 / (n3.length3D**2)))
#                 P0=Point(v2.coords[0])
#                 u=n3
#                 return Line(P0,u)

#         elif isinstance(obj,Line):

#             return self._intersection_line(obj)

#         else:
#             raise Exception  # not implemented yet

#     @property
#     def is_empty(self):
#         ""
#         return self.P0 is None and self.N is None

#     @property
#     def N(self):
#         """The vector normal to the plane.

#         :rtype: Vector

#         """
#         return self._N

#     @property
#     def P0(self):
#         """The start point of the plane.

#         :rtype: Point

#         """
#         return self._P0

#     def point_xy(self,x,y):
#         """Returns a 3D point on the plane given as x and y coordinates.

#         :param x: An x-coordinate.
#         :type x: float
#         :param y: A y-coordinate.
#         :type y: float

#         :raises ValueError: If there are no points on the plane with the xy values.

#         :rtype: Point

#         .. rubric:: Code Example

#         .. code-block:: python

#            >>> from crossproduct import Point, Vector, Plane
#            >>> pn = Plane(Point(0,0,0), Vector(1,1,1))
#            >>> result = pn.point_xy(1,1)
#            >>> print(result)
#            Point(1.0,1.0,-2.0)

#         """
#         try:
#             z=self.P0.z-(self.N.x*(x-self.P0.x)+self.N.y*(y-self.P0.y))/self.N.z
#         except ZeroDivisionError:
#             raise ValueError('xy points (%s,%s) must exist on the plane.' % (x,y))
#         return Point(x,y,z)

#     def point_yz(self,y,z):
#         """Returns a 3D point on the plane given as y and z coordinates.

#         :param y: A y-coordinate.
#         :type y: float
#         :param z: A z-coordinate.
#         :type z: float

#         :raises ValueError: If there are no points on the plane with the yz values.

#         :rtype: Point

#         .. code-block:: python

#            >>> from crossproduct import Point, Vector, Plane
#            >>> pn = Plane(Point(0,0,0), Vector(1,1,1))
#            >>> result = pn.point_yz(1,1)
#            >>> print(result)
#            Point(-2.0,1.0,1.0)

#         """
#         try:
#             x=self.P0.x-(self.N.y*(y-self.P0.y)+self.N.z*(z-self.P0.z))/self.N.x
#         except ZeroDivisionError:
#             raise ValueError('yz points (%s,%s) must exist on the plane.' % (y,z))
#         return Point(x,y,z)

#     def point_zx(self,z,x):
#         """Returns a 3D point on the plane given as z and x coordinates.

#         :param z: A z-coordinate.
#         :type z: float
#         :param x: An x-coordinate.
#         :type x: float

#         :raises ValueError: If there are no points on the plane with the zx values.

#         :rtype: Point

#         .. rubric:: Code Example

#         .. code-block:: python

#            >>> from crossproduct import Point, Vector, Plane
#            >>> pn = Plane(Point(0,0,0), Vector(1,1,1))
#            >>> result = pn.point_zx(1,1)
#            >>> print(result)
#            Point(1.0,-2.0,1.0)

#         """
#         try:
#             y=self.P0.y-(self.N.z*(z-self.P0.z)+self.N.x*(x-self.P0.x))/self.N.y
#         except ZeroDivisionError:
#             raise ValueError('zx points (%s,%s) must exist on the plane.' % (z,x))
#         return Point(x,y,z)

#     def signed_distance_to_point(self,point):
#         """Returns the signed distance to the supplied point.

#         :param point: A 3D point.
#         :type point:  Point

#         :return: The signed distance between the plane and the point.
#             The return value is positive for one side of the plane
#             (the side in the direction of the normal) and is negative for
#             the other side.
#         :rtype: float

#         """
#         v=point.subtract(self.P0) # vector_from_points(self.P0,point)
#         return self.N.dot(v) / self.N.length3D

# class Polygon(shapely.geometry.Polygon):
#     """Extends the shapely Polygon class
#     """

#     @property
#     def area3D(self):
#         ""
#         plane=self.plane
#         N=plane.N
#         i=plane.N.index_largest_absolute_coordinate
#         self_2D=self.project_2D(i)
#         if i==0:
#             return abs(self_2D.area*(N.length/(N.x)))
#         elif i==1:
#             return abs(self_2D.area*(N.length/(N.y)))
#         elif i==2:
#             return abs(self_2D.area*(N.length/(N.z)))
#         else:
#             raise Exception

#     def intersection(self,obj):
#         ""
#         shapely_polygon=shapely.geometry.Polygon(self)
#         result=shapely_polygon.intersection(obj)
#         return shapely_plus_object_from_shapely_object(result)

#     def intersection3D(self,obj):
#         """The geometric intersection between self and obj.

#         :param obj: A geometric object.

#         :returns: A tuple of the difference objects.
#         :rtype: tuple

#         """
#         if isinstance(obj,Polygon):

#             polygon=obj
#             a=self.plane.intersection(polygon.plane) # returns () or (Line,) or (Plane,)
#             #print(a)
#             if a.is_empty: # polygon planes do not intersect
#                 return Polygon()

#             elif isinstance(a,Plane): # polygons lie on the same plane

#                 i=self.plane.N.index_largest_absolute_coordinate
#                 self_2D=self.project_2D(i)
#                 polygon_2D=polygon.project_2D(i)
#                 result=self_2D.intersection(polygon_2D)
#                 if isinstance(result,Polygon):
#                     return result.project_3D(self.plane,i)
#                 else:
#                     raise NotImplementedError # to fix for other intersection options

#             elif isinstance(a,Line): # the intersection of the two polygon planes as a line

#                 line=a
#                 x=self.intersection3D(line)
#                 y=polygon.intersection3D(line)

#                 result=[]
#                 for x1 in x:
#                     for y1 in y:
#                         result.extend(x1.intersection3D(y1))

#                 return shapely.geometry.GeometryCollection(result)

#         else:
#             raise NotImplementedError

#         # if isinstance(obj,Line):
#         #     return self._intersection_line_3D(obj)
#         # elif isinstance(obj,Polygons):
#         #     return self._intersection_polygons_3D(obj)
#         # else:
#         #     raise Exception  # not implemented yet

#     @property
#     def plane(self):
#         """Returns the plane of the 3D polygon

#         :return plane: a 3D plane which contains all the polygon points
#         :rtype: Plane3D

#         """
#         if self.has_z:
#             exterior_coords=list(self.exterior.coords)
#             for i in range(0,len(exterior_coords)+1-3):
#                 c0,c1,c2=exterior_coords[i:i+3]
#                 v1=Point(c1).subtract(Point(c0)) #vector_from_points(c0,c1)
#                 v2=Point(c2).subtract(Point(c1)) #vector_from_points(c1,c2)
#                 N=v1.cross_product(v2)
#                 if N.length3D>ABS_TOL:
#                     return Plane(exterior_coords[0],N)
#             raise ValueError
#         else:
#             raise ValueError

#     def project_2D(self,coordinate_index):
#         """Projects the object on a 2D plane.

#         :rtype: Polygon

#         """

#         def _project_2D_exterior(polygon,coordinate_index):
#             """Returns the exterior of the polygon projected onto a 2D plane.

#             :rtype: list (coords)

#             """
#             points3D=[Point(x) for x in polygon.exterior.coords]
#             points2D=[x.project_2D(coordinate_index) for x in points3D]
#             return [x.coords[0] for x in points2D]

#         exterior_coords2D=_project_2D_exterior(self, coordinate_index)
#         holes_coords2D=\
#             [_project_2D_exterior(Polygon(hole), coordinate_index)
#              for hole in self.interiors]
#         polygon2D=Polygon(exterior_coords2D,
#                           holes=holes_coords2D)
#         return polygon2D

#     def project_3D(self,plane,coordinate_index):
#         """Projects the 2D polygon on a 3D plane.

#         :rype: Polygon

#         """

#         def _project_3D_exterior(polygon,plane,coordinate_index):
#             """Returns the exterior of the polygon projected onto a 3D plane.

#             :rtype: list (coords)
#             """
#             points2D=[Point(x) for x in polygon.exterior.coords]
#             points3D=[x.project_3D(plane,coordinate_index) for x in points2D]
#             return [x.coords[0] for x in points3D]

#         exterior_coords3D=_project_3D_exterior(self, plane, coordinate_index)
#         holes_coords3D=\
#             [_project_3D_exterior(Polygon(hole), plane, coordinate_index)
#              for hole in self.interiors]
#         polygon3D=Polygon(exterior_coords3D,
#                           holes=holes_coords3D)
#         return polygon3D

# # def vector_from_points(P0,P1):
# #     """Returns the vector from P0 to P1
# #     """
# #     P0=Point(P0)
# #     P1=Point(P1)
# #     return Vector(P1.x-P0.x, P1.y-P0.y, P1.z-P0.z)

# def shapely_plus_object_from_shapely_object(obj):
#     """Converts a shapely object to a shapely_plus object
#     """

#     if isinstance(obj,shapely.geometry.Point):
#         return Point(obj)

#     elif isinstance(obj,shapely.geometry.Polygon):
#         return Polygon(obj)

#     else:
#         raise NotImplementedError(type(obj))


import numpy as np
import joblib    # 版本0.22之后的scikit_learn中就除掉了joblib这个函数或包。 需要直接下载joblib这个包
from sklearn.preprocessing import normalize

testx = np.loadtxt("../dataset/testx.txt")                                     # 导入测试数据
testy = np.loadtxt("../dataset/testy.txt")
x_test = np.array([[x[2],x[6],x[9], x[13],x[17]]for x in testx])    # 特征选择以后的特征,这些特征是我们提前用特征选择算法优化而来
y_vals_test = np.array(testy).astype(int) -1
x_vals_test = normalize(x_test,axis=0,norm='max')                   # 特征归一化

'''导入各个模型'''
ADA = joblib.load('../model/adaboost.model')
SVM = joblib.load('../model/svm.model')
LR  = joblib.load('../model/logistic.model')
KNN = joblib.load('../model/knn.model')
TREE   = joblib.load('../model/tree.model')
FOREST = joblib.load('../model/forest.model')

'''计算各个模型测试结果（以概率矩阵形式保存）可以与 predict 函数做对比'''
adaboost = ADA.predict_proba(x_vals_test)
svm = SVM.predict_proba(x_vals_test)
lr  = LR.predict_proba(x_vals_test)
knn = KNN.predict_proba(x_vals_test)
tree   = TREE.predict_proba(x_vals_test)
forest = FOREST.predict_proba(x_vals_test)


import dbus

import threading

import logging

from gi.repository import GLib, Polkit

from dbus.mainloop.glib import DBusGMainLoop

authority = Polkit.Authority.get_sync()

logger = logging.getLogger(__name__)

DBusGMainLoop(set_as_default=True)
BUS = dbus.SystemBus()

class LoopWithTimeout():
	"""
	A LoopWithTimeout is a GLibMainLoop that supports timeouts.

	We don't directly subclass GLib.MainLoop because we can't.
	"""

	def __init__(self, timeout_length):
		"""
		Initializes the loop.
		"""

		self.loop = GLib.MainLoop()

		self.connected_clients = []

		self.timeout = 0
		self.timeout_length = timeout_length

		self.add_timeout()

	def add_client(self, client):
		"""
		Adds a client to the connected_clients.
		"""

		logger.debug("Client %s connected" % client)
		self.connected_clients.append(client)

		# Subscribe to changes
		BUS.add_signal_receiver(
			handler_function=self.on_name_owner_changed,
			signal_name="NameOwnerChanged",
			dbus_interface="org.freedesktop.DBus",
			path="/org/freedesktop/DBus",
			arg0=client
		)

	def on_name_owner_changed(self, name, old_owner, new_owner):
		"""
		A callback for NameOwnerChanged that can be hooked on the bus.

		This callback is reponsible to update the self.connected_clients
		list so that the MainLoop stays aware of the connected clients.

		Note that this method doesn't do any filtering.
		"""

		if not name.startswith(":") or new_owner:
			# Us or new_owner (wtf), discard
			return

		if name in self.connected_clients:
			# Client disconnection
			logger.debug("Client %s disconnected" % name)
			self.connected_clients.remove(name)

			# Unsubscribe to changes
			BUS.remove_signal_receiver(
				handler_or_match=self.on_name_owner_changed,
				signal_name="NameOwnerChanged",
				dbus_interface="org.freedesktop.DBus",
				path="/org/freedesktop/DBus",
				arg0=old_owner
			)

	def on_timeout_elapsed(self):
		"""
		Fired when the timeout elapsed.
		"""

		if len(threading.enumerate()) > 1 or self.connected_clients:
			# Threads still running or there still are connected clients,
			# delay the timeout
			return True

		self.quit()

		return False

	def remove_timeout(self):
		"""
		Removes the timeout.
		"""

		if self.timeout > 0:
			# Timeout already present, cancel it
			GLib.source_remove(self.timeout)
			self.timeout = 0

	def add_timeout(self):
		"""
		Timeout.
		"""

		if not self.timeout:
			self.timeout = GLib.timeout_add_seconds(self.timeout_length, self.on_timeout_elapsed)

	def __getattr__(self, name):
		"""
		Proxy to the self.loop object.
		"""

		return getattr(self.loop, name)

def get_user(sender):
	"""
	Returns the user name of the given sender.
	"""

	return dbus.Interface(
		dbus.SystemBus().get_object(
			"org.freedesktop.DBus",
			"/org/freedesktop/DBus"
		),
		"org.freedesktop.DBus"
	).GetConnectionUnixUser(sender)

def is_authorized(sender, connection, privilege, user_interaction=True):
	"""
	Checks if the sender has the given privilege.

	Returns True if yes, False if not.
	"""

	if not user_interaction:
		flags = Polkit.CheckAuthorizationFlags.NONE
	else:
		flags = Polkit.CheckAuthorizationFlags.ALLOW_USER_INTERACTION

	# Get PID
	pid = dbus.Interface(
		BUS.get_object(
			"org.freedesktop.DBus",
			"/org/freedesktop/DBus"
		),
		"org.freedesktop.DBus"
	).GetConnectionUnixProcessID(sender)

	try:
		result = authority.check_authorization_sync(
			Polkit.UnixProcess.new(pid),
			privilege,
			None,
			flags,
			None
		)
	except:
		return False

	return result.get_is_authorized()

MainLoop = LoopWithTimeout(5 * 60)


"""Change User. Add Product

Revision ID: 76cb9255d399
Revises:
Create Date: 2020-10-22 13:10:57.188016

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '76cb9255d399'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('product',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('title', sa.String(length=128), nullable=True),
    sa.Column('price', sa.Integer(), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.add_column('user', sa.Column('username', sa.String(length=128), nullable=True))
    op.create_unique_constraint(None, 'user', ['username'])
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint(None, 'user', type_='unique')
    op.drop_column('user', 'username')
    op.drop_table('product')
    # ### end Alembic commands ###


from dash import dash_table, html
import dash_bootstrap_components as dbc
from dash_iconify import DashIconify
import dash_mantine_components as dmc

def multiselect(select_options, default_value, select_label, select_id):
    return dmc.MultiSelect(
        data=select_options,
        value=default_value,
        label=select_label,
        icon=DashIconify(icon='flat-color-icons:line-chart'),
        searchable=True,
        nothingFound="Dataset not found",
        className='general-multiselect',
        id=select_id
    )

def year(input_label, default_value, input_id, min_year=1940, max_year=2023):
    return dmc.NumberInput(
        label=input_label,
        value=default_value,
        min=min_year,
        max=max_year,
        step=1,
        icon=DashIconify(icon='clarity:date-line'),
        className='general-number-input',
        id=input_id
    )

def button(button_label, button_id, icon=None):
    return dmc.Button(
        button_label,
        leftIcon=icon,
        variant="gradient",
        gradient={'from': 'grape', 'to': 'gray', 'deg': 45},
        className='general-button',
        id=button_id
    )

def checkbox(checkbox_label, checkbox_id):
    return dmc.Checkbox(
        label=checkbox_label,
        checked=False,
        className='general-checkbox',
        id=checkbox_id
    )

def select(select_options, default_value, select_label, select_id):
    return dmc.Select(
        data=select_options,
        value=default_value,
        label=select_label,
        searchable=True,
        nothingFound="Option not found",
        className='general-select',
        id=select_id
    )

def accordion_header(displayed_text):
    return dmc.Badge(
        displayed_text,
        variant='gradient',
        gradient={'from': 'rgb(192, 135, 192)', 'to': 'rgb(106, 79, 101)'},
        opacity=0.99,
        size='lg',
        radius='md',
        style={'width': '100%'}
    )

def request_data_header(text):
    return dmc.Text(
        text,
        size=18,
        weight=600,
        align='center'
    )

def request_data_column(header_text, year_inputs, button, *multiselects):
    return dbc.Col(
        dbc.Stack(
            [
                dmc.Text(header_text, size=18, weight=600, align='center'),
                *multiselects,
                dbc.Stack(
                    year_inputs,
                    direction='horizontal',
                    gap=3
                ),
                button
            ],
            gap=3
        )
    )

def icon(icon_name):
    return DashIconify(
        icon=icon_name,
        color="white",
        width=24
    )

def datatable(header, total_null_values, null_columns_string, df):
    return [
        dmc.Text(header, weight=550, size='lg', style={'margin-top': '10px', 'margin-bottom': '10px'}),
        dmc.Text(total_null_values, weight=410, size='sm', className='general-text'),
        dmc.Text(null_columns_string, weight=410, size='sm', className='general-text'),
        html.Div(
            dash_table.DataTable(
                data=df.to_dict('records'),
                columns=[{'name': str(i), 'id': str(i)} for i in df.columns],
                fill_width=False,
                cell_selectable=False,
                style_as_list_view=True,
                style_header={
                    'color': 'rgba(220, 220, 220, 0.95)',
                    'padding': '10px',
                    'fontFamily': 'Arial, sans-serif',
                    'fontSize': '14px',
                    'fontWeight': 'bold'
                },
                style_data={'color': 'rgba(220, 220, 220, 0.85)'},
                style_cell={'fontFamily': 'Arial, sans-serif', 'fontSize': '14px'},
                style_cell_conditional=[{'textAlign': 'center'}],
                id='monthly_dash_table'
            )
        )
    ]


# Import library
import datetime
import pandas as pd
import matplotlib.pyplot as plt
# Baca dataset
dataset = pd.read_csv('retail_raw_reduced.csv')
# Buat kolom baru yang bertipe datetime dalam format '%Y-%m'
dataset['order_month'] = dataset['order_date'].apply(lambda x: datetime.datetime.strptime(x, "%Y-%m-%d").strftime('%Y-%m'))
# Buat Kolom GMV
dataset['gmv'] = dataset['item_price']*dataset['quantity']
print(dataset.info)
#membuat subset data
dataset_dki_q4 = dataset[(dataset['province']=='DKI Jakarta')&(dataset['order_month']>='2019-10')]
print(dataset_dki_q4.info)
#membuat pie chart
import matplotlib.pyplot as plt
gmv_per_city_dki_q4 = dataset_dki_q4.groupby('city')['gmv'].sum().reset_index()
plt.figure(figsize=(6,6))
plt.pie(gmv_per_city_dki_q4['gmv'],labels=gmv_per_city_dki_q4['city'],autopct='%1.2f%%')
plt.title('GMV Contribution Per City - DKI Jakarta in Q4 2019',loc='center',pad=30,fontsize=15, color='blue')
plt.show()
#Membuat Bar Chart
import matplotlib.pyplot as plt
plt.clf()
dataset_dki_q4.groupby('city')['gmv'].sum().sort_values(ascending=False).plot(kind='bar',color='green')
plt.title('GMV Per City - DKI Jakarta in Q4 2019',loc='center',pad=30,fontsize=15,color='blue')
plt.xlabel('City',fontsize=15)
plt.ylabel('Total Amount (in Billions)',fontsize=15)
plt.ylim(ymin=0)
labels, locations= plt.yticks()
plt.yticks(labels, (labels/1000000000).astype(int))
plt.xticks(rotation=0)
plt.show()
'''
#horizontal
import matplotlib.pyplot as plt
plt.clf()
dataset_dki_q4.groupby('city')['gmv'].sum().sort_values(ascending=False).plot(kind='barh',color='green')
plt.title('GMV Per City - DKI Jakarta in Q4 2019',loc='center',pad=30,fontsize=15,color='blue')
plt.xlabel('City',fontsize=15)
plt.ylabel('Total Amount (in Billions)',fontsize=15)
plt.ylim(ymin=0)
labels, locations= plt.yticks()
plt.yticks(labels, (labels/1000000000).astype(int))
plt.xticks(rotation=0)
plt.show() '''
#Membuat Multi-Bar Chart
import matplotlib.pyplot as plt
dataset_dki_q4.groupby(['city','order_month'])['gmv'].sum().unstack().plot(kind='bar')
plt.title('GMV Per City, Breakdown by Month\nDKI Jakarta in Q4 2019',loc='center',pad=30,fontsize=15,color='blue')
plt.xlabel('Province',fontsize=12)
plt.ylabel('Total Amount (in Billions)',fontsize=12)
plt.legend(bbox_to_anchor=(1,1), shadow=True, title='Month')
plt.ylim(ymin=0)
labels, locations=plt.yticks()
plt.yticks(labels, (labels/1000000000).astype(int))
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
#Membuat Stacked Chart
import matplotlib.pyplot as plt
dataset_dki_q4.groupby(['order_month','city'])['gmv'].sum().sort_values(ascending=False).unstack().plot(kind='bar',stacked=True)
plt.title('GMV Per Month, Breakdown by City\nDKI Jakarta in Q4 2019',loc='center',pad=30,fontsize=15,color='blue')
plt.xlabel('Order Month',fontsize=12)
plt.ylabel('Total Amount (in Billions)',fontsize=12)
plt.legend(bbox_to_anchor=(1,1),shadow=True, ncol=1,title='City')
plt.ylim(ymin=0)
labels,locations=plt.yticks()
plt.yticks(labels, (labels/1000000000).astype(int))
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()

5. Menghasilkan Tipe Visualisasi Data yang Tepat/2. Part2/2. membuat bar dan pie chart.py


from typing import List, Dict, Tuple

from livelossplot.main_logger import MainLogger, LogItem
from livelossplot.outputs.base_output import BaseOutput

class BokehPlot(BaseOutput):
    """Simple plugin for a bokeh framework"""
    def __init__(
        self,
        max_cols: int = 2,
        skip_first: int = 2,
        cell_size: Tuple[int, int] = (400, 300),
        output_file: str = './bokeh_output.html'
    ):
        """
        Args:
            max_cols: max number of charts in one row
            skip_first: flag, skip first log
            cell_size: size of one chart
            output_file: file to save the output
        """
        from bokeh import plotting, io, palettes
        self.plotting = plotting
        self.io = io
        self.plot_width, self.plot_height = cell_size
        self.max_cols = max_cols
        self.skip_first = skip_first  # think about it
        self.figures = {}
        self.is_notebook = False
        self.output_file = output_file
        self.colors = palettes.Category10[10]

    def send(self, logger: MainLogger) -> None:
        """Draw figures with metrics and show"""
        log_groups = logger.grouped_log_history()
        new_grid_plot = False
        for idx, (group_name, group_logs) in enumerate(log_groups.items(), start=1):
            fig = self.figures.get(group_name)
            if not fig:
                fig = self.plotting.figure(title=group_name)
                new_grid_plot = True
            self.figures[group_name] = self._draw_metric_subplot(fig, group_logs)
        if new_grid_plot:
            self._create_grid_plot()
        if self.is_notebook:
            self.io.push_notebook(handle=self.target)
        else:
            self.plotting.save(self.grid)

    def _draw_metric_subplot(self, fig, group_logs: Dict[str, List[LogItem]]):
        """
        Args:
            fig: bokeh Figure
            group_logs: groups with list of log items

        Notes:
            for now, with local imports, no output annotation  -> self.plotting.Figure
            there used to be skip first part, but I skip it first
        """
        from bokeh.models import ColumnDataSource, HoverTool
        for i, (name, logs) in enumerate(group_logs.items()):
            if len(logs) > 0:
                source = ColumnDataSource(
                    data={
                        'step': [log.step for log in logs],
                        'value': [log.value for log in logs],
                    }
                )
                fig.line(x='step', y='value', color=self.colors[i], legend_label=name, source=source)

        fig.add_tools(
            HoverTool(
                tooltips=[
                    ('step', '@step'),
                    ('value', '@value{0.3f}'),
                ],
                formatters={
                    'step': 'printf',
                    'value': 'printf',
                },
                mode='vline'
            )
        )
        return fig

    def _create_grid_plot(self):
        rows = []
        row = []
        for idx, fig in enumerate(self.figures.values(), start=1):
            row.append(fig)
            if idx % self.max_cols == 0:
                rows.append(row)
                row = []
        self.grid = self.plotting.gridplot(
            rows, sizing_mode='scale_width', width=self.plot_width, height=self.plot_height
        )
        self.target = self.plotting.show(self.grid, notebook_handle=self.is_notebook)

    def _set_output_mode(self, mode: str):
        """Set notebook or script mode"""
        self.is_notebook = mode == 'notebook'
        if self.is_notebook:
            self.io.output_notebook()
        else:
            self.io.output_file(self.output_file)


from keras.models import Sequential
from keras.layers import Dense, Conv2D
from keras.layers import MaxPool2D, Flatten
from keras import optimizers, losses

from keras.models import Model
from keras.models import Input

import keras

import sys
from load_mnist_data import load_mnist_data_vectors

def MLP(input_shape, num_classes):
    '''
    model = Sequential([
        Dense(64, activation='relu', input_shape=input_shape),
        Dense(32, activation='relu'),
        Dense(num_classes, activation='softmax')
    ])
    '''

    inputs = Input(shape=input_shape)
    x = Dense(64, activation='relu')(inputs)
    x = Dense(32, activation='relu')(x)
    outputs = Dense(num_classes, activation='softmax')(x)
    model = Model(inputs=inputs, outputs=outputs)

    return model

if __name__ == '__main__':
    (x_train, y_train), (x_test, y_test) = load_mnist_data_vectors()

    epochs = 3
    batch_size = 128

    input_shape = (784,)
    num_classes = 10

    model = MLP(input_shape, num_classes)

    model.compile(loss=losses.categorical_crossentropy,
                  optimizer=optimizers.SGD(lr=0.01),
                  metrics=['accuracy'])

    tensorboard = keras.callbacks.TensorBoard(log_dir='./logs_mlp')
    checkpointer = keras.callbacks.ModelCheckpoint(filepath='./ckpt_mlp/weight.{epoch:02d}-{val_loss:.2f}.h5',
                                                   monitor='val_loss')

    history = model.fit(x_train, y_train,
                        batch_size=batch_size,
                        epochs=epochs,
                        verbose=1,
                        callbacks=[tensorboard, checkpointer],
                        validation_data=(x_test, y_test))

    model.save('model/mnist_mlp.h5')

    del model
    model = keras.models.load_model('model/mnist_mlp.h5')

    score = model.evaluate(x_test, y_test, verbose=0)
    print('Test loss:', score[0])
    print('Test accuracy:', score[1])


import numpy as np
import torch

import matplotlib.pyplot as plt

import convex_nn
import two_layer_relu

def plot_decision(X, w1, w2, title):

    x_mesh, y_mesh = np.meshgrid(np.linspace(np.min(X[:,0])-0.2, np.max(X[:,0])+0.2, 100), np.linspace(np.min(X[:,1])-0.2, np.max(X[:,1])+0.2, 100))

    X_mesh = np.vstack((x_mesh.flatten(), y_mesh.flatten(), np.ones_like(x_mesh).flatten())).T
    z_mesh = (convex_nn.relu(X_mesh@w1)@w2).reshape(x_mesh.shape) > 0

    plt.figure(figsize=[5,5])
    plt.contourf(x_mesh, y_mesh, z_mesh, levels=1)
    plt.plot(X[y>0,0], X[y>0,1], 'bo')
    plt.plot(X[y<0,0], X[y<0,1], 'ro')

    t = np.linspace(0, 2*np.pi, 100)
    plt.plot(np.sin(t), np.cos(t), ':k')
    plt.title(title)

    plt.savefig(title+'.pdf')

n = 50 # number of points
d = 3 # dimension of data including ones
m = 50 # number of neurons
beta = 1e-4

# generate data
np.random.seed(123)
X = np.random.randn(n,d-1)
X = np.append(X,np.ones((n,1)),axis=1)

y = ((np.linalg.norm(X[:,0:d-1],axis=1)>1)-0.5)*2

# construct loss function
loss_fn = two_layer_relu.get_hinge_loss(n, n, beta=beta)

# train convex nn
w1, w2 = convex_nn.train(X, y, m=50, beta=beta, oversample=True)

cvx_model = two_layer_relu.NonConvexRelu(d, m=50)

cvx_model.l1.weight.data = torch.Tensor(w1.T)
cvx_model.l2.weight.data = torch.Tensor(w2.T)

cvx_loss = loss_fn(cvx_model(torch.Tensor(X)).flatten(), torch.Tensor(y), cvx_model).item()
print("cvx model loss: ", cvx_loss)

plot_decision(X, w1, w2, title='binary_cvx')

# train nonconvex nn
losses, ncvx_model = two_layer_relu.train(X, y, m, beta, lr=1e-2)

ncvx_loss = loss_fn(ncvx_model(torch.Tensor(X)).flatten(), torch.Tensor(y), ncvx_model).item()
print("non-convex model loss: ", ncvx_loss)

w1 = ncvx_model.l1.weight.data.numpy().T
w2 = ncvx_model.l2.weight.data.numpy().T

plot_decision(X, w1, w2, title='binary_SGD')

# plot training loss
plt.figure()
plt.semilogy(losses, label='SGD')
plt.plot([0, len(losses)], [cvx_loss, cvx_loss], label='convex program')
plt.xlabel('epochs')
plt.ylabel('training loss')
plt.legend()
plt.savefig('binary_training_loss.pdf')

plt.show()


import sys
from sakura.common.streams import on_end_of_redirection

CURRENT_PDB_SESSION = None

def hook_pdb():
    import pdb
    class HookedPdb(pdb.Pdb):
        def set_trace(self, frame):
            global CURRENT_PDB_SESSION
            if CURRENT_PDB_SESSION is not None and CURRENT_PDB_SESSION is not self:
                print('-- pdb.set_trace() FAILED. --')
                print('Another developer is currently running a pdb session on the same sakura daemon.')
                print('Cannot run another concurrent session, sorry. Please retry later.')
                print('Continuing execution.')
                print('-----------------------------')
                return None
            CURRENT_PDB_SESSION = self
            on_end_of_redirection(self.end_of_redirection)
            super().set_trace(frame)
        def end_of_redirection(self):
            sys.__stdout__.write("pdb: end of stdin/out/err redirection -- forcing 'continue' command!\n")
            self.cmdqueue.append('cont')
            on_end_of_redirection(None)
        def _release_session(self):
            global CURRENT_PDB_SESSION
            if CURRENT_PDB_SESSION is self:
                CURRENT_PDB_SESSION = None
                on_end_of_redirection(None)
        def set_continue(self):
            self._release_session()
            return super().set_continue()
        def __del__(self):
            self._release_session()
            if hasattr(super(), '__del__'):
                super().__del__()

    pdb.Pdb = HookedPdb


# StyleGAN2 - https://arxiv.org/abs/1912.04958
# Not implemented features
# - Path length regularization
#
# Code reference:
# https://github.com/NVlabs/stylegan2

import math
from dataclasses import dataclass, replace
from typing import Optional

import torch
import torch.nn.functional as F
from torch import Tensor, nn

from .nvidia_ops import Blur
from .progressive_gan import ProgressiveGANDiscriminator, init_weights
from .stylegan import MappingNetwork, StyleGANConfig

batched_conv2d = torch.vmap(F.conv2d)

@dataclass
class StyleGAN2Config(StyleGANConfig):
    blur_size: int = 4  # override StyleGAN
    residual_D: bool = True

class StyleGAN2Discriminator(ProgressiveGANDiscriminator):
    def __init__(self, config: Optional[StyleGAN2Config] = None, **kwargs):
        config = config or StyleGAN2Config()
        config = replace(config, **kwargs)
        super().__init__(config)

class ModulatedConv2d(nn.Module):
    def __init__(self, in_dim: int, out_dim: int, kernel_size: int, w_dim: int, demodulation: bool = True):
        super().__init__()
        # to contain weights for initialization and parametrization
        self.conv = nn.Conv2d(in_dim, out_dim, kernel_size, padding=(kernel_size - 1) // 2)
        self.style_weight = nn.Linear(w_dim, in_dim)  # A in paper
        self.demodulation = demodulation

    def forward(self, imgs: Tensor, w_embs: Tensor):
        b, c, _, _ = imgs.shape

        # modulation
        style = self.style_weight(w_embs).view(b, 1, c, 1, 1) + 1
        weight = self.conv.weight.unsqueeze(0) * style
        if self.demodulation:
            weight = weight / torch.linalg.vector_norm(weight, dim=(2, 3, 4), keepdim=True)

        bias = self.conv.bias.view(1, -1).expand(b, -1) if self.conv.bias is not None else None
        imgs = batched_conv2d(imgs, weight, bias, padding=self.conv.padding)
        return imgs

class StyleGAN2GeneratorBlock(nn.Module):
    def __init__(self, in_dim: int, out_dim: int, config: StyleGAN2Config, upsample: bool = False):
        super().__init__()
        # TODO: merge upsample with conv into conv_transpose (up_conv_blur)
        self.up = nn.Upsample(scale_factor=2.0) if upsample else nn.Identity()
        self.conv = ModulatedConv2d(in_dim, out_dim, 3, config.w_dim)
        self.blur = Blur(config.blur_size) if upsample else nn.Identity()
        self.noise_weight = nn.Parameter(torch.tensor(0.0))  # B in paper
        self.act = config.act()

    def forward(self, imgs: Tensor, w_embs: Tensor):
        imgs = self.blur(self.conv(self.up(imgs), w_embs))
        b, _, h, w = imgs.shape
        noise = torch.randn(b, 1, h, w, device=imgs.device)
        return self.act(imgs + noise * self.noise_weight)

class RGBLayer(ModulatedConv2d):
    def __init__(self, in_dim: int, config: StyleGAN2Config):
        super().__init__(in_dim, config.img_channels, 1, config.w_dim, demodulation=False)

class StyleGAN2Generator(nn.Module):
    def __init__(self, config: Optional[StyleGAN2Config] = None, **kwargs):
        config = config or StyleGAN2Config()
        config = replace(config, **kwargs)
        assert config.img_size > 4 and math.log2(config.img_size).is_integer()
        super().__init__()
        self.mapping_network = MappingNetwork(config)

        map_size = config.init_map_size
        in_depth = config.input_depth
        self.learned_input = nn.Parameter(torch.empty(1, in_depth, map_size, map_size))
        self.up_blur = Blur(config.blur_size, up=2)

        depth = config.min_channels * config.img_size // map_size
        out_depth = min(depth, config.max_channels)

        self.first_block = StyleGAN2GeneratorBlock(in_depth, out_depth, config)
        self.first_to_rgb = RGBLayer(out_depth, config)
        in_depth = out_depth
        depth //= 2

        self.stages = nn.ModuleList()
        while map_size < config.img_size:
            out_depth = min(depth, config.max_channels)
            stage = dict(
                block1=StyleGAN2GeneratorBlock(in_depth, out_depth, config, upsample=True),
                block2=StyleGAN2GeneratorBlock(out_depth, out_depth, config),
                to_rgb=RGBLayer(out_depth, config),
            )
            self.stages.append(nn.ModuleDict(stage))
            in_depth = out_depth
            depth //= 2
            map_size *= 2

        self.reset_parameters()

    def reset_parameters(self):
        nn.init.ones_(self.learned_input)
        self.apply(init_weights)

    def forward(self, z_embs: Tensor):
        w_embs = self.mapping_network(z_embs)

        x = self.learned_input.expand(z_embs.size(0), -1, -1, -1)
        x = self.first_block(x, w_embs[:, 0])
        y = self.first_to_rgb(x, w_embs[:, 1])

        for i, stage in enumerate(self.stages):
            x = stage["block1"](x, w_embs[:, (i + 1) * 2])
            x = stage["block2"](x, w_embs[:, (i + 1) * 2 + 1])
            y = stage["to_rgb"](x, w_embs[:, (i + 1) * 2 + 1]) + self.up_blur(y)

        return y


#!/usr/bin/env python3
from minimodem import *
import select
import time
import subprocess
import sys
import configparser

from packet import *
from tunInterface import *

def dispatchIncomingPacketsToTun(minimodem, tun):
    packet = minimodem.getIncomingPackets()
    if packet[0] > 0:
        print('Sending to TUN : '+str(packet[1]))
        tun.writePacket(packet[1][3])

if __name__ == '__main__':
    config = configparser.ConfigParser()
    config.read(sys.argv[1])

    pkt = Packetizer(callsign=config['radio']['callsign'])

    mm = minimodem_wrapper(speed = 1200, interface=config['radio']['audiodevice'])
    tun = tunInterface(config = config['tun'])

    while(True):
        readers, writers, _ = select.select([mm.rxHandler.stdout, mm.rxHandler.stderr, mm.txHandler.stderr, tun.tun], [], [], 5)
        if mm.rxHandler.stderr in readers:
            mm.processRxPacketTags()

        if mm.txHandler.stderr in readers:
            mm.processEndOfTransmission()

        if mm.rxHandler.stdout in readers:
            mm.processRxPacketData()

        if tun.tun in readers:
            data = tun.getPacket()
            packet = pkt.createPacket(data)
            print ("Sending to Radio: "+str(packet))
            mm.send(packet)

        # Send data on timeout
        mm.processTxPackets()

        # Dispatch incoming packets to tun
        dispatchIncomingPacketsToTun(mm, tun)


##################################################
# VOICEVOX　GPUメモリ監視プログラム
##################################################
import os
import win32service
import win32serviceutil
import win32event
import logging
import servicemanager
import socket
import datetime
import time
import subprocess
from subprocess import PIPE
import time
from io import StringIO
import pandas as pd
import psutil

##################################################
# ログ設定
##################################################
today = datetime.date.today()
logging.basicConfig(
    # ログ出力先　※環境によって変更する
    filename = 'C:\\voicevox_engine\\voicevox-gpu-memory-monitor\\log\\{}-app.log'.format(today.strftime('%Y%m%d')),
    level = logging.DEBUG,
    format="%(asctime)s:LINE[%(lineno)s] %(levelname)s %(message)s"
)

##################################################
# PythonServiceクラス
##################################################
class PythonService(win32serviceutil.ServiceFramework):
    # サービス名
    _svc_name_ = 'VoiceVox Engine Service'
    # 表示名(サービス画面にはこれを表示)
    _svc_display_name_ = 'VoiceVox Engine Service'
    # サービスの説明
    _svc_description_ = '一定周期でVoiceVox Engineで使っているGPUメモリを監視する'
    # 実行コマンド ※環境によって変更する
    _exe_path = "C:\\voicevox_engine\\0.14.6\\windows-directml\\run.exe --use_gpu --host 192.168.50.222"
    # メモリ仕使用率閾値　※環境によって変更する
    _threshold = 90
    # インターバル(秒)　※環境によって変更する
    _interval = 10.0
    # VOICEVOX EXE名
    _exe_name = "run.exe"
    # CSV に出力するカラム一覧
    _keys = ["timestamp","memory.total","memory.free","memory.used"]
    # GPUスペック出力カラム一覧
    _spec_keys = ["index","name","memory.total"]

    # 初期化
    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self,args)
        self.stop_event = win32event.CreateEvent(None,0,0,None)
        socket.setdefaulttimeout(60)
        self.stop_requested = False

    # サービス停止
    def SvcStop(self):
        logging.info("execute service stop")
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.stop_event)
        self.stop_requested = True

    # サービス開始
    def SvcDoRun(self):
        logging.info("execute service start")
        servicemanager.LogMsg(
			servicemanager.EVENTLOG_INFORMATION_TYPE,
			servicemanager.PYS_SERVICE_STARTED,
			(self._svc_name_,'')
			)
        # メイン処理スタート
        self.main_loop()

    # GPUスペック出力関数
    def get_gpu_specs(self):
        output = self.get_gpu_info(self._spec_keys)
        output = pd.read_csv(StringIO(output), names=self._spec_keys)
        return output

    # GPU情報出力関数
    def get_gpu_info(self,keys=_keys, no_units=True):
        queries = ",".join(keys)
        output_fmt = "csv,noheader"
        if no_units:
            output_fmt += ",nounits"

        cmd = f"nvidia-smi --query-gpu={queries} --format={output_fmt}"
        output = subprocess.check_output(cmd, shell=True).decode()
        # カンマの後の空白を削除する
        output = output.replace(", ", ",")
        return output

    # メイン処理
    def main_loop(self):

        logging.info("service start successful")

        # GPU の一覧を取得
        gpu_specs = self.get_gpu_specs()

        # 初期画面表示
        logging.info("-----------------------------------------------------------------------------")
        logging.info("VOICEVOX GPU memory monitoring service")
        logging.info("--description--")
        logging.info(" GPU memory use rate" + str(self._threshold) + "%over VOICEVOX run.exe restart")
        logging.info(" GPU memory use rate" + str(self._interval) + "sec intarvel monitoring")
        logging.info("-----------------------------------------------------------------------------")
        logging.info("--GPU info--")
        logging.info(gpu_specs.head(1))
        logging.info("-----------------------------------------------------------------------------")
        logging.info("--exe path--")
        logging.info(self._exe_path)
        logging.info("-----------------------------------------------------------------------------")

        while True:
            if self.stop_requested:
                logging.info("service stopping")
                break
            try:
                time.sleep(self._interval)
                # logging.info('service in progress')
                #プロセス存在フラグ初期化
                process_exist_flg = False
                #使用率初期化
                use_rate = 0
                # GPU の情報を取得
                output = self.get_gpu_info(self._keys)
                # データを分割＆改行削除
                gpu_info = output.replace('\r\n','').split(',')

                # プロセス取得
                for proc in psutil.process_iter():
                    try:
                        # プロセス情報取得
                        processName = proc.name()
                        processID = proc.pid
                        cmdLine = proc.cmdline()
                        cmd_line_str = ' '.join(cmdLine)

                        # プロセス名チェック
                        if(processName == self._exe_name):
                            # プロセスあればフラグTrrue
                            process_exist_flg = True
                            # メモリ使用率計算
                            # memory.used / memory.total * 100
                            use_rate = round(int(gpu_info[3]) / int(gpu_info[1]),2) * 100
                            # 画面表示用
                            logging.info(gpu_info[0] +
                                " - " + str(gpu_info[1]) + "MB" +
                                " - " + str(gpu_info[2]) + "MB" +
                                " - " + str(gpu_info[3]) + "MB" +
                                " - " + str(use_rate) + "%" +
                                " - " + str(processName) +
                                " -  " + str(processID) +
                                " -  " + cmd_line_str)

                            #もし、使用率が閾値を超えたらプロセス再起動
                            if(use_rate > self._threshold):
                                # プロセスIDからプロセス取得
                                p = psutil.Process(processID)

                                #子のterminate
                                pid_list=[pc.pid for pc in p.children(recursive=True)]
                                for pid in pid_list:
                                    psutil.Process(pid).terminate ()
                                    logging.info("terminate child process {}" .format(pid))

                                #親のterminate
                                p.terminate ()
                                logging.info("terminate parent process {}" .format(processID))

                                #再起動
                                logging.info("voicevox process memory overflow ---> restart " + self._exe_path)
                                res_vve = subprocess.Popen(self._exe_path)

                    except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                        pass

                #プロセスが存在しなかったら起動する
                if(process_exist_flg == False):
                    logging.info("voicevox process  not found ---> start " + self._exe_path)
                    res_vve = subprocess.Popen(self._exe_path)

            except Exception as e:
                logging.error("Error occured.")
                logging.error(str(type(e)))
                logging.error(str(e.args))
                logging.error(str(e))

        logging.info("service stop successful")
        return

if __name__ == '__main__':
	win32serviceutil.HandleCommandLine(PythonService)


"""
this project is from an online course available on udemy.com
called "Complete-Python3 Bootcamp"
Milestone Project 2 - Blackjack Game¶
In this milestone project you will be creating a Complete BlackJack Card Game in Python.

Here are the requirements:

You need to create a simple text-based BlackJack game
The game needs to have one player versus an automated dealer.
The player can stand or hit.
The player must be able to pick their betting amount.
You need to keep track of the player's total money.
You need to alert the player of wins, losses, or busts, etc...
#############################################################################################
all code is by George A. Merrill except where otherwise noted
version 001 text based version
version 002 start of OpenGl version
version 003 start of GUI
"""
#################################################################################################
#

from Canvas import Canvas
from PIL import Image
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
import math
import time
from random import shuffle

suits = ['Spades', 'Hearts', 'Clubs', 'Diamonds']
ranks = {'Two': 2, 'Three': 3, 'Four': 4, 'Five': 5, 'Six': 6, 'Seven': 7, 'Eight': 8,
         'Nine': 9, 'Ten': 10, 'Jack': 10, 'Queen': 10, 'King':10, 'Ace': 11}
chips_player = 200
chips_dealer = 1000000
cvs = Canvas(1885, 1025, 'BlackJack')
game_mode = 'start'
current_bet = 0

class Card:
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        return f'{self.rank} of {self.suit}'

class Deck:
    def __init__(self):
        self.cards = []
        for s in suits:
            for r in ranks:
                self.cards.append(Card(s,r))

    def shuffle(self):
        shuffle(self.cards)

def find_total(hand):
    total = 0
    aces = 0
    for r in hand:
        if r.rank == 'Ace':
            aces += 1
        total += ranks[r.rank]
    while total > 21 and aces >= 1:
        total -= 10
        aces -= 1

    return total

def place_bet():
    global current_bet
    global game_mode
    if game_mode == 'bet':
        glColor3f(0, 0, 0)
        glRasterPos2d(650, 500)
        for c in 'Minium bet is 20 chips':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        glRecti(600, 350, 900, 450)
        glRasterPos2d(650, 475)
        for c in 'Place a bet below':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        glRecti(600, 350, 900, 450)
        glColor3f(0.0, .6, 0.)
        glRasterPos2d(700, 400)
        for c in 'Your Current bet:':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        glColor3f(0.9, 0.6, 0.0)
        glRasterPos2d(800, 375)
        for c in f'{current_bet}':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
    pass

def my_mouse(button, state, x, y):
    global game_mode
    if game_mode == 'start':
        game_mode = 'bet'

def my_display():
    global cvs
    cvs.set_window(0, 1885, 0, 1025)
    cvs.set_viewport(0, 1885, 0, 1025)
    if game_mode == 'start':
        cvs.set_bc(0, 0, 0)
        cvs.clear_screen()
        glColor3f(1, 1, 1)
        cvs.thick(8)
        glEnable(GL_TEXTURE_2D)
        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        image = Image.open("res\Splash.png")
        flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
        img_data = flipped_image.convert("RGBA").tobytes()
        # .crop((0, 0, 256, 244))
        # img_data = raw_data
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 500, 300, 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)
        glBindTexture(GL_TEXTURE_2D, texture)
        glBegin(GL_POLYGON)
        glTexCoord2f(0, 0)
        glVertex2i(425, 200)
        glTexCoord2f(0, 1)
        glVertex2i(425, 800)
        glTexCoord2f(1, 1)
        glVertex2i(1425, 800)
        glTexCoord2f(1, 0)
        glVertex2i(1425, 200)
        glEnd()
        glDisable(GL_TEXTURE_2D)
        glColor3f(0.6, 0.0, 0.9)
        glRasterPos2d(825, 100)
        for c in 'Click Anywhere to Start':
                glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
    elif game_mode == 'bet':
        cvs.set_bc(0, 1, 0)
        cvs.clear_screen()
        glColor3f(0, 0, 0)
        glRecti(75,600, 350, 900)
        my_deck = Deck()
        my_deck.shuffle()
        hand_player = []
        hand_dealer = []
        global chips_player
        global chips_dealer
        glColor3f(0.6, 0.0, 0.9)
        glRasterPos2d(100, 850)
        for c in 'Dealers Chips:':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        glColor3f(0.0, .6, 0.)
        glRasterPos2d(100, 750)
        for c in 'Your Chips:':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        glColor3f(0.9, 0.6, 0.0)
        glRasterPos2d(100, 825)
        for c in f'{chips_dealer}':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        glColor3f(0.9, 0.6, 0.0)
        glRasterPos2d(100, 725)
        for c in f'{chips_player}':
            glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ord(c))
        # place bet
        place_bet()

    else:
        pass
    glFlush()
    cvs.swap()

# register the callback functions
glutDisplayFunc(my_display)
glutMouseFunc(my_mouse)
glutMainLoop()


import os
import io
import sys
import time
import datetime
import subprocess
import argparse
from skimage import io
from PIL import Image
import cv2
import numpy as np

import uuid
from utils import *

# python3 predict.py -p ./test --model_path ./models/mobilenetV2_model --gpu -1 --frame_rate 12 --denoise_borders --biggest_side 320

'''parser = argparse.ArgumentParser()
parser.add_argument('-p', '--data_path', type=str, required=True)
parser.add_argument('--model_path', type=str, required=True)
parser.add_argument('--gpu', type=int, default=-1, required=False)
parser.add_argument('--biggest_side', type=int, default=0, required=False)
parser.add_argument('--delay', type=int, default=7, required=False)
parser.add_argument('--frame_rate', type=int, default=12, required=False)
parser.add_argument('--denoise_borders', action='store_true')
args = parser.parse_args()
globals().update(vars(args))
'''
def convert_to_binary_mask(im):
    im=Image.fromarray(im)
    fill_color = (0,0,0)  # your new background color
    im = im.convert("RGBA")   # it had mode P after DL it from OP
    if im.mode in ('RGBA', 'LA'):
        background = Image.new(im.mode[:-1], im.size, fill_color)
        background.paste(im, im.split()[-1]) # omit transparency
        im = background
    im=im.convert("RGB")
    im=np.array(im)
    gray_mask = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    ret, mask = cv2.threshold(gray_mask, 1, 255,cv2.THRESH_BINARY_INV)
    kernel = np.ones((5,5),np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    return mask

def test(data_path,model_path):
    start = time.time()
    biggest_side=0
    #delay=7
    denoise_borders='storetrue'
    gpu=-1
    #frame_rate=12
    biggest_side = None if not biggest_side else biggest_side
    #delay = round(100/frame_rate + .5)

    trainer = Trainer(path=model_path, gpu=gpu)
    if gpu < 0:
        torch.set_num_threads(2)
    trainer.load_state(mode="metric")
    trainer.model.eval()

    path=data_path
    img = cv2.imread(path,1)

    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = np.array(img, dtype=np.uint8)
    out = trainer.predict_mask(img, biggest_side=biggest_side, denoise_borders=denoise_borders)
    name=str(uuid.uuid4())+'.png'
    cv2.imwrite(name,out[0])

    print(" [INFO] %s ms. " % round((time.time()-start)*1000, 0))
    '''destination =os.path.join(path,'result',name)
    save_image(destination,img)
    file_name='result/'+ name '''
    return out[0],name
img,path=test('./much_people.jpg','./mobilenetV2_model/mobilenetV2_model')
convert_to_binary_mask(path)
#!python /content/PicsArtHack-binary-segmentation/predict.py -p /content/t.jpg --model_path /content/my_model/mobilenetV2_model