from __future__ import division, print_function

import math

import numpy

def identity_matrix():
  """Return 4x4 identity/unit matrix.

    >>> I = identity_matrix()
    >>> numpy.allclose(I, numpy.dot(I, I))
    True
    >>> numpy.sum(I), numpy.trace(I)
    (4.0, 4.0)
    >>> numpy.allclose(I, numpy.identity(4))
    True

    """
  return numpy.identity(4)

def translation_matrix(direction):
  """Return matrix to translate by direction vector.

    >>> v = numpy.random.random(3) - 0.5
    >>> numpy.allclose(v, translation_matrix(v)[:3, 3])
    True

    """
  M = numpy.identity(4)
  M[:3, 3] = direction[:3]
  return M

def translation_from_matrix(matrix):
  """Return translation vector from translation matrix.

    >>> v0 = numpy.random.random(3) - 0.5
    >>> v1 = translation_from_matrix(translation_matrix(v0))
    >>> numpy.allclose(v0, v1)
    True

    """
  return numpy.array(matrix, copy=False)[:3, 3].copy()

def reflection_matrix(point, normal):
  """Return matrix to mirror at plane defined by point and normal vector.

    >>> v0 = numpy.random.random(4) - 0.5
    >>> v0[3] = 1.
    >>> v1 = numpy.random.random(3) - 0.5
    >>> R = reflection_matrix(v0, v1)
    >>> numpy.allclose(2, numpy.trace(R))
    True
    >>> numpy.allclose(v0, numpy.dot(R, v0))
    True
    >>> v2 = v0.copy()
    >>> v2[:3] += v1
    >>> v3 = v0.copy()
    >>> v2[:3] -= v1
    >>> numpy.allclose(v2, numpy.dot(R, v3))
    True

    """
  normal = unit_vector(normal[:3])
  M = numpy.identity(4)
  M[:3, :3] -= 2.0 * numpy.outer(normal, normal)
  M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal
  return M

def reflection_from_matrix(matrix):
  """Return mirror plane point and normal vector from reflection matrix.

    >>> v0 = numpy.random.random(3) - 0.5
    >>> v1 = numpy.random.random(3) - 0.5
    >>> M0 = reflection_matrix(v0, v1)
    >>> point, normal = reflection_from_matrix(M0)
    >>> M1 = reflection_matrix(point, normal)
    >>> is_same_transform(M0, M1)
    True

    """
  M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  # normal: unit eigenvector corresponding to eigenvalue -1
  w, V = numpy.linalg.eig(M[:3, :3])
  i = numpy.where(abs(numpy.real(w) + 1.0) < 1e-8)[0]
  if not len(i):
    raise ValueError('no unit eigenvector corresponding to eigenvalue -1')
  normal = numpy.real(V[:, i[0]]).squeeze()
  # point: any unit eigenvector corresponding to eigenvalue 1
  w, V = numpy.linalg.eig(M)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0]
  if not len(i):
    raise ValueError('no unit eigenvector corresponding to eigenvalue 1')
  point = numpy.real(V[:, i[-1]]).squeeze()
  point /= point[3]
  return point, normal

def rotation_matrix(angle, direction, point=None):
  """Return matrix to rotate about axis defined by point and direction.

    >>> R = rotation_matrix(math.pi/2, [0, 0, 1], [1, 0, 0])
    >>> numpy.allclose(numpy.dot(R, [0, 0, 0, 1]), [1, -1, 0, 1])
    True
    >>> angle = (random.random() - 0.5) * (2*math.pi)
    >>> direc = numpy.random.random(3) - 0.5
    >>> point = numpy.random.random(3) - 0.5
    >>> R0 = rotation_matrix(angle, direc, point)
    >>> R1 = rotation_matrix(angle-2*math.pi, direc, point)
    >>> is_same_transform(R0, R1)
    True
    >>> R0 = rotation_matrix(angle, direc, point)
    >>> R1 = rotation_matrix(-angle, -direc, point)
    >>> is_same_transform(R0, R1)
    True
    >>> I = numpy.identity(4, numpy.float64)
    >>> numpy.allclose(I, rotation_matrix(math.pi*2, direc))
    True
    >>> numpy.allclose(2, numpy.trace(rotation_matrix(math.pi/2,
    ...                                               direc, point)))
    True

    """
  sina = math.sin(angle)
  cosa = math.cos(angle)
  direction = unit_vector(direction[:3])
  # rotation matrix around unit vector
  R = numpy.diag([cosa, cosa, cosa])
  R += numpy.outer(direction, direction) * (1.0 - cosa)
  direction *= sina
  R += numpy.array([[0.0, -direction[2], direction[1]], [direction[2], 0.0, -direction[0]],
                    [-direction[1], direction[0], 0.0]])
  M = numpy.identity(4)
  M[:3, :3] = R
  if point is not None:
    # rotation not around origin
    point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
    M[:3, 3] = point - numpy.dot(R, point)
  return M

def rotation_from_matrix(matrix):
  """Return rotation angle and axis from rotation matrix.

    >>> angle = (random.random() - 0.5) * (2*math.pi)
    >>> direc = numpy.random.random(3) - 0.5
    >>> point = numpy.random.random(3) - 0.5
    >>> R0 = rotation_matrix(angle, direc, point)
    >>> angle, direc, point = rotation_from_matrix(R0)
    >>> R1 = rotation_matrix(angle, direc, point)
    >>> is_same_transform(R0, R1)
    True

    """
  R = numpy.array(matrix, dtype=numpy.float64, copy=False)
  R33 = R[:3, :3]
  # direction: unit eigenvector of R33 corresponding to eigenvalue of 1
  w, W = numpy.linalg.eig(R33.T)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0]
  if not len(i):
    raise ValueError('no unit eigenvector corresponding to eigenvalue 1')
  direction = numpy.real(W[:, i[-1]]).squeeze()
  # point: unit eigenvector of R33 corresponding to eigenvalue of 1
  w, Q = numpy.linalg.eig(R)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0]
  if not len(i):
    raise ValueError('no unit eigenvector corresponding to eigenvalue 1')
  point = numpy.real(Q[:, i[-1]]).squeeze()
  point /= point[3]
  # rotation angle depending on direction
  cosa = (numpy.trace(R33) - 1.0) / 2.0
  if abs(direction[2]) > 1e-8:
    sina = (R[1, 0] + (cosa - 1.0) * direction[0] * direction[1]) / direction[2]
  elif abs(direction[1]) > 1e-8:
    sina = (R[0, 2] + (cosa - 1.0) * direction[0] * direction[2]) / direction[1]
  else:
    sina = (R[2, 1] + (cosa - 1.0) * direction[1] * direction[2]) / direction[0]
  angle = math.atan2(sina, cosa)
  return angle, direction, point

def scale_matrix(factor, origin=None, direction=None):
  """Return matrix to scale by factor around origin in direction.

    Use factor -1 for point symmetry.

    >>> v = (numpy.random.rand(4, 5) - 0.5) * 20
    >>> v[3] = 1
    >>> S = scale_matrix(-1.234)
    >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
    True
    >>> factor = random.random() * 10 - 5
    >>> origin = numpy.random.random(3) - 0.5
    >>> direct = numpy.random.random(3) - 0.5
    >>> S = scale_matrix(factor, origin)
    >>> S = scale_matrix(factor, origin, direct)

    """
  if direction is None:
    # uniform scaling
    M = numpy.diag([factor, factor, factor, 1.0])
    if origin is not None:
      M[:3, 3] = origin[:3]
      M[:3, 3] *= 1.0 - factor
  else:
    # nonuniform scaling
    direction = unit_vector(direction[:3])
    factor = 1.0 - factor
    M = numpy.identity(4)
    M[:3, :3] -= factor * numpy.outer(direction, direction)
    if origin is not None:
      M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction
  return M

def scale_from_matrix(matrix):
  """Return scaling factor, origin and direction from scaling matrix.

    >>> factor = random.random() * 10 - 5
    >>> origin = numpy.random.random(3) - 0.5
    >>> direct = numpy.random.random(3) - 0.5
    >>> S0 = scale_matrix(factor, origin)
    >>> factor, origin, direction = scale_from_matrix(S0)
    >>> S1 = scale_matrix(factor, origin, direction)
    >>> is_same_transform(S0, S1)
    True
    >>> S0 = scale_matrix(factor, origin, direct)
    >>> factor, origin, direction = scale_from_matrix(S0)
    >>> S1 = scale_matrix(factor, origin, direction)
    >>> is_same_transform(S0, S1)
    True

    """
  M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  M33 = M[:3, :3]
  factor = numpy.trace(M33) - 2.0
  try:
    # direction: unit eigenvector corresponding to eigenvalue factor
    w, V = numpy.linalg.eig(M33)
    i = numpy.where(abs(numpy.real(w) - factor) < 1e-8)[0][0]
    direction = numpy.real(V[:, i]).squeeze()
    direction /= vector_norm(direction)
  except IndexError:
    # uniform scaling
    factor = (factor + 2.0) / 3.0
    direction = None
  # origin: any eigenvector corresponding to eigenvalue 1
  w, V = numpy.linalg.eig(M)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0]
  if not len(i):
    raise ValueError('no eigenvector corresponding to eigenvalue 1')
  origin = numpy.real(V[:, i[-1]]).squeeze()
  origin /= origin[3]
  return factor, origin, direction

def projection_matrix(point, normal, direction=None, perspective=None, pseudo=False):
  """Return matrix to project onto plane defined by point and normal.

    Using either perspective point, projection direction, or none of both.

    If pseudo is True, perspective projections will preserve relative depth
    such that Perspective = dot(Orthogonal, PseudoPerspective).

    >>> P = projection_matrix([0, 0, 0], [1, 0, 0])
    >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
    True
    >>> point = numpy.random.random(3) - 0.5
    >>> normal = numpy.random.random(3) - 0.5
    >>> direct = numpy.random.random(3) - 0.5
    >>> persp = numpy.random.random(3) - 0.5
    >>> P0 = projection_matrix(point, normal)
    >>> P1 = projection_matrix(point, normal, direction=direct)
    >>> P2 = projection_matrix(point, normal, perspective=persp)
    >>> P3 = projection_matrix(point, normal, perspective=persp, pseudo=True)
    >>> is_same_transform(P2, numpy.dot(P0, P3))
    True
    >>> P = projection_matrix([3, 0, 0], [1, 1, 0], [1, 0, 0])
    >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20
    >>> v0[3] = 1
    >>> v1 = numpy.dot(P, v0)
    >>> numpy.allclose(v1[1], v0[1])
    True
    >>> numpy.allclose(v1[0], 3-v1[1])
    True

    """
  M = numpy.identity(4)
  point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
  normal = unit_vector(normal[:3])
  if perspective is not None:
    # perspective projection
    perspective = numpy.array(perspective[:3], dtype=numpy.float64, copy=False)
    M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective - point, normal)
    M[:3, :3] -= numpy.outer(perspective, normal)
    if pseudo:
      # preserve relative depth
      M[:3, :3] -= numpy.outer(normal, normal)
      M[:3, 3] = numpy.dot(point, normal) * (perspective + normal)
    else:
      M[:3, 3] = numpy.dot(point, normal) * perspective
    M[3, :3] = -normal
    M[3, 3] = numpy.dot(perspective, normal)
  elif direction is not None:
    # parallel projection
    direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False)
    scale = numpy.dot(direction, normal)
    M[:3, :3] -= numpy.outer(direction, normal) / scale
    M[:3, 3] = direction * (numpy.dot(point, normal) / scale)
  else:
    # orthogonal projection
    M[:3, :3] -= numpy.outer(normal, normal)
    M[:3, 3] = numpy.dot(point, normal) * normal
  return M

def projection_from_matrix(matrix, pseudo=False):
  """Return projection plane and perspective point from projection matrix.

    Return values are same as arguments for projection_matrix function:
    point, normal, direction, perspective, and pseudo.

    >>> point = numpy.random.random(3) - 0.5
    >>> normal = numpy.random.random(3) - 0.5
    >>> direct = numpy.random.random(3) - 0.5
    >>> persp = numpy.random.random(3) - 0.5
    >>> P0 = projection_matrix(point, normal)
    >>> result = projection_from_matrix(P0)
    >>> P1 = projection_matrix(*result)
    >>> is_same_transform(P0, P1)
    True
    >>> P0 = projection_matrix(point, normal, direct)
    >>> result = projection_from_matrix(P0)
    >>> P1 = projection_matrix(*result)
    >>> is_same_transform(P0, P1)
    True
    >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=False)
    >>> result = projection_from_matrix(P0, pseudo=False)
    >>> P1 = projection_matrix(*result)
    >>> is_same_transform(P0, P1)
    True
    >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=True)
    >>> result = projection_from_matrix(P0, pseudo=True)
    >>> P1 = projection_matrix(*result)
    >>> is_same_transform(P0, P1)
    True

    """
  M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  M33 = M[:3, :3]
  w, V = numpy.linalg.eig(M)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0]
  if not pseudo and len(i):
    # point: any eigenvector corresponding to eigenvalue 1
    point = numpy.real(V[:, i[-1]]).squeeze()
    point /= point[3]
    # direction: unit eigenvector corresponding to eigenvalue 0
    w, V = numpy.linalg.eig(M33)
    i = numpy.where(abs(numpy.real(w)) < 1e-8)[0]
    if not len(i):
      raise ValueError('no eigenvector corresponding to eigenvalue 0')
    direction = numpy.real(V[:, i[0]]).squeeze()
    direction /= vector_norm(direction)
    # normal: unit eigenvector of M33.T corresponding to eigenvalue 0
    w, V = numpy.linalg.eig(M33.T)
    i = numpy.where(abs(numpy.real(w)) < 1e-8)[0]
    if len(i):
      # parallel projection
      normal = numpy.real(V[:, i[0]]).squeeze()
      normal /= vector_norm(normal)
      return point, normal, direction, None, False
    else:
      # orthogonal projection, where normal equals direction vector
      return point, direction, None, None, False
  else:
    # perspective projection
    i = numpy.where(abs(numpy.real(w)) > 1e-8)[0]
    if not len(i):
      raise ValueError('no eigenvector not corresponding to eigenvalue 0')
    point = numpy.real(V[:, i[-1]]).squeeze()
    point /= point[3]
    normal = -M[3, :3]
    perspective = M[:3, 3] / numpy.dot(point[:3], normal)
    if pseudo:
      perspective -= normal
    return point, normal, None, perspective, pseudo

