docs: def as_retinotopy(data, output_style='visual', units=Ellipsis, prefix=None, suffix=None):
      '''
      as_retinotopy(data) converts the given data, if possible, into a 2-tuple, (polar_angle, eccen),
        both in degrees, with 0 degrees of polar angle corresponding to the upper vertical meridian
        and negative values corresponding to the left visual hemifield.
      as_retinotopy(data, output_style) yields the given retinotopy data in the given output_style;
        as_retinotopy(data) is equivalent to as_retinotopy(data, 'visual').
  
      This function is intended as a general conversion routine between various sources of retinotopy
      data. All lookups are done in a case insensitive manner. Data may be specified in any of the
      following ways:
        * A cortical mesh containing recognized properties (such as 'polar_angle' and 'eccentricity'
          or 'latitude' and 'longitude'.
        * A dict with recognized fields.
        * A tuple of (polar_angle, eccentricity) (assumed to be in 'visual' style).
        * A numpy vector of complex numbers (assumed in 'complex' style).
        * An n x 2 or 2 x n matrix whose rows/columns are (polar_angle, eccentricity) values (assumed
          in 'visual' style).
  
      The following output_styles are accepted:
        * 'visual':       polar-axis:         upper vertical meridian
                          positive-direction: clockwise
                          fields:             ['polar_angle' (degrees), 'eccentricity' (degrees)]
        * 'spherical':    polar-axis:         right horizontal meridian
                          positive-direction: counter-clockwise
                          fields:             ['theta' (radians), 'rho' (radians)]
        * 'standard':     polar-axis:         right horizontal meridian
                          positive-direction: counter-clockwise
                          fields:             ['angle' (radians), 'eccentricity' (degrees)]
        * 'cartesian':    axes:               x/y correspond to RHM/UVM
                          positive-direction: left/up
                          fields:             ('x' (degrees), 'y' (degrees))
        * 'geographical': axes:               x/y correspond to RHM/UVM
                          positive-direction: left/up
                          fields:             ('longitude' (degrees), 'latitude' (degrees))
        * 'complex':      axes:               x/y correspond to RHM/UVM
                          positive-direction: left/up
                          fields:             longitude (degrees) + I*latitude (degrees)
        * 'complex-rad':  axes:               x/y correspond to RHM/UVM
                          positive-direction: left/up
                          fields:             longitude (radians) + I*latitude (radians)
        * 'visual-rad':   polar-axis:         upper vertical meridian
                          positive-direction: clockwise
                          fields:             ['angle' (radians), 'eccentricity' (radians)]
  
      The following options may be given:
        * units (Ellipsis) specifies the unit that should be assumed (degrees or radians);
          if Ellipsis is given, then auto-detect the unit if possible. This may be a map whose keys
          are 'polar_angle' and 'eccentricity' (or the equivalent titles in data) and whose keys are
          the individual units.
        * prefix (None) specifies a prefix that is required for any keys or property names.
        * suffix (None) specifies a suffix that is required for any keys or property names.
      '''
      # simple sanity check:
      output_style = output_style.lower()
      if output_style not in _retinotopy_style_fns:
          raise ValueError('Unrecognized output style: %s' % output_style)
      # First step: get the retinotopy into a format we can deal with easily
      if is_tuple(data) and len(data) == 2:
          data = {'polar_angle': data[0], 'eccentricity': data[1]}
      if isinstance(data, list):
          data = np.asarray(data)
      if pimms.is_nparray(data):
          if pimms.is_vector(data, np.complexfloating):
              data = {'complex': data}
          else:
              if data.shape[0] != 2: data = data.T
              data = {'polar_angle': data[0], 'eccentricity': data[1]}
      # We now assume that data is a dict type; or is a mesh;
      # figure out the data we have and make it into theta/rho
      if isinstance(data, geo.VertexSet):
          pnames = {k.lower():k for k in six.iterkeys(data.properties)}
          mem_dat = lambda k: k in pnames
          get_dat = lambda k: data.prop(pnames[k])
      else:
          def _make_lambda(data,k): return lambda:data[k]
          data = pimms.lazy_map({k.lower():_make_lambda(data,k) for k in six.iterkeys(data)})
          mem_dat = lambda k: k in data
          get_dat = lambda k: data[k]
      # Check in a particular order:
      suffix = '' if suffix is None else suffix.lower()
      prefix = '' if prefix is None else prefix.lower()
      (angle_key, eccen_key, x_key, y_key, z_key) = [
          next((k for k in aliases if mem_dat(prefix + k + suffix)), None)
          for aliases in [['polar_angle', 'polar angle', 'angle', 'ang', 'polang', 'theta'],
                          ['eccentricity', 'eccen', 'ecc', 'rho'],
                          ['x', 'longitude', 'lon'], ['y', 'latitude', 'lat'],
                          ['z', 'complex', 'complex-rad', 'coordinate']]]
      rad2deg = 180.0 / np.pi
      deg2rad = np.pi / 180.0
      (hpi, dpi) = (np.pi / 2.0, np.pi * 2.0)
      if angle_key and eccen_key:
          akey = prefix + angle_key + suffix
          ekey = prefix + eccen_key + suffix
          theta = np.asarray(get_dat(akey))
          rho   = np.asarray(get_dat(ekey))
          theta = theta * (deg2rad if _default_polar_angle_units[angle_key]  == 'deg' else 1)
          rho   = rho   * (rad2deg if _default_eccentricity_units[eccen_key] == 'rad' else 1)
          if _default_polar_angle_axis[angle_key] == 'UVM': theta = theta - hpi
          if _default_polar_angle_dir[angle_key] == 'cw':   theta = -theta
          ok = np.where(np.isfinite(theta))[0]
          theta[ok[theta[ok] < -np.pi]] += dpi
          theta[ok[theta[ok] >  np.pi]] -= dpi
      elif x_key and y_key:
          (x,y) = [np.asarray(get_dat(prefix + k + suffix)) for k in [x_key, y_key]]
          if _default_x_units[x_key] == 'rad': x *= rad2deg
          if _default_y_units[y_key] == 'rad': y *= rad2deg
          theta = np.arctan2(y, x)
          rho   = np.sqrt(x*x + y*y)
      elif z_key:
          z = get_dat(prefix + z_key + suffix)
          theta = np.angle(z)
          rho   = np.abs(z)
          if _default_z_units[z_key] == 'rad': rho *= rad2deg
      else:
          raise ValueError('could not identify a valid retinotopic representation in data')
      # Now, we just have to convert to the requested output style
      f = _retinotopy_style_fns[output_style]
      return f(theta, rho)