Skip to content

Python API Reference

subcortex_visualization.plotting.plot_subcortical_data(subcortex_data=None, atlas='aseg_subcortex', value_column='value', hemisphere='L', views=['medial', 'lateral'], line_thickness=1.5, line_color='black', fill_title='values', cmap=None, NA_fill='#cccccc', fill_alpha=1.0, fill_by_significance=False, nonsig_fill_alpha=0.5, vmin=None, vmax=None, midpoint=None, show_legend=True, show_figure=True, fontsize=12, ax=None)

Visualize a given subcortical or cerebellar atlas template as a vector graphic, colored according to user-provided data values or, by default, a simple region-based color scheme.

Parameters:

Name Type Description Default
subcortex_data DataFrame

DataFrame with columns ['region', 'value', 'Hemisphere']. If None, regions will be simply colored based on their assigned index in the corresponding atlas (which is arbitrary).

None
atlas str

The atlas used for the subcortical regions. The default is 'aseg_subcortex'.

'aseg_subcortex'
value_column str

The name of the column in subcortex_data that contains the values to be visualized.

'value'
hemisphere (L, R, both)

Which hemisphere(s) to display. Use 'L' for left, 'R' for right, or 'both' for bilateral plots.

'L'
views list of str

Which faces of the subcortical regions to display. Options include 'medial', 'lateral', 'superior', and 'inferior'. Not applicable to the SUIT cerebellar lobule atlas.

['medial', 'lateral']
line_thickness float or str

Thickness of the outline for each region, or a column name in subcortex_data whose value gives region-specific thickness.

1.5
line_color str

Color of the outline around each subcortical region.

'black'
fill_title str

Label for the colorbar indicating the meaning of the fill values.

"values"
cmap str or Colormap

Colormap used to fill in the regions. Accepts a string name or a Colormap object.

'viridis'
NA_fill str

Color to use for regions with missing data (NaN values).

"#cccccc"
fill_alpha float

Opacity level for the filled regions, between 0 (transparent) and 1 (opaque).

1.0
fill_by_significance bool

If True, adjusts fill_alpha based on significance (e.g., p-values) in the data. Requires a 'p_value' column in subcortex_data.

False
nonsig_fill_alpha float

If fill_by_significance is True, this alpha level is applied to non-significant regions (e.g., p >= 0.05).

0.5
vmin float

Minimum value for colormap normalization. If None, the minimum of the input values is used.

None
vmax float

Maximum value for colormap normalization. If None, the maximum of the input values is used.

None
midpoint float

If provided, uses a diverging colormap centered around this value.

None
show_legend bool

If True, displays a legend or colorbar indicating the mapping of values to colors.

True
show_figure bool

If True, displays the figure using plt.show(). If False, returns the matplotlib Figure object.

True
fontsize int

Font size for the figure text elements.

12
ax Axes

Axes object to plot on. If None, a new figure and axes are created.

None

Returns:

Type Description
Figure or None

The generated figure, if show_figure is False. Otherwise, displays the plot and returns None.

Notes
  • The function loads SVG files and a lookup CSV bundled with the package, which can be found under data/ directory.
  • The input subcortex_data should align with regions defined in the lookup table.