def clip_matrix(left, right, bottom, top, near, far, perspective=False):
  """Return matrix to obtain normalized device coordinates from frustum.

    The frustum bounds are axis-aligned along x (left, right),
    y (bottom, top) and z (near, far).

    Normalized device coordinates are in range [-1, 1] if coordinates are
    inside the frustum.

    If perspective is True the frustum is a truncated pyramid with the
    perspective point at origin and direction along z axis, otherwise an
    orthographic canonical view volume (a box).

    Homogeneous coordinates transformed by the perspective clip matrix
    need to be dehomogenized (divided by w coordinate).

    >>> frustum = numpy.random.rand(6)
    >>> frustum[1] += frustum[0]
    >>> frustum[3] += frustum[2]
    >>> frustum[5] += frustum[4]
    >>> M = clip_matrix(perspective=False, *frustum)
    >>> numpy.dot(M, [frustum[0], frustum[2], frustum[4], 1])
    array([-1., -1., -1.,  1.])
    >>> numpy.dot(M, [frustum[1], frustum[3], frustum[5], 1])
    array([ 1.,  1.,  1.,  1.])
    >>> M = clip_matrix(perspective=True, *frustum)
    >>> v = numpy.dot(M, [frustum[0], frustum[2], frustum[4], 1])
    >>> v / v[3]
    array([-1., -1., -1.,  1.])
    >>> v = numpy.dot(M, [frustum[1], frustum[3], frustum[4], 1])
    >>> v / v[3]
    array([ 1.,  1., -1.,  1.])

    """
  if left >= right or bottom >= top or near >= far:
    raise ValueError('invalid frustum')
  if perspective:
    if near <= _EPS:
      raise ValueError('invalid frustum: near <= 0')
    t = 2.0 * near
    M = [[t / (left - right), 0.0, (right + left) / (right - left), 0.0],
         [0.0, t / (bottom - top), (top + bottom) / (top - bottom), 0.0],
         [0.0, 0.0, (far + near) / (near - far), t * far / (far - near)], [0.0, 0.0, -1.0, 0.0]]
  else:
    M = [[2.0 / (right - left), 0.0, 0.0, (right + left) / (left - right)],
         [0.0, 2.0 / (top - bottom), 0.0, (top + bottom) / (bottom - top)],
         [0.0, 0.0, 2.0 / (far - near), (far + near) / (near - far)], [0.0, 0.0, 0.0, 1.0]]
  return numpy.array(M)

def shear_matrix(angle, direction, point, normal):
  """Return matrix to shear by angle along direction vector on shear plane.

    The shear plane is defined by a point and normal vector. The direction
    vector must be orthogonal to the plane's normal vector.

    A point P is transformed by the shear matrix into P" such that
    the vector P-P" is parallel to the direction vector and its extent is
    given by the angle of P-P'-P", where P' is the orthogonal projection
    of P onto the shear plane.

    >>> angle = (random.random() - 0.5) * 4*math.pi
    >>> direct = numpy.random.random(3) - 0.5
    >>> point = numpy.random.random(3) - 0.5
    >>> normal = numpy.cross(direct, numpy.random.random(3))
    >>> S = shear_matrix(angle, direct, point, normal)
    >>> numpy.allclose(1, numpy.linalg.det(S))
    True

    """
  normal = unit_vector(normal[:3])
  direction = unit_vector(direction[:3])
  if abs(numpy.dot(normal, direction)) > 1e-6:
    raise ValueError('direction and normal vectors are not orthogonal')
  angle = math.tan(angle)
  M = numpy.identity(4)
  M[:3, :3] += angle * numpy.outer(direction, normal)
  M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction
  return M

def shear_from_matrix(matrix):
  """Return shear angle, direction and plane from shear matrix.

    >>> angle = (random.random() - 0.5) * 4*math.pi
    >>> direct = numpy.random.random(3) - 0.5
    >>> point = numpy.random.random(3) - 0.5
    >>> normal = numpy.cross(direct, numpy.random.random(3))
    >>> S0 = shear_matrix(angle, direct, point, normal)
    >>> angle, direct, point, normal = shear_from_matrix(S0)
    >>> S1 = shear_matrix(angle, direct, point, normal)
    >>> is_same_transform(S0, S1)
    True

    """
  M = numpy.array(matrix, dtype=numpy.float64, copy=False)
  M33 = M[:3, :3]
  # normal: cross independent eigenvectors corresponding to the eigenvalue 1
  w, V = numpy.linalg.eig(M33)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-4)[0]
  if len(i) < 2:
    raise ValueError('no two linear independent eigenvectors found %s' % w)
  V = numpy.real(V[:, i]).squeeze().T
  lenorm = -1.0
  for i0, i1 in ((0, 1), (0, 2), (1, 2)):
    n = numpy.cross(V[i0], V[i1])
    w = vector_norm(n)
    if w > lenorm:
      lenorm = w
      normal = n
  normal /= lenorm
  # direction and angle
  direction = numpy.dot(M33 - numpy.identity(3), normal)
  angle = vector_norm(direction)
  direction /= angle
  angle = math.atan(angle)
  # point: eigenvector corresponding to eigenvalue 1
  w, V = numpy.linalg.eig(M)
  i = numpy.where(abs(numpy.real(w) - 1.0) < 1e-8)[0]
  if not len(i):
    raise ValueError('no eigenvector corresponding to eigenvalue 1')
  point = numpy.real(V[:, i[-1]]).squeeze()
  point /= point[3]
  return angle, direction, point, normal

def decompose_matrix(matrix):
  """Return sequence of transformations from transformation matrix.

    matrix : array_like
        Non-degenerative homogeneous transformation matrix

    Return tuple of:
        scale : vector of 3 scaling factors
        shear : list of shear factors for x-y, x-z, y-z axes
        angles : list of Euler angles about static x, y, z axes
        translate : translation vector along x, y, z axes
        perspective : perspective partition of matrix

    Raise ValueError if matrix is of wrong type or degenerative.

    >>> T0 = translation_matrix([1, 2, 3])
    >>> scale, shear, angles, trans, persp = decompose_matrix(T0)
    >>> T1 = translation_matrix(trans)
    >>> numpy.allclose(T0, T1)
    True
    >>> S = scale_matrix(0.123)
    >>> scale, shear, angles, trans, persp = decompose_matrix(S)
    >>> scale[0]
    0.123
    >>> R0 = euler_matrix(1, 2, 3)
    >>> scale, shear, angles, trans, persp = decompose_matrix(R0)
    >>> R1 = euler_matrix(*angles)
    >>> numpy.allclose(R0, R1)
    True

    """
  M = numpy.array(matrix, dtype=numpy.float64, copy=True).T
  if abs(M[3, 3]) < _EPS:
    raise ValueError('M[3, 3] is zero')
  M /= M[3, 3]
  P = M.copy()
  P[:, 3] = 0.0, 0.0, 0.0, 1.0
  if not numpy.linalg.det(P):
    raise ValueError('matrix is singular')

  scale = numpy.zeros((3,))
  shear = [0.0, 0.0, 0.0]
  angles = [0.0, 0.0, 0.0]

  if any(abs(M[:3, 3]) > _EPS):
    perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T))
    M[:, 3] = 0.0, 0.0, 0.0, 1.0
  else:
    perspective = numpy.array([0.0, 0.0, 0.0, 1.0])

  translate = M[3, :3].copy()
  M[3, :3] = 0.0

  row = M[:3, :3].copy()
  scale[0] = vector_norm(row[0])
  row[0] /= scale[0]
  shear[0] = numpy.dot(row[0], row[1])
  row[1] -= row[0] * shear[0]
  scale[1] = vector_norm(row[1])
  row[1] /= scale[1]
  shear[0] /= scale[1]
  shear[1] = numpy.dot(row[0], row[2])
  row[2] -= row[0] * shear[1]
  shear[2] = numpy.dot(row[1], row[2])
  row[2] -= row[1] * shear[2]
  scale[2] = vector_norm(row[2])
  row[2] /= scale[2]
  shear[1:] /= scale[2]

  if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0:
    numpy.negative(scale, scale)
    numpy.negative(row, row)

  angles[1] = math.asin(-row[0, 2])
  if math.cos(angles[1]):
    angles[0] = math.atan2(row[1, 2], row[2, 2])
    angles[2] = math.atan2(row[0, 1], row[0, 0])
  else:
    # angles[0] = math.atan2(row[1, 0], row[1, 1])
    angles[0] = math.atan2(-row[2, 1], row[1, 1])
    angles[2] = 0.0

  return scale, shear, angles, translate, perspective

def compose_matrix(scale=None, shear=None, angles=None, translate=None, perspective=None):
  """Return transformation matrix from sequence of transformations.

    This is the inverse of the decompose_matrix function.

    Sequence of transformations:
        scale : vector of 3 scaling factors
        shear : list of shear factors for x-y, x-z, y-z axes
        angles : list of Euler angles about static x, y, z axes
        translate : translation vector along x, y, z axes
        perspective : perspective partition of matrix

    >>> scale = numpy.random.random(3) - 0.5
    >>> shear = numpy.random.random(3) - 0.5
    >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
    >>> trans = numpy.random.random(3) - 0.5
    >>> persp = numpy.random.random(4) - 0.5
    >>> M0 = compose_matrix(scale, shear, angles, trans, persp)
    >>> result = decompose_matrix(M0)
    >>> M1 = compose_matrix(*result)
    >>> is_same_transform(M0, M1)
    True

    """
  M = numpy.identity(4)
  if perspective is not None:
    P = numpy.identity(4)
    P[3, :] = perspective[:4]
    M = numpy.dot(M, P)
  if translate is not None:
    T = numpy.identity(4)
    T[:3, 3] = translate[:3]
    M = numpy.dot(M, T)
  if angles is not None:
    R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz')
    M = numpy.dot(M, R)
  if shear is not None:
    Z = numpy.identity(4)
    Z[1, 2] = shear[2]
    Z[0, 2] = shear[1]
    Z[0, 1] = shear[0]
    M = numpy.dot(M, Z)
  if scale is not None:
    S = numpy.identity(4)
    S[0, 0] = scale[0]
    S[1, 1] = scale[1]
    S[2, 2] = scale[2]
    M = numpy.dot(M, S)
  M /= M[3, 3]
  return M

def orthogonalization_matrix(lengths, angles):
  """Return orthogonalization matrix for crystallographic cell coordinates.

    Angles are expected in degrees.

    The de-orthogonalization matrix is the inverse.

    >>> O = orthogonalization_matrix([10, 10, 10], [90, 90, 90])
    >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
    True
    >>> O = orthogonalization_matrix([9.8, 12.0, 15.5], [87.2, 80.7, 69.7])
    >>> numpy.allclose(numpy.sum(O), 43.063229)
    True

    """
  a, b, c = lengths
  angles = numpy.radians(angles)
  sina, sinb, _ = numpy.sin(angles)
  cosa, cosb, cosg = numpy.cos(angles)
  co = (cosa * cosb - cosg) / (sina * sinb)
  return numpy.array([[a * sinb * math.sqrt(1.0 - co * co), 0.0, 0.0, 0.0],
                      [-a * sinb * co, b * sina, 0.0, 0.0], [a * cosb, b * cosa, c, 0.0],
                      [0.0, 0.0, 0.0, 1.0]])

