docs: 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