Source code in subcortex_visualization/plotting.py
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
def plot_subcortical_data(subcortex_data=None, atlas='aseg_subcortex', value_column='value', hemisphere='L',  
                          views=['medial', 'lateral'], line_thickness=1.5, line_color='black',
                          fill_title="values", cmap=None, NA_fill="#cccccc", fill_alpha=1.0,
                          fill_by_significance=False, nonsig_fill_alpha=0.5,   
                          vmin=None, vmax=None, midpoint=None, show_legend=True,
                          show_figure=True, fontsize=12, ax=None):

    """
    Visualize a given subcortical or cerebellar atlas template as a vector graphic, colored according to user-provided data values or, by default, a simple region-based color scheme. 

    Parameters
    ----------
    subcortex_data : pandas.DataFrame, optional
        DataFrame with columns ['region', 'value', 'Hemisphere'].
        If None, regions will be simply colored based on their assigned index in the corresponding atlas (which is arbitrary).

    atlas : str, default='aseg_subcortex'
        The atlas used for the subcortical regions. The default is 'aseg_subcortex'.

    value_column : str, default='value'
        The name of the column in `subcortex_data` that contains the values to be visualized.

    hemisphere : {'L', 'R', 'both'}, default='L'
        Which hemisphere(s) to display. Use 'L' for left, 'R' for right, or 'both' for bilateral plots.

    views : list of str, default=['medial', 'lateral']
        Which faces of the subcortical regions to display. Options include 'medial', 'lateral', 'superior', and 'inferior'. 
        Not applicable to the SUIT cerebellar lobule atlas.

    line_thickness : float or str, default=1.5
        Thickness of the outline for each region, or a column name in ``subcortex_data`` whose value gives region-specific thickness.

    line_color : str, default='black'
        Color of the outline around each subcortical region.

    fill_title : str, default="values"
        Label for the colorbar indicating the meaning of the fill values.

    cmap : str or matplotlib.colors.Colormap, default='viridis'
        Colormap used to fill in the regions. Accepts a string name or a Colormap object.

    NA_fill : str, default="#cccccc"
        Color to use for regions with missing data (NaN values).

    fill_alpha : float, default=1.0
        Opacity level for the filled regions, between 0 (transparent) and 1 (opaque).

    fill_by_significance : bool, default=False
        If True, adjusts fill_alpha based on significance (e.g., p-values) in the data. Requires a 'p_value' column in `subcortex_data`.

    nonsig_fill_alpha : float, default=0.5
        If `fill_by_significance` is True, this alpha level is applied to non-significant regions (e.g., p >= 0.05).

    vmin : float, optional
        Minimum value for colormap normalization. 
        If None, the minimum of the input values is used.

    vmax : float, optional
        Maximum value for colormap normalization. 
        If None, the maximum of the input values is used.

    midpoint : float, optional
        If provided, uses a diverging colormap centered around this value.

    show_legend : bool, default=True
        If True, displays a legend or colorbar indicating the mapping of values to colors.

    show_figure : bool, default=True
        If True, displays the figure using `plt.show()`. If False, returns the matplotlib Figure object.

    fontsize : int, default=12
        Font size for the figure text elements.

    ax : matplotlib.axes.Axes, optional
        Axes object to plot on. If None, a new figure and axes are created.

    Returns
    -------
    matplotlib.figure.Figure or None
        The generated figure, if `show_figure` is False. Otherwise, displays the plot and returns None.

    Notes
    -----
    - The function loads SVG files and a lookup CSV bundled with the package, which can be found under `data/` directory.
    - The input `subcortex_data` should align with regions defined in the lookup table.
    """

    # Aliases and backward compatibility
    if "Tian" in atlas:
        atlas = atlas.replace("Tian","Melbourne")

    # Use default of 'both' for SUIT cerebellar atlas
    # Handle colormap
    # If atlas is SUIT, use a specific colormap
    if atlas == 'SUIT_cerebellar_lobule':
        if hemisphere in ['L', 'R']:
            print("Individual-hemisphere visualization is not supported with the SUIT cerebellar lobule atlas. Rendering both hemispheres together, along with the vermis.")
            hemisphere = 'both'

    if cmap is None:
        cmap = 'viridis'  # default colormap

    # Otherwise, use the provided colormap
    if isinstance(cmap, str):
        cmap = matplotlib.colormaps.get_cmap(cmap)

    # If fill_by_significance is True, ensure that 'p_value' column is present in subcortex_data
    if fill_by_significance:
        if subcortex_data is None or 'p_value' not in subcortex_data.columns:
            raise ValueError("fill_by_significance=True requires a 'p_value' column in subcortex_data.")

    try:
        # Load ordering file
        atlas_ordering = pd.read_csv(files("subcortex_visualization").joinpath('data').joinpath(f"{atlas}/{atlas}_{hemisphere}_ordering.csv"))

    except Exception as e:
        try:
            # try appending '_subcortex' if not already present
            atlas = atlas + '_subcortex' 
            atlas_ordering = pd.read_csv(files("subcortex_visualization").joinpath('data').joinpath(f"{atlas}/{atlas}_{hemisphere}_ordering.csv"))
            print(f"Atlas name found with '_subcortex' suffix. Using atlas='{atlas}'.")

        except Exception as e:
            raise FileNotFoundError(f"Could not find atlas '{atlas}'. Please ensure the atlas name is correct and the corresponding data is included in the package.") from e


    # Define atlas data path
    atlas_data_path = files('subcortex_visualization').joinpath('data').joinpath(atlas)

    # Prepare data for plotting
    if subcortex_data is None: 
        atlas_ordering, color_lookup, cmap_colors = _prep_data(atlas_ordering, value_column=value_column, subcortex_data=None, cmap=cmap)
        norm=None

    else: 
        color_lookup=None
        atlas_ordering, norm, vmin, vmax, midpoint = _prep_data(atlas_ordering, value_column=value_column, 
                                                               subcortex_data=subcortex_data, 
                                                               cmap=cmap, vmin=vmin, vmax=vmax, midpoint=midpoint)


    # Generate plot

    # Case 1: Brainstem_Navigator atlas, where all regions are included in the same SVG for each hemisphere/view
    if atlas == 'Brainstem_Navigator':
        fig, axes = _plot_helper(
            atlas_ordering, 
            atlas_data_path,
            value_column=value_column,
            hemisphere=hemisphere,
            subcortex_data=subcortex_data,
            views=views,
            color_lookup=color_lookup,
            cmap=cmap,
            NA_fill=NA_fill,
            norm=norm,
            fill_alpha=fill_alpha,
            fill_by_significance=fill_by_significance,
            nonsig_fill_alpha=nonsig_fill_alpha,
            line_color=line_color,
            line_thickness=line_thickness,
        )
        flat_axes   = [ax for ax in axes.flat if ax.get_visible()]
        legend_ax   = flat_axes[0]
        is_multi_panel = True

    # Case 2: SUIT cerebellar lobule
    elif atlas=='SUIT_cerebellar_lobule':
        svg_path = files("subcortex_visualization").joinpath(
            f"data/{atlas}/{atlas}_{hemisphere}.svg")
        tree = ET.parse(svg_path)
        root = tree.getroot()
        ns   = {'svg': 'http://www.w3.org/2000/svg'}
        ET.register_namespace('', ns['svg'])
        paths = root.findall('.//svg:path', ns)

        if subcortex_data is None:
            fig, ax = _plot_helper_cerebellum(atlas_ordering, paths=paths,
                                    value_column=value_column,
                                    hemisphere=hemisphere,
                                    cmap=cmap,
                                    color_lookup=color_lookup,
                                    NA_fill=NA_fill, 
                                    fill_alpha=fill_alpha,
                                    fill_by_significance=fill_by_significance,
                                    line_color=line_color,
                                    line_thickness=line_thickness,
                                    ax=ax)
        else:
            fig, ax = _plot_helper_cerebellum(atlas_ordering, paths=paths,
                                    value_column=value_column,
                                    hemisphere=hemisphere,
                                    subcortex_data=subcortex_data,
                                    cmap=cmap, 
                                    NA_fill=NA_fill,
                                    norm=norm, 
                                    fill_alpha=fill_alpha,
                                    fill_by_significance=fill_by_significance,
                                    line_color=line_color,
                                    line_thickness=line_thickness,
                                    ax=ax)

        legend_ax      = ax
        flat_axes      = [ax]
        is_multi_panel = False

    # For all other atlases
    else:
        svg_dir=f'{atlas_data_path}/vectors/'

        fig, axes = _plot_helper_individual(atlas_ordering, svg_dir, value_column=value_column, 
            hemisphere=hemisphere, subcortex_data=subcortex_data, 
            views=views, figsize=(8, 8), line_color=line_color, line_thickness=line_thickness,
            color_lookup=color_lookup, cmap=cmap, NA_fill=NA_fill, 
            fill_alpha=fill_alpha, fill_by_significance=fill_by_significance,
            nonsig_fill_alpha=nonsig_fill_alpha,norm=norm)

        # Pick a legend anchor ax
        flat_axes = [ax for ax in axes.flat if ax.get_visible()]
        legend_ax = flat_axes[0]  # or flat_axes[0]
        is_multi_panel = True

    plt.rcParams.update({'font.size': fontsize})
    plt.tight_layout(h_pad=1.5)

    # Add a legend if requested — must come AFTER tight_layout so the
    # colorbar / subplots_adjust reservation is not overwritten.
    if show_legend:

        # 8 columns if both hemispheres, else 4; only exception is for SUIT atlas, which always uses 4 columns
        ncols = 4 if atlas == 'SUIT_cerebellar_lobule' else np.where(hemisphere == 'both', 8, 4)

        # Discrete legend when no data provided, colorbar otherwise
        if subcortex_data is None:
            _add_legend(ax=flat_axes if is_multi_panel else legend_ax,
                       multi_panel=is_multi_panel,
                       fig=fig, value_column=value_column, atlas_ordering=atlas_ordering,
                       cmap_colors=cmap_colors, fill_title=fill_title, ncols=ncols)
        else:
            _add_legend(ax=flat_axes if is_multi_panel else legend_ax,
                       multi_panel=is_multi_panel,
                       fig=fig, value_column=value_column, atlas_ordering=atlas_ordering,
                       cmap=cmap, norm=norm, fill_title=fill_title)

    if show_figure:
        plt.show()
    else:
        return fig