def affine_matrix_from_points(v0, v1, shear=True, scale=True, usesvd=True):
  """Return affine transform matrix to register two point sets.

    v0 and v1 are shape (ndims, \*) arrays of at least ndims non-homogeneous
    coordinates, where ndims is the dimensionality of the coordinate space.

    If shear is False, a similarity transformation matrix is returned.
    If also scale is False, a rigid/Euclidean transformation matrix
    is returned.

    By default the algorithm by Hartley and Zissermann [15] is used.
    If usesvd is True, similarity and Euclidean transformation matrices
    are calculated by minimizing the weighted sum of squared deviations
    (RMSD) according to the algorithm by Kabsch [8].
    Otherwise, and if ndims is 3, the quaternion based algorithm by Horn [9]
    is used, which is slower when using this Python implementation.

    The returned matrix performs rotation, translation and uniform scaling
    (if specified).

    >>> v0 = [[0, 1031, 1031, 0], [0, 0, 1600, 1600]]
    >>> v1 = [[675, 826, 826, 677], [55, 52, 281, 277]]
    >>> affine_matrix_from_points(v0, v1)
    array([[   0.14549,    0.00062,  675.50008],
           [   0.00048,    0.14094,   53.24971],
           [   0.     ,    0.     ,    1.     ]])
    >>> T = translation_matrix(numpy.random.random(3)-0.5)
    >>> R = random_rotation_matrix(numpy.random.random(3))
    >>> S = scale_matrix(random.random())
    >>> M = concatenate_matrices(T, R, S)
    >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20
    >>> v0[3] = 1
    >>> v1 = numpy.dot(M, v0)
    >>> v0[:3] += numpy.random.normal(0, 1e-8, 300).reshape(3, -1)
    >>> M = affine_matrix_from_points(v0[:3], v1[:3])
    >>> numpy.allclose(v1, numpy.dot(M, v0))
    True

    More examples in superimposition_matrix()

    """
  v0 = numpy.array(v0, dtype=numpy.float64, copy=True)
  v1 = numpy.array(v1, dtype=numpy.float64, copy=True)

  ndims = v0.shape[0]
  if ndims < 2 or v0.shape[1] < ndims or v0.shape != v1.shape:
    raise ValueError('input arrays are of wrong shape or type')

  # move centroids to origin
  t0 = -numpy.mean(v0, axis=1)
  M0 = numpy.identity(ndims + 1)
  M0[:ndims, ndims] = t0
  v0 += t0.reshape(ndims, 1)
  t1 = -numpy.mean(v1, axis=1)
  M1 = numpy.identity(ndims + 1)
  M1[:ndims, ndims] = t1
  v1 += t1.reshape(ndims, 1)

  if shear:
    # Affine transformation
    A = numpy.concatenate((v0, v1), axis=0)
    u, s, vh = numpy.linalg.svd(A.T)
    vh = vh[:ndims].T
    B = vh[:ndims]
    C = vh[ndims:2 * ndims]
    t = numpy.dot(C, numpy.linalg.pinv(B))
    t = numpy.concatenate((t, numpy.zeros((ndims, 1))), axis=1)
    M = numpy.vstack((t, ((0.0,) * ndims) + (1.0,)))
  elif usesvd or ndims != 3:
    # Rigid transformation via SVD of covariance matrix
    u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T))
    # rotation matrix from SVD orthonormal bases
    R = numpy.dot(u, vh)
    if numpy.linalg.det(R) < 0.0:
      # R does not constitute right handed system
      R -= numpy.outer(u[:, ndims - 1], vh[ndims - 1, :] * 2.0)
      s[-1] *= -1.0
    # homogeneous transformation matrix
    M = numpy.identity(ndims + 1)
    M[:ndims, :ndims] = R
  else:
    # Rigid transformation matrix via quaternion
    # compute symmetric matrix N
    xx, yy, zz = numpy.sum(v0 * v1, axis=1)
    xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1)
    xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1)
    N = [[xx + yy + zz, 0.0, 0.0, 0.0], [yz - zy, xx - yy - zz, 0.0, 0.0],
         [zx - xz, xy + yx, yy - xx - zz, 0.0], [xy - yx, zx + xz, yz + zy, zz - xx - yy]]
    # quaternion: eigenvector corresponding to most positive eigenvalue
    w, V = numpy.linalg.eigh(N)
    q = V[:, numpy.argmax(w)]
    q /= vector_norm(q)  # unit quaternion
    # homogeneous transformation matrix
    M = quaternion_matrix(q)

  if scale and not shear:
    # Affine transformation; scale is ratio of RMS deviations from centroid
    v0 *= v0
    v1 *= v1
    M[:ndims, :ndims] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0))

  # move centroids back
  M = numpy.dot(numpy.linalg.inv(M1), numpy.dot(M, M0))
  M /= M[ndims, ndims]
  return M

def superimposition_matrix(v0, v1, scale=False, usesvd=True):
  """Return matrix to transform given 3D point set into second point set.

    v0 and v1 are shape (3, \*) or (4, \*) arrays of at least 3 points.

    The parameters scale and usesvd are explained in the more general
    affine_matrix_from_points function.

    The returned matrix is a similarity or Euclidean transformation matrix.
    This function has a fast C implementation in transformations.c.

    >>> v0 = numpy.random.rand(3, 10)
    >>> M = superimposition_matrix(v0, v0)
    >>> numpy.allclose(M, numpy.identity(4))
    True
    >>> R = random_rotation_matrix(numpy.random.random(3))
    >>> v0 = [[1,0,0], [0,1,0], [0,0,1], [1,1,1]]
    >>> v1 = numpy.dot(R, v0)
    >>> M = superimposition_matrix(v0, v1)
    >>> numpy.allclose(v1, numpy.dot(M, v0))
    True
    >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20
    >>> v0[3] = 1
    >>> v1 = numpy.dot(R, v0)
    >>> M = superimposition_matrix(v0, v1)
    >>> numpy.allclose(v1, numpy.dot(M, v0))
    True
    >>> S = scale_matrix(random.random())
    >>> T = translation_matrix(numpy.random.random(3)-0.5)
    >>> M = concatenate_matrices(T, R, S)
    >>> v1 = numpy.dot(M, v0)
    >>> v0[:3] += numpy.random.normal(0, 1e-9, 300).reshape(3, -1)
    >>> M = superimposition_matrix(v0, v1, scale=True)
    >>> numpy.allclose(v1, numpy.dot(M, v0))
    True
    >>> M = superimposition_matrix(v0, v1, scale=True, usesvd=False)
    >>> numpy.allclose(v1, numpy.dot(M, v0))
    True
    >>> v = numpy.empty((4, 100, 3))
    >>> v[:, :, 0] = v0
    >>> M = superimposition_matrix(v0, v1, scale=True, usesvd=False)
    >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
    True

    """
  v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3]
  v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3]
  return affine_matrix_from_points(v0, v1, shear=False, scale=scale, usesvd=usesvd)

def euler_matrix(ai, aj, ak, axes='sxyz'):
  """Return homogeneous rotation matrix from Euler angles and axis sequence.

    ai, aj, ak : Euler's roll, pitch and yaw angles
    axes : One of 24 axis sequences as string or encoded tuple

    >>> R = euler_matrix(1, 2, 3, 'syxz')
    >>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
    True
    >>> R = euler_matrix(1, 2, 3, (0, 1, 0, 1))
    >>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
    True
    >>> ai, aj, ak = (4*math.pi) * (numpy.random.random(3) - 0.5)
    >>> for axes in _AXES2TUPLE.keys():
    ...    R = euler_matrix(ai, aj, ak, axes)
    >>> for axes in _TUPLE2AXES.keys():
    ...    R = euler_matrix(ai, aj, ak, axes)

    """
  try:
    firstaxis, parity, repetition, frame = _AXES2TUPLE[axes]
  except (AttributeError, KeyError):
    _TUPLE2AXES[axes]  # noqa: validation
    firstaxis, parity, repetition, frame = axes

  i = firstaxis
  j = _NEXT_AXIS[i + parity]
  k = _NEXT_AXIS[i - parity + 1]

  if frame:
    ai, ak = ak, ai
  if parity:
    ai, aj, ak = -ai, -aj, -ak

  si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak)
  ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak)
  cc, cs = ci * ck, ci * sk
  sc, ss = si * ck, si * sk

  M = numpy.identity(4)
  if repetition:
    M[i, i] = cj
    M[i, j] = sj * si
    M[i, k] = sj * ci
    M[j, i] = sj * sk
    M[j, j] = -cj * ss + cc
    M[j, k] = -cj * cs - sc
    M[k, i] = -sj * ck
    M[k, j] = cj * sc + cs
    M[k, k] = cj * cc - ss
  else:
    M[i, i] = cj * ck
    M[i, j] = sj * sc - cs
    M[i, k] = sj * cc + ss
    M[j, i] = cj * sk
    M[j, j] = sj * ss + cc
    M[j, k] = sj * cs - sc
    M[k, i] = -sj
    M[k, j] = cj * si
    M[k, k] = cj * ci
  return M

def euler_from_matrix(matrix, axes='sxyz'):
  """Return Euler angles from rotation matrix for specified axis sequence.

    axes : One of 24 axis sequences as string or encoded tuple

    Note that many Euler angle triplets can describe one matrix.

    >>> R0 = euler_matrix(1, 2, 3, 'syxz')
    >>> al, be, ga = euler_from_matrix(R0, 'syxz')
    >>> R1 = euler_matrix(al, be, ga, 'syxz')
    >>> numpy.allclose(R0, R1)
    True
    >>> angles = (4*math.pi) * (numpy.random.random(3) - 0.5)
    >>> for axes in _AXES2TUPLE.keys():
    ...    R0 = euler_matrix(axes=axes, *angles)
    ...    R1 = euler_matrix(axes=axes, *euler_from_matrix(R0, axes))
    ...    if not numpy.allclose(R0, R1): print(axes, "failed")

    """
  try:
    firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
  except (AttributeError, KeyError):
    _TUPLE2AXES[axes]  # noqa: validation
    firstaxis, parity, repetition, frame = axes

  i = firstaxis
  j = _NEXT_AXIS[i + parity]
  k = _NEXT_AXIS[i - parity + 1]

  M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3]
  if repetition:
    sy = math.sqrt(M[i, j] * M[i, j] + M[i, k] * M[i, k])
    if sy > _EPS:
      ax = math.atan2(M[i, j], M[i, k])
      ay = math.atan2(sy, M[i, i])
      az = math.atan2(M[j, i], -M[k, i])
    else:
      ax = math.atan2(-M[j, k], M[j, j])
      ay = math.atan2(sy, M[i, i])
      az = 0.0
  else:
    cy = math.sqrt(M[i, i] * M[i, i] + M[j, i] * M[j, i])
    if cy > _EPS:
      ax = math.atan2(M[k, j], M[k, k])
      ay = math.atan2(-M[k, i], cy)
      az = math.atan2(M[j, i], M[i, i])
    else:
      ax = math.atan2(-M[j, k], M[j, j])
      ay = math.atan2(-M[k, i], cy)
      az = 0.0

  if parity:
    ax, ay, az = -ax, -ay, -az
  if frame:
    ax, az = az, ax
  return ax, ay, az

def euler_from_quaternion(quaternion, axes='sxyz'):
  """Return Euler angles from quaternion for specified axis sequence.

    >>> angles = euler_from_quaternion([0.99810947, 0.06146124, 0, 0])
    >>> numpy.allclose(angles, [0.123, 0, 0])
    True

    """
  return euler_from_matrix(quaternion_matrix(quaternion), axes)

def quaternion_from_euler(ai, aj, ak, axes='sxyz'):
  """Return quaternion from Euler angles and axis sequence.

    ai, aj, ak : Euler's roll, pitch and yaw angles
    axes : One of 24 axis sequences as string or encoded tuple

    >>> q = quaternion_from_euler(1, 2, 3, 'ryxz')
    >>> numpy.allclose(q, [0.435953, 0.310622, -0.718287, 0.444435])
    True

    """
  try:
    firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
  except (AttributeError, KeyError):
    _TUPLE2AXES[axes]  # noqa: validation
    firstaxis, parity, repetition, frame = axes

  i = firstaxis + 1
  j = _NEXT_AXIS[i + parity - 1] + 1
  k = _NEXT_AXIS[i - parity] + 1

  if frame:
    ai, ak = ak, ai
  if parity:
    aj = -aj

  ai /= 2.0
  aj /= 2.0
  ak /= 2.0
  ci = math.cos(ai)
  si = math.sin(ai)
  cj = math.cos(aj)
  sj = math.sin(aj)
  ck = math.cos(ak)
  sk = math.sin(ak)
  cc = ci * ck
  cs = ci * sk
  sc = si * ck
  ss = si * sk

  q = numpy.empty((4,))
  if repetition:
    q[0] = cj * (cc - ss)
    q[i] = cj * (cs + sc)
    q[j] = sj * (cc + ss)
    q[k] = sj * (cs - sc)
  else:
    q[0] = cj * cc + sj * ss
    q[i] = cj * sc - sj * cs
    q[j] = cj * ss + sj * cc
    q[k] = cj * cs - sj * sc
  if parity:
    q[j] *= -1.0

  return q

def quaternion_about_axis(angle, axis):
  """Return quaternion for rotation about axis.

    >>> q = quaternion_about_axis(0.123, [1, 0, 0])
    >>> numpy.allclose(q, [0.99810947, 0.06146124, 0, 0])
    True

    """
  q = numpy.array([0.0, axis[0], axis[1], axis[2]])
  qlen = vector_norm(q)
  if qlen > _EPS:
    q *= math.sin(angle / 2.0) / qlen
  q[0] = math.cos(angle / 2.0)
  return q

