docs: def map_projection(name=None, chirality=Ellipsis,
                     center=Ellipsis, center_right=Ellipsis, radius=Ellipsis,
                     method=Ellipsis, registration=Ellipsis, sphere_radius=Ellipsis,
                     pre_affine=Ellipsis, post_affine=Ellipsis, meta_data=Ellipsis, remember=False):
      '''
      map_projection(name, hemi) yields the map projection with the given name if it exists; hemi must
        be either 'lh', 'rh', or 'lr'/None.
      map_projection(name, topo) yields a map projection using the given topology object topo to
        determine the hemisphere and assigning to the resulting projection's 'mesh' parameter the
        appropriate registration from the given topology.
      map_projection(name, mesh) uses the given mesh; the mesh's meta-data must specify the hemisphere
        for this to work--otherwise 'lr' is always used as the hemisphere.
      map_projection(affine, hemi) creates a map projection from the given affine matrix, which must
        align a set of spherical coordinates to a new set of 3D coordinates that are used as input to
        the method argument (default method 'orthographic' uses the first two of these coordinates as
        the x and y values of the map).
      map_projection() creates a new map projection using the optional arguments, if provided.
  
      All options that can be passed to load_map_projection and MapProjection can be passed to
      map_projection:
        * name is the first optional parameter appearing as name and affine above.
        * chirality is the second optional parameter, and, if set, will ensure that the resulting
          map projection's chirality is equivalent to the given chirality.
        * center specifies the 3D vector that points toward the center of the map.
        * center_right specifies the 3D vector that points toward any point on the positive x-axis
          of the resulting map.
        * radius specifies the radius that should be assumed by the model in radians of the
          cortical sphere; if the default value (Ellipsis) is given, then pi/3.5 is used.
        * method specifies the projection method used (default: 'equirectangular').
        * registration specifies the registration to which the map is aligned (default: 'native').
        * chirality specifies whether the projection applies to left or right hemispheres.
        * sphere_radius specifies the radius of the sphere that should be assumed by the model.
          Note that in Freesurfer, spheres have a radius of 100.
        * pre_affine specifies the pre-projection affine transform to use on the cortical sphere. Note
          that if the first (name) argument is provided as an affine transform, then that transform is
          applied after the pre_affine but before alignment of the center and center_right points.
        * post_affine specifies the post-projection affine transform to use on the 2D map.
        * meta_data specifies any additional meta-data to attach to the projection.
        * remember may be set to True to indicate that, after the map projection is constructed, the
          map_projection cache of named projections should be updated with the provided name. This
          can only be used when the provided name or first argument is a string.
      '''
      global map_projections # save flag lets us modify this
      # make a dict of the map parameters:
      kw = dict(center=center, center_right=center_right, radius=radius,
                method=method, registration=registration, sphere_radius=sphere_radius,
                pre_affine=pre_affine, post_affine=post_affine, meta_data=meta_data)
      kw = {k:v for (k,v) in six.iteritems(kw) if v is not Ellipsis}
      # interpret the hemi argument first
      hemi = chirality
      if hemi is None or hemi is Ellipsis:
          hemi = 'lr'
          topo = None
          mesh = None
      if pimms.is_str(hemi):
          hemi = to_hemi_str(hemi)
          topo = None
          mesh = None
      elif is_topo(hemi):
          topo = hemi
          hemi = hemi.chirality
          mesh = None
      elif is_mesh(hemi):
          mesh = hemi
          hemi = deduce_chirality(hemi)
          topo = None
      else: raise ValueError('Could not understand map_projection hemi argument: %s' % hemi)
      hemi = to_hemi_str(hemi)
      # name might be an affine matrix
      try:              aff = to_affine(name)
      except Exception: aff = None
      if pimms.is_matrix(aff):
          # see if this is an affine matrix
          aff = np.asarray(aff)
          (n,m) = aff.shape
          mtx = None
          # might be a transformation into 2-space or into 3-space:
          if n == 2:
              if   m == 3: mtx = np.vstack([np.hstack([mtx, [[0,0,0],[0,0,0]]]), [[0],[0],[0],[1]]])
              elif m == 4: mtx = np.vstack([mtx, [[0,0,0,0], [0,0,0,1]]])
          elif n == 3:
              if   m == 3: mtx = np.vstack([np.hstack([mtx, [[0,0,0]]]), [[0],[0],[0],[1]]])
              elif m == 4: mtx = np.vstack([mtx, [[0,0,0,1]]])
          elif n == 4:
              if   m == 4: mtx = aff
          if mtx is None: raise ValueError('Invalid affine matrix shape; must be {2,3}x{3,4} or 4x4')
          # Okay, since center and center-right ignore the pre-affine matrix, we can just use this as
          # the pre-affine and set the center to whatever comes out
          kw['pre_affine'] = mtx if kw.get('pre_affine') is None else mtx.dot(to_affine(pre_affine))
          name = None
      # load name if it's a string
      if pimms.is_str(name):
          if   name         in map_projections[hemi]: mp = map_projections[hemi][name]
          elif name.lower() in map_projections[hemi]: mp = map_projections[hemi][name.lower()]
          else:
              try: mp = load_map_projection(name, chirality=hemi)
              except Exception:
                  raise ValueError('could neither find nor load projection %s (%s)' % (name,hemi))
          # update parameters if need-be:
          if len(kw) > 0: mp = mp.copy(**kw)
      elif name is None:
          # make a new map_projection
          if chirality is not Ellipsis: kw['chirality'] = hemi
          mp = MapProjection(**kw)
      elif is_map_projection(name):
          # just updating an existing projection
          if chirality is not Ellipsis: kw['chirality'] = hemi
          mp = name
          if len(kw) > 0: mp = mp.copy(**kw)
      else: raise ValueError('first argument must be affine, string, or None')
      # if we have a topology/mesh, we should add it:
      if topo is not None: mesh = mp.extract_mesh(topo)
      if mesh is not None: mp   = mp.copy(mesh=mesh)
      # if save was requested, save it
      mp = mp.persist()
      if remember is True:
          if pimms.is_str(name):
              name = name.lower()
              hval = map_projections.get(hemi, pyr.m())
              map_projections = map_projections.set(hemi, hval.set(name, mp))
          else: warnings.warn('Cannot save map-projection with non-string name')
      # okay, return the projection
      return mp