text: #! /usr/bin/env python
  # -*- coding:utf-8 -*-
  # Author: Xiangtai Li(xiangtai94@gmail.com)
  # flow lib for img level operation
  
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  import numpy as np
  from utils.layers.flow.lib.io import read_flow
  UNKNOWN_FLOW_THRESH = 1e7
  SMALLFLOW = 0.0
  LARGEFLOW = 1e8
  # note this is numpy implementation
  def flow_error(tu, tv, u, v):
      """
      Calculate average end point error
      :param tu: ground-truth horizontal flow map
      :param tv: ground-truth vertical flow map
      :param u:  estimated horizontal flow map
      :param v:  estimated vertical flow map
      :return: End point error of the estimated flow
      """
      smallflow = 0.0
      '''
      stu = tu[bord+1:end-bord,bord+1:end-bord]
      stv = tv[bord+1:end-bord,bord+1:end-bord]
      su = u[bord+1:end-bord,bord+1:end-bord]
      sv = v[bord+1:end-bord,bord+1:end-bord]
      '''
      stu = tu[:]
      stv = tv[:]
      su = u[:]
      sv = v[:]
  
      idxUnknow = (abs(stu) > UNKNOWN_FLOW_THRESH) | (abs(stv) > UNKNOWN_FLOW_THRESH)
      stu[idxUnknow] = 0
      stv[idxUnknow] = 0
      su[idxUnknow] = 0
      sv[idxUnknow] = 0
  
      ind2 = [(np.absolute(stu) > smallflow) | (np.absolute(stv) > smallflow)]
      index_su = su[ind2]
      index_sv = sv[ind2]
      an = 1.0 / np.sqrt(index_su ** 2 + index_sv ** 2 + 1)
      un = index_su * an
      vn = index_sv * an
  
      index_stu = stu[ind2]
      index_stv = stv[ind2]
      tn = 1.0 / np.sqrt(index_stu ** 2 + index_stv ** 2 + 1)
      tun = index_stu * tn
      tvn = index_stv * tn
  
      '''
      angle = un * tun + vn * tvn + (an * tn)
      index = [angle == 1.0]
      angle[index] = 0.999
      ang = np.arccos(angle)
      mang = np.mean(ang)
      mang = mang * 180 / np.pi
      '''
  
      epe = np.sqrt((stu - su) ** 2 + (stv - sv) ** 2)
      epe = epe[ind2]
      mepe = np.mean(epe)
      return mepe
  
  def flow_to_image(flow, display=False):
      """
      Convert flow into middlebury color code image
      :param flow: optical flow map
      :return: optical flow image in middlebury color
      """
      u = flow[:, :, 0]
      v = flow[:, :, 1]
  
      maxu = -999.
      maxv = -999.
      minu = 999.
      minv = 999.
  
      idxUnknow = (abs(u) > UNKNOWN_FLOW_THRESH) | (abs(v) > UNKNOWN_FLOW_THRESH)
      u[idxUnknow] = 0
      v[idxUnknow] = 0
  
      maxu = max(maxu, np.max(u))
      minu = min(minu, np.min(u))
  
      maxv = max(maxv, np.max(v))
      minv = min(minv, np.min(v))
  
      rad = np.sqrt(u ** 2 + v ** 2)
      maxrad = max(-1, np.max(rad))
  
      if display:
          print("max flow: %.4f\nflow range:\nu = %.3f .. %.3f\nv = %.3f .. %.3f" % (maxrad, minu,maxu, minv, maxv))
  
      u = u/(maxrad + np.finfo(float).eps)
      v = v/(maxrad + np.finfo(float).eps)
  
      img = compute_color(u, v)
  
      idx = np.repeat(idxUnknow[:, :, np.newaxis], 3, axis=2)
      img[idx] = 0
  
      return np.uint8(img)
  
  
  def evaluate_flow_file(gt, pred):
      """
      evaluate the estimated optical flow end point error according to ground truth provided
      :param gt: ground truth file path
      :param pred: estimated optical flow file path
      :return: end point error, float32
      """
      # Read flow files and calculate the errors
      gt_flow = read_flow(gt)        # ground truth flow
      eva_flow = read_flow(pred)     # predicted flow
      # Calculate errors
      average_pe = flow_error(gt_flow[:, :, 0], gt_flow[:, :, 1], eva_flow[:, :, 0], eva_flow[:, :, 1])
      return average_pe
  
  
  def evaluate_flow(gt_flow, pred_flow):
      """
      gt: ground-truth flow
      pred: estimated flow
      """
      average_pe = flow_error(gt_flow[:, :, 0], gt_flow[:, :, 1], pred_flow[:, :, 0], pred_flow[:, :, 1])
      return average_pe
  
  def compute_color(u, v):
      """
      compute optical flow color map
      :param u: optical flow horizontal map
      :param v: optical flow vertical map
      :return: optical flow in color code
      """
      [h, w] = u.shape
      img = np.zeros([h, w, 3])
      nanIdx = np.isnan(u) | np.isnan(v)
      u[nanIdx] = 0
      v[nanIdx] = 0
  
      colorwheel = make_color_wheel()
      ncols = np.size(colorwheel, 0)
  
      rad = np.sqrt(u**2+v**2)
  
      a = np.arctan2(-v, -u) / np.pi
  
      fk = (a+1) / 2 * (ncols - 1) + 1
  
      k0 = np.floor(fk).astype(int)
  
      k1 = k0 + 1
      k1[k1 == ncols+1] = 1
      f = fk - k0
  
      for i in range(0, np.size(colorwheel,1)):
          tmp = colorwheel[:, i]
          col0 = tmp[k0-1] / 255
          col1 = tmp[k1-1] / 255
          col = (1-f) * col0 + f * col1
  
          idx = rad <= 1
          col[idx] = 1-rad[idx]*(1-col[idx])
          notidx = np.logical_not(idx)
  
          col[notidx] *= 0.75
          img[:, :, i] = np.uint8(np.floor(255 * col*(1-nanIdx)))
  
      return img
  
  def make_color_wheel():
      """
      Generate color wheel according Middlebury color code
      :return: Color wheel
      """
      RY = 15
      YG = 6
      GC = 4
      CB = 11
      BM = 13
      MR = 6
  
      ncols = RY + YG + GC + CB + BM + MR
  
      colorwheel = np.zeros([ncols, 3])
  
      col = 0
  
      # RY
      colorwheel[0:RY, 0] = 255
      colorwheel[0:RY, 1] = np.transpose(np.floor(255*np.arange(0, RY) / RY))
      col += RY
  
      # YG
      colorwheel[col:col+YG, 0] = 255 - np.transpose(np.floor(255*np.arange(0, YG) / YG))
      colorwheel[col:col+YG, 1] = 255
      col += YG
  
      # GC
      colorwheel[col:col+GC, 1] = 255
      colorwheel[col:col+GC, 2] = np.transpose(np.floor(255*np.arange(0, GC) / GC))
      col += GC
  
      # CB
      colorwheel[col:col+CB, 1] = 255 - np.transpose(np.floor(255*np.arange(0, CB) / CB))
      colorwheel[col:col+CB, 2] = 255
      col += CB
  
      # BM
      colorwheel[col:col+BM, 2] = 255
      colorwheel[col:col+BM, 0] = np.transpose(np.floor(255*np.arange(0, BM) / BM))
      col += + BM
  
      # MR
      colorwheel[col:col+MR, 2] = 255 - np.transpose(np.floor(255 * np.arange(0, MR) / MR))
      colorwheel[col:col+MR, 0] = 255
  
      return colorwheel
  
  def scale_image(image, new_range):
      """
      Linearly scale the image into desired range
      :param image: input image
      :param new_range: the new range to be aligned
      :return: image normalized in new range
      """
      min_val = np.min(image).astype(np.float32)
      max_val = np.max(image).astype(np.float32)
      min_val_new = np.array(min(new_range), dtype=np.float32)
      max_val_new = np.array(max(new_range), dtype=np.float32)
      scaled_image = (image - min_val) / (max_val - min_val) * (max_val_new - min_val_new) + min_val_new
      return scaled_image.astype(np.uint8)
  
  def warp_image(im, flow):
      """
      Use optical flow to warp image to the next, note this is numpy implementation
      :param im: image to warp
      :param flow: optical flow
      :return: warped image
      """
      from scipy import interpolate
      image_height = im.shape[0]
      image_width = im.shape[1]
      flow_height = flow.shape[0]
      flow_width = flow.shape[1]
      n = image_height * image_width
      (iy, ix) = np.mgrid[0:image_height, 0:image_width].astype(np.float64)
      (fy, fx) = np.mgrid[0:flow_height, 0:flow_width].astype(np.float64)
      fx += flow[:,:,0]
      fy += flow[:,:,1]
      mask = np.logical_or(fx <0 , fx > flow_width)
      mask = np.logical_or(mask, fy < 0)
      mask = np.logical_or(mask, fy > flow_height)
      fx = np.minimum(np.maximum(fx, 0), flow_width)
      fy = np.minimum(np.maximum(fy, 0), flow_height)
      points = np.concatenate((ix.reshape(n,1), iy.reshape(n,1)), axis=1)
      xi = np.concatenate((fx.reshape(n, 1), fy.reshape(n,1)), axis=1)
      warp = np.zeros((image_height, image_width, im.shape[2]))
      for i in range(im.shape[2]): # for each channel
          print(i)
          channel = im[:, :, i]
          #plt.imshow(channel, cmap='gray')
          values = channel.reshape(n, 1)
          new_channel = interpolate.griddata(points, values, xi, method='cubic')
          new_channel = np.reshape(new_channel, [flow_height, flow_width])
          new_channel[mask] = 1
          warp[:, :, i] = new_channel.astype(np.uint8)
  
      return warp.astype(np.uint8)