Source code for tvb.adapters.uploaders.gifti.parser

# -*- coding: utf-8 -*-
#
#
# TheVirtualBrain-Framework Package. This package holds all Data Management, and 
# Web-UI helpful to run brain-simulations. To use it, you also need to download
# TheVirtualBrain-Scientific Package (for simulators). See content of the
# documentation-folder for more details. See also http://www.thevirtualbrain.org
#
# (c) 2012-2023, Baycrest Centre for Geriatric Care ("Baycrest") and others
#
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this
# program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#   CITATION:
# When using The Virtual Brain for scientific publications, please cite it as explained here:
# https://www.thevirtualbrain.org/tvb/zwei/neuroscience-publications
#
#

"""
.. moduleauthor:: Mihai Andrei <mihai.andrei@codemart.ro>
.. moduleauthor:: Calin Pavel <calin.pavel@codemart.ro>
"""

import os
import nibabel
import numpy
from nibabel.nifti1 import intent_codes, data_type_codes
from tvb.basic.logger.builder import get_logger
from tvb.core.adapters.exceptions import ParseException
from tvb.datatypes.surfaces import CorticalSurface, center_vertices, make_surface
from tvb.datatypes.time_series import TimeSeriesSurface


OPTION_READ_METADATA = "ReadFromMetaData"


[docs]class GIFTIParser(object): """ This class reads content of a GIFTI file and builds / returns a Surface instance filled with details. """ UNIQUE_ID_ATTR = "UniqueID" SUBJECT_ATTR = "SubjectID" ASP_ATTR = "AnatomicalStructurePrimary" DATE_ATTR = "Date" DESCRIPTION_ATTR = "Description" NAME_ATTR = "Name" TIME_STEP_ATTR = "TimeStep" def __init__(self, operation_id): self.logger = get_logger(__name__) self.operation_id = operation_id @staticmethod def _get_meta_dict(data_array): data_array_meta = data_array.meta if data_array_meta is None: return {} return data_array_meta @staticmethod def _is_surface_gifti(data_arrays): return (len(data_arrays) == 2 and intent_codes.code["NIFTI_INTENT_POINTSET"] == data_arrays[0].intent and data_type_codes.code["NIFTI_TYPE_FLOAT32"] == data_arrays[0].datatype and intent_codes.code["NIFTI_INTENT_TRIANGLE"] == data_arrays[1].intent and data_type_codes.code["NIFTI_TYPE_INT32"] == data_arrays[1].datatype) @staticmethod def _is_timeseries_gifti(data_arrays): return (len(data_arrays) > 1 and intent_codes.code["NIFTI_INTENT_TIME_SERIES"] == data_arrays[0].intent and data_type_codes.code["NIFTI_TYPE_FLOAT32"] == data_arrays[0].datatype) def _parse_surface(self, data_arrays, data_arrays_part2, surface_type, should_center): meta_dict = self._get_meta_dict(data_arrays[0]) anatomical_structure_primary = meta_dict.get(self.ASP_ATTR) subject = meta_dict.get(self.SUBJECT_ATTR) title = meta_dict.get(self.NAME_ATTR) # Now try to determine what type of surface we have # If a surface type is not explicitly given we use the type specified in the metadata if surface_type == OPTION_READ_METADATA: surface_type = anatomical_structure_primary if surface_type is None: raise ParseException("Please specify the type of the surface") surface = make_surface(surface_type) if surface is None: raise ParseException("Could not determine surface type! %s" % surface_type) # Now fill TVB data type with metadata if subject is not None: surface.subject = subject if title is not None: surface.title = title surface.zero_based_triangles = True # Now fill TVB data type with geometry data vertices = data_arrays[0].data triangles = data_arrays[1].data vertices_in_lh = len(vertices) # If a second file is present append that data if data_arrays_part2 is not None: # offset the indices offset = len(vertices) vertices = numpy.vstack([vertices, data_arrays_part2[0].data]) triangles = numpy.vstack([triangles, offset + data_arrays_part2[1].data]) if should_center: vertices = center_vertices(vertices) # set hemisphere mask if cortex if isinstance(surface, CorticalSurface): # if there was a 2nd file then len(vertices) != vertices_in_lh surface.hemisphere_mask = numpy.zeros(len(vertices), dtype=numpy.bool_) surface.hemisphere_mask[vertices_in_lh:] = 1 surface.vertices = vertices surface.number_of_vertices = surface.vertices.shape[0] surface.triangles = triangles surface.number_of_triangles = surface.triangles.shape[0] return surface def _parse_timeseries(self, data_arrays): # Create TVB time series to be filled time_series = TimeSeriesSurface() time_series.start_time = 0.0 time_series.sample_period = 1.0 # First process first data_array and extract important data from it's metadata meta_dict = self._get_meta_dict(data_arrays[0]) sample_period = meta_dict.get(self.TIME_STEP_ATTR) time_series.subject = meta_dict.get(self.SUBJECT_ATTR) time_series.title = meta_dict.get(self.NAME_ATTR) if sample_period: time_series.sample_period = float(sample_period) time_series.sample_rate = 1 / time_series.sample_period return time_series, data_arrays # TODO: data_file_part2 should be optional and if it's not given, then it should be None, but it is actually taken as '' from h5 file, so it acts as if it is required
[docs] def parse(self, data_file, data_file_part2=None, surface_type=OPTION_READ_METADATA, should_center=False): """ Parse NIFTI file(s) and returns A Surface or a TimeSeries for it. :param surface_type: one of "Cortex" "Head" "ReadFromMetaData" :param data_file_part2: a file containing the second part of the surface """ self.logger.debug("Start to parse GIFTI file: %s" % data_file) if data_file is None: raise ParseException("Please select GIFTI file which contains data to import") if not os.path.exists(data_file): raise ParseException("Provided file %s does not exists" % data_file) if data_file_part2 is not None and not os.path.exists(data_file_part2): raise ParseException("Provided file part %s does not exists" % data_file_part2) try: gifti_image = nibabel.load(data_file) data_arrays = gifti_image.darrays self.logger.debug("File parsed successfully") if data_file_part2 is not None: data_arrays_part2 = nibabel.load(data_file_part2).darrays else: data_arrays_part2 = None except Exception as excep: self.logger.exception(excep) msg = "File: %s does not have a valid GIFTI format." % data_file raise ParseException(msg) self.logger.debug("Determine data type stored in GIFTI file") # First check if it's a surface if self._is_surface_gifti(data_arrays): # If a second part exists is must be of the same type if data_arrays_part2 is not None and not self._is_surface_gifti(data_arrays_part2): raise ParseException("Second file must be a surface too") return self._parse_surface(data_arrays, data_arrays_part2, surface_type, should_center) elif self._is_timeseries_gifti(data_arrays): return self._parse_timeseries(data_arrays) else: raise ParseException("Could not map data from GIFTI file to a TVB data type")