docs: def retinotopy_data(m, source='any'):
      '''
      retinotopy_data(m) yields a dict containing a retinotopy dataset with the keys 'polar_angle',
        'eccentricity', and any other related fields for the given retinotopy type; for example,
        'pRF_size' and 'variance_explained' may be included for measured retinotopy datasets and
        'visual_area' may be included for atlas or model datasets. The coordinates are always in the
        'visual' retinotopy style, but can be reinterpreted with as_retinotopy.
      retinotopy_data(m, source) may be used to specify a particular source for the data; this may be
        either 'empirical', 'model', or 'any'; or it may be a prefix/suffix beginning/ending with
        an _ character.
      '''
      if pimms.is_map(source):
          if all(k in source for k in ['polar_angle', 'eccentricity']): return source
      if geo.is_vset(m): return retinotopy_data(m.properties, source=source)
      source = source.lower()
      model_rets = ['predicted', 'model', 'template', 'atlas', 'inferred']
      empir_rets = ['empirical', 'measured', 'prf', 'data']
      wild = False
      extra_fields = {'empirical':('radius','variance_explained'), 'model':('radius','visual_area')}
      check_fields = []
      if source in empir_rets:
          fixes = empir_rets
          check_fields = extra_fields['empirical']
      elif source in model_rets:
          fixes = model_rets
          check_fields = extra_fields['model']
      elif source in ['any', '*', 'all']:
          fixes = model_rets + empir_rets
          check_fields = extra_fields['model'] + extra_fields['empirical']
          wild = True
      elif source in ['none', 'basic']:
          fixes = []
          check_fields = extra_fields['model'] + extra_fields['empirical']
          wild = True
      else: fixes = []
      # first, try all the fixes as prefixes then suffixes
      (z, prefix, suffix) = (None, None, None)
      if wild:
          try: z = as_retinotopy(m, 'visual')
          except Exception: pass
      for fix in fixes:
          if z: break
          try:
              z = as_retinotopy(m, 'visual', prefix=(fix + '_'))
              prefix = fix + '_'
          except Exception: pass
      for fix in fixes:
          if z: break
          try:
              z = as_retinotopy(m, 'visual', suffix=('_' + fix))
              suffix = fix + '_'
          except Exception: pass
      # if none of those worked, try with no prefix/suffix
      if not z:
          try:
              pref = source if source.endswith('_') else (source + '_')
              z = as_retinotopy(m, 'visual', prefix=pref)
              prefix = pref
              check_fields = extra_fields['model'] + extra_fields['empirical']
          except Exception:
              raise
              try:
                  suff = source if source.startswith('_') else ('_' + source)
                  z = as_retinotopy(m, 'visual', suffix=suff)
                  suffix = suff
                  check_fields = extra_fields['model'] + extra_fields['empirical']
              except Exception: pass
      # if still not z... we couldn't figure it out
      if not z: raise ValueError('Could not find an interpretation for source %s' % source)
      # okay, we found it; make it into a dict
      res = {'polar_angle': z[0], 'eccentricity': z[1]}
      # check for extra fields if relevant
      pnames = {k.lower():k for k in m} if check_fields else {}
      for fname in set(check_fields):
          for (aliases, trfn) in retinotopic_property_aliases.get(fname, []):
              if trfn is None: trfn = lambda x:x
              for f in aliases:
                  if prefix: f = prefix + f
                  if suffix: f = f + suffix
                  f = f.lower()
                  if f in pnames:
                      res[fname] = trfn(m[pnames[f]])
                      trfn = None
                      break
              if trfn is None: break
      # That's it
      return res