content: ''' Lots of functions for drawing and plotting visiony things '''
  # TODO: New naming scheme
  # viz_<func_name> will clear everything. The current axes and fig: clf, cla.  # Will add annotations
  # interact_<func_name> will clear everything and start user interactions.
  # show_<func_name> will always clear the current axes, but not fig: cla # Might # add annotates?
  # plot_<func_name> will not clear the axes or figure. More useful for graphs
  # draw_<func_name> same as plot for now. More useful for images
  from __future__ import division, print_function
  from hscom import __common__
  (print, print_, print_on, print_off, rrr, profile,
   printDBG) = __common__.init(__name__, '[df2]', DEBUG=False, initmpl=True)
  # Python
  from itertools import izip
  from os.path import splitext, split, join, normpath, exists
  import colorsys
  import itertools
  import pylab
  import sys
  import textwrap
  import time
  import warnings
  # Matplotlib / Qt
  import matplotlib
  import matplotlib as mpl  # NOQA
  from matplotlib.collections import PatchCollection, LineCollection
  from matplotlib.font_manager import FontProperties
  from matplotlib.patches import Rectangle, Circle, FancyArrow
  from matplotlib.transforms import Affine2D
  from matplotlib.backends import backend_qt4
  import matplotlib.pyplot as plt
  # Qt
  from PyQt4 import QtCore, QtGui
  from PyQt4.QtCore import Qt
  # Scientific
  import numpy as np
  import scipy.stats
  import cv2
  # HotSpotter
  from hscom import helpers
  from hscom import tools
  from hscom.Printable import DynStruct
  
  #================
  # GLOBALS
  #================
  
  TMP_mevent = None
  QT4_WINS = []
  plotWidget = None
  
  # GENERAL FONTS
  
  SMALLER  = 8
  SMALL    = 10
  MED      = 12
  LARGE    = 14
  #fpargs = dict(family=None, style=None, variant=None, stretch=None, fname=None)
  FONTS = DynStruct()
  FONTS.small     = FontProperties(weight='light', size=SMALL)
  FONTS.smaller   = FontProperties(weight='light', size=SMALLER)
  FONTS.med       = FontProperties(weight='light', size=MED)
  FONTS.large     = FontProperties(weight='light', size=LARGE)
  FONTS.medbold   = FontProperties(weight='bold', size=MED)
  FONTS.largebold = FontProperties(weight='bold', size=LARGE)
  
  # SPECIFIC FONTS
  
  FONTS.legend   = FONTS.small
  FONTS.figtitle = FONTS.med
  FONTS.axtitle  = FONTS.med
  FONTS.subtitle = FONTS.med
  FONTS.xlabel   = FONTS.smaller
  FONTS.ylabel   = FONTS.small
  FONTS.relative = FONTS.smaller
  
  # COLORS
  
  ORANGE = np.array((255, 127,   0, 255)) / 255.0
  RED    = np.array((255,   0,   0, 255)) / 255.0
  GREEN  = np.array((  0, 255,   0, 255)) / 255.0
  BLUE   = np.array((  0,   0, 255, 255)) / 255.0
  YELLOW = np.array((255, 255,   0, 255)) / 255.0
  BLACK  = np.array((  0,   0,   0, 255)) / 255.0
  WHITE  = np.array((255, 255, 255, 255)) / 255.0
  GRAY   = np.array((127, 127, 127, 255)) / 255.0
  DEEP_PINK    = np.array((255,  20, 147, 255)) / 255.0
  PINK         = np.array((255,  100, 100, 255)) / 255.0
  FALSE_RED    = np.array((255,  51,   0, 255)) / 255.0
  TRUE_GREEN   = np.array((  0, 255,   0, 255)) / 255.0
  DARK_ORANGE  = np.array((127,  63,   0, 255)) / 255.0
  DARK_YELLOW  = np.array((127,  127,   0, 255)) / 255.0
  PURPLE = np.array((102,   0, 153, 255)) / 255.0
  UNKNOWN_PURP = PURPLE
  
  # FIGURE GEOMETRY
  
  DPI = 80
  #DPI = 160
  #FIGSIZE = (24) # default windows fullscreen
  FIGSIZE_MED = (12, 6)
  FIGSIZE_SQUARE = (12, 12)
  FIGSIZE_BIGGER = (24, 12)
  FIGSIZE_HUGE = (32, 16)
  
  FIGSIZE = FIGSIZE_MED
  # Quality drawings
  #FIGSIZE = FIGSIZE_SQUARE
  #DPI = 120
  
  tile_within = (-1, 30, 969, 1041)
  if helpers.get_computer_name() == 'Ooo':
      TILE_WITHIN = (-1912, 30, -969, 1071)
  
  # DEFAULTS. (TODO: Can these be cleaned up?)
  
  DISTINCT_COLORS = True  # and False
  DARKEN = None
  ELL_LINEWIDTH = 1.5
  if DISTINCT_COLORS:
      ELL_ALPHA  = .6
      LINE_ALPHA = .35
  else:
      ELL_ALPHA  = .4
      LINE_ALPHA = .4
  
  LINE_ALPHA_OVERRIDE = helpers.get_arg('--line-alpha-override', type_=float, default=None)
  ELL_ALPHA_OVERRIDE = helpers.get_arg('--ell-alpha-override', type_=float, default=None)
  #LINE_ALPHA_OVERRIDE = None
  #ELL_ALPHA_OVERRIDE = None
  ELL_COLOR  = BLUE
  
  LINE_COLOR = RED
  LINE_WIDTH = 1.4
  
  SHOW_LINES = True  # True
  SHOW_ELLS  = True
  
  POINT_SIZE = 2
  
  
  base_fnum = 9001
  
  
  def next_fnum():
      global base_fnum
      base_fnum += 1
      return base_fnum
  
  
  def my_prefs():
      global LINE_COLOR
      global ELL_COLOR
      global ELL_LINEWIDTH
      global ELL_ALPHA
      LINE_COLOR = (1, 0, 0)
      ELL_COLOR = (0, 0, 1)
      ELL_LINEWIDTH = 2
      ELL_ALPHA = .5
  
  
  def execstr_global():
      execstr = ['global' + key for key in globals().keys()]
      return execstr
  
  
  def register_matplotlib_widget(plotWidget_):
      'talks to PyQt4 guis'
      global plotWidget
      plotWidget = plotWidget_
      #fig = plotWidget.figure
      #axes_list = fig.get_axes()
      #ax = axes_list[0]
      #plt.sca(ax)
  
  
  def unregister_qt4_win(win):
      global QT4_WINS
      if win == 'all':
          QT4_WINS = []
  
  
  def register_qt4_win(win):
      global QT4_WINS
      QT4_WINS.append(win)
  
  
  def OooScreen2():
      nRows = 1
      nCols = 1
      x_off = 30 * 4
      y_off = 30 * 4
      x_0 = -1920
      y_0 = 30
      w = (1912 - x_off) / nRows
      h = (1080 - y_off) / nCols
      return dict(num_rc=(1, 1), wh=(w, h), xy_off=(x_0, y_0), wh_off=(0, 10),
                  row_first=True, no_tile=False)
  
  
  def deterministic_shuffle(list_):
      randS = int(np.random.rand() * np.uint(0 - 2) / 2)
      np.random.seed(len(list_))
      np.random.shuffle(list_)
      np.random.seed(randS)
  
  
  def distinct_colors(N, brightness=.878):
      # http://blog.jianhuashao.com/2011/09/generate-n-distinct-colors.html
      sat = brightness
      val = brightness
      HSV_tuples = [(x * 1.0 / N, sat, val) for x in xrange(N)]
      RGB_tuples = map(lambda x: colorsys.hsv_to_rgb(*x), HSV_tuples)
      deterministic_shuffle(RGB_tuples)
      return RGB_tuples
  
  
  def add_alpha(colors):
      return [list(color) + [1] for color in colors]
  
  
  def _axis_xy_width_height(ax, xaug=0, yaug=0, waug=0, haug=0):
      'gets geometry of a subplot'
      autoAxis = ax.axis()
      xy     = (autoAxis[0] + xaug, autoAxis[2] + yaug)
      width  = (autoAxis[1] - autoAxis[0]) + waug
      height = (autoAxis[3] - autoAxis[2]) + haug
      return xy, width, height
  
  
  def draw_border(ax, color=GREEN, lw=2, offset=None):
      'draws rectangle border around a subplot'
      xy, width, height = _axis_xy_width_height(ax, -.7, -.2, 1, .4)
      if offset is not None:
          xoff, yoff = offset
          xy = [xoff, yoff]
          height = - height - yoff
          width = width - xoff
      rect = matplotlib.patches.Rectangle(xy, width, height, lw=lw)
      rect = ax.add_patch(rect)
      rect.set_clip_on(False)
      rect.set_fill(False)
      rect.set_edgecolor(color)
  
  
  def draw_roi(roi, label=None, bbox_color=(1, 0, 0),
               lbl_bgcolor=(0, 0, 0), lbl_txtcolor=(1, 1, 1), theta=0, ax=None):
      if ax is None:
          ax = gca()
      (rx, ry, rw, rh) = roi
      #cos_ = np.cos(theta)
      #sin_ = np.sin(theta)
      #rot_t = Affine2D([( cos_, -sin_, 0),
                        #( sin_,  cos_, 0),
                        #(  0,       0, 1)])
      #scale_t = Affine2D([( rw,  0, 0),
                          #( 0,  rh, 0),
                          #( 0,   0, 1)])
      #trans_t = Affine2D([( 1,  0, rx + rw / 2),
                          #( 0,  1, ry + rh / 2),
                          #( 0,  0, 1)])
      #t_end = scale_t + rot_t + trans_t + t_start
      # Transformations are specified in backwards order.
      trans_roi = Affine2D()
      trans_roi.scale(rw, rh)
      trans_roi.rotate(theta)
      trans_roi.translate(rx + rw / 2, ry + rh / 2)
      t_end = trans_roi + ax.transData
      bbox = matplotlib.patches.Rectangle((-.5, -.5), 1, 1, lw=2, transform=t_end)
      arw_x, arw_y, arw_dx, arw_dy   = (-0.5, -0.5, 1.0, 0.0)
      arrowargs = dict(head_width=.1, transform=t_end, length_includes_head=True)
      arrow = FancyArrow(arw_x, arw_y, arw_dx, arw_dy, **arrowargs)
  
      bbox.set_fill(False)
      #bbox.set_transform(trans)
      bbox.set_edgecolor(bbox_color)
      arrow.set_edgecolor(bbox_color)
      arrow.set_facecolor(bbox_color)
  
      ax.add_patch(bbox)
      ax.add_patch(arrow)
      #ax.add_patch(arrow2)
      if label is not None:
          ax_absolute_text(rx, ry, label, ax=ax,
                           horizontalalignment='center',
                           verticalalignment='center',
                           color=lbl_txtcolor,
                           backgroundcolor=lbl_bgcolor)
  
  
  # ---- GENERAL FIGURE COMMANDS ----
  def sanatize_img_fname(fname):
      fname_clean = fname
      search_replace_list = [(' ', '_'), ('\n', '--'), ('\\', ''), ('/', '')]
      for old, new in search_replace_list:
          fname_clean = fname_clean.replace(old, new)
      fname_noext, ext = splitext(fname_clean)
      fname_clean = fname_noext + ext.lower()
      # Check for correct extensions
      if not ext.lower() in helpers.IMG_EXTENSIONS:
          fname_clean += '.png'
      return fname_clean
  
  
  def sanatize_img_fpath(fpath):
      [dpath, fname] = split(fpath)
      fname_clean = sanatize_img_fname(fname)
      fpath_clean = join(dpath, fname_clean)
      fpath_clean = normpath(fpath_clean)
      return fpath_clean
  
  
  def set_geometry(fnum, x, y, w, h):
      fig = get_fig(fnum)
      qtwin = fig.canvas.manager.window
      qtwin.setGeometry(x, y, w, h)
  
  
  def get_geometry(fnum):
      fig = get_fig(fnum)
      qtwin = fig.canvas.manager.window
      (x1, y1, x2, y2) = qtwin.geometry().getCoords()
      (x, y, w, h) = (x1, y1, x2 - x1, y2 - y1)
      return (x, y, w, h)
  
  
  def get_screen_info():
      from PyQt4 import Qt, QtGui  # NOQA
      desktop = QtGui.QDesktopWidget()
      mask = desktop.mask()  # NOQA
      layout_direction = desktop.layoutDirection()  # NOQA
      screen_number = desktop.screenNumber()  # NOQA
      normal_geometry = desktop.normalGeometry()  # NOQA
      num_screens = desktop.screenCount()  # NOQA
      avail_rect = desktop.availableGeometry()  # NOQA
      screen_rect = desktop.screenGeometry()  # NOQA
      QtGui.QDesktopWidget().availableGeometry().center()  # NOQA
      normal_geometry = desktop.normalGeometry()  # NOQA
  
  
  def get_all_figures():
      all_figures_ = [manager.canvas.figure for manager in
                      matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
      all_figures = []
      # Make sure you dont show figures that this module closed
      for fig in iter(all_figures_):
          if not 'df2_closed' in fig.__dict__.keys() or not fig.df2_closed:
              all_figures.append(fig)
      # Return all the figures sorted by their number
      all_figures = sorted(all_figures, key=lambda fig: fig.number)
      return all_figures
  
  
  def get_all_qt4_wins():
      return QT4_WINS
  
  
  def all_figures_show():
      if plotWidget is not None:
          plotWidget.figure.show()
          plotWidget.figure.canvas.draw()
      for fig in iter(get_all_figures()):
          time.sleep(.1)
          fig.show()
          fig.canvas.draw()
  
  
  def all_figures_tight_layout():
      for fig in iter(get_all_figures()):
          fig.tight_layout()
          #adjust_subplots()
          time.sleep(.1)
  
  
  def get_monitor_geom(monitor_num=0):
      from PyQt4 import QtGui  # NOQA
      desktop = QtGui.QDesktopWidget()
      rect = desktop.availableGeometry()
      geom = (rect.x(), rect.y(), rect.width(), rect.height())
      return geom
  
  
  def golden_wh(x):
      'returns a width / height with a golden aspect ratio'
      return map(int, map(round, (x * .618, x * .312)))
  
  
  def all_figures_tile(num_rc=(3, 4), wh=1000, xy_off=(0, 0), wh_off=(0, 10),
                       row_first=True, no_tile=False, override1=False):
      'Lays out all figures in a grid. if wh is a scalar, a golden ratio is used'
      # RCOS TODO:
      # I want this function to layout all the figures and qt windows within the
      # bounds of a rectangle. (taken from the get_monitor_geom, or specified by
      # the user i.e. left half of monitor 0). It should lay them out
      # rectangularly and choose figure sizes such that all of them will fit.
      if no_tile:
          return
  
      if not np.iterable(wh):
          wh = golden_wh(wh)
  
      all_figures = get_all_figures()
      all_qt4wins = get_all_qt4_wins()
  
      if override1:
          if len(all_figures) == 1:
              fig = all_figures[0]
              win = fig.canvas.manager.window
              win.setGeometry(0, 0, 900, 900)
              update()
              return
  
      #nFigs = len(all_figures) + len(all_qt4_wins)
  
      num_rows, num_cols = num_rc
  
      w, h = wh
      x_off, y_off = xy_off
      w_off, h_off = wh_off
      x_pad, y_pad = (0, 0)
      printDBG('[df2] Tile all figures: ')
      printDBG('[df2]     wh = %r' % ((w, h),))
      printDBG('[df2]     xy_offsets = %r' % ((x_off, y_off),))
      printDBG('[df2]     wh_offsets = %r' % ((w_off, h_off),))
      printDBG('[df2]     xy_pads = %r' % ((x_pad, y_pad),))
      if sys.platform == 'win32':
          h_off +=   0
          w_off +=  40
          x_off +=  40
          y_off +=  40
          x_pad +=   0
          y_pad += 100
  
      def position_window(i, win):
          isqt4_mpl = isinstance(win, backend_qt4.MainWindow)
          isqt4_back = isinstance(win, QtGui.QMainWindow)
          if not isqt4_mpl and not isqt4_back:
              raise NotImplementedError('%r-th Backend %r is not a Qt Window' % (i, win))
          if row_first:
              y = (i % num_rows) * (h + h_off) + 40
              x = (int(i / num_rows)) * (w + w_off) + x_pad
          else:
              x = (i % num_cols) * (w + w_off) + 40
              y = (int(i / num_cols)) * (h + h_off) + y_pad
          x += x_off
          y += y_off
          win.setGeometry(x, y, w, h)
      ioff = 0
      for i, win in enumerate(all_qt4wins):
          position_window(i, win)
          ioff += 1
      for i, fig in enumerate(all_figures):
          win = fig.canvas.manager.window
          position_window(i + ioff, win)
  
  
  def all_figures_bring_to_front():
      all_figures = get_all_figures()
      for fig in iter(all_figures):
          bring_to_front(fig)
  
  
  def close_all_figures():
      all_figures = get_all_figures()
      for fig in iter(all_figures):
          close_figure(fig)
  
  
  def close_figure(fig):
      fig.clf()
      fig.df2_closed = True
      qtwin = fig.canvas.manager.window
      qtwin.close()
  
  
  def bring_to_front(fig):
      #what is difference between show and show normal?
      qtwin = fig.canvas.manager.window
      qtwin.raise_()
      qtwin.activateWindow()
      qtwin.setWindowFlags(Qt.WindowStaysOnTopHint)
      qtwin.setWindowFlags(Qt.WindowFlags(0))
      qtwin.show()
  
  
  def show():
      all_figures_show()
      all_figures_bring_to_front()
      plt.show()
  
  
  def reset():
      close_all_figures()
  
  
  def draw():
      all_figures_show()
  
  
  def update():
      draw()
      all_figures_bring_to_front()
  
  
  def present(*args, **kwargs):
      'execing present should cause IPython magic'
      print('[df2] Presenting figures...')
      with warnings.catch_warnings():
          warnings.simplefilter("ignore")
          all_figures_tile(*args, **kwargs)
          all_figures_show()
          all_figures_bring_to_front()
      # Return an exec string
      execstr = helpers.ipython_execstr()
      execstr += textwrap.dedent('''
      if not embedded:
          print('[df2] Presenting in normal shell.')
          print('[df2] ... plt.show()')
          plt.show()
      ''')
      return execstr
  
  
  def save_figure(fnum=None, fpath=None, usetitle=False, overwrite=True):
      #import warnings
      #warnings.simplefilter("error")
      # Find the figure
      if fnum is None:
          fig = gcf()
      else:
          fig = plt.figure(fnum, figsize=FIGSIZE, dpi=DPI)
      # Enforce inches and DPI
      fig.set_size_inches(FIGSIZE[0], FIGSIZE[1])
      fnum = fig.number
      if fpath is None:
          # Find the title
          fpath = sanatize_img_fname(fig.canvas.get_window_title())
      if usetitle:
          title = sanatize_img_fname(fig.canvas.get_window_title())
          fpath = join(fpath, title)
      # Add in DPI information
      fpath_noext, ext = splitext(fpath)
      size_suffix = '_DPI=%r_FIGSIZE=%d,%d' % (DPI, FIGSIZE[0], FIGSIZE[1])
      fpath = fpath_noext + size_suffix + ext
      # Sanatize the filename
      fpath_clean = sanatize_img_fpath(fpath)
      #fname_clean = split(fpath_clean)[1]
      print('[df2] save_figure() %r' % (fpath_clean,))
      #adjust_subplots()
      with warnings.catch_warnings():
          warnings.filterwarnings('ignore', category=DeprecationWarning)
          if not exists(fpath_clean) or overwrite:
              fig.savefig(fpath_clean, dpi=DPI)
  
  
  def set_ticks(xticks, yticks):
      ax = gca()
      ax.set_xticks(xticks)
      ax.set_yticks(yticks)
  
  
  def set_xticks(tick_set):
      ax = gca()
      ax.set_xticks(tick_set)
  
  
  def set_yticks(tick_set):
      ax = gca()
      ax.set_yticks(tick_set)
  
  
  def set_xlabel(lbl, ax=None):
      if ax is None:
          ax = gca()
      ax.set_xlabel(lbl, fontproperties=FONTS.xlabel)
  
  
  def set_title(title, ax=None):
      if ax is None:
          ax = gca()
      ax.set_title(title, fontproperties=FONTS.axtitle)
  
  
  def set_ylabel(lbl):
      ax = gca()
      ax.set_ylabel(lbl, fontproperties=FONTS.xlabel)
  
  
  def plot(*args, **kwargs):
      return plt.plot(*args, **kwargs)
  
  
  def plot2(x_data, y_data, marker='o', title_pref='', x_label='x', y_label='y', *args,
            **kwargs):
      do_plot = True
      ax = gca()
      if len(x_data) != len(y_data):
          warnstr = '[df2] ! Warning:  len(x_data) != len(y_data). Cannot plot2'
          warnings.warn(warnstr)
          draw_text(warnstr)
          do_plot = False
      if len(x_data) == 0:
          warnstr = '[df2] ! Warning:  len(x_data) == 0. Cannot plot2'
          warnings.warn(warnstr)
          draw_text(warnstr)
          do_plot = False
      if do_plot:
          ax.plot(x_data, y_data, marker, *args, **kwargs)
  
      min_ = min(x_data.min(), y_data.min())
      max_ = max(x_data.max(), y_data.max())
      # Equal aspect ratio
      ax.set_xlim(min_, max_)
      ax.set_ylim(min_, max_)
      ax.set_aspect('equal')
      ax.set_xlabel(x_label, fontproperties=FONTS.xlabel)
      ax.set_ylabel(y_label, fontproperties=FONTS.xlabel)
      ax.set_title(title_pref + ' ' + x_label + ' vs ' + y_label,
                   fontproperties=FONTS.axtitle)
  
  
  def adjust_subplots_xlabels():
      adjust_subplots(left=.03, right=.97, bottom=.2, top=.9, hspace=.15)
  
  
  def adjust_subplots_xylabels():
      adjust_subplots(left=.03, right=1, bottom=.1, top=.9, hspace=.15)
  
  
  def adjust_subplots_safe(left=.1, right=.9, bottom=.1, top=.9, wspace=.3, hspace=.5):
      adjust_subplots(left, bottom, right, top, wspace, hspace)
  
  
  def adjust_subplots(left=0.02,  bottom=0.02,
                      right=0.98,     top=0.90,
                      wspace=0.1,   hspace=0.15):
      '''
      left  = 0.125  # the left side of the subplots of the figure
      right = 0.9    # the right side of the subplots of the figure
      bottom = 0.1   # the bottom of the subplots of the figure
      top = 0.9      # the top of the subplots of the figure
      wspace = 0.2   # the amount of width reserved for blank space between subplots
      hspace = 0.2
      '''
      #print('[df2] adjust_subplots(%r)' % locals())
      plt.subplots_adjust(left, bottom, right, top, wspace, hspace)
  
  
  #=======================
  # TEXT FUNCTIONS
  # TODO: I have too many of these. Need to consolidate
  #=======================
  
  
  def upperleft_text(txt):
      txtargs = dict(horizontalalignment='left',
                     verticalalignment='top',
                     #fontsize='smaller',
                     #fontweight='ultralight',
                     backgroundcolor=(0, 0, 0, .5),
                     color=ORANGE)
      ax_relative_text(.02, .02, txt, **txtargs)
  
  
  def upperright_text(txt, offset=None):
      txtargs = dict(horizontalalignment='right',
                     verticalalignment='top',
                     #fontsize='smaller',
                     #fontweight='ultralight',
                     backgroundcolor=(0, 0, 0, .5),
                     color=ORANGE,
                     offset=offset)
      ax_relative_text(.98, .02, txt, **txtargs)
  
  
  def lowerright_text(txt):
      txtargs = dict(horizontalalignment='right',
                     verticalalignment='top',
                     #fontsize='smaller',
                     #fontweight='ultralight',
                     backgroundcolor=(0, 0, 0, .5),
                     color=ORANGE)
      ax_relative_text(.98, .92, txt, **txtargs)
  
  
  def absolute_lbl(x_, y_, txt, roffset=(-.02, -.02), **kwargs):
      txtargs = dict(horizontalalignment='right',
                     verticalalignment='top',
                     backgroundcolor=(0, 0, 0, .5),
                     color=ORANGE,
                     **kwargs)
      ax_absolute_text(x_, y_, txt, roffset=roffset, **txtargs)
  
  
  def ax_relative_text(x, y, txt, ax=None, offset=None, **kwargs):
      if ax is None:
          ax = gca()
      xy, width, height = _axis_xy_width_height(ax)
      x_, y_ = ((xy[0]) + x * width, (xy[1] + height) - y * height)
      if offset is not None:
          xoff, yoff = offset
          x_ += xoff
          y_ += yoff
      ax_absolute_text(x_, y_, txt, ax=ax, **kwargs)
  
  
  def ax_absolute_text(x_, y_, txt, ax=None, roffset=None, **kwargs):
      if ax is None:
          ax = gca()
      if 'fontproperties' in kwargs:
          kwargs['fontproperties'] = FONTS.relative
      if roffset is not None:
          xroff, yroff = roffset
          xy, width, height = _axis_xy_width_height(ax)
          x_ += xroff * width
          y_ += yroff * height
  
      ax.text(x_, y_, txt, **kwargs)
  
  
  def fig_relative_text(x, y, txt, **kwargs):
      kwargs['horizontalalignment'] = 'center'
      kwargs['verticalalignment'] = 'center'
      fig = gcf()
      #xy, width, height = _axis_xy_width_height(ax)
      #x_, y_ = ((xy[0]+width)+x*width, (xy[1]+height)-y*height)
      fig.text(x, y, txt, **kwargs)
  
  
  def draw_text(text_str, rgb_textFG=(0, 0, 0), rgb_textBG=(1, 1, 1)):
      ax = gca()
      xy, width, height = _axis_xy_width_height(ax)
      text_x = xy[0] + (width / 2)
      text_y = xy[1] + (height / 2)
      ax.text(text_x, text_y, text_str,
              horizontalalignment='center',
              verticalalignment='center',
              color=rgb_textFG,
              backgroundcolor=rgb_textBG)
  
  
  def set_figtitle(figtitle, subtitle='', forcefignum=True, incanvas=True):
      if figtitle is None:
          figtitle = ''
      fig = gcf()
      if incanvas:
          if subtitle != '':
              subtitle = '\n' + subtitle
          fig.suptitle(figtitle + subtitle, fontsize=14, fontweight='bold')
          #fig.suptitle(figtitle, x=.5, y=.98, fontproperties=FONTS.figtitle)
          #fig_relative_text(.5, .96, subtitle, fontproperties=FONTS.subtitle)
      else:
          fig.suptitle('')
      window_figtitle = ('fig(%d) ' % fig.number) + figtitle
      fig.canvas.set_window_title(window_figtitle)
  
  
  def convert_keypress_event_mpl_to_qt4(mevent):
      global TMP_mevent
      TMP_mevent = mevent
      # Grab the key from the mpl.KeyPressEvent
      key = mevent.key
      print('[df2] convert event mpl -> qt4')
      print('[df2] key=%r' % key)
      # dicts modified from backend_qt4.py
      mpl2qtkey = {'control': Qt.Key_Control, 'shift': Qt.Key_Shift,
                   'alt': Qt.Key_Alt, 'super': Qt.Key_Meta,
                   'enter': Qt.Key_Return, 'left': Qt.Key_Left, 'up': Qt.Key_Up,
                   'right': Qt.Key_Right, 'down': Qt.Key_Down,
                   'escape': Qt.Key_Escape, 'f1': Qt.Key_F1, 'f2': Qt.Key_F2,
                   'f3': Qt.Key_F3, 'f4': Qt.Key_F4, 'f5': Qt.Key_F5,
                   'f6': Qt.Key_F6, 'f7': Qt.Key_F7, 'f8': Qt.Key_F8,
                   'f9': Qt.Key_F9, 'f10': Qt.Key_F10, 'f11': Qt.Key_F11,
                   'f12': Qt.Key_F12, 'home': Qt.Key_Home, 'end': Qt.Key_End,
                   'pageup': Qt.Key_PageUp, 'pagedown': Qt.Key_PageDown}
      # Reverse the control and super (aka cmd/apple) keys on OSX
      if sys.platform == 'darwin':
          mpl2qtkey.update({'super': Qt.Key_Control, 'control': Qt.Key_Meta, })
  
      # Try to reconstruct QtGui.KeyEvent
      type_ = QtCore.QEvent.Type(QtCore.QEvent.KeyPress)  # The type should always be KeyPress
      text = ''
      # Try to extract the original modifiers
      modifiers = QtCore.Qt.NoModifier  # initialize to no modifiers
      if key.find(u'ctrl+') >= 0:
          modifiers = modifiers | QtCore.Qt.ControlModifier
          key = key.replace(u'ctrl+', u'')
          print('[df2] has ctrl modifier')
          text += 'Ctrl+'
      if key.find(u'alt+') >= 0:
          modifiers = modifiers | QtCore.Qt.AltModifier
          key = key.replace(u'alt+', u'')
          print('[df2] has alt modifier')
          text += 'Alt+'
      if key.find(u'super+') >= 0:
          modifiers = modifiers | QtCore.Qt.MetaModifier
          key = key.replace(u'super+', u'')
          print('[df2] has super modifier')
          text += 'Super+'
      if key.isupper():
          modifiers = modifiers | QtCore.Qt.ShiftModifier
          print('[df2] has shift modifier')
          text += 'Shift+'
      # Try to extract the original key
      try:
          if key in mpl2qtkey:
              key_ = mpl2qtkey[key]
          else:
              key_ = ord(key.upper())  # Qt works with uppercase keys
              text += key.upper()
      except Exception as ex:
          print('[df2] ERROR key=%r' % key)
          print('[df2] ERROR %r' % ex)
          raise
      autorep = False  # default false
      count   = 1  # default 1
      text = QtCore.QString(text)  # The text is somewhat arbitrary
      # Create the QEvent
      print('----------------')
      print('[df2] Create event')
      print('[df2] type_ = %r' % type_)
      print('[df2] text = %r' % text)
      print('[df2] modifiers = %r' % modifiers)
      print('[df2] autorep = %r' % autorep)
      print('[df2] count = %r ' % count)
      print('----------------')
      qevent = QtGui.QKeyEvent(type_, key_, modifiers, text, autorep, count)
      return qevent
  
  
  def test_build_qkeyevent():
      import draw_func2 as df2
      qtwin = df2.QT4_WINS[0]
      # This reconstructs an test mplevent
      canvas = df2.figure(1).canvas
      mevent = matplotlib.backend_bases.KeyEvent('key_press_event', canvas, u'ctrl+p', x=672, y=230.0)
      qevent = df2.convert_keypress_event_mpl_to_qt4(mevent)
      app = qtwin.backend.app
      app.sendEvent(qtwin.ui, mevent)
      #type_ = QtCore.QEvent.Type(QtCore.QEvent.KeyPress)  # The type should always be KeyPress
      #text = QtCore.QString('A')  # The text is somewhat arbitrary
      #modifiers = QtCore.Qt.NoModifier  # initialize to no modifiers
      #modifiers = modifiers | QtCore.Qt.ControlModifier
      #modifiers = modifiers | QtCore.Qt.AltModifier
      #key_ = ord('A')  # Qt works with uppercase keys
      #autorep = False  # default false
      #count   = 1  # default 1
      #qevent = QtGui.QKeyEvent(type_, key_, modifiers, text, autorep, count)
      return qevent
  
  
  # This actually doesn't matter
  def on_key_press_event(event):
      'redirects keypress events to main window'
      global QT4_WINS
      print('[df2] %r' % event)
      print('[df2] %r' % str(event.__dict__))
      for qtwin in QT4_WINS:
          qevent = convert_keypress_event_mpl_to_qt4(event)
          app = qtwin.backend.app
          print('[df2] attempting to send qevent to qtwin')
          app.sendEvent(qtwin, qevent)
          # TODO: FINISH ME
          #PyQt4.QtGui.QKeyEvent
          #qtwin.keyPressEvent(event)
          #fig.canvas.manager.window.keyPressEvent()
  
  
  def customize_figure(fig, docla):
      if not 'user_stat_list' in fig.__dict__.keys() or docla:
          fig.user_stat_list = []
          fig.user_notes = []
      # We dont need to catch keypress events because you just need to set it as
      # an application level shortcut
      # Catch key press events
      #key_event_cbid = fig.__dict__.get('key_event_cbid', None)
      #if key_event_cbid is not None:
          #fig.canvas.mpl_disconnect(key_event_cbid)
      #fig.key_event_cbid = fig.canvas.mpl_connect('key_press_event', on_key_press_event)
      fig.df2_closed = False
  
  
  def gcf():
      if plotWidget is not None:
          #print('is plotwidget visible = %r' % plotWidget.isVisible())
          fig = plotWidget.figure
          return fig
      return plt.gcf()
  
  
  def gca():
      if plotWidget is not None:
          #print('is plotwidget visible = %r' % plotWidget.isVisible())
          axes_list = plotWidget.figure.get_axes()
          current = 0
          ax = axes_list[current]
          return ax
      return plt.gca()
  
  
  def cla():
      return plt.cla()
  
  
  def clf():
      return plt.clf()
  
  
  def get_fig(fnum=None):
      printDBG('[df2] get_fig(fnum=%r)' % fnum)
      fig_kwargs = dict(figsize=FIGSIZE, dpi=DPI)
      if plotWidget is not None:
          return gcf()
      if fnum is None:
          try:
              fig = gcf()
          except Exception as ex:
              printDBG('[df2] get_fig(): ex=%r' % ex)
              fig = plt.figure(**fig_kwargs)
          fnum = fig.number
      else:
          try:
              fig = plt.figure(fnum, **fig_kwargs)
          except Exception as ex:
              print(repr(ex))
              warnings.warn(repr(ex))
              fig = gcf()
      return fig
  
  
  def get_ax(fnum=None, pnum=None):
      figure(fnum=fnum, pnum=pnum)
      ax = gca()
      return ax
  
  
  def figure(fnum=None, docla=False, title=None, pnum=(1, 1, 1), figtitle=None,
             doclf=False, **kwargs):
      '''
      fnum = fignum = figure number
      pnum = plotnum = plot tuple
      '''
      #matplotlib.pyplot.xkcd()
      fig = get_fig(fnum)
      axes_list = fig.get_axes()
      # Ensure my customized settings
      customize_figure(fig, docla)
      # Convert pnum to tuple format
      if tools.is_int(pnum):
          nr = pnum // 100
          nc = pnum // 10 - (nr * 10)
          px = pnum - (nr * 100) - (nc * 10)
          pnum = (nr, nc, px)
      if doclf:  # a bit hacky. Need to rectify docla and doclf
          fig.clf()
      # Get the subplot
      if docla or len(axes_list) == 0:
          printDBG('[df2] *** NEW FIGURE %r.%r ***' % (fnum, pnum))
          if not pnum is None:
              #ax = plt.subplot(*pnum)
              ax = fig.add_subplot(*pnum)
              ax.cla()
          else:
              ax = gca()
      else:
          printDBG('[df2] *** OLD FIGURE %r.%r ***' % (fnum, pnum))
          if not pnum is None:
              ax = plt.subplot(*pnum)  # fig.add_subplot fails here
              #ax = fig.add_subplot(*pnum)
          else:
              ax = gca()
          #ax  = axes_list[0]
      # Set the title
      if not title is None:
          ax = gca()
          ax.set_title(title, fontproperties=FONTS.axtitle)
          # Add title to figure
          if figtitle is None and pnum == (1, 1, 1):
              figtitle = title
          if not figtitle is None:
              set_figtitle(figtitle, incanvas=False)
      return fig
  
  
  def plot_pdf(data, draw_support=True, scale_to=None, label=None, color=0,
               nYTicks=3):
      fig = gcf()
      ax = gca()
      data = np.array(data)
      if len(data) == 0:
          warnstr = '[df2] ! Warning: len(data) = 0. Cannot visualize pdf'
          warnings.warn(warnstr)
          draw_text(warnstr)
          return
      bw_factor = .05
      if isinstance(color, (int, float)):
          colorx = color
          line_color = plt.get_cmap('gist_rainbow')(colorx)
      else:
          line_color = color
  
      # Estimate a pdf
      data_pdf = estimate_pdf(data, bw_factor)
      # Get probability of seen data
      prob_x = data_pdf(data)
      # Get probability of unseen data data
      x_data = np.linspace(0, data.max(), 500)
      y_data = data_pdf(x_data)
      # Scale if requested
      if not scale_to is None:
          scale_factor = scale_to / y_data.max()
          y_data *= scale_factor
          prob_x *= scale_factor
      #Plot the actual datas on near the bottom perterbed in Y
      if draw_support:
          pdfrange = prob_x.max() - prob_x.min()
          perb   = (np.random.randn(len(data))) * pdfrange / 30.
          preb_y_data = np.abs([pdfrange / 50. for _ in data] + perb)
          ax.plot(data, preb_y_data, 'o', color=line_color, figure=fig, alpha=.1)
      # Plot the pdf (unseen data)
      ax.plot(x_data, y_data, color=line_color, label=label)
      if nYTicks is not None:
          yticks = np.linspace(min(y_data), max(y_data), nYTicks)
          ax.set_yticks(yticks)
  
  
  def estimate_pdf(data, bw_factor):
      try:
          data_pdf = scipy.stats.gaussian_kde(data, bw_factor)
          data_pdf.covariance_factor = bw_factor
      except Exception as ex:
          print('[df2] ! Exception while estimating kernel density')
          print('[df2] data=%r' % (data,))
          print('[df2] ex=%r' % (ex,))
          raise
      return data_pdf
  
  
  def show_histogram(data, bins=None, **kwargs):
      print('[df2] show_histogram()')
      dmin = int(np.floor(data.min()))
      dmax = int(np.ceil(data.max()))
      if bins is None:
          bins = dmax - dmin
      fig = figure(**kwargs)
      ax  = gca()
      ax.hist(data, bins=bins, range=(dmin, dmax))
      #help(np.bincount)
      fig.show()
  
  
  def show_signature(sig, **kwargs):
      fig = figure(**kwargs)
      plt.plot(sig)
      fig.show()
  
  
  def plot_stems(x_data=None, y_data=None):
      if y_data is not None and x_data is None:
          x_data = np.arange(len(y_data))
          pass
      if len(x_data) != len(y_data):
          print('[df2] WARNING plot_stems(): len(x_data)!=len(y_data)')
      if len(x_data) == 0:
          print('[df2] WARNING plot_stems(): len(x_data)=len(y_data)=0')
      x_data_ = np.array(x_data)
      y_data_ = np.array(y_data)
      x_data_sort = x_data_[y_data_.argsort()[::-1]]
      y_data_sort = y_data_[y_data_.argsort()[::-1]]
  
      markerline, stemlines, baseline = pylab.stem(x_data_sort, y_data_sort, linefmt='-')
      pylab.setp(markerline, 'markerfacecolor', 'b')
      pylab.setp(baseline, 'linewidth', 0)
      ax = gca()
      ax.set_xlim(min(x_data) - 1, max(x_data) + 1)
      ax.set_ylim(min(y_data) - 1, max(max(y_data), max(x_data)) + 1)
  
  
  def plot_sift_signature(sift, title='', fnum=None, pnum=None):
      figure(fnum=fnum, pnum=pnum)
      ax = gca()
      plot_bars(sift, 16)
      ax.set_xlim(0, 128)
      ax.set_ylim(0, 256)
      space_xticks(9, 16)
      space_yticks(5, 64)
      ax.set_title(title)
      dark_background(ax)
      return ax
  
  
  def dark_background(ax=None, doubleit=False):
      if ax is None:
          ax = gca()
      xy, width, height = _axis_xy_width_height(ax)
      if doubleit:
          halfw = (doubleit) * (width / 2)
          halfh = (doubleit) * (height / 2)
          xy = (xy[0] - halfw, xy[1] - halfh)
          width *= (doubleit + 1)
          height *= (doubleit + 1)
      rect = matplotlib.patches.Rectangle(xy, width, height, lw=0, zorder=0)
      rect.set_clip_on(True)
      rect.set_fill(True)
      rect.set_color(BLACK * .9)
      rect = ax.add_patch(rect)
  
  
  def space_xticks(nTicks=9, spacing=16, ax=None):
      if ax is None:
          ax = gca()
      ax.set_xticks(np.arange(nTicks) * spacing)
      small_xticks(ax)
  
  
  def space_yticks(nTicks=9, spacing=32, ax=None):
      if ax is None:
          ax = gca()
      ax.set_yticks(np.arange(nTicks) * spacing)
      small_yticks(ax)
  
  
  def small_xticks(ax=None):
      for tick in ax.xaxis.get_major_ticks():
          tick.label.set_fontsize(8)
  
  
  def small_yticks(ax=None):
      for tick in ax.yaxis.get_major_ticks():
          tick.label.set_fontsize(8)
  
  
  def plot_bars(y_data, nColorSplits=1):
      width = 1
      nDims = len(y_data)
      nGroup = nDims // nColorSplits
      ori_colors = distinct_colors(nColorSplits)
      x_data = np.arange(nDims)
      ax = gca()
      for ix in xrange(nColorSplits):
          xs = np.arange(nGroup) + (nGroup * ix)
          color = ori_colors[ix]
          x_dat = x_data[xs]
          y_dat = y_data[xs]
          ax.bar(x_dat, y_dat, width, color=color, edgecolor=np.array(color) * .8)
  
  
  def phantom_legend_label(label, color, loc='upper right'):
      'adds a legend label without displaying an actor'
      pass
      #phantom_actor = plt.Circle((0, 0), 1, fc=color, prop=FONTS.legend, loc=loc)
      #plt.legend(phant_actor, label, framealpha=.2)
      #plt.legend(*zip(*legend_tups), framealpha=.2)
      #legend_tups = []
      #legend_tups.append((phantom_actor, label))
  
  
  def legend(loc='upper right'):
      ax = gca()
      ax.legend(prop=FONTS.legend, loc=loc)
  
  
  def plot_histpdf(data, label=None, draw_support=False, nbins=10):
      freq, _ = plot_hist(data, nbins=nbins)
      plot_pdf(data, draw_support=draw_support, scale_to=freq.max(), label=label)
  
  
  def plot_hist(data, bins=None, nbins=10, weights=None):
      if isinstance(data, list):
          data = np.array(data)
      if bins is None:
          dmin = data.min()
          dmax = data.max()
          bins = dmax - dmin
      ax  = gca()
      freq, bins_, patches = ax.hist(data, bins=nbins, weights=weights, range=(dmin, dmax))
      return freq, bins_
  
  
  def variation_trunctate(data):
      ax = gca()
      data = np.array(data)
      if len(data) == 0:
          warnstr = '[df2] ! Warning: len(data) = 0. Cannot variation_truncate'
          warnings.warn(warnstr)
          return
      trunc_max = data.mean() + data.std() * 2
      trunc_min = np.floor(data.min())
      ax.set_xlim(trunc_min, trunc_max)
      #trunc_xticks = np.linspace(0, int(trunc_max),11)
      #trunc_xticks = trunc_xticks[trunc_xticks >= trunc_min]
      #trunc_xticks = np.append([int(trunc_min)], trunc_xticks)
      #no_zero_yticks = ax.get_yticks()[ax.get_yticks() > 0]
      #ax.set_xticks(trunc_xticks)
      #ax.set_yticks(no_zero_yticks)
  #_----------------- HELPERS ^^^ ---------
  
  
  # ---- IMAGE CREATION FUNCTIONS ----
  @tools.debug_exception
  def draw_sift(desc, kp=None):
      # TODO: There might be a divide by zero warning in here.
      ''' desc = np.random.rand(128)
          desc = desc / np.sqrt((desc**2).sum())
          desc = np.round(desc * 255) '''
      # This is draw, because it is an overlay
      ax = gca()
      tau = 2 * np.pi
      DSCALE = .25
      XYSCALE = .5
      XYSHIFT = -.75
      ORI_SHIFT = 0  # -tau #1/8 * tau
      # SIFT CONSTANTS
      NORIENTS = 8
      NX = 4
      NY = 4
      NBINS = NX * NY
  
      def cirlce_rad2xy(radians, mag):
          return np.cos(radians) * mag, np.sin(radians) * mag
      discrete_ori = (np.arange(0, NORIENTS) * (tau / NORIENTS) + ORI_SHIFT)
      # Build list of plot positions
      # Build an "arm" for each sift measurement
      arm_mag   = desc / 255.0
      arm_ori = np.tile(discrete_ori, (NBINS, 1)).flatten()
      # The offset x,y's for each sift measurment
      arm_dxy = np.array(zip(*cirlce_rad2xy(arm_ori, arm_mag)))
      yxt_gen = itertools.product(xrange(NY), xrange(NX), xrange(NORIENTS))
      yx_gen  = itertools.product(xrange(NY), xrange(NX))
      # Transform the drawing of the SIFT descriptor to the its elliptical patch
      axTrans = ax.transData
      kpTrans = None
      if kp is None:
          kp = [0, 0, 1, 0, 1]
      kp = np.array(kp)
      kpT = kp.T
      x, y, a, c, d = kpT[:, 0]
      kpTrans = Affine2D([( a, 0, x),
                          ( c, d, y),
                          ( 0, 0, 1)])
      axTrans = ax.transData
      # Draw 8 directional arms in each of the 4x4 grid cells
      arrow_patches = []
      arrow_patches2 = []
      for y, x, t in yxt_gen:
          index = y * NX * NORIENTS + x * NORIENTS + t
          (dx, dy) = arm_dxy[index]
          arw_x  = x * XYSCALE + XYSHIFT
          arw_y  = y * XYSCALE + XYSHIFT
          arw_dy = dy * DSCALE * 1.5  # scale for viz Hack
          arw_dx = dx * DSCALE * 1.5
          #posA = (arw_x, arw_y)
          #posB = (arw_x+arw_dx, arw_y+arw_dy)
          _args = [arw_x, arw_y, arw_dx, arw_dy]
          _kwargs = dict(head_width=.0001, transform=kpTrans, length_includes_head=False)
          arrow_patches  += [FancyArrow(*_args, **_kwargs)]
          arrow_patches2 += [FancyArrow(*_args, **_kwargs)]
      # Draw circles around each of the 4x4 grid cells
      circle_patches = []
      for y, x in yx_gen:
          circ_xy = (x * XYSCALE + XYSHIFT, y * XYSCALE + XYSHIFT)
          circ_radius = DSCALE
          circle_patches += [Circle(circ_xy, circ_radius, transform=kpTrans)]
      # Efficiently draw many patches with PatchCollections
      circ_collection = PatchCollection(circle_patches)
      circ_collection.set_facecolor('none')
      circ_collection.set_transform(axTrans)
      circ_collection.set_edgecolor(BLACK)
      circ_collection.set_alpha(.5)
      # Body of arrows
      arw_collection = PatchCollection(arrow_patches)
      arw_collection.set_transform(axTrans)
      arw_collection.set_linewidth(.5)
      arw_collection.set_color(RED)
      arw_collection.set_alpha(1)
      # Border of arrows
      arw_collection2 = matplotlib.collections.PatchCollection(arrow_patches2)
      arw_collection2.set_transform(axTrans)
      arw_collection2.set_linewidth(1)
      arw_collection2.set_color(BLACK)
      arw_collection2.set_alpha(1)
      # Add artists to axes
      ax.add_collection(circ_collection)
      ax.add_collection(arw_collection2)
      ax.add_collection(arw_collection)
  
  
  def feat_scores_to_color(fs, cmap_='hot'):
      assert len(fs.shape) == 1, 'score must be 1d'
      cmap = plt.get_cmap(cmap_)
      mins = fs.min()
      rnge = fs.max() - mins
      if rnge == 0:
          return [cmap(.5) for fx in xrange(len(fs))]
      score2_01 = lambda score: .1 + .9 * (float(score) - mins) / (rnge)
      colors    = [cmap(score2_01(score)) for score in fs]
      return colors
  
  
  def colorbar(scalars, colors):
      'adds a color bar next to the axes'
      orientation = ['vertical', 'horizontal'][0]
      TICK_FONTSIZE = 8
      # Put colors and scalars in correct order
      sorted_scalars = sorted(scalars)
      sorted_colors = [x for (y, x) in sorted(zip(scalars, colors))]
      # Make a listed colormap and mappable object
      listed_cmap = mpl.colors.ListedColormap(sorted_colors)
      sm = plt.cm.ScalarMappable(cmap=listed_cmap)
      sm.set_array(sorted_scalars)
      # Use mapable object to create the colorbar
      cb = plt.colorbar(sm, orientation=orientation)
      # Add the colorbar to the correct label
      axis = cb.ax.xaxis if orientation == 'horizontal' else cb.ax.yaxis
      position = 'bottom' if orientation == 'horizontal' else 'right'
      axis.set_ticks_position(position)
      axis.set_ticks([0, .5, 1])
      cb.ax.tick_params(labelsize=TICK_FONTSIZE)
  
  
  def draw_lines2(kpts1, kpts2, fm=None, fs=None, kpts2_offset=(0, 0),
                  color_list=None, **kwargs):
      if not DISTINCT_COLORS:
          color_list = None
      # input data
      if not SHOW_LINES:
          return
      if fm is None:  # assume kpts are in director correspondence
          assert kpts1.shape == kpts2.shape
      if len(fm) == 0:
          return
      ax = gca()
      woff, hoff = kpts2_offset
      # Draw line collection
      kpts1_m = kpts1[fm[:, 0]].T
      kpts2_m = kpts2[fm[:, 1]].T
      xxyy_iter = iter(zip(kpts1_m[0],
                           kpts2_m[0] + woff,
                           kpts1_m[1],
                           kpts2_m[1] + hoff))
      if color_list is None:
          if fs is None:  # Draw with solid color
              color_list    = [ LINE_COLOR for fx in xrange(len(fm))]
          else:  # Draw with colors proportional to score difference
              color_list = feat_scores_to_color(fs)
      segments  = [((x1, y1), (x2, y2)) for (x1, x2, y1, y2) in xxyy_iter]
      linewidth = [LINE_WIDTH for fx in xrange(len(fm))]
      line_alpha = LINE_ALPHA
      if LINE_ALPHA_OVERRIDE is not None:
          line_alpha = LINE_ALPHA_OVERRIDE
      line_group = LineCollection(segments, linewidth, color_list, alpha=line_alpha)
      #plt.colorbar(line_group, ax=ax)
      ax.add_collection(line_group)
      #figure(100)
      #plt.hexbin(x,y, cmap=plt.cm.YlOrRd_r)
  
  
  def draw_kpts(kpts, *args, **kwargs):
      draw_kpts2(kpts, *args, **kwargs)
  
  
  def draw_kpts2(kpts, offset=(0, 0), ell=SHOW_ELLS, pts=False, pts_color=ORANGE,
                 pts_size=POINT_SIZE, ell_alpha=ELL_ALPHA,
                 ell_linewidth=ELL_LINEWIDTH, ell_color=ELL_COLOR,
                 color_list=None, rect=None, arrow=False, **kwargs):
      if not DISTINCT_COLORS:
          color_list = None
      printDBG('drawkpts2: Drawing Keypoints! ell=%r pts=%r' % (ell, pts))
      # get matplotlib info
      ax = gca()
      pltTrans = ax.transData
      ell_actors = []
      # data
      kpts = np.array(kpts)
      kptsT = kpts.T
      x = kptsT[0, :] + offset[0]
      y = kptsT[1, :] + offset[1]
      printDBG('[df2] draw_kpts()----------')
      printDBG('[df2] draw_kpts() ell=%r pts=%r' % (ell, pts))
      printDBG('[df2] draw_kpts() drawing kpts.shape=%r' % (kpts.shape,))
      if rect is None:
          rect = ell
          rect = False
          if pts is True:
              rect = False
      if ell or rect:
          printDBG('[df2] draw_kpts() drawing ell kptsT.shape=%r' % (kptsT.shape,))
          # We have the transformation from unit circle to ellipse here. (inv(A))
          a = kptsT[2]
          b = np.zeros(len(a))
          c = kptsT[3]
          d = kptsT[4]
  
          kpts_iter = izip(x, y, a, b, c, d)
          aff_list = [Affine2D([( a_, b_, x_),
                                ( c_, d_, y_),
                                (  0,  0,  1)])
                      for (x_, y_, a_, b_, c_, d_) in kpts_iter]
          patch_list = []
          ell_actors = [Circle( (0, 0), 1, transform=aff) for aff in aff_list]
          if ell:
              patch_list += ell_actors
          if rect:
              rect_actors = [Rectangle( (-1, -1), 2, 2, transform=aff) for aff in aff_list]
              patch_list += rect_actors
          if arrow:
              _kwargs = dict(head_width=.01, length_includes_head=False)
              arrow_actors1 = [FancyArrow(0, 0, 0, 1, transform=aff, **_kwargs) for aff in aff_list]
              arrow_actors2 = [FancyArrow(0, 0, 1, 0, transform=aff, **_kwargs) for aff in aff_list]
              patch_list += arrow_actors1
              patch_list += arrow_actors2
          ellipse_collection = matplotlib.collections.PatchCollection(patch_list)
          ellipse_collection.set_facecolor('none')
          ellipse_collection.set_transform(pltTrans)
          if ELL_ALPHA_OVERRIDE is not None:
              ell_alpha = ELL_ALPHA_OVERRIDE
          ellipse_collection.set_alpha(ell_alpha)
          ellipse_collection.set_linewidth(ell_linewidth)
          if not color_list is None:
              ell_color = color_list
          if ell_color == 'distinct':
              ell_color = distinct_colors(len(kpts))
          ellipse_collection.set_edgecolor(ell_color)
          ax.add_collection(ellipse_collection)
      if pts:
          printDBG('[df2] draw_kpts() drawing pts x.shape=%r y.shape=%r' % (x.shape, y.shape))
          if color_list is None:
              color_list = [pts_color for _ in xrange(len(x))]
          ax.autoscale(enable=False)
          ax.scatter(x, y, c=color_list, s=2 * pts_size, marker='o', edgecolor='none')
          #ax.autoscale(enable=False)
          #ax.plot(x, y, linestyle='None', marker='o', markerfacecolor=pts_color, markersize=pts_size, markeredgewidth=0)
  
  
  # ---- CHIP DISPLAY COMMANDS ----
  def imshow(img, fnum=None, title=None, figtitle=None, pnum=None,
             interpolation='nearest', **kwargs):
      'other interpolations = nearest, bicubic, bilinear'
      #printDBG('[df2] ----- IMSHOW ------ ')
      #printDBG('[***df2.imshow] fnum=%r pnum=%r title=%r *** ' % (fnum, pnum, title))
      #printDBG('[***df2.imshow] img.shape = %r ' % (img.shape,))
      #printDBG('[***df2.imshow] img.stats = %r ' % (helpers.printable_mystats(img),))
      fig = figure(fnum=fnum, pnum=pnum, title=title, figtitle=figtitle, **kwargs)
      ax = gca()
      if not DARKEN is None:
          imgdtype = img.dtype
          img = np.array(img, dtype=float) * DARKEN
          img = np.array(img, dtype=imgdtype)
  
      plt_imshow_kwargs = {
          'interpolation': interpolation,
          #'cmap': plt.get_cmap('gray'),
          'vmin': 0,
          'vmax': 255,
      }
      try:
          if len(img.shape) == 3 and img.shape[2] == 3:
              # img is in a color format
              imgBGR = img
              if imgBGR.dtype == np.float64:
                  if imgBGR.max() <= 1:
                      imgBGR = np.array(imgBGR, dtype=np.float32)
                  else:
                      imgBGR = np.array(imgBGR, dtype=np.uint8)
              imgRGB = cv2.cvtColor(imgBGR, cv2.COLOR_BGR2RGB)
              ax.imshow(imgRGB, **plt_imshow_kwargs)
          elif len(img.shape) == 2:
              # img is in grayscale
              imgGRAY = img
              ax.imshow(imgGRAY, cmap=plt.get_cmap('gray'), **plt_imshow_kwargs)
          else:
              raise Exception('unknown image format')
      except TypeError as te:
          print('[df2] imshow ERROR %r' % te)
          raise
      except Exception as ex:
          print('[df2] img.dtype = %r' % (img.dtype,))
          print('[df2] type(img) = %r' % (type(img),))
          print('[df2] img.shape = %r' % (img.shape,))
          print('[df2] imshow ERROR %r' % ex)
          raise
      #plt.set_cmap('gray')
      ax.set_xticks([])
      ax.set_yticks([])
      #ax.set_autoscale(False)
      #try:
          #if pnum == 111:
              #fig.tight_layout()
      #except Exception as ex:
          #print('[df2] !! Exception durring fig.tight_layout: '+repr(ex))
          #raise
      return fig, ax
  
  
  def get_num_channels(img):
      ndims = len(img.shape)
      if ndims == 2:
          nChannels = 1
      elif ndims == 3 and img.shape[2] == 3:
          nChannels = 3
      elif ndims == 3 and img.shape[2] == 1:
          nChannels = 1
      else:
          raise Exception('Cannot determine number of channels')
      return nChannels
  
  
  def stack_images(img1, img2, vert=None):
      nChannels = get_num_channels(img1)
      nChannels2 = get_num_channels(img2)
      assert nChannels == nChannels2
      (h1, w1) = img1.shape[0: 2]  # get chip dimensions
      (h2, w2) = img2.shape[0: 2]
      woff, hoff = 0, 0
      vert_wh  = max(w1, w2), h1 + h2
      horiz_wh = w1 + w2, max(h1, h2)
      if vert is None:
          # Display the orientation with the better (closer to 1) aspect ratio
          vert_ar  = max(vert_wh) / min(vert_wh)
          horiz_ar = max(horiz_wh) / min(horiz_wh)
          vert = vert_ar < horiz_ar
      if vert:
          wB, hB = vert_wh
          hoff = h1
      else:
          wB, hB = horiz_wh
          woff = w1
      # concatentate images
      if nChannels == 3:
          imgB = np.zeros((hB, wB, 3), np.uint8)
          imgB[0:h1, 0:w1, :] = img1
          imgB[hoff:(hoff + h2), woff:(woff + w2), :] = img2
      elif nChannels == 1:
          imgB = np.zeros((hB, wB), np.uint8)
          imgB[0:h1, 0:w1] = img1
          imgB[hoff:(hoff + h2), woff:(woff + w2)] = img2
      return imgB, woff, hoff
  
  
  def show_chipmatch2(rchip1, rchip2, kpts1, kpts2, fm=None, fs=None, title=None,
                      vert=None, fnum=None, pnum=None, **kwargs):
      '''Draws two chips and the feature matches between them. feature matches
      kpts1 and kpts2 use the (x,y,a,c,d)
      '''
      printDBG('[df2] draw_matches2() fnum=%r, pnum=%r' % (fnum, pnum))
      # get matching keypoints + offset
      (h1, w1) = rchip1.shape[0:2]  # get chip (h, w) dimensions
      (h2, w2) = rchip2.shape[0:2]
      # Stack the compared chips
      match_img, woff, hoff = stack_images(rchip1, rchip2, vert)
      xywh1 = (0, 0, w1, h1)
      xywh2 = (woff, hoff, w2, h2)
      # Show the stacked chips
      fig, ax = imshow(match_img, title=title, fnum=fnum, pnum=pnum)
      # Overlay feature match nnotations
      draw_fmatch(xywh1, xywh2, kpts1, kpts2, fm, fs, **kwargs)
      return ax, xywh1, xywh2
  
  
  # draw feature match
  def draw_fmatch(xywh1, xywh2, kpts1, kpts2, fm, fs=None, lbl1=None, lbl2=None,
                  fnum=None, pnum=None, rect=False, colorbar_=True, **kwargs):
      '''Draws the matching features. This is draw because it is an overlay
      xywh1 - location of rchip1 in the axes
      xywh2 - location or rchip2 in the axes
      '''
      if fm is None:
          assert kpts1.shape == kpts2.shape, 'shapes different or fm not none'
          fm = np.tile(np.arange(0, len(kpts1)), (2, 1)).T
      pts       = kwargs.get('draw_pts', False)
      ell       = kwargs.get('draw_ell', True)
      lines     = kwargs.get('draw_lines', True)
      ell_alpha = kwargs.get('ell_alpha', .4)
      nMatch = len(fm)
      #printDBG('[df2.draw_fnmatch] nMatch=%r' % nMatch)
      x1, y1, w1, h1 = xywh1
      x2, y2, w2, h2 = xywh2
      offset2 = (x2, y2)
      # Custom user label for chips 1 and 2
      if lbl1 is not None:
          absolute_lbl(x1 + w1, y1, lbl1)
      if lbl2 is not None:
          absolute_lbl(x2 + w2, y2, lbl2)
      # Plot the number of matches
      if kwargs.get('show_nMatches', False):
          upperleft_text('#match=%d' % nMatch)
      # Draw all keypoints in both chips as points
      if kwargs.get('all_kpts', False):
          all_args = dict(ell=False, pts=pts, pts_color=GREEN, pts_size=2,
                          ell_alpha=ell_alpha, rect=rect)
          all_args.update(kwargs)
          draw_kpts2(kpts1, **all_args)
          draw_kpts2(kpts2, offset=offset2, **all_args)
      # Draw Lines and Ellipses and Points oh my
      if nMatch > 0:
          colors = [kwargs['colors']] * nMatch if 'colors' in kwargs else distinct_colors(nMatch)
          if fs is not None:
              colors = feat_scores_to_color(fs, 'hot')
  
          acols = add_alpha(colors)
  
          # Helper functions
          def _drawkpts(**_kwargs):
              _kwargs.update(kwargs)
              fxs1 = fm[:, 0]
              fxs2 = fm[:, 1]
              draw_kpts2(kpts1[fxs1], rect=rect, **_kwargs)
              draw_kpts2(kpts2[fxs2], offset=offset2, rect=rect, **_kwargs)
  
          def _drawlines(**_kwargs):
              _kwargs.update(kwargs)
              draw_lines2(kpts1, kpts2, fm, fs, kpts2_offset=offset2, **_kwargs)
  
          # User helpers
          if ell:
              _drawkpts(pts=False, ell=True, color_list=colors)
          if pts:
              _drawkpts(pts_size=8, pts=True, ell=False, pts_color=BLACK)
              _drawkpts(pts_size=6, pts=True, ell=False, color_list=acols)
          if lines:
              _drawlines(color_list=colors)
      else:
          draw_boxedX(xywh2)
      if fs is not None and colorbar_ and 'colors' in vars() and colors is not None:
          colorbar(fs, colors)
      #legend()
      return None
  
  
  def draw_boxedX(xywh, color=RED, lw=2, alpha=.5, theta=0):
      'draws a big red x. redx'
      ax = gca()
      x1, y1, w, h = xywh
      x2, y2 = x1 + w, y1 + h
      segments = [((x1, y1), (x2, y2)),
                  ((x1, y2), (x2, y1))]
      trans = Affine2D()
      trans.rotate(theta)
      trans = trans + ax.transData
      width_list = [lw] * len(segments)
      color_list = [color] * len(segments)
      line_group = LineCollection(segments, width_list, color_list, alpha=alpha,
                                  transOffset=trans)
      ax.add_collection(line_group)
  
  
  def disconnect_callback(fig, callback_type, **kwargs):
      #print('[df2] disconnect %r callback' % callback_type)
      axes = kwargs.get('axes', [])
      for ax in axes:
          ax._hs_viewtype = ''
      cbid_type = callback_type + '_cbid'
      cbfn_type = callback_type + '_func'
      cbid = fig.__dict__.get(cbid_type, None)
      cbfn = fig.__dict__.get(cbfn_type, None)
      if cbid is not None:
          fig.canvas.mpl_disconnect(cbid)
      else:
          cbfn = None
      fig.__dict__[cbid_type] = None
      return cbid, cbfn
  
  
  def connect_callback(fig, callback_type, callback_fn):
      #print('[df2] register %r callback' % callback_type)
      if callback_fn is None:
          return
      cbid_type = callback_type + '_cbid'
      cbfn_type = callback_type + '_func'
      fig.__dict__[cbid_type] = fig.canvas.mpl_connect(callback_type, callback_fn)
      fig.__dict__[cbfn_type] = callback_fn
  