def quaternion_matrix(quaternion):
  """Return homogeneous rotation matrix from quaternion.

    >>> M = quaternion_matrix([0.99810947, 0.06146124, 0, 0])
    >>> numpy.allclose(M, rotation_matrix(0.123, [1, 0, 0]))
    True
    >>> M = quaternion_matrix([1, 0, 0, 0])
    >>> numpy.allclose(M, numpy.identity(4))
    True
    >>> M = quaternion_matrix([0, 1, 0, 0])
    >>> numpy.allclose(M, numpy.diag([1, -1, -1, 1]))
    True

    """
  q = numpy.array(quaternion, dtype=numpy.float64, copy=True)
  n = numpy.dot(q, q)
  if n < _EPS:
    return numpy.identity(4)
  q *= math.sqrt(2.0 / n)
  q = numpy.outer(q, q)
  return numpy.array([[1.0 - q[2, 2] - q[3, 3], q[1, 2] - q[3, 0], q[1, 3] + q[2, 0], 0.0],
                      [q[1, 2] + q[3, 0], 1.0 - q[1, 1] - q[3, 3], q[2, 3] - q[1, 0], 0.0],
                      [q[1, 3] - q[2, 0], q[2, 3] + q[1, 0], 1.0 - q[1, 1] - q[2, 2], 0.0],
                      [0.0, 0.0, 0.0, 1.0]])

def quaternion_from_matrix(matrix, isprecise=False):
  """Return quaternion from rotation matrix.

    If isprecise is True, the input matrix is assumed to be a precise rotation
    matrix and a faster algorithm is used.

    >>> q = quaternion_from_matrix(numpy.identity(4), True)
    >>> numpy.allclose(q, [1, 0, 0, 0])
    True
    >>> q = quaternion_from_matrix(numpy.diag([1, -1, -1, 1]))
    >>> numpy.allclose(q, [0, 1, 0, 0]) or numpy.allclose(q, [0, -1, 0, 0])
    True
    >>> R = rotation_matrix(0.123, (1, 2, 3))
    >>> q = quaternion_from_matrix(R, True)
    >>> numpy.allclose(q, [0.9981095, 0.0164262, 0.0328524, 0.0492786])
    True
    >>> R = [[-0.545, 0.797, 0.260, 0], [0.733, 0.603, -0.313, 0],
    ...      [-0.407, 0.021, -0.913, 0], [0, 0, 0, 1]]
    >>> q = quaternion_from_matrix(R)
    >>> numpy.allclose(q, [0.19069, 0.43736, 0.87485, -0.083611])
    True
    >>> R = [[0.395, 0.362, 0.843, 0], [-0.626, 0.796, -0.056, 0],
    ...      [-0.677, -0.498, 0.529, 0], [0, 0, 0, 1]]
    >>> q = quaternion_from_matrix(R)
    >>> numpy.allclose(q, [0.82336615, -0.13610694, 0.46344705, -0.29792603])
    True
    >>> R = random_rotation_matrix()
    >>> q = quaternion_from_matrix(R)
    >>> is_same_transform(R, quaternion_matrix(q))
    True
    >>> is_same_quaternion(quaternion_from_matrix(R, isprecise=False),
    ...                    quaternion_from_matrix(R, isprecise=True))
    True
    >>> R = euler_matrix(0.0, 0.0, numpy.pi/2.0)
    >>> is_same_quaternion(quaternion_from_matrix(R, isprecise=False),
    ...                    quaternion_from_matrix(R, isprecise=True))
    True

    """
  M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4]
  if isprecise:
    q = numpy.empty((4,))
    t = numpy.trace(M)
    if t > M[3, 3]:
      q[0] = t
      q[3] = M[1, 0] - M[0, 1]
      q[2] = M[0, 2] - M[2, 0]
      q[1] = M[2, 1] - M[1, 2]
    else:
      i, j, k = 0, 1, 2
      if M[1, 1] > M[0, 0]:
        i, j, k = 1, 2, 0
      if M[2, 2] > M[i, i]:
        i, j, k = 2, 0, 1
      t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3]
      q[i] = t
      q[j] = M[i, j] + M[j, i]
      q[k] = M[k, i] + M[i, k]
      q[3] = M[k, j] - M[j, k]
      q = q[[3, 0, 1, 2]]
    q *= 0.5 / math.sqrt(t * M[3, 3])
  else:
    m00 = M[0, 0]
    m01 = M[0, 1]
    m02 = M[0, 2]
    m10 = M[1, 0]
    m11 = M[1, 1]
    m12 = M[1, 2]
    m20 = M[2, 0]
    m21 = M[2, 1]
    m22 = M[2, 2]
    # symmetric matrix K
    K = numpy.array([[m00 - m11 - m22, 0.0, 0.0, 0.0], [m01 + m10, m11 - m00 - m22, 0.0, 0.0],
                     [m02 + m20, m12 + m21, m22 - m00 - m11, 0.0],
                     [m21 - m12, m02 - m20, m10 - m01, m00 + m11 + m22]])
    K /= 3.0
    # quaternion is eigenvector of K that corresponds to largest eigenvalue
    w, V = numpy.linalg.eigh(K)
    q = V[[3, 0, 1, 2], numpy.argmax(w)]
  if q[0] < 0.0:
    numpy.negative(q, q)
  return q

def quaternion_multiply(quaternion1, quaternion0):
  """Return multiplication of two quaternions.

    >>> q = quaternion_multiply([4, 1, -2, 3], [8, -5, 6, 7])
    >>> numpy.allclose(q, [28, -44, -14, 48])
    True

    """
  w0, x0, y0, z0 = quaternion0
  w1, x1, y1, z1 = quaternion1
  return numpy.array([
      -x1 * x0 - y1 * y0 - z1 * z0 + w1 * w0, x1 * w0 + y1 * z0 - z1 * y0 + w1 * x0,
      -x1 * z0 + y1 * w0 + z1 * x0 + w1 * y0, x1 * y0 - y1 * x0 + z1 * w0 + w1 * z0
  ],
                     dtype=numpy.float64)

def quaternion_conjugate(quaternion):
  """Return conjugate of quaternion.

    >>> q0 = random_quaternion()
    >>> q1 = quaternion_conjugate(q0)
    >>> q1[0] == q0[0] and all(q1[1:] == -q0[1:])
    True

    """
  q = numpy.array(quaternion, dtype=numpy.float64, copy=True)
  numpy.negative(q[1:], q[1:])
  return q

def quaternion_inverse(quaternion):
  """Return inverse of quaternion.

    >>> q0 = random_quaternion()
    >>> q1 = quaternion_inverse(q0)
    >>> numpy.allclose(quaternion_multiply(q0, q1), [1, 0, 0, 0])
    True

    """
  q = numpy.array(quaternion, dtype=numpy.float64, copy=True)
  numpy.negative(q[1:], q[1:])
  return q / numpy.dot(q, q)

def quaternion_real(quaternion):
  """Return real part of quaternion.

    >>> quaternion_real([3, 0, 1, 2])
    3.0

    """
  return float(quaternion[0])

def quaternion_imag(quaternion):
  """Return imaginary part of quaternion.

    >>> quaternion_imag([3, 0, 1, 2])
    array([ 0.,  1.,  2.])

    """
  return numpy.array(quaternion[1:4], dtype=numpy.float64, copy=True)

def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True):
  """Return spherical linear interpolation between two quaternions.

    >>> q0 = random_quaternion()
    >>> q1 = random_quaternion()
    >>> q = quaternion_slerp(q0, q1, 0)
    >>> numpy.allclose(q, q0)
    True
    >>> q = quaternion_slerp(q0, q1, 1, 1)
    >>> numpy.allclose(q, q1)
    True
    >>> q = quaternion_slerp(q0, q1, 0.5)
    >>> angle = math.acos(numpy.dot(q0, q))
    >>> numpy.allclose(2, math.acos(numpy.dot(q0, q1)) / angle) or \
        numpy.allclose(2, math.acos(-numpy.dot(q0, q1)) / angle)
    True

    """
  q0 = unit_vector(quat0[:4])
  q1 = unit_vector(quat1[:4])
  if fraction == 0.0:
    return q0
  elif fraction == 1.0:
    return q1
  d = numpy.dot(q0, q1)
  if abs(abs(d) - 1.0) < _EPS:
    return q0
  if shortestpath and d < 0.0:
    # invert rotation
    d = -d
    numpy.negative(q1, q1)
  angle = math.acos(d) + spin * math.pi
  if abs(angle) < _EPS:
    return q0
  isin = 1.0 / math.sin(angle)
  q0 *= math.sin((1.0 - fraction) * angle) * isin
  q1 *= math.sin(fraction * angle) * isin
  q0 += q1
  return q0

def random_quaternion(rand=None):
  """Return uniform random unit quaternion.

    rand: array like or None
        Three independent random variables that are uniformly distributed
        between 0 and 1.

    >>> q = random_quaternion()
    >>> numpy.allclose(1, vector_norm(q))
    True
    >>> q = random_quaternion(numpy.random.random(3))
    >>> len(q.shape), q.shape[0]==4
    (1, True)

    """
  if rand is None:
    rand = numpy.random.rand(3)
  else:
    assert len(rand) == 3
  r1 = numpy.sqrt(1.0 - rand[0])
  r2 = numpy.sqrt(rand[0])
  pi2 = math.pi * 2.0
  t1 = pi2 * rand[1]
  t2 = pi2 * rand[2]
  return numpy.array(
      [numpy.cos(t2) * r2,
       numpy.sin(t1) * r1,
       numpy.cos(t1) * r1,
       numpy.sin(t2) * r2])

def random_rotation_matrix(rand=None):
  """Return uniform random rotation matrix.

    rand: array like
        Three independent random variables that are uniformly distributed
        between 0 and 1 for each returned quaternion.

    >>> R = random_rotation_matrix()
    >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
    True

    """
  return quaternion_matrix(random_quaternion(rand))

# epsilon for testing whether a number is close to zero
_EPS = numpy.finfo(float).eps * 4.0

# axis sequences for Euler angles
_NEXT_AXIS = [1, 2, 0, 1]

# map axes strings to/from tuples of inner axis, parity, repetition, frame
_AXES2TUPLE = {
    'sxyz': (0, 0, 0, 0),
    'sxyx': (0, 0, 1, 0),
    'sxzy': (0, 1, 0, 0),
    'sxzx': (0, 1, 1, 0),
    'syzx': (1, 0, 0, 0),
    'syzy': (1, 0, 1, 0),
    'syxz': (1, 1, 0, 0),
    'syxy': (1, 1, 1, 0),
    'szxy': (2, 0, 0, 0),
    'szxz': (2, 0, 1, 0),
    'szyx': (2, 1, 0, 0),
    'szyz': (2, 1, 1, 0),
    'rzyx': (0, 0, 0, 1),
    'rxyx': (0, 0, 1, 1),
    'ryzx': (0, 1, 0, 1),
    'rxzx': (0, 1, 1, 1),
    'rxzy': (1, 0, 0, 1),
    'ryzy': (1, 0, 1, 1),
    'rzxy': (1, 1, 0, 1),
    'ryxy': (1, 1, 1, 1),
    'ryxz': (2, 0, 0, 1),
    'rzxz': (2, 0, 1, 1),
    'rxyz': (2, 1, 0, 1),
    'rzyz': (2, 1, 1, 1)
}

_TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items())

def vector_norm(data, axis=None, out=None):
  """Return length, i.e. Euclidean norm, of ndarray along axis.

    >>> v = numpy.random.random(3)
    >>> n = vector_norm(v)
    >>> numpy.allclose(n, numpy.linalg.norm(v))
    True
    >>> v = numpy.random.rand(6, 5, 3)
    >>> n = vector_norm(v, axis=-1)
    >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
    True
    >>> n = vector_norm(v, axis=1)
    >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
    True
    >>> v = numpy.random.rand(5, 4, 3)
    >>> n = numpy.empty((5, 3))
    >>> vector_norm(v, axis=1, out=n)
    >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
    True
    >>> vector_norm([])
    0.0
    >>> vector_norm([1])
    1.0

    """
  data = numpy.array(data, dtype=numpy.float64, copy=True)
  if out is None:
    if data.ndim == 1:
      return math.sqrt(numpy.dot(data, data))
    data *= data
    out = numpy.atleast_1d(numpy.sum(data, axis=axis))
    numpy.sqrt(out, out)
    return out
  else:
    data *= data
    numpy.sum(data, axis=axis, out=out)
    numpy.sqrt(out, out)

def unit_vector(data, axis=None, out=None):
  """Return ndarray normalized by length, i.e. Euclidean norm, along axis.

    >>> v0 = numpy.random.random(3)
    >>> v1 = unit_vector(v0)
    >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
    True
    >>> v0 = numpy.random.rand(5, 4, 3)
    >>> v1 = unit_vector(v0, axis=-1)
    >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
    >>> numpy.allclose(v1, v2)
    True
    >>> v1 = unit_vector(v0, axis=1)
    >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
    >>> numpy.allclose(v1, v2)
    True
    >>> v1 = numpy.empty((5, 4, 3))
    >>> unit_vector(v0, axis=1, out=v1)
    >>> numpy.allclose(v1, v2)
    True
    >>> list(unit_vector([]))
    []
    >>> list(unit_vector([1]))
    [1.0]

    """
  if out is None:
    data = numpy.array(data, dtype=numpy.float64, copy=True)
    if data.ndim == 1:
      data /= math.sqrt(numpy.dot(data, data))
      return data
  else:
    if out is not data:
      out[:] = numpy.array(data, copy=False)
    data = out
  length = numpy.atleast_1d(numpy.sum(data * data, axis))
  numpy.sqrt(length, length)
  if axis is not None:
    length = numpy.expand_dims(length, axis)
  data /= length
  if out is None:
    return data