subcortex_visualization.segmentation.parcel_segstats(input_vol, atlas_space='MNI152NLin6Asym', atlas='aseg_subcortex', func_name='Functional map', parc_stat=np.mean, ignore_background=True, background_value=0, interpolation=None)

Extract voxel values from an input volume based on a parcellation atlas and apply a reduction function to each parcel.

Parameters:

Name Type Description Default
input_vol Nifti1Image or str

The input 3D or 4D NIfTI image from which to extract voxel values. Can be a nibabel Nifti1Image object or a file path to a NIfTI image.

required
atlas_space str

The standard space to use for the corresponding atlas. Options include 'MNI152NLin6Asym' (the default) and 'MNI152NLin2009cAsym'.

'MNI152NLin6Asym'
atlas str or list of str

Name(s) of the subcortical atlas/atlases to apply. Default is 'aseg_subcortex', which is the FreeSurfer subcortical segmentation atlas. If multiple atlases are provided, the function will iterate over them and concatenate results.

'aseg_subcortex'
func_name str

A name for the functional map being summarized, used for labeling purposes in the output DataFrame. Default is 'Functional map'.

'Functional map'
parc_stat function

A function like np.mean, np.std, etc. that takes an array of values and returns a single summary statistic (scalar). Default is np.mean. Can also be a list of functions, in which case the output DataFrame will have one row per parcel per summary statistic.

