@@ -256,7 +256,8 @@ def check_surface(atlas):
256256 raise ValueError ('Provided GIFTIs do not seem to be valid '
257257 'label.gii files' )
258258 adata .append (data )
259- labs .append (hemi .labeltable .get_labels_as_dict ())
259+ ldict = hemi .labeltable .get_labels_as_dict ()
260+ labs .append ({k : ldict .get (k ) for k in np .unique (data )})
260261
261262 # we need each hemisphere to have unique values so they don't get averaged
262263 # check to see if the two hemispheres have more than 1 overlapping value
@@ -273,7 +274,8 @@ def check_surface(atlas):
273274 return adata , atlas_info
274275
275276
276- def check_atlas (atlas , atlas_info = None , donor = None , data_dir = None ):
277+ def check_atlas (atlas , atlas_info = None , geometry = None , space = None , donor = None ,
278+ data_dir = None ):
277279 """
278280 Checks that `atlas` is a valid atlas
279281
@@ -285,9 +287,14 @@ def check_atlas(atlas, atlas_info=None, donor=None, data_dir=None):
285287 atlas_info : {os.PathLike, pandas.DataFrame, None}, optional
286288 Filepath or dataframe containing information about `atlas`. Must have
287289 at least columns ['id', 'hemisphere', 'structure'] containing
288- information mapping `atlas` IDs to hemisphere (i.e., "L" or "R ") and
290+ information mapping `atlas` IDs to hemisphere (i.e., "L", "R", "B ") and
289291 broad structural class (i.e.., "cortex", "subcortex/brainstem",
290292 "cerebellum", "white matter", or "other"). Default: None
293+ geometry : (2,) tuple-of-GIFTI, optional
294+ Surfaces files defining geometry of `atlas`, if `atlas` is a tuple of
295+ GIFTI images. Default: None
296+ space : {'fsaverage', 'fsnative', 'fslr'}, optional
297+ If `geometry` is supplied, what space files are in. Default: None
291298 donor : str, optional
292299 If specified, indicates which donor the specified `atlas` belongs to.
293300 Only relevant when `atlas` is surface-based, to ensure the correct
@@ -314,21 +321,18 @@ def check_atlas(atlas, atlas_info=None, donor=None, data_dir=None):
314321 coords = triangles = None
315322 except TypeError :
316323 atlas , info = check_surface (atlas )
317- if donor is None :
318- data = fetch_fsaverage5 ()
319- coords = transforms .fsaverage_to_mni152 (
320- np .row_stack ([hemi .vertices for hemi in data ])
321- )
322- else :
323- data = fetch_fsnative (donor , data_dir = data_dir )
324- coords = transforms .fsnative_to_xyz (
325- np .row_stack ([hemi .vertices for hemi in data ]), donor
326- )
327- triangles , offset = [], 0
328- for hemi in data :
329- triangles .append (hemi .faces + offset )
330- offset += hemi .vertices .shape [0 ]
331- triangles = np .row_stack (triangles )
324+ # backwards compatibility for `donor` keyword
325+ if geometry is None and donor is None :
326+ geometry = fetch_fsaverage5 ()
327+ space = 'fsaverage5'
328+ elif geometry is None and donor is not None :
329+ geometry = fetch_fsnative (donor , data_dir = data_dir )
330+ space = 'fsnative'
331+ elif geometry is not None and space is None :
332+ raise ValueError ('If providing geometry files space parameter '
333+ 'must be specified' )
334+ coords , triangles = check_geometry (geometry , space , donor = donor ,
335+ data_dir = data_dir )
332336 if atlas_info is None and info is not None :
333337 atlas_info = info
334338
@@ -340,6 +344,63 @@ def check_atlas(atlas, atlas_info=None, donor=None, data_dir=None):
340344 return atlas
341345
342346
347+ def check_geometry (surface , space , donor = None , data_dir = None ):
348+ """
349+ Loads geometry `surface` files and transforms coordinates in `space`
350+
351+ Parameters
352+ ----------
353+ surface : (2,) tuple-of-GIFTI
354+ Surface geometry files in GIFTI format (lh, rh)
355+ space : {'fsaverage', 'fsnative', 'fslr'}
356+ What space `surface` files are in; used to apply appropriate transform
357+ to MNI152 space. If 'fsnative' then `donor` must be supplied as well
358+ donor : str, optional
359+ If specified, indicates which donor the specified `surface` belongs to
360+ data_dir : str, optional
361+ Directory where donor-specific FreeSurfer data exists (or should be
362+ downloaded and unpacked). Only used if provided `donor` is not None.
363+ Default: $HOME/abagen-data
364+
365+ Returns
366+ -------
367+ coords : (N, 3) np.ndarray
368+ Coordinates from `surface` files
369+ triangles : (T, 3) np.ndarray
370+ Triangles from `surface` files
371+ """
372+
373+ if len (surface ) != 2 :
374+ raise TypeError ('Must provide a tuple of geometry files' )
375+
376+ # fsaverage5, fsaverage6, etc
377+ if 'fsaverage' in space and space != 'fsaverage' :
378+ space = 'fsaverage'
379+ space_opts = ('fsaverage' , 'fsnative' , 'fslr' )
380+ if space not in space_opts :
381+ raise ValueError (f'Provided space must be one of { space_opts } .' )
382+ if space == 'fsnative' and donor is None :
383+ raise ValueError ('Specified space is "fsnative" but no donor ID '
384+ 'supplied' )
385+
386+ try :
387+ coords , triangles = map (list , zip (* [
388+ load_gifti (img ).agg_data () for img in surface
389+ ]))
390+ except TypeError :
391+ coords , triangles = map (list , zip (* [i for i in surface ]))
392+
393+ triangles [- 1 ] += coords [0 ].shape [0 ]
394+ coords , triangles = np .row_stack (coords ), np .row_stack (triangles )
395+
396+ if space == 'fsaverage' :
397+ coords = transforms .fsaverage_to_mni152 (coords )
398+ elif space == 'fsnative' :
399+ coords = transforms .fsnative_to_xyz (coords , donor , data_dir = data_dir )
400+
401+ return coords , triangles
402+
403+
343404def check_atlas_info (atlas_info , labels ):
344405 """
345406 Checks whether provided `atlas_info` is correct format for processing
@@ -461,11 +522,11 @@ def coerce_atlas_to_dict(atlas, donors, atlas_info=None, data_dir=None):
461522 donors = check_donors (donors )
462523 group_atlas = True
463524
464- # FIXME: so that we're not depending on type checks so much :grimacing:
465- if isinstance (atlas , dict ):
525+ try :
466526 atlas = {
467527 WELL_KNOWN_IDS .subj [donor ]: check_atlas (atl , atlas_info ,
468- donor , data_dir )
528+ donor = donor ,
529+ data_dir = data_dir )
469530 for donor , atl in atlas .items ()
470531 }
471532 # if it's a group atlas they should all be the same object
@@ -477,7 +538,7 @@ def coerce_atlas_to_dict(atlas, donors, atlas_info=None, data_dir=None):
477538 f'requested donors. Missing donors: { donors } .' )
478539 LGR .info ('Donor-specific atlases provided; using native coords for '
479540 'tissue samples' )
480- else :
541+ except AttributeError :
481542 atlas = check_atlas (atlas , atlas_info )
482543 atlas = {donor : atlas for donor in donors }
483544 LGR .info ('Group-level atlas provided; using MNI coords for '
0 commit comments