def random_vector(size):
  """Return array of random doubles in the half-open interval [0.0, 1.0).

    >>> v = random_vector(10000)
    >>> numpy.all(v >= 0) and numpy.all(v < 1)
    True
    >>> v0 = random_vector(10)
    >>> v1 = random_vector(10)
    >>> numpy.any(v0 == v1)
    False

    """
  return numpy.random.random(size)

def vector_product(v0, v1, axis=0):
  """Return vector perpendicular to vectors.

    >>> v = vector_product([2, 0, 0], [0, 3, 0])
    >>> numpy.allclose(v, [0, 0, 6])
    True
    >>> v0 = [[2, 0, 0, 2], [0, 2, 0, 2], [0, 0, 2, 2]]
    >>> v1 = [[3], [0], [0]]
    >>> v = vector_product(v0, v1)
    >>> numpy.allclose(v, [[0, 0, 0, 0], [0, 0, 6, 6], [0, -6, 0, -6]])
    True
    >>> v0 = [[2, 0, 0], [2, 0, 0], [0, 2, 0], [2, 0, 0]]
    >>> v1 = [[0, 3, 0], [0, 0, 3], [0, 0, 3], [3, 3, 3]]
    >>> v = vector_product(v0, v1, axis=1)
    >>> numpy.allclose(v, [[0, 0, 6], [0, -6, 0], [6, 0, 0], [0, -6, 6]])
    True

    """
  return numpy.cross(v0, v1, axis=axis)

def angle_between_vectors(v0, v1, directed=True, axis=0):
  """Return angle between vectors.

    If directed is False, the input vectors are interpreted as undirected axes,
    i.e. the maximum angle is pi/2.

    >>> a = angle_between_vectors([1, -2, 3], [-1, 2, -3])
    >>> numpy.allclose(a, math.pi)
    True
    >>> a = angle_between_vectors([1, -2, 3], [-1, 2, -3], directed=False)
    >>> numpy.allclose(a, 0)
    True
    >>> v0 = [[2, 0, 0, 2], [0, 2, 0, 2], [0, 0, 2, 2]]
    >>> v1 = [[3], [0], [0]]
    >>> a = angle_between_vectors(v0, v1)
    >>> numpy.allclose(a, [0, 1.5708, 1.5708, 0.95532])
    True
    >>> v0 = [[2, 0, 0], [2, 0, 0], [0, 2, 0], [2, 0, 0]]
    >>> v1 = [[0, 3, 0], [0, 0, 3], [0, 0, 3], [3, 3, 3]]
    >>> a = angle_between_vectors(v0, v1, axis=1)
    >>> numpy.allclose(a, [1.5708, 1.5708, 1.5708, 0.95532])
    True

    """
  v0 = numpy.array(v0, dtype=numpy.float64, copy=False)
  v1 = numpy.array(v1, dtype=numpy.float64, copy=False)
  dot = numpy.sum(v0 * v1, axis=axis)
  dot /= vector_norm(v0, axis=axis) * vector_norm(v1, axis=axis)
  dot = numpy.clip(dot, -1.0, 1.0)
  return numpy.arccos(dot if directed else numpy.fabs(dot))

def inverse_matrix(matrix):
  """Return inverse of square transformation matrix.

    >>> M0 = random_rotation_matrix()
    >>> M1 = inverse_matrix(M0.T)
    >>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
    True
    >>> for size in range(1, 7):
    ...     M0 = numpy.random.rand(size, size)
    ...     M1 = inverse_matrix(M0)
    ...     if not numpy.allclose(M1, numpy.linalg.inv(M0)): print(size)

    """
  return numpy.linalg.inv(matrix)

def concatenate_matrices(*matrices):
  """Return concatenation of series of transformation matrices.

    >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
    >>> numpy.allclose(M, concatenate_matrices(M))
    True
    >>> numpy.allclose(numpy.dot(M, M.T), concatenate_matrices(M, M.T))
    True

    """
  M = numpy.identity(4)
  for i in matrices:
    M = numpy.dot(M, i)
  return M

def is_same_transform(matrix0, matrix1):
  """Return True if two matrices perform same transformation.

    >>> is_same_transform(numpy.identity(4), numpy.identity(4))
    True
    >>> is_same_transform(numpy.identity(4), random_rotation_matrix())
    False

    """
  matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True)
  matrix0 /= matrix0[3, 3]
  matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True)
  matrix1 /= matrix1[3, 3]
  return numpy.allclose(matrix0, matrix1)

def is_same_quaternion(q0, q1):
  """Return True if two quaternions are equal."""
  q0 = numpy.array(q0)
  q1 = numpy.array(q1)
  return numpy.allclose(q0, q1) or numpy.allclose(q0, -q1)

def _import_module(name, package=None, warn=True, postfix='_py', ignore='_'):
  """Try import all public attributes from module into global namespace.

    Existing attributes with name clashes are renamed with prefix.
    Attributes starting with underscore are ignored by default.

    Return True on successful import.

    """
  import warnings
  from importlib import import_module
  try:
    if not package:
      module = import_module(name)
    else:
      module = import_module('.' + name, package=package)
  except ImportError as err:
    if warn:
      warnings.warn(str(err))
  else:
    for attr in dir(module):
      if ignore and attr.startswith(ignore):
        continue
      if postfix:
        if attr in globals():
          globals()[attr + postfix] = globals()[attr]
        elif warn:
          warnings.warn('no Python implementation of ' + attr)
      globals()[attr] = getattr(module, attr)
    return True

# _import_module('_transformations', __package__)

# if __name__ == '__main__':
#     import doctest
#     import random  # noqa: used in doctests
#     try:
#         numpy.set_printoptions(suppress=True, precision=5, legacy='1.13')
#     except TypeError:
#         numpy.set_printoptions(suppress=True, precision=5)
#     doctest.testmod()


import cv2
from flask import Flask, render_template, Response, stream_with_context
import mediapipe as mp
import numpy as np
import time
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)

POSE_TASK = None
TASK_COMPLETED = False
TASK_COMPLETION_TIME = None

app = Flask(__name__)

def generate_head_pose_estimation():
    global TASK_COMPLETED, TASK_COMPLETION_TIME, POSE_TASK

    cap = cv2.VideoCapture(1)

    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)  # Set frame width
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)  # Set frame height
    mp_face_mesh = mp.solutions.face_mesh
    face_mesh = mp_face_mesh.FaceMesh(
        min_detection_confidence=0.5,
        min_tracking_confidence=0.5,
        max_num_faces = 1
    )

    update_pose_task()

    while cap.isOpened():
        ret, frame = cap.read()

        mp_drawing = mp.solutions.drawing_utils
        drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)

        frame = cv2.cvtColor(cv2.flip(frame, 1), cv2.COLOR_BGR2RGB)
        frame.flags.writeable = False

        result = face_mesh.process(frame)

        frame.flags.writeable = True
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

        frame_h, frame_w, frame_c = frame.shape
        mesh_idx = [33, 263, 1, 61, 291, 199]
        face_3d = []
        face_2d = []

        if result.multi_face_landmarks:
            for face_landmarks in result.multi_face_landmarks:
                for idx, lm in enumerate(face_landmarks.landmark):
                    if idx in mesh_idx:
                        if idx == 1:
                            nose_2d = (lm.x * frame_w, lm.y * frame_h)
                            nose_3d = (lm.x * frame_w, lm.y * frame_h, lm.z * 3000)
                        x, y = int(lm.x * frame_w), int(lm.y * frame_h)

                        face_2d.append([x, y])
                        face_3d.append([x, y, lm.z])

                face_2d = np.array(face_2d, dtype=np.float64)
                face_3d = np.array(face_3d, dtype=np.float64)

                focal_length = 1 * frame_w # Not zoomed in

                # called the camera intrinsic matrix or the camera matrix.
                # It encodes the properties of a camera that relate the 3D world to the 2D image captured by the camera.
                # This matrix is fundamental in computer vision and photogrammetry.
                cam_matrix = np.array([
                    [focal_length, 0, frame_w / 2],
                    [0, focal_length, frame_h / 2],
                    [0, 0, 1]
                ], dtype=np.float64)

                dist_matrix = np.zeros((4, 1), dtype=np.float64) # Assuming no lens distortion
                success, rot_vec, trans_vec = cv2.solvePnP(face_3d, face_2d, cam_matrix, dist_matrix)

                if not success:
                    continue

                rmat, jac = cv2.Rodrigues(rot_vec)

                angles, mtxR, mtxQ, Qx, Qy, Qz = cv2.RQDecomp3x3(rmat)

                x = angles[0] * 360
                y = angles[1] * 360
                z = angles[2] * 360

                if y < -10:
                    detected_pose = "Looking: Left"
                elif y > 10:
                    detected_pose = "Looking: Right"
                elif x < -10:
                    detected_pose = "Looking: Down"
                elif x > 10:
                    detected_pose = "Looking: Up"
                else:
                    detected_pose = "Looking: Forward"

                if detected_pose == f"Looking: {POSE_TASK}":
                    cv2.putText(frame, "CORRECT", (20,100), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 2)
                    if not TASK_COMPLETED:
                        TASK_COMPLETED = True
                        TASK_COMPLETION_TIME = time.time()

                    if time.time() - TASK_COMPLETION_TIME > 3:
                        update_pose_task()
                else:
                    cv2.putText(frame, "WRONG", (20,100), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 2)

                cv2.putText(frame, f"Task: Look {POSE_TASK}", (20,150), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 2)
                cv2.putText(frame, detected_pose, (20,50), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 2)

                nose_3d_projection, jacobian = cv2.projectPoints(nose_3d, rot_vec, trans_vec, cam_matrix, dist_matrix)

                # p1 = (int(nose_2d[0]), int(nose_2d[1]))
                # p2 = (int(nose_2d[0] + y * 10), int(nose_2d[1] - x * 10))

                # cv2.line(frame, p1, p2, (0, 255, 0), 2)

                cv2.putText(frame, detected_pose, (20,50), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 2)

            mp_drawing.draw_landmarks(
                image=frame,
                landmark_list=face_landmarks,
                connections=mp_face_mesh.FACEMESH_CONTOURS,
                landmark_drawing_spec=drawing_spec,
                connection_drawing_spec=drawing_spec
            )

        ret, jpeg = cv2.imencode('.jpg', frame)
        if not ret:
            continue

        frame = jpeg.tobytes()
        yield (b'--frame\r\n'
                b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')

    cap.release()

def update_pose_task():
    global POSE_TASK, TASK_COMPLETED
    poses = ["Right", "Left", "Up", "Down", "Forward"]
    POSE_TASK = np.random.choice(poses)
    TASK_COMPLETED = False

@app.route('/video_feed')
def video_feed():
    return Response(stream_with_context(generate_head_pose_estimation()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/give_task')
def endow_ALD_task():
    update_pose_task()
    return f"Task: Turn your head {POSE_TASK}"

@app.route('/')
def index():
    return render_template('ALD.html')

if __name__ == '__main__':
    app.debug = True
    app.run(port=8000)


import csv
import pprint
import unittest

# 2.a.open file, parse data and extract those rows with a value of 25 lease years
with open('bink_data.csv', 'r') as f:
    for line in csv.DictReader(f):
        if 25 <= int(line['Lease Years']) <= 25:
            pprint.pp(line)

# 2.b. multiply column 1 by column 2 as separate rows try 1 unsuccessful
    if 25 <= int(line['Lease Years'])*float(line['Current Rent']) <= 25:
        pprint.pp(newline)
# Try 2 unsuccessful
def total_rent():
    for line in lines:
        if 25 <= int(line['Lease Years']) and float(line['Current Rent']):
            print(['Lease Years']*['Current Rent'])

if __name__ == '__main__':
    unittest.main()


from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import backend as K
K.set_image_dim_ordering('th')

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(3, 300, 300)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(13))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])

train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        'images_dataset/Train Directory',
        target_size=(300, 300),
        batch_size = 16

        )

validation_generator = test_datagen.flow_from_directory(
        'images_dataset/validation',
        target_size=(300, 300),
        batch_size= 16
        )

model.fit_generator(
        train_generator,
        steps_per_epoch=320 // 8,
        epochs=1,
        validation_data = validation_generator,
        validation_steps = 80 // 8
        )
model.save_weights('first_try.h5')


"""Tic Tac Toe

Exercises

1. Give the X and O a different color and width.
2. What happens when someone taps a taken spot? """#A FIGURA É SOBREPOSTA
"""3. How would you detect when someone has won?""" # LINHA 64-72
"""4. How could you create a computer player?""" # LINHA 50-62

from turtle import *

from freegames import line

import random

def grid():
    """Draw tic-tac-toe grid."""
    line(-67, 200, -67, -200)
    line(67, 200, 67, -200)
    line(-200, -67, 200, -67)
    line(-200, 67, 200, 67)

def drawx(x, y):
    """Draw X player."""
    width(4) # (1. ADICIONANDO ESPESSURA AO X)
    color('blue') #(1.)ADICIONANDO UMA COR PARA O DEMARCADOR X
    line(x, y, x + 133, y + 133)
    line(x, y + 133, x + 133, y)

def drawo(x, y):
    """Draw O player."""
    width(4) # (1. ADICIONANDO ESPESSURA AO O)
    color('green') #(1.)ADICIONANDO UMA COR PARA O DEMARCADOR O
    up()
    goto(x + 67, y + 5)
    down()
    circle(62)