mean
ignore_background bool

If True, the background label (as defined by background_value) is skipped when extracting parcel values.

True
background_value int

Integer label in the parcellation that represents background (non-parcel) voxels.

0
interpolation str or None

If the input volume and atlas have different affines or spatial dimensions, this parameter specifies the interpolation method for resampling the atlas to match the input volume. Options include 'nearest', 'linear', and 'cubic'. If None (default), no resampling is performed and an error will be raised if affines or dimensions do not match.

None

Returns:

Name Type Description
results_df DataFrame

One row per parcel per summary statistic, with columns: 'stat', 'value', 'Atlas', 'Functional_Map', 'region', 'Hemisphere', 'Region_Index'.

Notes
  • Users should ensure that the input volume is in the same standard space as the atlas specified by atlas_space to avoid issues with affine and spatial dimension mismatches. If resampling is necessary, users must specify an interpolation method.
Source code in subcortex_visualization/segmentation.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def parcel_segstats(input_vol, atlas_space='MNI152NLin6Asym', 
                    atlas='aseg_subcortex', func_name='Functional map', parc_stat=np.mean,
                    ignore_background=True, background_value=0, interpolation=None):
    """
    Extract voxel values from an input volume based on a parcellation atlas and apply a reduction function to each parcel.

    Parameters
    ----------
    input_vol : nibabel.nifti1.Nifti1Image or str
        The input 3D or 4D NIfTI image from which to extract voxel values. Can be a nibabel Nifti1Image object or a file path to a NIfTI image.

    atlas_space : str, optional
        The standard space to use for the corresponding atlas. Options include 'MNI152NLin6Asym' (the default) and 'MNI152NLin2009cAsym'.

    atlas : str or list of str, optional
        Name(s) of the subcortical atlas/atlases to apply. Default is 'aseg_subcortex', which is the FreeSurfer subcortical segmentation atlas. If multiple atlases are provided, the function will iterate over them and concatenate results.

    func_name : str, optional
        A name for the functional map being summarized, used for labeling purposes in the output DataFrame. Default is 'Functional map'.

    parc_stat : function, optional
        A function like np.mean, np.std, etc. that takes an array of values and returns a single summary statistic (scalar). Default is np.mean.
        Can also be a list of functions, in which case the output DataFrame will have one row per parcel per summary statistic.

    ignore_background : bool, default=True
        If True, the background label (as defined by ``background_value``) is skipped
        when extracting parcel values.

    background_value : int, default=0
        Integer label in the parcellation that represents background (non-parcel) voxels.

    interpolation : str or None, optional
        If the input volume and atlas have different affines or spatial dimensions, this parameter
        specifies the interpolation method for resampling the atlas to match the input volume.
        Options include 'nearest', 'linear', and 'cubic'. If None (default), no resampling is
        performed and an error will be raised if affines or dimensions do not match.

    Returns
    -------
    results_df : pandas.DataFrame
        One row per parcel per summary statistic, with columns:
        'stat', 'value', 'Atlas', 'Functional_Map', 'region', 'Hemisphere', 'Region_Index'.

    Notes
    -----
    - Users should ensure that the input volume is in the same standard space as the atlas specified by `atlas_space` to avoid issues with affine and spatial dimension mismatches. If resampling is necessary, users must specify an interpolation method.
    """

    if isinstance(atlas, str):
        atlas = [atlas]

    if not isinstance(parc_stat, list):
        parc_stat = [parc_stat]

    # Load in the input volume, if it's a file path
    if isinstance(input_vol, str):
        input_vol = nib.load(input_vol)

    # Find the affine and input data dimensions
    input_vol_affine = input_vol.affine
    input_data = input_vol.get_fdata()

    # Initialize list to hold results dataframes for each atlas
    results_df_list = []

    # Iterate over user-specified atlas(es)
    for this_atlas in atlas:

        if this_atlas == 'Brainstem_Navigator':
            # One NIFTI file per ROI — discover all .nii.gz files in the atlas directory
            atlas_dir = files("subcortex_visualization.atlases").joinpath(f"{atlas_space}/{this_atlas}")
            nifti_files = sorted([f for f in atlas_dir.iterdir() if f.name.endswith('.nii.gz')])

            for roi_idx, nifti_path in enumerate(nifti_files):
                roi_name = nifti_path.name.replace('.nii.gz', '')
                roi_vol = nib.load(nifti_path)
                roi_vol_affine = roi_vol.affine

                if not np.allclose(input_vol_affine, roi_vol_affine):
                    if interpolation is not None:
                        print(f"Resampling ROI '{roi_name}' to match input data affine and dimensions using {interpolation} interpolation...")
                        roi_vol = resample_img(roi_vol, target_affine=input_vol_affine, target_shape=input_data.shape[:3], interpolation=interpolation, force_resample=True, copy_header=True)
                    else:
                        raise ValueError(f"No resampling method was specified. Re-run this function with a specified 'interpolation' argument from the available options for resample_img (including 'nearest', 'cubic', or 'linear') to resample ROI '{roi_name}' to your input data.\nInput data affine:\n{input_vol_affine}\nROI affine:\n{roi_vol_affine}\n")

                roi_data = roi_vol.get_fdata()
                mask = roi_data != 0

                if input_data.shape[:3] != roi_data.shape[:3]:
                    min_shape = np.minimum(input_data.shape[:3], roi_data.shape[:3])
                    input_data_roi = input_data[:min_shape[0], :min_shape[1], :min_shape[2]]
                    mask = mask[:min_shape[0], :min_shape[1], :min_shape[2]]
                else:
                    input_data_roi = input_data

                voxels = input_data_roi[mask]

                if input_data.ndim == 4:
                    voxels = voxels.reshape(-1, input_data.shape[-1])
                    vals = [s(voxels, axis=0) for s in parc_stat]
                else:
                    vals = [s(voxels, axis=0) for s in parc_stat]

                this_lab_df = pd.DataFrame({
                    'stat': [s.__name__ for s in parc_stat],
                    'value': vals
                })

                this_hemi = 'B'
                if roi_name.endswith(('_lh', '-lh', '-L', '_L', '_l')):
                    this_hemi = 'L'
                elif roi_name.endswith(('_rh', '-rh', '-R', '_R', '_r')):
                    this_hemi = 'R'
                elif 'vermis' in roi_name:
                    this_hemi = 'V'

                this_region_clean = re.sub(r'(_lh|-lh|-L|_L|_l|_rh|-rh|-R|_R|_r|-vermis)$', '', roi_name)

                this_lab_df['Atlas'] = this_atlas
                this_lab_df['Functional_Map'] = func_name
                this_lab_df['region'] = this_region_clean
                this_lab_df['Hemisphere'] = this_hemi
                this_lab_df['Region_Index'] = roi_idx + 1

                results_df_list.append(this_lab_df)

        else:
            # Define atlas volume
            this_atlas_volume_path = files("subcortex_visualization.atlases").joinpath(f"{atlas_space}/{this_atlas}/{this_atlas}.nii.gz")
            # Try loading atlas, if that fails, append _subcortex and try again (since some atlases have that suffix in the filename)
            try:
                this_atlas_vol = nib.load(this_atlas_volume_path)
                # Define atlas lookup table (LUT)
                this_atlas_LUT = pd.read_csv(files("subcortex_visualization.atlases").joinpath(f"{atlas_space}/{this_atlas}/{this_atlas}_lookup.csv"), header=None)
            except FileNotFoundError:
                try:
                    this_atlas_volume_path = files("subcortex_visualization.atlases").joinpath(f"{atlas_space}/{this_atlas}/{this_atlas}_subcortex.nii.gz")
                    this_atlas_vol = nib.load(this_atlas_volume_path)
                    Warning(f"Atlas volume file for atlas '{this_atlas}' not found with expected filename '{this_atlas}.nii.gz'. Successfully loaded atlas volume with alternative filename '{this_atlas}_subcortex.nii.gz'. Please check that the atlas volume file in the package directory is named according to one of these two conventions to avoid this warning in the future.")

                    this_atlas_LUT = pd.read_csv(files("subcortex_visualization.atlases").joinpath(f"{atlas_space}/{this_atlas}/{this_atlas}_subcortex_lookup.csv"), header=None)

                except FileNotFoundError:
                    raise FileNotFoundError(f"Atlas volume file not found for atlas '{this_atlas}'. Looked for files named '{this_atlas}.nii.gz' and '{this_atlas}_subcortex.nii.gz' in the subcortex_visualization.atlases package directory. Please check that the atlas name is correct and that the corresponding atlas volume file is present in the package directory.")

            # If first row has 'Region_Name' in any column, then set the column names to the first row and drop the first row from the data
            if this_atlas_LUT.iloc[0].str.contains('Region_Name').any():
                this_atlas_LUT.columns = this_atlas_LUT.iloc[0]
                this_atlas_LUT = this_atlas_LUT.drop(0).reset_index(drop=True)

            # Find affines
            this_atlas_vol_affine = this_atlas_vol.affine

            # Compare affines and raise error if they don't match
            if not np.allclose(input_vol_affine, this_atlas_vol_affine):
                # Affines don't match; resample if interpolation is specified, otherwise raise an error
                Warning(f"Affines of input data and atlas do not match. Atlas affine:\n{this_atlas_vol_affine}\nInput data affine:\n{input_vol_affine}\n")

                # Resample the atlas to match the input volume
                if interpolation is not None:
                    print(f"Resampling atlas '{this_atlas}' to match input data affine and dimensions using {interpolation} interpolation...")
                    this_atlas_vol = resample_img(this_atlas_vol, target_affine=input_vol_affine, target_shape=input_data.shape[:3], interpolation=interpolation, force_resample=True, copy_header=True)
                    this_atlas_vol_affine = this_atlas_vol.affine

                else:
                    raise ValueError(f"No resampling method was specified. Re-run this function with a specified 'interpolation' argument from the available options for resample_img (including 'nearest', 'cubic', or 'linear') to resample the desired atlas volume to your input data.\nInput data affine:\n{input_vol_affine}\nAtlas affine:\n{this_atlas_vol_affine} \n")

            # Extract data
            labels = this_atlas_vol.get_fdata()

            # Round to int — resampled atlases may have fractional label values
            labels = np.round(labels).astype(int)

            # If affines match but spatial dims still differ, it's likely an FOV mismatch — crop to the smaller shape
            if input_data.shape[:3] != labels.shape[:3]:
                Warning(f"Spatial dimensions of input data and atlas do not match. Input data shape: {input_data.shape}\nAtlas labels shape: {labels.shape}\nAttempting to crop atlas labels to match input data dimensions...")
                try:
                    min_shape = np.minimum(input_data.shape[:3], labels.shape[:3])
                    input_data = input_data[:min_shape[0], :min_shape[1], :min_shape[2]]
                    labels = labels[:min_shape[0], :min_shape[1], :min_shape[2]]

                except Exception as e:
                    print(f"Error occurred while attempting to crop atlas labels to match input data dimensions: {e}\nUnable to proceed with extraction.")

            # If affines and data dimensions are compatible, proceed with extraction

            this_atlas_LUT.columns = ['Index', 'Region']
            this_atlas_regions = this_atlas_LUT['Region'].values
            unique_labels = np.unique(labels)

            # skip background if requested, which is the default
            if ignore_background:
                unique_labels = unique_labels[unique_labels != background_value]


            # Iterate over each unique label and extract voxel values
            for i, lab in enumerate(unique_labels):
                # If lab is a str/float, convert to int
                lab = int(lab)
                mask = labels == lab
                voxels = input_data[mask]

                # Handle 4D (time series) vs 3D
                if input_data.ndim == 4:
                    voxels = voxels.reshape(-1, input_data.shape[-1])
                    vals = [s(voxels, axis=0) for s in parc_stat]
                else:
                    vals = [s(voxels, axis=0) for s in parc_stat]

                # One row per summary statistic
                this_lab_df = pd.DataFrame({
                    'stat': [s.__name__ for s in parc_stat],
                    'value': vals
                })

                this_atlas_region_full = this_atlas_regions[i]

                # Infer hemisphere from region name suffix
                this_hemi = 'B'
                if this_atlas_region_full.endswith(('_lh', '-lh', '-L', '_L', '_l', '_LH')):
                    this_hemi = 'L'
                elif this_atlas_region_full.endswith(('_rh', '-rh', '-R', '_R', '_r', '_RH')):
                    this_hemi = 'R'
                elif 'vermis' in this_atlas_region_full:
                    this_hemi = 'V'

                # Strip hemisphere suffix to get the clean region name
                this_region_clean = re.sub(r'(_lh|-lh|-L|_L|_l|_LH|_rh|-rh|-R|_R|_r|_RH|-vermis)$', '', this_atlas_region_full)

                # Add region/atlas metadata to the dataframe
                this_lab_df['Atlas'] = this_atlas
                this_lab_df['Functional_Map'] = func_name
                this_lab_df['region'] = this_region_clean
                this_lab_df['Hemisphere'] = this_hemi
                this_lab_df['Region_Index'] = lab

                # Append this label's results to the list
                results_df_list.append(this_lab_df)

    # Concatenate results from all atlases into a single DataFrame
    results_df = pd.concat(results_df_list, ignore_index=True)

    # Return the dataframe
    return results_df

