Skip to content

Commit bc4a887

Browse files
Add hover to add_annotation (mne-tools#13931)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent e4cb5ab commit bc4a887

8 files changed

Lines changed: 243 additions & 186 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for interactive label browsing using ``hover=True`` in :meth:`mne.viz.Brain.add_annotation`, by `Eric Larson`_.

mne/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ def pytest_configure(config: pytest.Config):
9494
"slowtest: mark a test as slow",
9595
"ultraslowtest: mark a test as ultraslow or to be run rarely",
9696
"pgtest: mark a test as relevant for mne-qt-browser",
97-
"pvtest: mark a test as relevant for pyvistaqt",
97+
# used by PyVista's MNE integration tests (but also useful in some testing):
98+
"pvtest: mark a test as relevant for pyvista",
9899
):
99100
config.addinivalue_line("markers", marker)
100101

mne/label.py

Lines changed: 8 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
_check_fname,
4141
_check_option,
4242
_check_subject,
43+
_import_nibabel,
4344
_validate_type,
4445
check_random_state,
4546
fill_doc,
@@ -2123,87 +2124,6 @@ def _read_annot_cands(dir_name, raise_error=True):
21232124
return cands
21242125

21252126

2126-
def _read_annot(fname):
2127-
"""Read a Freesurfer annotation from a .annot file.
2128-
2129-
Note : Copied from PySurfer
2130-
2131-
Parameters
2132-
----------
2133-
fname : str
2134-
Path to annotation file
2135-
2136-
Returns
2137-
-------
2138-
annot : numpy array, shape=(n_verts)
2139-
Annotation id at each vertex
2140-
ctab : numpy array, shape=(n_entries, 5)
2141-
RGBA + label id colortable array
2142-
names : list of str
2143-
List of region names as stored in the annot file
2144-
2145-
"""
2146-
if not op.isfile(fname):
2147-
dir_name = op.split(fname)[0]
2148-
cands = _read_annot_cands(dir_name)
2149-
if len(cands) == 0:
2150-
raise OSError(
2151-
f"No such file {fname}, no candidate parcellations found in directory"
2152-
)
2153-
else:
2154-
raise OSError(
2155-
f"No such file {fname}, candidate parcellations in "
2156-
"that directory:\n" + "\n".join(cands)
2157-
)
2158-
with open(fname, "rb") as fid:
2159-
n_verts = np.fromfile(fid, ">i4", 1)[0]
2160-
data = np.fromfile(fid, ">i4", n_verts * 2).reshape(n_verts, 2)
2161-
annot = data[data[:, 0], 1]
2162-
ctab_exists = np.fromfile(fid, ">i4", 1)[0]
2163-
if not ctab_exists:
2164-
raise Exception("Color table not found in annotation file")
2165-
n_entries = np.fromfile(fid, ">i4", 1)[0]
2166-
if n_entries > 0:
2167-
length = np.fromfile(fid, ">i4", 1)[0]
2168-
np.fromfile(fid, ">c", length) # discard orig_tab
2169-
2170-
names = list()
2171-
ctab = np.zeros((n_entries, 5), np.int64)
2172-
for i in range(n_entries):
2173-
name_length = np.fromfile(fid, ">i4", 1)[0]
2174-
name = np.fromfile(fid, f"|S{name_length}", 1)[0]
2175-
names.append(name)
2176-
ctab[i, :4] = np.fromfile(fid, ">i4", 4)
2177-
ctab[i, 4] = (
2178-
ctab[i, 0]
2179-
+ ctab[i, 1] * (2**8)
2180-
+ ctab[i, 2] * (2**16)
2181-
+ ctab[i, 3] * (2**24)
2182-
)
2183-
else:
2184-
ctab_version = -n_entries
2185-
if ctab_version != 2:
2186-
raise Exception("Color table version not supported")
2187-
n_entries = np.fromfile(fid, ">i4", 1)[0]
2188-
ctab = np.zeros((n_entries, 5), np.int64)
2189-
length = np.fromfile(fid, ">i4", 1)[0]
2190-
np.fromfile(fid, f"|S{length}", 1) # Orig table path
2191-
entries_to_read = np.fromfile(fid, ">i4", 1)[0]
2192-
names = list()
2193-
for i in range(entries_to_read):
2194-
np.fromfile(fid, ">i4", 1) # Structure
2195-
name_length = np.fromfile(fid, ">i4", 1)[0]
2196-
name = np.fromfile(fid, f"|S{name_length}", 1)[0]
2197-
names.append(name)
2198-
ctab[i, :4] = np.fromfile(fid, ">i4", 4)
2199-
ctab[i, 4] = ctab[i, 0] + ctab[i, 1] * (2**8) + ctab[i, 2] * (2**16)
2200-
2201-
# convert to more common alpha value
2202-
ctab[:, 3] = 255 - ctab[:, 3]
2203-
2204-
return annot, ctab, names
2205-
2206-
22072127
def _get_annot_fname(annot_fname, subject, hemi, parc, subjects_dir):
22082128
"""Get the .annot filenames and hemispheres."""
22092129
if annot_fname is not None:
@@ -2251,6 +2171,7 @@ def read_labels_from_annot(
22512171
parc="aparc",
22522172
hemi="both",
22532173
surf_name="white",
2174+
*,
22542175
annot_fname=None,
22552176
regexp=None,
22562177
subjects_dir=None,
@@ -2295,6 +2216,8 @@ def read_labels_from_annot(
22952216
write_labels_to_annot
22962217
morph_labels
22972218
"""
2219+
nib = _import_nibabel("Reading labels from parcellations")
2220+
22982221
logger.info("Reading labels from parcellation...")
22992222

23002223
subjects_dir = get_subjects_dir(subjects_dir)
@@ -2318,7 +2241,9 @@ def read_labels_from_annot(
23182241
orig_names = set()
23192242
for fname, hemi in zip(annot_fname, hemis):
23202243
# read annotation
2321-
annot, ctab, label_names = _read_annot(fname)
2244+
_check_fname(fname, overwrite="read", must_exist=True, name="annotation file")
2245+
annot, ctab, label_names = nib.freesurfer.io.read_annot(fname, orig_ids=True)
2246+
ctab[:, 3] = 255 - ctab[:, 3]
23222247
label_rgbas = ctab[:, :4] / 255.0
23232248
label_ids = ctab[:, -1]
23242249

@@ -2362,7 +2287,7 @@ def read_labels_from_annot(
23622287
labels = sorted(labels, key=lambda label: label.name)
23632288

23642289
if len(labels) == 0:
2365-
msg = "No labels found."
2290+
msg = f"No labels found in {annot_fname[0]}."
23662291
if regexp is not None:
23672292
orig_names = "\n".join(sorted(orig_names))
23682293
msg += (

mne/tests/test_label.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
_blend_colors,
4242
_load_vert_pos,
4343
_n_colors,
44-
_read_annot,
4544
_read_annot_cands,
4645
label_sign_flip,
4746
select_sources,
@@ -380,7 +379,7 @@ def test_annot_io(tmp_path):
380379
shutil.copy(surf_src / "rh.white", surf_dir)
381380

382381
# read original labels
383-
with pytest.raises(OSError, match="\nPALS_B12_Lobes$"):
382+
with pytest.raises(OSError, match="PALS_B12_Lobesey"):
384383
read_labels_from_annot(subject, "PALS_B12_Lobesey", subjects_dir=tmp_path)
385384
labels = read_labels_from_annot(subject, "PALS_B12_Lobes", subjects_dir=tmp_path)
386385

@@ -486,8 +485,6 @@ def test_read_labels_from_annot(tmp_path):
486485
)
487486
with pytest.raises(OSError, match="does not exist"):
488487
_read_annot_cands("foo")
489-
with pytest.raises(OSError, match="no candidate"):
490-
_read_annot(str(tmp_path))
491488

492489
# read labels using hemi specification
493490
labels_lh = read_labels_from_annot("sample", hemi="lh", subjects_dir=subjects_dir)

mne/utils/misc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,13 @@ def _auto_weakref(function):
497497
__weakref_values__ = dict()
498498
evaldict = dict(__weakref_values__=__weakref_values__)
499499
for name, value in zip(names, function.__closure__):
500-
__weakref_values__[name] = weakref.ref(value.cell_contents)
500+
try:
501+
__weakref_values__[name] = weakref.ref(value.cell_contents)
502+
except TypeError: # pragma: no cover
503+
raise TypeError(
504+
f"Cannot create weak reference to {name} "
505+
f"(type {type(value.cell_contents)})"
506+
)
501507
body = dedent(inspect.getsource(function))
502508
body = body.splitlines()
503509
for li, line in enumerate(body):

0 commit comments

Comments
 (0)