def floor(value):
    """Round value down to grid with square size 133."""
    return ((value + 200) // 133) * 133 - 200

#CRIANDO (4. CRIANDO UM COMPUTER PLAYER)
def MaquinaNPC():
    local_vazio = [] #PARTE AINDA NÃO JOGADA
    for i in range(3):
        for j in range(3):
            if grid2[i][j] == -1:
                local_vazio.append((i, j))

    if local_vazio:
        linha, coluna = random.choice(local_vazio)
        x = linha * 133 - 200
        y = coluna * 133 - 200
        tap(x, y)

#VERIFICAR SE HOUVE VENCEDOR
def vencedor(player):
    for i in range(3):
        if all(grid2[i][j] == player for j in range(3)) or all(grid2[j][i] == player for j in range(3)):
            return True
    if all(grid2[i][i] == player for i in range(3)) or all(grid2[i][2 - i] == player for i in range(3)):
        return True

    return False

def tap(x, y):
    """Draw X or O in tapped square."""
    x = floor(x)
    y = floor(y)
    player = state['player']

    linha = int((x + 200) // 133)
    coluna = int((y + 200) // 133)

    if grid2[linha][coluna] == -1:
        draw = players[player]
        draw(x, y)
        grid2[linha][coluna] = player
        update()

        if vencedor(player):
            print(f"Jogador(a) {player + 1} fez uma linha/diagonal seguida!")

        state['player'] = not player

        if state['player'] == 1:
            MaquinaNPC()

state = {'player': 0}
players = [drawx, drawo]
grid2= [[-1, -1, -1],
        [-1, -1, -1],
        [-1, -1, -1]]

setup(420, 420, 370, 0)
hideturtle()
tracer(False)
grid()
update()
onscreenclick(tap)
done()


from tabnanny import check
import cv2
from matplotlib import ticker

#import video or open webcam
cap = cv2.VideoCapture("Mark.mp4")# Change the video name to number for webcam (0 for default webcam, 1,2,3,... for external webcam)
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') #import the haarcascade_frontalface_default.xml
#? haarcascade_frontalface_default.xml is face detection file.

#show video
while (cap.isOpened()): #while video is open
    check, frame = cap.read() #read video
    if(check == True):#check if video has next frame.
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #convert to grayscale
        face_detect  = face_cascade.detectMultiScale(gray_frame, scaleFactor=1.3, minNeighbors=5) #detect face, scaleFactor is the scale of the image, minNeighbors is the number of neighbors
        for (x, y, w, h) in face_detect: #for each face detected
            #cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), thickness=3) #draw rectangle around face
            frame[y:y+h+30, x:x+w+30] = cv2.blur(frame[y:y+h+30, x:x+w+30], (50,50)) #blur the face
            cv2.imshow("Video", frame) #show video
        if cv2.waitKey(1) & 0xFF == ord('q'): #if q is pressed, break
            break

    else:
        break

cap.release() #release video
cv2.destroyAllWindows() #close all windows and give memory back to computer


import cv2
import sys
import time
import multiprocessing as mp
from gelsight import gsdevice

class Device:
    def __init__(self, cam_id=None) -> None:
        cam_id = gsdevice.get_camera_id("GelSight Mini") if cam_id is None else cam_id
        self.dev = gsdevice.Camera(gsdevice.Finger.MINI, cam_id)
        self.dev.connect()

    def get_frame(self, *frame_queues):
        # use self.dev.get_image((320, 240)) to get a 320x240 image if you want
        try:
            for _ in range(10):
                self.dev.get_image((320, 240))
            while True:
                frame = self.dev.get_image((320, 240))
                for frame_queue in frame_queues:
                    frame_queue.put(frame)
        finally:
            self.dev.stop_video()

    def run(self, *frame_queues):
        """Starts the device and the process that gets the frame from the device
        frame_queue is a multiprocessing.Queue()
        that is used to pass the frame to the other processes"""
        self.processes = []
        self.processes.append(mp.Process(target=self.get_frame, args=(*frame_queues,)))

        [p.start() for p in self.processes]

    def stop(self):
        [p.terminate() for p in self.processes]

if __name__ == "__main__":
    gsmini = Device()
    frame_queue = mp.Queue()
    frame_queue_2 = mp.Queue()
    gsmini.run(frame_queue, frame_queue_2)
    try:
        while True:
            tic = time.time()
            frame = frame_queue.get()
            cv2.imshow("gsmini", frame)
            getKey = cv2.waitKey(1)
            if getKey == 27 or getKey == ord("q"):  # ESC or q
                break
            sys.stdout.write("\rFPS: {:.2f}".format(1 / (time.time() - tic)))
    finally:
        gsmini.stop()
        gsmini.dev.stop_video()
        cv2.destroyAllWindows()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np
from matplotlib import pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
from matplotlib.gridspec import GridSpec

#%%

class MrDynamics:

    '''
    This object - MrDynamics will generate list of co-ordinate and velocity of a
    charged particle.

    q and m are the charge and mass of the particle.

    Tmax is the simulation time.
    Δt is the time step which act as a accuracy of Borish scheme.

    r and V are the initial position and velocity, respectively.

    E and B are electro-static field and magnetic field, respectively.

    '''

    def __init__(self, q, m, Tmax, Δt, r, V, E, B):

        # list of position and velocity
        self.ListOfx,  self.ListOfy,  self.ListOfz  = [ r[0] ],  [ r[1] ],  [ r[2] ]
        self.ListOfVx, self.ListOfVy, self.ListOfVz = [ V[0] ],  [ V[1] ],  [ V[2] ]

        self.Boris(q, m, Tmax, Δt, r, V, E, B)

    def Boris(self, q, m, Tmax, Δt, r, V, E, B):

        x,   y,  z = r
        Vx, Vy, Vz = V
        Ex, Ey, Ez = E
        Bx, By, Bz = B

        # Step - 1 : Half step velocity
        Vx += ( (q/m)* (Ex + Bz* Vy - By* Vz) )* (Δt/2)
        Vy += ( (q/m)* (Ey + Bx* Vz - Bz* Vx) )* (Δt/2)
        Vz += ( (q/m)* (Ez + By* Vx - Bx* Vy) )* (Δt/2)

        # Step - 2 : Boris Algorithm
        t  = 0
        qd = (q* Δt)/ (2* m)

        while (t <= Tmax):

            hx  = qd* Bx
            hy  = qd* By
            hz  = qd* Bz

            Sx  = 2* hx/ (1 + ( hx**2 + hy**2 + hz**2 ) )
            Sy  = 2* hy/ (1 + ( hx**2 + hy**2 + hz**2 ) )
            Sz  = 2* hz/ (1 + ( hx**2 + hy**2 + hz**2 ) )

            Ux  = Vx + qd* Ex
            Uy  = Vy + qd* Ey
            Uz  = Vz + qd* Ez

            Ux_d = Ux + Sz* ( Uy + Uz*hx - hz*Ux ) - Sy* ( Uz + Ux*hy - hx*Uy )
            Uy_d = Uy + Sx* ( Uz + Ux*hy - hx*Uy ) - Sz* ( Ux + Uy*hz - hy*Uz )
            Uz_d = Uz + Sy* ( Ux + Uy*hz - hy*Uz ) - Sx* ( Uy + Uz*hx - hz*Ux )

            Vx = Ux_d + qd* Ex
            Vy = Uy_d + qd* Ey
            Vz = Uz_d + qd* Ez

            x += Vx* Δt
            y += Vy* Δt
            z += Vz* Δt

            self.ListOfx.append(x)
            self.ListOfy.append(y)
            self.ListOfz.append(z)

            t += Δt

        # Step - 3 : Find velocity

        Vel = lambda Pos, Δt : [(Pos[i+2] - Pos[i])/ (2* Δt) for i in range(0, len(Pos) - 2)]

        self.ListOfVx = self.ListOfVx + Vel( self.ListOfx, Δt )
        self.ListOfVy = self.ListOfVy + Vel( self.ListOfy, Δt )
        self.ListOfVz = self.ListOfVz + Vel( self.ListOfz, Δt )

        self.ListOfx = self.ListOfx[:-1]
        self.ListOfy = self.ListOfy[:-1]
        self.ListOfz = self.ListOfz[:-1]

    def PlotTrajectory(self):

        fig = plt.figure( figsize = (80, 60) )

        gs = GridSpec(2, 2)

        ax1 = fig.add_subplot( gs[0, 0], projection = '3d' )
        ax2 = fig.add_subplot( gs[0, 1] )
        ax3 = fig.add_subplot( gs[1, 0] )
        ax4 = fig.add_subplot( gs[1, 1] )

        ax1.plot(self.ListOfx, self.ListOfy, self.ListOfz, c = 'r', linewidth = 2)
        ax1.scatter(self.ListOfx[0], self.ListOfy[0], self.ListOfz[0])

        ax1.set_xlabel('X', fontsize = 12, fontweight = 'bold')
        ax1.set_ylabel('Y', fontsize = 12, fontweight = 'bold')
        ax1.set_zlabel('Z', fontsize = 12, fontweight = 'bold')

        ax2.plot(self.ListOfx, self.ListOfVx)
        ax2.scatter(self.ListOfx[0], self.ListOfVx[0], color = 'r')
        ax2.set_title('x | Vx')
        ax2.grid()

        ax3.plot(self.ListOfy, self.ListOfVy)
        ax3.scatter(self.ListOfy[0], self.ListOfVy[0], color = 'r')
        ax3.set_title('y | Vy')
        ax3.grid()

        ax4.plot(self.ListOfz, self.ListOfVz)
        ax4.scatter(self.ListOfz[0], self.ListOfVz[0], color = 'r')
        ax4.set_title('z | Vz')
        ax4.grid()

#%%

if __name__ == '__main__':

    q, m = 1, 1
    Δt, Tmax = 1E-2, 5

    r = [0, 0, 0]
    V = [1, 0, 1]

    E = [0, 0, 0]
    B = [0, 0, 10]

    Answer = MrDynamics(q, m, Tmax, Δt, r, V, E, B)

    Answer.PlotTrajectory()


import os
import sys
from utils import submit

cache_dir = sys.argv[1]
host_name = sys.argv[2]
patient_dimension = sys.argv[3]
time_series = sys.argv[4]
year = sys.argv[5]
output_dir = sys.argv[6]

submit(host_name, cache_dir, "datatrans.PreprocPerPatSeriesRowCol",
           "--patient_dimension={0}".format(patient_dimension),
           "--time_series=" + time_series,
           "--output_file=" + output_dir,
           "--year=" + year, *sys.argv[7:])


import logging
import os
import configparser

from vsc.filesystem.gpfs import GpfsOperations
from vsc.config.base import VscStorage

QUOTA_CONF_FILE = '/etc/quota_check.conf'

def set_up_apps(gpfs, storage_settings, storage, filesystem_info, filesystem_name):
    """Set up the apps fileset."""
    logging.info("Setting up the apps fileset on storage %s", storage)
    fileset_name = storage_settings.path_templates[storage]['apps'][0]
    fileset_path = os.path.join(filesystem_info['defaultMountPoint'], fileset_name)
    if fileset_name not in [f['filesetName'] for f in gpfs.gpfslocalfilesets[filesystem_name].values()]:
        gpfs.make_fileset(fileset_path, fileset_name)
        gpfs.chmod(0o755, fileset_path)
        logging.info("Fileset %s created and linked at %s", fileset_name, fileset_path)

def set_up_filesystem(gpfs, storage_settings, storage, filesystem_info, filesystem_name, vo_support=False):
    """Set up the filesets and directories such that user, vo directories and friends can be created."""

    # Create the basic gent fileset
    logging.info("Setting up for storage %s", storage)
    fileset_name = storage_settings.path_templates[storage]['user'][0]
    fileset_path = os.path.join(filesystem_info['defaultMountPoint'], fileset_name)
    if fileset_name not in [f['filesetName'] for f in gpfs.gpfslocalfilesets[filesystem_name].values()]:
        gpfs.make_fileset(fileset_path, fileset_name)
        gpfs.chmod(0o755, fileset_path)
        logging.info("Fileset %s created and linked at %s", fileset_name, fileset_path)

    if vo_support:
        # Create the basic vo fileset
        fileset_name = storage_settings.path_templates[storage]['vo'][0]
        vo_fileset_path = os.path.join(filesystem_info['defaultMountPoint'], fileset_name)
        if fileset_name not in [f['filesetName'] for f in gpfs.gpfslocalfilesets[filesystem_name].values()]:
            gpfs.make_fileset(vo_fileset_path, fileset_name)
            gpfs.chmod(0o755, vo_fileset_path)
            logging.info("Fileset %s created and linked at %s", fileset_name, vo_fileset_path)

def main():

    storage_settings = VscStorage()

    local_storage_conf = configparser.SafeConfigParser()
    local_storage_conf.read(QUOTA_CONF_FILE)

    gpfs = GpfsOperations()
    gpfs.list_filesystems()
    gpfs.list_filesets()

    for storage_name in local_storage_conf.get('MAIN', 'storage').split(','):

        filesystem_name = storage_settings[storage_name].filesystem
        filesystem_info = gpfs.get_filesystem_info(filesystem_name)

        if storage_name in ('VSC_HOME'):
            set_up_filesystem(gpfs, storage_settings, storage_name, filesystem_info, filesystem_name)
            set_up_apps(gpfs, storage_settings, storage_name, filesystem_info, filesystem_name)
        else:
            set_up_filesystem(gpfs, storage_settings, storage_name, filesystem_info, filesystem_name, vo_support=True)

if __name__ == '__main__':
    main()


from datetime import datetime, timedelta

from httpx import AsyncClient
import pytest

from app.database.models import Base
from app.main import app
from app.routers import telegram
from app.routers.event import create_event
from tests.client_fixture import get_test_placeholder_user
from tests.conftest import get_test_db, test_engine

@pytest.fixture
async def telegram_client():
    Base.metadata.create_all(bind=test_engine)
    app.dependency_overrides[telegram.get_db] = get_test_db
    async with AsyncClient(app=app, base_url="http://test") as ac:
        yield ac
    app.dependency_overrides = {}
    Base.metadata.drop_all(bind=test_engine)

today_date = datetime.today().replace(hour=0, minute=0, second=0)

@pytest.fixture
def fake_user_events(session):
    Base.metadata.create_all(bind=test_engine)
    user = get_test_placeholder_user()
    session.add(user)
    session.commit()
    create_event(
        db=session,
        title='Cool today event',
        start=today_date,
        end=today_date + timedelta(days=2),
        all_day=False,
        content='test event',
        owner_id=user.id,
        location="Here",
        is_google_event=False,
    )
    create_event(
        db=session,
        title='Cool (somewhen in two days) event',
        start=today_date + timedelta(days=1),
        end=today_date + timedelta(days=3),
        all_day=False,
        content='this week test event',
        owner_id=user.id,
        location="Here",
        is_google_event=False,
    )
    yield user
    Base.metadata.drop_all(bind=test_engine)


import cv2
import math
import os
import sys
from PIL import Image
from PIL import ImageChops
import numpy

from target import *

cascPath = "haarcascade_frontalface_default.xml"

def shift_resize_images(target_frames, name, output_scale):

	#find the largest face height/width and scale all other faces

	largest_target_width = 0
	largest_target_height = 0
	for target in target_frames:
		if(target.w > largest_target_width):
			largest_target_width = target.w
		if(target.h > largest_target_height):
			largest_target_height = target.h

	# scale up images to match largest face (pixel-pixel wise)
	# (this is so all targets are the same size, and we dont lose resolution from downsizing larger images)
	for target in target_frames:
		#set new image height and width so we can match face's pixel height/width
		scaleW = largest_target_width / target.w
		scaleH = largest_target_height / target.h
		newImageW = int(scaleW*target.imageW)
		newImageH = int(scaleH*target.imageH)
		target.scale_image(newImageW, newImageH)
		#resize image
		target.PILimage = target.PILimage.resize((target.imageW, target.imageH))

		"""
		# for debugging: use cv2 with face rectangles so we can see
		img = face.PILimage
		open_cv_image = numpy.array(img)
		open_cv_image = open_cv_image[:, :, ::-1].copy()
		#rectangle marking face
		cv2.rectangle(open_cv_image, (face.x, face.y), (face.x+face.w, face.y+face.h), (0, 255, 0), 2)
		#line marking error
		cv2.line(open_cv_image, (int(face.imageW/2), int(face.imageH/2)),
			(int((face.x+face.x+face.w)/2),int((face.y+face.y+face.h)/2)), (0, 255, 0), 4)
		cv2.imshow("Face found", open_cv_image)
		cv2.waitKey(0)
		"""

	#scale outwards by specified amount so we can see more of the subject
	final_width = target_frames[0].w*output_scale[0]
	final_height = target_frames[0].h*output_scale[1]

	#position faces to the center of image and then
	#paste images onto black background
	for target in target_frames:
		background = Image.new('RGB', (final_width, final_height), (0, 0, 0))

		#offset the image to have the face centered in new, larger image
		offset = (int((final_width-target.imageW)/2+target.xErr),
			int((final_height-target.imageH)/2+target.yErr))

		background.paste(target.PILimage, offset)
		target.PILimage = background

		"""
		#convert to cv image so we can see
		open_cv_image = numpy.array(background)
		open_cv_image = open_cv_image[:, :, ::-1].copy()
		cv2.imshow("Face found", open_cv_image)
		cv2.waitKey(0)
		"""

	video=cv2.VideoWriter(f'video_{name}.avi',0,30,(final_width,final_height))

	for target in target_frames:
		open_cv_image = numpy.array(target.PILimage)
		open_cv_image = open_cv_image[:, :, ::-1].copy()
		# cv2.imshow("Face found", open_cv_image)
		# cv2.waitKey(0)
		video.write(open_cv_image)

	cv2.destroyAllWindows()
	video.release()

def parse_video(path, entity_movement_threshold):
	# files = os.listdir(imagesPath)
	# files = [file for file in files if "jpg" in file]
	# files.sort(key=lambda x:int(x[5:][:-4])) #"frame###.jpg" sorts by number
	# print(files)

	frames = []

	vidcap = cv2.VideoCapture(path)
	success,image = vidcap.read()
	count = 1
	while success:
		frames.append(image)
		success,image = vidcap.read()
		count += 1

	# Create the haar cascade
	cascade = cv2.CascadeClassifier(cascPath)
	detected_entities = []

	frame = 0

	for image in frames:

		# Read the image
		# image = cv2.imread(file)
		gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

		# Detect targets in the image
		entities = cascade.detectMultiScale(
		    gray,
		    scaleFactor=1.1,
		    minNeighbors=5,
		    minSize=(30, 30),
		)

		print(f"Found {len(entities)} entitiy(s) in video {path} frame {frame}!")

		# Draw a rectangle around the targets
		height, width, channel = image.shape

		#try to match target to entities
		for (x, y, w, h) in entities:
			if(w !=0 and h!=0):
				newTarget = Target(x,y,w,h,width,height,image)
				found = False
				for entry in detected_entities:
					if(abs(entry.frames[-1].x - x) < entity_movement_threshold
						and abs(entry.frames[-1].y - y) < entity_movement_threshold):
						entry.frames.append(newTarget)
						found=True
				if not found:
					newEntity = Entity(newTarget, len(detected_entities))
					detected_entities.append(newEntity)
				# Uncomment to see each target per frame
				# cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
				# cv2.imshow("Target found", image)
				# cv2.waitKey(0)

		frame+=1

	return detected_entities, frame

if __name__ == "__main__":

	if(len(sys.argv)!=2):
		print("Wrong arguments")
		print("Usage: face_track.py <input mp4>")
		exit()

	videoPath = sys.argv[1]

	#amount of pixel movement between frames for an entity to still be considered
	#that entity
	entity_movement_threshold = 40
	#percent of frames that an entity must be in for an entity to count as a real entity
	entity_frame_threshold = 0.50
	#amount of outside of image from face (2,3) referes to 2x larger than detection width, 3x taller than detection height
	output_scale = (2,2)

	detected_entities, frames = parse_video(videoPath, entity_movement_threshold)

	for entity in detected_entities:
		if(len(entity.frames)>=(frames*entity_frame_threshold)):
			print(f"Entity '{entity.name}' found in {len(entity.frames)} frames. Saving result")
			shift_resize_images(entity.frames, entity.name, output_scale)


from django.http import HttpResponse, JsonResponse
from booksapp.models import Books, Cached_books

def api_downloadbook_controller(helpers, sessions, request, book_id):

    # Ответ
    response = JsonResponse

    # Пользователь
    user_dict = sessions.check_if_authorized(request, True)

    # Базовые проверки
    base_checks = helpers.base_auth_checks(user_dict, response)
    if base_checks is not None:
        return base_checks

    # Если все нормально, получаем данные книги
    try:
        book_object = Books.objects.get(book_id=book_id)
        cached_book_id = book_object.cached_book_id_id
        book_name = book_object.book_name
    except Books.DoesNotExist:
        cached_book_id = 0
        book_name = 'Not_found'
    except Exception:
        return response({'success': False, 'message': 'Произошла непредвиденная ошибка'})

    # Получаем контент книги
    try:
        cached_book_object = Cached_books.objects.get(cached_book_id=cached_book_id)
        book_content = cached_book_object.book_content
    except Cached_books.DoesNotExist:
        book_content = 'Not_found'
    except Exception:
        return response({'success': False, 'message': 'Произошла непредвиденная ошибка'})
    print(helpers.translate(book_name))
    # Присваиваем http-ответу необходимые заголовки
    http_response = HttpResponse(book_content, content_type='application/octet-stream; charset=utf-8')
    http_response['Content-Disposition'] = 'attachment; filename="Book-for-reading.fb2"'

    # Возврат, если все нормально
    return http_response


#!/usr/bin/env python
# coding: utf-8

# Hello everyone,
#
# In this module, it is required to implement the preprocessor for Arabic Font Identification System.
#
# At first, what are the main steps that we should go through in this module?
#
#
# # TODOs:
#
# 1. Understand the problem
# 2. Binarization
# 3. Extract Edges
# 4. Extract Skeleton
# 5. Extract Diacritics
# 6. Extract Text only
# 7. Testing

# In[6]:

##################################################### imports #####################################################
from skimage.morphology import skeletonize
import cv2
import matplotlib.pyplot as plt
from random import randrange
import numpy as np
from skimage.segmentation import flood_fill

# # 1. Understand the problem
#
# We are interested only in the morphology of text letters, all images are first converted into binary (i.e., black text on a white background). It should be known that most images contain either meaningful texture, which is a part of the decoration or a meaningless one that resulted from the noise while capturing. In either case, we illuminate the background so it does not affect the results.
#
# - Input: --
# - Output: --

# In[27]:

def analyze_histogram(binary, show_hist=False):

    """Gets a binary image and returns it reverted if text was black (Makes text white for all images)
        Except for Square Kuffi

    Args:
        binary : The binary image you want to transform for consistency (White text on black bg) (Read using cv2)

    Returns:
        binary || reverted_binary : The outcome of analysis (Binary image with white text on black bg)
    """

    height = (binary.shape)[0]
    white_pixels_in_row = []

    # Calculate number of white pixels in a row
    for row in range(height - 1):
        white_pixels_in_row.append((binary[row, :] == 1).sum())

    # Set the threshold of white pixels in a row to be 70% of the row with most white pixels
    # We use this threshold to see how many rows contain a high number of white pixels
    pixels_threshold = int(0.7 * max(white_pixels_in_row))

    # Get the number of rows above the pixels_threshold
    rows_above_thresh = 0
    for row_pixels in white_pixels_in_row:
        if row_pixels > pixels_threshold:
            rows_above_thresh += 1

    # Calculate the ratio between the number of rows dominated by white pixels to the number of rows in the entire image
    ratio = rows_above_thresh / height

    # if show_hist:
    #     x = np.arange(height)
    #     plt.figure(figsize =(10, 7))
    #     plt.plot(x, white_pixels_in_row)
    #     plt.show()

    # If more than 70% of the image is white rows then the background is white and we have to revert the image
    if ratio > 0.7:
        return 1 - binary
    return binary

# # 2. Binarization

# In[8]:

def binarize(image):
    """Gets an image and returns it binarized having values 0 OR 1 using OTSU thresholding technique

    Args:
        image : The image you want to binarize (Read using cv2)

    Returns:
        binarized: The binarized version of the image
    """
    # Use Otsu for normal binarization
    # The parameters are 1-`The input image`, 2-`The binarization threshold (will not be used if OTSU technique is used)`,
    #                        3-`Maximum value`, 4-`Binarization technique`
    th, binary_image = cv2.threshold(image, 128, 1, cv2.THRESH_OTSU)

    # Perform histogram analysis to make text white for all images (Except for square Kuffi)
    corrected_binary_image = analyze_histogram(binary_image)
    return corrected_binary_image

# In[10]:

def crop(img):
    morph_img = img

    rows = morph_img.sum(axis=1)
    cols = morph_img.sum(axis=0)
    rows = rows != 0
    cols = cols != 0

    c = np.where(cols == cols.max())[0]
    r = np.where(rows == rows.max())[0]

    x = c[0], c[-1]
    y = r[0], r[-1]

    try:
        cropped_img = img[y[0]:y[1], x[0]:x[1]]
    except:
        cropped_img = img

    return cropped_img

# In[11]:

def image_resize(image, width = None, height = None, inter = cv2.INTER_CUBIC):
    # initialize the dimensions of the image to be resized and
    # grab the image size
    dim = None
    (h, w) = image.shape[:2]

    # if both the width and height are None, then return the
    # original image
    if width is None and height is None:
        return image

    # check to see if the width is None
    if width is None:
        # calculate the ratio of the height and construct the
        # dimensions
        t = int(height / h)
        x = int(h / height)
        if x == 0:
            height = int(h*t)
        else:
            height = int(h/x)
        if h > height: #shrink
            inter = cv2.INTER_AREA
        r = height / float(h)
        dim = (int(w * r), height)

    # otherwise, the height is None
    else:
        # calculate the ratio of the width and construct the
        # dimensions
        t = int(width / w)
        x = int(w / width)
        if x == 0:
            width = int(w*t)
        else:
            width = int(w/x)

        if w > width: #shrink
            inter = cv2.INTER_AREA
        r = width / float(w)
        dim = (width, int(h * r))

    # resize the image
    resized = cv2.resize(image, dim, interpolation = inter)

    # return the resized image
    return resized

# # 3. Extract Edges

# In[12]:

def extract_edges(image):
    """Gets the original input image and returns an image containing its edges using canny edge detector

    Args:
        image: The image you want to extract its edges (Read using cv2)

    Returns:
        edge_image: The edge extracted image
    """
    # Blur the image for better edge detection
    if image.max() == 1:
        image = image * 255
        image = image.astype(np.uint8)

    img_blur = cv2.GaussianBlur(image,(5,5), sigmaX=0, sigmaY=0)

    # Use Canny edge detection to extract edges
    edge_image = cv2.Canny(image=img_blur, threshold1=100, threshold2=200)

    # Change image max value from 255 to 1
    edge_image[edge_image == 255] = 1

    return edge_image

# # 4. Extract Skeleton

# In[14]:

def extract_skeleton(image):
    """Gets an image and returns an image containing its skeleton

    Args:
        image: The image you want to skeletonize (Read using cv2)

    Returns:
        skeleton: The skeletonized image
    """
    # Use Skimage's skeletonize method with the array representation of the binary image as input
    skeleton = skeletonize(np.asarray(image)).astype(np.uint8)

    return skeleton

# # 5. Separate Diacritics from Text

# In[15]:

def separate_diacritics_and_text_utility(image):
    """Utility function used by separate_diacritics_and_text
    """

    # binary = binarize(image)
    binary = image

    # height = (binary.shape)[0]
    # white_pixels_in_row = np.zeros(height, dtype=np.uint32)
    # Calculate number of white pixels in a row
    # for row in range(height - 1):
        # white_pixels_in_row[row] = (binary[row, :] == 1).sum()

    white_pixels_in_row = binary.sum(axis=1)

    # Get the row with most white pixels (This row would contain only text and commas no diacritics
    #                                        as it is an imaginary line where text is placed on)
    baseline_row = np.argmax(white_pixels_in_row)
    diacritics_image = binary

    # How Flood Fill works for our case:
    #   It starts at a seed point (pixel) and changes all connected pixels to it to have a certain color
    #   So what we want to do is get the first pixel of each word along the line (it would be black) and
    #   use that point as seed for the flood_fill algorithm changing that word to have the same color as
    #   the background. We keep doing that for all words until only diacritics remain.

    # Loop through the base_line's pixels to apply flood fill on all words
    rowPixels = binary[baseline_row, :]
    for i in range(len(rowPixels) - 1):
        # Handle the case where the first pixel is part of a word so we apply the flood fill from the end of the word
        if rowPixels[i] == 1 and rowPixels[i+1] == 0:
            diacritics_image = flood_fill(diacritics_image, (baseline_row, i), 0)
        elif rowPixels[i] == 0 and rowPixels[i+1] == 1:
            diacritics_image = flood_fill(diacritics_image, (baseline_row, i+1), 0)

    # print(baseline_row, binary.shape[0])
    text_image = binary - diacritics_image
    cv2.line(text_image,(0,baseline_row),(image.shape[1],baseline_row),(0,0,255),2)

    return diacritics_image, text_image

# In[16]:

def separate_diacritics_and_text(image, diacritics_ratio=0.2, max_iterations=10, text_convergence_threshold=5):
    """Gets an input image of the arabic text and returns an image containing only the diacritics in it and another
    image with the text as a tuple (diacritics, text)

    Args:
        image: The image you want to separate its diacritics from its text (Read using cv2)
        diacritics_ratio: threshold ratio of diacritics to text (default 0.2)
        max_iterations: maximum number of iterations to run the separtion algorithm (default 1000)
    Returns:
        diacritics_image: A binary image containing only diacritics (white text on black bg)
        text_image: A binary image containing only text (white text on black bg)
    """

    diacritics_image, text_image = separate_diacritics_and_text_utility(image)
    # while diacritics_image.sum() > diacritics_image.size * diacritics_ratio:
    while diacritics_image.sum() > text_image.sum() * diacritics_ratio and max_iterations:
        diacritics_image, text_image_1 = separate_diacritics_and_text_utility(diacritics_image)
        if text_image_1.sum() < text_convergence_threshold:
            break
        text_image = text_image + text_image_1
        max_iterations = max_iterations - 1

    return diacritics_image, text_image

# # 6. Testing

# In[17]:

def testing():

    # importing io module
    import sys
    sys.path.insert(1, "./../io_utils/")
    from io_utils import read_data, read_classes

    # reading data and class names
    classes_names = read_classes('../ACdata_base/names.txt')
    dataset_images, dataset_labels = read_data('../ACdata_base/')

    assert len(dataset_images) == len(dataset_labels)

    # get the range of each class in the dataset
    ranges = [0]
    tmp = dataset_labels[0]
    for idx, num in enumerate(dataset_labels):
        if num != tmp:
            tmp = num
            ranges.append(idx)
    ranges.append(len(dataset_labels))

    # plt.imshow(crop(binarize(cv2.imread("../ACdata_base/9/1624.jpg", 0))), cmap='gray')

    # Choosing a random example from each class and apply the preprocessing Functions on it
    for i, class_name in enumerate(classes_names):
        index = randrange(ranges[i], ranges[i+1])
        test_image = dataset_images[index]

        if(i == 8):
            test_image = cv2.imread("../ACdata_base/9/1624.jpg", 0)

        binary_image = binarize(test_image)
        cropped_image = crop(binary_image)
        assert len(np.unique(np.asarray(binary_image))) == 2

        edge_image = extract_edges(cropped_image)
        skeleton_image = extract_skeleton(cropped_image)
        diacritics_image, text_image = separate_diacritics_and_text(cropped_image)

        f, axarr = plt.subplots(3,2, figsize=(15, 10))
        f.suptitle(class_name + " test")

        axarr[0,0].imshow(test_image, cmap='gray')
        axarr[0,0].set_title("Original")

        # axarr[0,1].imshow(binary_image, cmap='gray')
        # axarr[0,1].set_title("Binary")
        axarr[0,1].imshow(cropped_image, cmap='gray')
        axarr[0,1].set_title("Cropped Binary")

        axarr[1,0].imshow(edge_image, cmap='gray')
        axarr[1,0].set_title("Edge")

        axarr[1,1].imshow(skeleton_image, cmap='gray')
        axarr[1,1].set_title("Skeleton")

        axarr[2,0].imshow(diacritics_image, cmap='gray')
        axarr[2,0].set_title("Diacritics")

        axarr[2,1].imshow(text_image, cmap='gray')
        axarr[2,1].set_title("Text Only")

# In[21]:

# if __name__ == '__main__':
    # testing()

# In[28]:

def create_py():
    get_ipython().system('jupyter nbconvert --to script preprocessing.ipynb')

# In[29]:

if __name__ == '__main__':
    create_py()

# In[ ]:


# Training script for all the models

import numpy as np
import torch
import torch.nn.functional as F
import torch.optim as optim
from tqdm import trange
import argparse
import matplotlib.pyplot as plt
from settings_MIMO import *
from networks_generator_MIMO import generate_real_channels, estimate_channels
from neural_net_MIMO import Regular_Net, Robust_Net

N_EPOCHES = 500
N_MINIBATCHES = 50
MINIBATCH_SIZE = 1000
VALIDATION_SIZE = 2000
TRAINING_DEBUG = False

def plot_training_curves(objs, starting_point):
    print("Plotting training curves...")
    fig, axes = plt.subplots(1,2)
    fig.suptitle("MIMO over {}".format(SETTING_STRING))
    # Regular Net
    axes[0].set_xlabel("Training Epoches")
    axes[0].set_ylabel(f"Nominal Min Rate (Unscaled)")
    axes[0].set_title("Regular Neural Network")
    axes[0].plot(objs[starting_point:, 0], 'r', label="Train")
    axes[0].plot(objs[starting_point:, 2], 'b--', linewidth=0.8, label="Valid")
    axes[0].legend()
    # Robust Net
    axes[1].set_xlabel("Training Epoches")
    axes[1].set_ylabel(f"Robust Min Rate (Unscaled)")
    axes[1].set_title("Robust Neural Network")
    axes[1].plot(objs[starting_point:, 1], 'r', label="Train")
    axes[1].plot(objs[starting_point:, 3], 'b--', linewidth=0.8, label="Valid")
    axes[1].legend()
    plt.show()
    print("Finished plotting!")
    return

if(__name__=="__main__"):
    parser = argparse.ArgumentParser(description="main script argument parser")
    parser.add_argument("--plot", default=False, help="Whether plot the training curves (executed after training)")
    parser.add_argument("--start", type=int, default=0, help="The starting point of training updates to plot training curves")
    args = parser.parse_args()
    if args.plot:
        sinrs = np.load(f"Trained_Models_MIMO/training_curves_Min-Rate_{SETTING_STRING}.npy")
        plot_training_curves(sinrs, args.start)
        exit(0)

    regular_net = Regular_Net().to(DEVICE)
    robust_net = Robust_Net().to(DEVICE)
    regular_optimizer, robust_optimizer = optim.Adam(regular_net.parameters(), lr=1e-4), optim.Adam(robust_net.parameters(), lr=1e-4)
    regular_obj_max, robust_obj_max = -np.inf, -np.inf
    obj_eps = []
    for i in trange(1, N_EPOCHES+1):
        # [TRAIN]
        regular_obj_ep, robust_obj_ep = 0, 0
        regular_net.train()
        robust_net.train()
        for j in range(N_MINIBATCHES):
            # Generate minibatch training data online
            H_est = estimate_channels(generate_real_channels(MINIBATCH_SIZE))
            # [Regular net]
            regular_optimizer.zero_grad()
            robust_optimizer.zero_grad()
            regular_obj, _ = regular_net(torch.tensor(H_est, dtype=torch.cfloat).to(DEVICE))
            robust_obj, _ = robust_net(torch.tensor(H_est, dtype=torch.cfloat).to(DEVICE))
            (-regular_obj).backward(); regular_optimizer.step()
            (-robust_obj).backward(); robust_optimizer.step()
            regular_obj_ep += regular_obj.item() / N_MINIBATCHES
            robust_obj_ep += robust_obj.item() / N_MINIBATCHES
        # [VALIDATE]
        H_est = estimate_channels(generate_real_channels(VALIDATION_SIZE))
        with torch.no_grad():
            regular_obj, _ = regular_net(torch.tensor(H_est, dtype=torch.cfloat).to(DEVICE))
            regular_obj = regular_obj.item()
            robust_obj, _ = robust_net(torch.tensor(H_est, dtype=torch.cfloat).to(DEVICE))
            robust_obj = robust_obj.item()
        obj_eps.append([regular_obj_ep, robust_obj_ep, regular_obj, robust_obj])
        print("[Min Rate Unscaled with RCI alpha {}] [Regular] Tr:{:7.4e}; Va:{:7.4e} [Robust] Tr:{:7.4e}; Va:{:7.4e}".format(
            RCI_BF_ALPHA, regular_obj_ep, regular_obj, robust_obj_ep, robust_obj))
        if regular_obj > regular_obj_max:
            regular_net.save_model()
            regular_obj_max = regular_obj
        if robust_obj > robust_obj_max:
            robust_net.save_model()
            robust_obj_max = robust_obj
        np.save(f"Trained_Models_MIMO/training_curves_Min-Rate_{SETTING_STRING}.npy", np.array(obj_eps))

    print("Training Script finished!")


"""Creating email lists for my classes based on the banner website is
annoying.  How to handle this on a Mac is apparently also a little
tricky.  So, I am tweaking it again.  I think the main Mac issue is
that clicking on the email class link opens the link in the mail app
which fills in the names of people whose email addesses are found.  My
code for making an email list assumes the names are not present.

I would like to write a function that takes a list of text which may
or may not contain line breaks or more than one email per line and
clean it up.  The code should also remove any names in the email
string."""
#jbargie@siue.edu, cbranch@siue.edu, jdennin@siue.edu, lyhogan@siue.edu, bkasmar@siue.edu, mostroo@siue.edu, bsaligr@siue.edu, pwollen@siue.edu, Derek Briesacher <dbriesa@siue.edu>, Patrick Buller <pbuller@siue.edu>, thartna@siue.edu, Gregory Jacobs <gjacobs@siue.edu>, cjames@siue.edu, jkutz@siue.edu, dowens@siue.edu, Austin Schukar <aschuka@siue.edu>, jstilt@siue.edu

import re
p = re.compile('<(.*)>')

def clean_one_email(email_in):
    email_out = email_in.strip()
    if email_out.find('<') > -1:
        q = p.search(email_out)
        if q is not None:
            email_out = q.group(1)
    return email_out

def clean_raw_emails(txtlist_in, delim=','):
    """Take a list of text lines and return a list containing one
    email per line.  Remove names if they are present.  Break lines
    that contain delim into lists."""
    if type(txtlist_in) == str:
        txtlist_in = [txtlist_in]

    all_emails = []

    for line in txtlist_in:
        cur_list = line.split(delim)
        cur_clean = [clean_one_email(item) for item in cur_list]
        all_emails.extend(cur_clean)

    return all_emails

def find_one_email(lastname, firstname, email_list):
    last = lastname.lower()
    first = firstname.lower()

    Nf = len(first)

    i = 1

    N = 7

    while i < Nf:
        fi = first[0:i]
        full = fi + last
        if len(full) > N:
            full = full[0:N]

        inds = email_list.findall(full)
        if not inds:
            i += 1
        else:
            if len(inds) > 1:
                matchstr = ''
                for ind in inds:
                    matchstr += email_list[ind] + ' '
            assert len(inds) == 1, 'Found more than one match for %s, matches: %s' % (full, matchstr)
            return email_list[inds[0]]