Repository: mne-tools/mne-python

Issue:
List of dict values for Forward object
Would it be possible to document the various values of the Forward object? For example, if `fwd = mne.Forward()` it would be nice to have a succinct list showing the differences between `fwd['src']` and `fwd['src'][0]` and `fwd['src'][0]['vertno']` without having to do a `.__dict__()` search for each level of information.


Discussion:
Hello! 👋 Thanks for opening your first issue here! ❤️ We will try to get back to you soon. 🚴🏽‍♂️

I can take a stab at this in the next few days

Resolving patch:
diff --git a/mne/datasets/_fetch.py b/mne/datasets/_fetch.py
--- a/mne/datasets/_fetch.py
+++ b/mne/datasets/_fetch.py
@@ -185,7 +185,7 @@ def fetch_dataset(
     # return empty string if outdated dataset and we don't want to download
     if (not force_update) and outdated and not download:
         logger.info(
-            'Dataset out of date, force_upload=False, and download=False, '
+            'Dataset out of date but force_update=False and download=False, '
             'returning empty data_path')
         return (empty, data_version) if return_version else empty
 
diff --git a/mne/forward/forward.py b/mne/forward/forward.py
--- a/mne/forward/forward.py
+++ b/mne/forward/forward.py
@@ -54,12 +54,77 @@
 class Forward(dict):
     """Forward class to represent info from forward solution.
 
+    Like :class:`mne.Info`, this data structure behaves like a dictionary.
+    It contains all metadata necessary for a forward solution.
+
+    .. warning::
+        This class should not be modified or created by users.
+        Forward objects should be obtained using
+        :func:`mne.make_forward_solution` or :func:`mne.read_forward_solution`.
+
     Attributes
     ----------
     ch_names : list of str
-        List of channels' names.
+        A convenience wrapper accessible as ``fwd.ch_names`` which wraps
+        ``fwd['info']['ch_names']``.
 
-        .. versionadded:: 0.20.0
+    See Also
+    --------
+    mne.make_forward_solution
+    mne.read_forward_solution
+
+    Notes
+    -----
+    Forward data is accessible via string keys using standard
+    :class:`python:dict` access (e.g., ``fwd['nsource'] == 4096``):
+
+        source_ori : int
+            The source orientation, either ``FIFF.FIFFV_MNE_FIXED_ORI`` or
+            ``FIFF.FIFFV_MNE_FREE_ORI``.
+        coord_frame : int
+            The coordinate frame of the forward solution, usually
+            ``FIFF.FIFFV_COORD_HEAD``.
+        nsource : int
+            The number of source locations.
+        nchan : int
+            The number of channels.
+        sol : dict
+            The forward solution, with entries:
+
+            ``'data'`` : ndarray, shape (n_channels, nsource * n_ori)
+                The forward solution data. The shape will be
+                ``(n_channels, nsource)`` for a fixed-orientation forward and
+                ``(n_channels, nsource * 3)`` for a free-orientation forward.
+            ``'row_names'`` : list of str
+                The channel names.
+        mri_head_t : instance of Transform
+            The mri ↔ head transformation that was used.
+        info : instance of :class:`~mne.Info`
+            The measurement information (with contents reduced compared to that
+            of the original data).
+        src : instance of :class:`~mne.SourceSpaces`
+            The source space used during forward computation. This can differ
+            from the original source space as:
+
+            1. Source points are removed due to proximity to (or existing
+               outside)
+               the inner skull surface.
+            2. The source space will be converted to the ``coord_frame`` of the
+               forward solution, which typically means it gets converted from
+               MRI to head coordinates.
+        source_rr : ndarray, shape (n_sources, 3)
+            The source locations.
+        source_nn : ndarray, shape (n_sources, 3)
+            The source normals. Will be all +Z (``(0, 0, 1.)``) for volume
+            source spaces. For surface source spaces, these are normal to the
+            cortical surface.
+        surf_ori : int
+            Whether ``sol`` is surface-oriented with the surface normal in the
+            Z component (``FIFF.FIFFV_MNE_FIXED_ORI``) or +Z in the given
+            ``coord_frame`` in the Z component (``FIFF.FIFFV_MNE_FREE_ORI``).
+
+    Forward objects also have some attributes that are accessible via ``.``
+    access, like ``fwd.ch_names``.
     """
 
     def copy(self):
diff --git a/mne/source_space.py b/mne/source_space.py
--- a/mne/source_space.py
+++ b/mne/source_space.py
@@ -55,8 +55,14 @@
 class SourceSpaces(list):
     """Represent a list of source space.
 
-    Currently implemented as a list of dictionaries containing the source
-    space information
+    This class acts like a list of dictionaries containing the source
+    space information, one entry in the list per source space type. See
+    Notes for details.
+
+    .. warning::
+        This class should not be created or modified by the end user. Use
+        :func:`mne.setup_source_space`, :func:`mne.setup_volume_source_space`,
+        or :func:`mne.read_source_spaces` to create :class:`SourceSpaces`.
 
     Parameters
     ----------
@@ -68,9 +74,126 @@ class SourceSpaces(list):
 
     Attributes
     ----------
+    kind : str
+        The kind of source space, one of
+        ``{'surface', 'volume', 'discrete', 'mixed'}``.
     info : dict
         Dictionary with information about the creation of the source space
         file. Has keys 'working_dir' and 'command_line'.
+
+    See Also
+    --------
+    mne.setup_source_space : Setup a surface source space.
+    mne.setup_volume_source_space : Setup a volume source space.
+    mne.read_source_spaces : Read source spaces from a file.
+
+    Notes
+    -----
+    Each element in SourceSpaces (e.g., ``src[0]``) is a dictionary. For
+    example, a surface source space will have ``len(src) == 2``, one entry for
+    each hemisphere. A volume source space will have ``len(src) == 1`` if it
+    uses a single monolithic grid, or ``len(src) == len(volume_label)`` when
+    created with a list-of-atlas-labels. A mixed source space consists of both
+    surface and volumetric source spaces in a single SourceSpaces object.
+
+    Each of those dictionaries can be accessed using standard Python
+    :class:`python:dict` access using the string keys listed below (e.g.,
+    ``src[0]['type'] == 'surf'``). The relevant key/value pairs depend on
+    the source space type:
+
+    **Relevant to all source spaces**
+
+    The following are always present:
+
+        id : int
+            The FIF ID, either ``FIFF.FIFFV_MNE_SURF_LEFT_HEMI`` or
+            ``FIFF.FIFFV_MNE_SURF_RIGHT_HEMI`` for surfaces, or
+            ``FIFF.FIFFV_MNE_SURF_UNKNOWN`` for volume source spaces.
+        type : str
+            The type of source space, one of ``{'surf', 'vol', 'discrete'}``.
+        np : int
+            Number of vertices in the dense surface or complete volume.
+        coord_frame : int
+            The coordinate frame, usually ``FIFF.FIFFV_COORD_MRI``.
+        rr : ndarray, shape (np, 3)
+            The dense surface or complete volume vertex locations.
+        nn : ndarray, shape (np, 3)
+            The dense surface or complete volume normals.
+        nuse : int
+            The number of points in the subsampled surface.
+        inuse : ndarray, shape (np,)
+            An integer array defining whether each dense surface vertex is used
+            (``1``) or unused (``0``).
+        vertno : ndarray, shape (n_src,)
+            The vertex numbers of the dense surface or complete volume that are
+            used (i.e., ``np.where(src[0]['inuse'])[0]``).
+        subject_his_id : str
+            The FreeSurfer subject name.
+
+    **Surface source spaces**
+
+    Surface source spaces created using :func:`mne.setup_source_space` can have
+    the following additional entries (which will be missing, or have values of
+    ``None`` or ``0`` for volumetric source spaces):
+
+        ntri : int
+            Number of triangles in the dense surface triangulation.
+        tris : ndarray, shape (ntri, 3)
+            The dense surface triangulation.
+        nuse_tri : int
+            The number of triangles in the subsampled surface.
+        use_tris : ndarray, shape (nuse_tri, 3)
+            The subsampled surface triangulation.
+        dist : scipy.sparse.csr_matrix, shape (n_src, n_src) | None
+            The distances (euclidean for volume, along the cortical surface for
+            surfaces) between source points.
+        dist_limit : float
+            The maximum distance allowed for inclusion in ``nearest``.
+        pinfo : dict
+            Information about the patch of cortex represented by a vertex in
+            the subsampled surface.
+        patch_inds : list of ndarray
+            For each vertex in the subsampled surface, the indices of the
+            vertices in the dense surface that it represents (i.e., is closest
+            to of all subsampled indices).
+        nearest : ndarray, shape (np,)
+            For each vertex on the dense surface, this gives the vertex index
+            on the subsampled surface that it's closest to.
+        nearest_dist : ndarray, shape (np,)
+            The distances corresponding to ``nearest``.
+
+    **Volume source spaces**
+
+    Volume source spaces created using :func:`mne.setup_volume_source_space`
+    can have the following additional entries (which will be missing, or
+    have values of ``None`` or ``0`` for surface source spaces):
+
+        mri_width, mri_height, mri_depth : int
+            The MRI dimensions (in voxels).
+        neighbor_vert : ndarray
+            The 26-neighborhood information for each vertex.
+        interpolator : scipy.sparse.csr_matrix | None
+            The linear interpolator to go from the subsampled volume vertices
+            to the high-resolution volume.
+        shape : tuple of int
+            The shape of the subsampled grid.
+        mri_ras_t : instance of :class:`~mne.transforms.Transform`
+            The transformation from MRI surface RAS (``FIFF.FIFFV_COORD_MRI``)
+            to MRI scanner RAS (``FIFF.FIFFV_MNE_COORD_RAS``).
+        src_mri_t : instance of :class:`~mne.transforms.Transform`
+            The transformation from subsampled source space voxel to MRI
+            surface RAS.
+        vox_mri_t : instance of :class:`~mne.transforms.Transform`
+            The transformation from the original MRI voxel
+            (``FIFF.FIFFV_MNE_COORD_MRI_VOXEL``) space to MRI surface RAS.
+        mri_volume_name : str
+            The MRI volume name, e.g. ``'subjects_dir/subject/mri/T1.mgz'``.
+        seg_name : str
+            The MRI atlas segmentation name (e.g., ``'Left-Cerebellum-Cortex'``
+            from the parameter ``volume_label``).
+
+    Source spaces also have some attributes that are accessible via ``.``
+    access, like ``src.kind``.
     """
 
     def __init__(self, source_spaces, info=None):  # noqa: D102
@@ -709,9 +832,9 @@ def _read_one_source_space(fid, this):
                 tag = read_tag(fid, d.pos)
                 trans = tag.data
                 if trans['from'] == FIFF.FIFFV_MNE_COORD_MRI_VOXEL:
-                    res['vox_mri_t'] = tag.data
+                    res['vox_mri_t'] = trans
                 if trans['to'] == FIFF.FIFFV_MNE_COORD_RAS:
-                    res['mri_ras_t'] = tag.data
+                    res['mri_ras_t'] = trans
 
         tag = find_tag(fid, mri, FIFF.FIFF_MNE_SOURCE_SPACE_INTERPOLATOR)
         if tag is not None:
@@ -1669,7 +1792,7 @@ def _make_discrete_source_space(pos, coord_frame='mri'):
     # Ready to make the source space
     sp = dict(coord_frame=coord_frame, type='discrete', nuse=npts, np=npts,
               inuse=np.ones(npts, int), vertno=np.arange(npts), rr=rr, nn=nn,
-              id=-1)
+              id=FIFF.FIFFV_MNE_SURF_UNKNOWN)
     return sp
 
 
@@ -1721,7 +1844,8 @@ def _make_volume_source_space(surf, grid, exclude, mindist, mri=None,
     rr = np.array([x * grid, y * grid, z * grid]).T
     sp = dict(np=npts, nn=np.zeros((npts, 3)), rr=rr,
               inuse=np.ones(npts, bool), type='vol', nuse=npts,
-              coord_frame=FIFF.FIFFV_COORD_MRI, id=-1, shape=ns)
+              coord_frame=FIFF.FIFFV_COORD_MRI, id=FIFF.FIFFV_MNE_SURF_UNKNOWN,
+              shape=ns)
     sp['nn'][:, 2] = 1.0
     assert sp['rr'].shape[0] == npts
 


Test patch: