Skip to content

Commit 5aa2622

Browse files
AgarwalNilayclaude
andcommitted
docs(freesurfer): backfill missing docstrings across the module
Adds numpy-style docstrings (Parameters/Returns) to previously undocumented functions and methods, and Returns sections to functions that already had Parameters but no Returns. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 537f05e commit 5aa2622

1 file changed

Lines changed: 227 additions & 10 deletions

File tree

cortex/freesurfer.py

Lines changed: 227 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ def get_paths(fs_subject, hemi, type="patch", freesurfer_subject_dir=None):
3737
Directory of freesurfer subjects. Defaults to the value for
3838
the environment variable 'SUBJECTS_DIR' (which should be set
3939
by freesurfer)
40+
41+
Returns
42+
-------
43+
path : str
44+
Path template (with an unfilled `{name}` field) for the requested
45+
file type. `None` is returned if `type` does not match any of
46+
'patch', 'surf', 'curv', or 'slim'.
4047
"""
4148
if freesurfer_subject_dir is None:
4249
freesurfer_subject_dir = os.environ['SUBJECTS_DIR']
@@ -113,6 +120,9 @@ def flatten(fs_subject, hemi, patch, freesurfer_subject_dir=None, save_every=Non
113120
114121
Returns
115122
-------
123+
success : bool
124+
True if the user confirmed and `mris_flatten` was run. False if the
125+
user declined the confirmation prompt (in which case nothing is run).
116126
117127
Notes
118128
-----
@@ -352,6 +362,11 @@ def _remove_disconnected_polys(polys):
352362
353363
This filtering is useful to remove disconnected vertices resulting from a
354364
poor surface cut.
365+
366+
Returns
367+
-------
368+
polys : ndarray
369+
The subset of `polys` belonging to the largest connected component.
355370
"""
356371
n_points = np.max(polys) + 1
357372
import scipy.sparse as sp
@@ -388,6 +403,12 @@ def _move_disconnect_points_to_zero(pts, polys):
388403
This cleaning step is useful after _remove_disconnected_polys, to
389404
avoid using this points in boundaries computations (through pts.max(axis=0)
390405
here and there).
406+
407+
Returns
408+
-------
409+
pts : ndarray
410+
`pts`, with the coordinates of any point not referenced by `polys`
411+
set to zero.
391412
"""
392413
mask = np.zeros(len(pts), dtype=bool)
393414
mask[np.unique(polys)] = True
@@ -406,7 +427,19 @@ def make_fiducial(fs_subject, freesurfer_subject_dir=None):
406427

407428

408429
def parse_surf(filename):
409-
"""
430+
"""Parse a freesurfer binary surface file.
431+
432+
Parameters
433+
----------
434+
filename : str
435+
Path to a freesurfer surface file (e.g. `lh.smoothwm`).
436+
437+
Returns
438+
-------
439+
pts : ndarray, shape (n_verts, 3)
440+
Vertex coordinates.
441+
polys : ndarray, shape (n_faces, 3)
442+
Triangle faces, as vertex indices into `pts`.
410443
"""
411444
with open(filename, 'rb') as fp:
412445
#skip magic
@@ -465,15 +498,38 @@ def write_patch(filename, pts, edges=None):
465498

466499

467500
def parse_curv(filename):
468-
"""
501+
"""Parse a freesurfer binary curvature file.
502+
503+
Parameters
504+
----------
505+
filename : str
506+
Path to a freesurfer curvature file (e.g. `lh.curv`).
507+
508+
Returns
509+
-------
510+
curv : ndarray, shape (n_verts,)
511+
Per-vertex scalar value stored in the file (e.g. curvature, sulcal
512+
depth, or thickness, depending on which file was read).
469513
"""
470514
with open(filename, 'rb') as fp:
471515
fp.seek(15)
472516
return np.frombuffer(fp.read(), dtype='>f4').byteswap().view(np.dtype('>f4').newbyteorder('='))
473517

474518

475519
def parse_patch(filename):
476-
"""
520+
"""Parse a freesurfer binary patch file.
521+
522+
Parameters
523+
----------
524+
filename : str
525+
Path to a freesurfer patch file (e.g. `lh.flatten.patch.3d`).
526+
527+
Returns
528+
-------
529+
data : structured ndarray
530+
One record per vertex in the patch, with fields 'vert' (1-indexed
531+
vertex number; negative for boundary/edge vertices), 'x', 'y', 'z'
532+
(the vertex's coordinates in the patch).
477533
"""
478534
with open(filename, 'rb') as fp:
479535
header, = struct.unpack('>i', fp.read(4))
@@ -486,6 +542,18 @@ def parse_patch(filename):
486542

487543
def get_surf(subject, hemi, type, patch=None, flatten_step=None, freesurfer_subject_dir=None):
488544
"""Read freesurfer surface file
545+
546+
Returns
547+
-------
548+
pts : ndarray
549+
Vertex coordinates for this surface (or patch, if `patch` is given).
550+
polys : ndarray
551+
Triangle faces for this surface (or patch).
552+
idx : ndarray
553+
If `type` is "patch", a per-vertex flag array (1 for interior
554+
vertices, -1 for boundary/edge vertices, 0 for vertices not in the
555+
patch). Otherwise, the curvature values for this surface/hemisphere,
556+
as returned by `get_curv`.
489557
"""
490558
if type == "patch":
491559
assert patch is not None
@@ -599,6 +667,14 @@ def get_label(cx_subject, label, fs_subject=None, fs_dir=None, src_subject='fsav
599667
Freesurfer subject directory; None defaults to OS environment variable
600668
hemisphere : list | tuple
601669
670+
Returns
671+
-------
672+
idx : ndarray
673+
Vertex indices (into the pycortex fiducial surface) that have label
674+
values, with right-hemisphere indices offset by the number of left-
675+
hemisphere vertices.
676+
values : ndarray
677+
Label value for each vertex in `idx`.
602678
"""
603679
if fs_dir is None:
604680
fs_dir = os.environ['SUBJECTS_DIR']
@@ -619,6 +695,26 @@ def get_label(cx_subject, label, fs_subject=None, fs_dir=None, src_subject='fsav
619695

620696

621697
def _mri_surf2surf_command(src_subj, trg_subj, input_file, output_file, hemi):
698+
"""Build the `mri_surf2surf` command-line argument list.
699+
700+
Parameters
701+
----------
702+
src_subj : str
703+
Freesurfer name of the source subject.
704+
trg_subj : str
705+
Freesurfer name of the target subject.
706+
input_file : str
707+
Path to the source `.gii` file to resample.
708+
output_file : str
709+
Path to write the resampled `.gii` file to.
710+
hemi : str in ("lh", "rh")
711+
Hemisphere.
712+
713+
Returns
714+
-------
715+
cmd : list of str
716+
Argument list suitable for `subprocess`, e.g. `sp.Popen(cmd)`.
717+
"""
622718
# mri_surf2surf --srcsubject <source subject name> --srcsurfval
623719
# <sourcefile> --trgsubject <target suhject name> --trgsurfval <target
624720
# file> --hemi <hemifield>
@@ -633,6 +729,19 @@ def _mri_surf2surf_command(src_subj, trg_subj, input_file, output_file, hemi):
633729

634730

635731
def _check_datatype(data):
732+
"""Downcast 64-bit dtypes to their 32-bit equivalent for gifti export.
733+
734+
Parameters
735+
----------
736+
data : ndarray
737+
Array whose dtype should be checked.
738+
739+
Returns
740+
-------
741+
dtype : numpy dtype
742+
`np.int32` if `data` is `int64`, `np.float32` if `data` is
743+
`float64`, otherwise `data`'s own dtype unchanged.
744+
"""
636745
dtype = data.dtype
637746
if dtype == np.int64:
638747
return np.int32
@@ -659,7 +768,12 @@ def mri_surf2surf(data, source_subj, target_subj, hemi, subjects_dir=None):
659768
660769
hemi: str in ("lh", "rh")
661770
string indicating hemisphere.
662-
771+
772+
Returns
773+
=======
774+
output_data : ndarray, shape=(n_imgs, n_target_verts)
775+
`data` resampled onto the target subject's vertices.
776+
663777
Notes
664778
=====
665779
Requires path to mri_surf2surf or freesurfer environment to be active.
@@ -709,6 +823,11 @@ def _read_sphere_reg(subject, hemi, subjects_dir=None):
709823
710824
These are the coordinates on which freesurfer's spherical registration
711825
defines the cross-subject vertex correspondence used by ``mri_surf2surf``.
826+
827+
Returns
828+
-------
829+
pts : ndarray, shape (n_verts, 3)
830+
Registered sphere vertex coordinates for this subject/hemisphere.
712831
"""
713832
surf_file = get_paths(subject, hemi, 'surf',
714833
freesurfer_subject_dir=subjects_dir).format(
@@ -882,8 +1001,14 @@ def get_curv(fs_subject, hemi, type='wm', freesurfer_subject_dir=None):
8821001
type : str
8831002
'wm' or other type of surface (e.g. 'fiducial' or 'pial')
8841003
freesurfer_subject_dir : str
885-
directory for Freesurfer subjects (defaults to value for the
1004+
directory for Freesurfer subjects (defaults to value for the
8861005
environment variable $SUBJECTS_DIR if None)
1006+
1007+
Returns
1008+
-------
1009+
curv : ndarray
1010+
Per-vertex curvature (or other surface-info) values for this subject
1011+
and hemisphere.
8871012
"""
8881013
if type == "wm":
8891014
curv_file = get_paths(fs_subject, hemi, 'curv', freesurfer_subject_dir=freesurfer_subject_dir).format(name='')
@@ -909,6 +1034,13 @@ def show_surf(subject, hemi, type, patch=None, curv=True, freesurfer_subject_dir
9091034
curv : bool
9101035
9111036
freesurfer_subject_dir :
1037+
1038+
Returns
1039+
-------
1040+
fig : mayavi figure
1041+
The mayavi figure the surface was plotted into.
1042+
surf : mayavi surface object
1043+
The rendered surface object within `fig`.
9121044
"""
9131045
warnings.warn(('Deprecated and probably broken! Try `cortex.segment.show_surf()`\n'
9141046
'which uses a third-party program (meshlab, available for linux & mac\n'
@@ -952,7 +1084,23 @@ def picker_callback(picker):
9521084
return fig, surf
9531085

9541086
def write_dot(fname, pts, polys, name="test"):
955-
"""
1087+
"""Write a mesh's edge graph out as a Graphviz .dot file.
1088+
1089+
Parameters
1090+
----------
1091+
fname : str
1092+
Path to write the .dot file to.
1093+
pts : ndarray
1094+
Vertex coordinates, used to compute edge lengths.
1095+
polys : ndarray
1096+
Triangle faces defining the mesh's edges.
1097+
name : str, optional
1098+
Name to give the graph in the .dot file. Default is "test".
1099+
1100+
Returns
1101+
-------
1102+
None
1103+
Writes `fname` as a side effect; has no return value.
9561104
"""
9571105
import networkx as nx
9581106
def iter_surfedges(tris):
@@ -975,7 +1123,20 @@ def iter_surfedges(tris):
9751123

9761124

9771125
def read_dot(fname, pts):
978-
"""
1126+
"""Read 2D vertex layout positions back out of a Graphviz .dot file.
1127+
1128+
Parameters
1129+
----------
1130+
fname : str
1131+
Path to a .dot file previously written by `write_dot` and laid out
1132+
by Graphviz.
1133+
pts : array-like
1134+
Only used for its length, to preallocate the output array.
1135+
1136+
Returns
1137+
-------
1138+
data : ndarray, shape (len(pts), 2)
1139+
2D (x, y) layout position for each vertex.
9791140
"""
9801141
import re
9811142
parse = re.compile(r'\s(\d+)\s\[label="", pos="([\d\.]+),([\d\.]+)".*];')
@@ -994,7 +1155,24 @@ def read_dot(fname, pts):
9941155

9951156

9961157
def write_decimated(path, pts, polys):
997-
"""
1158+
"""Decimate a surface mesh and write it out as a freesurfer surface plus
1159+
a matching full-resolution patch file.
1160+
1161+
Parameters
1162+
----------
1163+
path : str
1164+
Path prefix to write to; `.smoothwm` and `.full.patch.3d` suffixes
1165+
are appended for the surface and patch files, respectively.
1166+
pts : ndarray
1167+
Vertex coordinates of the mesh to decimate.
1168+
polys : ndarray
1169+
Triangle faces of the mesh to decimate.
1170+
1171+
Returns
1172+
-------
1173+
None
1174+
Writes `path + '.smoothwm'` and `path + '.full.patch.3d'` as a side
1175+
effect; has no return value.
9981176
"""
9991177
from .polyutils import boundary_edges, decimate
10001178
dpts, dpolys = decimate(pts, polys)
@@ -1013,7 +1191,29 @@ def write_decimated(path, pts, polys):
10131191

10141192

10151193
class SpringLayout(object):
1016-
"""
1194+
"""Relaxes a mesh's vertex positions using a spring-force model, where
1195+
each edge acts as a spring with its rest length set by the distance
1196+
between the corresponding vertices in a (possibly different) reference
1197+
mesh `dpts`.
1198+
1199+
Parameters
1200+
----------
1201+
pts : ndarray
1202+
Initial vertex coordinates to relax.
1203+
polys : ndarray
1204+
Triangle faces defining the mesh's edges/springs.
1205+
dpts : ndarray, optional
1206+
Vertex coordinates used to set each spring's rest (target) length.
1207+
Defaults to `pts`, i.e. the mesh starts at its own rest lengths.
1208+
pins : array-like, optional
1209+
Indices of vertices that should remain fixed (not moved) during
1210+
relaxation.
1211+
stepsize : float, optional
1212+
Step size used to scale vertex movement at each iteration. Default 1.
1213+
neighborhood : int, optional
1214+
Number of times to expand each vertex's neighbor set by one more hop,
1215+
widening the set of springs attached to it. Default 0 (immediate
1216+
mesh neighbors only).
10171217
"""
10181218
def __init__(self, pts, polys, dpts=None, pins=None, stepsize=1, neighborhood=0):
10191219
self.pts = pts
@@ -1102,7 +1302,24 @@ def view_step(self):
11021302
self.figure.mlab_source.set(x=self.pts[:,0], y=self.pts[:,1], z=self.pts[:,2])
11031303

11041304
def stretch_mwall(pts, polys, mwall):
1105-
"""
1305+
"""Stretch the medial wall vertices of an inflated surface out into a
1306+
flat disc, as a starting point for flattening.
1307+
1308+
Parameters
1309+
----------
1310+
pts : ndarray
1311+
Vertex coordinates of the (typically inflated) surface.
1312+
polys : ndarray
1313+
Triangle faces of the surface.
1314+
mwall : array-like
1315+
Indices of the medial wall vertices to stretch out.
1316+
1317+
Returns
1318+
-------
1319+
layout : SpringLayout
1320+
A `SpringLayout` initialized with the medial wall vertices pinned at
1321+
their new, stretched-out positions and the original `pts` as the
1322+
rest-length reference, ready to be relaxed with `.run()`.
11061323
"""
11071324
inflated = pts.copy()
11081325
center = pts[mwall].mean(0)

0 commit comments

Comments
 (0)