subcortex_visualization.utils.get_atlas_regions(atlas_name)

Return the names of regions in a given subcortical or cerebellar atlas.

Parameters:

Name Type Description Default
atlas_name str

Name of the subcortical/cerebellar atlas.

required

Returns:

Type Description
np.ndarray or tuple of np.ndarray

For most atlases: a 1-D array of region names ordered by segmentation index. For 'SUIT_cerebellar_lobule': a tuple of (hemisphere_regions, vermis_regions). For 'Brainstem_Navigator': a tuple of (hemisphere_regions, midline_regions).

Source code in subcortex_visualization/utils.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def get_atlas_regions(atlas_name):
    """
    Return the names of regions in a given subcortical or cerebellar atlas.

    Parameters
    ----------
    atlas_name : str
        Name of the subcortical/cerebellar atlas.

    Returns
    -------
    np.ndarray or tuple of np.ndarray
        For most atlases: a 1-D array of region names ordered by segmentation index.
        For 'SUIT_cerebellar_lobule': a tuple of (hemisphere_regions, vermis_regions).
        For 'Brainstem_Navigator': a tuple of (hemisphere_regions, midline_regions).
    """

    # If the atlas is the SUIT cerebellar lobules, use hemisphere of 'both'
    if atlas_name == 'SUIT_cerebellar_lobule':
        hemisphere = 'both'

        # Load ordering file
        atlas_ordering = pd.read_csv(files("subcortex_visualization").joinpath(f"data/{atlas_name}/{atlas_name}_{hemisphere}_ordering.csv"))

        # Identify regions for left/right cerebellar hemispheres versus vermis
        hemisphere_regions = atlas_ordering.query("Hemisphere == 'L'").sort_values('seg_index').region.unique()
        vermis_regions = atlas_ordering.query("Hemisphere=='V'").sort_values('seg_index').region.unique()

        # Return both lists of regions as a tuple
        return (hemisphere_regions, vermis_regions)

    # If atlas is Brainstem_Navigator, some regions are 'B' (both) and some regions are 'L' or 'R'
    if atlas_name == 'Brainstem_Navigator':
        # Load ordering file
        atlas_ordering = pd.read_csv(files("subcortex_visualization").joinpath(f"data/{atlas_name}/{atlas_name}_L_ordering.csv"))

        # Sort by segmentation index and print the array of region names
        hemisphere_regions = atlas_ordering.query("Hemisphere=='L'").sort_values('seg_index').region.unique()
        midline_regions = atlas_ordering.query("Hemisphere=='B'").sort_values('seg_index').region.unique()

        # Return both lists of regions as a tuple
        return (hemisphere_regions, midline_regions)

    # Else, use left hemisphere just to get names
    else:
        hemisphere='L'

        # Load ordering file
        atlas_ordering = pd.read_csv(files("subcortex_visualization").joinpath(f"data/{atlas_name}/{atlas_name}_{hemisphere}_ordering.csv"))

        # Sort by segmentation index and print the array of region names
        unique_regions = atlas_ordering.sort_values('seg_index').region.unique()

        return unique_regions