surface_genetic_expression is broken with current versions of
- What is the current behavior?
Following the tutorial using BrainStat in a Jupyter Notebook running through Singularity. In the tutorial, you are supposed to be able to fetch gene expression using
surface_genetic_expression. However, this is broken currently:
2024-06-04 10:34:06.077 (36615.547s) [ C8222740] vtkPythonAlgorithm.cxx:97 ERR| vtkPythonAlgorithm (0x56267e036dd0): Failure when calling method: "ProcessRequest":
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File /opt/conda/lib/python3.11/site-packages/vtk-9.3.20230807rc0-py3.11-linux-x86_64.egg/vtkmodules/util/vtkAlgorithm.py:152, in VTKPythonAlgorithmBase.InternalAlgorithm.ProcessRequest(self, vtkself, request, inInfo, outInfo)
151 def ProcessRequest(self, vtkself, request, inInfo, outInfo):
--> 152 return vtkself.ProcessRequest(request, inInfo, outInfo)
File /opt/conda/lib/python3.11/site-packages/vtk-9.3.20230807rc0-py3.11-linux-x86_64.egg/vtkmodules/util/vtkAlgorithm.py:198, in VTKPythonAlgorithmBase.ProcessRequest(self, request, inInfo, outInfo)
196 return self.RequestUpdateExtent(request, inInfo, outInfo)
197 elif request.Has(vtkDemandDrivenPipeline.REQUEST_DATA()):
--> 198 return self.RequestData(request, inInfo, outInfo)
200 return 1
File /opt/conda/lib/python3.11/site-packages/brainspace-0.1.10-py3.11.egg/brainspace/vtk_interface/io_support/gifti_support.py:123, in vtkGIFTIWriter.RequestData(self, request, inInfo, outInfo)
122 def RequestData(self, request, inInfo, outInfo):
--> 123 _write_gifti(vtkPolyData.GetData(inInfo[0], 0), self.__FileName)
124 return 1
File /opt/conda/lib/python3.11/site-packages/brainspace-0.1.10-py3.11.egg/brainspace/vtk_interface/decorators.py:41, in wrap_input.<locals>._wrapper_decorator.<locals>._wrapper_wrap(*args, **kwds)
38 @functools.wraps(func)
39 def _wrapper_wrap(*args, **kwds):
40 args, kwds = _wrap_input_data(args, kwds, *xargs, skip=skip)
---> 41 data = func(*args, **kwds)
42 return data
File /opt/conda/lib/python3.11/site-packages/brainspace-0.1.10-py3.11.egg/brainspace/vtk_interface/io_support/gifti_support.py:61, in _write_gifti(pd, opth)
58 if not pd.has_only_triangle:
59 raise ValueError('GIFTI writer only accepts triangles.')
---> 61 points = GiftiDataArray(data=pd.Points, intent=INTENT_POINTS)
62 cells = GiftiDataArray(data=pd.GetCells2D(), intent=INTENT_CELLS)
63 # if data is not None:
64 # data_array = GiftiDataArray(data=data, intent=INTENT_POINTDATA)
65 # gii = nb.gifti.GiftiImage(darrays=[points, cells, data_array])
66 # else:
File /opt/conda/lib/python3.11/site-packages/nibabel-5.2.1-py3.11.egg/nibabel/gifti/gifti.py:476, in GiftiDataArray.__init__(self, data, intent, datatype, encoding, endian, coordsys, ordering, meta, ext_fname, ext_offset)
474 datatype = self.data.dtype
475 else:
--> 476 raise ValueError(
477 f'Data array has type {self.data.dtype}. '
478 'The GIFTI standard only supports uint8, int32 and float32 arrays.\n'
479 'Explicitly cast the data array to a supported dtype or pass an '
480 'explicit "datatype" parameter to GiftiDataArray().'
481 )
482 self.datatype = data_type_codes.code[datatype]
483 self.encoding = gifti_encoding_codes.code[encoding]
ValueError: Data array has type float64. The GIFTI standard only supports uint8, int32 and float32 arrays.
Explicitly cast the data array to a supported dtype or pass an explicit "datatype" parameter to GiftiDataArray().
2024-06-04 10:34:06.107 (36615.577s) [ C8222740] vtkExecutive.cxx:729 ERR| vtkCompositeDataPipeline (0x56267e246d40): Algorithm vtkPythonAlgorithm (0x56267e036dd0) returned failure for request: vtkInformation (0x56266eec1ac0)
Debug: Off
Modified Time: 80451
Reference Count: 2
Registered Events: (none)
Request: REQUEST_DATA
FROM_OUTPUT_PORT: -1
ALGORITHM_AFTER_FORWARD: 1
FORWARD_DIRECTION: 0
---------------------------------------------------------------------------
ImageFileError Traceback (most recent call last)
Cell In[72], line 4
2 schaefer_200_fs5 = fetch_parcellation("fsaverage5", "schaefer", 200)
3 surfaces = fetch_template_surface("fsaverage5", join=False)
----> 4 expression = surface_genetic_expression(schaefer_200_fs5, surfaces, space="fsaverage5")
File /opt/conda/lib/python3.11/site-packages/brainstat/context/genetics.py:100, in surface_genetic_expression(labels, surfaces, space, atlas_info, ibf_threshold, probe_selection, donor_probes, lr_mirror, missing, tolerance, sample_norm, gene_norm, norm_matched, norm_structures, region_agg, agg_metric, corrected_mni, reannotated, return_counts, return_donors, return_report, donors, data_dir, verbose, n_proc)
98 name = f.name
99 write_surface(surface, name, otype="gii")
--> 100 surfaces_gii.append(nib.load(name))
101 finally:
102 Path(name).unlink()
File /opt/conda/lib/python3.11/site-packages/nibabel-5.2.1-py3.11.egg/nibabel/loadsave.py:104, in load(filename, **kwargs)
102 raise FileNotFoundError(f"No such file or no access: '{filename}'")
103 if stat_result.st_size <= 0:
--> 104 raise ImageFileError(f"Empty file: '{filename}'")
106 sniff = None
107 for image_klass in all_image_classes:
ImageFileError: Empty file: '/tmp/tmp6aalcznf.gii
I encountered the same issue.
- python 3.11
- brainstat 0.4.2
- brainspace 0.1.10
- abagen 0.1.3
- nibabel 5.2.0
- vtk 9.3.1
This is my code:
from brainstat.context.genetics import surface_genetic_expression
from nilearn import datasets
import numpy as np
destrieux = datasets.fetch_atlas_surf_destrieux()
labels = np.hstack((destrieux['map_left'], destrieux['map_right']))
fsaverage = datasets.fetch_surf_fsaverage()
surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
expression = surface_genetic_expression(labels, surfaces, space='fsaverage')
And this is error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\images.py:352, in check_atlas(atlas, atlas_info, geometry, space, donor, data_dir)
351 try:
--> 352 atlas = check_img(atlas)
353 coords = triangles = None
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\images.py:218, in check_img(img)
217 elif not isinstance(img, nib.spatialimages.SpatialImage):
--> 218 raise TypeError('Provided image must be an existing filepath or a '
219 'pre-loaded niimg-like object')
221 # ensure 3D or squeezable to 3D
TypeError: Provided image must be an existing filepath or a pre-loaded niimg-like object
During handling of the above exception, another exception occurred:
TypeError Traceback (most recent call last)
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\images.py:419, in check_geometry(surface, space, donor, data_dir)
418 try:
--> 419 coords, triangles = map(list, zip(*[
420 load_gifti(img).agg_data() for img in surface
421 ]))
422 except TypeError:
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\images.py:420, in <listcomp>(.0)
418 try:
419 coords, triangles = map(list, zip(*[
--> 420 load_gifti(img).agg_data() for img in surface
421 ]))
422 except TypeError:
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\utils.py:220, in load_gifti(img)
217 elif (isinstance(err, TypeError)
218 and not str(err) == 'stat: path should be string, bytes, os.'
219 'PathLike or integer, not GiftiImage'):
--> 220 raise err
222 return img
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\utils.py:210, in load_gifti(img)
209 try:
--> 210 img = nib.load(img)
211 except (ImageFileError, TypeError) as err:
212 # it's gzipped, so read the gzip and pipe it in
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\nibabel\loadsave.py:96, in load(filename, **kwargs)
82 r"""Load file given filename, guessing at file type
83
84 Parameters
(...)
94 Image of guessed type
95 """
---> 96 filename = _stringify_path(filename)
98 # Check file exists and is not empty
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\nibabel\filename_parser.py:41, in _stringify_path(filepath_or_buffer)
26 """Attempt to convert a path-like object to a string.
27
28 Parameters
(...)
39 https://github.com/pandas-dev/pandas/blob/325dd68/pandas/io/common.py#L131-L160
40 """
---> 41 return pathlib.Path(filepath_or_buffer).expanduser().as_posix()
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\pathlib.py:871, in Path.__new__(cls, *args, **kwargs)
870 cls = WindowsPath if os.name == 'nt' else PosixPath
--> 871 self = cls._from_parts(args)
872 if not self._flavour.is_supported:
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\pathlib.py:509, in PurePath._from_parts(cls, args)
508 self = object.__new__(cls)
--> 509 drv, root, parts = self._parse_args(args)
510 self._drv = drv
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\pathlib.py:493, in PurePath._parse_args(cls, args)
492 else:
--> 493 a = os.fspath(a)
494 if isinstance(a, str):
495 # Force-cast str subclasses to str (issue #21127)
TypeError: expected str, bytes or os.PathLike object, not GiftiImage
During handling of the above exception, another exception occurred:
TypeError Traceback (most recent call last)
Cell In[8], line 10
8 fsaverage = datasets.fetch_surf_fsaverage()
9 surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
---> 10 expression = surface_genetic_expression(labels, surfaces, space='fsaverage')
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\brainstat\context\genetics.py:110, in surface_genetic_expression(labels, surfaces, space, atlas_info, ibf_threshold, probe_selection, donor_probes, lr_mirror, missing, tolerance, sample_norm, gene_norm, norm_matched, norm_structures, region_agg, agg_metric, corrected_mni, reannotated, return_counts, return_donors, return_report, donors, data_dir, verbose, n_proc)
106 # Use abagen to grab expression data.
107 logger.info(
108 "If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
109 )
--> 110 atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
111 expression = get_expression_data(
112 atlas,
113 atlas_info=atlas_info,
(...)
134 n_proc=n_proc,
135 )
137 return expression
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\images.py:366, in check_atlas(atlas, atlas_info, geometry, space, donor, data_dir)
363 elif geometry is not None and space is None:
364 raise ValueError('If providing geometry files space parameter '
365 'must be specified')
--> 366 coords, triangles = check_geometry(geometry, space, donor=donor,
367 data_dir=data_dir)
368 if atlas_info is None and info is not None:
369 atlas_info = info
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\abagen\images.py:423, in check_geometry(surface, space, donor, data_dir)
419 coords, triangles = map(list, zip(*[
420 load_gifti(img).agg_data() for img in surface
421 ]))
422 except TypeError:
--> 423 coords, triangles = map(list, zip(*[i for i in surface]))
425 triangles[-1] += coords[0].shape[0]
426 coords, triangles = np.row_stack(coords), np.row_stack(triangles)
File e:\jupyter_notebook\jupyter_notebook_env\RJX\Lib\site-packages\nibabel\filebasedimages.py:206, in FileBasedImage.__getitem__(self, key)
204 def __getitem__(self, key) -> None:
205 """No slicing or dictionary interface for images"""
--> 206 raise TypeError('Cannot slice image objects.')
TypeError: Cannot slice image objects.
I got the same error of araikes's. Anybody solved this problem?
this bug is because the new check atlas function in abagen has a little bug https://github.com/rmarkello/abagen/issues/235
Summary
Fixed the float64 to float32 dtype issue that was causing GIFTI writing to fail.
Changes
- Cast surface
Pointstofloat32before writing to GIFTI format - Added dtype check to avoid unnecessary conversions
- Make a copy of the surface to avoid modifying the original data
Why
The surface_genetic_expression function was failing because surface coordinate data was in float64 format, but the GIFTI standard only supports uint8, int32, and float32 datatypes. This caused a ValueError when writing surfaces to GIFTI format.
Testing
The fix ensures that surface data is properly converted to GIFTI-compatible format before writing.
Branch
351-surface_genetic_expression-is-broken-with-current-versions-of
Also fixed compatibility issues with abagen 0.1.3 running on Python 3.13 with pandas 2.x and nibabel 5.x.